news 2026/10/1 3:48:12

Unity投影阴影原理与自定义Shader接入:从Shadow Mapping到问题排查

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Unity投影阴影原理与自定义Shader接入:从Shadow Mapping到问题排查

第一次正经碰上Unity投影阴影的坑,是在一个很普通的小场景里:室内一盏平行光,模型用的是Standard材质,阴影参数也全都打开了,结果墙角、椅子边到处是一团团灰蒙蒙的暗斑,调到后面阴影干脆整个浮到空中。我那时候对阴影的理解就是"灯照不到的地方就是阴影",参数挨个拖了一遍,纯靠手感碰运气。后来把Light面板、Quality设置、Frame Debugger全过了一遍,才算把Unity投影阴影的实现链路彻底想明白。

这篇博文就想用从业者之间交流的方式,把Unity投影阴影的底层原理、平行光/点光/聚光灯各自的运算差异、自定义Shader接入阴影投射与接收的完整方法,以及我实际排查阴影问题时的定位思路整理成一套可以直接参考的笔记。适合刚接触Unity渲染、正在跟阴影较劲的开发者,也适合那些想把手写Shader的阴影能力补全的人,看完你至少能对"阴影为什么是这样的"有一个整体判断,而不是继续在参数面板里瞎调。

1. 投影阴影的底牌:一张从光源视角生成的深度图

1.1 阴影映射:在光源位置放一台"只记录深度"的相机

要搞懂Unity里的投影阴影,先别盯着Light组件的阴影选项,回到图形学最底层的Shadow Mapping思路。Unity整个实时阴影体系就是建立在这个算法上的,只不过它在引擎里帮你把细节全包了。

Shadow Mapping的原理其实只有两步。第一步,从光源的位置和方向渲染一次场景,不输出颜色,只输出深度,得到一张阴影贴图(Shadow Map),里面每个纹素记录的是"离光源最近的表面距离"。第二步,从主相机角度正常渲染场景,每个像素在光照计算时,会重新算出自己到光源的距离,然后和阴影贴图里对应的深度做比较:如果这个像素比阴影贴图记录的深度更远,说明它的正前方还有其他遮挡物,它就该处于阴影中;如果它本身就在最前面,那就是被照亮的区域。

生活里可以这么类比:光源那边站着一个拿着签到表的守卫,签到表上每一格只写了"当前最靠近我的那个人是谁"。主相机这边的人想确认自己有没有被挡,就去对表,发现自己不是最靠前的那个,那就自觉站进影子里。这套思路放在任何实时引擎里都成立,Unity只是把第一遍"光源视角渲染"封装成了ShadowCaster渲染阶段。

这个机制决定了几个重要特性。阴影贴图的分辨率不是无限大的,所以阴影边缘天然存在锯齿和采样误差;深度值存在浮点精度和量化误差,所以必须加Bias偏移来补偿;光源的视野范围决定了一张阴影贴图能覆盖多少场景,范围太大会导致纹素稀疏,阴影又糊又脏。理解了这三点,后面遇到的问题基本都有了解释。

1.2 深度纹理的分辨率决定了阴影的"毛边程度"

Shadow Map的纹素在场景里的实际尺寸,直接决定了阴影边缘的精细度。比如一张1024x1024的阴影贴图覆盖10米宽的场景,平均每个纹素大约对应1厘米见方的面积,听起来很精细;但如果是覆盖100米,一个纹素就摊到约10厘米见方,阴影边缘自然会出现一格格的大颗粒。

Unity的阴影质量设置里,Shadow Resolution分为Low(512)、Medium(1024)、High(2048)、Ultra(4096),很多人习惯直接拉Ultra,觉得高分辨率一定好。我实际操作下来的经验是,高分辨率只解决了边缘颗粒感,但没解决Bias问题,反而会因为深度精度更敏感,让表面Acne噪点更清晰。所以更合理的做法是:先把分辨率定在一个平台能接受的水平,再回过头来用Bias把噪点压掉。

