自由学习记录(214)

Kajiya--Kay 其實不是從某套更深的毛髮物理方程嚴格推導出來的。原論文自己就說得很清楚:diffuse 部分是把 Lambert shading 用到一根很小的圓柱上;specular 部分則是類似 Phong 的 ad hoc model。 ​编辑ACM Digital Library+1

整條歷史鏈大概是:

Lambert → Phong →「圓柱沒有唯一 normal」這個問題 → Kajiya--Kay → Marschner。

先看他們真正卡住的地方。

1989 年 Kajiya 和 Timothy Kay 做的論文叫《Rendering Fur with Three Dimensional Textures》。它首先是篇 fur / volumetric texel rendering 論文,不是一篇純粹研究 BRDF 的論文。他們想表示大量非常細的毛,而不是給每根毛建立大量 polygon。​编辑ACM Digital Library+1

於是問題來了:

普通 surface shader 有

N

因為你在算一塊表面的光照。

但一根極細的毛,他們近似成:

cylinder

你真正穩定知道的是毛髮的軸:

T

也就是 tangent。

而圓柱周圍不是一個 normal:

N

而是一整圈:

N(ϕ)

這一步就是 Kajiya--Kay 出現的真正原因。

後來 2003 Marschner 等人真的去測量實際人髮纖維的散射,發現 Kajiya--Kay 預測不了若干非常明顯的現象,所以才把 hair 當成具有折射、內部傳輸以及 cuticle 結構的 dielectric fiber,得到今天熟悉的:

R,TT,TRT

三類主要 scattering paths。​编辑Stanford Graphics+1

比較一下就很清楚:

so many lives ahead

→ 前面還有很多條生命/很多人的人生

so much life ahead

→ 未來還有大量生活、人生經歷

而這裡還多了一個 of

so much of life ahead

這會稍微偏向「人生這整個東西中,還有很大一部分在未來」。

overflow-visible! 复制代码
复制代码
Computer Graphics
└ Rendering
  ├ Geometry / Visibility
  │ ├ Rasterization
  │ ├ Ray intersection
  │ └ Occlusion
  │
  ├ Light Transport
  │ ├ Path Tracing
  │ ├ Photon Mapping
  │ ├ Bidirectional methods
  │ └ Multiple scattering algorithms
  │
  ├ Appearance / Scattering Models   ← 這裡
  │ ├ BRDF / BSDF
  │ ├ Microfacet models
  │ ├ Subsurface scattering
  │ ├ Participating media
  │ └ Hair / Fiber scattering       ← Kajiya--Kay
  │    ├ Kajiya--Kay
  │    ├ Marschner
  │    └ 現代 hair BCSDF / fiber models
  │
  ├ Sampling / Monte Carlo
  ├ Reconstruction / Denoising
  └ Display / Tone mapping

PBRT 第四版基本就是按這個分類放的:第 9 章叫 Reflection Models ,裡面先建立 BRDF、BTDF、BSDF,然后有 dielectric、conductor、measured BSDF,最后单独有 Scattering from Hair 。也就是说,现代教材会非常明确地把 hair equation 看成「局部散射模型」这一类,而不是光传输算法。

Kajiya--Kay 研究的不是:

「光从灯出发,经过整个场景,最后怎么到 camera?」

这个属于 light transport

它研究的是:

「假设一束光已经撞到这根 hair fiber 上了,那么它朝观察方向出去多少?」

也就是这个局部问题:

(Li​,ωi​,ωo​,hair properties)→Lo​

所以如果把 Rendering Equation 展开:

Lo​(x,ωo​)=Le​+∫Kajiya--Kay / Marschner 研究這裡f(x,ωi​,ωo​)​​Li​(x,ωi​)∣n⋅ωi​∣dωi​

Kajiya--Kay 主要在设计中间这个:

f

也就是:

「材料/物體對光的局部 response 是什麼?」

这就是 Appearance Modeling 的核心问题。

对于普通表面:

overflow-visible! 复制代码
复制代码
Appearance model
└ Surface scattering
  ├ Lambert
  ├ Phong
  ├ Blinn--Phong
  ├ Cook--Torrance
  ├ GGX
  └ Oren--Nayar

对于特殊结构:

overflow-visible! 复制代码
复制代码
Appearance model
└ Structured / volumetric scattering
  ├ Skin
  │ └ subsurface scattering
  │
  ├ Cloth
  │ └ fiber / sheen scattering
  │
  ├ Hair
  │ └ fiber scattering
  │    ├ Kajiya--Kay
  │    └ Marschner
  │
  ├ Smoke / cloud
  │ └ phase function
  │
  └ Thin film
     └ interference

所以你之前一直看的 Cook--Torrance,其实和 Kajiya--Kay 在同一个大层级

区别只是:

overflow-visible! 复制代码
复制代码
Reflectance / scattering model
├ Cook--Torrance
│ └ 普通粗糙表面的 microfacet scattering
│
└ Kajiya--Kay
  └ 细长 cylinder / hair fiber 的 anisotropic scattering

这也解释为什么你会感觉它们「都是一堆公式」。

它们本来就是同一批人在解决同一种抽象问题:

给定材料微观结构,建立一个函数,描述入射方向 → 出射方向的能量分布。

Marschner 2003 的论文名字甚至直接叫 Light Scattering from Human Hair Fibers 。他们明确说,当时图形学通常使用 Kajiya--Kay 的 phenomenological model,而他们通过实际测量 hair scattering,发现了 Kajiya--Kay 不能表达的多个 specular highlights 和绕 fiber axis 的散射变化,然后建立更物理的模型。​编辑Stanford Graphics+1

PBRT 目录 = 一个 physically based renderer 的"系统解剖图",而不是整个 rendering research community 的严格分类表。

Cook 和 Torrance 1981/1982 的论文标题本身就是 A Reflectance Model for Computer Graphics ;PBRT 也把它放在 Reflection Models / microfacet reflectance 这条历史线上。​编辑pbr-book.org+1

实际 production 更常见的是:

overflow-visible! 复制代码
复制代码
选择已有 D/F/G
        ↓
调整它们的参数化
        ↓
为性能做 approximation
        ↓
为了艺术控制增加 mapping / remap
        ↓
必要时补 energy compensation

而不是随手重新发明 D。

更重要的是:在 Cook--Torrance 外额外加一个概念,也完全不一定是 Research。

例如:

fr​=fdiffuse​+fmicrofacet​+fclearcoat​+fsheen​

你刚刚"加了新的 lobe"。

但 clearcoat、sheen 都已经是成熟概念。

这仍然可能只是:

material model composition / production shading

不是研究。

UE Substrate 甚至就是把这个想法工程化了:Material 可以由 BSDF blocks 组成,再进行 layering / mixing;Epic 把它描述成一种 modular、BSDF-based material authoring framework。​编辑Epic Games Developers+1

一个完全没有渲染背景的例子:

y=ax+b

这是一个模型族。你可以随便调:

a,b

但无论怎么调,都不可能得到:

y=x2

你当然可以说:

那我直接把代码改成 a*x*x+b 不就行了?

可以。

但此时问题不是"线性模型经过调整解决了抛物线",而是:

你已经不再使用线性模型。

这就是渲染里"现有 abstraction 无法解决"的准确含义。

光碟表面从宏观看是平面,但放大以后,它不是随机粗糙:

overflow-visible! 复制代码
复制代码
宏观 CD
    ↓
一条从内向外盘旋的螺旋数据轨道
    ↓
取一个非常小的局部区域
    ↓
螺旋几乎可以看成一排平行轨道

|||||||||||||||||||
|||||||||||||||||||
|||||||||||||||||||

CD 的典型 track pitch 大约是 1.6 μm,也就是相邻轨道只隔 1.6 微米,已经和可见光波长 0.4--0.7 μm 是同一个数量级。因此这排周期结构会直接成为反射式 diffraction grating。不同波长满足衍射条件的出射方向不同,所以白光被拆成不同颜色。Optica 的实际光学实验也直接把 CD 的轨道作为约 1.6 μm pitch 的 diffraction grating 使用。​编辑Optica Publishing Group+1

关键就在这里:

overflow-visible! 复制代码
复制代码
沿轨道方向 T
────────────────────

跨轨道方向 B
↑
│  ║ ║ ║ ║ ║ ║
│  ║ ║ ║ ║ ║ ║

这两个方向的表面结构完全不同。

沿着轨道走,表面变化相对小;垂直跨过轨道走,会不断碰到周期性的 pit/land/track 结构。

所以从传统 microfacet BRDF 的角度看,它天然就不是:

αx​=αy​

而更接近:

αT​=αB​

这正是 anisotropic roughness 的来源:微表面的 slope distribution 在两个切线方向上不同。PBRT 对 anisotropic microfacet distribution 的表达实际上就是给两个切线方向独立的 αx​,αy​。​编辑PBR Book+1

an-

└ not

isotropic

iso = same

tropic = direction / turning

比如两个方向:

sT​=∂T∂h​,sB​=∂B∂h​

如果统计结果是:

Var(sT​)≪Var(sB​)

它直白地说:

overflow-visible! 复制代码
复制代码
沿 T 方向走
高度变化很平缓
→ slope 分布窄

沿 B 方向走
上下起伏很厉害
→ slope 分布宽

这就是 anisotropic roughness 很底层的一种说法。NVIDIA 关于 anisotropic microfacet 的工作也会直接从 anisotropic scaling / slope distribution 的角度描述这种结构。​编辑NVIDIA+1

