81.Shader开发基础总结

  1. 81.总结
    1. 81.1 核心要点速览
      1. 渲染路径是什么
      2. 前向渲染 Forward
      3. 顶点照明 Vertex Lit
      4. 延迟渲染 Deferred
      5. 三种路径对照
      6. 多种光源与 Forward 分工
      7. 阴影:投射、接收与衰减合一
        1. 投射与 Fallback
        2. 接收(前向)
        3. 衰减、附加光与特殊材质
      8. 标准漫反射与标准高光
      9. 立方体纹理与环境映射
      10. 渲染纹理与镜面、玻璃
      11. 程序纹理、程序材质与 Shader 动态
      12. 动态效果与时间驱动
      13. 顶点动画:广告牌、批处理与阴影一致
      14. 屏幕后期效果
        1. OnRenderImage、材质与 Sobel
        2. 纯色底、高斯、Bloom、累积模糊
      15. 深度与法线纹理
        1. 获取与解码
        2. 雾与基于深度的屏幕效果
        3. 几何边缘(深度 + 法线)
    2. 81.2 面试题精选
      1. 基础题
        1. 1. Shader 里 LightMode 是干什么的?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        2. 2. 前向渲染里 ForwardBase 和 ForwardAdd 大致各干什么?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        3. 3. ForwardAdd 里为什么常见 Blend One One?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        4. 4. 点光源在附加 Pass 里怎么用纹理算衰减?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        5. 5. 没有 ShadowCaster Pass 时,物体为什么可能既不投影也不易接好阴影?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        6. 6. 镜面材质里对 UV 做 1 - uv.x 一般在解决什么?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        7. 7. 程序纹理用 C# 生成和全在 Shader 里算各有什么取舍?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        8. 8. 顶点动画 Shader 里为什么要写 DisableBatching?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        9. 9. Renderer.material 和 sharedMaterial 改参数时影响范围差在哪?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        10. 10. CGINCLUDE 和写在单个 Pass 里的 CGPROGRAM 有啥分工?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        11. 11. DepthTextureMode.Depth 和 DepthNormals 各多出来什么纹理?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
      2. 进阶题
        1. 1. 顶点照明路径为什么「省」但画面容易糙?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        2. 2. 延迟渲染为什么擅长「多光源」?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        3. 3. 接收阴影的「三剑客」各自干什么?有什么硬约定?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        4. 4. UNITY_LIGHT_ATTENUATION 和 multi_compile_fwdadd_fullshadows 解决什么问题?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        5. 5. 玻璃 Shader 为什么常把 Queue 设成 Transparent 却保留 RenderType=Opaque?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        6. 6. ComputeGrabScreenPos 之后为什么在片元里还要除以 w?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        7. 7. 序列帧动画 Shader 里如何用时间选帧又不爆索引?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        8. 8. OnRenderImage 里若不对 destination 写入会发生什么?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        9. 9. 后处理基类里为什么要查 shader.isSupported 再给 Material?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        10. 10. 二维高斯模糊为什么通常拆成「先横再竖」两个 Pass?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        11. 11. Bloom 为什么要「先提亮区再模糊再叠回去」?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
      3. 深度题
        1. 1. 延迟渲染里透明物体为什么常常要「特殊处理」甚至回退前向?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        2. 2. 透明度测试与透明度混合在阴影上为何难度差很多?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        3. 3. Schlick 菲涅尔近似在环境反射里怎么用?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        4. 4. Render Texture 绑相机和 GrabPass 各适合什么场景?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        5. 5. frac 在滚动背景里解决了什么数值问题?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        6. 6. 顶点动画又想合批时,课文里提了哪两种「把模型空间信息带进顶点」的办法?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        7. 7. 河流做了顶点起伏,为什么默认 Fallback 的阴影会「对不上」?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        8. 8. 用深度纹理做「相机运动」运动模糊时,为什么要传上一帧的 worldToClip 矩阵?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        9. 9. 基于 _CameraDepthNormalsTexture 的边缘检测,相对只对 _MainTex 做 Sobel 有什么优势?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章

81.总结


81.1 核心要点速览

渲染路径是什么

渲染路径回答的是:引擎在本帧如何调度光源、阴影 Pass、以及延迟/前向各阶段读写哪些 RT。它在 Project Settings 与相机上配置,优先级高于单个材质里的「自我感觉」——材质不能单独换一条管线。

LightMode 是 Pass 上的标签,用来声明「这一段要插在引擎哪条调度链里」。当前工程启用的渲染路径只会执行与之匹配的 Pass;不匹配时,该 Pass 可能完全不参与光照,表现就像 Shader 写错,实际是 Pass 没被调度

  • 全局 / 相机Edit → Project Settings → Graphics 设默认路径;相机组件可覆盖,便于主场景与 UI、小地图分轨。
  • 迁移或合并工程时:先列出目标路径会调用的 LightMode 清单,再改自定义 Shader 的 Pass 标签与 Fallback,避免合并后大面积「灯不亮」。

前向渲染 Forward

前向的大致数据流是:光栅化阶段把几何属性插值进片元,光照在片元阶段按光源贡献累加。多光源时,引擎通常用 多 Pass 或多次叠加 实现,而不是在单个片元里对「所有灯」写一个巨型循环——那样寄存器与分支压力都难看。成本上记住一句:灯越多、被照亮的像素越多,前向越容易在片元侧堆工作量

