24.Shader开发实践总结

  1. 24.总结
    1. 24.1 核心要点速览
      1. 流光效果
        1. 在做什么
        2. 原理
        3. 混合与队列
        4. 实现顺序(对照 Lesson02_MovingLight)
      2. 模型描边效果
        1. 在做什么
        2. 原理
        3. 队列与标签
        4. 易错
      3. 遮挡半透明效果
        1. 在做什么
        2. 原理(对照深度缓冲)
        3. 两个 Shader 差在哪
      4. 物体切割效果
        1. 在做什么
        2. 原理
        3. 数据流
        4. 易错(脚本)
      5. 翻页效果
        1. 在做什么
        2. 顶点顺序(别乱改)
        3. 角度权重
        4. 贴图
      6. 卡通风格渲染
        1. 在做什么
        2. 漫反射与 Ramp
        3. 阴影(Built-in 前向)
        4. 描边与另一篇描边的区别
        5. 高光
      7. 素描风格渲染
        1. 在做什么
        2. 权重与 diff * 7
        3. 与卡通的差异(记一句)
        4. UsePass 条件
      8. 噪声素材与工具链
        1. 美术(PS)
        2. 程序(Mathf.PerlinNoise)
        3. 工具
      9. 消融效果
        1. 在做什么
        2. 溶解
        3. 烧边
        4. 阴影
      10. 水波效果
        1. 在做什么
        2. 法线
        3. 折射与菲涅耳
        4. 工程提醒
      11. 噪声雾效
        1. 在做什么
        2. 管线要点
        3. C#
    2. 24.2 面试题精选
      1. 基础题
        1. 1. Unity Shader 里 _Time 各分量是什么,流光示例为何常用 .x
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        2. 2. 模型描边第一个 Pass 为什么要关 ZWrite
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        3. 3. ZTest Greater 在遮挡半透明里画的是哪一块
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        4. 4. 翻页顶点里 weight 与 sin 起伏的关系
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        5. 5. 卡通描边 Pass 为什么用 Cull Front 加法线外扩
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        6. 6. 素描里 diff * 7.0 与多张纹理权重在做什么
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        7. 7. 消融里 clip 与 _Dissolve == 1 为什么要特殊处理
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        8. 8. Mathf.PerlinNoise 里 scale 起什么作用
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
      2. 进阶题
        1. 1. 流光里用 Blend One One 与常规 Alpha 混合各适合什么
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        2. 2. 遮挡半透明的两个 Pass 如何分工
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        3. 3. 物体切割里 step、_Invert 与 clip 如何配合
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        4. 4. 翻页顶点着色器里平移、起伏与旋转的先后顺序
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        5. 5. 卡通里 halfLambertNum 为什么要乘 UNITY_LIGHT_ATTENUATION
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        6. 6. UsePass "Unlit/Lesson12_ToonShader/OUTLINE" 要能工作需满足什么
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        7. 7. 消融的 ShadowCaster Pass 为什么要对噪声再做 clip
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        8. 8. 水波里对法线纹理做 +speed 与 -speed 两次采样再归一化的目的
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
      3. 深度题
        1. 1. Transparent 队列下的流光与排序、开销
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        2. 2. RenderType 为 Opaque 而 Queue 为 Transparent 时排序上要注意什么
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        3. 3. 用 sharedMaterial 传切割位置有什么工程风险
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        4. 4. 卡通 Outline 与主 Pass 的深度关系及可能穿帮
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        5. 5. 用 step 做硬高光与用 pow 做连续高光各适合什么
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        6. 6. 素描把 atten 乘在最终 sketchColor 上与卡通只乘 halfLambertNum 有何观感差异
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        7. 7. GrabPass 水波在性能与平台上要注意什么
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        8. 8. 噪声雾效里 f * _FogDensity * (1 + noise) 再 saturate 的语义
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章

24.总结


24.1 核心要点速览

下面按课题写:每个效果尽量拆成「在做什么 → 原理 → 实现或易错」几块,长句压成条,复习时先扫四级标题再回实现篇对代码。

流光效果

在做什么

亮纹来自贴图本身,时间只改往哪采:给 uv.x 加上 时间 × 速度,等于把纹理沿 U 轴平移,屏幕上亮区就沿表面走。

原理

  • 顶点:MVPTRANSFORM_TEX 照旧;动画放片元,改 UV 不用每帧动网格。
  • 片元:uv 偏移后 tex2D,再乘 _Color 控制叠色强度。

混合与队列

  • Blend One Onedst = src + dst,越叠越亮,像往背景上「加一盏灯」;和 SrcAlpha OneMinusSrcAlpha 那种「按透明度盖上去」不是同一套语义。
  • Queue = Transparent:和同屏其它透明物一起排序,模型穿插时前后可能不符合直觉,要实机看一眼。
  • Cull Off:薄片双面都画;封闭壳体若只关心外表面,可开回剔除减 overdraw。

