srp——平行光阴影的一些坑总结

本文主要学习的是网址:https://catlikecoding.com/unity/tutorials/custom-srp/directional-shadows/
的内容。

1、坑1——是否注意到系统是否采用了reverse-z了,如果是则要设置正确的投影矩阵:

 Matrix4x4 ConvertToAtlasMatrix(Matrix4x4 m, Vector2 offset, int split)
    {
        if (SystemInfo.usesReversedZBuffer) //这个很重要呀,否则z方向没有翻转
        {
            m.m20 = -m.m20;
            m.m21 = -m.m21;
            m.m22 = -m.m22;
            m.m23 = -m.m23;
        }
        float scale = 1f / split;
        m.m00 = (0.5f * (m.m00 + m.m30) + offset.x * m.m30) * scale;
        m.m01 = (0.5f * (m.m01 + m.m31) + offset.x * m.m31) * scale;
        m.m02 = (0.5f * (m.m02 + m.m32) + offset.x * m.m32) * scale;
        m.m03 = (0.5f * (m.m03 + m.m33) + offset.x * m.m33) * scale;

        m.m10 = (0.5f * (m.m10 + m.m30) + offset.y * m.m30) * scale;
        m.m11 = (0.5f * (m.m11 + m.m31) + offset.y * m.m31) * scale;
        m.m12 = (0.5f * (m.m12 + m.m32) + offset.y * m.m32) * scale;
        m.m13 = (0.5f * (m.m13 + m.m33) + offset.y * m.m33) * scale;

        m.m20 = 0.5f * (m.m20 + m.m30);
        m.m21 = 0.5f * (m.m21 + m.m31);
        m.m22 = 0.5f * (m.m22 + m.m32);
        m.m23 = 0.5f * (m.m23 + m.m33);
        return m;
    }

2、是否搞清楚了commandbuffer的执行顺序问题
在绘制阴影之前要做什么?
在绘制阴影之后做什么?

在绘制阴影之前要申请好rt,设置好灯光空间的矩阵:

/// <summary>
    /// 申请shadowmap内存
    /// </summary>
    private void ApplyShadowMap()
    {
        int atlasSize = (int)m_shadowSettings.directinalLightShadow.shadowMapSize;
        m_commandBuffer.GetTemporaryRT(m_directionalShadowTexID, atlasSize, atlasSize, 32, FilterMode.Bilinear, RenderTextureFormat.Shadowmap); //申请内存
        m_commandBuffer.SetRenderTarget(m_directionalShadowTexID, RenderBufferLoadAction.DontCare, RenderBufferStoreAction.Store); //设置目标
        m_commandBuffer.ClearRenderTarget(true, false, Color.clear); //这个颜色对于深度缓冲不起作用,只对颜色缓冲有作用
        m_context.ExecuteCommandBuffer(m_commandBuffer); //拷贝命令
        m_commandBuffer.Clear();
    }

要设置好矩阵:

int tileIndex = tileOffset + i;
m_directionalMatrices[tileIndex] = ConvertToAtlasMatrix(projectionMatrix * viewMatrix, SetTileViewport(tileIndex, split, tileSize), split); //这个待解读
m_commandBuffer.SetViewProjectionMatrices(viewMatrix, projectionMatrix);
m_context.ExecuteCommandBuffer(m_commandBuffer);
m_commandBuffer.Clear();
m_context.DrawShadows(ref shadowDrawingSettings);

在绘制阴影之后,要将绘制的结果,设置到shader中去:

m_commandBuffer.SetGlobalMatrixArray(m_directionalShadowMatricesID, m_directionalMatrices);
m_commandBuffer.SetGlobalVectorArray(m_cascadeCullingSpheresID, m_cascadeCullingSpheres);
m_commandBuffer.SetGlobalInt(m_cascadeCountId, m_shadowSettings.directinalLightShadow.CascadeCount);

m_context.ExecuteCommandBuffer(m_commandBuffer);
m_commandBuffer.Clear();

主要设置到shader中,需要通过ExecuteCommandBuffer才能生效的,否则会出现阴影绘制不正确的结果。

3、物体使用的shader中是否包含了:Tags{“LightMode”=“ShadowCaster”}pass呢?
如果需要投影,则需要书写上面的pass。

