Unity Internal-ScreenSpaceShadows 分析

一、代码结构

// Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt)Shader "Hidden/Internal-ScreenSpaceShadows" {Properties {_ShadowMapTexture ("", any) = "" {} // 阴影贴图纹理(级联阴影图集)_ODSWorldTexture("", 2D) = "" {} // 存储世界空间坐标或额外数据的纹理}CGINCLUDE// 声明阴影贴图相关变量UNITY_DECLARE_SHADOWMAP(_ShadowMapTexture);float4 _ShadowMapTexture_TexelSize; // 阴影贴图纹素尺寸,用于计算偏移#define SHADOWMAPSAMPLER_AND_TEXELSIZE_DEFINEDsampler2D _ODSWorldTexture; // 世界空间坐标纹理#include "UnityCG.cginc" // Unity通用CG函数#include "UnityShadowLibrary.cginc" // 阴影相关库函数// 级联阴影混合配置(0 表示禁用混合)#define UNITY_USE_CASCADE_BLENDING 0#define UNITY_CASCADE_BLEND_DISTANCE 0.1 // 级联过渡距离// 顶点着色器输入结构struct appdata {float4 vertex : POSITION;float2 texcoord : TEXCOORD0;#if defined(UNITY_STEREO_INSTANCING_ENABLED) || defined(UNITY_STEREO_MULTIVIEW_ENABLED)float3 ray0 : TEXCOORD1;float3 ray1 : TEXCOORD2;#elsefloat3 ray : TEXCOORD1;#endifUNITY_VERTEX_INPUT_INSTANCE_ID};// 片段着色器输入结构struct v2f {float4 pos : SV_POSITION;// xy uv / zw screenposfloat4 uv : TEXCOORD0;// View space ray, for perspective casefloat3 ray : TEXCOORD1;// Orthographic view space positions (need xy as well for oblique matrices)float3 orthoPosNear : TEXCOORD2;float3 orthoPosFar  : TEXCOORD3;UNITY_VERTEX_INPUT_INSTANCE_IDUNITY_VERTEX_OUTPUT_STEREO};// 顶点着色器(当前为空,需根据需求实现)v2f vert (appdata v) {}// 声明深度纹理(用于计算相机空间坐标)UNITY_DECLARE_DEPTH_TEXTURE(_CameraDepthTexture);// 级联投影的尺寸比例(相对于第一个级联)float4 unity_ShadowCascadeScales;// 根据阴影划分方式定义级联权重计算函数#if defined (SHADOWS_SPLIT_SPHERES) // 使用球形分割的级联#define GET_CASCADE_WEIGHTS(wpos, z)    getCascadeWeights_splitSpheres(wpos)#else // 使用默认的级联方式(基于距离)#define GET_CASCADE_WEIGHTS(wpos, z)    getCascadeWeights(wpos, z)#endif// 根据级联数量定义阴影坐标计算函数#if defined (SHADOWS_SINGLE_CASCADE) // 单级联模式#define GET_SHADOW_COORDINATES(wpos,cascadeWeights) getShadowCoord_SingleCascade(wpos)#else // 多级联模式#define GET_SHADOW_COORDINATES(wpos,cascadeWeights) getShadowCoord(wpos,cascadeWeights)#endif// 获取级联权重(多级联模式)inline fixed4 getCascadeWeights(float3 wpos, float z) { }// 获取级联权重(球形分割模式)inline fixed4 getCascadeWeights_splitSpheres(float3 wpos) { }// 获取阴影坐标(多级联模式)inline float4 getShadowCoord( float4 wpos, fixed4 cascadeWeights ) { }// 获取阴影坐标(单级联模式)inline float4 getShadowCoord_SingleCascade( float4 wpos ) { }// 根据深度和逆投影矩阵计算相机空间坐标(PS阶段计算)inline float3 computeCameraSpacePosFromDepthAndInvProjMat(v2f i) { }// 根据顶点着色器输出信息计算相机空间坐标(VS阶段计算)inline float3 computeCameraSpacePosFromDepthAndVSInfo(v2f i) { }// 计算相机空间坐标(根据子着色器配置选择实现方式)inline float3 computeCameraSpacePosFromDepth(v2f i) { }// 硬阴影片段着色器fixed4 frag_hard (v2f i) : SV_Target { }// 软阴影片段着色器(PCF)fixed4 frag_pcfSoft(v2f i) : SV_Target { }ENDCG// 子着色器:硬阴影(SM 2.0 及以下)SubShader {Tags{ "ShadowmapFilter" = "HardShadow" }Pass {//具体代码在下方}}// 子着色器:硬阴影(强制PS阶段逆投影,通用但精度较低)SubShader {Tags{ "ShadowmapFilter" = "HardShadow_FORCE_INV_PROJECTION_IN_PS" }Pass{//具体代码在下方}}// 子着色器:软阴影(PCF,SM 3.0 及以上)SubShader {Tags {"ShadowmapFilter" = "PCF_SOFT"}Pass {//具体代码在下方}}// 子着色器:软阴影(强制PS阶段逆投影,SM 3.0 及以上)SubShader{Tags{ "ShadowmapFilter" = "PCF_SOFT_FORCE_INV_PROJECTION_IN_PS" }Pass{//具体代码在下方}}Fallback Off // 无备用着色器
}

