【Shader入门精要】第十四章——卡通风格的渲染

一、卡通风格的渲染(渐变纹理漫反射+分块的高光反射处理) 


Shader "MilkShader/14/ToonShading"
{
	Properties
	{
		_Color("Color Tint", Color) = (1,1,1,1)
		_MainTex ("Main Tex", 2D) = "white"{}
		_Ramp("Ramp Texture", 2D) = "white"{} //漫反射渐变纹理
		_Outline("Outline", Range(0,1)) = 0.1 //轮廓线宽度
		_OutlineColor("Outline Color", Color) = (1,1,1,1) 
		_Specular ("Specular", Color) = (1,1,1,1)  //高光反射颜色
		_SpecularScale ("Specular Scale", Range(0, 0.1)) = 0.01 //阈值
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" "Queue"="Geometry"}
		LOD 100

		Pass
		{
		//第一个PASS是用法线进行向外扩展,然后只渲染背面,颜色输出是边缘颜色
			NAME "OUTLINE"
			Cull Front

			CGPROGRAM

			#pragma vertex vert
			#pragma fragment frag

			#include "UnityCG.cginc"

			float _Outline;
			fixed4 _OutlineColor;

			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			}; 
			
			struct v2f {
			    float4 pos : SV_POSITION;
			};

			v2f vert(a2v v){
				v2f o;
				//将顶点和法线转到视角空间下
				float4 pos = mul(UNITY_MATRIX_MV, v.vertex);
				float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);  
				//设置法线的z分量,对其归一化后在将顶点沿其方向扩张
				//设置法线z的处理是为了尽可能避免背面扩张(现在我们渲染的是背面)后的顶点挡住正面的面片.
				normal.z = -0.5;
				//在视角空间下进行的一个顶点位置偏移(向法线方向)
				pos = pos + float4(normalize(normal), 0) * _Outline;
				//再转为裁剪空间
				o.pos = mul(UNITY_MATRIX_P, pos);
				return o;
			}
			float4 frag(v2f i) : SV_Target{
				//直接输出边缘颜色	
				return float4(_OutlineColor.rgb, 1);
			}
			ENDCG
		}

		Pass{
			//前向渲染
			Tags{"LightMode"="ForwardBase"}

			Cull Back

			CGPROGRAM
			
			#include "UnityCG.cginc"
			#include "Lighting.cginc"//漫反射、高光反射需要的
			#include "AutoLight.cginc"//这是阴影三剑客需要的头文件
			#include "UnityShaderVariables.cginc"

			#pragma vertex vert
			#pragma fragment frag

			#pragma multi_compile_fwdbase

			sampler2D _MainTex;
			float4 _MainTex_ST;
			fixed4 _Color;
			sampler2D _Ramp;
			fixed4 _Specular;
			fixed _SpecularScale;

			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float4 texcoord : TEXCOORD0;
				//float4 tangent : TANGENT; 为啥例子会有tangent
			}; 
		
			struct v2f {
				float4 pos : POSITION;
				float2 uv : TEXCOORD0;
				float3 worldNormal : TEXCOORD1;
				float3 worldPos : TEXCOORD2;
				SHADOW_COORDS(3)
			};

			v2f vert (a2v v) {
				v2f o;

				o.pos = UnityObjectToClipPos( v.vertex);
				o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);				
				o.worldNormal  = UnityObjectToWorldNormal(v.normal);
				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
				
				TRANSFER_SHADOW(o);
				return o;
			}

			float4 frag(v2f i) :SV_Target{
			 	 fixed3 worldNormal = normalize(i.worldNormal);
				 fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
				 fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
				 fixed3 worldHalfDir = normalize(worldLightDir + worldViewDir);

				 fixed4 c = tex2D (_MainTex, i.uv);
				 fixed3 albedo = c.rgb * _Color.rgb;

				 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
				 UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);				 				

				 fixed diff = dot(worldNormal, worldLightDir);
				 diff = (diff * 0.5 + 0.5) * atten; //先进行转为(0,1)范围,再乘以衰减值*阴影 
				 //这里的漫反射是用渐变纹理方式,diff作为纹理坐标进行采样,通过这种方式采样出的漫反射颜色是卡通化的
				 fixed3 diffuse = _LightColor0.rgb * albedo * tex2D(_Ramp, float2(diff, diff)).rgb;

				 fixed spec = dot(worldNormal, worldHalfDir);
				 fixed w = fwidth(spec) * 2.0;//抗锯齿处理? 这种获取w阈值绝对值的方式很懵逼
				 //下面lerp(...)是比较复杂的,先假设_SpecularScale为0.5,  spec是(0,1)的一个动态数值根据halfDir和normal而变化
				 // spec + _SpecularScale - 1 是将 spec从(0,1)转为(-(1-_SpecularScale), _SpecularScale),现在是(-0.5, 0.5)
				 // w实际上可以直接理解为是一个动态的阈值范围, 实际上可以直接看成固定的比如:[-0.3, 0.3]
				 // 此时当spec + _SpecularScale - 1 小于 -0.3(-w)时就会返回0, 在-w,w之间时就会返回0~1, 大于w时会返回1
				 // 这样这个高光反射系数就不会产生突变而是渐变效果
				 // 书上直接说_SpecularScale是一个阈值,而我的理解是如上,不然太难理解了..
				 // 这样我们得到的高光反射颜色是一个个块状的,而不是之前那样子的 之前是pow(dot(normal, half), x)
				 // 这种lerp(...) 处理 如果觉得难以理解的话 简单的做法是 直接 step(阈值, dot(...)) 当点积大于阈值时会返回1,小于阈值时会返回0
				 // 但是这种做法就是导致了突变,高光部分会产生锯齿,可以想象假设 阈值时 0.5,  那么当 (0.5, 1)时直接返回1,(0,0.5)时候返回0
				 // 在0.49 时候还是0, 逐渐变为0.51时突变为1, 反过来也是一样,高光颜色会直接从0,突变为有色状态,锯齿就是由于突变导致的。
				 // 上面那个阈值0.5,可看成一个分界线, 当dot()在0.5(左边)时就会返回0,在0.5的右边时返回1
				 // 此时下面的做法就是 将这个分界线,变成了2个! 即 -w 和 w, 虽然这2个值得获取很难理解,但是就可以直接理解为就是一个很小的常数
				 //    当dot() < -w 时,返回的是0, > w时候 返回的是1,  在(-w,w)中间时返回的是0~1的数值,所以效果就是渐变,解决了抗锯齿问题
				 fixed3 specular = _Specular.rgb * lerp(0,1, smoothstep(-w,w,spec + _SpecularScale - 1)) * step(0.0001, _SpecularScale);
				 
				 return fixed4(ambient + diffuse + specular, 1.0);
			}

			ENDCG
		}
	}
	Fallback "Diffuse"
}

