1.创建场景,放一个球和一个平面上去。
2.添加光照
3.给球一个内置的diff材质
4.赋给平面一个材质,用来接收阴影,shader如下
Shader "Unlit/diff"
{
Properties
{
_Color("Color", Color) = (1,1,1,1)
}
SubShader
{
Tags{ "RenderType" = "Opaque"
"LightMode" = "ForwardBase"}
LOD 100
Pass
{
CGPROGRAM
// Upgrade NOTE: excluded shader from DX11; has structs without semantics (struct v2f members worldPos)
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 uv : TEXCOORD0;
};
struct v2f
{
float3 worldNormal : NORMAL;
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
float4 _Color;
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
// Transform the normal from object space to world space
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
o.uv = v.uv;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
// Get the normal in world space
fixed3 worldNormal = normalize(i.worldNormal);
// Get the light direction in world space
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed4 diff = saturate(dot(worldLightDir, worldNormal)) * _Color;
return diff;
}
ENDCG
}
}
}
把这个材质赋给球和平面,效果如下
image.png
接下来是要在光源方向渲染一张深度图,作为shadowMap。为了达到此效果,我们需要在光源处挂一个相机,这里通过脚本来挂。新建一个monobehaviour
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class shadowMap : MonoBehaviour {
Camera _lightCamera;
public Material depthMat;
public int qulity = 1;
public GameObject lightObj;
RenderTexture lightDepthTexture;
// Use this for initialization
void Start ()
{
Camera curCam = GetComponent<Camera>();
_lightCamera = CreateLightCamera();
_lightCamera.RenderWithShader(depthMat.shader, "");
_lightCamera.transform.parent = lightObj.transform;
_lightCamera.transform.localPosition = Vector3.zero;
_lightCamera.transform.localRotation = new UnityEngine.Quaternion();
Matrix4x4 projectionMatrix = GL.GetGPUProjectionMatrix(_lightCamera.projectionMatrix, true);
Shader.SetGlobalMatrix("_worldToLightClipMat", projectionMatrix * _lightCamera.worldToCameraMatrix);
_lightCamera.RenderWithShader(depthMat.shader, "");
}
public Camera CreateLightCamera()
{
GameObject goLightCamera = new GameObject("Shadow Camera");
Camera LightCamera = goLightCamera.AddComponent<Camera>();
LightCamera.backgroundColor = Color.black;
LightCamera.clearFlags = CameraClearFlags.SolidColor;
LightCamera.orthographic = true;
LightCamera.orthographicSize = 6f;
LightCamera.nearClipPlane = 0.3f;
LightCamera.farClipPlane = 20;
LightCamera.enabled = false;
if (!LightCamera.targetTexture)
LightCamera.targetTexture = CreateTextureFor(LightCamera);
lightDepthTexture = LightCamera.targetTexture;
Shader.SetGlobalTexture("_LightDepthTexture", lightDepthTexture);
return LightCamera;
}
private RenderTexture CreateTextureFor(Camera cam)
{
RenderTexture rt = new RenderTexture(Screen.width * qulity, Screen.height * qulity, 24, RenderTextureFormat.Default);
rt.hideFlags = HideFlags.DontSave;
return rt;
}
// Update is called once per frame
void Update () {
}
}
该脚本的功能,就是在启动时,在光源处创建一个正交相机,然后渲染一帧深度图,放入lightDepthTexture中。注意
Matrix4x4 projectionMatrix = GL.GetGPUProjectionMatrix(_lightCamera.projectionMatrix, true);
这一句第二个参数一定要填true,这样在DX下面,计算出来的屏幕坐标才能在纹理上得到到正确的翻转采样
接下来创建深度图材质
Shader "Unlit/depth"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
}
SubShader
{
Tags{ "RenderType" = "Opaque" }
LOD 100
Pass
{
//Cull Front
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
float2 depth:TEXCOORD0;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.depth.xy = o.vertex.zw;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
float depth = i.depth.x / i.depth.y;
#if UNITY_REVERSED_Z
depth = 1 - depth; //(1, 0)-->(0, 1)
#else
depth = depth * 0.5 + 0.5; //(-1, 1)-->(0, 1)
#endif
return depth;
}
ENDCG
}
}
}
为了保证在OpenGL和DX下有同样的效果,需要处理一下
UNITY_REVERSED_Z宏,这个宏在DX11下开启,开启后,深度值在[1,0]之间,近平面是1,远是0 。在OpenGL下,这个宏不会开启,深度值是[-1,1]
运行一下,可以看到这个深度图
image.png
接下来要做的事就是修改diff.shader,让它可以接收阴影。在它的片元着色器中计算屏幕空间的坐标,和深度, 然后采样光源空间的深度图。进行深度比较。
Shader "Unlit/diff"
{
Properties
{
_Color("Color", Color) = (1,1,1,1)
}
SubShader
{
Tags{ "RenderType" = "Opaque"
"LightMode" = "ForwardBase" }
LOD 100
Pass
{
CGPROGRAM
// Upgrade NOTE: excluded shader from DX11; has structs without semantics (struct v2f members worldPos)
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 uv : TEXCOORD0;
};
struct v2f
{
float3 worldNormal : NORMAL;
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float4 lightClipPos : TEXCOORD1;
};
float4 _Color;
sampler2D _LightDepthTexture;
float4 _LightDepthTexture_ST;
float4x4 _worldToLightClipMat;
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
// Transform the normal from object space to world space
float4 worldPos = mul(unity_ObjectToWorld, v.vertex);
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
o.lightClipPos = mul(_worldToLightClipMat, worldPos);
o.uv = v.uv;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
// Get the normal in world space
fixed3 worldNormal = normalize(i.worldNormal);
// Get the light direction in world space
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed4 diff = saturate(dot(worldLightDir, worldNormal)) * _Color;
// 取光源坐标系下的深度
float4 scrPos = ComputeScreenPos(i.lightClipPos);
float depTexture = tex2Dproj(_LightDepthTexture, scrPos).r;
float depth = i.lightClipPos.z / i.lightClipPos.w;
#if UNITY_REVERSED_Z
depth = 1 - depth; //(1, 0)-->(0, 1)
#else
depth = depth * 0.5 + 0.5; //(-1, 1)-->(0, 1)
#endif
float shadow = (depTexture )> (depth) ? 1 : 0.5;
return diff * shadow;
}
ENDCG
}
}
}
此时结果是
image.png
此时产生了acne.
需要加个bias修正一下。
float shadow = (depTexture + 0.01 )> (depth) ? 1 : 0.5;
image.png