Pass
		{
			Tags{"LightMode"="ShadowCaster"}
			ColorMask 0 //because we only need to write depth disable writing color data, by adding ColorMask 0 before the HLSL program.
			HLSLPROGRAM
			#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
			float4x4 unity_ObjectToWorld;
			float4x4 unity_WorldToObject;
			float4x4 unity_MatrixV;
			float4x4 unity_MatrixVP;
			float4x4 glstate_matrix_projection;
			real4 unity_WorldTransformParams;
			float3 _WorldSpaceCameraPos;

			#define UNITY_MATRIX_M unity_ObjectToWorld
			#define UNITY_MATRIX_I_M unity_WorldToObject
			#define UNITY_MATRIX_V unity_MatrixV
			#define UNITY_MATRIX_VP unity_MatrixVP
			#define UNITY_MATRIX_P glstate_matrix_projection

			#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/UnityInstancing.hlsl"
			#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/SpaceTransforms.hlsl"
			#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/EntityLighting.hlsl"

			#pragma multi_compile_instancing
			#pragma vertex vert
			#pragma fragment frag

			struct InputProperty
			{
				float3 positionOS:POSITION; //输入三维
				float2 baseUV:TEXCOORD0;
				UNITY_VERTEX_INPUT_INSTANCE_ID
			};

			struct OutputProperty
			{
				float4 positionCS:SV_POSITION;	//输出四维
				float2 baseUV:VAR_BASE_UV;
				UNITY_VERTEX_INPUT_INSTANCE_ID
			};
			sampler2D _MainTex;
			float4 _MainTex_ST;
			float4 _MainColor;
			OutputProperty vert(InputProperty inputProperty)
			{
				OutputProperty outputProperty;
				UNITY_SETUP_INSTANCE_ID(inputProperty);
				UNITY_TRANSFER_INSTANCE_ID(inputProperty, outputProperty);
				float3 positionWS = TransformObjectToWorld(inputProperty.positionOS);
				outputProperty.positionCS = TransformWorldToHClip(positionWS);

#if UNITY_REVERSED_Z
				outputProperty.positionCS.z =
					min(outputProperty.positionCS.z, outputProperty.positionCS.w * UNITY_NEAR_CLIP_VALUE);
#else
				outputProperty.positionCS.z =
					max(outputProperty.positionCS.z, outputProperty.positionCS.w * UNITY_NEAR_CLIP_VALUE);
#endif
				outputProperty.baseUV = inputProperty.baseUV * _MainTex_ST.xy + _MainTex_ST.zw;
				return outputProperty;
			}

			float4 frag(OutputProperty outputProperty):SV_TARGET
			{
				//现在不做任何事情
				/*UNITY_SETUP_INSTANCE_ID(outputProperty);
				float4 color = tex2D(_MainTex, outputProperty.baseUV);
				color *= _MainColor;*/ 
				return 1;
			}
			ENDHLSL
		}

4、最后在说下,深度纹理采样的采样器的写法

	TEXTURE2D_SHADOW(_DirectionalShadowTex);
			#define SHADOW_SAMPLER linear_clamp //注意这里没有linear_clamp_compare的操作,就是直接采样,不比较
			SAMPLER(SHADOW_SAMPLER); //这个SAMPLER不是SAMPLER_CMP

			float SampleDirectionalShadow(float3 position)
			{
				return SAMPLE_TEXTURE3D(_DirectionalShadowTex, SHADOW_SAMPLER, position);
			}

			ShadowData GetShadowData(SurfaceProperty sp)
			{
				ShadowData data;
				for (int i = 0; i < _CascadeCount; ++i)
				{
					float4 sphere = _CascadeCullingSpheres[i];
					float distanceToSphereCenter = DistanceSquared(sp.position, sphere.xyz); //判断在哪个层级
					if (distanceToSphereCenter < sphere.w)
					{
						data.cascadeIndex = i;
						break;
					}
				}
				if (i == _CascadeCount) data.cascadeIndex = _CascadeCount - 1;
				return data;
			}

			float LightAttenuation(SurfaceProperty sp)
			{
				ShadowData shadowData = GetShadowData(sp);
				int matrixIndex = _DirectionalLightShadowData[0].y + shadowData.cascadeIndex;
				//sp.position -= directionalLightDirection *400.0f; //这里不对
				float3 position = mul(_DirectionalShadowMatrices[0], float4(sp.position, 1.0f)).xyz; //这里我使用了1级,所以直接使用0号矩阵
				float shadow = SampleDirectionalShadow(position);
				if (position.z >= shadow) return 1.0f; //重点是这句,由于采用了reverse-z,所以近处的z要大,远处的z要小
				else return 0.2f;
			}

这样,最终的结果是:
在这里插入图片描述
从上图来看,基本上,展示出了深度的信息,所造成的阴影。
但是有如下几个问题:
1、物体表面有脏脏的感觉,对目前就是要这样的感觉,这是因为深度图的精度问题。
2、这里使用第一个优化,加入normal bias
原理是啥?
尽量让那些脏的地方(就是有黑点阴影的地方),让其变为不在阴影区域内,那么咋办呢?
就是让那些徘徊在阴影和非阴影的地方,统一变成不在阴影中,咋操作呢?
对于法线朝着光方向的,那些点,沿着法线膨胀了,自然离光更近了,不在阴影之中了。
那些法线朝着光方向(有偏移角度的),那些点,则会更远离光,所以在阴影中。
这样的效果为:
在这里插入图片描述
好多了。
3、采样器的宏:
#define SAMPLER(samplerName) SamplerState samplerName
#define SAMPLER_CMP(samplerName) SamplerComparisonState samplerName
在E:\OGL\com.unity.render-pipelines.core-6.9.0\package\ShaderLibrary\API中有具体的声明。

