这边文章会按照阴影实现的原理一步一步去解释每一步的代码和实现
这篇文章使用的是常规的阴影贴图的原理实现,原理很简单,就是在光源的位置假设一个摄像机我们叫做L,然后用L摄像机把照射物体A的深度值存储到一个深度贴图里面,我们正常渲染场景的摄像机叫做E,我们把E照射的物体P点,转换光源空间,然后对比两个点的深度值,就知道p点是否有阴影。
1 如图我们先建立一个简单的场景然后在光源下面建立一个摄像机用来渲染深度图,cube是我们产生阴影的物体,plane是我们接受阴影的物体。
2. 我们建立一个ShadowTest脚本挂接在光源上,在Start函数里面创建渲染贴图
Camera m_renderCam;
RenderTexture rt;
void Start()
{
m_renderCam = transform.Find("Camera").GetComponent<Camera>();
rt = new RenderTexture(512, 512, 24);
m_renderCam.targetTexture = rt;
}
3.因为我们只对cube产生阴影,所以我们新添加一个player的层级,把cube的层级设置为player,同时把灯光摄像机的cullingmask设置为player,保证我们的深度图上只有这个cube。
4. 我们简单的建立一个unlit shader,重命名为shadermap,把里面的代码改成存储物体的深度,比较简单,不在详细解释。
Shader "Unlit/shadermap"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float d = i.vertex.z/i.vertex.w;
fixed4 col = EncodeFloatRGBA(d);
return col;
}
ENDCG
}
}
}
这里为了防止精度丢失我们使用了EncodeFloatRGBA 把浮点数存储为rgb值。
5. 我们把让L摄像机的渲染的物体替换为我们的shader
m_renderCam.SetReplacementShader(Shader.Find("Unlit/shadermap"), "RenderType");
6.接下来我们为我们plane建立一个ShadowReceive材质,并建立一个shader,同样的我们用unlit的shader来改写。
首先我们需要把深度图传入到shader里面所以我们定义sampler2d 变量用来接收我们的 贴图
sampler2D _depthTexture;
接下来我们需要拿到p点在光源空间下的深度值,这个深度值是通过采样_depthTexture贴图得到,但是uv值从哪里来?这里我们需要用到投影纹理映射的知识 ,跟我们常用的mvp变换一样,我们首先把p点转换到裁剪空间,但是我们不能使用这里的vp矩阵,因为在这个shader里面vp矩阵是通过我们的E摄像机得到的,而我们实际转换的是光源空间的vp矩阵,所有我们这里在shader定义 float4x4 _vpMatrix;变量然后通过shaowtest脚本传入进来。
//变换到灯光摄像机的 vp矩阵 从外部传入
float4x4 _vpMatrix;
我们在v2f中定义一个float4 pos 用来存放我们在灯光空间通过mvp转换后的坐标。
struct v2f
{
float2 uv : TEXCOORD0;
float4 pos: TEXCOORD1;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _depthTexture;
//变换到灯光摄像机的 vp矩阵 从外部传入
float4x4 _vpMatrix;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv,_MainTex);
//vp矩阵和m矩阵相乘 组合出光源空间下的mvp
_vpMatrix = mul(_vpMatrix,unity_ObjectToWorld);
//通过mvp把顶点变换到裁剪空间
o.pos = mul(_vpMatrix,v.vertex);
return o;
}
7.根据投影纹理原理,我们对深度图采样,实际上是在屏幕空间下,屏幕空间中我们会通过透视除法把坐标转换成ndc坐标,unity下他的取值范围是-1到1,而我们要采样的uv的值是0到1,所以我们就要通过一个矩阵把坐标的值变换过来。这一步在shader里面做和在脚本里面做都是可以的,我们这里直接在脚本里面做。然后通过_vpMatrix传入。
GameObject plane = GameObject.Find("Plane");
Material m = plane.GetComponent<MeshRenderer>().material;
//传入深度图
m.SetTexture("_depthTexture", rt);
Matrix4x4 vp = GL.GetGPUProjectionMatrix(m_renderCam.projectionMatrix, false) * m_renderCam.worldToCameraMatrix;
Matrix4x4 sm = new Matrix4x4();
sm.m00 = 0.5f;
sm.m11 = 0.5f;
sm.m22 = 0.5f;
sm.m33 = 1f;
sm.m03 = 0.5f;
sm.m13 = 0.5f;
sm.m23 = 0.5f;
vp = sm * vp;
//传入变换矩阵
m.SetMatrix("_vpMatrix", vp);
8.剩下的就是我们在片元着色器里面对贴图进行采样,然后比较深度值了
fixed4
9.运行unity,产生阴影效果
示例demo地址
git@:zhudianyu/LearnShadow.git