参考:https://zhuanlan.zhihu.com/p/45679584
实验的原理很简单:
1、从摄像机角度拍摄一张深度图,为cameraDepth
2、从灯光角度拍摄一张深度图,为lightDepth
3、将两个图blit,重建世界坐标点,转换到灯光空间,采样灯光空间的深度,比较,然后判断是否在阴影里,最后生成一个新的图,记作screenspaceShadowTex
4、在绘制物体的时候,用物体的屏幕坐标uv,采样这个screenspaceShadowTex,乘以颜色,即可。
为啥叫做屏幕空间。
其实质和shadowmap类似。唯一的不同是,我们将阴影的结果放在一个图上,在绘制物体的时候,用物体的屏幕坐标uv采样,得到系数即可。
实现:
1、从摄像机角度拍摄一张深度图,为cameraDepth
_depthCamera.RenderWithShader(shadowCasterMat.shader, "");
Shader "Kingsoft/CustomShadow/Caster"
{
SubShader
{
Tags {
"RenderType" = "Opaque"
}
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float4 pos : SV_POSITION;
float2 depth:TEXCOORD0;
};
v2f vert (appdata_full v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.depth = o.pos.zw;
return o;
}
fixed4 frag (v2f i) : COLOR
{
float depth = i.depth.x/i.depth.y;
#if defined(SHADER_API_GLES) || defined(SHADER_API_GLES3)
depth = depth*0.5 + 0.5; //(-1, 1)-->(0, 1)
#elif defined (UNITY_REVERSED_Z)
depth = 1 - depth; //(1, 0)-->(0, 1)
#endif
return depth;
}
ENDCG
}
}
}
2、从灯光角度拍摄一张深度图,为lightDepth
if (!_lightCamera)
{
_lightCamera = CreateLightCamera();
_lightCamera.transform.parent = _light.transform;
_lightCamera.transform.localPosition = Vector3.zero;
_lightCamera.transform.localRotation = Quaternion.identity;
}
_lightCamera.orthographicSize = orthographicSize;
_lightCamera.nearClipPlane = nearClipPlane;
_lightCamera.farClipPlane = farClipPlane;
_lightCamera.RenderWithShader(shadowCasterMat.shader, "");
3、将两个图blit,重建世界坐标点,转换到灯光空间,采样灯光空间的深度,比较,然后判断是否在阴影里,最后生成一个新的图,记作screenspaceShadowTex
Matrix4x4 projectionMatrix = GL.GetGPUProjectionMatrix(Camera.main.projectionMatrix, false);
Shader.SetGlobalMatrix("_inverseVP", Matrix4x4.Inverse(projectionMatrix * Camera.main.worldToCameraMatrix));
shadowCollectorMat.SetTexture("_CameraDepthTex", depthTexture);
shadowCollectorMat.SetTexture("_LightDepthTex", lightDepthTexture);
Graphics.Blit(depthTexture, screenSpaceShadowTexture, shadowCollectorMat);
Shader.SetGlobalTexture("_ScreenSpceShadowTexture", screenSpaceShadowTexture);
projectionMatrix = GL.GetGPUProjectionMatrix(_lightCamera.projectionMatrix, false);
Shader.SetGlobalMatrix("_WorldToShadow", projectionMatrix * _lightCamera.worldToCameraMatrix);
混合的shader:
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Kingsoft/CustomShadow/Collector"
{
Subshader
{
ZTest off
Fog { Mode Off }
Cull back
Lighting Off
ZWrite Off
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
uniform sampler2D _CameraDepthTex;
uniform sampler2D _LightDepthTex;
uniform float4x4 _inverseVP;
uniform float4x4 _WorldToShadow;
struct Input
{
float4 texcoord : TEXCOORD0;
float4 vertex : POSITION;
};
struct Output
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
Output vert (Input i)
{
Output o;
o.pos = UnityObjectToClipPos (i.vertex);
o.uv = i.texcoord;
return o;
}
fixed4 frag( Output i ) : SV_TARGET
{
fixed4 cameraDepth = tex2D(_CameraDepthTex, i.uv);
half depth_ = cameraDepth.r;
#if defined (SHADER_TARGET_GLSL)
depth_ = depth_ * 2 - 1; // (0, 1)-->(-1, 1)
#elif defined (UNITY_REVERSED_Z)
depth_ = 1 - depth_; // (0, 1)-->(1, 0)
#endif
// reconstruct world position by depth;
float4 clipPos;
clipPos.xy = i.uv * 2 - 1;
clipPos.z = depth_;
clipPos.w = 1;
float4 posWorld = mul(_inverseVP, clipPos);
posWorld /= posWorld.w;
half4 shadowCoord = mul(_WorldToShadow, posWorld);
half2 uv = shadowCoord.xy;
uv = uv*0.5 + 0.5; //(-1, 1)-->(0, 1)
half depth = shadowCoord.z / shadowCoord.w;
#if defined(SHADER_API_GLES) || defined(SHADER_API_GLES3)
depth = depth*0.5 + 0.5; //(-1, 1)-->(0, 1)
#elif defined (UNITY_REVERSED_Z)
depth = 1 - depth; //(1, 0)-->(0, 1)
#endif
half4 col = tex2D(_LightDepthTex, uv);
half sampleDepth = col.r;
half shadow = (sampleDepth < depth - 0.05) ? 0.1 : 1;
return shadow;
}
ENDCG
}
}
Fallback off
}
4、在绘制物体的时候,用物体的屏幕坐标uv,采样这个screenspaceShadowTex,乘以颜色,即可。
fixed4 frag (v2f_full i) : COLOR0
{
fixed4 tex = tex2D (_MainTex, i.uv.xy)*_Color;
i.screenPos.xy = i.screenPos.xy / i.screenPos.w;
float2 sceneUVs = i.screenPos.xy*0.5 + 0.5;
#if UNITY_UV_STARTS_AT_TOP
sceneUVs.y = _ProjectionParams.x < 0 ? 1 - sceneUVs.y : sceneUVs.y;
#endif
half shadow = tex2D(_ScreenSpceShadowTexture, sceneUVs).r;
fixed4 c = tex2D(_MainTex, i.uv.xy) * _Color * shadow;
return c;
}
ok,效果自然是很差的:
因为没有做软阴影,但是大致的思想是实现了。
其中矩阵的传递、z反转、y反转等等,这些之前的博客都有涉及,不再展开。
最后的最后,再总结下,什么是屏幕空间阴影。
所谓的屏幕空间阴影,是在绘制物体的时候,使用屏幕坐标空间的uv,采样一个shadow图,然后乘以颜色。
这个shadow图,是怎么来的,是通过两个深度图(摄像机和灯光空间的深度图),通过世界坐标的重建,然后再转换到灯光空间,比较深度值,得到的一张shadow图。
效率怎样呢?内存上,使用了3个rt,gpu上,画3次的全场景的物体。第一次发生在从摄像机角度绘制;第二次发生在从灯光角度绘制;第三次在实际绘制物体上。