Built-in 常见拆分:

  • ForwardBase:环境、主定向光、Lightmap、球谐间接等 基底 多在这里一次算清,给后续 Pass 一个合理的起点颜色。
  • ForwardAdd:对 附加逐像素光源Blend One One 叠加上去;每多一盏附加光,往往就多一次绘制或一轮叠加。

UNITY_LIGHTMODEL_*LightingXXX 等符号与当前路径下的 uniform、宏绑定;版本升级后名字可能微调,Lighting.cginc 为准 比死记字符串可靠。

顶点照明 Vertex Lit

光照在 顶点着色器 算完,结果经插值进入片元,片元一般不再做完整逐像素光照。顶点数量远小于片元时省算力,但高光、明暗边界会沿三角形边缘拉开,马赫带、三角感 比逐片元前向明显。适合作为对照理解「为什么老设备爱用、现在主流程很少选」。

延迟渲染 Deferred

第一阶段:几何 Pass 把表面参数编码进 G-buffer(多张 RT)。第二阶段:屏幕空间光照 读 G-buffer,对每个光源在屏幕分辨率上累加贡献。灯多时,成本主要由 屏幕分辨率 × 光源数量 驱动,往往比前向「每个物体多画几遍」更容易预算。

代价是 G-buffer 通道与带宽有限:透明物体、MSAA、复杂材质需要的额外数据常常放不下,需要前向 Pass 回退、拆材质或砍特性。延迟路径下 内置变量集合、Include、LightMode 与前向不同,不能照搬前向 Shader 直接跑。

三种路径对照

维度 前向 Forward 顶点照明 Vertex Lit 延迟 Deferred
计算主战场 多在片元;多光源常多 Pass / 多趟 顶点为主,片元吃插值 屏幕空间光照;多点光时常更划算
画质倾向 逐像素光照好做 粗、易见三角感 受 G-buffer 精度与带宽限制
常见取舍 灯不太多、透明和 MSAA 要好商量时 极省、老机或演示 灯多、大面积不透明静态

抓三张牌即可:前向怕多灯叠片元顶点照明省算力换画质延迟怕透明与 G-buffer 装不下

多种光源与 Forward 分工

Built-in 用 ForwardBase / ForwardAdd 分工#pragma multi_compile_* 变体,在 编译期 展开点光、聚光、平行光等组合,把 AutoLight.cginc 里需要的 attenuation、Cookie、方向向量绑到正确的 uniform 上,避免运行时一条 if 链扫所有灯型。

  • 基底与附加ForwardBase 铺环境、主光等;ForwardAdd 用加法混合叠附加光,职责清晰,变体也好查。
  • 变体指令multi_compile_fwdbasemulti_compile_fwdaddPOINT / SPOT / USING_DIRECTIONAL_LIGHT 等分支配套使用。
  • 方向:平行光常用 _WorldSpaceLightPos0.xyz;点光、聚光用世界空间 光位置 − 表面点
  • 点光衰减unity_WorldToLight 得到 lightCoorddot(lightCoord, lightCoord).xx 作纹理坐标采 _LightTexture0UNITY_ATTEN_CHANNEL 取通道;部分配置下距离衰减在 _LightTextureB0
  • 聚光lightCoord 常为 float4,用分量判断是否在锥体内;_LightTexture0 常管 Cookie / 角度衰减,_LightTextureB0 常管距离衰减。

阴影:投射、接收与衰减合一

阴影分两条链路:投射(ShadowCaster) 从光源视角写深度到 shadow map;接收 在算光照时把像素深度与 map 比较得到可见性因子。SHADOW_*UNITY_LIGHT_ATTENUATION 封装了 PCF、级联等细节,但调试时仍要分清 哪个 Pass 写深度、哪个 Pass 读阴影

投射与 Fallback

  • 没有 LightMode = ShadowCaster 时,物体不会向光源阴影图贡献几何深度;可用 Fallback 指向内置带 ShadowCaster 的 Shader,减少重复实现。
  • 手写投射模板:#pragma multi_compile_shadowcasterV2F_SHADOW_CASTERTRANSFER_SHADOW_CASTER_NORMALOFFSETSHADOW_CASTER_FRAGMENT;该 Pass 只输出深度相关,不要混入完整光照。

接收(前向)

  • #include "AutoLight.cginc",配合 SHADOW_COORDSTRANSFER_SHADOWSHADOW_ATTENUATION
  • 宏默认约定:appdata 顶点字段名为 vertex,v2f 中裁剪空间为 pos。改名后宏展开对不上,编译或软阴影阶段容易出错。

衰减、附加光与特殊材质

  • UNITY_LIGHT_ATTENUATION距离衰减 × 阴影 合成一个因子,再乘到漫反射、高光等项。ForwardAdd 若需要附加光也产生阴影,需 multi_compile_fwdadd_fullshadows 等变体,否则常见现象是 有衰减、无阴影
  • Alpha Test:主 Pass clip 的位置须与 ShadowCaster 一致;Fallback 的 Cutout Shader 属性名(如 _Cutoff)要对齐。单面网格从光背面不可见时,投射可能断裂,可将 Cast Shadows 设为 Two Sided。
  • Alpha Blend:关闭 ZWrite 的半透明与「用深度写阴影图」的假设冲突;实时真透明阴影成本高,用不透明 Fallback 投射阴影属于 工程上可接受的近似