所以以后你看到:

  • microfacet normal:强调"小面的朝向"

  • microfacet slope:强调"小面相对于宏观表面的倾斜程度"

  • slope distribution:强调"所有这些倾斜程度的统计分布"

  • anisotropic slope distribution:强调"沿 T/B 两个方向,倾斜统计不同"

今天游戏开发里大家嘴里说:

"Cook-Torrance BRDF"

经常实际上指的是上面这个 specular microfacet BRDF

fs​=4N⋅LN⋅VDFG​

然后另外再放一个 diffuse:

fr​=fd​+fs​

例如最简单:

fd​=πρ​

于是整个材质的 fr​ 才是:

fr​=πρd​​+4(N⋅L)(N⋅V)DFG​​

所以如果你问:

"Cook--Torrance 是不是在构建那个 f?"

拉丝金属有一个方向:

T

你转动拉丝方向,高光也应该跟着旋转。

但是你的 isotropic Cook--Torrance 根本没有:

T

这个输入。

于是你把整个场景的 L,V 绕着 N 转一圈,只要它们与 N 的夹角不变,isotropic distribution 对这些方位没有区别。

也就是说它具有一个结构性的不变量:

f(RN​L,RN​V)=f(L,V)

其中 RN​ 表示绕法线 N 旋转。

这不是 roughness 调得不好。

是这个函数本身根本"不知道哪个方向叫拉丝方向"。

于是怎么办?

增加:

T,B

以及:

αx​,αy​

变成 anisotropic microfacet。

overflow-visible! 复制代码
复制代码
isotropic microfacet
├ N
└ α

        ↓ 扩展模型

anisotropic microfacet
├ N
├ T / B
├ αx
└ αy

注意这个案例非常重要:

Cook/microfacet 这个大 abstraction 并没有失败。

失败的是:

isotropic microfacet 这个更小的模型。

所以我们扩展到 anisotropic microfacet,仍然待在 microfacet theory 里面。PBRT 也正是这样组织的。​编辑PBR Book

Marschner 等人做的正是这种实验:他们实际测量单根人发在不同入射/出射方向下的 scattering,发现了 Kajiya--Kay 没有预测出的、视觉上显著的现象 ,包括 multiple specular highlights、out-of-plane scattering,以及随 fiber axis 周围旋转而变化的 scattering。​编辑Stanford Graphics+1

这就是一句非常标准的:

existing model cannot explain the observations。

不是:

"我不会调参数。"

而是:

∀θ,fold​(x;θ)=fmeasured​(x)

在整个测量域上不存在一组参数能够把那些现象同时复现出来。

Unity 现在 URP 依然明确把 transparent 放在 opaque 后的独立 transparent pass;它甚至提供 _CameraOpaqueTexture,定义就是在透明物体开始绘制之前保存 opaque scene 的结果。​编辑Unity Documentation+1

overflow-visible! 复制代码
复制代码
普通 Alpha Blend
└─ 运算具有顺序依赖
   └─ 必须知道谁在后、谁在前
      └─ 通常 back-to-front

这才是 Unity Transparent 3000 内通常 back-to-front 的根本原因。官方至今仍明确这样定义 Transparent queue。​编辑Unity Documentation+1


Unity 6 的官方文档到现在仍然提醒 transparent object 会出现 sorting 问题,尤其是相交、互相包围、尺寸差异大的透明 mesh;这就是同一个问题一直延续到今天的直接证据。​编辑Unity Documentation

https://graphics.stanford.edu/papers/hair/hair-sg03final.pdf

透明物体有 depth,也通常做 ZTest;但普通 alpha-blended transparent 的颜色 Pass 通常 ZWrite Off

Unity 2022.3 的 Transparent shader 体系就是这种常见设计。​编辑Unity Documentation+1

你可以把它拆成三个完全不同的动作:

overflow-visible! 复制代码
复制代码
Fragment 产生自己的 depth
        │
        ├─ ZTest:拿自己的 depth 和 Depth Buffer 比
        │
        └─ ZWrite:要不要把自己的 depth 覆盖进 Depth Buffer

假设这个 cube 是同一个 Mesh、同一个 submesh、同一个 material/pass,那么通常就是一次 Draw,12 个三角形都走同一套 VS/FS、ZTest、Cull、Blend 等状态。Unity 的 Mesh 本身保存 index data,Input Assembler 会根据 index buffer 里的 index 组合出一个个 triangle。​编辑Unity Documentation+1

你说"顺序是不是建模的时候定下来",可以沿着这个理解:

overflow-visible! 复制代码
复制代码
Blender / DCC
└─ 面、三角形存在某种排列
   ↓ Export
FBX
   ↓ Import
Unity Mesh
└─ Index Buffer
   ├─ triangle 0
   ├─ triangle 1
   ├─ triangle 2
   ├─ ...
   └─ triangle 11

所以确实存在一个三角形列表顺序,它最初可以来自建模/导出阶段。

但 Unity 导入时还可能重新排。当前 Unity 官方 Optimize Mesh 默认 Everything,明确允许 Unity 为 GPU 性能重新排列 polygons、vertices 和 indices。也就是说,你在 Blender 里创建面的顺序,并不保证最后还是 GPU index buffer 里的顺序。​编辑Unity Documentation

更关键的是,即使最终 index buffer 是:

overflow-visible! 复制代码
复制代码
triangle 0 = 前面
triangle 1 = 前面

triangle 2 = 右面
triangle 3 = 右面

triangle 4 = 后面
triangle 5 = 后面
...

也不要把它想成 GPU:

overflow-visible! 复制代码
复制代码
先完整画前面
→ 再完整画右面
→ 再完整画后面

一次 DrawIndexed 更接近:

overflow-visible! 复制代码
复制代码
Index Buffer
↓
12 triangles 被送进 graphics pipeline
↓
GPU 大规模并行处理
├─ 一批 vertices
├─ 一批 primitives
├─ rasterization
└─ 大量 fragment/pixel invocations

Unity 的 ZWrite On 本身严格来说是"允许当前 draw 更新当前绑定的 depth buffer",并不等于"创建一张 Depth Texture"。Unity 官方也明确区分了两者:Camera Depth Texture 有时直接来自原生 Z-buffer,有时由额外 pass 生成。​编辑Unity Documentation+1

UE 的层级基本是:

overflow-visible! 复制代码
复制代码
Unity
Material / Shader
└ Pass
  └ ZWrite On / Off
    └ Depth-Stencil Render State
      └ GPU Depth Buffer

UE 不是没有 ZWrite 这个底层能力,而是把它从普通 Material 作者手里收到了 Renderer/Pass 层。

UE 更像:

overflow-visible! 复制代码
复制代码
UE Material
├ Blend Mode = Opaque / Masked / Translucent
├ Disable Depth Test          ← 只有部分情况暴露
├ Pixel Depth Offset          ← 改写进去的 depth 值
└ Allow Custom Depth Writes   ← 注意:CustomDepth,不是 SceneDepth

Renderer
└ Mesh Pass
   ├ Depth Prepass
   ├ Base Pass
   └ Translucency Pass
      ↓
   MeshPassProcessor / PSO
      ↓
   FDepthStencilStateInitializerRHI
      └ bEnableDepthWrite
          ↓
       D3D12 Depth-Stencil State

也就是说,UE 不是没有 ZWrite 这个底层能力,而是把它从普通 Material 作者手里收到了 Renderer/Pass 层。

Epic 5.8 的 RHI API 其实直接证明了这一点。FDepthStencilStateInitializerRHI 里面就有:

overflow-visible! 复制代码

C++

复制代码
bool bEnableDepthWrite;
ECompareFunction DepthTest;

TStaticDepthStencilState 第一个模板参数也是:

overflow-visible! 复制代码

C++

复制代码
template<
    bool bEnableDepthWrite,
    ECompareFunction DepthTest,
    ...
>

Epic 的 5.8 Material Properties 文档里确实只有 Disable Depth TestAllow Custom Depth Writes 这种接口,而没有通用的 Enable/Disable Scene Depth Write。并且官方明确说明 Disable Depth Test 只对 translucent 有意义;Allow Custom Depth Writes 则只是让 translucent material 编译额外 shader,以写入 Custom Depth​编辑Epic Games Developers+1

所以千万不要把:

overflow-visible! 复制代码
复制代码
Allow Custom Depth Writes

理解成 UE 版本的:

overflow-visible! 复制代码
复制代码
ZWrite On

它们不是同一个 buffer:

overflow-visible! 复制代码
复制代码
Scene Depth
├ 主渲染管线使用
├ Early-Z / occlusion / deferred 等依赖它
└ SceneDepth 节点看到的是这一类场景深度信息

Custom Depth
├ 单独的一份可选 buffer
├ Primitive 可选择 Render CustomDepth Pass
└ Outline / stencil / 自定义效果常用

Unity 把很多固定功能渲染状态直接写在 ShaderLab Pass {} 里给你看;UE 的普通 Material Graph 则只是描述 material evaluation,真正这个 draw 的 DepthWrite = true/false 是 Renderer 在构造对应 Mesh Pass 的 PSO 时替你决定的。 UE 的 RHI 最终仍然明确存在 bEnableDepthWrite,所以到了 D3D12/RenderDoc 那层,你依然可以观察到真正的 Depth-Stencil State。​编辑Epic Games Developers

先看 Custom Depth 到底特殊在哪。