二、HardShadow

    // 子着色器:硬阴影(SM 2.0 及以下)SubShader {Tags{ "ShadowmapFilter" = "HardShadow" }Pass {ZWrite Off ZTest Always Cull Off // 禁用深度写入和剔除CGPROGRAM#pragma vertex vert#pragma fragment frag_hard#pragma multi_compile_shadowcollector // 多编译阴影收集器变体// 使用顶点着色器传递的中间数据计算相机空间坐标inline float3 computeCameraSpacePosFromDepth(v2f i) {return computeCameraSpacePosFromDepthAndVSInfo(i);}ENDCG}}

1.vert

v2f vert (appdata v) 
{v2f o;UNITY_SETUP_INSTANCE_ID(v); // 如果使用了GPU实例化技术,则设置实例ID,以便在顶点着色器中正确访问实例相关的数据。UNITY_TRANSFER_INSTANCE_ID(v, o); // 将实例ID从输入结构体传输到输出结构体,使得片元着色器可以访问这个ID。UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); // 初始化与立体渲染相关的输出数据,确保左右眼图像正确生成。float4 clipPos;#if defined(STEREO_CUBEMAP_RENDER_ON) // 如果定义了STEREO_CUBEMAP_RENDER_ON宏,表示正在对立体环境贴图进行渲染。clipPos = mul(UNITY_MATRIX_VP, mul(unity_ObjectToWorld, v.vertex)); // 先将顶点坐标从对象空间转换到世界空间,然后转换到裁剪空间。
#else // 否则,默认情况下的处理方式。clipPos = UnityObjectToClipPos(v.vertex); // 直接将顶点坐标从对象空间转换为裁剪空间。
#endifo.pos = clipPos; // 将计算得到的裁剪空间坐标赋值给输出结构体的pos成员。o.uv.xy = v.texcoord; // 从输入结构体中获取纹理坐标,并赋值给输出结构体的uv成员的xy分量。// unity_CameraInvProjection at the PS level. o.uv.zw = ComputeNonStereoScreenPos(clipPos); // 计算屏幕空间位置,并赋值给uv成员的zw分量,注意这里指的是非立体渲染的情况。// Perspective case
#if defined(UNITY_STEREO_INSTANCING_ENABLED) || defined(UNITY_STEREO_MULTIVIEW_ENABLED)// 如果启用了立体渲染的实例化或多重视图功能,根据当前是左眼还是右眼选择正确的光线向量。o.ray = unity_StereoEyeIndex == 0 ? v.ray0 : v.ray1;
#else// 默认情况下,直接使用传入的光线向量。o.ray = v.ray;
#endif
}

2.frag_hard


