【Unity Shader】(7)渐变纹理 控制漫反射光照效果 (动漫风、插画风的理论)

在Unity Shader中实现渐变纹理,并控制漫反射光照效果

回顾我们之前写过的基础纹理贴图,我们设置了一个固定的“BaseColor”来控制材质球的基底颜色。那有没有一种可能,让我们的材质球,根据不同阴影部分或者顶点信息,来改变我们的基地颜色呢?——答案是渐变纹理

渐变纹理

渐变纹理最早出现在Gooch等人在1998年发表的一片著名的论文《A Non-Photorealistic Lighting Model For Automatic Technical illustration》提出,作者提出了一种给予冷到暖色调(Cool-To-Warm Tones)的着色技术,来营造一种插画风格的渲染效果。

1
可以看出,上面三图是分别对于各自的渐变纹理进行的效果展示。最右侧的便是卡通风格的渐变纹理——该渐变纹理中的色点是突变的,且没有平滑的颜色过渡,以此来模拟卡通中的阴影色块。

纹理的选择

对于纹理的选择,我们这里并不是单纯的使用三个颜色来直接更改我们的颜色过渡。而是使用纹理的形式(将在后面使用颜色调整来实现)。而我们也知道,图片纹理的类型Wrap Type可以分为如下几种:
6
对于我们的渐变纹理,这里建议使用Clamp,而不是Repeat。
因为在接下来的代码,我们会使用fixed2(halfLambert,halfLambert)来对渐变纹理进行采样,但是使用Repeat将会超出[0,1]的范围,比如采样取值为1.0001后,经过[0,1]截取,就会变成0.0001,但是使用0.0001的值,将会得到纹理最左侧的颜色。而使用Clamp却不会发生这种事情。



代码实现

①在Properties之中声明如下属性:

Properties{
    
    
	_Color("Base Color",Color) = (1,1,1,1)
	_RampTex("Ramp Tex",2D) = "white"{
    
     }
	_Specular("Specular",Color) = (1,1,1,1)
	_Gloss("Gloss",Range(8.0,256)) = 20
}

其中RampTex就是用来存放我们的渐变纹理的,原理与基础纹理是类似的。

②声明Pass的光照模式