实现顺序(对照 Lesson02_MovingLight

  1. Unlit,主纹理 + 空片元先跑通。
  2. Properties_MainTex_Color_Speed
  3. Tags:RenderType / Queue → Transparent;Blend One One;按需 Cull Off
  4. 片元里推 UV,乘色输出。
含义 复习提示
_Time 常见分量为 t/20、t、2t、3t .x 偏慢,.y 偏快;换分量等于换时间缩放,记得同步调 _Speed
Blend One One 加法叠色 多层易曝、易糊;玻璃/UI 类透明不要用这套
Queue Transparent 排序靠画家算法,别假设永远正确
Cull Off 双面 能单面就关,省片元
float2 uv = float2(i.uv.x + _Time.x * _Speed, i.uv.y);
return tex2D(_MainTex, uv) * _Color;

例:闪电贴图里一条高亮横带,U 向滚动时整条亮带会沿剑身移动;觉得慢先改 _Speed,再考虑换 _Time 分量。

模型描边效果

在做什么

先画一层 沿法线外扩的壳(纯色),再画 未变形的本体(主纹理)。轮廓来自两层深度关系 + 颜色,不是单 Pass 勾线。

原理

  • Pass1:顶点 vertex + normalize(normal) * _OutLineWidthUnityObjectToClipPos,片元输出 _OutLineColor
  • Pass1 通常 **ZWrite Off**:壳若写深度,Pass2 画真表面时可能被自己的深度挡死,出现自遮挡或层次乱。
  • Pass2:正常采样主纹理,深度由场景与 Pass2 共同决定。

队列与标签

  • Queue 提到 Transparent:解决「本体盖住描边」一类排序问题;真正画多晚Queue,不是看 RenderType
  • RenderType 仍可标 Opaque:给替换 Shader、部分工具做分类用;接手的人容易误解,材质或 Wiki 里写一句

易错

  • _OutLineWidth对象空间 单位,模型放大 10 倍,同样数值会显得更细。
  • 硬边:法线在边上不连续,外扩可能 断线,这是法线扩法固有代价。
要点
外扩 对象空间沿法线推顶点
Pass1 ZWrite Off 避免第二 Pass 被壳的深度挡死
Queue Transparent 时与 RenderType 不一致要文档说明
Fallback "Diffuse" 等,低端回退

建议搭法:单 Pass 主色 → 复制 Pass 做外扩 + ZWrite Off → 第二 Pass 主纹理 → 改队列 + Fallback。

遮挡半透明效果

在做什么

第一遍专门画 「深度上已经比前景更远」 的那片表面(被挡层),第二遍画 正常可见 表面。合起来:正面看清,被挡处半透明或 X 光感。

原理(对照深度缓冲)

  • 缓冲里存的是 当前最近深度。默认 ZTest LEqual:更近或相等才过。
  • Pass1:ZTest Greater + ZWrite Off → 只画 比缓冲里更远 的片元,即被前景挡在后面的表面;不写深度 免得破坏后续物体。
  • Pass2:默认 LEqual,画正常可见部分。
  • Blend SrcAlpha OneMinusSrcAlpha:常规透明合成,不是加法发亮。

两个 Shader 差在哪

Pass1 颜色 / Alpha Pass2
OcclusionTransparent1 主纹理 RGB,A 用统一 _Alpha 主纹理可见部分
OcclusionTransparent2 A 用菲涅耳近似,掠射角更「透」 同上

菲涅耳(文中形式):_FresnelScale + (1 - _FresnelScale) * pow(1 - dot(V, N), _FresnelN)VN 单位化,pow 指数调边缘软硬。

例子:球前放一个立方体,球 被立方体挡住的那块球面 仍能着色,看起来像隔着玻璃看后面。

物体切割效果

在做什么

每个片元判断:在世界空间某一轴上,自己在 切割平面哪一侧;不在保留侧就 clip 掉。双面用 不同贴图 时靠 VFACE 分面。

原理

  • step 得到 0/1;_Invert 翻转保留侧。
  • clip(v)只有 v < 0 才丢v == 0 仍会画,所以 cutValue == 0 时要 clip(-1) 这类写法。
  • 轴:0/1/2x/y/z_CuttingPos 比较;_CuttingPos 常绑定空物体,脚本每帧 SetVector

数据流

  • 顶点输出 worldPos(如 mul(unity_ObjectToWorld, vertex).xyz),片元里做比较。
  • VFACEface > 0_MainTex,否则 _BackTex;需要 Cull Off#pragma target 3.0

易错(脚本)

  • sharedMaterial 改的是 资产:同材质多物体 切割面会联动。正式用 material 拷贝或 MaterialPropertyBlock
  • [ExecuteAlways]:编辑模式也能预览,调场景方便。
属性 含义
_CuttingDir 0/1/2 → x / y / z
_Invert 是否反转保留侧
_CuttingPos 世界空间参考点

翻页效果

在做什么

模型空间 里模拟一张纸:先平移到装订边,再弯折,再绕局部 Z 轴翻页,最后平移回去。波纹与旋转的顺序决定「弯折是不是还在同一页面上」。

顶点顺序(别乱改)

  1. vertex += float3(_MoveDis, 0, 0):把旋转中心挪到纸边。
  2. Y 向 sin、X 向收缩:中间拱起,模拟书页弯曲。
  3. 绕 Z 轴 sincos 旋转 _AngleProgress
  4. 减回平移,到裁剪空间。

若把 sin 挪到旋转 之后,弯折作用在已旋转的坐标系上,波纹方向会跟着歪,和课文效果不一致。

角度权重

weight = 1 - abs(90 - _AngleProgress) / 90:0°、180° 为 0,90° 为 1 → 中间翻得最弯,合上时平

贴图

  • VFACE + Cull Off + target 3.0,与切割篇双面套路相同。
属性 作用
_AngleProgress 0~180°
_WeightY / _WeightX 起伏/收缩强度,乘 weight
_WaveLength 控制 sin 波纹密度
_MoveDis 铰链在纸的哪一侧

卡通风格渲染

在做什么

漫反射用 Ramp 纹理 把连续明暗切成色带;阴影用 Built-in 宏接到 半兰伯特 上;描边用 Cull Front + 外扩;高光可改成 硬阈值 做块面高光。

漫反射与 Ramp

  • 半兰伯特标量进 tex2D(_RampTex, float2(halfLambertNum, halfLambertNum)),法线仍走切线空间 + 法线贴图那条链。
  • 本质:用纹理当 LUT,替代连续 pow 漫反射。

阴影(Built-in 前向)

  • ForwardBasemulti_compile_fwdbaseLighting.cginc + AutoLight.cginc
  • SHADOW_COORDS / TRANSFER_SHADOW / UNITY_LIGHT_ATTENUATIONhalfLambertNum *= atten 进 Ramp,否则投影进不了色带。
  • Fallback "Diffuse":顺带吃内置 ShadowCaster 链。

描边与另一篇描边的区别

  • 本篇:Cull Front + 外扩,描边 Pass 可以写深度;主 Pass Cull Back,靠深度挡掉壳的内侧,只留外圈。
  • 「双 Pass 描边关 ZWrite」那套:第一遍不写深度,动机不同,不要混用两套假设。

高光

  • step 阈值:spec = step(_SpecularNum, dot(N, halfVec)) → 高光整块亮/灭,赛璐璐感强,边缘不如 pow 平滑。

SubShader 顺序OUTLINEForwardBase

素描风格渲染

在做什么

6 张 素描纹理 + 顶点算出的权重,按明暗层级混排线;阴影来时 整幅atten;描边 复用 Lesson12 的 OUTLINE Pass。

权重与 diff * 7

  • 兰伯特 diff 映射到 [0,7),拆成 6 段,对应 _Sketch0_Sketch5
  • 段内相邻两张用 互补权重(如 x = diff - ky = 1 - x),比单层权重少一块死白。

与卡通的差异(记一句)

  • 素描:sketchColor.rgb * atten * _Color,阴影里 整张贴图一起暗
  • 卡通:halfLambertNum 先乘 atten 再 Ramp,高光路径 单独,暗部里高光相对更跳。

UsePass 条件

  • Lesson12 里 Pass 必须 Name "OUTLINE"
  • 本 Shader 的 Properties 仍要有 _OutLineColor_OutLineWidth,否则引用 Pass 读不到参数。

噪声素材与工具链

美术(PS)

  • 灰度噪声:固定尺寸(如 512×512)→ 云彩 → 自动色调 → 导出,给 clip、溶解阈值用。
  • 一维渐变条:RGB 画布 + 渐变工具直线拖(Shift 约束),导出后 Shader 里用 value 当 UV 的一维坐标,常做 消融烧边

程序(Mathf.PerlinNoise

  • 每个像素:(x/width)*scale(y/height)*scale 喂给 Perlin;**scale 大** → 相邻采样步长大 → 图更 ;**scale 小** → 更平滑。
  • SetPixelApplyEncodeToPNGAssetDatabase.Refresh

工具

  • PerlinNoiseTextureToolEditorWindow 调宽高、scale、文件名,把上面流程固化。

消融效果

在做什么

按噪声值与溶解进度比较 clip 掉片元;边界用 渐变条 上色;阴影 Pass 必须与主 Pass 同一套 clip,否则影子是完整剪影。

溶解

  • clip(noise.r - _Dissolve):噪声低于进度就消失。
  • clip 只对 <0 生效;_Dissolve == 1 时若 noise.r == _Dissolve 仍会留点,要 clip(-1) 分支强制全清。

烧边

  • smoothstep 出边界 value,采 _Gradientlerp 时用 step(0.000001, _Dissolve) 避免进度为 0 时整面误混渐变。

阴影

  • ShadowCaster同样 clip 条件,否则光眼里还是整块模型,与画面上的洞对不上。

水波效果

在做什么

背后屏幕:GrabPass + 透明队列晚画,抓到的才是「身后的场景」。折射用 法线扰动后的屏幕偏移;反射用 Cubemap;菲涅耳决定 反射 vs 折射 比例。

法线

  • 同一张 BumpMap 采两次:uv + speeduv - speed,再 normalize
  • 直觉:单向滚像「一条纹在跑」,反向叠加后 更乱、更像水;归一化保证后续反射/菲涅耳单位向量可靠。

折射与菲涅耳

  • grabPos.xy += bump.xy * _Distortion * grabPos.z(实现形式以课文为准),再透视除法采 _GrabTexture
  • Schlick:fresnel = _FresnelScale + (1-_FresnelScale)*pow(1-dot(V,N),5)color = refl * fresnel + grab * (1-fresnel);主纹理 UV 可加 speed 做细节流动。

工程提醒

  • GrabPass:多水面 ≈ 多次拷屏,带宽贵。
  • 排序错了:Grab 到的不是你以为的那层背景。
  • 换 URP/HDRP:Grab 行为与 Built-in 不一定一致,要查文档。

噪声雾效

在做什么

全屏后处理:用 深度 还原 worldPos,先算 高度雾 基线,再用 滚动噪声 把雾浓做空间起伏,最后 lerp 到雾色。

管线要点

  • 深度:LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture));射线插值还原 worldPos(课文中的 Lesson23_NoiseFog 写法)。
  • 高度雾:f = (_FogEnd - worldPos.y) / (_FogEnd - _FogStart)
  • 噪声:noise = (tex2D(...).r - 0.5) * _NoiseAmount,可正可负;f = saturate(f * _FogDensity * (1 + noise)) → 雾 一块浓一块淡saturate 防止 lerp 因子越界。