补充:
1、使用PCF的方式进行多次采样
定义关键字: private string[] m_pcfKeywords = new string[] { “PCF3x3”, “PCF5x5”, “PCF7x7” };
设置关键字:

  private void SetPCFKeywords(int enableIndex)
    {
        for (int i = 0; i < m_pcfKeywords.Length; ++i)
        {
            if (i == enableIndex) m_commandBuffer.EnableShaderKeyword(m_pcfKeywords[i]);
            else m_commandBuffer.DisableShaderKeyword(m_pcfKeywords[i]);
        }
    }

然后是shader中的编写:
#pragma multi_compile _ PCF3x3 PCF5x5 PCF7x7

#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Shadow/ShadowSamplingTent.hlsl"

#define MAX_SHADOWED_DIRECTIONAL_LIGHT_COUNT 4
#define MAX_CASCADE_COUNT 4
#define MAX_DIRECTIONAL_LIGHT_COUNT 4

#if defined (PCF3x3)
	#define SampleCount 4
	#define SampleTentFunc SampleShadow_ComputeSamples_Tent_3x3
#elif defined (PCF5x5)
	#define SampleCount 9
	#define SampleTentFunc SampleShadow_ComputeSamples_Tent_5x5
#elif defined (PCF7x7)
	#define SampleCount 16
	#define SampleTentFunc SampleShadow_ComputeSamples_Tent_7x7
#endif

然后是采样得到深度只得平均值:

float LightAttenuation(SurfaceProperty sp)
{
	ShadowData shadowData = GetShadowData(sp);
	int matrixIndex = _DirectionalLightShadowData[0].y + shadowData.cascadeIndex;
	sp.position += sp.normal *_DirectionalLightShadowData[0].z; //这个貌似也可以directionalLightDirection *_DirectionalLightShadowData[0].z;
	float3 position = mul(_DirectionalShadowMatrices[0], float4(sp.position, 1.0f)).xyz;
	
#if defined(SampleTentFunc)
	float weights[SampleCount];
	float2 positions[SampleCount];
	float4 size = _ShadowMapInfo.yyxx;
	SampleTentFunc(size, position.xy, weights, positions);
	float shadow = 0;
	for (int i = 0; i < SampleCount; ++i)
	{
		shadow += weights[i] * SampleDirectionalShadow(float3(positions[i].xy, position.z));
	}
#else
	float shadow = SampleDirectionalShadow(position);
#endif
	if (position.z >= shadow) return 1.0f;
	else return 0.2f;
}

ok,效果如下:
在这里插入图片描述
在这里插入图片描述在这里插入图片描述在这里插入图片描述

平行光级联阴影调试的一些小技巧:
使用cascadeIndex作为绘制的颜色,具体如下:

ShadowData GetShadowData(SurfaceProperty sp)
{
	ShadowData data;
	for (int i = 0; i < _CascadeCount; ++i)
	{
		float4 sphere = _CascadeCullingSpheres[i];
		float distanceToSphereCenter = DistanceSquared(sp.position, sphere.xyz);
		if (distanceToSphereCenter < sphere.w)
		{
			data.cascadeIndex = i;
			break;
		}
	}
	if (i == _CascadeCount) data.cascadeIndex = _CascadeCount - 1;
	return data;
}
float LightAttenuation(SurfaceProperty sp)
{
	ShadowData shadowData = GetShadowData(sp);
	return shadowData.cascadeIndex  * 0.25f; //这里直接在片段着色器中设置颜色即可
}

效果如下:
在这里插入图片描述
这里是四级级联的效果。

在这里插入图片描述
你看这个球,使用了两级进行绘制。
通过某个点的着色使用级联阴影的索引/4,因为最大是4级。进行着色,能够很好的验证是否正确的绘制了级联阴影。至少采样的index能容易的验证出来。

这个阴影怎么了???
在这里插入图片描述
加入normalBias之后的效果:
在这里插入图片描述
注意这里的:
float3 normalBias = sp.normal *_CascadeDatas[shadowData.cascadeIndex].y * _DirectionalLightShadowData[0].z;
normalBias是个三维的变量,开始写成一唯的,导致怎么调都不对。
sp.normal ——物体表面点的法线
_CascadeDatas[shadowData.cascadeIndex].y ——像素的大小
_DirectionalLightShadowData[0].z ——光的normalBias,在Light的Inspector面板可以调整,如果为0,则表现自阴影的问题。

Fade->Filter->Blend
基于深度+基于距离的阴影衰减——两个系数相乘

shadowStrength——阴影的强度
shadow——阴影的衰减系数
这两者是完全不同的概念
shadowStrength,是根据深度进行衰减、根据距离衰减,两者相乘得到的一个数字。
shadow,是经过采样深度图,将点转换到灯光空间,完成采样,进行比较,判断该点是否在阴影中的一个系数。

两者,shadow才是真正的根,shadowStrength只是对这个根,进行缩放得到阴影的强度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值