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
相关文章:
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) "" {} // 阴影贴图纹理&…...
nginx配置oss代理
工作中会有一些时候需要将图片,视频,音频等文件放到oss这种对象存储中进行存储,实现高性能的访问,这种情况叫做动静分离.这里只做了图片的配置,视频以及音频的配置是一样的. 以下是nginx.conf的配置信息,其中还有ssl的加密配置,以及后端服务器的代理模块配置,(这里不用的话可以…...
UML对象图
UML对象图 一、对象图核心概念 对象图(Object Diagram)描述的是系统在某一时刻对象(实例)的状态快照。它关注的是实际对象之间的实例关系,而不是类与类之间的静态结构。主要特点有: 对象(Ob…...
手机不同App音量自动调节软件
软件介绍 在日常使用手机的过程中,大家是不是经常会遇到在不同App之间切换时,需要频繁调整音量的情况呢?这样真的很不方便。而一款名为App Volume Control的软件就能很好地解决这个问题。 App Volume Control借助辅助功能服务,能…...
模板方法模式详解
模板方法模式详解及真实场景解决方案 推荐学习完策略模式和模板方法模式看这个案例: 策略与模板方法模式组合详解 模式定义 模板方法模式是一种行为设计模式,在父类中定义算法的骨架,允许子类在不改变算法结构的情况下重写特定步骤。核心思…...
基于SSM邮件收发管理系统(带源码、论文)
摘要 随着互联网技术的迅速发展和普及,网络通信已经成了人们离不开的通信手段。作为最早出现的网络通信方式还有世界上应用最为广泛的网络服务之一,电子邮件综合了电话通信和传统邮件的特点,具有传播速度快、价格低廉的优良特性。随着技术发…...
1990-2019年各地级市GDP数据
1990-2019年各地级市GDP数据 1、时间:1990-2019年 2、来源:城市年鉴 3、指标:行政区划代码、年份、省份、城市、经度、纬度、地区生产总值(万元) 4、范围:250地级市 5、指标解释:地区生产总值(Gross R…...
Scala相关知识学习总结5
1、多维数组 定义: val arr Array.ofDim[Double](3,4) 表示二维数组中有三个一维数组,每个一维数组有四个元素。 2、列表 List 不可变 List:默认不可变,可创建有序且可重复的列表,可使用:从右向左增加数据…...
【LangChain Agent 】详解,构建自主决策的 LLM 应用
🐇明明跟你说过:个人主页 🏅个人专栏:《深度探秘:AI界的007》 🏅 🔖行路有良友,便是天堂🔖 目录 一、引言 1、什么是 Lang Chain 2、什么是 Agent 二、LangChain …...
Scala基础知识7
一,可变数组和不可变数组的转换方法: 不可变数组转换为可变数组使用 toBuffer 函数. 可变数组转换为不可变数组使用 toArray 函数。 转换过程中原始数组不会发生变化,而是返回一个新的数组。 二,多维数组 使用 Array of 函数创建多维数组&am…...
vmware、centos: 快照、redis集群克隆、启动异常
问题描述 提示:这里描述项目中遇到的问题: 启动虚拟机报错: 指定的虚拟磁盘需要进行修复 打不开磁盘“D:\Virtual Machines\CentOS 7 64 位\CentOS 7 64 位-000001.vmdk”或它所依赖的某个快照磁盘。 模块“Disk”启动失败。 未能启动虚拟机…...
visual studio断点无法进入
问题背景: 使用VSQt进行编码,新增函数处断点无法进入。 问题排查 查看moc文件,汇编代码以及设置调试选项均不生效。 原因分析 之前无意间进行了排版优化,导致新增的代码未正常编译进去,通过“文件-> 高级保存选…...
CNVD-2025-06046:Google Chrome沙箱逃逸漏洞大揭秘与防护指南
CNVD-2025-06046:Google Chrome沙箱逃逸漏洞大揭秘与防护指南 前言:浏览器界的“越狱”事件 嘿,小伙伴们!今天咱们要聊的不是什么好莱坞大片,而是一出在浏览器世界里真实上演的“越狱”大戏!你没听错&…...
androd的XML页面 跳转 Compose Activity 卡顿问题
解决 XML 点击跳转到 Compose Activity 卡顿问题 当从 XML 布局的 Activity 跳转到 Compose Activity 时出现卡顿现象,这通常是由以下几个原因导致的: 可能的原因及解决方案 1. Compose 首次初始化开销 问题:Compose 框架首次初始化需要时…...
Springboot同时支持不同的数据库,Oracle,Postgresql
关键字 Java,Springboot,Vscode,支持多种数据库 背景环境 我在实际项目开发工程中遇到这样一个问题,用户 A 使用 Oracle 数据库,用户 B 使用 Postgresql 数据库,但是用户 AB 都使用我们的项目。所以项目…...
【计网】TCP协议的拥塞控制与流量控制
拥塞控制与流量控制的区别 流量控制 流量控制主要是控制端到端(发送端和接收端)之间的数据传输量,是局部的机制。 目的:防止发送方发送数据过快导致接收方来不及处理实现方式:通过滑动窗口机制,接收方通…...
Redis - 字典(Hash)结构和 rehash 机制
字典结构 Redis中的字典由dict.h/dict结构表示: typedef struct dict ( //类型特定函数dictType *type;//私有数据 void "privdata;//哈希表 dictht ht[2];//rehash索引 //当rehash不在进行时,值为-1 in trehashidx; } dict; Redis字典所使用的哈希表由dict.h/dictht 结…...
Redis 与 MongoDB 对比分析
Redis 与 MongoDB 对比分析 1. 核心定位 Redis:内存键值数据库,侧重高性能读写和低延迟,常用于缓存、实时分析、消息队列等场景。MongoDB:分布式文档型数据库,侧重灵活的数据模型和大规模数据存储,支持复…...
【愚公系列】《高效使用DeepSeek》058-选题策划
🌟【技术大咖愚公搬代码:全栈专家的成长之路,你关注的宝藏博主在这里!】🌟 📣开发者圈持续输出高质量干货的"愚公精神"践行者——全网百万开发者都在追更的顶级技术博主! 👉 江湖人称"愚公搬代码",用七年如一日的精神深耕技术领域,以"…...
解决 Lettuce 在 Redis 集群模式下的故障转移问题
引言 在高可用系统中,故障转移是确保服务不中断的重要机制。当我们使用 Lettuce 作为 Redis 的 Java 客户端时,如何高效地处理故障转移成为一项关键任务。本篇文章将探讨如何在 Redis 集群模式下配置 Lettuce 以优化故障转移。 背景 在初期设置 Lettu…...
Vim搜索和替换
模式 命令模式 操作过程 键入/开始搜索,找到想要搜索的内容后直接回车,此时光标会从搜索框回到编辑器中。 之后可以 使用小写n跳到下一个符合条件的内容,使用大写N跳到上一个搜索的内容。也可以直接使用上下左右按钮调整光标位置。...
【探商宝】 Llama 4--技术突破与争议并存的开源多模态
核心亮点 混合专家架构(MoE)的全面应用 Llama 4 是 Meta 首个全系列采用 MoE 架构 的模型,通过稀疏激活机制显著提升效率。例如: Llama 4 Scout(中杯):16 位专家,17B 活跃参数&#…...
可发1区的超级创新思路(python 实现):一种轻量化的动态稀疏门控网络
首先声明,该模型为原创!原创!原创!且该思路还未有成果发表,感兴趣的小伙伴可以借鉴! 一、应用领域 视频异常检测、生成视频检测。 二、模型解析 该模型由1.关键帧动态选择机制、2.关键帧动态选择机制以及3.关键帧动态选择机制三大核心组件构成,形成端到端的视频异常…...
flink cdc的source数据流如何配置事件时间,如何设置时间语义,分配时间戳并生成水位线
在 Flink CDC 中为 Source 数据流配置事件时间需要结合时间语义设置、时间戳分配和水位线生成三个核心步骤。以下是具体配置方法及注意事项: 1. 设置时间语义 Flink 默认使用处理时间(Processing Time),需显式指定事件时间语义&a…...
Vue3:初识Vue,Vite服务器别名及其代理配置
一、创建一个Vue3项目 创建Vue3项目默认使用Vite作为现代的构建工具,以下指令本质也是通过下载create-vue来构建项目。 基于NodeJs版本大于等于18.3,使用命令行进行操作。 1、命令执行 npm create vuelatest输入项目名称 2、选择附加功能 选择要包含的功…...
医疗场景与事件驱动的高匹配颗粒度医疗智能体研发方向探析(代码版)
结合技术实现、应用场景与挑战展开分析: 一、医疗场景驱动的智能体核心方向 全场景覆盖的诊疗辅助医疗智能体系统编程方案 1.1、技术架构设计 #mermaid-svg-OKB0oAt38jXWNClG {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#m…...
HTML5 浏览器兼容性:让旧浏览器也能拥抱 HTML5
在前端开发领域,HTML5 带来了一系列令人兴奋的新特性和功能,极大地提升了用户体验和开发效率。然而,由于互联网用户使用的浏览器版本千差万别,部分旧版本浏览器并不支持 HTML5,这给开发者带来了兼容性方面的挑战。不过…...
Kubernetes 集群搭建(三):使用dashboard用户界面(需要访问外网获取yaml)
(一)简介 K8s Dashboard是Kubernetes提供的一种基于Web的用户界面工具,用于可视化地管理和监控Kubernetes集群 主要功能: 资源查看与管理: 查看Kubernetes集群中的各种资源,如节点、Pod、服务、部署等。 对…...
【图像处理基石】什么是ISP色彩管理?
在ISP(Image Signal Processor,图像信号处理器)管线中,色彩管理是图像处理流程的核心环节,主要负责从传感器原始数据(RAW)到最终输出图像(如YUV、RGB或JPEG)的色彩转换、…...
金融维度下的公链价值重构:重塑财富新秩序
公链的引言: 众人对 Crypto、Token 的探讨热度居高不下。在此大背景下,我打算另辟蹊径,从金融维度重新剖析区块链分布式账本。那么,我们究竟该如何正确认知它?又该如何搭建起一套从金融视角出发的分析框架呢࿱…...
LeetCode算法题(Go语言实现)_34
题目 考虑一棵二叉树上所有的叶子,这些叶子的值按从左到右的顺序排列形成一个 叶值序列 。 如果有两棵二叉树的叶值序列是相同,那么我们就认为它们是 叶相似 的。 如果给定的两个根结点分别为 root1 和 root2 的树是叶相似的,则返回 true&…...
# 项目部署指南:Flask、Gradio与Docker实现流程
Python项目部署指南:Flask、Gradio与Docker实践 1. 引言 在机器学习和Web开发中,将模型或应用部署为在线服务是关键一步。本文将介绍如何使用 Flask 和 Gradio 快速构建前端界面,并通过 Docker 容器化实现高效部署,涵盖完整流程图…...
2022第十三届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组(题解解析)
记录刷题的过程、感悟、题解。 希望能帮到,那些与我一同前行的,来自远方的朋友😉 大纲: 1、九进制转十进制-(解析)-简单的进制转化问题😄 2、顺子日期-(解析)-考察日期 3…...
UML之序列图的参与者与生命线
序列图是建模过程中必选的一种描述行为的手段,它展示在某些有用的行为中元素之间的消息交换和相互作用。交互是构成行为的一个单元;这些元素必须是可连接元素,通常将这些可连接元素称为交互中的参与者(Participants)。…...
基于Python Flask快速构建网络安全工具资源库的Web应用实践
引言 在网络安全领域,信息收集(OSINT)是渗透测试、漏洞挖掘和威胁分析的关键环节。然而,面对海量工具和分散的技术文档,安全研究人员常需耗费大量时间查找和比对工具信息。本文将介绍如何利用 Python Flask HTML 技…...
xv6-labs-2024 lab1
lab-1 注:实验环境在我的汇编随手记的末尾部分有搭建教程。 0.前置 第零章 xv6为我们提供了多种系统调用,其中,exec将从某个文件里读取内存镜像(这确实是一个好的说法),并且将其替换到调用它的内存空间,也就是这个…...
HTTP Form v.s. Flask-WTF Form v.s. Bootstrap Form
在Flask-WTF和Bootstrap 的Form创建中,添加了页面显示Flash Messages。 相比Flask_WTF, Bootstrap用 render_form(form)渲染样式,自动带错误提示,不需要像Flask_WTF那样手写 for error in ... 。 项目结构: register_app/ ├── HTTP_Form_App.py ├── FlaskWTF_Form…...
Linux网络编程——https的协议及其加密解密方式
目录 一、前言 https协议 常见的加密方式 1、对称加密 2、非对称加密 3、数字签名 1. 只用对称加密 2、只用单一的非对称加密 3、双方都使用非对称加密 4、非对称加密对称加密 证书 证书颁发流程 服务器与客户端的证书验证流程 5、证书非对称加密对称加密 前言 上一…...
Node.js 下载与安装(图文)
下载 官网:【直达:https://nodejs.org/en/】。 点击【Download】,选择【版本,系统】。点击【Windows Installer(.msi)】。 安装 双击【.msi文件】,选择【安装路径】,也可以一直【下一步】。 查看版本 …...
3.31-4.06 Web3 游戏周报:Pebble 玩家留存率登顶,Treasure DAO 面临重组危机
回顾上周的区块链游戏概况,查看 Footprint Analytics 与 ABGA 最新发布的数据报告。 【3.31–4.06】Web3 游戏行业动态 链游生态系统 Treasure DAO 因财务危机面临重组,将终止游戏运营和 Treasure Chain3A 链游 Shrapnel 开发商 Neon Machine 深陷财务…...
echarts生成3D立体地图react组件
地图散点图效果: react项目中安装echarts、echarts-gl依赖: npm install echarts echarts-gl 文件目录结构: 地图组件map目录下文件代码,点击散点图圆点触发事件handleCityClick: index.jsx: import { …...
Node.js 中处理 Excel 文件的最佳实践
在现代应用开发中,Excel 文件仍然是数据交换和存储的重要格式之一。在 Node.js 环境中,处理 Excel 文件的需求日益增加。本文将介绍如何在 Node.js 中高效地处理 Excel 文件,涵盖工具选择、基本操作和最佳实践。 1. 选择合适的库 在 Node.js…...
解决Ubuntu系统鼠标不流畅的问题
电脑是联想的台式组装机,安装ubuntu系统(不管是16、18、20、22)后,鼠标都不流畅。最近几天想解决这个问题,于是怀疑到了显卡驱动上。怀疑之前一直用的是集成显卡,而不是独立显卡,毕竟2060的显卡…...
【Linux】虚拟机设置静态IP
主播我今天下午学了几节微服务课,上课的时候,直接把手机拿走了去上课(电脑连的我手机的热点),虚拟机没关,晚上主播我回来继续学,电脑连上热点之后,发现虚拟机连接不上了,…...
Web API:AbortController
Web API:AbortController 主要用途基本工作原理基本用法示例高级用例1. 实现请求超时2. 取消多个请求3. 与其他异步 API 一起使用 浏览器支持总结 主要用途 AbortController 是一个 Web API,主要用于取消一个或多个 Web 请求(如 fetch 请求&…...
服务器配置虚拟IP
服务器配置虚拟IP的核心步骤取决于具体场景,主要包括本地单机多IP配置和高可用集群下的虚拟IP管理两种模式。 一、本地虚拟IP配置(单服务器多IP) 基于Linux系统: 确认网络接口:使用 ip addr 或 ifconfig 查…...
《AI大模型应知应会100篇》第5篇:大模型发展简史:从BERT到ChatGPT的演进
第5篇:大模型发展简史:从BERT到ChatGPT的演进 摘要 近年来,人工智能领域最引人注目的进步之一是大模型(Large Language Models, LLMs)的发展。这些模型不仅推动了自然语言处理(NLP)技术的飞跃&…...
小球反弹(蓝桥杯C语言)
有一长方形,长为 343720343720 单位长度,宽为 233333233333 单位长度。在其内部左上角顶点有一小球 (无视其体积),其初速度如图所示且保持运动速率不变,分解到长宽两个方向上的速率之比为 dx:dy15:17dx:dy15:17。小球碰到长方形的…...
Java面试39-Zookeeper中的Watch机制的原理
Zookeeper是一个分布式协调组件,为分布式架构下的多个应用组件提供了顺序访问控制能力。它的数据存储采用了类似于文件系统的树形结构,以节点的方式来管理存储在Zookeeper上的数据。 Zookeeper提供了一个Watch机制,可以让客户端感知到Zooke…...
3️⃣ Coze工作流基础教学(2025年全新版本)
目录 一、什么是工作流 二、为什么用工作流 三、工作流使用场景 四、怎么学习工作流 五、工作流功能概述 六、制作工作流基础流程 6.1 创建工作流 6.2 配置工作流 6.3 调试工作流 6.4 发布工作流 6.5 使用工作流 6.6 复制工作流 6.7 删除工作流 6.8 设置工作流异…...