SubShader{
    
    
	Pass{
    
    
		Tags{
    
    "LightMode"="ForwardBase"}

③声明顶点着色器和片元着色器,再包含我们需要的Unity内置文件

#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"

④设置我们在Properties之中的变量类型

fixed4 _Color;
sampler2D _RampTex;
float4 _RampTex_ST;
fixed4 _Specular;
float _Gloss;

⑤声明顶点着色器的输入和输出结构体:

struct a2v {
    
    
	float4 vertex: POSITION;
	float3 normal: NORMAL;
	float4 texcoord: TEXCOORD0;
};

struct v2f {
    
    
	float4 pos : SV_POSITION;
	float3 worldNormal : TEXCOORD0;
	float3 worldPos : TEXCOORD1;
	float2 uv : TEXCOORD2;
};

⑥定义顶点着色器

v2f vert(a2v v) {
    
    
	v2f o;
	//将形参的v得到的POSITION顶点信息,传入到v2f类型的o之中的pos作为片元位置信息
	o.pos = UnityObjectToClipPos(v.vertex);
	//将v的法线信息从物体坐标转换成世界左边传入到o的
	o.worldNormal = UnityObjectToWorldNormal(v.normal);
	//使用mul矩阵运算,将v的顶点信息,转换为世界坐标,然后将每个顶点的xyz信息存储到o的worldPos(TEXCOORD类型)之中
	o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
	//使用Unity内置的TRANSFORM_TEX宏来计算经过平铺和偏移后的纹理坐标
	o.uv = TRANSFORM_TEX(v.texcoord, _RampTex);

	return o;
	//处理完毕,将处理完的信息传入流水线下一流程——也就是传入frag之中v2f 类型的形参 i
}

⑦定义片元着色器

fixed4 frag(v2f i) : SV_Target{
    
    
	//用worldNormal存放世界坐标下的顶点方向的信息
	fixed3 worldNormal = normalize(i.worldNormal);
	//用worldLightDir存放世界坐标下的灯光方向的信息
	fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
	//获得周围的光源信息的xyz位置坐标。这里是非常理想的环境下,因为我们只有一个平行光且没有Skybox的光,
	fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
	//使用半兰伯特法,计算光照信息—— 0.5×( n · I ) + 0.5
	fixed halfLambert = 0.5 * dot(worldNormal, worldLightDir) + 0.5;
	fixed3 diffuseColor = tex2D(_RampTex, fixed2(halfLambert, halfLambert)).rgb * _Color.rgb;
	fixed3 diffuse = _LightColor0.rgb * diffuseColor;

	//视线 v 
	fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
	fixed3 halfDir = normalize(worldLightDir + viewDir);
	fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);

	return fixed4(ambient + diffuse + specular, 1.0);
}

⑧完整代码:


// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

Shader"LeonShader/Shader_7_3_RampTexture"{
    
    
	Properties{
    
    
		_Color("Base Color",Color) = (1,1,1,1)
		_RampTex("Ramp Tex",2D) = "white"{
    
     }
		_Specular("Specular",Color) = (1,1,1,1)
		_Gloss("Gloss",Range(8.0,256)) = 20
	}
	SubShader{
    
    
		Pass{
    
    
			Tags{
    
    "LightMode"="ForwardBase"}

			CGPROGRAM

			#pragma vertex vert
			#pragma fragment frag
			#include "Lighting.cginc"
		
			fixed4 _Color;
			sampler2D _RampTex;
			float4 _RampTex_ST;
			fixed4 _Specular;
			float _Gloss;

			struct a2v {
    
    
				float4 vertex: POSITION;
				float3 normal: NORMAL;
				float4 texcoord: TEXCOORD0;
			};

			struct v2f {
    
    
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
				float2 uv : TEXCOORD2;
			};

			v2f vert(a2v v) {
    
    
				v2f o;
				//将形参的v得到的POSITION顶点信息,传入到v2f类型的o之中的pos作为片元位置信息
				o.pos = UnityObjectToClipPos(v.vertex);
				//将v的法线信息从物体坐标转换成世界左边传入到o的
				o.worldNormal = UnityObjectToWorldNormal(v.normal);
				//使用mul矩阵运算,将v的顶点信息,转换为世界坐标,然后将每个顶点的xyz信息存储到o的worldPos(TEXCOORD类型)之中
				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
				//使用Unity内置的TRANSFORM_TEX宏来计算经过平铺和偏移后的纹理坐标
				o.uv = TRANSFORM_TEX(v.texcoord, _RampTex);

				return o;
				//处理完毕,将处理完的信息传入流水线下一流程——也就是传入frag之中v2f 类型的形参 i
			}

			fixed4 frag(v2f i) : SV_Target{
    
    
				//用worldNormal存放世界坐标下的顶点方向的信息
				fixed3 worldNormal = normalize(i.worldNormal);
				//用worldLightDir存放世界坐标下的灯光方向的信息
				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
				//获得周围的光源信息的xyz位置坐标。这里是非常理想的环境下,因为我们只有一个平行光且没有Skybox的光,
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
				//使用半兰伯特法,计算光照信息—— 0.5×( n · I ) + 0.5
				fixed halfLambert = 0.5 * dot(worldNormal, worldLightDir) + 0.5;
				fixed3 diffuseColor = tex2D(_RampTex, fixed2(halfLambert, halfLambert)).rgb * _Color.rgb;
				fixed3 diffuse = _LightColor0.rgb * diffuseColor;

				//视线 v 
				fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
				fixed3 halfDir = normalize(worldLightDir + viewDir);
				fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);

				return fixed4(ambient + diffuse + specular, 1.0);
			}



			ENDCG
		}
	}
	FallBack "Specular"
}

最终效果

6

猜你喜欢

转载自blog.csdn.net/weixin_46840974/article/details/124080692