unity屏幕空间阴影

参考: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次的全场景的物体。第一次发生在从摄像机角度绘制;第二次发生在从灯光角度绘制;第三次在实际绘制物体上。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值