unity Shader 平面阴影的实现

首先欢迎大家关注订阅我的小专栏,里面会定时更新一些游戏开发的内容。

网址:https://xiaozhuanlan.com/xiaochengzi

本专栏讲述利用Unity进行游戏开发使用到的一些热门技术、框架、shader、优化技巧等等,偏向在工作中的实际应用,也为广大游戏开发者提供一些宝贵的经验。
专栏内容:
1.unity优化技巧
2.工作中常用的一些热门通用技术。
3.unity shader 讲解
4.游戏框架
5.unity一些好用的插件、组件的介绍
6.自己写的一些辅助插件、辅助工具
7.安卓、IOS打包、真机调试、Profiler性能分析技巧专讲
8.Jenkins自动化构建出包(编写Windows批处理命令)
9.游戏中的帧同步、状态同步技术专讲
持续更新。。。
关于我:
目前在职盛趣游戏(原盛大游戏),担任游戏客户端程序开发一职,在职1年多。

 

平面阴影的效果图:

unity Shader 平面阴影的实现_第1张图片

 

一、平面阴影的介绍:

       平面阴影作为一种最简单的实时阴影实现,尽管其仅能局限于在完全平坦的地面的情况下使用,但由于其性能良好,在许多移动端手游中仍然可以发挥较强的使用价值。因为我们只考虑物体的阴影投射到平面上的情形,所以有一套相对比较简单的专用算法。

首先考虑最简单的情况,如何计算一个平行光的投影,平行光在我们的计算中其实就是一个方向矢量,是阴影的投射方向,而平面是阴影要影响的目标物体(通常是地面)。我们需要知道目标物体的Object Space矩阵,在目标物体的空间内将投影物体的顶点进行重新计算,计算其沿光线方向,在阴影接收平面上的位置,这个位置关系可以通过三角形相似来计算如果我们使用Unity自带的Plane作为阴影接受平面,那么我们只需要计算顶点的xz的位置,如果阴影投射到Build in的Plane上,那么在其Object Space中,y应该为0,但在实际使用时,为了保证阴影永远在物体上面,我们会对z进行偏移。

二、平面阴影的实现原理:

1.向shader传入阴影接收平面(通常是地面)的世界空间到模型空间的转换矩阵和模型空间到世界空间的转换矩阵。

2.在阴影接收平面空间下,根据灯光方向计算目标物体的顶点在平面上的投影坐标,最终得到阴影。

 

unity Shader 平面阴影的实现_第2张图片

根据相似三角形,计算出AC,即目标物体的顶点沿着光照方向到达阴影接收平面的距离;

首先,每一个投射阴影的物体都需要下面这么脚本来告诉它接受物体的信息,具体来说就是到其object Space空间的矩阵,以及从平面的Object Space返回的矩阵。

 

这个C#代码放在需要显示阴影的对象上:

using UnityEngine;
using System.Collections;
 
[ExecuteInEditMode]
public class PlaneShadowCaster : MonoBehaviour
{
    public Transform reciever;   //阴影接收平面(通常是地面)
    void Update()
    {
        GetComponent().sharedMaterial.SetMatrix("_World2Ground", reciever.GetComponent().worldToLocalMatrix);
        GetComponent().sharedMaterial.SetMatrix("_Ground2World", reciever.GetComponent().localToWorldMatrix);
    }
}

接下来是shader代码,放在需要显示阴影的对象上。