overflow-visible! 复制代码
复制代码
SceneDepth
└ 主渲染管线的"真实可见性深度"
   ├ Early Z / Depth Pass
   ├ Base Pass depth test
   ├ 遮挡关系
   ├ 后续很多屏幕空间效果
   └ Renderer 自己维护其一致性

CustomDepth
└ 额外的一张独立 Depth Texture
   ├ 只有 Render CustomDepth Pass = true 的 Primitive 才进去
   ├ 可以完全不影响 SceneDepth
   ├ 可以附带 Custom Stencil 0~255
   ├ 主要给 Post Process / masking / outline / 标记对象
   └ 可以整张 texture 按需创建甚至完全关闭

真的是一条独立 Mesh Pass

UE 5.8 的 EMeshPass::Type 里面同时存在:

overflow-visible! 复制代码

C++

复制代码
DepthPass
BasePass
...
CustomDepth
...
CSMShadowDepth
VSMShadowDepth

所以 CustomDepth 在 Renderer 眼里就是单独的一类 pass。​编辑Epic Games Developers

硬件/API 层实际上是:

overflow-visible! 复制代码
复制代码
GPU Raster Pipeline
└ Depth Buffer
   └ 可用,但不是强制

D3D12
├ 可以创建 DSV
├ 可以不创建/不绑定 DSV
├ DepthEnable = true / false
└ DepthWriteMask = ALL / ZERO
overflow-visible! 复制代码
复制代码
GPU硬件
└ 提供 Depth Test / Depth Write / Hi-Z 能力
   └ 不强制使用

Graphics API
└ 提供 Depth Resource / DSV / Depth State
   └ 不强制绑定

UE Renderer
└ 决定主场景需要 SceneDepth
   ├ 创建资源
   ├ 安排谁写
   ├ 安排什么时候写
   ├ 安排什么时候 read-only
   └ 后续 Pass 使用它

Material
└ 通常只是参与这个系统
   └ 并不拥有 SceneDepth 本身

所以我之前使用 Renderer 这个词,真正想强调的正是:SceneDepth 的"必需性"应该归因到 UE 的 renderer 算法体系,而不能再往下归因成 GPU 的强制规则。

Daniil Trifonov

dah-nee-EEL TREE-fuh-nof

→ 中文粗略:「達尼伊爾・特里弗諾夫」

Daniil 重音在最後:da-ni-ILTrifonov 是俄語姓氏,重音在第一節,俄語詞尾的 v 實際會清化,接近 f ,所以會聽起來像 TREE-fuh-nof 。他的英語訪談也可以直接聽本人名字所處的語音環境。​编辑YouTube+1

Liszt

→ 接近英文 list

→ 不是「利茲特」、也不是把 szt 全部分開念。

因為這是匈牙利姓氏,匈牙利語 sz 對應 /s/;整體就是 /list/,其中 i 是短音。​编辑Forvo+1

Mephisto Waltz

muh-FISS-toh waltz

→ 「麼-FISS-偷 沃爾茨」

Mephisto 的重音在 phis ;它是 Mephistopheles(梅菲斯特/魔鬼)的簡稱。這首曲子的題材正是 Faust 傳說中的 Mephisto。​编辑LA Phil

Unity Renderer / Render Pipeline └ 决定 ├ 这一时刻绑定哪张 Color Render Target ├ 这张 RT 是 Camera Color? ├ 中间纹理? ├ GBuffer? └ 哪个 Pass 在什么时候画进去 Unity ShaderLab Pass └ Blend SrcAlpha OneMinusSrcAlpha └ 声明: "这个 Draw 写当前绑定的 Render Target 时, 使用这种 Blend State"

梅菲斯特(Mephisto)通常是 Mephistopheles(梅菲斯托费勒斯) 的简称。它最核心的来源是"浮士德传说":一个人为了知识、力量、享乐等,与魔鬼进行交易;Mephistopheles 就是那个诱惑、帮助并试图毁掉浮士德的魔鬼角色。这个名字至少在 1587 年出版的德语《Faustbuch》中已经出现,后来马洛、尤其歌德的《浮士德》让它变成欧洲文化里极有辨识度的名字。​编辑Wikipedia+1

所以它和"撒旦"不完全是一回事。更准确的关系大概是:

撒旦 / Devil

└ 一个更大的宗教、神话概念

 └ Mephistopheles

  └ 浮士德故事体系中具体的魔鬼角色

   └ Mephisto

    └ Mephistopheles 的短称

一个很容易误解的地方是:Mephistopheles 并不是《圣经》里一个古老的魔王本名。 它实际上是浮士德文学传统中形成的角色名,后来才不断被文学、歌剧、音乐、游戏借用。名字本身的词源甚至没有完全确定;传统上常解释成类似"厌恶光明 / 不爱光明者",但现代研究对具体构词仍有争议。​编辑Wikipedia+1

这也解释了为什么会"见过很多次"。现在看到一个角色叫 Mephisto,通常不应该只理解成"名字听起来像恶魔",而是在调用一个相当具体的文化模板:

诱惑者 + 操纵者 + 魔鬼 + 聪明/讽刺 + 与某个人形成危险搭档关系。

而且这里的关联更直接。李斯特的《第一梅菲斯特圆舞曲》描绘的是诗人 Nikolaus Lenau 版本的《浮士德》故事:Mephistopheles 带 Faust 进入乡村酒馆,自己拿起小提琴,把舞会煽动到近乎疯狂的状态。 所以曲子里那种炫技、躁动、诱惑性很强的感觉,本来就是在"演"这个魔鬼。​编辑LA Phil+1

Unity 官方现在仍然把 Blend 定义为一个修改 GPU render state 的 ShaderLab command,而且可以放在 PassSubShader 中。​编辑Unity Documentation+1

这句话没有说:

overflow-visible! 复制代码
复制代码
我要混合 CameraColor

它只是在说:

overflow-visible! 复制代码
复制代码
无论当前这个 Draw 的 Color Render Target 是谁:

PS 输出 = Source
RT 已有值 = Destination

执行:
Source * SrcAlpha
+
Destination * (1-SrcAlpha)

Microsoft 对 D3D12 的定义也是这个结构:blend state 属于 graphics PSO,并由 Output Merger 对 pixel shader 输出和当前 render target 中已经存在的值进行组合。​编辑Microsoft Learn+1

例如现在 Unity Renderer 做:

overflow-visible! 复制代码
复制代码
Renderer
│
├ 创建 CameraColorTexture
│
├ 把 CameraColorTexture 绑定为当前 Render Target
│
└ Draw TransparentObject
      │
      ├ PS 输出
      │   float4(1,0,0,0.5)
      │
      └ ShaderLab:
          Blend SrcAlpha OneMinusSrcAlpha

讲扒和弦时,也会明确采用"一边播放音乐,一边弹钢琴核对"的办法,只是通常称为"核对""测试""排除",而不会叫"夹逼法"。​编辑好和弦 - NiceChord.com+1

属于"绝对音高搜索"。随着扒谱能力提高,人一般会逐渐从:

一个音一个音上下试

变成:

先找到一个基准音 → 听音程关系 → 一串音直接推出

Unity Renderer 做:

overflow-visible! 复制代码
复制代码
Renderer
│
├ 创建 CameraColorTexture
│
├ 把 CameraColorTexture 绑定为当前 Render Target
│
└ Draw TransparentObject
      │
      ├ PS 输出
      │   float4(1,0,0,0.5)
      │
      └ ShaderLab:
          Blend SrcAlpha OneMinusSrcAlpha

GPU 到 Output Merger 时:

overflow-visible! 复制代码
复制代码
Pixel Shader
     ↓
Source Color
     ↓
┌───────────────┐
│ Blend Hardware│ ← Blend State
└───────────────┘
     ↑
Destination Color
     ↑
当前绑定的 Color Render Target
     ↓
写回同一个 Render Target

这个 Destination 就是你现在在找的那个"缓冲区里的已有像素"。

Microsoft 的 D3D12 文档甚至直接把这两个量定义成:

overflow-visible! 复制代码
复制代码
Source
= Pixel Shader 输出

Destination
= Render Target 中已经存在的值

然后 Output Merger 做 read-modify-write。​编辑Microsoft Learn+1

现代 Unity renderer 可能有:

overflow-visible! 复制代码
复制代码
Color Render Targets
├ Camera Color
├ GBuffer0
├ GBuffer1
├ GBuffer2
├ Lighting accumulation target
├ Transparent intermediate target
├ Post-process intermediate
└ 最后 SwapChain Back Buffer

所以:

Transparent intermediate target

可以理解成:

overflow-visible! 复制代码
复制代码
Transparent
└─ intermediate target
   └─ 给 Transparent/Translucency 相关处理使用的
      中间 Render Target

也就是:

overflow-visible! 复制代码
复制代码
前面的渲染结果
       ↓
Transparent pass
       ↓
[ Transparent intermediate target ]   ← 暂时装这里
       ↓
之后的 composite / post process / ...
       ↓
最终画面

重点是这个 target 不是最终输出目标,只是管线走到这里的时候暂时产生的一份结果。

Post-process intermediate

就是:

overflow-visible! 复制代码
复制代码
Post-process
└─ intermediate [resource / texture / target]
   └─ 后处理流程中的中间结果

例如:

overflow-visible! 复制代码
复制代码
Scene Color
   ↓
Bloom
   ↓
[Intermediate A]
   ↓
Tonemap
   ↓