二、素描风格的渲染

Shader "MilkShader/14/Hatching"
{
	Properties
	{		
		_Color ("Color Tint", Color) = (1,1,1,1) //控制颜色
		_TileFactor ("Tile Factor", Float) = 1 //平铺系数(对纹理坐标的缩放系数<x,y都有影响>)
		//描边使用的参数
		_Outline("Outline", Range(0,1)) = 0.1
		_OutlineColor("Outline Color", Color) = (1,1,1,1) 
		//六张素描纹理(逐渐增多横向线条的纹理)
		_Hatch0 ("Hatch 0", 2D) = "white"{}
		_Hatch1 ("Hatch 1", 2D) = "white"{}
		_Hatch2 ("Hatch 2", 2D) = "white"{}
		_Hatch3 ("Hatch 3", 2D) = "white"{}
		_Hatch4 ("Hatch 4", 2D) = "white"{}
		_Hatch5 ("Hatch 5", 2D) = "white"{}
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" "Queue"="Geometry"}
		
		UsePass "MilkShader/14/ToonShading/OUTLINE"//使用了之前的描边PASS

		Pass
		{
			Tags{ "LightMode" = "ForwardBase"}
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			// make fog work
			#pragma multi_compile_fwdbase
			
			#include "UnityCG.cginc"
			#include "Lighting.cginc"
			#include "AutoLight.cginc"
			#include "UnityShaderVariables.cginc"

			fixed4 _Color;
			fixed _TileFactor;
			fixed _Outline;
			fixed4 _OutlineColor;
			sampler2D _Hatch0;
			sampler2D _Hatch1;
			sampler2D _Hatch2;
			sampler2D _Hatch3;
			sampler2D _Hatch4;
			sampler2D _Hatch5;

			struct appdata
			{
				float4 vertex : POSITION;
				float4 tangent : TANGENT; 
				float2 texcoord : TEXCOORD0;
				float3 normal : NORMAL;
			};

			struct v2f
			{
				float4 pos : SV_POSITION;
				float2 uv : TEXCOORD0;
				fixed3 hatchWeight0 : TEXCOORD1;
				fixed3 hatchWeight1 : TEXCOORD2;
				float3 worldPos : TEXCOORD3;
				SHADOW_COORDS(4)
			};
					
			v2f vert (appdata v)
			{
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);

				o.uv = v.texcoord.xy * _TileFactor;

				fixed3 worldNormal = normalize(UnityObjectToWorldNormal(v.normal));
				fixed3 worldLightDir = normalize(WorldSpaceLightDir(v.vertex));
				//法线和光源的点积
				fixed diff = max(0, dot(worldNormal, worldLightDir));

				//将diff转到[0,7]范围
				float hatchFactor = diff * 7.0;

				o.hatchWeight0 = fixed3(0,0,0);
				o.hatchWeight1 = fixed3(0,0,0);
				//分别判断每一个区间[0,1] [1,2] [2,3] [3,4] [4,5] [5,6] [6,7]再计算对应区间的素描纹理权重值				
				//这个是固定的算法,记住是这样写就好了,如果想研究的话可以参考冯乐乐的第十四章
				//实际上就是不同的区间时会对其中1个或2个素描纹理权重进行赋值,以此在片元着色器进行使用它们来进行与相应素描颜色进行相乘
				//也就是决定在什么情况下,哪几个素描纹理产生多少影响
				// hatchWeight0.x 是 第一个素描纹理的权重, 以此类推 hatchWeight1.z 是最后一个素描纹理的权重
				// 初始化全为0是表示素描纹理完全没有任何作用
				if(hatchFactor > 6.0){				
				}else if(hatchFactor > 5.0){
					o.hatchWeight0.x = hatchFactor - 5.0;
				}else if(hatchFactor > 4.0){
					o.hatchWeight0.x = hatchFactor - 4.0;
					o.hatchWeight0.y = 1 - o.hatchWeight0.x;
				}else if(hatchFactor > 3.0){
					o.hatchWeight0.y = hatchFactor - 3.0;
					o.hatchWeight0.z = 1 - o.hatchWeight0.y;
				}else if(hatchFactor > 2.0){
					o.hatchWeight0.z = hatchFactor - 2.0;
					o.hatchWeight1.x = 1 - o.hatchWeight0.z;
				}else if(hatchFactor > 1.0){
					o.hatchWeight1.x = hatchFactor - 1.0;
					o.hatchWeight1.y = 1 - o.hatchWeight1.x;
				}else{
					o.hatchWeight1.y = hatchFactor;
					o.hatchWeight1.z = 1 - o.hatchWeight1.y;
				}
				
				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

				TRANSFER_SHADOW(o);

				return o;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				//从不同的素描纹理采样后乘以相应的素描纹理权重值(在顶点着色器我们就已经对它们进行了一个赋值)
				fixed4 hatchTex0 = tex2D(_Hatch0, i.uv) * i.hatchWeight0.x;
				fixed4 hatchTex1 = tex2D(_Hatch1, i.uv) * i.hatchWeight0.y;
				fixed4 hatchTex2 = tex2D(_Hatch2, i.uv) * i.hatchWeight0.z;
				fixed4 hatchTex3 = tex2D(_Hatch3, i.uv) * i.hatchWeight1.x;
				fixed4 hatchTex4 = tex2D(_Hatch4, i.uv) * i.hatchWeight1.y;
				fixed4 hatchTex5 = tex2D(_Hatch5, i.uv) * i.hatchWeight1.z;
				//非素描部分的颜色是白色(如果素描的纹理权重总和越大,非素描部分越黑,否则越白)
				fixed4 whiteColor = fixed4(1,1,1,1) * (1 - i.hatchWeight0.x - i.hatchWeight0.y - i.hatchWeight0.z - i.hatchWeight1.x
				- i.hatchWeight1.y - i.hatchWeight1.z);

				fixed4 hatchColor = hatchTex0 + hatchTex1 + hatchTex2 + hatchTex3 + hatchTex4 + hatchTex5 + whiteColor;

				UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);

				return fixed4(hatchColor.rgb * _Color.rgb * atten, 1.0);
			}
			ENDCG
		}
	}
	Fallback "Diffuse"
}

平铺系数越大素描的线越密集,否则反之。

中秋节快乐!

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值