C#

  • 继承雾效基类;除雾参外设 _Noise、滚动速度;相机 depthTextureMode |= DepthTextureMode.Depth

24.2 面试题精选

基础题

1. Unity Shader 里 _Time 各分量是什么,流光示例为何常用 .x

题目

说明 _Time 四个分量的含义,并联系「U 轴偏移做流光」解释用 _Time.x 和用 _Time.y 时观感差异。

深入解析
  • Unity 内置 float4 _Time,文档与教程中常见写法为四个分量对应 **t/20t2t3t**,其中 t 为场景加载后的秒级时间。
  • .xt/20,同样 _Speed周期更长、滚动更慢;**.y** 为 t,变化更快。
  • 片元里 uv.x + _Time.x * _Speed 只是把「时间因子」乘到偏移上,换分量本质是换时间缩放,需与美术节奏一起调 _Speed
答题示例

_Time 是内置时间向量,四个分量通常对应 t/20、t、2t、3tt 是场景时间。

流光用 U 偏移时,乘 .x 相当于用较慢的 t/20,同样速度参数下动得比 .y 更柔和;要快一点就改用 .y 或调大 _Speed

参考文章
  • 2.流光效果-具体实现

2. 模型描边第一个 Pass 为什么要关 ZWrite

题目

双 Pass 描边里,外扩那一 Pass 为何要 ZWrite Off?若打开深度写入,第二 Pass 正常画模型时容易出现什么现象?