[Intermediate B]
   ↓
Color grading
   ↓
Final output

A、B 都可以叫 intermediate

UE 文档甚至专门说 User Scene Texture 可以作为 user-defined intermediate texture,一个 post-process material 写进去,后面的 post-process material 再读出来,用于 multi-pass blur 之类的效果。​编辑Epic Games Developers

所以这个词最好在脑中固定成:

intermediate = 中途产生、供下一阶段继续使用的

ROP 不是某一个单独"写颜色"的硬件,而是后端 Raster Operation 阶段的总称;CROP 和 ZROP 是它下面两类功能单元。 NVIDIA 2026 年 Nsight Graphics 的架构文档就是这样拆的。​编辑NVIDIA Docs+1

可以直接记成:

overflow-visible! 复制代码
复制代码
ROP = Raster Operation
└─ ZROP = Z / Depth Raster Operation
   ├─ Depth Test
   ├─ Stencil Test
   └─ Depth / Stencil Buffer Update

└─ CROP = Color Raster Operation
   ├─ Color Blending
   └─ Render Target Color Update

其中 Z 是传统 3D 图形学里的 Z-depth(深度) ,所以 ZROP 不是一种奇怪的新 ROP,而是"处理 Z/Depth + Stencil 的那半边 ROP"。NVIDIA 当前文档的原话定义就是:ZROP 负责 depth test、stencil test 和 depth/stencil buffer 更新;CROP 负责最终 color blend 和 render-target 更新。​编辑NVIDIA Docs

更重要的是,别把 ROP 理解成 Rasterizer。它们处于流水线的不同位置:

overflow-visible! 复制代码
复制代码
Triangle
↓
RASTER
├─ 把 triangle 转成 fragments / samples
└─ coverage 等

↓
PROP = Pre-ROP
├─ 安排 Pixel Shader / Depth Test / Color 的顺序
├─ Early-Z
└─ Late-Z

↓
ROP
├─ ZROP
│  └─ 最终 Depth / Stencil 操作
│
└─ CROP
   └─ 最终 Color / Blend / Render Target 写入

NVIDIA 明确把 RASTERPROPZROPCROP 列成四个不同的 Screen Pipe 硬件区域。RASTER 产生 fragment/sample;PROP 管理这些数据在 pixel shading、depth testing、color blending 之间的流动;最后进入 ZROP/CROP。​编辑NVIDIA Docs

例如一个 fragment 的 Pixel Shader 算出了:

overflow-visible! 复制代码
复制代码
Shader output color = (1.0, 0.2, 0.1, 0.5)
Depth = 0.37

后面大致可以发生:

overflow-visible! 复制代码
复制代码
ZROP
├─ 读当前 Depth Buffer,例如 0.52
├─ 比较 0.37 < 0.52
├─ PASS
└─ 必要时把 Depth 改成 0.37

CROP
├─ 取得 Pixel Shader 输出颜色
├─ 取得 Render Target 原来的颜色
├─ 按 Blend State 做 Src/Dst blending
└─ 更新 Render Target

所以如果现在是在 Nsight Graphics / Nsight Perf SDK 里看到:

overflow-visible! 复制代码
复制代码
crop__...
zrop__...
rop__...

这些名字基本就是性能计数器的硬件前缀。官方 Perf SDK 甚至直接定义:

overflow-visible! 复制代码
复制代码
ROP
└─ Raster Operation Stage,包含 ZROP 和 CROP

ZROP
└─ final depth-test / stencil-test / related buffer updates

CROP
└─ final color blend into bound render targets

​编辑NVIDIA Developer

这里还有一个特别容易混的东西:ZCULL ≠ ZROP

overflow-visible! 复制代码
复制代码
Depth 相关硬件
├─ ZCULL
│  └─ 更早、更粗粒度地淘汰肯定不可见的东西
│
└─ ZROP
   └─ 最终精确的 depth/stencil test + buffer update

NVIDIA Perf SDK 把 ZCULL 放在 RASTER 这一侧,并描述为 coarse-resolution depth testing;而 ZROP 是 ROP 后端的最终 depth/stencil 操作。​编辑NVIDIA Developer

早期 NVIDIA GPU Gems 也是把 framebuffer 后端称作 raster operations (ROP) ,包括 depth/stencil 读写与比较、color 读写以及 blending。​编辑NVIDIA Developer

因此三个词最短的语义就是:

ROP = 最终像素状态处理这一整个后端。
ZROP = 决定这个 sample 在深度/模板上能不能留下以及更新 Z/S。
CROP = 决定留下来的颜色怎样和 Render Target 合并并写进去。

https://fyitester.com/zh-CN/textile-color-theory-and-color-difference-evaluation/

你看到的软件表达是:

overflow-visible! 复制代码
复制代码
Result =
Src × SrcFactor
+
Dst × DstFactor

但硬件真正面对的是:

overflow-visible! 复制代码
复制代码
Pixel Shader
    ↓
Src

当前 Render Target
    ↓
找到同一 pixel/sample 的旧值
    ↓
Dst
    ↓
格式解释 / sample / channel mask
    ↓
Blend
    ↓
写回 Render Target

也就是:

overflow-visible! 复制代码
复制代码
READ
 ↓
MODIFY
 ↓
WRITE

Microsoft 对 D3D12 graphics pipeline 就直接称 Output Merger 通常是针对 Render Target 和 Depth-Stencil View 的 read-modify-write 操作。​编辑Microsoft Learn

所以真正昂贵和特殊的是:

overflow-visible! 复制代码
复制代码
              普通 ALU
                 │
           算 Src * A ...
                 │
                 ▼
       ┌─────────────────┐
       │   CROP / ROP    │
       ├─────────────────┤
       │ 找 RT 旧值      │
       │ 保证访问顺序    │
       │ Blend           │
       │ Color mask      │
       │ sample handling │
       │ 更新 RT         │
       │ 配合缓存/压缩   │
       └─────────────────┘
                 │
                 ▼
            Color Target

特别是 Dst 这东西决定了 Blend 不能简单理解成:

"丢给 CUDA Core / shader ALU 算一下不就好了?"

假设两个 fragments 都要写 (100,100)

overflow-visible! 复制代码
复制代码
Fragment A
读取 Dst = 0
计算 → 0.5

Fragment B
读取 Dst = ?

B 到底应该看到:

overflow-visible! 复制代码
复制代码
0

还是:

overflow-visible! 复制代码
复制代码
A 写进去后的 0.5

这已经不只是:

overflow-visible! 复制代码
复制代码
a * b + c

的问题了。

而是:

overflow-visible! 复制代码
复制代码
同一 Render Target address
        ↓
读取
        ↓
修改
        ↓
写回
        ↓
下一个 fragment 必须遵守 API 定义的顺序

NVIDIA 所以专门有 PROP 来协调这一段,官方明确说它负责维护 pixel shading、depth testing 和 color blending 的 API ordering。​编辑NVIDIA Docs

Depth 更加适合做固定硬件。

因为 Depth 最核心的事情甚至比 blend 更简单:

overflow-visible! 复制代码
复制代码
incomingDepth < storedDepth ?

可能就是一次比较。

但它发生的位置极其重要:

overflow-visible! 复制代码
复制代码
Rasterization
    ↓
Depth Test
 ┌──┴───────────────┐
失败                 通过
 ↓                    ↓
整个 fragment       Pixel Shader
可以直接杀掉

也就是说,硬件做一个极便宜的比较,可能帮你避免后面几十、几百条 shader instructions。

因此才会进一步发展成:

overflow-visible! 复制代码
复制代码
Depth hardware
├ Early-Z
├ Late-Z
├ ZROP
├ Stencil
├ Hierarchical-Z / Hi-Z
├ depth compression
└ fast clear

AMD 现在也仍然有针对 depth target 的专门 fast-clear 路径,并明确指出其 fast clear 可以远快于普通地把整个 target 填一遍。​编辑GPUOpen

所以这里可以得到一个非常稳定的硬件设计判断:

"算法简单"恰恰不会降低专用硬件的价值。只要它足够固定、出现频率极高、位于关键数据流节点,而且硬件化以后能避免通用 shader/core 和内存系统承担大量重复工作,它就是极好的固定功能硬件候选。

复杂但高频 └ RT Core └ BVH traversal / intersection 简单但极高频 ├ Texture filtering hardware ├ Rasterizer ├ ZROP └ CROP / Blend

Blend 尤其特殊的一点就是:它位于 Shader ALU 和最终 Render Target 存储之间。它真正值钱的不是那几个乘加,而是把"旧像素读取 → 顺序保证 → 混合 → 写回"这一整个极高吞吐的尾端路径硬件化。 ​编辑NVIDIA Docs+1

这也是为什么 ROP 这个词比单纯叫"Blend Unit"更能反映它实际在干什么:它本来就是 Raster Operations / 像素最终落到 render target 前后的那组操作

  • 如果所谓"外包"实际是劳务派遣:不能全做。

    └ 《劳动合同法》第66条规定,劳务派遣只是补充用工,只能用于"临时性、辅助性、替代性"岗位。​编辑SAMR+1

    └ 《劳务派遣暂行规定》第4条更直接:被派遣劳动者不得超过用工总量的10% ;这里的"用工总量"=直接签劳动合同的人数+派遣人数。​编辑Government of China+1

    └ 所以假设一个法律意义上的"用工单位"有100个人,正常情况下不能搞成"20正编+80劳务派遣"。

  • 如果是真正的业务外包:没有这个10%限制。

    └ 例如游戏公司把"30套角色模型交付""某地图美术资产制作"整个业务包给另一家公司。

    └ 发包公司管的是交付物、质量、期限、合同金额 ;至于承包公司的美术几点上班、谁做哪个模型、绩效怎么考,是承包公司自己管理。

    └ 这种情况下,那些人本来就不是你的"用工人数",因此不是拿"正编90%+外包10%"这么算。