标准漫反射与标准高光

「标准」在这里指:法线贴图 + 世界空间光照 已经具备的前提下,把 ForwardBase / ForwardAdd、阴影宏、UNITY_LIGHT_ATTENUATION、附加光按 Built-in 约定接全,得到可维护的默认模板(漫反射去掉高光项;高光保留 Blinn-Phong 一类镜面项)。

  • Queue / RenderType:不透明物体常用 Queue=GeometryRenderType=Opaque,便于替换材质、深度预通道、依赖 RenderType 的后处理按预期工作。
  • FallbackFallback "Diffuse""Specular" 让引擎在查找 ShadowCaster 等子 Pass 时走成熟路径,减少「有主 Pass、无投射阴影」的遗漏。

立方体纹理与环境映射

Cubemap 把六张 2D 贴图贴在立方体六面,采样时用 单位方向向量 选中对应面与 UV,适合天空盒、环境反射和粗粒度折射(精细折射往往还要屏幕空间或光线步进,这里不展开)。

  • 接缝:六面 Wrap ModeClamp 可减轻面与面交界处的重复采样感。
  • 动态Camera.RenderToCubemap 把场景渲染进 Cubemap;分辨率、是否 Readable、与 CPU 读回的同步都要按用途权衡。
  • 反射:世界空间反射方向 texCUBE,与漫反射 lerp 混合是常见写法。
  • 折射refract(入射方向, 法线, ηA/ηB),片元里常只保留 折射率比值 一个常数。
  • 菲涅尔:Schlick 形式 R0 + (1-R0) * pow(1 - dot(V,N), 5),掠射时反射增强;再与漫反射或环境项混合。
  • 与直接光的关系:Cubemap 是 环境/反射项,仍应叠加 ForwardBase/ForwardAdd 与阴影后的直接光,否则暗部会像自发光。

渲染纹理与镜面、玻璃

Render Texture:相机输出写到纹理而非直接上屏,材质再采样,用于镜子、监视器、扭曲等。与 GrabPass(Pass 内抓取当前颜色缓冲)、**OnRenderImage**(整帧后再处理)是不同入口,共同点都是 先有一份颜色/深度再二次采样

  • 镜子:额外相机渲染「镜像视角」到 RT,主材质按镜像 UV 采样;UV 常做水平翻转 1 - uv.x。若镜子本体不投射复杂阴影,有时 Fallback "Diffuse" 借内置 ShadowCaster,接收仍按项目需求接。
  • Grab 与顺序:玻璃要在 身后场景已画入颜色缓冲 之后再 Grab,故 Queue 常设为 Transparent 一类偏后顺序;RenderType 仍可保持 Opaque 便于替换,以项目约定为准。
  • 屏幕 UVComputeGrabScreenPos(clipPos) 得齐次坐标,片元中 xy / w 映射到屏幕 UV,再按法线偏移 xy 模拟折射。
  • Custom Render TextureInitialization / Update Mode 控制何时更新;Double Buffered 缓解同一帧读写同一张 RT 的竞争。与「镜面多相机」解决的是不同层面的问题。

程序纹理、程序材质与 Shader 动态

程序纹理:颜色由算法生成,不依赖预烘焙大图,参数化、平铺规则纹理都方便。

  • C# 路径Texture2D + SetPixel / Apply 适合离线或低频更新,再赋给 _MainTex
  • Shader 路径floor 划分格子、奇偶判定、lerp 两色,典型如棋盘格。
  • 材质工作流:Substance、Houdini 等离线生成多通道贴图,与运行时片元里算纹是 两条生产线.sbsar 进 Unity 常依赖对应插件。

**Renderer.materialsharedMaterial**:前者访问会 实例化 材质,只影响该物体;后者改资源,同引用物体一起变。批量物体改参数前先想清楚要哪一种。

动态效果与时间驱动

动画本质:每帧更新 顶点、UV、颜色或透明度之一。约 24 FPS 以上人眼会感知为连续运动;与游戏逻辑对齐时,除 _Time 外还要考虑 Time.deltaTime、固定步长等。

  • **_Time / _SinTime / _CosTime**:内置时间标量与预计算三角函数,适合滚动、摇摆等。
  • **unity_DeltaTime**:帧间隔与平滑帧间隔,需要与物理或 UI 同步的位移、速度常用它。
  • 序列帧:大图集行列分格,时间取模选帧,UV 缩放到单格;透明物体注意 Queue 与混合。
  • 滚动 UVuv + speed * tfrac 回卷到 [0,1),做无缝平铺。
  • 河流类顶点动画:模型空间正弦位移 + 可选 UV 流动;常 DisableBatching=True,否则批处理破坏物体空间语义。

顶点动画:广告牌、批处理与阴影一致