关于纹素覆盖率有个经验值可以顺手记一下:一张1024的阴影贴图,覆盖范围最好控制在5到15米之内,超过这个范围阴影边缘就会开始露馅。这也是为什么接下来要讲级联阴影——平行光想要大面积可控的阴影质量,单靠一张贴图是不够的。

1.3 硬阴影、软阴影与PCF开销

Unity的Shadow Type下拉框就两个选项:Hard Shadows和Soft Shadows,但它俩背后是完全不同的计算复杂度。

Hard Shadows只做一次深度比较,结果是0或1,阴影边缘非常锐利,锯齿感也很直白。Soft Shadows则是在阴影贴图上做相邻多个纹素的采样,通常叫PCF(Percentage Closer Filtering),取平均值来模拟半影过渡,效果自然很多,但代价是每个像素要做多次阴影贴图采样,Shader里这等于几倍的纹理读取和比较操作,GPU开销直线上升。

阴影类型实现方式边缘表现性能成本
Hard Shadows单次深度采样+比较锐利、锯齿感明显低
Soft ShadowsPCF 2x2或4x4多次采样柔和半影、过渡自然中到高

移动端经常有个尴尬情况:开了Soft Shadows帧率掉得肉眼可见。如果项目目标平台是手机,我通常建议先上Hard Shadows,把阴影距离和分辨率控制好,效果已经能接受;真要软阴影,也只对近距离主角附近的物体开,远处的物体走另一个低开销的渲染层级,这是URP里做分层的常见思路。

2. 光不一样,阴影的运算方式也不一样:三类光源的差异

2.1 平行光:为什么必须上级联阴影

平行光在Unity里算一个特殊光源,它没有位置,只有方向,整个场景都在它的"照射范围"内。如果按普通Shadow Mapping的思路,只生成一张阴影贴图去覆盖整个视锥体,结果必然是近处地面每厘米都铺满高密度纹素,远处却稀疏得根本看不出阴影轮廓。

Unity解决这个问题的方案是级联阴影(Cascaded Shadow Map),原理很简单:把主相机的视锥体按距离切成几段,每一段单独生成一张阴影贴图。近处的段覆盖范围小,纹素密集,阴影精细;远处的段覆盖范围大,纹素稀疏,但反正远处也不在意细节。Unity的Quality设置里Cascade Count可以设No Cascades、Two Cascades、Four Cascades,这个数值不是简单的开/关,而是阴影贴图的总预算是固定的,级联数越多,每个级联分到的分辨率越少,但分布更合理。

实际项目里我的经验是:中小型室内场景用Two Cascades就够了,省下来的GPU可以留给模型和特效;大世界场景直接用Four Cascades,否则远处地面阴影糊成一团,和没开阴影差不多。级联阴影还有一个隐形问题,就是级联与级联之间的接缝处容易出现一条明显的阴影断层,两个方案能缓解:一是把级联切分曲线调得偏近一点,让高分辨率级联稍微覆盖到中远区域;二是让阴影贴图在级联边缘多采样几个纹素做混合,但后者需要改管线,普通项目不推荐自己动。

2.2 点光源:六张阴影贴图的显存代价

点光源的特点是四面八方全方向发光,Shadow Mapping必须覆盖完整的球体方向。Unity的做法不是畸形投影,而是生成一个立方体贴图,把场景从点光源位置分别往前后左右上下六个方向渲染,等于一次要渲染六张阴影贴图。这意味着点光源阴影的开销几乎是一个聚光灯的六倍,包括渲染次数、显存占用、深度采样成本。

这个代价从数据上看非常明显。一张512x512的阴影贴图如果是聚光灯,占用的显存和采样带宽可以忽略;换成点光源,就是六张512贴图同时常驻,而且每一帧若每四个方向都渲染,绘制压力是直线上升的。所以大范围、高强度的点光源阴影,在移动端通常直接被定性为性能杀手。