深入解析
  • Pass1 把网格沿法线放大,若写入深度,缓冲里会留下「更靠外」的深度;Pass2 画真实表面时,深度测试可能认为本体在扩面之后,导致表面被错误遮挡或描边层次错乱。
  • 关掉 Pass1 的写入后,深度缓冲主要反映场景里其他物体与第二 Pass的贡献,第二 Pass 才能稳定画上纹理表面,描边由第一 Pass 的颜色与队列排序呈现。
答题示例

第一个 Pass 只是扩出去画一条边,如果它还写深度,会把「假的大壳」写进深度缓冲。

第二个 Pass 画真模型时深度对不过,表面可能被挡掉或描边关系乱掉。所以外扩 Pass 一般关 ZWrite,让第二 Pass 正常盖在正确的深度关系上。

参考文章
  • 4.模型描边效果-具体实现

3. ZTest Greater 在遮挡半透明里画的是哪一块

题目

遮挡半透明第一个 Pass 使用 ZTest GreaterZWrite Off,相对默认的 LEqual,多画了场景里的哪类片元?为什么要配 Blend SrcAlpha OneMinusSrcAlpha

深入解析
  • 默认通过测试的是「更近或相等」;Greater 通过的是「比缓冲里已存深度更远」的片元,在遮挡关系下对应已被前面几何挡住、在深度上处于后方的表面区域。
  • ZWrite Off 避免这一半透明层破坏后续物体的深度一致性。Alpha 混合用标准 SrcAlpha OneMinusSrcAlpha 把被挡区域与背景按透明度合成。