Billboard:在模型空间用 相机方向 重建正交基 right / up / normal,把顶点摆到始终面向屏幕。normal 常取「相机位置 − 物体中心」在模型空间的方向;_VerticalBillboarding 缩放法线 Y 分量:偏 0 时像立在地面上的牌,偏 1 时完全朝向相机。通常 **透明队列、Cull OffDisableBatching=True**。

批处理与物体空间:动态/静态批处理合并网格或统一矩阵后,每个物体独立的模型空间 失效,object space 里写的正弦波会整错。要么关闭批处理,要么把 未变形前的模型空间坐标 烘焙进第二套 UV 或顶点色,在 Shader 用 appdata_full 读回。

与阴影一致:主 Pass 位移了顶点,Fallback 的 ShadowCaster 仍用原始网格 时,阴影会留在脚下。应在自定义 ShadowCaster Pass 中 复用同一套顶点变换,再调用 TRANSFER_SHADOW_CASTER_NORMALOFFSET 等写入阴影图。

屏幕后期效果

场景先渲染到颜色缓冲,再 全屏三角形 采样该缓冲做二次处理(调色、模糊、描边)。Built-in 典型写法:MonoBehaviourOnRenderImage,内部用 Graphics.Blit 串联多 Pass 材质。

OnRenderImage、材质与 Sobel

  • 契约OnRenderImage(source, dest) 必须向 dest 写入,否则输出为黑。[ImageEffectOpaque] 仅约束调用顺序在 不透明物体之后source 是否含透明内容仍取决于当前管线与相机设置,排障时用 Frame Debugger 核对实际 Pass 顺序。
  • BlitGraphics.Blit(source, dest, material, pass)source 绑定为材质的 _MainTex 并执行指定 Pass,是串联多 Pass 后处理的主轴。
  • 运行时材质new Material 常设 HideFlags.DontSave,避免运行期生成的材质写进场景资产。
  • 亮度 / 饱和度 / 对比度:亮度缩放 RGB;饱和度用 Rec.709 亮度 L = 0.2126R + 0.7152G + 0.0722Blerp;对比度向 0.5 拉。全屏 Pass 常用 ZTest AlwaysCull OffZWrite Off
  • Sobel:3×3 邻域灰度与 Sobel 核得 GxGy|Gx|+|Gy| 作边缘强度;步长用 _MainTex_TexelSize 对齐像素。

纯色底、高斯、Bloom、累积模糊

  • 描边 + 底色_BackgroundExtent_BackgroundColor 在原图与「纯色底 + 描边」之间插值。
  • 可分高斯:二维核拆成水平、垂直各一次一维卷积;5-tap 权重常用 0.0545 / 0.2442 / 0.4026。公共结构体与函数放在 CGINCLUDEENDCG,供多 Pass 共用。
  • 加重模糊:降低临时 RT 分辨率、多轮 Blit、或增大 UV 偏移步长,可组合使用。
  • Bloom:阈值提取亮区 → 高斯模糊 → 与原始场景相加;增益过高会导致整屏灰雾。
  • 累积运动模糊:一 Pass 混合当前帧与历史 RGB;另一 Pass 将权重写入 Alpha 供下一帧使用,此处 Alpha 作历史权重容器,与透明度语义不同。

深度与法线纹理

深度纹理表示 从相机沿视线到该像素遮挡面的距离(经 Unity 线性化与宏封装后使用),雾效、屏幕空间运动模糊、几何边缘检测、部分水面交互都依赖它。

获取与解码

  • 开启Camera.depthTextureMode 包含 DepthDepthNormals,每多一种模式通常多一份 填充与带宽
  • 仅深度_CameraDepthTexture 配合 SAMPLE_DEPTH_TEXTURELinearEyeDepth(视空间 Z)、Linear01Depth(归一化线性深度)。
  • 深度 + 法线_CameraDepthNormalsTextureDecodeDepthNormal,或拆成 DecodeFloatRGDecodeViewNormalStereo;编码细节随 Unity 版本与平台宏变化,以当前 UnityCG.cginc 等为准。

雾与基于深度的屏幕效果

  • 自定义雾:由深度还原世界坐标 worldPos = _WorldSpaceCameraPos + depth * rayDirrayDir 由相机视锥四角射线在片元插值;C# 一次传入射线向量矩阵较省事。雾因子可用线性、指数、平方指数等,再 lerp 场景色与雾色。
  • 内置雾:物体 Shader 中 #pragma multi_compile_fogUNITY_FOG_* 在顶点/片元插值雾色。
  • 屏幕空间运动模糊:当前帧用裁剪坐标与深度还原世界位置,乘 上一帧视图投影矩阵 得到上一帧屏幕坐标,与当前帧坐标差为 屏幕空间速度,沿该方向多采样颜色缓冲。material.SetMatrix 传入历史矩阵;若启用 TAA、动态分辨率或抖动投影,须保证 矩阵与 source 纹理分辨率、抖动 一致,否则拖影错位。

几何边缘(深度 + 法线)

仅对 颜色 做 Sobel 时,贴图自身的明暗对比也会被当成边缘。对 深度观察空间法线 做 Roberts 等对角差分,更容易突出 几何不连续(深度突变、法线转折)。阈值需与 深度尺度 一致,常做归一化或按近远平面缩放,否则近处全边、远处无边。


81.2 面试题精选

基础题

1. Shader 里 LightMode 是干什么的?