可以把"做300个道具""做50套动作""场景植被资产生产"外包出去;但如果连"游戏到底长什么样、技术路线是什么、资产规范怎么定、今天需求为什么改、什么算验收合格"的人都外包掉了,甲方自己就逐渐失去定义任务和验收任务的能力

Screen Pipe ├ PROP │ └ 协调 Pixel Shader / Depth / Blend 的执行顺序 │ ├ ZROP │ ├ Depth Test │ ├ Stencil Test │ └ Depth/Stencil Buffer Update │ └ CROP ├ Color Blend └ Render Target Update

为什么 Depth 和 Stencil 放一起其实很好理解,因为 API 层本身也长期把它们组合成一个体系:

overflow-visible! 复制代码
复制代码
D3D12_DEPTH_STENCIL_DESC
├ DepthEnable
├ DepthWriteMask
├ DepthFunc
├ StencilEnable
├ FrontFace
└ BackFace

资源格式也经常就是:

overflow-visible! 复制代码
复制代码
D24_UNORM_S8_UINT

24 bit Depth
+
8 bit Stencil

所以一个 fragment 到这里时,很自然就是:

overflow-visible! 复制代码
复制代码
fragment/sample
   ↓
ZROP
├ depth compare
├ stencil compare
├ stencil op
├ depth update
└ stencil update

Screen 绝对不是"只有最终要显示到显示器时才工作" 。这里的 screen 是 screen-space / raster-space 的意思。

RASTER 接收 World Pipe 出来的 primitive,然后产生 pixels/fragments 和 coverage samples,后面的 PROP、Pixel Shader、ROP 再处理这些东西。​编辑NVIDIA Docs

所以 Screen Pipe 真正强调的是:

overflow-visible! 复制代码
复制代码
进入它之前
└ 我处理的是 triangle / vertex / primitive
   └ "世界中的几何"

进入它之后
└ 我处理的是
   ├ 屏幕坐标 x,y
   ├ fragment
   ├ sample
   ├ coverage
   ├ depth
   └ render-target pixel

raster 是一个历史上更宽的词

Rasterizer 是:

overflow-visible! 复制代码
复制代码
primitive
↓
哪些 pixel/sample 被覆盖?
↓
产生 fragments / coverage

而传统所谓的 Raster Operations 是:

overflow-visible! 复制代码
复制代码
这些 rasterized fragments 已经产生了
↓
对 framebuffer 做最后操作
├ Depth
├ Stencil
├ Blend
└ Color write

所以:

overflow-visible! 复制代码
复制代码
Rasterizer
≠ Raster Operations

虽然两个名字都带 Raster

NVIDIA 很早以前的 GPU Gems 就已经把管线末端称为 raster operations (ROP),并明确说这里负责 depth/stencil read/write、depth/stencil comparisons、color read/write 和 alpha blending。​编辑NVIDIA Developer

因此这个名字的历史语义其实是:

overflow-visible! 复制代码
复制代码
Rasterization pipeline
├ rasterization
│  └ geometry → fragments
│
└ raster operations
   └ fragments → framebuffer

这两个概念从很早期的固定功能图形硬件时代就一起存在,于是 ROP 这个名字被留下来了。

现在 NVIDIA 又进一步把以前笼统叫 ROP 的东西拆成:

overflow-visible! 复制代码
复制代码
ZROP
└ Z / Stencil Raster Operations

CROP
└ Color Raster Operations

当前 NVIDIA 文档就是这样定义:ZROP 做 depth/stencil test 和 buffer update,CROP 做 final color blend 和 render-target update。​编辑NVIDIA Docs

所以你现在可以形成一个非常稳定的名字模型:

overflow-visible! 复制代码
复制代码
WORLD PIPE
处理:
triangle / vertex / primitive

        ↓ rasterization 是分界

SCREEN PIPE
处理:
fragment / sample / pixel

├ RASTER
│ └ primitive → fragments
│
├ PROP
│ └ 协调这些 fragments 后续怎么走
│
├ ZROP
│ └ fragments 对 depth/stencil raster buffer 做操作
│
└ CROP
  └ fragments 对 color raster buffer 做操作

传统多 Pass 下采样的瓶颈在于:纹理越小,GPU 耗时中 DrawCall 固定成本占比越高,到了 2×2 这种尺寸时,绝大部分时间都在等调度而不是算像素。SPD 用 compute shader + 原子计数器把所有层级打包在一次调度里完成,​消除了小尺寸纹理阶段的 GPU 浪费​。

SPD 在 UE 里最大的价值就是把 Bloom 的多 Pass 下采样压缩成单次 Compute Shader 调用,省 DrawCall、省时间,尤其在大分辨率纹理上效果显著。

核心算法和 UE 内部集成的 FidelityFX-SPD 是一致的。

RenderFormer是神经渲染领域的一个代表技术,原文提到它由微软提出,属于数据驱动的渲染方式。

原始數學 parameter space 和「人感受到的變化空間」不一定一致,所以要 remap。Disney 還把 normalized specular 控制映射到約 [0, 0.08] 的 normal-incidence specular reflectance,而不是要求 artist 直接操縱 IOR;目的也是把物理模型包成穩定、合理、容易操作的區域。​编辑Disney Animation Media


  • production = 產生、生成、製作某個輸出

  • heuristic = 啟發式/經驗性捷徑,也就是「不完整求解整個問題,而用一套通常夠用的簡化規則」

例如在心理語言學裡,production 指的是「語言產出」:人準備說一句話時,並不會把所有可能句法結構都完整計算一次,而可能依照一些簡單規則選擇一句容易生成的表達。1996 年 Gibson 等人的研究就把某些語料中的句法頻率解釋為由一套獨立於 comprehension system 的 production heuristic 所造成。​编辑PubMed+1

可以畫成:

語言系統

├ comprehension

│ └ 聽到/看到一句話 → 怎麼解析它

└ production

 └ 想表達一個意思 → 怎麼生成一句話

  └ production heuristic

   = 生成時採用的簡化選擇規則

heuristic:

「通常這樣做效果不錯,那先這樣選。」

而 production heuristic 就再多限定一層:

「在產生某個東西的階段,用什麼捷徑來決定怎麼產生。」

但在製造業,production heuristic 又真的可以是「生產啟發式」

例如工廠有 100 個訂單,要決定下一個做哪個。

可以完整最佳化:

算出全部排列 → 找全域最優解

也可以用:

急單先做

短工時先做

庫存低的先做

這些就是 production heuristics。

technical lineage 的核心其實在 lineage,不是 technical。

lineage 原本是「血統、世系、譜系」:

grandfather → father → son

一代從上一代而來

放到技術裡,就把「生物血統」這個關係借過來:

technology A

└ technology B developed from A

└ technology C inherited ideas from B

這條「誰從誰演變/繼承而來」的線,就是 technical lineage

主流 rendering technique 基本都应该存在可追溯的 technical lineage。

Panner 輸出一組會隨時間變化的 UV texture coordinates,用來造成 texture 在 U/V 方向持續移動的效果。它不是在移動 texture asset,也不是在移動 mesh。

可以理解成:

overflow-visible! 复制代码
复制代码
Panner
└ 操作的東西:UV coordinates
   ├ U / X direction → Speed X
   └ V / Y direction → Speed Y
      └ 隨 Time 持續增加 offset

概念上近似:

overflow-visible! 复制代码
复制代码
UV_out = UV_in + Time × Speed

然後 Texture Sample 用這組變化後的 UV 去採樣,所以畫面看起來像 texture 一直在「流」。Epic 也明確說它的 Coordinate 是 base UV texture coordinates,Time 決定當前的 panning position。​编辑Epic Games Developers+1

攝影:

The camera pans left.

不是:

相機整台往左搬。

通常表示:

overflow-visible! 复制代码
复制代码
camera stays roughly here
└ viewing direction sweeps horizontally
   ← ← ←

所以攝影裡的:

  • pan left

  • pan right

  • panning shot

都非常正常。

I panned the cube to the left.

如果意思是「在 Unreal 裡把 Cube 的 Location X 往左移」,pan 很怪。

應該是:

I moved the cube to the left.

I translated the cube along the X axis.

因為 pan 通常不是 object transform。

但:

I panned the camera to the left.

正常。

因為說的是 camera viewing motion。

還有一個很重要的 Unreal 語義差異:

The Panner moves the texture.

教程裡經常這樣說,作為視覺描述沒大問題;Epic 自己的文檔也會說讓 texture pan​编辑Epic Games Developers

但如果在講實現機制,這句就不夠精確。

更精確是:

The Panner offsets the UV coordinates over time.

因為實際關係是:

overflow-visible! 复制代码
复制代码
Texture
└ 沒有被搬動

Mesh
└ 沒有被搬動

Panner
└ UV coordinates 發生 offset
   └ Texture Sample 改變採樣位置
      └ 視覺上 texture 像在滑動