答题示例

Greater 表示只有比深度缓冲里更远的片元才通过,所以画的是「在深度上已经落在遮挡物后面」的那部分表面。

关掉写入是避免半透明乱改深度。混合用 SrcAlpha OneMinusSrcAlpha 做正常透明叠色。

参考文章
  • 6.遮挡半透明效果-具体实现

4. 翻页顶点里 weight 与 sin 起伏的关系

题目

文中用 weight = 1 - abs(90 - _AngleProgress) / 90 再乘 _WeightY 去做 Y 向 sin 偏移。0°、90°、180° 时 weight 各为多少,对翻页弯曲感有什么影响?

深入解析
  • 代入得 0°、180° 时 weight=090° 时 weight=1,与正文「两端不需要起伏、中间最弯」一致。
  • sin(appdata_base.vertex.x * _WaveLength) * weight * _WeightY 在 weight 为 0 时整项为 0,页面贴合;90° 时起伏幅度由 _WeightY 与波长共同决定。
答题示例

weight 在 0 度和 180 度是 0,在 90 度是 1。

所以页面在合上、完全翻开时是平的,翻到中间弯得最厉害,正好配合 sin 做波浪形变。

参考文章
  • 10.翻页效果-具体实现

5. 卡通描边 Pass 为什么用 Cull Front 加法线外扩

题目

Lesson12_ToonShader 里描边 Pass 使用 Cull Front 并把顶点沿法线外扩。这样画出来的是什么几何,与主 Pass 的 Cull Back 如何分工?

深入解析
  • Cull Front 丢弃正面、只光栅化背面;外扩后这些背面片元在屏幕边缘形成一圈比本体略大的壳,片元着色器输出纯色即轮廓。
  • 主 Pass Cull Back 只画正面,正常深度测试下内层扩面被挡,只留下外缘可见的描边颜色。
答题示例

Cull Front 只画背面,把模型沿法线撑大一圈再只渲染背面,边缘就会露出一圈颜色当描边。

主 Pass Cull Back 画正面,靠深度把里面那层扩出去的壳挡住,外面只看到一圈边线。

参考文章
  • 12.卡通风格渲染-具体实现

6. 素描里 diff * 7.0 与多张纹理权重在做什么

题目

Lesson14_Sketch 把兰伯特结果乘 7 再分支写 sketchWeights0/1,片元里六张图按权重相加。为什么要放大到 0~7,而不是直接用 0~1 采一张渐变?

深入解析
  • 0~7 把明暗划成多段,对应 6 张素描贴图之间的过渡;段内用小数在相邻两张之间分权重,得到离散笔触层级。
  • 多张贴图比单张 Ramp 更易做出笔触方向与疏密变化;修正版让相邻权重和为 1,减轻大块留白。
答题示例

乘 7 是把亮度切成多段,每段对应不同素描纹理,用权重在两张之间混。

比一张渐变更像手绘排线,改算法是为了少出一片死白。

参考文章
  • 14.素描风格渲染-具体实现

7. 消融里 clip_Dissolve == 1 为什么要特殊处理

题目

clip(noise.r - _Dissolve) 为何在 _Dissolve == 1 时改成 clip(-1)clip 对 0 的行为会带来什么问题?

深入解析
  • clip(x) 仅在 x < 0 时丢弃;**x == 0 仍通过**,进度拉满时可能残留 noise.r == _Dissolve 的片元。
  • 进度为 1 时强制传入负值可保证全部剔除
答题示例

clip 只扔掉小于 0 的,等于 0 还会画。

溶解到 1 时噪声若刚好等于阈值会留渣,所以 dissolve 为 1 时直接 clip 负数全扔掉。

参考文章
  • 19.噪声-消融效果-具体实现

8. Mathf.PerlinNoisescale 起什么作用

题目

工具用 (x/width)*scale 作为 Perlin 输入。增大 scale 时生成图更「碎」还是更「平」?

深入解析
  • 坐标乘 scale 使相邻像素在噪声场上步进更大,高频相对增强,视觉上更细碎scale 小则采样更缓、更平滑。
答题示例

scale 大相当于 UV 拉得更开,相邻像素差得多,图就更花更碎;scale 小就平。

参考文章
  • 17.噪声-制作纹理-柏林噪声纹理生成工具

进阶题

1. 流光里用 Blend One One 与常规 Alpha 混合各适合什么

题目

本系列流光 Shader 使用 Blend One One。若改成典型的 SrcAlpha OneMinusSrcAlpha 一类 Alpha 混合,表现与适用场景会有什么不同?

深入解析
  • Blend One Onesrc + dst不断向背景加亮,适合闪电、能量、高光扫过等「发光体」;不保留物理上可信的覆盖率,透明语义弱,叠多了易曝、糊
  • Alpha 混合:按源 Alpha 与背景插值,适合玻璃、UI、需要正确遮挡与层次的透明材质。
  • 工程上还要结合 Queue 与同屏其他透明物体的排序,加法混合往往更「抢眼」,也更容易和后期 Bloom 等一起把画面洗白。
