UnityShader_填补之前雪地没有阴影的坑

292 阅读4分钟

本文已参与[新人创作礼]活动,一起开启掘金创作之路
填补UnityShader_足迹沙漠雪地塌陷这篇文章的坑,给雪地加上阴影的投射和接收

Shader "ShaderPath/Tessellation"
{
	Properties
	{
		_Tess("Tessellation", Range(1,64)) = 4
		_TopMain ("TopMain", 2D) = "white" {}
		_BottomMain ("BottomMain", 2D) = "white" {}
		_TopColor ("TopColor",Color) = (1,1,1,1)//上层的主色调
		_BottomColor ("BottomColor",Color) = (1,1,1,1)//下层的主色调
		_DispTex("Displacement Texture", 2D) = "white" {}//脚印踩下去之后的模型顶点偏移贴图
		_ImprintTex("Imprint Texture", 2D) = "white" {}//印记贴图(仅记录深度)
		_Displacement("Displacement", Range(0, 1.0)) = 0.3//深度因子
		_SpecularColor ("SpecularColor",Color) = (1,1,1,1)//高光反射的主色调
		_Gloss ("Gloss",Range(1,100)) = 2 //光泽度(反光度) 控制高光区域的大小
	}
	

	CGINCLUDE
	#include "UnityCG.cginc"
	#include "Lighting.cginc"
	#include "AutoLight.cginc"
	//UnityDistanceBasedTess 函数在下面库中
	#include "Tessellation.cginc"

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

	//细分顶点结构体
	struct InternalTessInterp_appdata {
		float4 vertex : INTERNALTESSPOS;
		float4 tangent : TANGENT;
		float3 normal : NORMAL;
		float2 texcoord : TEXCOORD0;
	};

	struct v2f
	{
		float4 pos : SV_POSITION;
		float2 texcoord:TEXCOORD0;
		float4 T2W0 : TEXCOORD1;
		fixed4 T2W1 : TEXCOORD2; 
		float4 T2W2 : TEXCOORD3;
		#if UNITY_PASS_FORWARDBASE
		float3 normal : NORMAL;
		unityShadowCoord4 _ShadowCoord : TEXCOORD4;
		#endif
	};

	float _Tess;
	sampler2D _TopMain;
	float4 _TopMain_ST;
	sampler2D _BottomMain;
	float4 _BottomMain_ST;
	sampler2D _ImprintTex;
	sampler2D _DispTex;
	float _Displacement;
	fixed4 _SpecularColor;
	fixed4 _TopColor;
	fixed4 _BottomColor;
	float _Gloss;

	InternalTessInterp_appdata tessvert (appdata v) 
	{
		InternalTessInterp_appdata o;
		o.vertex = v.vertex;
		o.tangent = v.tangent;
		o.normal = v.normal;
		o.texcoord = v.texcoord;
		return o;
	}


	v2f vert (appdata v)
	{
		v2f o;
		o.pos = UnityObjectToClipPos(v.vertex);
		o.texcoord = TRANSFORM_TEX(v.texcoord, _TopMain);

		float4 worldPos = mul(unity_ObjectToWorld, v.vertex);
		float3 worldNormal = UnityObjectToWorldNormal(v.normal);
		fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
		fixed tangentSign = v.tangent.w * unity_WorldTransformParams.w;
		fixed3 worldBinormal = cross(worldNormal, worldTangent) * tangentSign;
		o.T2W0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
		o.T2W1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
		o.T2W2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
		#if UNITY_PASS_FORWARDBASE
		o.normal = worldNormal;
		o._ShadowCoord = ComputeScreenPos(o.pos);
		#elif UNITY_PASS_SHADOWCASTER 
		o.pos = UnityApplyLinearShadowBias(o.pos);
		#endif
		return o;
	}

	//将模型顶点根据深度图进行拉伸,使其凹陷,并且_Displacement可以控制拉伸长度,即雪厚度
	void disp(inout appdata v)
	{
		//关于为什么这里用tex2Dlod,可以简单理解成因为tex2D是在片元着色器中使用的,
		//在顶点着色器中无法使用,tex2Dlod可以在顶点着色器中使用
		//详见链接http://www.ufgame.com/9620.html
		//变换深度纹理以适合摄影机
		float d = tex2Dlod(_ImprintTex, float4(1 - v.texcoord.x, v.texcoord.y, 0,0)).r * _Displacement;
		//深度纹理经过变换以适合照相机将其全部放置在一个更有趣的整体表面上,使其从顶部缩进并调低一半
		d *= 1 - tex2Dlod(_DispTex, float4(v.texcoord,0,0)) * .5f;
		v.vertex.xyz += v.normal * d;
	}

	//在 (minDist,maxDist)区间内细分会不断变化
	float4 tessDistance(appdata v0, appdata v1, appdata v2) {
		float minDist = 10.0; //细分最小距离,小于细分不在增加
		float maxDist = 25.0; //细分最远距离,超出不在细分
		//这个函数计算每个顶点到相机的距离,得出最终的tessellation 因子。
		return UnityDistanceBasedTess(v0.vertex, v1.vertex, v2.vertex, minDist, maxDist, _Tess);
	}

	//InputPatch<InternalTessInterp_appdata,3> 输入块,由3个InternalTessInterp_appdata的顶点构成
	UnityTessellationFactors hsconst_surf (InputPatch<InternalTessInterp_appdata,3> v) 
	{
		UnityTessellationFactors o;
		float4 tf;
		appdata vi[3];
		vi[0].vertex = v[0].vertex;
		vi[0].tangent = v[0].tangent;
		vi[0].normal = v[0].normal;
		vi[0].texcoord = v[0].texcoord;
		vi[1].vertex = v[1].vertex;
		vi[1].tangent = v[1].tangent;
		vi[1].normal = v[1].normal;
		vi[1].texcoord = v[1].texcoord;
		vi[2].vertex = v[2].vertex;
		vi[2].tangent = v[2].tangent;
		vi[2].normal = v[2].normal;
		vi[2].texcoord = v[2].texcoord;
		tf = tessDistance(vi[0], vi[1], vi[2]);
		o.edge[0] = tf.x; o.edge[1] = tf.y; o.edge[2] = tf.z; o.inside = tf.w;
		return o;
	}

	//指明输入进hull shader的图元是三角形。
	[UNITY_domain("tri")]
	//决定舍入规则,fractional_odd意为factor截断在[1,max]范围内,然后取整到小于此数的最大奇数整数值。
	[UNITY_partitioning("fractional_odd")]
	//决定图元的朝向,由组成三角形的三个顶点的顺序所产生的方向决定,cw为clockwise顺时针,ccw为counter clockwise逆时针。
	[UNITY_outputtopology("triangle_cw")]
	//指明计算factor的方法
	[UNITY_patchconstantfunc("hsconst_surf")]
	//hull shader输出的outputpatch中的顶点数量。
	[UNITY_outputcontrolpoints(3)]
	//给出控制点在path中的ID,与outputcontrolpoints对应,例如outputcontrolpoints为4,那么i的取值就是[0,4)的整数。
	InternalTessInterp_appdata hs_surf (InputPatch<InternalTessInterp_appdata,3> v, uint id : SV_OutputControlPointID) {
		return v[id];
	}

	//SV_DomainLocation:由曲面细分阶段阶段传入的顶点位置信息。
	[UNITY_domain("tri")]
	v2f ds_surf (UnityTessellationFactors tessFactors, const OutputPatch<InternalTessInterp_appdata,3> vi, float3 bary : SV_DomainLocation) {
		appdata v;
		UNITY_INITIALIZE_OUTPUT(appdata,v);
		v.vertex = vi[0].vertex*bary.x + vi[1].vertex*bary.y + vi[2].vertex*bary.z;
		v.tangent = vi[0].tangent*bary.x + vi[1].tangent*bary.y + vi[2].tangent*bary.z;
		v.normal = vi[0].normal*bary.x + vi[1].normal*bary.y + vi[2].normal*bary.z;
		v.texcoord = vi[0].texcoord*bary.x + vi[1].texcoord*bary.y + vi[2].texcoord*bary.z;
		//以上就是细分顶点最终生成的位置,下面是将所有顶点进行拉伸
		disp (v);
		v2f o = vert (v);
		return o;
	}

	
	
	ENDCG
	SubShader
	{
		Cull Off

		Pass
		{
			Tags{ "RenderType" = "Opaque" "LightMode" = "ForwardBase"}
			LOD 300

			CGPROGRAM
			#pragma vertex tessvert
			#pragma fragment frag
			#pragma hull hs_surf //细分控制着色器
			#pragma domain ds_surf //细分计算着色器
			#pragma target 4.6
			#pragma multi_compile_fwdbase

			fixed4 frag (v2f i) : SV_Target
			{
				float3 worldPos = float3(i.T2W0.w,i.T2W1.w,i.T2W2.w);
				float3 worldNormal = float3(i.T2W0.z,i.T2W1.z,i.T2W2.z);
				fixed shadow = SHADOW_ATTENUATION(i);
				float diffuse = (1 + dot(_WorldSpaceLightPos0, i.normal))/ 2 * shadow;
				float3 ambient = ShadeSH9(float4(i.normal, 1));
				float4 lightIntensity = diffuse * _LightColor0 +float4(ambient, 1);
				//上下两层其实就是一层,但是利用深度的不同分布进行差值,陷下去的用bottom,没陷下去的用Top
				//所以只用一个texcoord,毕竟都是渲染同一个地面
				half4 c = lerp(
				tex2D(_TopMain, i.texcoord) * _TopColor,
				tex2D(_BottomMain, i.texcoord) * _BottomColor,
				1 - tex2D(_ImprintTex, float2(1 - i.texcoord.x, i.texcoord.y)).r);

				fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));//获取光源在世界空间下的方向(光源发射出来的方向)
				fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - worldPos);//计算眼睛的方向 相机位置-模型的世界坐标 向量
				
				//Blinn-Phong模型高光 
				fixed3 halfView = normalize(lightDir + viewDir);
				fixed3 specular = _LightColor0.rgb * _SpecularColor * pow(saturate(dot(worldNormal,halfView)),_Gloss);
				// UNITY_LIGHT_ATTENUATION(atten,i,worldPos);
				return fixed4((c.xyz + specular)*lightIntensity ,1);//(c.xyz * shadow + specular ,1);
			}
			ENDCG
		}
		Pass
		{
			Tags
			{
				"LightMode" = "ShadowCaster"
			}

			CGPROGRAM
			#pragma vertex tessvert
			#pragma fragment fragShadow
			#pragma hull hs_surf //细分控制着色器
			#pragma domain ds_surf //细分计算着色器
			#pragma multi_compile_shadowcaster
			#pragma target 4.6

			float4 fragShadow(v2f i) : SV_Target
			{
				SHADOW_CASTER_FRAGMENT(i)
			}

			ENDCG
		}
	}
}