所以 Panner 這個名字其實是站在視覺效果層 命名的,不是站在底層數學 operation 命名的。Epic 的 Material Expression 文檔也說 Material Expression 本質上是對輸入執行小段 HLSL 並輸出結果;Panner 的輸出就是 UV coordinates。​编辑Epic Games Developers+1

讓 Scene View Extension 在 post-processing 開始時訂閱特定 post-processing pass event。可插的位置包括:

overflow-visible! 复制代码
复制代码
BeforeDOF
AfterDOF
TranslucencyAfterDOF
SSRInput
ReplacingTonemapper
MotionBlur
Tonemap
FXAA
SMAA
...

Epic 的 RDG 官方文档明确说,高层 rendering code 使用 RDG 记录 rendering commands,然后 RDG 根据资源关系建立 dependency、安排 transition、pass culling、parallel command recording 等;官方还直接建议 fullscreen pixel shader 使用 FPixelShaderUtils::AddFullscreenPass,compute shader 使用 FComputeShaderUtils::AddPass​编辑Epic Games Developers+2​编辑Epic Games Developers+2

所以你在 RenderDoc 里面最终真的应该能看到类似:

overflow-visible! 复制代码
复制代码
...
Tonemap?
MyPaperPass
    Draw(...)
...

或者 compute 版本:

overflow-visible! 复制代码
复制代码
MyPaperComputePass
    Dispatch(...)

而不是 Material Graph 里某个节点。

overflow-visible! 复制代码
复制代码
ScriptableRendererFeature
↓
AddRenderPasses()
↓
renderer.EnqueuePass(...)
↓
ScriptableRenderPass

Unity 官方甚至直接描述 ScriptableRendererFeature 是用来「inject render passes into the renderer」。​编辑Unity Documentation+1

所以概念上:

overflow-visible! 复制代码
复制代码
Unity URP

ScriptableRendererFeature
└─ ScriptableRenderPass


UE

SceneViewExtension / Renderer hook
└─ FRDGBuilder
   └─ RDG Pass

可以放在你脑子里的同一个位置。

但不要认为 API 一一对应。

UE 没有把整个 Deferred Renderer 做成一个跟 URP Add Renderer Feature 完全相同的 Inspector 插槽体系。

UE 更接近:

overflow-visible! 复制代码
复制代码
插件
↓
找到 Renderer 暴露的 hook
↓
拿到 FRDGBuilder
↓
插入 RDG Pass

你真正想找的 UV 原始入口只有:

overflow-visible! 复制代码
复制代码
Texture Coordinate

官方名稱是 TextureCoordinate expression,它直接輸出 mesh 的 UV,輸出類型就是 float2

在 UE 裡插入一個真正參與本幀 GPU 工作、讀寫渲染資源的 RDG Pass,當然可以算「改動渲染管線」。

如果在 3D / shader / UV 語境裡看到 Ellipsoidal UV,通常可以拆成:

ellipsoidal = 橢球狀的、以橢球為基準的

UV = 二維參數座標

所以它大致就是:

把 3D 表面按照「橢球」這個參考形狀,轉換成 UV 座標。

關鍵不是「UV 長得像橢圓」,而是 UV 的計算假設那個物體/投影域接近 ellipsoid

可以把層級看成:

overflow-visible! 复制代码
复制代码
UV mapping / parameterization
└ 需要把 3D surface → 2D coordinates (u,v)
   ├ Planar
   ├ Cylindrical
   ├ Spherical
   └ Ellipsoidal
      └ 把 reference shape 從 sphere 換成 ellipsoid

ellipsoid 本身就是 sphere 經過不同方向縮放後的形狀,例如:

a2x2​+b2y2​+c2z2​=1

a=b=c,它退化成 sphere;當三個軸長不同,就是 ellipsoid。

"I like my toast done on one side" 就是:

「我喜歡吐司只烤一面。」

這裡最值得注意的是 done

done 在食物語境裡不是「完成了某件事」,而是:

cooked / heated / prepared 到某種程度

例如:

  • How do you like your steak done? = 牛排想要幾分熟?

  • well-done = 全熟

  • The potatoes aren't done yet. = 馬鈴薯還沒熟

所以:

my toast done on one side

my toast = 我的吐司

done = 烤好、烤到想要的程度

on one side = 只在其中一面

這句出自 Sting 的 Englishman in New York ;歌曲借 Quentin Crisp 這個英國人在紐約的形象來列舉一些帶有英國身份感、個人習慣色彩的特徵。值得修正一個常見說法:「英國人傳統上都只烤一面」並不可靠 ;英國人自己對此也大量表示這不是普遍習慣,所以更適合把它讀成具體人物的偏好,而不是英國人的標準吃法。​编辑Reddit+1

I like my eggs done this way.

不是「我喜歡我的蛋做某件事」,而是 「我喜歡蛋被做成這種狀態。」

假設一個完全平的三角形:

N=(0,0,1)

那下面全部都可以是合法的 tangent:

T=(1,0,0) T=(0,1,0)

甚至:

T=(cosθ,sinθ,0)

因為它們全部滿足:

T⋅N=0

也就是都躺在表面上。

所以你說:

「純頂點模型有一個 normal 已經是極限了,tangent 到底哪裡冒出來?」

這個疑惑其實非常合理。答案是:

單靠 Position + Normal,Tangent 根本算不出唯一答案。你還需要另一份資訊。通常這份資訊就是 UV。

Modesty, propriety can lead to notoriety

  • modesty = 謙遜、不張揚

  • propriety = 得體、合乎禮節/分寸

  • notoriety = 出名、聲名在外,但這個詞通常帶一點「因為某種異常或負面原因而出名」的色彩

所以這句故意製造矛盾:

謙遜、守分寸,竟然也可能讓人「出了名」。

因為如果周圍的人都不這樣,那麼「不張揚」本身反而變得顯眼。

因為如果周圍的人都不這樣,那麼「不張揚」本身反而變得顯眼。

You could end up as the only one

end up as = 最後變成/最後落到某種狀態。

所以:

最後可能只剩下這麼一個人還這樣。

這裡不是字面上的「全世界唯一一個」,而是誇張地說這些特質變得罕見。

Gentleness, sobriety are rare in this society

  • gentleness = 溫和、柔和、不粗暴

  • sobriety 這裡比較有意思。

sober 最直接是「沒有喝醉」,所以 sobriety 可以是「清醒、節制、不酗酒」。

Unity 現在的官方文件直接把 vertex tangent 定義成:

沿著表面 texture U 軸的方向。

Mesh.RecalculateTangents() 明確使用 vertex positions、normals、texture coordinates 來算 tangent。​编辑docs.unity3d.com+1

這一下就把整件事接起來了。

使它變成一個很漂亮的區分:

avoid ≠ flee

避開衝突可以是判斷;

run 描寫的則更像被恐懼奪走行動節奏。

而且後面馬上接:

Manners maketh man

整段一直在定義的 gentleman 並不是階級身份,而是人在壓力下仍維持某種行為形式

通常就是這樣:

B=N×T

也就是:

Bitangent = cross(Normal, Tangent)

但工程裡通常還要再乘一個「方向符號」:

B=(N×T)⋅s,s∈{−1,+1}

例如 glTF 的標準就是:

B = cross(N, T.xyz) * T.w

其中 T.w 存的是 tangent basis 的 handedness,用來處理 UV 鏡像等情況。​编辑Khronos Registry+1

所以你現在可以直接把 TBN 想成:

overflow-visible! 复制代码
复制代码
        N
        ↑
        |
        •────→ T

B = N × T

也就是說,真正需要「額外決定」的通常只有 T;一旦 N + T + handedness 已知,B 基本就被推導出來了,不需要 mesh 再額外存一整條 Bitangent。Direct3D/HLSL 本身雖然歷史上有 BINORMAL semantic,但 shader pipeline 並不要求你一定存它;這些 vertex attributes 本質上是你提供給 shader 的資料。​编辑Microsoft Learn

NVIDIA / AMD / Apple

└ 設計晶片

 └ TSMC / Samsung / Intel

  └ 真正蓋 Fab、生產晶片

   └ AMAT / ASML / Lam / TEL / KLA

    └ 賣製造晶片的機器

AMAT 官方把自己定位為 materials engineering(材料工程)設備公司,而且它的特點就是產品線非常廣。​编辑SEC

AMAT 到底做什麼?

一顆先進晶片不是「刻一下」就做出來,而是在 wafer 上反覆進行數百甚至上千個製程步驟。

AMAT 涵蓋很多步驟:

Applied Materials

└ Semiconductor Systems

 ├ 薄膜沉積 Deposition

 │ ├ PVD

 │ ├ CVD

 │ └ ALD 等

 ├ Etch 蝕刻

 ├ Ion Implantation 離子植入

 ├ CMP 化學機械研磨

 ├ Patterning 相關製程

 ├ Metrology 量測

 ├ Inspection / eBeam 檢測

 ├ Transistor / Interconnect 製程

 └ Advanced Packaging 先進封裝

└ Applied Global Services(AGS)

 ├ 維修

 ├ 零件

 ├ 設備升級

 └ Fab 自動化軟體

官方也明確說,它擁有半導體資本設備產業中「最全面的產品組合」之一。​编辑SEC

所以 AMAT 和 ASML 最大的差異非常重要:

ASML = 一個極端強大的核心領域:Lithography 光刻。

AMAT = 很多製程步驟都有設備。