fixed4 frag_hard (v2f i) : SV_Target
{UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i); // 在顶点处理后设置立体视觉的眼睛索引,这对于正确采样阴影贴图纹理数组中的对应切片是必需的。float4 wpos; // 声明一个float4类型的变量wpos,用于存储世界空间位置。float3 vpos; // 声明一个float3类型的变量vpos,用于存储相机空间位置。#if defined(STEREO_CUBEMAP_RENDER_ON) // 如果定义了STEREO_CUBEMAP_RENDER_ON宏,则表示正在进行立体全景图渲染。// 使用提供的世界坐标纹理来获取世界空间位置wpos.xyz = tex2D(_ODSWorldTexture, i.uv.xy).xyz; // 从_ODSWorldTexture纹理中根据uv坐标采样得到世界空间位置的xyz分量。wpos.w = 1.0f; // 设置w分量为1.0f,以确保正确的齐次坐标转换。// 将世界坐标转换为相机空间坐标vpos = mul(unity_WorldToCamera, wpos).xyz; // 通过乘以unity_WorldToCamera矩阵将wpos从世界空间转换到相机空间。
#else // 如果没有启用立体全景图渲染。// 计算相机空间的位置vpos = computeCameraSpacePosFromDepth(i); // 根据深度信息计算相机空间位置。// 将相机空间位置转换回世界空间位置wpos = mul (unity_CameraToWorld, float4(vpos,1)); // 通过乘以unity_CameraToWorld矩阵将vpos从相机空间转换回到世界空间。
#endif// 获取级联权重fixed4 cascadeWeights = GET_CASCADE_WEIGHTS(wpos, vpos.z); // 根据世界空间位置和深度计算级联阴影映射的权重。// 获取阴影坐标float4 shadowCoord = GET_SHADOW_COORDINATES(wpos, cascadeWeights); // 根据世界空间位置和级联权重计算阴影坐标。// 执行单次采样以确定是否处于阴影中fixed shadow = UNITY_SAMPLE_SHADOW(_ShadowMapTexture, shadowCoord); // 使用阴影坐标在_ShadowMapTexture上进行采样,判断当前像素是否处于阴影中。// 根据阴影结果混合颜色shadow = lerp(_LightShadowData.r, 1.0, shadow); // 使用线性插值(lerp)函数,基于阴影结果混合光照阴影数据和全亮的颜色。fixed4 res = shadow; // 将shadow结果赋值给输出颜色res。return res; // 返回最终的颜色结果。
}

3.computeCameraSpacePosFromDepthAndVSInfo

/**
* 根据深度纹理和视图相关的参数计算一个给定点在相机空间中的坐标
*/
inline float3 computeCameraSpacePosFromDepthAndVSInfo(v2f i)
{// 从_CameraDepthTexture纹理中根据uv坐标采样得到当前像素的深度值zdepth。float zdepth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv.xy);// 将非线性深度值转换为0到1范围内的线性深度值。对于正交投影,直接使用原始深度值zdepth。// unity_OrthoParams.w用来区分是否是正交投影:0表示透视投影,1表示正交投影。float depth = lerp(Linear01Depth(zdepth), zdepth, unity_OrthoParams.w);#if defined(UNITY_REVERSED_Z)// 如果定义了UNITY_REVERSED_Z宏,则反转深度值,因为Unity在某些平台上使用反向Z缓冲区。zdepth = 1 - zdepth;
#endif// 计算视角位置,针对透视投影的情况:// 使用i.ray(从顶点着色器传过来的光线方向)乘以depth(深度值),得到视角下的位置vposPersp。float3 vposPersp = i.ray * depth;// 针对正交投影的情况:// 使用线性插值lerp函数,基于zdepth在i.orthoPosNear(近裁剪面位置)和i.orthoPosFar(远裁剪面位置)之间进行插值,得到视角下的位置vposOrtho。float3 vposOrtho = lerp(i.orthoPosNear, i.orthoPosFar, zdepth);// 根据是否为正交投影选择合适的视角位置:// unity_OrthoParams.w为1时选择vposOrtho(正交投影情况),否则选择vposPersp(透视投影情况)。float3 camPos = lerp(vposPersp, vposOrtho, unity_OrthoParams.w);// 返回计算得到的相机空间坐标。return camPos.xyz;
}

4.GET_CASCADE_WEIGHTS

// 如果定义了SHADOWS_SPLIT_SPHERES宏,则使用getCascadeWeights_splitSpheres函数计算级联权重。
#if defined (SHADOWS_SPLIT_SPHERES)// 定义宏GET_CASCADE_WEIGHTS,当调用时将使用世界坐标wpos作为参数调用getCascadeWeights_splitSpheres函数。#define GET_CASCADE_WEIGHTS(wpos, z)    getCascadeWeights_splitSpheres(wpos)
#else// 否则,默认情况下使用getCascadeWeights函数计算级联权重,该函数需要世界坐标wpos和深度值z作为参数。#define GET_CASCADE_WEIGHTS(wpos, z)    getCascadeWeights(wpos, z)
#endif// 如果定义了SHADOWS_SINGLE_CASCADE宏,则处理单个级联的情况。
#if defined (SHADOWS_SINGLE_CASCADE)// 定义宏GET_SHADOW_COORDINATES,当调用时将仅使用世界坐标wpos作为参数调用getShadowCoord_SingleCascade函数。#define GET_SHADOW_COORDINATES(wpos,cascadeWeights) getShadowCoord_SingleCascade(wpos)
#else// 否则,默认情况下使用getShadowCoord函数获取阴影坐标,该函数需要世界坐标wpos和级联权重cascadeWeights作为参数。#define GET_SHADOW_COORDINATES(wpos,cascadeWeights) getShadowCoord(wpos,cascadeWeights)
#endif