题目

Pass 里写的 LightMode 和 Project 里选的渲染路径是什么关系?

深入解析
  • LightMode 声明该 Pass 参与哪条渲染路径、哪一光照阶段
  • 工程 当前渲染路径 决定引擎会调度哪些 Pass;不匹配时该 Pass 可能根本不跑或不起光照作用。
  • 前向里常见 ForwardBase / ForwardAdd;延迟里常见 Deferred 等,具体枚举以当前 Unity 版本文档为准
答题示例

LightMode 告诉引擎这个 Pass 属于哪条路径的哪一步;和项目渲染路径对不上就不会按你想的参与光照。

参考文章
  • 1.渲染路径-渲染路径概述

2. 前向渲染里 ForwardBaseForwardAdd 大致各干什么?

题目

多光源时为什么常常要两个 Pass?

深入解析
  • ForwardBase:一般算 环境/主光基底、Lightmap、球谐间接 等,先铺好「底色」。
  • ForwardAdd:对 附加逐像素光源加法混合,每多一类附加光可能多一轮开销。
  • 这是 Built-in 前向的经典拆分,URP/HDRP 机制不同,面试若限定 Unity 版本要说清。
答题示例

Base 先算主光和环境等底色,Add 再叠附加光,多光就多次叠加。

参考文章
  • 2.渲染路径-前向渲染路径

3. ForwardAdd 里为什么常见 Blend One One

题目

它和基底 Pass 的颜色怎么叠在一起?

深入解析
  • 加法混合把本 Pass 算出的 附加光贡献 直接累进已有颜色缓冲。
  • 环境光、主光基底已在 ForwardBase 算过,**ForwardAdd** 不应再重复环境项。
答题示例

Add Pass 用 One One 把每一盏附加光的结果加到 framebuffer 上,和 Base 里已经铺好的底色相加。

参考文章
  • 8.多种光源-光照衰减
  • 11.多种光源-综合实现

4. 点光源在附加 Pass 里怎么用纹理算衰减?

题目

unity_WorldToLight_LightTexture0 大致干什么?

深入解析
  • 先把顶点 变到光源空间 得 **lightCoord**。
  • dot(lightCoord, lightCoord).xx距离平方 映射到衰减纹理坐标,采 **_LightTexture0**(或配置下配合 **_LightTextureB0**),取 **UNITY_ATTEN_CHANNEL**。
答题示例

顶点乘 WorldToLight 得到光源空间坐标,用距离平方去采光照衰减纹理得到 atten。

参考文章
  • 8.多种光源-光照衰减
  • 9.多种光源-点光源光照衰减计算

5. 没有 ShadowCaster Pass 时,物体为什么可能既不投影也不易接好阴影?

题目

Fallback 能补哪一块?

深入解析
  • 投影依赖把深度写入光源视角的阴影图;无 ShadowCaster 则本 Shader 不参与该 Pass,除非 Fallback 链上有带 ShadowCaster 的内置 Shader。
  • 接收还要在光照 Pass 里对阴影图采样并乘到光照上,与是否 Fallback 无投影是两件事。
答题示例

投影必须走 ShadowCaster 写深度图,没有就自己写或靠 Fallback 借内置的;接收阴影是另一套采样和衰减计算。

参考文章
  • 15.阴影-不透明物体阴影-让不透明物体投射阴影

6. 镜面材质里对 UV 做 1 - uv.x 一般在解决什么?

题目

和相机渲染到 RT 的关系?

深入解析
  • 相机看到的是 真实世界侧 画面,贴到镜子模型上需要 左右对调 才像反射。
  • 顶点或片元 改 UV 比改 RT 渲染目标更轻。
答题示例

把采样到的镜子图在横向上翻一下,看起来才像镜面左右对调。

参考文章
  • 36.高级纹理-渲染纹理-镜面效果-效果实现

7. 程序纹理用 C# 生成和全在 Shader 里算各有什么取舍?

题目

棋盘格例子说明数据在哪更新。

深入解析
  • **C#**:CPU 写像素,适合 编辑器工具、低频更新、要与 Inspector 交互;注意 Apply内存
  • Shader:每像素数学算色,无大贴图资产、参数可调;依赖 GPU 并行
答题示例

C# 适合算好一张图丢给材质;Shader 适合每帧每像素现算图案。

参考文章
  • 41.高级纹理-程序纹理-程序纹理是什么
  • 42.高级纹理-程序纹理-基础实现-CSharp代码动态生成程序纹理
  • 43.高级纹理-程序纹理-基础实现-Shader代码动态生成程序纹理

8. 顶点动画 Shader 里为什么要写 DisableBatching

题目

批处理合并后丢了什么信息?

深入解析
  • 批处理把多物体 合成更少 DrawCall,顶点可能进入 统一空间合并网格逐物体模型空间坐标 不再独立。
  • 依赖 object space 算 sin 波、广告牌基向量时会 乱套
答题示例

合批后各模型自己的模型空间混了,顶点动画按模型空间算就会错,所以要关批或把原始坐标烘焙到色/UV。

参考文章
  • 49.动态效果-顶点动画-滚动的2D河流-具体实现
  • 52.动态效果-顶点动画-顶点动画的注意事项-批处理

9. Renderer.materialsharedMaterial 改参数时影响范围差在哪?

题目

什么时候会实例化新材质?

深入解析
  • **material**:首次访问常 克隆 一份实例,只影响该 Renderer。
  • **sharedMaterial**:改的是 资源引用,同材质的物体 一起变
答题示例

material 是拷一份自己改;sharedMaterial 一改全场景共用那份的都变。

参考文章
  • 55.屏幕后期处理效果-CSharp代码修改材质参数

10. CGINCLUDE 和写在单个 Pass 里的 CGPROGRAM 有啥分工?

题目

高斯模糊为什么常用它?

深入解析
  • CGINCLUDESubShader 内、Pass 外,放 多 Pass 共用的函数、宏、结构体
  • 可分高斯 水平/垂直两 Pass 片元逻辑几乎相同,只改 UV 偏移方向,适合抽一层避免复制粘贴。
答题示例

CGINCLUDE 给多个 Pass 共享一份 CG 代码;高斯模糊两个方向 Pass 共用同一套卷积函数。

参考文章
  • 64.屏幕后期处理效果-效果实现-高斯模糊-基础实现

11. DepthTextureMode.DepthDepthNormals 各多出来什么纹理?

题目

Shader 里变量名一般叫什么?

深入解析
  • Depth:**_CameraDepthTexture**,配合 SAMPLE_DEPTH_TEXTURE + LinearEyeDepth/Linear01Depth
  • DepthNormals:**_CameraDepthNormalsTexture**,一包里编码深度与法线,用 DecodeDepthNormal 等解。
答题示例

Depth 只要深度图;DepthNormals 同一张里还有法线,省一次几何 Pass 但更重一点。

参考文章
  • 72.深度和法线纹理-查看深度纹理
  • 73.深度和法线纹理-查看法线纹理

进阶题

1. 顶点照明路径为什么「省」但画面容易糙?

题目

和典型前向片元光照比,瓶颈和缺陷各在哪?

深入解析
  • 光照在 顶点 算完再 插值,片元侧缺少完整逐光源计算,高光、聚光斑、阴影边界 等高频细节容易 糊、跳、分叉
  • 顶点数远少于片元数,计算量小,适合极简或老管线,不适合主画质。
答题示例

光在顶点算再插到像素里,算得少但细节差,高光和阴影边缘容易不行。

参考文章
  • 3.渲染路径-顶点照明渲染路径

2. 延迟渲染为什么擅长「多光源」?

题目

用「谁遍历谁」说明与前向的差异。

深入解析
  • 前向:光源影响常体现为 对物体的额外绘制或额外计算,光源多 → 物体侧成本涨得快。
  • 延迟:几何 Pass 把参数写进 G-buffer,光照 Pass 在屏幕空间按光源算,复杂度更跟 像素与光源数量 绑定,多小型动态光时常更划算。
  • 代价是 带宽、G-buffer 布局、透明与 MSAA 等要额外处理。
答题示例

前向是多光就多折腾物体;延迟先把表面信息存 G-buffer 再在屏幕上算光,多光往往更划算但透明和抗锯齿麻烦。

参考文章
  • 4.渲染路径-延迟渲染路径
  • 5.渲染路径-渲染路径对比

3. 接收阴影的「三剑客」各自干什么?有什么硬约定?

题目

SHADOW_COORDSTRANSFER_SHADOWSHADOW_ATTENUATION 怎么用?

深入解析
  • SHADOW_COORDS(n):在 v2f 里占位阴影插值坐标,n 取下一个空闲 TEXCOORD 索引
  • **TRANSFER_SHADOW**:顶点里写入阴影坐标;宏内部会读 appdata 的 vertex 和 **v2f 的 pos**,改名会破坏展开。
  • **SHADOW_ATTENUATION**:片元里采样比较得阴影因子,常与 (漫反射+高光) 相乘。
答题示例

COORDS 声明插值寄存器,TRANSFER 在顶点填坐标,ATTENUATION 在片元算出阴影明暗;顶点参数和 pos 名字要对上宏。

参考文章
  • 16.阴影-不透明物体阴影-让不透明物体接受阴影

4. UNITY_LIGHT_ATTENUATIONmulti_compile_fwdadd_fullshadows 解决什么问题?

题目

为什么不手写 atten 再乘 SHADOW_ATTENUATION?

深入解析
  • UNITY_LIGHT_ATTENUATION(atten, i, worldPos)AutoLight.cginc 里把 距离衰减与阴影衰减 合成一个因子。
  • #pragma multi_compile_fwdadd_fullshadowsForwardAdd 生成 带阴影 / 不带阴影 等变体,附加光才能正确收到阴影相关关键字与纹理。
答题示例

一个宏统一光衰减和阴影;ForwardAdd 要用 fullshadows 变体,附加光才有完整阴影路径。

参考文章
  • 17.阴影-不透明物体阴影-光照衰减和阴影

5. 玻璃 Shader 为什么常把 Queue 设成 Transparent 却保留 RenderType=Opaque

题目

Grab 的顺序和材质替换各得到什么?

深入解析
  • Queue 延后:保证 身后几何先画进 framebufferGrab 内容才正确。
  • RenderType 仍 Opaque:便于 Shader Replacement 等仍按不透明参与某些遍历;与「视觉上透明」解耦。
答题示例

Transparent 队列为了晚画好抓屏;RenderType 保持 Opaque 是给引擎其它系统一个「材质类别」标签。

参考文章
  • 39.高级纹理-渲染纹理-玻璃效果-基础实现

6. ComputeGrabScreenPos 之后为什么在片元里还要除以 w

题目

和透视投影有什么关系?

深入解析
  • grabPos齐次屏幕空间 插值结果,透视除法后才是 0~1 纹理 UV 可用的坐标。
  • 偏移折射时应对 xy 与 w 一致处理,避免近大远小下采样错位。
答题示例

齐次坐标要先除 w 才变成真正的屏幕归一化 UV,不然透视下会歪。

参考文章
  • 39.高级纹理-渲染纹理-玻璃效果-基础实现
  • 40.高级纹理-渲染纹理-玻璃效果-带法线纹理实现

7. 序列帧动画 Shader 里如何用时间选帧又不爆索引?

题目

图集行列与取余。

深入解析
  • floor(_Time.y * speed) 或类似得到 流动索引,对 总帧数取余 循环。
  • 帧索引 推出 行、列,把 0~1 UV 映射到 子矩形 内采样。
答题示例

时间乘速度取整再 mod 总帧数得到第几帧,然后把 UV 缩放到那一小格里采样。

参考文章
  • 46.动态效果-纹理动画-序列帧动画

8. OnRenderImage 里若不对 destination 写入会发生什么?

题目

Graphics.Blit 最少要怎么写才能不黑屏?

深入解析
  • 回调负责把结果写到 destination空实现 等于 无输出 → 常见 黑屏
  • Graphics.Blit(source, destination) 至少 拷贝 sourcedestination
答题示例

不往 destination 里写就黑;至少要 Blit 一下把 source 拷过去。

参考文章
  • 56.屏幕后期处理效果-基本实现原理

9. 后处理基类里为什么要查 shader.isSupported 再给 Material

题目

HideFlags.DontSave 大致防什么?

深入解析
  • 平台/硬件 可能不支持该 Shader,直接 Blit 会 报错或花屏
  • 运行时 new Material 若不 DontSave,可能被 序列化进场景污染资产
答题示例

先 isSupported 再创建材质,避免无效 Blit;DontSave 防止运行时材质当资源存盘。

参考文章
  • 57.屏幕后期处理效果-屏幕后处理基类

10. 二维高斯模糊为什么通常拆成「先横再竖」两个 Pass?

题目

复杂度从多少降到多少量级?

深入解析
  • 5×5 二维卷积25 次 纹理采样量级;两次一维 5 tap10 次
  • 中间需 一张临时 RT 存第一次结果,再 Blit 第二次。
答题示例

横竖各扫一遍一维核,采样数从 n² 量级降到约 2n;中间多一次 Blit 换性能。

参考文章
  • 63.屏幕后期处理效果-效果实现-高斯模糊-基本原理
  • 64.屏幕后期处理效果-效果实现-高斯模糊-基础实现

11. Bloom 为什么要「先提亮区再模糊再叠回去」?

题目

阈值 Pass 干什么用?

深入解析
  • 阈值提取暗部不参与 模糊,避免 整屏发灰
  • 模糊 让高光 晕开合成 把光晕 加回 原图产生 辉光
答题示例

先抠出够亮的像素再模糊,暗处不会被糊成一片,最后加回去才有 Bloom。

参考文章
  • 67.屏幕后期处理效果-效果实现-Bloom效果-具体实现

深度题

1. 延迟渲染里透明物体为什么常常要「特殊处理」甚至回退前向?

题目

G-buffer 假设与透明混合冲突在哪里?

深入解析
  • 延迟 光照阶段读的是一张或多张已固定的 G-buffer,默认按 不透明表面 排序与混合。
  • 透明物体需要 深度排序与 Alpha 混合,与「先全屏算完光照」的流程 冲突;常见做法是 单独前向透明 Pass分层渲染简化光照
  • 具体项目要在 画质、排序稳定性、性能 间取舍。
答题示例

延迟是按屏幕像素读 G-buffer 算光,透明要排序和混合,和那一套打架,所以透明经常单独前向或分层处理。

参考文章
  • 4.渲染路径-延迟渲染路径
  • 5.渲染路径-渲染路径对比

2. 透明度测试与透明度混合在阴影上为何难度差很多?

题目

内置半透明 Shader 为什么默认不搞真·透明阴影?

深入解析
  • Alpha Test:片元 discard/clip 后可在 ShadowCaster 里同样剔除,再配合 Cutout 类 Fallback_Cutoff / _Color 命名对齐;单面网格常需 Two Sided Cast
  • Alpha Blend:关 ZWrite、要排序混合,与 阴影深度/遮挡 的经典路径冲突;内置多直接不承接;Fallback 到不透明 可「假投影」但不真实。
答题示例

Cutout 能 clip 掉阴影投射像素所以还能做;半透明关深度写入又排序,和阴影深度逻辑拧巴,所以内置往往不做真透明阴影。

参考文章
  • 18.阴影-透明物体阴影-透明度测试物体阴影
  • 19.阴影-透明物体阴影-透明度混合物体阴影

3. Schlick 菲涅尔近似在环境反射里怎么用?

题目

式子中 R₀、V·N 各代表什么直觉?

深入解析
  • R(θ) = R₀ + (1−R₀)(1−V·N)⁵:掠射 (V·N→0) 时反射变强,正对 (V·N→1) 时接近 R₀
  • 实现上用 lerp漫反射 / 反射采样色 间按 R(θ) 混合,再与 多光源+阴影 流程结合。
答题示例

用视角和法线夹角控制反射权重,掠角更亮更像玻璃边缘;R0 是正对时的基础反射率。

参考文章
  • 32.高级纹理-立方体纹理-菲涅尔反射-菲涅尔反射基础实现
  • 33.高级纹理-立方体纹理-菲涅尔反射-菲涅尔反射结合漫反射及阴影

4. Render Texture 绑相机和 GrabPass 各适合什么场景?

题目

镜子、全屏玻璃各偏向哪种?

深入解析
  • Target Texture离屏相机 只画特定视角(镜面、小地图),分辨率与更新频率可控,可多相机管理。
  • GrabPass:当前 Pass 前 抓取已有颜色缓冲,实现 折射、扭曲、简单玻璃 快,但 每 Grab 多一遍拷贝/带宽成本,复杂项目常改用 单 Pass 模糊 RT 等替代。
答题示例

镜子用专用相机渲染到 RT 再采样;GrabPass 适合抓屏做玻璃扰动,但性能要省着用。

参考文章
  • 34.高级纹理-渲染纹理-渲染目标纹理是什么
  • 35.高级纹理-渲染纹理-镜面效果-原理和准备工作
  • 39.高级纹理-渲染纹理-玻璃效果-基础实现

5. frac 在滚动背景里解决了什么数值问题?

题目

和 Repeat Wrap 的异同?

深入解析
  • frac 显式把坐标 折叠到 [0,1)负数 也按小数部分拉回,适合 手写 UV 动画
  • 纹理 Wrap=Repeat 由采样器处理;Shader 里 frac 更可控且可与 速度、时间 直接组合。
答题示例

时间让 UV 一直加会超大,frac 折回 0~1 才能无缝滚。

参考文章
  • 47.动态效果-纹理动画-滚动的背景

6. 顶点动画又想合批时,课文里提了哪两种「把模型空间信息带进顶点」的办法?

题目

Shader 里怎么取?

深入解析
  • 顶点色:脚本里 mesh.colors 写入位置或辅助量,Shader **appdata_full.color**。
  • UV2/UV3:存两个分量时用 mesh.uv2 等,Shader texcoord1 等语义读取。
答题示例

把要的模型空间信息烘焙进顶点色或第二套 UV,合批后还能在顶点阶段读回来算动画。

参考文章
  • 52.动态效果-顶点动画-顶点动画的注意事项-批处理

7. 河流做了顶点起伏,为什么默认 Fallback 的阴影会「对不上」?

题目

要怎么改 ShadowCaster?

深入解析
  • 内置 ShadowCaster原始网格 算深度,与 主 Pass 位移后 表面不一致。
  • 需在 ShadowCaster Pass 的顶点阶段 重复同一套位移,再交给 TRANSFER_SHADOW_CASTER_NORMALOFFSET 等宏。
答题示例

Fallback 的阴影还在用没动的顶点;要自己写 ShadowCaster,顶点算法和主 Pass 一致。

参考文章
  • 53.动态效果-顶点动画-顶点动画的注意事项-阴影

8. 用深度纹理做「相机运动」运动模糊时,为什么要传上一帧的 worldToClip 矩阵?

题目

和 69 课那种全屏累积模糊各解决什么问题?

深入解析
  • 每像素用 深度 还原 世界坐标,再乘 上一帧视图投影矩阵历史屏幕位置当前 − 历史屏幕空间运动向量
  • 69帧缓冲累积 不依赖 几何重投影,但需 维护 RTAlpha 通道,适合 整体拖影 而非严格 每像素速度
答题示例

深度版要知道每个像素在上一帧投到哪,必须带旧 VP;累积版是混上一帧整图,思路不一样。

参考文章
  • 74.深度和法线纹理-效果实现-深度纹理实现运动模糊-基本原理
  • 75.深度和法线纹理-效果实现-深度纹理实现运动模糊-具体实现
  • 69.屏幕后期处理效果-效果实现-运动模糊-具体实现

9. 基于 _CameraDepthNormalsTexture 的边缘检测,相对只对 _MainTex 做 Sobel 有什么优势?

题目

Roberts 对角比的是什么?

深入解析
  • 深度突变法线不连续 对应 几何轮廓颜色 Sobel 易被 贴图明暗、光照 误检。
  • 课文用 对角采样 比较 深度差/法线差敏感度阈值,得到 几何边缘
答题示例

深度+法线抓的是模型形状边界,不容易被花纹骗;颜色边缘会把材质细节也当边。

参考文章
  • 79.深度和法线纹理-效果实现-深度法线纹理实现边缘检测-基本原理
  • 80.深度和法线纹理-效果实现-深度法线纹理实现边缘检测-具体实现


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

×

喜欢就点赞,疼爱就打赏