答题示例

One One 是加法,颜色往背景上叠亮,适合做流光、闪电这种发亮特效,但容易过亮、多层叠在一起很糊。

Alpha 混合按透明度与背景插值,更适合常规透明材质。选哪种要看是要「发亮」还是要「正确的透明遮挡关系」。

参考文章
  • 2.流光效果-具体实现

2. 遮挡半透明的两个 Pass 如何分工

题目

OcclusionTransparent1 中第一个 Pass 与第二个 Pass 在深度测试与视觉效果上各负责什么?Pass 顺序能否对调,为什么?

深入解析
  • Pass1ZTest Greater 专门渲染被遮挡部分并带透明度;Pass2:默认 LEqual(未改即前向默认),渲染正常可见部分。
  • 若简单对调:先画可见再画被挡,需仍满足「被挡层」用 Greater 才能出现;通常教程顺序是先 Greater 半透明层、再正常层,与深度缓冲更新节奏一致;对调后若仍先写入了某些深度会改变结果,工程上应遵循「谁依赖当前缓冲状态」的顺序设计。
答题示例

第一个 Pass 用 Greater 把被挡区域半透叠上去;第二个 Pass 用默认深度测试画没被挡到的正常表面。

不能随意对调而不改状态,因为 Greater 那层依赖当前缓冲里已经是「前景挡在后面」的深度关系,顺序错了效果就不对。

参考文章
  • 6.遮挡半透明效果-具体实现

3. 物体切割里 step_Invertclip 如何配合

题目

切割 Shader 里用 step 得到 cutValue,再经 _Invertclip(-1) 决定是否显示片元。说明 step 各参数含义及 clip 丢弃条件。

深入解析
  • 文中实现为 cutValue = step(_CuttingPos.axis, worldPos.axis):HLSL step(edge, x)x >= edge 时为 1,否则 0;即比较切割位置与片元世界坐标在选定轴上的前后关系。
  • _Invert 为真时 cutValue = 1 - cutValue 翻转保留侧。**clip(v)** 在 v < 0 时丢弃片元,故 clip(-1) 用于丢弃。
答题示例

step 拿切割参考坐标和片元世界坐标比,大于等于边的一侧返回 1,另一侧返回 0。

_Invert 把保留侧翻过来。clip(-1) 小于零直接扔掉片元,实现切开效果。

参考文章
  • 8.物体切割效果-具体实现

4. 翻页顶点着色器里平移、起伏与旋转的先后顺序

题目

Lesson10_PageTurning 顶点里先 vertex += (_MoveDis,0,0),再做 sin 起伏与 X 收缩,再乘 Z 旋转矩阵,最后减回平移。若把「起伏」挪到旋转之后,效果可能有什么不同?

深入解析
  • 当前顺序把形变定义在翻页前的局部坐标系里:起伏沿「页」的局部 X 起伏、绕 Z 旋转模拟装订边,数学上与美术预期的「先弯纸再绕轴翻」一致。
  • 若先旋转再起伏,sin 作用的轴已随旋转改变,波纹方向与弯曲轴会跟教程效果不一致,调参难度上升。
答题示例

现在是先平移、在局部空间里做弯折和压缩,再绕 Z 旋转,最后移回去。

如果旋转后再 sin,弯折作用在已经转过的坐标上,波纹方向会跟着变,就不像在同一页纸上折了。

参考文章
  • 10.翻页效果-具体实现

5. 卡通里 halfLambertNum 为什么要乘 UNITY_LIGHT_ATTENUATION

题目

加入阴影接收后,代码在采样 Ramp 前令 halfLambertNum *= atten。这样做的渲染语义是什么?

深入解析
  • halfLambertNum 表征 N·L 明暗;**atten** 含 阴影与距离衰减,乘进去后 Ramp 的 UV 也会随投影区域与光衰变暗,被照亮与进影的区域在色带上拉开。
  • 若不乘,渐变纹理只反映法线与光方向关系,阴影贴图不会参与卡通明暗分带
答题示例

atten 里有阴影和衰减,乘到半兰伯特上以后,进阴影的地方整体会变暗,Ramp 采出来也会掉到更暗的色带。

不乘的话卡通分带跟阴影对不齐,看起来会像没吃到投影。

参考文章
  • 12.卡通风格渲染-具体实现

6. UsePass "Unlit/Lesson12_ToonShader/OUTLINE" 要能工作需满足什么

题目

素描 Shader 用 UsePass 引用卡通课里的描边 Pass。若 Lesson12 里该 Pass 没有 Name "OUTLINE" 会怎样?材质上还要暴露哪些属性?