5.getCascadeWeights_splitSpheres

/*** 基于片段的世界位置以及每个级联分割球体的位置来获取级联权重。* 返回一个仅有一个组件设置为对应适当级联的float4值。** @param wpos 片元的世界坐标。* @return 四个浮点数,每个代表一个级联是否适用(0或1)。*/
inline fixed4 getCascadeWeights_splitSpheres(float3 wpos)
{// 计算当前世界位置到每个分割球体中心的距离向量。float3 fromCenter0 = wpos.xyz - unity_ShadowSplitSpheres[0].xyz;float3 fromCenter1 = wpos.xyz - unity_ShadowSplitSpheres[1].xyz;float3 fromCenter2 = wpos.xyz - unity_ShadowSplitSpheres[2].xyz;float3 fromCenter3 = wpos.xyz - unity_ShadowSplitSpheres[3].xyz;// 计算这些距离向量的平方长度。float4 distances2 = float4(dot(fromCenter0, fromCenter0), dot(fromCenter1, fromCenter1), dot(fromCenter2, fromCenter2), dot(fromCenter3, fromCenter3));// 如果距离平方小于相应的分割球体半径平方,则认为该级联适用。fixed4 weights = float4(distances2 < unity_ShadowSplitSqRadii);// 确保只有一个级联被选中,通过饱和减法消除其他级联的影响。weights.yzw = saturate(weights.yzw - weights.xyz);return weights;
}

6.getCascadeWeights

/*** 根据片段的世界位置和深度值获取级联权重。* 返回一个仅有一个组件设置为对应适当级联的float4值。** @param wpos 片元的世界坐标。* @param z    深度值。* @return 四个浮点数,每个代表一个级联是否适用(0或1)。*/
inline fixed4 getCascadeWeights(float3 wpos, float z)
{// 检查深度z是否大于等于每个级联的近裁剪面距离。fixed4 zNear = float4(z >= _LightSplitsNear);// 检查深度z是否小于每个级联的远裁剪面距离。fixed4 zFar = float4(z < _Light_SplitsFar);// 计算级联权重,只有在z同时满足zNear和zFar条件时,权重才为1。fixed4 weights = zNear * zFar;return weights;
}

7.getShadowCoord_SingleCascade

/*** 同getShadowCoord函数,但针对单个级联进行了优化。** @param wpos 片元的世界坐标。* @return 针对单个级联优化后的阴影贴图坐标。*/
inline float4 getShadowCoord_SingleCascade(float4 wpos)
{// 直接将世界坐标转换为第一个级联的阴影坐标,并设置w分量为0。return float4(mul(unity_WorldToShadow[0], wpos).xyz, 0);
}

8.getShadowCoord

/*** 根据给定的世界位置和z深度返回阴影映射坐标。* 这些坐标属于包含所有级联的地图的阴影映射图集。** @param wpos            片元的世界坐标。* @param cascadeWeights  级联权重。* @return 阴影贴图坐标。*/
inline float4 getShadowCoord(float4 wpos, fixed4 cascadeWeights)
{// 将世界坐标转换为每个级联的阴影坐标。float3 sc0 = mul(unity_WorldToShadow[0], wpos).xyz;float3 sc1 = mul(unity_WorldToShadow[1], wpos).xyz;float3 sc2 = mul(unity_WorldToShadow[2], wpos).xyz;float3 sc3 = mul(unity_WorldToShadow[3], wpos).xyz;// 根据级联权重混合这些阴影坐标。float4 shadowMapCoordinate = float4(sc0 * cascadeWeights[0] + sc1 * cascadeWeights[1] + sc2 * cascadeWeights[2] + sc3 * cascadeWeights[3], 1);
#if defined(UNITY_REVERSED_Z)// 如果启用了反向Z缓冲区,则调整z坐标以避免精度问题。float noCascadeWeights = 1 - dot(cascadeWeights, float4(1, 1, 1, 1));shadowMapCoordinate.z += noCascadeWeights;
#endifreturn shadowMapCoordinate;
}