// shader,放在需要显示阴影的对象上
Shader "Custom/PlanarShadow1" {
	Properties{
	_Instensity("Shininess", Range(2, 4)) = 2.0  //光照强度

	_Diffuse("Diffuse Color",Color) = (1,1,1,1)
    //纹理贴图
	_MainTex("Main Tex",2D) = "white"{}
	//控制纹理颜色
	_Color("Color",Color) = (1,1,1,1)
	}

		SubShader{
//光照计算
	    Pass{
			Tags{"LightMode" = "ForwardBase"}
			    CGPROGRAM
	            #include "Lighting.cginc"
	            #pragma vertex vert
	            #pragma fragment frag

		//fixed4 _Diffuse;
		fixed4 _Color;
		sampler2D _MainTex;
		float4 _MainTex_ST;
		fixed4 _Specular;
		half _Gloss;

		struct a2v {
			float4 vertex:POSITION;//告诉unity把模型空间下的顶点坐标填充给vertex
			float3 normal:NORMAL;
			//纹理坐标  然后还要贴图,就可以取到该坐标的颜色,然后替换漫反射的颜色
			float4 texcoord:TEXCOORD0;
		};
		struct v2f {
			float4 svPos:SV_POSITION;
			float3 worldNormal:TEXCOORD0;
			float4 worldVertex:TEXCOORD1;
			float2 uv:TEXCOORD2;
		};

		v2f vert(a2v v) {
			v2f f;
			f.svPos = UnityObjectToClipPos(v.vertex);
			f.worldNormal = UnityObjectToWorldNormal(v.normal);
			f.worldVertex = mul(v.vertex, unity_WorldToObject);
			f.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
			return f;
		}
		fixed4 frag(v2f f) :SV_Target{
			//法线的方向
			fixed3 normalDir = normalize(f.worldNormal);
		    //光线的方向
			fixed3 lightDir = normalize(WorldSpaceLightDir(f.worldVertex));
			//返回的颜色值代替漫反射的颜色
			fixed3 texColor = tex2D(_MainTex, f.uv.xy)*_Color.rgb;

			fixed3 diffuse = _LightColor0.rgb * texColor * max(dot(normalDir, lightDir), 0);
			//反射光的方向
			//fixed3 reflectDir = normalize(reflect(-lightDir, normalDir));
			//视野方向
			fixed3 viewDir = normalize(UnityWorldSpaceViewDir(f.worldVertex));
			//blinn-Phong光照模型   计算平分线
			fixed3 halfDir = normalize(lightDir + viewDir);

			//漫反射光照+上环境光+纹理颜色
			fixed3 tempColor = diffuse + UNITY_LIGHTMODEL_AMBIENT.rgb*texColor;

			return fixed4(tempColor, 1);

		}

		 ENDCG
	   }



	//计算阴影
	Pass
	{
		Tags{"LightMode" = "ForwardBase"}
		  Stencil          //加个模板
			{
				Ref 0
				Comp equal
				Pass incrWrap
				Fail keep
				ZFail keep
			}
			ZWrite off

	//	Blend DstColor SrcColor
		Blend Srcalpha OneminusSrcAlpha
		Offset -1, -1		//使阴影在平面之上  
		CGPROGRAM
		#pragma vertex vert
		#pragma fragment frag
		#include "UnityCG.cginc"

		float4x4 _World2Ground;  //阴影接收平面(世界空间到模型空间的转换矩阵)
		float4x4 _Ground2World;	 //阴影接收平面(模型空间到世界空间的转换矩阵)
		float _Instensity;

		struct v2f {
			float4 pos:SV_POSITION;
			float atten : TEXCOORD0;
		};

	   v2f vert(float4 vertex:POSITION)
	   {
		   float3 litDir;
		   litDir = WorldSpaceLightDir(vertex);//世界空间主光照相对于当前物体的方向
		   litDir = mul(_World2Ground,float4(litDir,0)).xyz;//光源方向转换到接受阴影的平面空间
		   litDir = normalize(litDir);// 归一
		   float4 vt;
		   vt = mul(unity_ObjectToWorld,vertex); //将当前物体转换到世界空间
		   vt = mul(_World2Ground,vt); // 将物体在世界空间的矩阵转换到地面空间
		   vt.xz = vt.xz - (vt.y / litDir.y)*litDir.xz;// 用三角形相似计算沿光源方向投射后的XZ
		   vt.y = 0;// 使阴影保持在接受平面上
		   vt = mul(_Ground2World, vt); // 阴影顶点矩阵返回到世界空间
		   vt = mul(unity_WorldToObject, vt); // 返回到物体的坐标
		   v2f o;
		   o.pos = UnityObjectToClipPos(vt);//输出到裁剪空间
		   o.atten = distance(vertex, vt) / _Instensity;// 根据物体顶点到阴影的距离计算衰减
		   return o;
	   }

	   float4 frag(v2f i) :COLOR
	   {
		   return float4(0.3, 0.3, 0.3, 0.5);//一个灰色的阴影出来了
			//return smoothstep(0,1,i.atten / 2);
		}

		ENDCG
	   }
	}
}

解释:

1.第一个pass块主要进行光照计算(漫反射光照+环境光+纹理的颜色)。

2.第二个pass就是阴影的绘制了。在计算阴影的ForwardBase中,首先使用一个可以叠加阴影的混合模式,然后使用z偏移保证出来的阴影在接受平面之上。_World2Ground和_Ground2World分别是我们自定义的两个进出阴影接收平面矩阵。在具体计算中,首先将光源方向和投影物体的顶点都转换到接收平面的空间,在它们都处于同一个空间后,通过简单的三角形近似算法,来计算投影物体顶点沿光线投射后在接收平面上的新位置。因为这是一个Build In的Unity Plane,所以只计算其xz分量即可,并将Y分量设为0,这样投影出来的阴影就出现在接收平面上了。投影计算完成后,我们返回到世界空间,然后到投影物体本身的Object Space,之后的事情就如通常那样,一个经典的MVP变换即可。
效果如下:

unity Shader 平面阴影的实现_第3张图片

 

 

你可能感兴趣的:(unity,shader,平面阴影,贴片阴影,unity,Shader)