例如台積電建一座先進 Fab,並不是「買 ASML 就完成了」。ASML 把 pattern 曝光到晶圓上之後,還需要沉積材料、蝕刻、填金屬、CMP、量測、檢查......這裡大量都是 AMAT、Lam、TEL、KLA 的生意。


AMAT 最大的競爭對手

不能簡單說「AMAT 對手就是 ASML」,因為半導體設備其實分很多市場。

最直接的大型競爭格局大致是:

公司 最主要強項 和 AMAT 的關係
Lam Research (LRCX) Etch、Deposition 最直接的大型競爭者之一
Tokyo Electron (TEL) Etch、Deposition、Coater/Developer、清洗等 非常直接,而且產品也很廣
KLA (KLAC) Inspection、Metrology、Process Control AMAT 檢測/量測業務的直接競爭者
ASM International ALD、Epitaxy、Deposition AMAT 沉積領域競爭者
ASML DUV/EUV Lithography 同一設備產業巨頭,但大部分產品不是正面重疊
中國 AMEC、NAURA 等 Etch、Deposition 等 中國市場日益重要的競爭者

尤其 Lam Research 可以記住。

Lam 最近一季做到 $6.72B revenue ,主要設備正是 deposition、etch 等,和 AMAT 高度重疊。​编辑Lam Research Newsroom

KLA 則不一樣:

AMAT

└ 「加工 wafer」是核心能力

KLA

└ 「看看你加工得對不對」是核心能力

 ├ defect inspection

 ├ metrology

 └ yield / process control

KLA FY2026 revenue 已達 $13.58B​编辑KLA Corporation

而 ASML 更特殊:

ASML

└ 光刻

 └ 特別是 EUV

因此 ASML 與 AMAT 都吃半導體資本支出,但不能把它們看成可互換的設備供應商。ASML 2025 年營收為 €32.7B​编辑ASML


應該是 revenue /ˈrev.ə.nuː/。它確實是經濟、會計裡非常核心的詞:營收、收入 。例如公司賣商品收到的總額,扣成本以前通常先叫 revenue。Merriam-Webster 也把它定義為某個來源產生的總收入。​编辑Merriam-Webster

這個詞其實很好記,因為它的詞源不是什麼抽象的「財務」概念,而是:

revenue

└ Old French revenue = 「回來、返回之物」

 └ revenir = come back

  ├ re- = back

  └ venir = come

所以最原始的空間模型其實是:

東西出去 → 某些東西「回來」 → return → 收回來的錢 → revenue

15 世紀早期英語裡就已經有「來自土地、財產的收入」這個意思。​编辑Etymology Online

因此它跟 return 在概念上真的很近,不是偶然。甚至 revenue 的歷史語義可以粗略理解成「回流進來的東西」。

至於「總讓人想到小徑」,很可能是聲音/字形把它和另一組詞混在一起了:

  • avenue /ˈæv.ə.njuː/:大道、林蔭道,也可以泛指途徑

  • revenue /ˈrev.ə.njuː/:營收

  • ravine /rəˈviːn/:山溝、峽谷

尤其 avenue ↔ revenue 幾乎只差前面的 /æv//rev/,尾部都是很醒目的 -venue / -ənuː/,所以很容易產生「道路、小徑」的聯想。

有個更有意思的地方:avenue 和 revenue 還真的有很遠的詞源親緣avenue 原先也是法語中「arrival / approach,來到、接近」的概念,而 revenue 是「come back」。也就是兩個詞底下都有某種 come / movement 的空間模型。現在一個凝固成「通往某處的道路」,另一個凝固成「流回來的錢」。

半導體設備五大類巨頭

ASML → 光刻王

Applied Materials / AMAT綜合材料製程設備王,產品最廣

Lam Research → 蝕刻 + 沉積強者

Tokyo Electron → 日本綜合設備巨頭

KLA → 檢測、量測、良率控制王

AI demand

└ TSMC / Samsung / SK Hynix / Micron 等決定擴產

 └ WFE(Wafer Fab Equipment)資本支出增加

  └ AMAT / Lam / TEL / KLA 訂單增加

一旦 N + T + handedness 已知,B 基本就被推導出來了,不需要 mesh 再額外存一整條 Bitangent。

渲染方程是1986年由James Kajiya提出的计算机图形学核心积分方程,是所有全局照明方法(光线跟踪、路径跟踪等)的理论基础,系统描述了光线在场景中的分布与传输规律,所有主流光照模型如Lambert、Phong、Blinn-Phong乃至PBR都是它的特例

James T. Kajiya 在 1986 年發表了《The Rendering Equation》,提出著名的渲染方程。論文自己說,它提出一個積分方程,用來統一/generalize 當時多種已知 rendering algorithms。​编辑ACM Digital Library+1

三年後,1989 年,他又和 Timothy L. Kay 發表:

Kajiya & Kay --- "Rendering Fur with Three Dimensional Textures"

這才是我們前面一直說的 Kajiya--Kay hair/fur shading model​编辑ACM Digital Library+1

所以時間線是:

overflow-visible! 复制代码
复制代码
James T. Kajiya

1986
└─ The Rendering Equation
   └─ 全局光傳輸的框架
      └─ ∫ incoming light × scattering ...

1989
└─ Kajiya + Timothy Kay
   └─ Rendering Fur with Three Dimensional Textures
      └─ Kajiya--Kay
         └─ 毛髮 / 纖維的 phenomenological shading model

記住的「Kajiya 是頭髮高光」其實是:

Kajiya--Kay

不是單獨:

Kajiya rendering equation

Rendering Equation 是外面的 transport framework。

Cook--Torrance、Lambert 這些主要是在回答裡面的:

f

怎麼定義。

而 Path Tracing 又是另一層。

它不是另一個 f

overflow-visible! 复制代码
复制代码
Rendering Equation
↓
有一個積分
↓
這積分通常沒辦法解析求完
↓
怎麼估計它?

Monte Carlo
↓
Path Tracing

所以更穩定的分類其實是:

overflow-visible! 复制代码
复制代码
光傳輸問題
└─ Rendering Equation
   │
   ├─ Material scattering
   │  └─ f
   │     ├─ Lambert
   │     ├─ Cook--Torrance
   │     ├─ Hair scattering model
   │     └─ ...
   │
   └─ 如何求這個積分
      ├─ Path Tracing
      ├─ Bidirectional Path Tracing
      ├─ Photon Mapping 類方法
      └─ 各種近似

https://www.cs.cmu.edu/afs/cs/academic/class/15462-s13/www/lec_slides/86kajiyaRenderingEquation.pdf

L=Le​+TL

可以展開成:

L=Le​+TLe​+T2Le​+T3Le​+⋯

Veach 後來把這個 operator 形式寫得非常漂亮:它就是 emitted light,加一次 bounce、兩次 bounce、三次 bounce......一直加下去。​编辑Computer Science at UCSD+1

突然間:

overflow-visible! 复制代码
复制代码
Le
└─ camera 直接看到 light

TLe
└─ 1 bounce

T²Le
└─ 2 bounce

T³Le
└─ 3 bounce

...

可以精確問一個 renderer:

overflow-visible! 复制代码
复制代码
Whitted Ray Tracing 到底漏了什麼?
Radiosity 到底假設了什麼?
Path Tracing 到底在估計什麼?
Bidirectional Path Tracing 為什麼也是同一答案?

第一次給 CG 一個統一的 target:不管你發明什麼 renderer,你都可以問它究竟如何近似同一個 light-transport solution。

Epic 5.8 官方定义得很直接:SkyAtmosphereLightDirection 根据 Light Index 获取 Atmospheric Directional Light 的方向,这个 index 必须对应 Directional Light 上的 Atmosphere Sun Light Index。UE 当前只支持两盏 atmosphere lights,通常是 Sun=0、Moon=1。​编辑Epic Games Developers+1

所以如果你的场景就是:

overflow-visible! 复制代码
复制代码
DirectionalLight_Sun
└─ Atmosphere Sun Light = true
└─ Atmosphere Sun Light Index = 0

Material 里:

overflow-visible! 复制代码
复制代码
SkyAtmosphereLightDirection
└─ Light Index = 0

那么根本不需要:

overflow-visible! 复制代码
复制代码
Event Tick
→ GetActorRotation
→ GetForwardVector
→ Set Vector Parameter Value
→ MPC

Renderer 自己已经知道这盏太阳灯的方向。你旋转太阳时,这个 Material Expression 跟着变。​编辑Epic Games Developers

但它有一个非常重要的限制:

它不是「任意 Directional Light Direction」节点。

它只能访问被登记进 Sky Atmosphere 系统的那两盏 atmospheric directional lights。

不算怪,但它確實很容易讓人第一次看到時誤以為「Actor 裡存了一個 Forward Vector」。

Epic 在 UE 5.7 對 Get Actor Forward Vector 的官方定義就是:

Get the forward (X) vector (length 1.0) from this Actor, in world space. ​编辑Epic Games Developers+1

這裡 Get 想表達的是「給我這個 Actor 當前的 Forward Vector」,而不是「Get 一個已經儲存在 Actor 裡的變數」。

所以真正值得區分的是這兩個名字:

Get Actor Forward Vector

└ Target = Actor

└ Rotation 已經隱含在裡面

Get Forward Vector

└ Input = Rotator

└ 明確表示「從這個 Rotation 算出 Forward」

如果讓我從純 API 語義來命名,GetActorForwardVector() 還算自然;如果叫 CalculateActorForwardVector(),反而會把一個非常便宜、非常基礎的 derived property 搞得像某種計算任務。