深入解析
  • UsePass 路径依赖对方 Shader 里 Pass 的 Name;未命名则无法稳定引用
  • 描边用到的 _OutLineColor_OutLineWidth 须在本材质上可见,通常写在当前 Shader 的 Properties 中供两 Pass 共享。
答题示例

UsePass 是按名字抓 Pass 的,卡通那边必须先给描边 Pass 起名叫 OUTLINE。

轮廓颜色和宽度要在当前材质的 Properties 里声明,外轮廓 Pass 才能读到。

参考文章
  • 14.素描风格渲染-具体实现

7. 消融的 ShadowCaster Pass 为什么要对噪声再做 clip

题目

主 Pass 已按噪声溶解,阴影投射 Pass 若不做相同剔除会出现什么现象?

深入解析
  • 阴影图记录的是「光源视角下哪些片元存在」;若投射 Pass 不 clip已溶解掉的区域仍写深度,阴影会呈完整剪影,与肉眼溶解不一致。
  • 同步 clip(_Dissolve == 1 ? -1 : noise.r - _Dissolve) 后,阴影轮廓随溶解收缩
答题示例

阴影 Pass 不写 clip 的话,物体在光眼里还是整块,影子不会跟着溶掉。

在 ShadowCaster 里用同样的 clip,影子才会和画面上的洞对齐。

参考文章
  • 19.噪声-消融效果-具体实现

8. 水波里对法线纹理做 +speed-speed 两次采样再归一化的目的

题目

bump1bump2 取相反方向滚动后 normalize(bump1+bump2),相比单次采样有什么好处?

深入解析
  • 双向滚动叠加可打破单一方向的条纹感,得到更各向、更乱序的法线扰动,水纹更自然。
  • 归一化保持法线近似单位长度,后续算反射向量与菲涅耳更稳。
答题示例

一个往正方向滚一个往反方向滚,叠在一起比单次扫动更不像一条纹在跑。

normalize 一下让法线还是单位向量,反射和菲涅耳才不会漂。

参考文章
  • 21.噪声-水波效果-具体实现

深度题

1. Transparent 队列下的流光与排序、开销

题目

该 Shader 将 RenderTypeQueue 设为 Transparent,并配合 Blend One One。从渲染顺序与性能角度,可能遇到哪些问题?可以怎样缓解?

深入解析
  • 排序:透明队列通常按深度等规则排序,穿插模型时顺序不总符合直觉,加法混合又会改写颜色,错误排序时局部会异常亮或异常暗。
  • OverdrawCull Off 双面 + 全屏或大面片特效时,片元执行次数上升;One One 对 HDR 与 Bloom 管线还可能放大「亮斑」。
  • 缓解思路:能单面则恢复剔除;控制面片与全屏范围;需要稳定层次时对特效用独立 Queue 偏移或与美术约定相机角度;与 URP/HDRP 时还要考虑相机栈与透明 Pass 的实际行为差异(以当前管线文档为准)。
答题示例

走 Transparent 队列就要面对透明排序问题,加法混合又对顺序特别敏感,叠错会显得怪。

双面和全屏会拉高 overdraw,加法还容易和 Bloom 一起把画面打曝。可以从能剔则剔、控制几何范围、调队列或与美术定相机来减轻。

参考文章
  • 1.流光效果-基本原理
  • 2.流光效果-具体实现

2. RenderType 为 Opaque 而 Queue 为 Transparent 时排序上要注意什么

题目

描边 Shader 将 RenderType 标为 Opaque、Queue 设为 Transparent。这与「通体透明材质」在排序与批处理上的直觉差异是什么?可能带来什么副作用?

深入解析
  • RenderType 影响材质替换、部分后效或替代 Shader 的分类;Queue 决定大致绘制顺序。二者不一致时,物体进入透明排序区间,可能与大量透明物体一起排序,却仍按不透明方式声明,易造成文档与工具链认知不一致
  • 副作用包括:与纯不透明物体分批、排序规则不同;调试时 RenderType 与表现不一致;部分管线优化路径可能按 RenderType 假设队列。
答题示例

RenderType 更多是分类标签,Queue 才真正决定大概什么时候画。标 Opaque 却进 Transparent 队列,会跟透明物体一起排序,但材质分类上仍叫 Opaque,容易让接手的人误判。

可能带来排序穿插、批处理行为与「看起来不透明」预期不一致,文档和调试要说明白。

参考文章
  • 4.模型描边效果-具体实现

3. 用 sharedMaterial 传切割位置有什么工程风险

题目

示例在 Update 里对 Renderer.sharedMaterial 取材质并 SetVector("_CuttingPos", ...)。若场景里多个 Renderer 共用同一材质资产,会出现什么?更稳妥的写法是什么?

深入解析
  • sharedMaterial 指向资源上的共享材质,一改所有引用该材质的实例都会受影响,切割平面会「联动」。
  • 更稳妥:运行时 material 属性取实例副本再改,或 MaterialPropertyBlock 按 Renderer 写属性,避免污染共享资产。
