- 思路
- 实践
- 获取光源空间ShadowMap[A]
- 获取屏幕空间的深度图[B]
- 获取SSSM(Screen Space Shadow Map)
- 绘制一个全屏的Quad[C]
- 输出SSSM RT Shader
- 在全屏Quad[C]里,制作将屏幕空间深度重建屏幕世界坐标[D]
- 在将屏幕的世界坐标[D]转换到光源空间下的坐标[E]
- 比较[E]与[A]对应的Shadow Map深度,确定是阴影的将在Quad[C]写入屏幕阴影像素
- 完成的输出SSSM RT Shader
- 接收阴影处理:对SSSM采样
- 运行效果
- FrameDebugger查看三大绘制过程
- 屏幕空间的优缺点
- 优点
- 缺点
- Project
现在试试实现SSSM。
思路- 先拿到 光源空间下的 ShadowMap,具体,可以参考:Unity Shader - Custom DirectionalLight ShadowMap 自定义方向光的ShadowMap
- 再拿到屏幕空间的深度图,具体,可以参考:Unity Shader - 获取BuiltIn深度纹理和自定义深度纹理的数据
- 绘制一个全屏的Quad,并且将 屏幕空间的深度图 与 光源空间的ShadowMap阴影图,计算出屏幕空间的阴影图。进一步说明就是:将屏幕空间的深度 重构一下 世界坐标,再构建出世界坐标懂光源你空间的坐标,将深度的世界坐标通过世界到光源矩阵转换光源空间下,这时再比较深度来判定是否在阴影中。最后将这个全屏Quad的渲染目标到一个RT,叫做:SSSM(Screen Space Shadow Map)阴影图。
- 接收阴影处理:对SSSM采样。将SSSM阴影图进shader全局uniform,渲染其他的接收阴影的对象时,将顶点坐标做到屏幕坐标,传入FS,插值后的屏幕坐标来采样SSS阴影图,采样得到的衰减数据与光照做混合计算即可。
先拿到 光源空间下的 ShadowMap,具体,可以参考:Unity Shader - Custom DirectionalLight ShadowMap 自定义方向光的ShadowMap
获取屏幕空间的深度图[B]再拿到屏幕空间的深度图,具体,可以参考:Unity Shader - 获取BuiltIn深度纹理和自定义深度纹理的数据
获取SSSM(Screen Space Shadow Map) 绘制一个全屏的Quad[C]参考之前写的一个:Unity Shader - 模仿RenderImage制作全屏Quad
输出SSSM RT Shader fixed4 frag (v2f i) : SV_Target {
// to world from depth
float linear01depth = (DecodeFloatRG(tex2D(_CustomDepthMap, i.uv).rg));
float3 wp = _WorldSpaceCameraPos.xyz + i.ray * linear01depth;
// return float4(wp, 1);
// world to lightspace
// 取得当前绘制顶点相对光源空间下的坐标,即:阴影映射坐标
float4 shadowCoord = mul(_CustomShadowMapLightSpaceMatrix, float4(wp, 1));
// output SSSM atten
return GetAtten(shadowCoord);
}
在全屏Quad[C]里,制作将屏幕空间深度重建屏幕世界坐标[D]
参考:Unity Shader - 根据片段深度重建片段的世界坐标
// to world from depth
float linear01depth = (DecodeFloatRG(tex2D(_CustomDepthMap, i.uv).rg));
float3 wp = _WorldSpaceCameraPos.xyz + i.ray * linear01depth;
// return float4(wp, 1);
在将屏幕的世界坐标[D]转换到光源空间下的坐标[E]
// world to lightspace
// 取得当前绘制顶点相对光源空间下的坐标,即:阴影映射坐标
float4 shadowCoord = mul(_CustomShadowMapLightSpaceMatrix, float4(wp, 1));
比较[E]与[A]对应的Shadow Map深度,确定是阴影的将在Quad[C]写入屏幕阴影像素
// output SSSM atten
return GetAtten(shadowCoord);
完成的输出SSSM RT Shader
// jave.lin 2020.04.14 - 根据屏幕深度重构世界坐标 - output SSSM atten
Shader "Custom/ReconstructSSSMFromDepth" {
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
uint vid : SV_VertexID;
};
struct v2f {
float4 vertex : SV_POSITION;
float2 uv : TEXCOORD0;
float3 ray : TEXCOORD1;
};
// reconstruct depth variables
sampler2D _CustomDepthMap;
float4x4 _Ray;
// shadow map veriables
float4 _CustomShadowMap_TexelSize;
sampler2D _CustomShadowMap;
float4x4 _CustomShadowMapLightSpaceMatrix;
float _CustomShadowStrengthen;
float _CustomShadowBias;
int _CustomShadowSmoothType;
float _CustomShadowPCFSpread;
float _CustomShadowBlurDistance;
float _CustomShadowBlurWeight[25];
// 获取光照衰减系数
float GetAtten(float4 shadowCoord) {
float2 uv = shadowCoord.xy / shadowCoord.w;
uv = uv * 0.5 + 0.5; // (-1,1)->(0,1)
// clamp to edge color : 1
if (uv.x > 1 || uv.y > 1 || uv.x (0,1)
#endif
if (fragDepth > 1) return 1;
float strengthen = _CustomShadowStrengthen;
float atten = 1;
if (_CustomShadowSmoothType == 0) {// hard
float shadowMapDepth = DecodeFloatRG(tex2D(_CustomShadowMap, uv).xy);
if (fragDepth - _CustomShadowBias > shadowMapDepth) {
atten = 1 - strengthen;
}
} else if (_CustomShadowSmoothType == 1) {// PCFs_Like
float2 offset = 0;
float minus_step = strengthen / 9.0;
for(int i = -1; i center_shadowMapDepth) {
distance = saturate(fragDepth- _CustomShadowBias - center_shadowMapDepth);
distance /= _CustomShadowBlurDistance;
}
float2 offset = 0;
float w = 0;
float idx = 0;
int i =0, j=0;
for(i = -2; i 0 && appliedAtten) {
half3 hDir = normalize(viewDir + lightDir);
fixed HdotN = max(0, dot(hDir, i.worldNormal));
specular = pow(HdotN, 64);
}
fixed4 combinedCol = 0;
// ambient 是模拟各个方向的光所以不需要atten
combinedCol.xyz = ambient +
diffuse * atten * albedo.rgb * _LightColor0.rgb * _MainColor.rgb +
specular * atten * _LightColor0.rgb;
return combinedCol;
} else {
return tex2D(_MainTex, i.uv) * _MainColor * atten;
}
}
v2f vert (a2v v) {
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
o.screenPos = ComputeScreenPos(o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target {
float atten = _CustomShadowEnable ? GetAtten(i) : 1;
return shading(i, atten);
}
ENDCG
SubShader {
Tags { "RenderType"="Opaque" "MyShadowMap"="1" }
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
}
运行效果
但是Scene视图中的阴影就有问题了。 具体什么原因不知道,但我就不纠结了。如下图:
貌似还不知道有点有啥用,为了研究一下而学习。(有可能是为了可以根据 深度容差来区别阴影边缘 + 阴影模糊,就可以比较轻松的做到“软”阴影的效果)
但看到有些文章说是为了所CSM(cascade shadow map)而用的(那意思:不在屏幕空间下做不可以了?显然不是的,因为CSM的整体思路就是对绘制的片段在世界坐标下与镜头的距离来做SM的层级的选择确定后再采样的,所以屏幕空间阴影有什么优点,我也暂时没有兴趣了解,后面那天需要用到,我再去细究)
缺点没写如深度的物体间接受不了阴影。如:半透明。
在非SSSM中,我们可以对半透明物体采样Shadow map来着色阴影的,如下图: 虽然可以接收阴影了。 但是半透明的物体,没有投射阴影是很奇怪的。
backup : UnityShader_CustomShadow_includeSSSM_2018.3.0f2
2021/10/21 在阅读 Unity 2019.4.30f1 的 URP 7.7.1 版本的 shader : Packages/com.unity.render-pipeline.universal/Shaders/Utils/ScreenSpaceShadows.shader
中的代码,发现有一段很简单的就描述了如何生成 SSSM:(已添加了一些注释便于阅读理解)
half4 Fragment(Varyings input) : SV_Target
{
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
// jave.lin : 获取相机拍摄深度
float deviceDepth = SAMPLE_TEXTURE2D_X(_CameraDepthTexture, sampler_CameraDepthTexture, input.uv.xy).r;
// jave.lin : 根据查看下图了解,不同的一些渲染平台,深度正值增长方向都可能有所不同
#if UNITY_REVERSED_Z
deviceDepth = 1 - deviceDepth;
#endif
deviceDepth = 2 * deviceDepth - 1; //NOTE: Currently must massage depth before computing CS position.
// jave.lin : 将深度值 转换到 view space pos
float3 vpos = ComputeViewSpacePosition(input.uv.zw, deviceDepth, unity_CameraInvProjection);
// jave.lin : 将 view space pos 转到 world space pos
float3 wpos = mul(unity_CameraToWorld, float4(vpos, 1)).xyz;
// jave.lin : 将 world space world 转到 shadow/light space pos
//Fetch shadow coordinates for cascade.
float4 coords = TransformWorldToShadowCoord(wpos);
// Screenspace shadowmap is only used for directional lights which use orthogonal projection.
ShadowSamplingData shadowSamplingData = GetMainLightShadowSamplingData();
half4 shadowParams = GetMainLightShadowParams();
// 根据相关配置参数 + shadow space pos 采样 + 判断是否中阴影中
return SampleShadowmap(TEXTURE2D_ARGS(_MainLightShadowmapTexture, sampler_MainLightShadowmapTexture), coords, shadowSamplingData, shadowParams, false);
}
UNITY_REVERSED_Z
的宏定义在下面几个文件中有定义,可以自行搜索: