中EN
概念页

着色频率与材质系统

Shading Frequency & Material Systems

更新于 2026-10-15 约 14 分钟着色材质着色器变体性能MixMaterialPBRT

实现着色时要按"计算频率"划分工作:整个 draw call 不变的放到应用端作为 uniform,其余放在相应的可编程阶段。实践中大多数着色在像素级完成,顶点着色器负责变换和变形。材质系统把面向美术的材质与底层着色器组织起来,处理海量着色器变体的组合问题。

计算频率

频率例子位置
"只算一次"常量子表达式、依赖硬件配置或安装选项的量编译期、离线、安装或加载时
变化很慢依赖游戏内时间的光照因子可分摊到多帧
每帧合并观察和投影矩阵应用端
每模型 / 每 draw call与位置相关的光照参数、每个材质的参数应用端,uniform
draw call 内变化顶点(细分前)、面片(外壳)、细分后顶点(域)、图元(几何)、像素可编程着色器

按更新频率给 uniform 分组既提高应用效率,也减少常量更新、提升 GPU 性能。

为什么逐像素

  • 书中用三种顶点密度的模型对比:极密的龙模型两者差别很小;茶壶上逐顶点出现棱角状高光;两个三角形的平面上逐顶点明显错误。原因是高光等项在表面上非线性变化,而顶点着色器结果在三角形上是线性插值的。(旧文献分别称逐顶点、逐像素为 Gouraud 着色和 Phong 着色。)
  • 只把高光放像素、其余放顶点理论上省一点,但实践中重复计算和额外 varying 的开销往往抵消收益。
  • 越来越多着色计算用计算着色器实现(第 20 章)。

插值的细节

  • 单位法线线性插值后长度小于 1,像素着色器里要重新归一化;顶点间法线长度差异大(如顶点混合的副作用)会让插值方向偏向长的一侧,所以通常插值前后都归一化。
  • 指向特定位置的向量(视线、精确光源方向)通常不插值,而是插值表面位置后在像素着色器里相减得到。若必须插值这些向量,插值前不要归一化,否则方向错误。
  • 光照所用坐标系(世界空间、相机空间,偶尔模型空间)由整个渲染系统统一决定:光源极多时用世界空间避免变换光源位置;相机空间便于优化与视线相关的运算并可能提高精度。
  • 平面着色 (flat shading) 作为风格选择(如《Kentucky Route Zero》):把图元属性放在第一个顶点并关闭插值即可,通常在顶点着色器中实现。

实现示例要点

书中的 WebGL 2 示例:像素着色器循环遍历所有点光源(动态分支,简单场景可行,但不适合大量光源——见第 20 章);光源数组放在 uniform block 中并按 std140 布局(用 vec4 对齐);应用端在编译前把 MAXLIGHTS 替换为实际值;funlitf_{\text{unlit}} 和暖色在应用端算好作为 uniform 传入;clamp 到 [0,1](HLSL 的 saturate)在多数 GPU 上几乎免费。

材质系统

  • 着色器是 GPU 可编程阶段的程序,是底层 API 资源;材质是面向美术的表面外观封装。两者不是一一对应:同一材质在不同渲染情况下可能用不同着色器,一个着色器也可被多个材质共享。
  • 常见形式:材质模板(一类材质及其参数)+ 材质实例(模板 + 一组参数值);虚幻引擎支持多层级的模板继承。
  • 参数可在运行时(uniform)或编译时(替换源代码)解析;常见的编译期参数是开关某个材质特性的布尔量,可由美术勾选,或由系统自动设置(例如远处物体关闭效果不明显的特性)。
  • 材质参数可以经过复杂计算得到着色参数,例如地形材质根据高度和法线在高处的水平面上混入白色雪;随时间变化的霓虹灯。

需要组合的几类功能

  • 表面着色与几何处理(刚体变换、顶点混合、变形、细分、实例化、裁剪);
  • 表面着色与合成操作(discard、混合;在移动 GPU 上混合通常在像素着色器里完成);
  • 着色参数的计算与着色模型本身;
  • 可单独选择的材质特性;
  • 着色模型与光源求值(延迟渲染会改变这种组合结构)。 GPU 着色器不支持编译后链接代码片段,所以这些组合只能在源代码层面完成,主要靠 #include、#if、#define 等预处理和字符串操作。

着色器变体:运行时分支 vs 编译期变体

  • 旧硬件上动态分支不可用或极慢,所有变体(包括各类光源数量的组合)都在编译期处理。
  • 现代 GPU 对动态分支处理得很好,特别是在整个 draw call 内分支一致时;光源数量等现在多在运行时处理。但功能越多寄存器用量越大、占用率越低,所以编译期变体仍有价值。
  • 书中例子:三类光源中复杂的通用聚光灯不到 5%,可以编两个变体——"有至少一个通用聚光灯"和"没有",常用的后者寄存器占用更低、更快。
  • 规模:《Destiny: The Taken King》某些区域单帧使用超过 9000 个编译变体;Unity 有的着色器可能变体近 1000 亿,只编译实际用到的,并为此重新设计了编译系统。

材质系统的组织策略(通常混合使用)

策略做法
代码复用公共函数放共享文件,#include 引入
减法式超级着色器 (übershader),用预处理条件和动态分支去掉不用的部分
加法式功能定义为带输入输出的节点并组合,可用文本或可视化图编辑器(方便技术美术);虚幻的材质图只能影响着色模型输入的计算
模板式定义接口,不同实现可插入;如把着色参数计算与着色模型分开。虚幻的"材质域"(Surface、Light Function)、Unity 的 surface shader;延迟着色中 G-buffer 也扮演接口

其他职责

  • 跨平台:Destiny 用自定义着色语言方言加预处理层自动翻译到不同平台,虚幻和 Unity 有类似系统。
  • 性能:Destiny 和虚幻会自动把整个 draw call 中不变的计算移出着色器;Destiny 按更新频率(每帧、每光源、每物体)划分常量作用域以降低 API 开销。

GEA 的视角:平面、Gouraud 与逐像素;着色器变体膨胀(卷 II 12.5.1、12.5.12)

  • GEA 把着色频率讲成一段历史:最早的引擎每个三角形只做一次简化光照(忽略高光、通常只有一个光源),即平面着色,现在偶尔用来刻意营造"计算机渲染"的外观;之后在顶点上算光照、在三角形内插值顶点颜色,即 Gouraud 着色,网格密时效果尚可,三角形大时线性插值的缺陷非常明显;今天的引擎多在像素着色器里逐像素应用着色方程(卷 II 12.5.1,见 着色方程与经典光照模型)。这与本页「计算频率」「为什么逐像素」一致;本页 RTR4 的平面着色例子是作为风格选择的《Kentucky Route Zero》。
  • 前向渲染处理复杂多光源场景时,会出现大量不同版本的顶点和像素着色器:处理不同光源数目、不同光源类型、不同蒙皮权重数目等。GEA 把避免这种着色器变体膨胀列为延迟渲染的好处之一(卷 II 12.5.12、12.5.12.1,见 延迟着色)。本页「着色器变体:运行时分支 vs 编译期变体」从材质系统角度讨论同一问题。
  • 材质库:很多游戏只需少量标准材质(木、石、金属、塑料、布、皮肤……),网格以松耦合方式引用材质库中的材质,避免在每个网格里重复材质数据(卷 II 12.9.2,见 渲染开发与 GPU 调试工具)。

PBRT 的视角:纹理与材质分离的材质接口(PBRT 4e 10.5)

  • 分工:纹理描述标量或光谱在表面上的空间变化;材质在交点处求值纹理,得到参数,构造对应的 BSDF。两者解耦,可以任意组合(与 Cook 1984 的 shade tree 相似;OSL 也是"着色器返回材质描述而非颜色"的模式)。
  • 静态类型的 GetBxDF():每种材质声明自己的 BxDF 类型(如 DiffuseMaterial 返回 DiffuseBxDF),通用代码可以在栈上为具体 BxDF 分配空间,并按类型分组在 GPU 上向量化执行(第 15 章)。代价:一种材质不能根据参数返回不同类型的 BxDF。Material::GetBSDF() 通过 TaggedPointer::Dispatch 与泛型 lambda 把具体类型还原出来,用 ScratchBuffer 分配 BxDF 内存。
  • TextureEvaluator:材质用 texEval(tex, ctx) 而不是 tex.Evaluate(ctx),让波前渲染器能把"纹理轻"与"纹理重"的材质分开处理;CPU 积分器用的 UniversalTextureEvaluator 被编译器优化为零开销。
  • MixMaterial:随机按 amount 选一个子材质(材质版的随机 alpha 测试,见 透明渲染与 Alpha 混合),所以 50/50 混合不是两个 BSDF 的平均。低采样率下有噪声(书中图 10.20:1 spp 明显,128 spp 无碍,通常几十个样本足够),好处是采样和求值只针对一个 BSDF。它需要在 SurfaceInteraction::GetBSDF() 中特殊处理(循环解析嵌套的 Mix),作者承认这不是好的抽象设计。
  • 空材质表示两种介质之间不散射的界面,返回未设置的 BSDF。
  • 色散:DielectricMaterial 的折射率是光谱纹理;只要折射率不是 ConstantSpectrum 就调用 TerminateSecondary(),只沿第一个波长追踪(反射时本可以保留全部波长,但此时还不知道会采样哪个分量)。
  • 调试选项:可把所有材质替换为以 ρhd\rho_{hd} 为反射率的漫反射,用于排查问题。
  • BTF/SVBRDF 视角:BTF 是六维函数(空间 2 维 + 方向 4 维);pbrt 的"纹理 + 参数化 BSDF"相当于对 BTF 做了一种固定的因式分解。测量时 BTF 不预设因式分解,所以更适合采集;神经表示见 Rainer 等 2019/2020、NeuMIP(Kuznetsov 等 2021)。

分歧:粗糙度到 α\alpha 的映射

pbrt-v4 的 TrowbridgeReitzDistribution::RoughnessToAlpha() 是 α=r\alpha=\sqrt{r}(网页代码),材质的 remapRoughness 为真时使用;而 微表面理论 中 RTR4 记录的 Disney/Burley 约定是 α=r2\alpha=r^2,被大多数实时引擎采用。同一个"粗糙度 0.5"在 pbrt 中对应 α≈0.71\alpha\approx0.71,在 Disney 约定下是 α=0.25\alpha=0.25,跨渲染器迁移材质时必须换算(rDisney=rpbrt1/4r_{\text{Disney}}=r_{\text{pbrt}}^{1/4})。书中只说该映射"给出相当直观的控制",没有讨论与 Disney 约定的差异。

原书问题(第 10 章参考文献)

  • 第 10 章参考文献把 Rainer 等 2020 "Unified neural encoding of BTFs"(CGF 39(2))的第一作者写成 "Rainer, R."。同一作者在紧邻的 2019 条目中是 "Rainer, G."(Gilles Rainer),2020 论文的第一作者同样是 Gilles Rainer(Crossref,DOI 10.1111/cgf.13921,CGF 39(2), 167–178),应为 "Rainer, G."。

LearnOpenGL 的视角:Material / Light 结构体与光照贴图(Lighting / Materials · Lighting maps)

来源:Materials、Lighting maps;摘录 Lighting — Materials、Lighting — Lighting maps。

材质与灯光分量拆开

片元里用 GLSL struct Material { vec3 ambient, diffuse, specular; float shininess; } 与 struct Light { vec3 position, ambient, diffuse, specular; }。环境/漫反射材质色通常取物体色;镜面材质色控制高光色;灯光三分量可独立压暗环境、保持高光。Uniform 按 material.diffuse 这种「结构体名.成员」设置——结构体只是命名空间。

只把灯光当灰色强度时物体易过亮;把 light.ambient 降到约 0.2、diffuse 约 0.5、specular 保持 1 是教程默认手感。光色随时间 sin 变化可演示「能反射的颜色受入射光谱限制」(接 着色器中的颜色 的相乘模型)。

漫反射贴图与镜面贴图

整物体单一 vec3 材质不够:改用 sampler2D diffuse(及 specular)。环境项通常直接采样同一张漫反射贴图。sampler2D 是不透明类型,只能作为 uniform(不能当作普通函数参数随意实例化),構造含 sampler 的 struct 时尤需注意。

镜面贴图常用灰度:木纹区域近黑(无高光),金属边框近白。也可把彩色写进镜面贴图,但物理上高光色多由光源决定,观感往往不真实。练习中的 emission map 与 GEA「自发光纹理」相同:与光照条件无关的自发光(见 着色方程与经典光照模型)。

纹理单元:漫反射绑 GL_TEXTURE0、镜面绑 GL_TEXTURE1,并用 setInt("material.diffuse", 0) 等把 sampler 指到单元——细节见 着色器纹理采样、纹理映射管线。

UE5 的视角:图到 HLSL 与 Custom 的折叠代价

来源:Epic KB How the Unreal Engine Translates a Material Graph to HLSL;How the Unreal Engine Translates a Material Graph to HLSL、Custom Material Expressions。专页:材质图到 HLSL 的翻译、Custom 材质表达式、材质系统。

UE 材质图在 Apply 时由 FHLSLMaterialTranslator 填进 MaterialTemplate.ush,内置节点在翻译期做常量折叠(整次 draw call 恒定的运算收到 CPU / 均匀参数里)。这与本页「应用端 uniform vs 像素阶段」同一分层:能上提的别留给 GPU。Custom 节点把一段 HLSL 当黑盒插入,无法参与折叠,官方明确可能比等价节点图更贵——只在内置节点做不到时使用。静态参数与 Usage 标记则在编译期制造着色器排列,对应上文「变体膨胀」在 UE 中的具体旋钮。

  1. realtime_rendering_4ed.pdf 第 5 章 Shading Basics
  2. Game_Engine_Architecture_4th_Edtion_Volume_2_-_Jason_Gregory.pdf
  3. Jason Gregory《Game Engine Architecture》第 4 版(2026)卷 II 第 12 章 12.5.1、12.5.12、12.9.2 节
  4. PBRT 4e source excerpt: chapter 10 (Textures and Materials)
  5. Pharr, Jakob, Humphreys《Physically Based Rendering: From Theory to Implementation》第 4 版(MIT Press, 2023)第 10 章 10.1–10.5 节,pbr-book.org/4ed/Textures_and_Materials
  6. Lighting — Materials
  7. Lighting — Lighting maps
  8. learnopengl.com/Lighting/Materials
  9. learnopengl.com/Lighting/Lighting-maps
  10. How the Unreal Engine Translates a Material Graph to HLSL Epic KB
  11. Custom Material Expressions UE 5.8

相关页面

图形学着色模型与光源Shading Models & Light Sources着色模型描述物体颜色如何随表面朝向、观察方向和光照变化。光源通过两个参数与着色模型交互:指向光源的方向 \mathbf{l} 和光的颜色 \mathbf{c}{\text{light}}。方向光、点光源、聚光灯的区别就在于这两个量如何随场景变化;距离衰减和方向衰减函数的选择同时影响画面与性能。图形学顶点着色器Vertex Shader顶点着色器是程序员直接控制的第一个管线阶段,逐个处理输入顶点,至少要输出顶点在齐次裁剪空间里的位置。它不能创建或删除顶点,顶点之间也不能传递结果,正因如此可以大规模并行。图形学像素着色器Pixel Shader像素着色器(OpenGL 里叫片元着色器)对光栅化产生的每个片元运行,用插值后的顶点数据算出颜色,也可以输出不透明度、修改深度或直接丢弃片元。多渲染目标让它一次输出多组数据,催生了延迟着色。它通常只能写自己所在的像素,但能通过 2×2 quad 拿到相邻像素的导数。图形学可编程着色器与 GPU 管线Programmable Shaders & GPU PipelineGPU 把第 2 章的概念管线实现为一组硬件阶段,分为完全可编程、可配置但不可编程、完全固定三类。现代 GPU 采用统一着色器架构:各类着色器共用同一套编程模型和核心,由 GPU 自行分配负载。着色器用 HLSL、GLSL 等类 C 语言编写,输入分为 uniform 和 varying 两类。图形学网格整合与顶点法线Mesh Consolidation & Vertex Normals整合 (consolidation) 指把零散多边形合并成共享顶点的网格、让所有面朝向一致、判断是否为实体,并生成平滑的顶点法线与折痕边。这些步骤是背面剔除、折痕检测、阴影体、碰撞检测、3D 打印等的前提。图形学绘制调用与状态切换优化Draw Calls & State Change Optimization调用图形 API 的开销主要在 CPU 端的驱动里:状态切换昂贵,每次绘制调用有固定开销,大量小批次会让 GPU"饿死"。对策是:合理放置缓冲;按状态代价排序分组;共享纹理和着色器;合并网格和实例化以减少调用;并用一个整数排序键同时兼顾状态分组与前后排序。图形学分块着色Tiled Shading分块着色把屏幕划分为瓦片(如 32×32 像素),先为每个瓦片建立可能影响它的光源列表,着色时每个像素在一次着色器调用中只遍历所在瓦片的列表。它可用于延迟着色(tiled deferred),也可用于前向着色(tiled forward,又称 Forward+)。配合 z-prepass 得到的瓦片深度范围可进一步剔除光源,但深度不连续会削弱效果,于是出现 HalfZ、2.5D 剔除等改进。图形学对象空间与纹理空间着色Object- & Texture-Space Shading对象空间/纹理空间着色把着色计算的频率与几何采样(光栅化)的频率解耦:在物体的参数化(纹理图表)上计算着色并存入纹理,最终光栅化时只查表。思想源自 Pixar 的 Reyes 离线渲染器的微多边形着色。它能消除 quad 过度着色、按屏幕覆盖率控制着色量、在帧间复用着色结果,甚至让着色以低于光栅化的帧率异步运行;代价是物体必须有唯一的 uv 图表、先着色后遮挡带来浪费等。图形学PN 三角形与 Phong 细分PN Triangles & Phong Tessellation两种只用每个三角形自身的顶点位置和法线、把平面三角形替换成曲面三角形的方法,不需要邻接信息,适合硬件细分即时生成。PN 三角形构造三次贝塞尔三角形并二次插值法线;Phong 细分是"几何版 Phong 着色",生成二次曲面,更快更简单。两者都能明显改善低模的轮廓和着色。游戏开发 · UE5材质系统Material SystemUE 材质 (Material) 是用节点图描述「表面如何与光交互」的着色器生成器。它有三个正交维度:着色模型 (Shading Model) 决定光照模型(Default Lit、次表面、清漆、毛发等),混合模式 (Blend Mode) 决定与背景的合成公式(Opaque/Masked/Translucent…),参数 + 材质实例 (Material Instance) 让美术在不重编译着色器的前提下做变体。PBR 输入(Base Color/Roughness/Metallic/Specular)直接对应 RTR4 的金属度-粗糙度工作流;最大的工程坑是静态参数导致的着色器排列爆炸。图形学着色方程与经典光照模型Shading Equation & Classic Lighting ModelsGEA 把渲染方程在两条简化假设下化成的求和式称为着色方程:只有理想化的点光源和方向光,间接光只用一个环境光项粗略近似。配上 Lambert、Phong、Blinn-Phong 这类简单 BRDF,它给不出照片级真实感,但能产生可信的画面,是早期游戏的全部光照,也是理解 BRDF 用法的最好入门(GEA4 卷 II 12.4、12.5.1)。图形学渲染开发与 GPU 调试工具Rendering Development & GPU Debugging Tools渲染引擎的运行时部分只是拼图的一块,还需要一批离线工具来制作内容、优化性能、诊断问题。GEA 介绍了四类:数字内容创作 (DCC) 工具、材质编辑器、资产调理管线,以及 PIX、Razor GPU 这样的 GPU 帧捕获、检查和回放工具(GEA4 卷 II 12.9)。图形学延迟着色Deferred Shading延迟着色先用一遍几何 pass 把所有可见表面的材质参数写入多张渲染目标(G-buffer),再以后处理方式对这些存储值逐光源计算光照,从而把"几何可见性"与"光照"彻底解耦,并把材质与光源分离。它避免了着色的过度绘制、简化了着色器管理、天然适合贴花,但 G-buffer 占用显存和带宽,且难以直接支持透明和 MSAA。本页也覆盖前向渲染下限制光源影响范围的基础手段与"光照预通道 (light prepass)"类混合方案。图形学微表面理论Microfacet Theory微表面理论把小于像素的微几何建模为大量平坦微面元,用法线分布函数 (NDF) D(\mathbf{m}) 描述其朝向统计,用遮蔽函数 G1 和遮蔽-阴影函数 G2 描述可见性,并据此推导宏观 BRDF。镜面反射时每个微面元是完美菲涅耳镜,积分退化为在半向量处求值,得到 FG2D/(4|\mathbf{n}\cdot\mathbf{l}||\mathbf{n}\cdot\mathbf{v}|)。GGX 分布配合高度相关的 Smith G2 是当今影视和游戏最常用的组合;该框架忽略微面元间的多次反射,可用补偿项弥补。本页对应 RTR4 9.6–9.8 节。图形学透明渲染与 Alpha 混合Transparency & Alpha Blending本页讲最简单的"视角相关"透明:半透明物体只衰减其后方物体的颜色。z-buffer 每像素只存一个表面,所以透明物体通常要从后往前用 over 运算符混合;顺序无关透明 (OIT) 方法用多遍渲染、逐像素链表或近似公式避免排序,各有内存、性能或准确性代价。预乘 alpha 让过滤、混合和合成更正确高效。图形学着色器纹理采样Shader Texture SamplingGPU 把图像存成 sampler2D:一张浮点向量的「表」,坐标连续时硬件做插值。片元里用 texture2D(sampler, uv)(现代 GLSL 为 texture)按归一化 UV 取色。BoS 强调:通道是 [0,1] 连续域、可越界重复、可用噪声扭曲 UV——图像变成可拉伸的「数码软垫」,为后处理与光学玩具式特效铺路。图形学着色器中的颜色Color in Shaders在 GLSL 里颜色与空间坐标共用 vec2/vec3/vec4:可用 .rgba / .xyzw / .stpq 或下标访问,并 swizzle 任意重排。mix 在 [0,1] 上混合;HSB(HSV)比 RGB 更直观,还可映到极坐标色轮。游戏开发 · UE5材质图到 HLSL 的翻译Material Graph to HLSL Translation材质编辑器点 Apply 之后,引擎先把节点图翻译成 HLSL,再把 HLSL 编译成 GPU 字节码。翻译以 MaterialTemplate.ush 为模板、由 FHLSLMaterialTranslator 做文本替换与常量折叠;内置节点能被折叠,Custom 材质表达式 则不能。每个材质还会按特性/平台/用途生成多份着色器排列。游戏开发 · UE5Custom 材质表达式Custom Material ExpressionCustom 节点允许在材质图里直接写 HLSL:任意数量输入、指定输出类型,可选额外输出、define 与外部 .ush/.usf 包含路径。它是「内置节点做不到」时的逃生口;代价是无法常量折叠、输入仅函数局部可见、且必须是合法 HLSL。优先用内置节点与材质函数,只在必要时上 Custom。图形学片元着色器与 GLSL 基础Fragment Shaders & GLSL Basics片元着色器(fragment shader)是在每个像素(片元)上并行执行的小程序:输入位置等信息,输出颜色。GLSL 是 OpenGL / OpenGL ES / WebGL 共用的着色语言;本书用全屏 billboard 上的像素着色练习,建立程序化图形的基础心智模型。游戏美术PBR 贴图工作流PBR Texture WorkflowsPBR(Physically Based Rendering)贴图制作有两条主流工作流——金属度/粗糙度 (Metal/Roughness, MR) 与镜面/光泽度 (Specular/Glossiness, SG)。它们表达同一套物理数据,只是通道切分不同;Substance、UE、Filament 等以 MR 为主,SG 多用于兼容旧资产。本页从美术生产角度对照通道语义、引擎对应关系与取舍,不重复 BRDF/微表面公式(见图形学 基于物理的着色)。游戏美术Filament 材质模型参数Filament Material Model ParametersGoogle Filament 的 Lit(标准)模型按 Disney/UE/Substance 互通思路设计,用 baseColor、metallic、roughness、reflectance 等参数描述导体与电介质,并叠加 clearCoat、sheen、iridescence、transmission 等层。本页从美术/TA 角度说明含义、合理范围,以及与 UE / Substance 的对照;不做 BRDF 公式展开。

输入关键词开始搜索

滚轮或双指缩放 · 拖动平移 · 双击放大 / 还原