Get 沒告訴你這東西是 stored property 還是 derived property。 UE 大量 API 都接受這種模糊性;它的 Get 更接近「向這個物件詢問這個值」,而不是 C++ 意義上的「讀取某個字段」。

第二個節點不是再去「獲取一次 Rotation」,而是在做一次表示形式轉換

旋轉描述

具體的一根世界空間方向箭頭

例如 Actor:

overflow-visible! 复制代码
复制代码
Rotation
Pitch = 0
Yaw   = 90°
Roll  = 0

這已經足以知道 Actor 朝世界 +Y

但是這份資料本身仍然是:

overflow-visible! 复制代码
复制代码
(0°, 90°, 0°)

而不是:

overflow-visible! 复制代码
复制代码
Forward = (0, 1, 0)

Get Forward Vector 做的就是把前者變成後者。

如果腦中只留下「lobe = 瓣狀」,這個詞幾乎不可能自然進入渲染診斷語言。因為實際看 Render 時,畫面裡通常根本沒有一個東西長得像「瓣」。

在 BRDF 裡,更有操作性的定義是:

lobe = 某種散射機制,把反射能量集中到哪些方向、集中得多窄、多高。

BRDF 本身是「給定入射方向 ωi​,不同出射方向 ωo​ 上會有多少反射」的函數。固定一個入射方向,再把所有可能的出射方向畫成 3D 極座標圖:某些方向函數值很高,圖形就從球面上「鼓」出去。那個鼓出的區域才被形象地叫做 lobe​编辑PBR Book

所以:

overflow-visible! 复制代码
复制代码
BRDF
└─ directional distribution
   └─ 某一群方向得到比較多能量
      └─ 在函數圖上形成一個凸起
         └─ lobe

「瓣」只是它在函數圖上的外形,不是材質表面真的有一瓣東西。

畫面裡直接看到的是:

overflow-visible! 复制代码
复制代码
lobe                          畫面結果
窄 / concentrated        →    小而銳利的 highlight
寬 / broad               →    大而模糊的 highlight
偏向某個方向             →    highlight 位置改變
沿一個軸被拉長           →    anisotropic 拉絲高光
存在第二個較寬 lobe      →    主高光外又有一層柔和 sheen / haze

PBRT 直接用「concentrated」描述這件事:光滑表面的 BSDF 只在很窄的一組方向取得大值;表面越粗糙,這個區域就越不集中。​编辑PBR Book+1

看到:

「這個高光太寬了。」

更底層的診斷語言就是:

The specular lobe is too broad.

看到:

「這個金屬的反射集中得過頭,像鏡子。」

就是:

The specular lobe is too narrow / concentrated.

UE 5.8 的 Substrate 其實把這個詞用得非常具體。Substrate Slab BSDF 裡現在有:

overflow-visible! 复制代码
复制代码
Primary specular lobe
└─ Roughness

Secondary specular lobe
├─ Second Roughness
└─ Second Roughness Weight

Epic 官方明確說 Second Roughness 控制 secondary specular lobe 的 roughness,而 Second Roughness Weight 控制 primary / secondary specular lobe 的混合比例。​编辑Epic Games Developers

這正好可以拿來建立一個非常具體的診斷模型。假設材質高光變成:

overflow-visible! 复制代码
复制代码
       /\                ← narrow primary lobe
      /  \
 ____/    \____          ← broad secondary lobe

畫面裡可能表現成:

「中間有很銳利的反射,但外面還包著一大片柔和的反射。」

There seems to be a broad secondary specular lobe underneath the sharp primary lobe.

就比「高光看起來有點怪」精確很多。

PBRT 4th edition 现在仍然明确这样定义:BRDF 描述 surface reflection,BTDF 描述 transmission,BSDF 是把两者统一起来的上位抽象。​编辑PBR Book+1

所以未来更可能发生的是:

引擎和材质系统越来越倾向把"BSDF"当成主抽象层,但 BRDF 不会消失。

讨论的是「反射」这一部分,BRDF 仍然是正确而且有用的词。

第二種更重要,是:

BSDF 裡的一個 additive scattering component。

例如可以概念性寫:

fr​=fdiffuse​+fspecular​+fcoat​

於是有人會直接說:

  • diffuse lobe

  • specular lobe

  • coat lobe

此時 specular lobe 不只是「那個凸起的外觀」,還可能指產生那個方向分布的一整個 BRDF 項

這也是 Unreal Substrate 裡 Primary Specular Lobe / Second Specular Lobe 的語境。Epic 的 Substrate 設計允許兩個 specular response 疊加,例如一個較銳利、一個較粗糙,用來產生 sharp reflection + hazy reflection 的組合。​编辑Epic Games Developers+1

所以若說:

roughness makes the lobe wider

主要是在講函數的 angular support / concentration

而說:

this material has two specular lobes

通常已經不只是說「高光有兩坨」:

fspec​=w1​f1​(ωi​,ωo​)+w2​f2​(ωi​,ωo​)

是在說 BRDF 模型裡有兩個不同的散射成分。

這才是 lobe 比「高光散/集中」多出來的資訊。

還可以再往下一層。對 microfacet BRDF:

fr​=4(n⋅ωi​)(n⋅ωo​)F(ωi​,h)D(h)G(ωi​,ωo​)​

真正控制主要形狀的不是一個叫 lobe 的數學變量,而是 D(h)、F、G 等函數共同形成了一塊高值方向區域。

所以:

overflow-visible! 复制代码
复制代码
microfacet parameters
└─ NDF D(h)
   + Fresnel F
   + masking-shadowing G
      ↓
BRDF f(wi, wo)
      ↓
在 outgoing-direction domain 中形成高值結構
      ↓
人把這個結構叫 lobe

lobe結果的形態描述,不是造成結果的底層數學機制。

這就能解釋一個之前一直卡住的地方:為什麼從來沒「用過 lobe」。

因為實際使用的是:

overflow-visible! 复制代码
复制代码
Roughness
Normal
View Direction
Light Direction
NDF
Fresnel
Geometry term
Dot Product
...

它們共同決定 BRDF evaluate 出來的數值;最後那些數值在方向空間中的形狀,才被論文或文件稱為 lobe。Microfacet BRDF 本身就是從微表面法線分布等因素計算這種方向性反射,而不是呼叫一個叫 lobe 的 primitive。​编辑PBR Book

SM6 这次 shader compile 把错误报告出来了 。目前错误文本本身明确指向 CollectionParameter 参数解析,而不是某个 SM6-only feature。CollectionParameter 是 UE 5.8 正常支持的 Material Expression。​编辑Epic Games Developers+1

Epic 5.8 的 MPC 裡,每個真正的 collection parameter 都有 ParameterName 和唯一的 Id;這個 Id 就是用來讓 Material 在 rename 後仍能追蹤參數的。​编辑Epic Games Developers+1

你現在最值得注意的是:MPC_DirectionalLightDirectionWS 是 Collection 資產的名字,不代表這顆 node 已經選中了裡面的 Vector Parameter。

Epic 5.8 對 ShouldTickIfViewportsOnly() 的定義非常直接:它只是讓 Actor 在 TickType == LEVELTICK_ViewportsOnly 時也能 Tick。它並不負責讓 Viewport 自己開始持續刷新。​编辑Epic Games Developers+1

蓝图里的 Event Tick 并不是 C++ Tick() 的真正 override。它实际对应另一个事件------ReceiveTick()

调用链大致是:

复制代码
FActorTickFunction
→ C++ Actor::TickActor()
→ 虚函数 Tick()
→ UE 再决定是否派发蓝图 ReceiveTick()
→ 蓝图 Event Tick

因此两者不完全等价:

  • 父类 C++ Tick():Actor 已进入视口 Tick 调用链后,直接执行。
  • 子蓝图 Event Tick:还需要 UE 额外派发 ReceiveTick(),会经过蓝图事件和 World 状态相关判断。
  • ShouldTickIfViewportsOnly() 只是允许 Actor 进入第一层 Tick,并不保证蓝图事件一定在编辑器 World 中按运行时方式派发。
  • Play 时 World 已完成初始化和 BeginPlay,所以蓝图 Event Tick 正常执行。
相关推荐
祈禾7 小时前
计算机组成原理之编译、汇编、链接、解释
开发语言·汇编·笔记·学习
知识分享小能手7 小时前
线性代数学习教程,从入门到精通,矩阵的初等变换与线性方程组(6)
学习·线性代数·矩阵
我命由我123458 小时前
四流一致(合同流、业务流、资金流、发票流)
经验分享·学习·职场和发展·求职招聘·职场发展·产品经理·学习方法
知识分享小能手8 小时前
线性代数学习教程,从入门到精通,矩阵的初等变换与线性方程组(5)
学习·线性代数·矩阵
我的xiaodoujiao9 小时前
快速学习Python基础知识详细图文教程18--多线程
开发语言·python·学习·测试工具
AI Dog10 小时前
MathHub:数学建模学习社区,内置数学建模智能体Modulus
学习·数学建模·智能体·modulus·数学建模智能体
坐吃山猪10 小时前
WebFlux_学习Subscriber总结
java·开发语言·学习·webflux
GlueNa2SiO310 小时前
10-Docker生产环境部署与K8s入门
笔记·学习·docker·容器·kubernetes
茯苓gao10 小时前
无感FOC核心原理:没有编码器,电机如何获得转子电角度?
笔记·嵌入式硬件·学习