我在项目里的习惯是:点光源的Range尽可能控制在5到10米内,阴影贴图分辨率用512就够,除非这个点光源是近距离的核心视觉光源。如果一个场景需要多个点光源投射动态阴影,就该考虑换一盏平行光或者用烘焙光照分担压力,而不是单纯堆分辨率。

2.3 聚光灯:一张锥形贴图就能解决

聚光灯的阴影计算在三种光源里最接近教科书里的Shadow Mapping。它有一个确定的光锥范围,从光源位置往锥体方向渲染一张带透视投影的阴影贴图,只覆盖几十度角的区域,一张512贴图就能提供相当精细的阴影边缘。开销上,聚光灯阴影和普通平行光单级联的阴影差不多,主要受阴影贴图分辨率和场景中能看到该阴影的物体数量影响。

聚光灯阴影在实践中容易踩的坑有两个。一个是Spot Angle和Range必须和阴影贴图投影范围严格对应,否则会出现阴影边缘突然截断或整片阴影歪斜的情况。另一个是聚光灯的Cookie(纹理遮罩)和阴影叠加时,如果Cookie分辨率太低,阴影边缘会出现雾蒙蒙的色带,这往往是烘焙出来的贴图没勾选sRGB选项导致的。

2.4 Bias:阴影开裂与漂浮的根源

Bias是阴影系统里最容易被忽略、也最影响结果的两个参数。Unity的Light组件里提供Bias和Normal Bias,它们的字面意思都不难,但很多人调了半天还是错。

先看Bias(深度偏移)。阴影贴图的深度是经过量化存储的,和主相机渲染时算出的深度之间存在微小误差。当一个表面几乎平行于入射光时,表面的不同位置到光源的距离相差极小,深度值的误差就可能盖过真实距离差,导致表面一部分被判成在阴影里,一部分又不在,形成密集的亮暗噪点,也就是常说的阴影痤疮Acne。加Bias就是在深度比较前,把阴影贴图里的深度往后推一点,给误差让出空间,但推多了,阴影就会整个离开物体根部,浮到空中,这叫Peter Panning。

再看Normal Bias。它是在阴影采样时把采样坐标沿顶点法线方向偏移一点,专门解决斜面自遮阴影的问题。Normal Bias太大会让紧贴地面的接触阴影消失,出现"漏光";太小则斜面噪点压不住。

参数作用过大后果过小后果
Bias深度比较偏移阴影悬浮、穿透表面噪点Acne
Normal Bias采样坐标沿法线偏移接触阴影丢失、漏光斜面自遮阴影明显

我个人的调法优先级是:先压Acne噪点,再加到刚好没有,然后就停手,宁可用硬阴影也不要让阴影浮起来。因为噪点还可以用低分辨率阴影贴图模糊掉一部分,但Peter Panning一眼就能看出"影子没贴地",观感伤害更大。

3. 自定义Shader要投阴影,必须走ShadowCaster Pass这一步

3.1 为什么自己写的Shader默认没有阴影

很多开发者在用Standard材质时从没想过阴影是哪里来的,直到开始写自定义Shader,突然发现模型死活不投影子,灯光、网格、质量设置全都正常,就是没有阴影。原因很直接:Unity渲染阴影时,只认专门负责"光源视角渲染"的那个Pass,也就是Tag为LightMode=ShadowCaster的Pass。内置的Standard、Diffuse等Shader都自带这个Pass,所以换材质不会丢阴影;你自己的Shader只写了Forward光照Pass,引擎在阴影渲染阶段扫不到ShadowCaster,自然就没有任何东西往阴影贴图里写,后面的模型就算开了接收阴影,也没阴影可接收。

