您需要 登录 才可以下载或查看,没有账号?注册
x
本帖最后由 雨の日曜日 于 2021-10-1 20:23 编辑
written by : Young
Unity's built-in directional light real-time shadow technology is cascaded shadow mapping (CSM). Due to unity encapsulation, it may not meet the real-time shadow requirements of the actual project, but we can see some implementation details from built in shaders, which is open-source from unity, which can be used as a reference for custom implementation of CSM.
Unity内置的方向光实时阴影技术是Cascaded Shadow Mapping(简称CSM)。 由于Unity封装的原因,可能并不能满足实际项目实时阴影的需求,但我们可以从Unity开源出来的built-in shaders看出一些实现细节,这些可以作为自定义实现CSM的参考。
Basic shadow mapping is the basis of CSM. This article implements it first.
基本的Shadow mapping是CSM的基础,本篇先实现它。
principle
Shadow mapping consists of two painting passes. In the first process, the camera is set at the light source to draw the scene, the depth test and write are turned on, and the shadow map is generated. The second process is to draw the scene normally, convert the current clip to the light source space, compare the calculated depth with the depth obtained by sampling the shadow map, and judge whether the current clip is in the shadow
原理
Shadow mapping由两个绘制过程构成。第一个过程,在光源处设置相机绘制场景,深度测试和写入打开,生成shadow map。第二个过程,正常绘制场景,把当前片段转换到光源空间,计算得到的深度与采样shadow map得到的深度比较大小,判断当前片段是否在阴影中。
Implemented with unity
Next, take directional light as an example to briefly describe the steps implemented with unity.
用Unity实现 下面以方向光为例,简述用Unity实现的步骤。
Create a depth camera at a light source
Create an orthographic projection camera at the light source location:
在光源处创建深度相机 在光源位置创建正交投影相机:
public Camera CreateDirLightCamera(){ GameObject goLightCamera = new GameObject("Directional Light Camera"); Camera LightCamera = goLightCamera.AddComponent<Camera>(); LightCamera.backgroundColor = Color.white; LightCamera.clearFlags = CameraClearFlags.SolidColor; LightCamera.orthographic = true; LightCamera.orthographicSize = 6f; LightCamera.nearClipPlane = 0.3f; LightCamera.farClipPlane = 20;
LightCamera.enabled = false;}
It should be noted that the background color of the depth camera is white by default, and a white value of 1 indicates infinity. Ensure that the depth comparison is correct during the normal drawing process of the rear main camera.
需要注意的是深度相机的背景色默认为白色,白色值为1表示无限远。保证后面主相机正常绘制过程中深度比较的正确。
Create render texture
From the frame debugger, it can be seen that the shadow map implemented internally by unity is a variable size 2-power render texture. Our implementation here is consistent with it:
创建render texture 从Frame Debugger可看出Unity内部实现的shadow map是个尺寸可变的2的幂次方render texture。我们这里实现跟其保持一致:
private RenderTexture Create2DTextureFor(Camera cam){ RenderTextureFormat rtFormat = RenderTextureFormat.Default; if (!SystemInfo.SupportsRenderTextureFormat(rtFormat)) rtFormat = RenderTextureFormat.Default;
rt_2d = new RenderTexture(512* shadowResolution, 512 * shadowResolution, 24, rtFormat); rt_2d.hideFlags = HideFlags.DontSave;
Shader.SetGlobalTexture("_gShadowMapTexture", rt_2d);
return rt_2d;}
Unity uses sampler2d_ The shadowmaptexture global variable is used to save the depth map for the shader to access. Here, the global variable is used_ Gshadowmaptexture to save the depth map, prefixed with "_ G "to distinguish and prevent conflict.
Unity用sampler2D _ShadowMapTexture全局变量来保存深度图供shader访问,这里用全局变量_gShadowMapTexture来保存深度图,前缀加”_g”以示区别,防止冲突。
The created render texture is used as the drawing target of the depth camera:
创建完的render texture 作为深度相机的绘制目标:
if (!LightCamera.targetTexture) LightCamera.targetTexture = Create2DTextureFor(LightCamera);
Customize shadow caster Unity draws the depth of objects whose pass contains "lightmode" = "shadowcaster" into the shadow map. Refer to several macros of built in shaders in unity version 4.7.1 on directional light shadow generation:
深度相机绘制
我们可以通过设置深度相机绘制shader为上述自定义shader,设置CullingMask只为Layer为”Caster”的物体生成深度图:
LightCamera.cullingMask = 1 << LayerMask.NameToLayer("Caster");LightCamera.RenderWithShader(shadowCaster, "")
The normal drawing process needs to convert the current clip from world coordinates to light source space coordinates. Unity uses the global variable unity_ Worldtoshadow is accessed by the shader. It is used here_ Gworldtoshow to save this transformation:
正常绘制流程需要把当前片段从世界坐标转换到光源空间坐标。Unity使用全局变量unity_WorldToShadow供shader访问,这里用_gWorldToShadow来保存这个变换:
Matrix4x4 projectionMatrix = GL.GetGPUProjectionMatrix(dirLightCamera.projectionMatrix, false);Shader.SetGlobalMatrix("_gWorldToShadow", projectionMatrix * dirLightCamera.worldToCameraMatrix);
It should be noted here that GL. Getgpupprojectionmatrix is used to deal with the differences of projection matrices of different platforms.
这里要注意的是用GL.GetGPUProjectionMatrix来处理不同平台投影矩阵的差异性。
Custom shadow receiver Shadow calculation is processed during the normal drawing process of the model. Unity's own shader that receives light encapsulates the processing of shadows. Refer to build in shaders to receive several macros of Shadows:
自定义Shadow Receiver 阴影计算在模型正常绘制过程处理。Unity自带的接收光照的shader封装了对阴影的处理。参考built-in shaders接收阴影的几个宏:
#ifdef DIRECTIONAL #define LIGHTING_COORDS(idx1,idx2) SHADOW_COORDS(idx1) #define TRANSFER_VERTEX_TO_FRAGMENT(a) TRANSFER_SHADOW(a) #define LIGHT_ATTENUATION(a) SHADOW_ATTENUATION(a)#endif
There are many macro definitions. Here is a rough list to define the shadow coordinates of float4:
宏定义比较多,这里列个大概,定义一个float4的阴影坐标:
#define unityShadowCoord4 float4#define SHADOW_COORDS(idx1) unityShadowCoord4 _ShadowCoord : TEXCOORD##idx1;
Save current clip_ Gworldtoshow transforms to homogeneous coordinates in light space:
保存当前片段经_gWorldToShadow变换到光源空间的齐次坐标:
#define TRANSFER_SHADOW(a) a._ShadowCoord = mul( unity_WorldToShadow[0], mul( unity_ObjectToWorld, v.vertex ) );
XY after the shadow coordinate perspective division is used for the sampling of shadow map. Z / W represents the depth of the current clip in the light source space. Compare to determine whether it is a shadow:
阴影坐标透视除法后的xy用于shadow map的采样,z/w表示当前片段在光源空间的深度,比较确定是否是阴影:
half shadow = SAMPLE_DEPTH_TEXTURE_PROJ(_ShadowMapTexture, UNITY_PROJ_COORD(shadowCoord)) < (shadowCoord.z / shadowCoord.w) ? _LightShadowData.r : 1.0;
Above code_ Lightshadowdata. X stores shadow intensity.
上面代码_LightShadowData.x存储的是阴影强度。
Custom shadow receive shader: 自定义的Shadow receive shader:
Shader "CustomShadow/Receiver" { SubShader { Tags { "RenderType"="Opaque" } LOD 300
Pass { Name "FORWARD" Tags{ "LightMode" = "ForwardBase" }
CGPROGRAM #include "UnityCG.cginc"
struct v2f { float4 pos : SV_POSITION; float4 shadowCoord : TEXCOORD0; };
uniform float4x4 _gWorldToShadow; uniform sampler2D _gShadowMapTexture; uniform float4 _gShadowMapTexture_TexelSize; uniform float _gShadowStrength;
v2f vert (appdata_full v) { v2f o; o.pos = UnityObjectToClipPos (v.vertex); float4 worldPos = mul(unity_ObjectToWorld, v.vertex); o.shadowCoord = mul(_gWorldToShadow, worldPos);
return o; }
fixed4 frag (v2f i) : COLOR0 { // shadow i.shadowCoord.xy = i.shadowCoord.xy/i.shadowCoord.w; float2 uv = i.shadowCoord.xy; uv = uv*0.5 + 0.5; //(-1, 1)-->(0, 1)
float depth = i.shadowCoord.z / i.shadowCoord.w; #if defined (SHADER_TARGET_GLSL) depth = depth*0.5 + 0.5; //(-1, 1)-->(0, 1) #elif defined (UNITY_REVERSED_Z) depth = 1 - depth; //(1, 0)-->(0, 1) #endif
// sample depth texture float4 col = tex2D(_gShadowMapTexture, uv); float sampleDepth = DecodeFloatRGBA(col); float shadow = sampleDepth < depth ? _gShadowStrength : 1;
return shadow; }
#pragma vertex vert #pragma fragment frag #pragma fragmentoption ARB_precision_hint_fastest ENDCG } }}
Note here:
1. After projection transformation, the sampling coordinate XY range is - 1 to 1, which needs to be mapped to 0 to 1. The Z value also has platform differences to deal with.
2. Customization_ Gshadowstrength and_ Lightshadowdata. X is similar:
这里要注意的是:
1.经过投影变换后,采样坐标xy范围为-1到1,需映射到0到1。z值同样有平台差异问题需处理。
2.自定义_gShadowStrength跟_LightShadowData.x类似:
Shader.SetGlobalFloat("_gShadowStrength", 0.5f);
PCF Soft Shadow PCF (percentage closer filtering) realizes anti aliasing of shadow edges by sampling and averaging nearby pixels for multiple times to achieve the effect of soft shadow. Here is a 3x3 PCF:
PCF 软件阴影 PCF(Percentage Closer Filtering)通过对附近像素多次采样求平均来实现阴影边缘抗锯齿,达到软阴影的效果。下面是个3x3的PCF:
float PCFSample(float depth, float2 uv){ float shadow = 0.0; for (int x = -1; x <= 1; ++x) { for (int y = -1; y <= 1; ++y) { float4 col = tex2D(_gShadowMapTexture, uv + float2(x, y) * _gShadowMapTexture_TexelSize.xy); float sampleDepth = DecodeFloatRGBA(col); shadow += sampleDepth < depth ? _gShadowStrength : 1; } } return shadow /= 9;}
结果
|