三、HardShadow_FORCE_INV_PROJECTION_IN_PS

    // ----------------------------------------------------------------------------------------// 子着色器:硬阴影(强制PS阶段逆投影,通用但精度较低)SubShader {Tags{ "ShadowmapFilter" = "HardShadow_FORCE_INV_PROJECTION_IN_PS" }Pass{ZWrite Off ZTest Always Cull OffCGPROGRAM#pragma vertex vert#pragma fragment frag_hard#pragma multi_compile_shadowcollector// 在PS阶段使用逆投影矩阵计算坐标(兼容性更高但性能较低)inline float3 computeCameraSpacePosFromDepth(v2f i) {return computeCameraSpacePosFromDepthAndInvProjMat(i);}ENDCG}}

1.vert

同HardShadow

2.frag_hard

同HardShadow

3.computeCameraSpacePosFromDepthAndInvProjMat

/**
* 从深度信息和逆投影矩阵获取相机空间坐标。
*/
inline float3 computeCameraSpacePosFromDepthAndInvProjMat(v2f i)
{// 使用提供的_CameraDepthTexture纹理和uv坐标采样得到当前像素的深度值zdepth。float zdepth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv.xy);#if defined(UNITY_REVERSED_Z)// 如果定义了UNITY_REVERSED_Z宏,则反转深度值,因为Unity在某些平台上使用反向Z缓冲区。zdepth = 1 - zdepth;#endif// 视角位置计算,适用于倾斜裁剪的投影情况:// 这种方法不如通过插值光线和深度计算的方法精确或快速,但它能处理更复杂的投影方式。// 根据i.uv.zw(通常包含未变换的屏幕空间坐标)和zdepth构造clipPos。float4 clipPos = float4(i.uv.zw, zdepth, 1.0);// 将clipPos转换为NDC(标准化设备坐标),范围从[0,1]转换到[-1,1]。clipPos.xyz = 2.0f * clipPos.xyz - 1.0f;// 使用unity_CameraInvProjection矩阵将裁剪空间坐标转换回相机空间坐标。float4 camPos = mul(unity_CameraInvProjection, clipPos);// 透视除法:将xyz分量除以w分量,得到最终的相机空间坐标。camPos.xyz /= camPos.w;// 因为相机空间中的z轴方向通常是相反的,所以需要对z分量取反。camPos.z *= -1;// 返回计算得到的相机空间坐标。return camPos.xyz;
}

四、PCF_SOFT