这里还有一个容易被误解的点:FallBack关键字的作用。如果你自定义Shader里写了FallBack "Diffuse",Unity在扫描ShadowCaster Pass时,会退而求其次去找FallBack Shader里的ShadowCaster Pass。这就是为什么有些半成品Shader明明自己没写阴影,还是能投出影子,其实用的是兜底Shader的阴影Pass。这种写法方便,但代价是阴影Pass和你的主Shader可能完全不匹配,比如本来要做Alpha Test的草叶,FallBack的阴影Pass却不做裁切,投出来的影子会是一整片实心四边形。

3.2 一个精简可靠的ShadowCaster Pass代码

下面是内置渲染管线下一个可以运行的ShadowCaster Pass,我把关键注释都写在代码里。这个版本的核心思路是:顶点阶段通过UnityCG封装的宏完成深度位置计算和Bias偏移,片元阶段做alpha裁切,让透明镂空材质也能投射出正确的镂空阴影。

Shader "Custom/DemoShadowCaster" { Properties { _MainTex ("Texture", 2D) = "white" {} _Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5 } SubShader { Tags { "RenderType"="Opaque" "Queue"="Geometry" } // 正常的光照Pass省略,只演示阴影相关部分 Pass { Name "ShadowCaster" Tags { "LightMode"="ShadowCaster" } CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_shadowcaster #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; float2 uv : TEXCOORD0; }; struct v2f { V2F_SHADOW_CASTER; float2 uv : TEXCOORD2; }; sampler2D _MainTex; fixed _Cutoff; v2f vert (appdata v) { v2f o; o.uv = v.uv; // 这个宏负责把顶点变换到光源裁剪空间, // 同时应用Unity的阴影Bias偏移 TRANSFER_SHADOW_CASTER_NORMALOFFSET(o) return o; } fixed4 frag (v2f i) : SV_Target { fixed4 tex = tex2D(_MainTex, i.uv); clip(tex.a - _Cutoff); // 输出的是深度值,不是颜色 SHADOW_CASTER_FRAGMENT(i) } ENDCG } } FallBack Off }

这里有个细节值得单独说:uv我放在TEXCOORD2,而不是TEXCOORD0或1,是因为UnityCG的V2F_SHADOW_CASTER宏内部在不同Unity版本里对TEXCOORD0/1的使用并不完全一致,为了不让自己的uv和宏内部变量冲突,直接往后挪一位最安全。如果你写的Shader不需要alpha裁切,完全可以不声明uv,结构体里只留V2F_SHADOW_CASTER,frag里直接SHADOW_CASTER_FRAGMENT并返回深度即可,性能更好。

另外,如果你用的是URP或者HDRP,这套内置管线的宏只是兼容基础,更主流的方式是引用URP的ShadowCasterPass.hlsl,或者直接在Shader Graph里连一张MainTexture的Alpha到Surface选项。URP里自定义LitShader做阴影投射的原理一模一样,但要额外注意LightMode标签要写成ShadowCaster,URP才会把它纳入阴影渲染队列。

3.3 接收阴影:SHADOW_COORDS / TRANSFER_SHADOW / SHADOW_ATTENUATION

能投阴影只是第一步,模型还得能接收阴影。接收阴影不是引擎自动完成的,需要在光照Pass里手动接入一套阴影采样宏。这套宏的名字在UnityCG里是稳定的三件套:SHADOW_COORDS、TRANSFER_SHADOW、SHADOW_ATTENUATION。

pass { Tags { "LightMode"="ForwardBase" } CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fwdbase #include "UnityCG.cginc" struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; float2 uv : TEXCOORD0; }; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; float3 normalWorld : TEXCOORD1; SHADOW_COORDS(2) }; v2f vert (appdata v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv = v.uv; o.normalWorld = UnityObjectToWorldNormal(v.normal); TRANSFER_SHADOW(o) return o; } fixed4 frag (v2f i) : SV_Target { // shadow接近于1表示在光照中,接近0表示在阴影中 fixed shadow = SHADOW_ATTENUATION(i); // 后续光照计算里把shadow乘进漫反射项即可 return shadow; } ENDCG }

这套宏的机制可以展开说一下。TRANSFER_SHADOW在顶点阶段根据顶点位置和当前渲染的Shadow Map投影矩阵,算出一个阴影采样坐标,存进v2f里;片元阶段SHADOW_ATTENUATION拿着这个坐标去采样阴影贴图,再做一次深度比较,返回一个0到1之间的阴影衰减值。如果Shader里漏写了#pragma multi_compile_fwdbase,阴影相关的关键字不会被编译进去,宏可能直接展开为空,结果就是所有接收阴影的代码都失效,模型永远白亮。这是我在项目里撞过好几次的坑,排查优先级很高。

4. 阴影问题的三个典型案例:从现象到根因的排查链路

4.1 表面出现细密噪点:阴影痤疮的定位过程

现象很典型:物体的斜面上出现一层细密的黑点或亮暗相间的马赛克,距离稍远看像是蒙了一层灰膜,靠近看又像数字噪点。这就是经典的Shadow Acne问题。

我的排查链路通常是这么走的:

  1. 先把Light组件的Shadow Type改成Hard Shadows,排除Soft Shadows里PCF采样带来的雾气感。
  2. 打开Frame Debugger,确认阴影渲染阶段确实走了,并且没有多余的重叠Pass。
  3. 把Light的Bias从0.05开始逐步加到0.2、0.5,观察噪点密度是否下降。
  4. 如果噪点还有,把Quality里的Shadow Resolution降一档,用更粗的纹素把微小抖动抹平。
  5. 如果用的自定义Shader,检查ShadowCaster Pass里是否正确地套用了TRANSFER_SHADOW_CASTER_NORMALOFFSET,漏了这一行等于完全没有任何Bias,噪点必然存在。

经验上,这个问题90%是Bias过小,10%是自定义Shader没做偏移。在URP里还需要再看一下Shadow Settings面板里的Depth Bias和Normal Bias,URP把它们从Light组件挪到了Pipe Asset的全局设置里,很多人找不到。

4.2 阴影整体悬浮或穿透墙壁:Bias过大的代价

和Acne刚好相反,如果阴影不贴在物体根部,反而像垫了一层气垫一样浮在空中,或者干脆穿到墙的另一面去,那基本就是Bias和Normal Bias调得太大。这个现象的视觉破坏力非常高,因为它会让所有接触阴影全部失真,墙角、桌椅腿、人物脚底全都"飘"起来。

我定位这个问题的标准动作是:先把Light组件上Bias和Normal Bias同时降到0,看阴影是否回到物体表面。如果确实回到表面但噪点暴增,那就是典型的"Bias过高盖过了噪点"状态。接着从0开始,每次只提升一点,建议步长0.05,每调一次就在物体斜面处看一眼,直到噪点刚好消失就停下来。千万不能一上来就加Bias到1.0以上,那几乎必然导致悬浮。

还有一类悬浮情况跟Bias一点关系没有:光源是点光源或聚光灯时,Shadow Map的Near Plane太小,导致光源周围很近的一段距离内阴影贴图根本没有有效深度信息,投影出来的阴影就会被截断或错位。Unity里这类光源没有直接暴露Near Plane参数的情况,常见做法是适当缩小光源Range或者把光源移远一些,让阴影贴图的有效覆盖区域避开这个问题。

4.3 距离一远阴影就消失或级联切换断层

第三种高频问题来自阴影距离和级联配置。场景编辑器里看着阴影正常,一到实际游戏里走几米,远处的阴影说没就没,或者在某个特定距离上有一条明显的阴影边界线。

第一个关卡是Shadow Distance。Unity默认的阴影距离可能只有几十米。大场景里如果你把Shadow Distance设得很小,远处地面自然一片空白。这不是Bug,而是引擎为了性能故意限制的,因为在阴影距离之外,所有灯光都不会参与阴影计算。

第二个关卡是级联断层。当级联数从2调到4时,如果切分比例设置不合理,级联与级联之间的阴影分辨率差异会在远处形成一条肉眼可见的"接缝"。URP里可以在阴影设置面板调Cascade Splits的分割比例,内置管线则可以通过Quality设置和脚本控制。一般把前两级联的覆盖比例压小一点,让中远景度数落在低精度级联里,视觉上反而更平滑。

场景规模建议Shadow Distance建议级联数建议分辨率
室内小场景(10米内)20-30米21024
中型室外(50米内)40-60米42048
大世界(100米以上)80-120米42048-4096

这几个数值不是铁律,但按这个起点调,基本能避开一半的阴影视觉问题。调完记得开Profiler看一眼Shadow Pass的耗时,阴影距离和分辨率是线性拉高开销的,不是无脑往上堆就好。

5. 不用Shadow Map的投影阴影:伪阴影、Projector与优化取舍

5.1 平面阴影:用顶点投影公式手工算阴影

在某些场景里,Shadow Map其实不是最优解,比如高度风格化的手游、地面全是平面的竞技场、或者需要极低Shader开销的休闲游戏。这时候我会直接用手写一个平面阴影(Planar Shadow),原理是把模型的每个顶点沿光线方向投影到一个平面上,渲染成半透明的黑色。

投影公式推导起来不复杂。设定平面上一点Q、平面法线N、平行光方向L(指向光源方向),世界空间下物体有一个顶点V,那么从V出发延-L方向的射线会和平面相交于S,满足:

S = V - L * t (S - Q) · N = 0

代入后解得:

t = (V - Q) · N / (L · N)

放到Shader里就是:

float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; float distToPlane = dot(worldPos - _PlanePos, _PlaneNormal); float t = distToPlane / dot(_LightDir, _PlaneNormal); float3 shadowPos = worldPos - _LightDir * t; o.pos = mul(unity_WorldToClip, float4(shadowPos, 1.0));

算出来的shadowPos就是压到地面上的影子顶点。然后让片元输出一个固定的半透明黑色,并在frag里根据距离做衰减就行。平面阴影的效果在平地上非常干净利落,而且Shader开销几乎可以忽略。限制也很明显:只适用于物体投影在单个平面上的场景,跨台阶、上坡、绕到凹凸地形里就露馅了。这个方案适合用来做技能指示圈、角色脚下的实时投影,或者在低端移动设备上替代实时阴影。

5.2 Projector阴影:老项目的过渡方案

Unity的Projector组件也能做投影阴影,做法是在场景里放一个Projector,并指定一张阴影纹理,它会把纹理沿着投影器锥体投射到所有物体表面。这种做法在十年前的老项目里很常见,优点是不依赖Shadow Map,对Shader版本和平台兼容性好;缺点是阴影只是一个贴花效果,不会根据实际遮挡关系变形,遇到墙角、柱子会有投影穿帮。

现在再用Projector做整体阴影已经不划算,但它在某些特效场景还有价值:比如人物脚下的光晕投影、临时提示圈、传送门地面特效,这些不需要物理正确性的地方,Projector比Shadow Map省事得多。

5.3 静态场景用烘焙光照与Light Probe

还有一个"不实时"的阴影方案其实最值得认真用:静态场景全部烘焙。Lightmap烘焙会把静态物体的阴影直接画进光照贴图里,运行时零阴影计算开销,质量也高。真正的问题只有一个:动态物体在静态场景里没有对应的阴影关系,身上不会出现静态物体投下的影子,脚下也不会有接触阴影。

解决方案是给动态物体放Light Probe组,让它们从周围空间采样烘焙好的间接光照,至少在明暗方向上有一个近似。如果动态物体本身也需要投射阴影,那就把它设成影响光照贴图的动态阴影投射器,配合Shadow Map做局部的实时阴影,只覆盖玩家附近的一小块区域。这种"大部分烘焙+局部实时阴影"的组合,是我在移动端项目里压榨阴影质量的主选方案之一。

5.4 性能优化清单:先看阴影距离,再看分辨率和级联

最后把实时阴影的性能优化做一个清单式的总结。优化顺序很重要,我建议按下面这个优先级操作,因为前面几个变量的性价比是最高的:

  • 第一优先:缩小Shadow Distance。这是最立竿见影的优化项,远一点的阴影直接不要。
  • 第二优先:降低阴影贴图分辨率。移动端从2048降到1024往往看不出太大区别,但帧率能回来一截。
  • 第三优先:减少Cascade Count。大世界场景确实需要4级联,但中小项目2级联完全够。
  • 第四优先:限制投射阴影的光源数量。场景里尽量只保留一盏主光源开启实时阴影。
  • 第五优先:控制需要投阴影的动态物体数量。远处的、不重要的角色和道具,可以把Mesh Renderer的Cast Shadows设为Off,或者用LOD让阴影在远距离直接消失。

在做完这轮优化后,我通常会开一次深度性能测试,重点看DrawCall里的ShadowCaster Pass和阴影贴图采样带宽。很多时候真正卡住帧率的不是阴影本身,而是阴影贴图覆盖范围内塞进了太多需要渲染的物体,导致阴影Pass的顶点处理量爆炸。

最后聊聊我踩过几次坑之后的习惯

如果现在有人问我,调Unity投影阴影第一步应该做什么,我会说:先开Frame Debugger,看阴影渲染阶段到底走了哪个Pass,再去看深浅参数。90%的阴影问题在Frame Debugger面前都会现出原形——要么ShadowCaster Pass没被调用,要么阴影贴图没进主相机的采样链,要么就是Bias数值彻底失衡。

另一个小技巧是我一直沿用的:在做任何一个项目前,先花半小时把灯光、阴影、质量设置、URP管线的Shadow参数全部过一遍,把这些数值固化成一个预设。这样后续所有场景都用同一套阴影基准,出了问题也容易横向对比。别指望一套参数能通吃所有场景,室内、室外、移动端、PC端各存一套,这才是避免阴影翻车最实在的办法。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 3:47:48

HarmonyOS Java华容道开发:状态管理与UI解耦实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 3:43:46

AutoML实战:TPOT用遗传算法自动搜索机器学习管道

刚开始接触机器学习那段时间,我对手动调参有一种莫名的执念——总觉得要自己一个模型一个模型地试、一个参数一个参数地调,才算是“真正懂算法”。直到后来接了一个业务需求,老板只给三天时间就要出一版能跑的模型,几十个特征还带…

作者头像 李华
网站建设 2026/10/1 3:43:39

大模型推理提速三板斧:量化、投机采样与PD分离实战指南

每次我在社区帮人排查大模型推理速度问题时,都会遇到一个相同场景:显卡明明在跑,显存也没爆,但生成速度就是上不去,一个千字回答要等上一两分钟。任务管理器里看GPU利用率只有百分之二三十,算力根本没吃满。…

作者头像 李华
网站建设 2026/10/1 3:42:45

Qoder AI编程IDE全流程指南:安装配置、模型选型与Credits计费

最近 AI 编程工具真的是卷到飞起,前有 Cursor 打开局面,后有各种 Agent 工具轮番上阵。Qoder 是我最近在几个项目里实际用下来的一款 AI 编程 IDE/插件,如果你平时写前端、做全栈,或者一个人要扛好几个项目,它会很对你…

作者头像 李华
网站建设 2026/10/1 3:41:41

PMX骨骼名称对照与映射:解决MMD动作套用错位

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 3:40:59

Java后端必学:LangChain4j从入门到RAG实战指南

1. 为什么 Java 后端值得花时间学 LangChain4j先说一个我自己的真实感受。过去一年多大模型应用开发几乎被 Python 生态垄断,LangChain、LlamaIndex 这些框架的教程铺天盖地,Java 后端想接个大模型,要么自己裸写 HTTP 请求拼 JSON&#xff0c…

作者头像 李华