其实就加了几句代码,并且把原来的代码结构整理了一下,将着色器代码分离,方便复用。

	struct v2f
	{
		......
		#if UNITY_PASS_FORWARDBASE
		float3 normal : NORMAL;
		unityShadowCoord4 _ShadowCoord : TEXCOORD4;
		#endif
	};
	v2f vert (appdata v)
	{
		......
		#if UNITY_PASS_FORWARDBASE
		o.normal = worldNormal;
		o._ShadowCoord = ComputeScreenPos(o.pos);
		#elif UNITY_PASS_SHADOWCASTER 
		o.pos = UnityApplyLinearShadowBias(o.pos);
		#endif
		return o;
	}
	fixed4 frag (v2f i) : SV_Target
	{
		fixed shadow = SHADOW_ATTENUATION(i);
		float diffuse = (1 + dot(_WorldSpaceLightPos0, i.normal))/ 2 * shadow;
		float3 ambient = ShadeSH9(float4(i.normal, 1));
		float4 lightIntensity = diffuse * _LightColor0 +float4(ambient, 1);
		......
		return fixed4((c.xyz + specular)*lightIntensity ,1);//(c.xyz * shadow + specular ,1);
	}
	Pass
	{
		Tags
		{
			"LightMode" = "ShadowCaster"
		}
		CGPROGRAM
		#pragma vertex tessvert
		#pragma fragment fragShadow
		#pragma hull hs_surf //细分控制着色器
		#pragma domain ds_surf //细分计算着色器
		#pragma multi_compile_shadowcaster
		#pragma target 4.6

		float4 fragShadow(v2f i) : SV_Target
		{
			SHADOW_CASTER_FRAGMENT(i)
		}
		ENDCG
	}

在这里插入图片描述 阴影如上可见,人的阴影及雪地之间的阴影 后面会有更详细的对Unity自带阴影相关内置函数的研究

不要把挣钱当作最终目的,那样你会活得很累