SubShader {Tags {"ShadowmapFilter" = "PCF_SOFT"}Pass {ZWrite Off ZTest Always Cull OffCGPROGRAM#pragma vertex vert#pragma fragment frag_pcfSoft#pragma multi_compile_shadowcollector#pragma target 3.0 // 需要SM 3.0 支持// 使用顶点着色器传递的中间数据计算坐标inline float3 computeCameraSpacePosFromDepth(v2f i) {return computeCameraSpacePosFromDepthAndVSInfo(i);}ENDCG}}

1.vert

同HardShadow

2.frag_pcfSoft

/*** 软阴影 (SM 3.0)*/
fixed4 frag_pcfSoft(v2f i) : SV_Target // 定义片元着色器函数frag_pcfSoft,接收一个v2f类型的输入结构体i,并返回一个fixed4类型的结果。
{UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i); // 在顶点处理后设置立体视觉的眼睛索引,这对于正确采样阴影贴图纹理数组中的对应切片是必需的。float4 wpos; // 声明一个float4类型的变量wpos,用于存储世界空间位置。float3 vpos; // 声明一个float3类型的变量vpos,用于存储相机空间位置。#if defined(STEREO_CUBEMAP_RENDER_ON) // 如果定义了STEREO_CUBEMAP_RENDER_ON宏,则表示正在进行立体全景图渲染。wpos.xyz = tex2D(_ODSWorldTexture, i.uv.xy).xyz; // 使用提供的_ODSWorldTexture纹理和uv坐标采样得到世界空间位置的xyz分量。wpos.w = 1.0f; // 设置w分量为1.0f,以确保正确的齐次坐标转换。vpos = mul(unity_WorldToCamera, wpos).xyz; // 将世界坐标转换为相机空间坐标。
#elsevpos = computeCameraSpacePosFromDepth(i); // 根据深度信息计算相机空间位置。// 样本属于的级联wpos = mul(unity_CameraToWorld, float4(vpos,1)); // 将相机空间位置转换回世界空间位置。
#endiffixed4 cascadeWeights = GET_CASCADE_WEIGHTS(wpos, vpos.z); // 获取级联权重,确定当前像素属于哪个阴影级联。float4 coord = GET_SHADOW_COORDINATES(wpos, cascadeWeights); // 计算阴影坐标。float3 receiverPlaneDepthBias = 0.0;
#ifdef UNITY_USE_RECEIVER_PLANE_BIAS// 接收平面深度偏移:需要基于第一个级联中的阴影坐标计算,否则在级联边界处的导数会出错。float3 coordCascade0 = getShadowCoord_SingleCascade(wpos); // 获取单个级联的阴影坐标。float biasMultiply = dot(cascadeWeights, unity_ShadowCascadeScales); // 根据级联权重计算偏移乘数。receiverPlaneDepthBias = UnityGetReceiverPlaneDepthBias(coordCascade0.xyz, biasMultiply); // 计算接收平面深度偏移。
#endif#if defined(SHADER_API_MOBILE)half shadow = UnitySampleShadowmap_PCF5x5(coord, receiverPlaneDepthBias); // 对于移动平台,使用5x5的PCF进行阴影采样。
#elsehalf shadow = UnitySampleShadowmap_PCF7x7(coord, receiverPlaneDepthBias); // 对于其他平台,使用7x7的PCF进行阴影采样。
#endifshadow = lerp(_LightShadowData.r, 1.0f, shadow); // 使用线性插值混合阴影结果。// 如果启用了级联混合并且不是使用分割球或单一级联的情况,则在此进行级联间的混合。//// 目前不支持分割球,并且当只有一个级联时不需要混合。
#if UNITY_USE_CASCADE_BLENDING && !defined(SHADOWS_SPLIT_SPHERES) && !defined(SHADOWS_SINGLE_CASCADE)half4 z4 = (float4(vpos.z,vpos.z,vpos.z,vpos.z) - _LightSplitsNear) / (_LightSplitsFar - _LightSplitsNear); // 计算z值在各个级联中的比例。half alpha = dot(z4 * cascadeWeights, half4(1,1,1,1)); // 计算当前像素所属的级联alpha值。UNITY_BRANCHif (alpha > 1 - UNITY_CASCADE_BLEND_DISTANCE) // 如果alpha值超过指定范围,则进行级联混合。{// 将alpha调整到0..1范围内,以便在混合距离内平滑过渡。alpha = (alpha - (1 - UNITY_CASCADE_BLEND_DISTANCE)) / UNITY_CASCADE_BLEND_DISTANCE;// 采样下一个级联cascadeWeights = fixed4(0, cascadeWeights.xyz); // 更新级联权重,指向下一个级联。coord = GET_SHADOW_COORDINATES(wpos, cascadeWeights); // 重新获取阴影坐标。#ifdef UNITY_USE_RECEIVER_PLANE_BIASbiasMultiply = dot(cascadeWeights, unity_ShadowCascadeScales); // 重新计算偏移乘数。receiverPlaneDepthBias = UnityGetReceiverPlaneDepthBias(coordCascade0.xyz, biasMultiply); // 重新计算接收平面深度偏移。
#endifhalf shadowNextCascade = UnitySampleShadowmap_PCF3x3(coord, receiverPlaneDepthBias); // 对下一个级联进行阴影采样。shadowNextCascade = lerp(_LightShadowData.r, 1.0f, shadowNextCascade); // 混合阴影结果。shadow = lerp(shadow, shadowNextCascade, alpha); // 根据alpha值在两个级联之间进行线性插值。}
#endifreturn shadow; // 返回最终的阴影结果。
}

3.computeCameraSpacePosFromDepthAndVSInfo

同HardShadow

五、PCF_SOFT_FORCE_INV_PROJECTION_IN_PS

SubShader{Tags{ "ShadowmapFilter" = "PCF_SOFT_FORCE_INV_PROJECTION_IN_PS" }Pass{ZWrite Off ZTest Always Cull OffCGPROGRAM#pragma vertex vert#pragma fragment frag_pcfSoft#pragma multi_compile_shadowcollector#pragma target 3.0// 在PS阶段使用逆投影矩阵计算坐标inline float3 computeCameraSpacePosFromDepth(v2f i) {return computeCameraSpacePosFromDepthAndInvProjMat(i);}ENDCG}}

1.vert

同HardShadow

2.frag_pcfSoft

同PCF_SOFT

3.computeCameraSpacePosFromDepthAndInvProjMat

同HardShadow_FORCE_INV_PROJECTION_IN_PS

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/77085.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Token+JWT+Redis 实现鉴权机制

TokenJWTRedis 实现鉴权机制 使用 Token、JWT 和 Redis 来实现鉴权机制是一种常见的做法&#xff0c;尤其适用于分布式应用或微服务架构。下面是一个大致的实现思路&#xff1a; 1. Token 和 JWT 概述 Token&#xff1a;通常是一个唯一的字符串&#xff0c;可以用来标识用户…

RPC与其他通信技术的区别,以及RPC的底层原理

1、什么是 RPC&#xff1f; 远程过程调用&#xff08;RPC&#xff09; 是一种协议&#xff0c;它允许程序在不同计算机之间进行通信&#xff0c;让开发者可以像调用本地函数一样发起远程请求。 通过 RPC&#xff0c;开发者无需关注底层网络细节&#xff0c;能够更专注于业务逻…

简洁的 PlantUML 入门教程

评论中太多朋友在问&#xff0c;我的文章中图例如何完成的。 我一直用plantUML,也推荐大家用&#xff0c;下面给出一个简洁的PlantUML教程。 &#x1f331; 什么是 PlantUML&#xff1f; PlantUML 是一个用纯文本语言画图的工具&#xff0c;支持流程图、时序图、用例图、类图、…

互联网三高-高性能之JVM调优

1 运行时数据区 JVM运行时数据区是Java虚拟机管理的内存核心模块&#xff0c;主要分为线程共享和线程私有两部分。 &#xff08;1&#xff09;线程私有 ① 程序计数器&#xff1a;存储当前线程执行字节码指令的地址&#xff0c;用于分支、循环、异常处理等流程控制‌ ② 虚拟机…

浅谈StarRocks 常见问题解析

StarRocks数据库作为高性能分布式分析数据库&#xff0c;其常见问题及解决方案涵盖环境部署、数据操作、系统稳定性、安全管控及生态集成五大核心领域&#xff0c;需确保Linux系统环境、依赖库及环境变量配置严格符合官方要求以避免节点启动失败&#xff0c;数据导入需遵循格式…

P1332 血色先锋队(BFS)

题目背景 巫妖王的天灾军团终于卷土重来&#xff0c;血色十字军组织了一支先锋军前往诺森德大陆对抗天灾军团&#xff0c;以及一切沾有亡灵气息的生物。孤立于联盟和部落的血色先锋军很快就遭到了天灾军团的重重包围&#xff0c;现在他们将主力只好聚集了起来&#xff0c;以抵…

大文件上传之断点续传实现方案与原理详解

一、实现原理 文件分块&#xff1a;将大文件切割为固定大小的块&#xff08;如5MB&#xff09; 进度记录&#xff1a;持久化存储已上传分块信息 续传能力&#xff1a;上传中断后根据记录继续上传未完成块 块校验机制&#xff1a;通过哈希值验证块完整性 合并策略&#xff1a;所…

【动手学深度学习】卷积神经网络(CNN)入门

【动手学深度学习】卷积神经网络&#xff08;CNN&#xff09;入门 1&#xff0c;卷积神经网络简介2&#xff0c;卷积层2.1&#xff0c;互相关运算原理2.2&#xff0c;互相关运算实现2.3&#xff0c;实现卷积层 3&#xff0c;卷积层的简单应用&#xff1a;边缘检测3.1&#xff0…

Opencv计算机视觉编程攻略-第十一节 三维重建

此处重点讨论在特定条件下&#xff0c;重建场景的三维结构和相机的三维姿态的一些应用实现。下面是完整投影公式最通用的表示方式。 在上述公式中&#xff0c;可以了解到&#xff0c;真实物体转为平面之后&#xff0c;s系数丢失了&#xff0c;因而无法会的三维坐标&#xff0c;…

大厂不再招测试?软件测试左移开发合理吗?

&#x1f449;目录 1 软件测试发展史 2 测试左移&#xff08;Testing shift left&#xff09; 3 测试右移&#xff08;Testing shift right&#xff09; 4 自动化测试 VS 测试自动化 5 来自 EX 测试的寄语 最近两年&#xff0c;互联网大厂的招聘中&#xff0c;测试工程师岗位似…

windows10下PointNet官方代码Pytorch实现

PointNet模型运行 1.下载源码并安装环境 GitCode - 全球开发者的开源社区,开源代码托管平台GitCode是面向全球开发者的开源社区,包括原创博客,开源代码托管,代码协作,项目管理等。与开发者社区互动,提升您的研发效率和质量。https://gitcode.com/gh_mirrors/po/pointnet.pyto…

git pull 和 git fetch

关于 git pull 和 git fetch 的区别 1. git fetch 作用&#xff1a;从远程仓库获取最新的分支信息和提交记录&#xff0c;但不会自动合并或修改当前工作目录中的内容。特点&#xff1a; 它只是更新本地的远程分支引用&#xff08;例如 remotes/origin/suyuhan&#xff09;&am…

前端开发中的单引号(‘ ‘)、双引号( )和反引号( `)使用

前端开发中的单引号&#xff08;’ &#xff09;、双引号&#xff08;" "&#xff09;和反引号&#xff08; &#xff09;使用 在前端开发中&#xff0c;单引号&#xff08;’ &#xff09;、双引号&#xff08;" "&#xff09;和反引号&#xff08; &…

程序化广告行业(69/89):DMP与PCP系统核心功能剖析

程序化广告行业&#xff08;69/89&#xff09;&#xff1a;DMP与PCP系统核心功能剖析 在数字化营销浪潮中&#xff0c;程序化广告已成为企业精准触达目标受众的关键手段。作为行业探索者&#xff0c;我深知其中知识的繁杂与重要性。一直以来&#xff0c;都希望能和大家一同学习…

Amodal3R ,南洋理工推出的 3D 生成模型

Amodal3R 是一款先进的条件式 3D 生成模型&#xff0c;能够从部分可见的 2D 物体图像中推断并重建完整的 3D 结构与外观。该模型建立在基础的 3D 生成模型 TRELLIS 之上&#xff0c;通过引入掩码加权多头交叉注意力机制与遮挡感知注意力层&#xff0c;利用遮挡先验知识优化重建…

LLM面试题八

推荐算法工程师面试题 二分类的分类损失函数&#xff1f; 二分类的分类损失函数一般采用交叉熵(Cross Entropy)损失函数&#xff0c;即CE损失函数。二分类问题的CE损失函数可以写成&#xff1a;其中&#xff0c;y是真实标签&#xff0c;p是预测标签&#xff0c;取值为0或1。 …

30天学Java第7天——IO流

概述 基本概念 输入流&#xff1a;从硬盘到内存。&#xff08;输入又叫做 读 read&#xff09;输出流&#xff1a;从内存到硬盘。&#xff08;输出又叫做 写 write&#xff09;字节流&#xff1a;一次读取一个字节。适合非文本数据&#xff0c;它是万能的&#xff0c;啥都能读…

面试可能会遇到的问题回答(嵌入式软件开发部分)

写在前面&#xff1a; 博主也是刚入社会的小牛马&#xff0c;如果下面有写的不好或者写错的地方欢迎大家指出~ 一、四大件基础知识 1、计算机组成原理 &#xff08;1&#xff09;简单介绍一下中断是什么。 ①回答&#xff1a; ②难度系数&#xff1a;★★ ③难点分析&…

层归一化详解及在 Stable Diffusion 中的应用分析

在深度学习中&#xff0c;归一化&#xff08;Normalization&#xff09;技术被广泛用于提升模型训练的稳定性和收敛速度。本文将详细介绍几种常见的归一化方式&#xff0c;并重点分析它们在 Stable Diffusion 模型中的实际使用场景。 一、常见的归一化技术 名称归一化维度应用…

深入理解Socket编程:构建简单的计算器服务器

一、Socket通信基础 1. Socket通信基本流程 服务器端流程&#xff1a; 创建Socket (socket()) 绑定地址和端口 (bind()) 监听连接 (listen()) 接受连接 (accept()) 数据通信 (read()/write()) 关闭连接 (close()) 客户端流程&#xff1a; 创建Socket (socket()) 连接…