答题示例

sharedMaterial 改的是资源本体,所有用这个材质的物体切割位置都会变成同一个。

单物体演示可以,正式项目要么用 material 实例化一份再改,要么用 MaterialPropertyBlock 按物体写参数。

参考文章
  • 8.物体切割效果-具体实现

4. 卡通 Outline 与主 Pass 的深度关系及可能穿帮

题目

完整 Lesson12_ToonShader 先画外扩描边再画 ForwardBase 主表面,且描边 Pass 未像另一篇描边那样关 ZWrite。依赖什么测试挡住内层扩面?近裁剪、薄模型或宽度极大时可能出什么问题?

深入解析
  • 依赖主 Pass 正常深度写入Cull Back 只画正面,使相机侧可见的扩面内侧被后续深度挡住,只留外缘。
  • 过宽的 _OutLineWidth、近裁剪面相交、深度精度不足时,可能出现轮廓厚度不均、局部闪烁或穿帮,需调宽度、模型厚度或队列。
答题示例

描边 Pass 把模型撑大一圈,主 Pass 再画正面并写深度,把里面那层挡掉,只留下外边一圈颜色。

描边太宽、模型太薄或者深度打架时,可能看到轮廓断掉或闪,要调宽度和模型。

参考文章
  • 12.卡通风格渲染-具体实现

5. 用 step 做硬高光与用 pow 做连续高光各适合什么

题目

同一卡通 Shader 前期用 pow(max(0, dot(N, halfA)), _SpecularNum),后期把 _SpecularNum 改成阈值并用 step。两者在画面与调参上各有什么取舍?

深入解析
  • pow:高光平滑过渡_SpecularNum 大则锐利但仍连续,适合偏 PBR 或柔和卡通。
  • step:高光二值化,阈值以下全黑、以上整块亮,块面感强,符合赛璐璐硬高光;但会失去柔和过渡,角度稍变高光可能整坨跳变。
答题示例

pow 是连续变化,高光边缘还是渐变的,调指数是在调锐利程度。

step 是硬切,高光要么整块亮要么没有,更像平面卡通,但边缘会跳,不如 pow 好混自然光。

参考文章
  • 12.卡通风格渲染-具体实现

6. 素描把 atten 乘在最终 sketchColor 上与卡通只乘 halfLambertNum 有何观感差异

题目

Lesson14 返回 sketchColor.rgb * atten * _Color,而卡通实现把衰减主要乘进 Ramp 的输入。对阴影内高光、环境光分量可能产生什么不同?

深入解析
  • **整幅乘 atten**:Lesson14_Sketch 定稿片元为 sketchColor.rgb * atten * _Color,没有再在片元里拆布林冯;笔触层与 whiteColor 所代表的亮部都乘同一 atten,进影后整体变暗。
  • 卡通halfLambertNum *= atten 后再采 _RampTex,高光仍用 pow/step 路径未再乘 attenLesson12 定稿);与素描「整张贴图混合结果统一乘衰减」不同,进影后高光相对更亮、与漫反射分阶的耦合更弱
答题示例

素描最后是加权素描纹理和白色一起乘 atten,进影子整幅一起暗。

卡通先把半兰伯特乘 atten 再查 Ramp,高光单独算且没乘 atten,阴影里高光往往还相对更跳一点。

参考文章
  • 14.素描风格渲染-具体实现

7. GrabPass 水波在性能与平台上要注意什么

题目

水波 Shader 每物体 GrabPass 抓屏,再加立方体反射与菲涅耳混合。主要瓶颈与常见坑是什么?

深入解析
  • GrabPass 易触发额外全屏拷贝,多水面、高分辨率下带宽与填充率压力大;透明队列排序错误会导致抓到错误背景
  • 移动平台、SRP 下 GrabPass 行为与 Built-in 可能不一致,需按目标管线查文档或改 Blit/相机颜色目标 方案。
答题示例

GrabPass 相当于多一次拷屏,水面一多就很贵。

排序不对抓到的就不是你想折射的那层;换 URP 以后行为也可能不一样,不能当内置管线照搬。

参考文章
  • 21.噪声-水波效果-具体实现

8. 噪声雾效里 f * _FogDensity * (1 + noise)saturate 的语义

题目

noisetex2D - 0.5 缩放得到,可正可负。乘在雾因子上会如何改变雾浓?saturate 最后起什么作用?

深入解析
  • 1 + noisenoise 为负压低雾因子,为正抬高,形成空间不均匀的雾浓。
  • saturate 把混合因子钳在 0~1,避免 lerp 第三分量越界产生意外外推。
答题示例

noise 正的地方雾更浓,负的地方更淡,所以雾一块一块不均匀。

saturate 把因子卡在 0 到 1,防止 lerp 混出怪色。

参考文章
  • 23.噪声-噪声雾效-具体实现


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