自由学习记录(211)

真正決定一次繪製範圍的不是 Slot 名稱,而是 Section。

如果兩個Section在Index Buffer中是兩段獨立區域:

overflow-visible! 复制代码
复制代码
Section 0
└ Index 0~2999

Section 1
└ Index 7000~9999

中間可能夾著別的Section,GPU不能用一次普通的連續DrawIndexed只畫這兩段,而跳過中間內容。

UE有合批,而且不只一種

1. Dynamic Instancing/Auto-Instancing

相同Static Mesh + 相同Material + 相同Shader與Render State + 兼容的參數綁定 └ 合成Instanced Draw

Epic把它稱為Dynamic Instancing:相同Mesh和Material的Static Mesh Draw可以被合併。

但它主要解決的是:

overflow-visible! 复制代码
复制代码
同一個石頭放了100次
└ 從100組Draw
   → 少量Instanced Draw

UE中對應Unity GPU Instancing的主要顯式工具是:

overflow-visible! 复制代码
复制代码
Instanced Static Mesh Component
└ ISM

Hierarchical Instanced Static Mesh Component
└ HISM

它們讓相同Static Mesh、相同材質的多個實例共享高效繪製路徑。Epic建議大量重複物件使用ISM;HISM還提供適合大量靜態實例的層級剔除結構。

Epic的Merge Actors文檔明確寫到:普通Merge的Draw Call數等於材質數;把所有材質烘焙合併後,會得到單一Section和單一Draw Call。​编辑Epic Games Developers

來自德文 über (讀作 /ˈyːbɐ/,注意德文原音有變母音 ü,英文借用時通常簡化成 /uː/),意思是「在...之上、超越、超級」------德文裡的介係詞/字首,表示「超過、凌駕」的意思。

uber- 這個字首在英文/科技用語裡的常見用法:

被借用當一個強調字首,意思接近「超級的、終極的、統合一切的」:

  • uber-cool(超級酷)
  • uber-successful(超級成功)
  • Übermensch(尼采哲學術語「超人」,德文原詞,über + Mensch人)

所以 "uber shader" 讀作 /ˈuːbər ˈʃeɪdər/,uber 這裡取的是德文「超級、統合」的比喻義,跟叫車 App Uber(那家公司名字其實也是借用同一個德文字根,取「無所不在、凌駕一切的交通服務」的意涵)是同一個詞源邏輯,只是用在完全不同的領域。

section 是「模型資料結構上原本就存在的分區」,不是「切割動作產生的結果」

"get section by static mesh""create mesh sections" ,這裡的 section 是指把靜態模型(static mesh)的資料,讀取/複製到程序化模型(procedural mesh)裡的技術步驟 ,跟後面「用刀切一半」的 slice 動作,是兩個完全不同階段的東西:

  • 先用 section 把模型從一種格式(static)轉換成另一種格式(procedural)------這是準備階段,還沒切
  • 再用 slice 真正把模型切成兩半------這才是實際切割動作

为什么不直接把顶点复制六次?

因为两个三角形共享顶点0和顶点2。Index Buffer让多个三角形可以复用Vertex Buffer中的同一个顶点。

Index Buffer让多个三角形可以复用Vertex Buffer中的同一个顶点。

Position Normal Tangent UV Vertex Color Bone Indices Bone Weights

GPU不是看到某个特殊的"分隔符",而是每个Draw Call告诉它:

从哪里开始读,以及读多少个Index。

WIREFRAME会绘制组成三角形的边线,而不填满内部。

一组 shader invocation 组成一个 wave/warp

同一个时钟周期执行完全不同的指令

同一个 SIMD/SIMT 执行单元里的每个 lane

硬件大致是:

overflow-visible! 复制代码
复制代码
一个 Instruction Fetch / Decode
└ 解码一次 MUL

一个 Scheduler / Issue
└ 发射一次 MUL

32 个数据 lane
├ lane 0:0.2 × 0.5
├ lane 1:0.7 × 0.5
├ lane 2:0.4 × 0.5
└ ...

关键是:

32 个 lane 拥有不同数据,但共享这一次"现在执行 MUL"的决定。

NVIDIA 的 CUDA 文档同样说明,一个 warp 每次执行一条公共指令;分歧后会执行所有被选择的路径,并关闭不属于当前路径的线程。​编辑Intel+1

为什么不给每个 lane 一套独立控制器

理论上完全可以。

可以把硬件改成:

overflow-visible! 复制代码
复制代码
lane 0
├ 独立取指
├ 独立解码
├ 独立调度
└ 独立 ALU

lane 1
├ 独立取指
├ 独立解码
├ 独立调度
└ 独立 ALU

......

这样每个 lane 确实可以在同一周期执行不同代码。

但此时它就不再是一组便宜的 GPU SIMD lane,而接近于:

32 个小型、相互独立的处理器。

你需要复制或复杂化很多东西:

overflow-visible! 复制代码
复制代码
每线程完全独立执行
├ 更多取指单元
├ 更多指令解码器
├ 更多调度逻辑
├ 更多程序计数器读取通路
├ 更复杂的寄存器寻址
├ 更复杂的功能单元路由
├ 更复杂的缓存访问仲裁
└ 更高的功耗和芯片面积

特别是,如果线程 A 要乘法、线程 B 要除法、线程 C 要纹理采样、线程 D 要 transcendental function,那么硬件要么:

overflow-visible! 复制代码
复制代码
方案一
└ 给每个 lane 配置非常完整的功能单元

要么:

overflow-visible! 复制代码
复制代码
方案二
└ 建立一个庞大的动态路由网络
   └ 把每个 lane 分别送到不同功能单元

两种方案都很昂贵。

GPU 的价值恰恰来自:

overflow-visible! 复制代码
复制代码
减少控制硬件
└ 把晶体管面积用于更多 ALU、寄存器和带宽
   └ 在大量相似任务上获得极高吞吐量

所以它不是物理上做不到,而是:

一旦为每个 lane 提供真正完整的独立执行能力,GPU 会逐渐失去 GPU 的面积效率和功耗效率。

更小的 Wave

更小的 wave 会降低分支发散的影响。

例如:

overflow-visible! 复制代码
复制代码
Wave64
└ 64 个线程必须尽量保持一致

Wave32
└ 只有 32 个线程需要保持一致

如果一块屏幕同时出现皮肤和衣服,较小的 wave 更容易落在单一材质区域中。

但 wave 越小也不是无条件更好:

overflow-visible! 复制代码
复制代码
更小的 wave
├ 发散粒度更细
└ 但调度与状态管理数量增加

所以它仍然是吞吐率与灵活性之间的折中。

NVIDIA 将其描述为在 GPU 上动态重新排序线程,以改善执行和数据访问的一致性;DirectX Shader Model 6.9 也已经定义相关接口。不过目前 DirectX 的 SER 机制主要围绕 ray-generation 和 ray-tracing workload,而且规范允许某些设备实现接口但不真正执行重排序。​编辑NVIDIA Developer+2​编辑Microsoft GitHub+2

每个 lane 大致拥有:

overflow-visible! 复制代码
复制代码
lane
├ 自己的数据
├ 自己对应的寄存器元素
├ 自己的 active / inactive 状态
└ 自己的计算结果

但是它没有一整套独立的:

overflow-visible! 复制代码
复制代码
├ 取指
├ 解码
├ 指令调度
└ 下一条指令决定

这些控制工作由一组 lane 上方的控制前端共同承担。

一个 warp 的指令需要多少时钟周期、实际分配到多少物理 ALU,取决于 GPU 微架构

需要PIX或RenderDoc这一类帧捕获工具。PIX的GPU Capture会记录应用发出的D3D12 API调用及参数,并允许检查命令、资源和Pipeline状态;它通过位于应用和D3D之间的捕获层观察这些调用。​编辑Microsoft for Developers+1

厂商GPU执行层

overflow-visible! 复制代码
复制代码
GPU机器指令
Wave / Warp执行
Occupancy
Cache命中
内存停顿
Shader Stall
硬件队列并行度

UE视口基本不会完整暴露这一层。这里通常需要:

overflow-visible! 复制代码
复制代码
NVIDIA
└ Nsight Graphics

AMD
└ Radeon GPU Profiler

Intel
└ Graphics Performance Analyzers

这些工具会向更接近厂商GPU硬件的计数器、Shader执行和时间线深入。​编辑NVIDIA Developer+2​编辑GPUOpen+2

即使使用厂商Profiler,通常也不是毫无遮蔽地看到一切。

所以不存在一个单一的"绝对原始视图"。

读取三角形拓扑 └ 选择线框或边缘可视化路径 └ 设置专用Pipeline State或调试Shader └ 额外提交绘制 └ 把线条合成到视口

  • 一个 warp 才是由 32 个线程组成。

  • 一个 warp 通常不会一半执行皮肤 Shader,另一半执行另一个物体的不同 Shader。

1 个线程 ≈ 1 次 Shader invocation ≈ 被安排到 1 个 lane 上执行 32 个线程 └ 组成 1 个 NVIDIA warp └ 线程分别占据 lane 0~31

Rasterizer 把三角形投影到屏幕后,判断:

overflow-visible! 复制代码
复制代码
这个三角形覆盖了:
├ 屏幕位置 (620, 410)
├ 屏幕位置 (621, 410)
├ 屏幕位置 (622, 410)
├ 屏幕位置 (620, 411)
└ ......

Draw 产生了大量 Pixel Shader invocation

overflow-visible! 复制代码
复制代码
Skin Pixel Shader invocation 0
Skin Pixel Shader invocation 1
Skin Pixel Shader invocation 2
......
Skin Pixel Shader invocation 12,000

硬件不会逐个执行,而会将它们打包:

overflow-visible! 复制代码
复制代码
Warp 0
├ lane 0:Skin invocation 0
├ lane 1:Skin invocation 1
├ lane 2:Skin invocation 2
├ ...
└ lane 31:Skin invocation 31

Warp 1
├ lane 0:Skin invocation 32
├ lane 1:Skin invocation 33
└ ...

Warp 2
└ ...

因此可以暂时理解为:

NVIDIA GPU 通常一次以 32 个 Shader invocation 为一个 warp 来调度。

它不是一个固定的、从左到右每 32 像素切块的屏幕分区。

不会形成:

overflow-visible! 复制代码
复制代码
同一个 warp
├ lane 0~15:执行 SkinPixelShader
└ lane 16~31:执行 WallPixelShader

UE 中,角色皮肤和衣服如果是不同 Material Slot,通常会成为不同的 Mesh Batch / Draw:

角色 Skeletal Mesh ├ Material Slot 0:Skin │ └ Draw Skin sections │ └ Skin shader waves │ ├ Material Slot 1:Cloth │ └ Draw Cloth sections │ └ Cloth shader waves │ └ Material Slot 2:Eye └ Draw Eye sections └ Eye shader waves

所以即便皮肤和衣服在屏幕上紧挨着,正常情况下也不会因为空间相邻就自动塞进同一个 warp。

overflow-visible! 复制代码
复制代码
左上角
└ 一路向右、向下
   └ 完整走完螢幕
      └ 算一次畫面更新

這幾乎是在描述顯示器取得 framebuffer 的方式,而不是 Pixel Shader 的執行順序。

顯示掃描輸出(scan-out)

先把完整鏈分成三套「分塊」

overflow-visible! 复制代码
复制代码
第一套:GPU 渲染分塊
└ Rasterizer / Tile / Quad / Wave

第二套:Frame Buffer 與 Present
└ Back Buffer → Present → 可供顯示

第三套:顯示掃描
└ Scan-out Engine → Display Link → 螢幕面板

它們都可能看起來像「一塊一塊處理」,但沒有一一對應關係。

遊戲完成一張畫面後,結果存放在一張二維影像資源裡:

overflow-visible! 复制代码
复制代码
Back Buffer
┌────────────────────┐
│ 已經算好的像素資料 │
│                    │
│                    │
└────────────────────┘

程式呼叫 Present 後,DXGI/Windows 顯示系統會讓某張 swap-chain buffer 成為即將顯示的畫面。

接下來不是 Shader Core 再跑一次,而是 GPU 中另外一套顯示硬體:

overflow-visible! 复制代码
复制代码
Display Engine / Scan-out Engine
└ 從顯示用 buffer 讀取像素
   └ 經 DisplayPort / HDMI 傳給螢幕

傳統顯示掃描大致按照畫面行序推進:

overflow-visible! 复制代码
复制代码
第 0 行:左 → 右
第 1 行:左 → 右
第 2 行:左 → 右
......
最後一行

假設顯示引擎正在讀 Frame A:

overflow-visible! 复制代码
复制代码
已掃描部分
┌────────────────────┐
│ Frame A             │
│ Frame A             │
├────────────────────┤ ← 掃描位置
│ 尚未讀取            │
│ 尚未讀取            │
└────────────────────┘

此時程式又 Present 了 Frame B,而且沒有等待適當同步。

顯示引擎可能出現:

overflow-visible! 复制代码
复制代码
上半部
└ 已經從 Frame A 讀出

下半部
└ 改從 Frame B 讀出

Compute Shader threads 可以共享 group shared memory、同步 barrier,以及共同的 group ID?

GPU Graphics Pipeline └ Vertex Processing

Rasterizer └ 產生 Pixel Shader invocations └ 按 quad / wave 執行 └ 寫入 Render Target

Swap Chain └ 完成一張 Back Buffer └ Present └ 選擇可供顯示的 buffer

Display Engine └ 從 buffer 逐步 scan out └ DisplayPort / HDMI └ 螢幕逐步刷新

Shader + Blend/Depth/Rasterizer 状态组成 D3D12 PSO

Static Switch ├ 运行时Shader可以更短 ├ 不需要执行被关闭的支路 └ 代价:产生新的编译版本

Material Analyzer也专门统计静态开关、基础属性覆盖和静态组件遮罩,因为它们会增加Permutation和存储成本。​编辑Epic Games Developers+1

三个互相独立的Static Switch:

overflow-visible! 复制代码
复制代码
A:Detail Normal
B:Clear Coat
C:Wetness

理论组合数是:

overflow-visible! 复制代码
复制代码
2 × 2 × 2 = 8

十个独立布尔开关的潜在组合是:

overflow-visible! 复制代码
复制代码
2¹⁰ = 1024

普通参数变化 └ 同一份DXIL └ 绑定不同Constant Buffer数据 Static Switch变化 └ 不同DXIL Shader Blob └ 通常进入不同的PSO/Shader组合

Translucent

overflow-visible! 复制代码
复制代码
最终颜色
= 当前表面颜色 × 透明度
+ 背景颜色 × 剩余权重

它通常需要:

overflow-visible! 复制代码
复制代码
读取已经存在的背景颜色
├ 启用Blend State

Unity 常见的几种"合批"

  • SRP Batcher

    减少 CPU 切换 Shader 和材质数据的成本(是切換pso的成本,drawcall還是這麼多,這兩個mesh不同,所以還是兩個drawcall),但通常仍然是多个 Draw Call。

  • Static Batching

    预先把静态物体的几何数据合并。能减少 Draw Call,但会增加合并后的顶点数据和内存占用,而且不适合会骨骼变形的角色。

  • Dynamic Batching

    CPU 每帧把很小的网格拼起来再提交。只适用于规模较小、条件严格的网格;现代硬件上未必划算。

  • GPU Instancing

    一次绘制同一个 Mesh 的多个实例。要求几何基本相同,不是"两个不同 Mesh 只因材质相同就合并"。

我的好大佬啊,

  • bloopers(NG片段、拍攝失誤花絮,通常是搞笑、出錯的片段,跟「behind-the-scenes」不完全一樣,bloopers 更聚焦在「笑料、NG」)
  • outtakes(被剪掉的片段,類似 bloopers,常指沒有被放進正式版本裡的片段)

Tile 通常

GPU 当前处理 framebuffer 中某个矩形区域的工作单位。

Framebuffer └ 一整张二维结果图,长期存在于 GPU 内存

Framebuffer 在哪里?

以你的 Windows 11 + UE + D3D12 + 独立显卡为例:

overflow-visible! 复制代码
复制代码
UE
└ FRDGTexture / FTextureRHI

D3D12
└ ID3D12Resource
   └ Render Target / Depth Stencil / Back Buffer

GPU 虚拟内存
└ 驱动分配实际物理存储

物理位置
└ 通常主要位于显卡 VRAM

例如 2560×1440 的最终颜色图:

overflow-visible! 复制代码
复制代码
Back Buffer
├ 像素 (0,0)
├ 像素 (1,0)
├ ...
└ 像素 (2559,1439)

不要把 framebuffer 理解成"显存中只有一张屏幕图"。

UE 一帧中可能同时存在很多类似 framebuffer 的二维资源:

overflow-visible! 复制代码
复制代码
GPU VRAM

├ Scene Depth
├ GBuffer A
├ GBuffer B
├ GBuffer C
├ Velocity
├ Scene Color
├ Lumen 中间结果
├ TSR History
├ 后处理临时纹理
└ Swap Chain Back Buffer

现代图形 API 更常用 Render Target、Depth Target、Image、Texture Resource 等准确名称。"Framebuffer"通常是泛指当前用于保存颜色、深度等渲染结果的一组图像。

每次Texture Sample大致涉及:

overflow-visible! 复制代码
复制代码
计算UV和Mip
└ 查询Texture Cache
   ├ 命中:较便宜
   └ 未命中:从更远层级读取纹理数据
      └ 最终可能访问显存

一个ALU很多但纹理很少的Shader,可能处于Compute-bound;一个指令不多但大量读取纹理和Render Target的Shader,可能是Bandwidth-bound。

带宽需求 ≈ 屏幕像素数 × 每像素读写字节数 × Pass数量 × Overdraw

tile,主要指 raster tile / render tile

它首先只是一个坐标范围:

overflow-visible! 复制代码
复制代码
整张 Render Target:2560×1440

某个 Tile:
x = 320~335
y = 160~175

也就是:

overflow-visible! 复制代码
复制代码
Framebuffer
┌──────────────────────────────┐
│                              │
│       ┌────────────┐         │
│       │ 当前 Tile  │         │
│       └────────────┘         │
│                              │
└──────────────────────────────┘

因此 tile 首先"位于":

Framebuffer 的二维坐标空间中。

物理上,tile 的像素数据在哪里?

这取决于 GPU 架构。

Tile-based GPU

在典型的移动端 Tile-Based Deferred Renderer 中:

overflow-visible! 复制代码
复制代码
外部内存 / 显存
└ 完整 Framebuffer

GPU 芯片内部
└ 小型高速 Tile Buffer
   └ 只容纳当前一个或少数几个 tile

流程是:

overflow-visible! 复制代码
复制代码
1. 把场景几何按屏幕 tile 分类

2. 选择一个 tile
   └ 例如 16×16 或 32×32 区域

3. 在 GPU 芯片内部计算该 tile
   ├ Color
   ├ Depth
   ├ Stencil
   └ MSAA samples

4. tile 完成
   └ resolve / store

5. 写回外部 framebuffer

6. 处理下一个 tile

关系就是:

overflow-visible! 复制代码
复制代码
VRAM / DRAM
└ 完整 Framebuffer
   ↑
   │ Tile 完成后写回
   │
GPU On-Chip Memory
└ 当前 Tile 的临时颜色和深度

Arm 对 tile-based rendering 的说明正是:处理中的 tile 数据保留在片上 tile buffer,结束后才把结果写回外部内存中的 framebuffer,从而减少外部内存带宽。​编辑Arm Developer+1

Tile 和 wave 又在哪里相遇?

假设 GPU 正在处理 framebuffer 中的一个区域:

overflow-visible! 复制代码
复制代码
Tile:16×16
└ 256 个像素位置

Rasterizer 判断哪些三角形覆盖它:

overflow-visible! 复制代码
复制代码
Tile
├ 三角形 A 覆盖 80 个位置
├ 三角形 B 覆盖 120 个位置
└ 其他位置没有覆盖

然后产生 Pixel Shader invocation:

overflow-visible! 复制代码
复制代码
Fragments
└ Pixel Shader threads
   └ 被组成多个 wave / warp

如果是 32-lane warp,忽略边缘、overdraw、helper lane 和 MSAA:

overflow-visible! 复制代码
复制代码
256 invocations
└ 最少大致需要 8 个 warps

[numthreads(8, 8, 1)]

但这是 Compute Shader 的工作网格,不等于光栅器内部 tile,也不等于显示器 scan-out 区域。

Arm 将这种 render pass 模型描述为:attachment 数据在 tile 内处理,通常只在 render pass 结束时产生外部内存输出;如果结果后续不需要,甚至可以完全跳过 store。​编辑Arm Developer+1

即使手机是统一内存:

overflow-visible! 复制代码
复制代码
CPU 和 GPU
└ 共享同一组 LPDDR

这组 LPDDR 对 GPU 核心而言,仍然是 off-chip memory

为什么"片上"和"外部"差别这么大

这里的"外部"不是指 GPU 以外的另一台设备。

它指的是:

overflow-visible! 复制代码
复制代码
GPU 芯片内部
├ Shader Core
├ Cache
└ Tile SRAM

与:

overflow-visible! 复制代码
复制代码
GPU 芯片外部
└ LPDDR / GDDR / 系统 DRAM

而访问 tile SRAM 是:

overflow-visible! 复制代码
复制代码
Shader Core
→ GPU 芯片内部 SRAM

后者通常更省能耗、延迟更低,而且不会占用外部内存带宽。Apple 也直接把 TBDR 的价值表述为避免 GPU 与 system memory 之间昂贵的往返访问。​编辑Apple Developer

因此移动端强调 tile,不是因为 framebuffer 的定义变了,而是因为:

overflow-visible! 复制代码
复制代码
手机 SoC
├ 功耗预算小
├ 内存带宽有限
├ CPU/GPU/NPU 共用 LPDDR
└ 无法频繁搬运大量 framebuffer 数据

Imagination 将降低 system-memory bandwidth 视为 PowerVR TBDR 的核心设计原则。​编辑Imgtec Documentation


SV_* 是 HLSL 中由 Direct3D 規範保留的「系統語義」。把變數標記為 : SV_X,就是把這個變數綁定到一個由圖形管線定義的接口端點;它在哪個階段可用、由誰產生、由誰消費、數值如何解釋以及會造成什麼管線行為,都由圖形 API 規範決定,而不是 Shader 作者自由約定。

: SV_POSITION └─ 宣告這個返回值要交給 Rasterizer,作為頂點位置

Rasterizer 不會進入你的函式裡搜尋:

overflow-visible! 复制代码
复制代码
哪個 float4 看起來像位置?
哪個變數叫 pos?
哪一行做了矩陣乘法?

它只接收正式的 Shader 輸出接口。Direct3D 明確要求送往 Rasterizer 的頂點輸出至少包含最終四分量位置,並用 SV_POSITION 標記;Rasterizer 隨後才執行裁切、透視除法、Viewport 映射和片元生成。​编辑Microsoft Learn+1

False 意思只是:

overflow-visible! 复制代码
复制代码
目前這張材質圖
├─ 沒有 Scene Color 節點
├─ 沒有 PerInstanceRandom 節點
├─ 沒有 PerInstanceCustomData 節點
└─ 沒有 VertexInterpolator 節點

不是說:

overflow-visible! 复制代码
复制代码
這個材質現在沒有隨機數
這個材質現在沒有 instance
這個材質現在是靜止的

Epic 對 PerInstanceRandom 的定義是:

InstancedStaticMeshComponent 為每個 instance 提供一個隨機值;同一 instance 內它保持不變,但不同 instance 可以不同。​编辑Epic Games Developers+1

因此它的完整路徑是:

overflow-visible! 复制代码
复制代码
Material Asset
└─ 圖中存在 PerInstanceRandom 節點
   └─ 編譯器標記:
      Has Per Instance Random = True
         └─ Shader 編譯出讀取該輸入的程式碼
            └─ Draw 時由 InstancedStaticMeshComponent 提供數值

而且這裡的 Random 也不等於「每幀亂跳」:

overflow-visible! 复制代码
复制代码
同一個 instance
└─ 通常保持同一個 random value

不同 instance
└─ random value 不同

所以更準確的名稱其實接近:

overflow-visible! 复制代码
复制代码
Uses Stable Per-Instance Random Input

只是 UI 為了簡短寫成了:

overflow-visible! 复制代码
复制代码
Has Per Instance Random

但它其實是全方向的

你說「不是全方位地看到」,這部分反了。

Environment Map 通常就是要描述某一個空間點周圍的全部方向 。例如 Cubemap 用六個面覆蓋完整方向域;Shader 使用反射向量去查相應方向。​编辑Microsoft Learn+1

你截圖右側也明確寫了:

overflow-visible! 复制代码
复制代码
the computation of the texture coordinates
is possible for all viewing directions

也就是:

無論攝影機從哪個方向觀看物體,都應該能計算對應的環境貼圖座標。

MatCap 与 Sphere Map 的图片外观可以非常相似,真正区别在 Shader 怎样生成采样坐标:

复制代码
经典 MatCap:
ViewSpaceNormal.xy → UV

Sphere Map:
Normal + ViewDirection → ReflectionVector → UV

這份 PDF 在「文獻搜索體系」裡,不應先歸類成普通的 SIGGRAPH 論文。它的準確身份是:

overflow-visible! 复制代码
复制代码
ACM SIGGRAPH 2000
└─ Courses 教學課程
   └─ Course 27
      └─ Approaches for Procedural Shading on Graphics Hardware
         └─ Course Notes 課程講義合集
            └─ Uses of Environment Maps
               └─ Environment Maps and Their Applications
                  作者:Wolfgang Heidrich

SIGGRAPH 2000 的 Course 27 標題頁明確寫的是 "SIGGRAPH 2000 Course 27 Notes --- Approaches for Procedural Shading on Graphics Hardware" 。而課程目錄則把這份 PDF 放在 "Uses of environment maps -- Heidrich" 這一節下。​编辑UMBC User Pages+1

所以最準確的稱呼是:

SIGGRAPH Course Notes 中的一篇 tutorial/survey chapter。

使用完整標題加作者:

overflow-visible! 复制代码
复制代码
"Environment Maps and Their Applications" Wolfgang Heidrich

或者加文獻類型:

overflow-visible! 复制代码
复制代码
"Environment Maps and Their Applications" "course notes"
"Environment Maps and Their Applications" SIGGRAPH 2000
Wolfgang Heidrich environment maps tutorial
overflow-visible! 复制代码
复制代码
environment maps

因為現在這個詞還大量指向機器人、SLAM、無線網路的 radio environment map,以及 AI agent 的 environment map。

paraboloid /pəˈræbəlɔɪd/(最可能,拼字最接近)

數學/幾何學術語,「拋物面」------由拋物線(parabola)旋轉或延伸而成的三維曲面,常見於衛星天線、探照燈反射面的形狀(利用拋物面能把光線/訊號聚焦到一點的特性)

另外,這門課後來延續成了 Real-Time Shading 的課程與書籍體系。2001 年的課程頁面明確說它之前的名稱就是 "Approaches for Procedural Shading on Graphics Hardware";Marc Olano、John Hart、Wolfgang Heidrich 和 Michael McCool 又在 2002 年出版了《Real-Time Shading》。​编辑UMBC CSEE+2​编辑Illinois Experts+2

https://lunakittems.artstation.com/projects/vJqAQA

https://www.reddit.com/r/Unity3D/comments/1seqrq9/a_different_way_to_do_stylized_eyes_in_unity_i/

cornea

擴張、放大、變寬的動作或狀態」

  • 例句:"nitric oxide causes dilation of the blood vessels"(一氧化氮會導致血管擴張)
  • 截圖上方也提到 "Pupil dilation"(瞳孔放大/擴張)------這是很常見的醫學用語,指瞳孔因光線變化、情緒、藥物等因素而放大

「(對某個主題)長篇大論地說明/書寫」

  • 例句:"the main editorial involved no dilation on the privileges or responsibilities of citizenship"(這篇社論並沒有針對公民的權利或義務做長篇闡述)

順便回應你之前在虛幻引擎影片裡看到的 "time dilation":

那段影片裡提到的 time dilation (時間膨脹/時間延緩)用的就是這個字的第一個意思 ------「擴張、延展」,在遊戲開發語境裡,time dilation 指把時間的流速拉長/放慢(類似「子彈時間 bullet time」效果),讓遊戲裡的時間變慢,產生類似電影裡慢動作的效果,這也解釋了為什麼那支切割教學影片裡用 time dilation 來製造「慢動作切割方塊」的畫面。

Cornea 角膜 ------ 眼球最前面透明的「保護玻璃」,截圖裡形容成「虹膜的擋風玻璃」很貼切

Retina 視網膜 ------ 眼球最內側後方,負責「感光成像」,像相機的「感光元件/底片」

  • Sclera 鞏膜 ------ 就是眼白,包覆整個眼球的白色外殼(眼睛的「外牆」)
  • Conjunctiva 結膜 ------ 覆蓋在鞏膜和眼瞼內側的一層透明黏膜(保護、潤滑用)

六個方向的那種不叫 MatCap Texture,它叫:

overflow-visible! 复制代码
复制代码
Cubemap / Cube Texture / Cubic Environment Map

Environment Map ├─ Cubemap │ └─ 六個方形面表示完整方向 │ ├─ Sphere Map / Lat-long / Octahedral Map │ └─ 用一張 2D 圖重新排列完整方向 │ └─ MatCap └─ 用一張 2D 圖保存球面材質的最終外觀

「六張圖」不一定代表六個磁碟文件。它在 GPU 裡通常是一個邏輯資源:

overflow-visible! 复制代码
复制代码
TextureCube
└─ 6 個 array slices / faces

輸入文件也可能是:

overflow-visible! 复制代码
复制代码
六個獨立文件
一張十字排列圖
一張橫向 strip
一張 HDRI,導入後轉成 Cubemap

眼睛模型的 3 大組成元件

  • 眼球(The eye ball)

    • 結構: 極為簡單的半球體模型(Hemisphere)。

    • 用途: 套用主要的眼睛貼圖與眼球著色器(Eye Shader)。

  • 眼睛覆蓋層(The eye overlay)

    • 結構: 覆蓋在眼球與眼瞼(眼皮)接縫處的小型網格模型。

    • 用途: 專門用來計算與產生環境光遮蔽(Ambient Occlusion),讓眼球與眼瞼之間的過渡更自然、更有嵌入感,避免眼球看起來像是浮在臉上。

  • 睫毛(The eye lashes)

    • 結構: 獨立的簡單網格模型。

    • 用途: 套用帶有透明通道(Alpha Blended)的睫毛貼圖。

CRYENGINE | Documentation - Eye Shader (Pre-3.6)

六個面的那種是 Cubemap Texture,不算 MatCap Texture。單張圖片也不一定是 MatCap,它可能是另一種 Environment Map 參數化。判斷標準不是圖片數量,而是圖片中的像素表示「環境方向」還是「材質表面朝向的最終外觀」。

BSSRDF 則允許:

overflow-visible! 复制代码
复制代码
光在位置 Pi 進入
└─ 在物體內部散射
   └─ 從另一個位置 Po 離開

普通 BRDF 假設:

overflow-visible! 复制代码
复制代码
光在位置 P 進入
└─ 仍然從位置 P 離開

典型材質:

overflow-visible! 复制代码
复制代码
皮膚
蠟
牛奶
玉石
大理石

一個材質不是只能有一個反射模式。

overflow-visible! 复制代码
复制代码
Material BSDF
├─ Diffuse lobe
├─ Specular reflection lobe
├─ Clear coat lobe
├─ Sheen lobe
└─ Transmission lobe

例如塑膠:

overflow-visible! 复制代码
复制代码
塑膠 BSDF
├─ 表面介質高光
│  └─ Specular microfacet BRDF
└─ 材料內部散射後返回
   └─ Diffuse BRDF

金屬:

overflow-visible! 复制代码
复制代码
金屬 BSDF
├─ Conductive specular BRDF
└─ 幾乎沒有普通 diffuse lobe

玻璃:

overflow-visible! 复制代码
复制代码
玻璃 BSDF
├─ Specular reflection BRDF
└─ Specular transmission BTDF

最常見的 Microfacet BRDF

現代 PBR 的 Specular 通常使用微表面模型:

overflow-visible! 复制代码
复制代码
宏觀看起來平滑的表面
└─ 微觀上由大量微小鏡面組成

典型形式:

fr=D F G4(N⋅L)(N⋅V)f_r = \frac{D\,F\,G} {4(N\cdot L)(N\cdot V)}fr​=4(N⋅L)(N⋅V)DFG​

overflow-visible! 复制代码
复制代码
D:Normal Distribution Function
└─ 有多少 microfacet 朝向 Half Vector
   └─ Roughness 主要影響這裡

F:Fresnel
└─ 不同觀看角度反射多少光

G:Geometry / Visibility
└─ microfacet 之間的遮蔽與陰影

分母
└─ 幾何投影和正規化

Roughness └─ 控制 specular lobe 的寬度和形狀

Lambert Diffuse 則是最簡單的漫反射 BRDF:

fd=ρπf_d=\frac{\rho}{\pi}fd​=πρ​

其中 ρ\rhoρ 是表面反照率。

UE 5.8 的 Substrate 把材質理解為 Slab of Matter,每個 Slab 是一個 principled BSDF 表示

overflow-visible! 复制代码
复制代码
Substrate Material
└─ Slab BSDF
   ├─ Diffuse response
   ├─ Specular response
   ├─ Roughness
   ├─ Mean free path
   ├─ Transmittance
   └─ F0 / F90 等物理參數

多層材質:

overflow-visible! 复制代码
复制代码
Top Slab:透明塗層
└─ Vertical Layer Operator
   └─ Bottom Slab:金屬/塑膠/皮膚

這不是單純:

overflow-visible! 复制代码
复制代码
Color A + Color B

而是接近:

overflow-visible! 复制代码
复制代码
光先與上層 BSDF 交互
├─ 一部分反射
└─ 一部分進入下層
   └─ 再與下層 BSDF 交互

Substrate 的層級是:

overflow-visible! 复制代码
复制代码
Material Graph
└─ 建立 Slab BSDF
   └─ 組合多個 Slab
      └─ 編譯成引擎可執行的材質表示
         └─ 在 Base Pass/Lighting Pass 等位置使用
  • impersonator /ɪmˈpɜːsəneɪtər/,意思是「模仿者、模仿藝人」------指模仿某個名人(外表、聲音、表演風格)的人,常見於模仿秀、翻唱歌手等
overflow-visible! 复制代码
复制代码
Shift + 點擊 Red 左側眼睛
└ 只顯示 Red,其他通道全部隱藏

同理可以查看 Green、Blue、Alpha。普通點擊眼睛只是逐個顯示/隱藏;Shift + 點擊眼睛 會隔離該通道。​编辑GIMP Documentation

先看這句話字面上想表達什麼:

"Why is it that people who sing are better at speaking than they are at talking normally?"

= 「為什麼會唱歌的人,講話會比他們平常說話還要厲害?」

問題出在哪:

  • speaking(說話、發聲表達)
  • talking normally(平常正常說話)

這兩者在英文裡幾乎是同義詞,一般情況下沒有明確區分「speaking」和「talking」誰更高級/正式,所以整句話變成「說話比說話厲害」,邏輯上顯得很奇怪、甚至有點同義反覆(tautology)的感覺。

Skeletal Mesh/Asset Preview Viewport 裡:

overflow-visible! 复制代码
复制代码
按住 L
└ 同時按住滑鼠左鍵拖動
   └ 旋轉預覽場景的 Directional Light

不是 Ctrl + LCtrl + L 通常是 Level Viewport 裡操控場景太陽光;資產預覽窗口使用的是 L + LMB Drag 。Epic 的 UE 5.8 文件也將其列為 L + Mouse MoveHold L and drag with the left mouse button​编辑Epic Games Developers+1

Ramp 更适合美术控制:

  • 可以决定阴影颜色;
  • 可以控制分界是硬还是软;
  • 可以增加第三、第四色阶;
  • 可以让不同材质使用不同明暗风格;
  • 改贴图即可调整效果,不必修改 Shader。
  • vermin /ˈvɜːmɪn/ = 「害獸、害蟲」,泛指老鼠、田鼠這類會危害農作物或環境衛生的小型野生動物
  • humanely /ˈhjuːmeɪnli/ = 「人道地」------強調用盡量減少痛苦、快速致死的方式進行,而不是殘忍虐殺
  • shot(過去分詞,shoot 的過去分詞)= 「被射殺」

"is effectively dead. The twitching is literally just nerves."

= 「(牠)實際上已經死了。那些抽搐純粹只是神經反應。」

這句話的背景知識(常見於狩獵/害獸防治影片):

動物被射殺瞬間死亡後,身體有時還會出現抽搐(twitching) ,這是神經系統的殘餘反射動作,不代表動物還活著、還在痛苦掙扎,講者在解釋這一點,避免觀眾誤以為動物「死不透、還在受苦」。

KIA = Killed In Action(軍事用語「陣亡」,這裡被戲謔地借用來統計「被射殺確認死亡的老鼠數量」,68隻)

riddle 這個字的動詞意思:

「(被大量的洞、痕跡等)佈滿、遍布」------通常是負面、密集、到處都是的畫面感:

  • riddled with bullet holes(佈滿彈孔)
  • riddled with mistakes(錯誤百出、到處都是錯)
  • riddled with rats(老鼠成災、到處都是老鼠)

"Nice little ambush point."

逐字翻譯:

= 「(這是個)不錯的小埋伏點。」

單字解釋:

ambush /ˈæmbʊʃ/,重音在第一音節

  • 名詞:埋伏、伏擊
  • 動詞:伏擊、突襲
  • ambush point = 埋伏地點,軍事/狩獵用語,指「適合躲藏起來、等待獵物/敵人出現再突然行動的位置」

"Rancher takes care of big Predator problem | 70 Coyotes Down"

= 「牧場主人解決了嚴重的掠食者問題 | 70隻郊狼(北美狼)倒下」
"But managed to uh redeem myself."
逐字翻譯:

= 「但(我)還是設法挽回了面子/扳回一城。」

"rancher my ass bro is a full spec ops unit 😭"

= 「什麼牧場主人啊,老兄根本就是一整支特種部隊。」

  • "my ass" (口語粗俗用法)------放在名詞後面表示「才怪、屁啦」,強烈質疑/否定前面那個說法,類似中文「什麼XX啊」的嘲諷語氣
    • "Rancher, my ass" = 「牧場主人?才怪咧」
  • spec ops(specialized operations 的縮寫)= 「特種部隊」

留言2(@janedoecheezepizza,5.6K讚):

"This man never lost on a dog round on cod zombies"

= 「這傢伙在《決勝時刻:殭屍模式》的『狗狗關卡』裡肯定從沒輸過。」

  • cod = Call of Duty(《決勝時刻》,知名射擊遊戲系列)的縮寫
  • zombies(殭屍模式)= 《決勝時刻》裡一個很受歡迎的殭屍生存遊戲模式
  • "dog round" = 殭屍模式裡,偶爾會出現的一波「特殊殭屍狗」關卡,是玩家公認最容易失手、最緊張刺激的回合之一

"gifted with the cat distribution system"------這是網路流行梗,"cat distribution system"(貓咪分配系統)是一個幽默的說法,意指「貓好像會自己神秘地找到適合的人類/家庭收養牠們」,彷彿宇宙中真的存在一套系統會把流浪貓分配給有緣人

NVD = Normal Vaginal Delivery(自然產、經陰道自然分娩)------相對於剖腹產(C-section),指產婦經由產道自然生產的方式

Delayed cry (延遲哭泣)------指新生兒出生後沒有立即哭出聲,正常情況下新生兒出生應該馬上啼哭(這是肺部開始自主呼吸的重要指標),如果延遲哭泣,代表新生兒可能有呼吸窘迫或缺氧的狀況,需要緊急處理

Full Resuscitation /ˌriːsəsɪˈteɪʃən/ = 「完整復甦(急救)程序」

  • resuscitation 這個字用你熟悉的規則看:-tation 讀 /teɪʃən/(符合 -tion 顎化規則)
  • 指針對新生兒進行的一系列急救措施(清除呼吸道、刺激呼吸、必要時人工給氧或心肺復甦)

Shocking legs

這裡不是字面上的「令人震驚的腿」,而是英式口語裡的:

shocking

└ extremely bad

└ terrible / awful

└ 糟糕透了、難看得要命

完整句本來是:

He had shocking legs.

他的腿難看得要命。

但口語中直接把主語和動詞全部刪掉:

Shocking legs.

為什麼使用 had

he had nice legs

使用過去式,是因為 Alex 現在已經失去雙腿。這句同時存在兩層意思:

過去他的腿也並不好看;

現在他沒有腿了,但這不影響她愛他。

所以這個笑話利用了 had 的現實背景。假如只是普通伴侶,說:

He didn't have nice legs.

原句是:

Alex has been home with his family for six weeks, but is now returning to hospital for an extended period of rehabilitation.

"Are you going to be okay?"

意思是:

Alex 已經在家和家人生活了六週,但現在要再次回到醫院,接受一段較長時間的復健。

「一個人會沒事嗎/能應付得來嗎?」

這裡的 rehabilitation 是不可數名詞,表示整體的復健過程,不是一次次獨立的「復健」。

rehabilitation

└ 恢復身體功能與獨立生活能力的整體過程

  ├ 物理治療

  ├ 職能治療

  ├ 義肢訓練

  └ 日常生活技能訓練

所以不是:

several rehabilitations

I wasn't prepared for what came my way.

我沒有準備好應對後來發生在我身上的事。

cats are coddled

└ 貓被嬌養、被過度保護

like plush toys

└ 像對待絨毛玩具一樣

coddle

coddle 不只是一般的「照顧」。

它帶有:

給予過度保護

└ 過分寵愛

└ 不把對方當成能獨立行動的存在

└ Texture Coordinate:產生原始座標 (X, Y, Z) └ Mapping:平移、旋轉、縮放這組座標

https://cdna.artstation.com/p/media_assets/images/images/000/668/470/original/10.gif?1605038889

作者在文中寫道:

"looking over the model, finding areas that either blend into the background or lack discernible form, and putting in lights..."

意思是:在審視模型時,尋找那些「跟背景融為一體」或者「因為缺乏光影對比,導致看不清幾何立體輪廓」的地方。

真正需要管的是:

无论灯从哪里来,角色材质是否能够稳定、可控、性能合理地响应它。

测试灯光组不是最终游戏布光,而是材质实验室。就像测试动画不代表角色永远只播放测试动作。

我檢查了圖片,它是:

overflow-visible! 复制代码
复制代码
1024 × 32

而:

overflow-visible! 复制代码
复制代码
1024 = 32 × 32

所以它正好是:

overflow-visible! 复制代码
复制代码
32 個小格
每格 32 × 32
= 32 × 32 × 32 個查找點

也就是一個 32³ 的顏色立方體

Epic 的 UE 5.8 文件展示的是同一種結構,只是用較低解析度的 16³ LUT,攤平後是 256×16;你這張則是更細的 32³ LUT
在攝影學中,EV 是由「光圈、快門速度」共同決定的數值。UE 改採基於物理的光照計算(PBR)後,導入了 EV100 作為標準:

  • 定義 :當感光度固定在 ISO 100 、光圈為 f/1.0 、快門時間為 1秒 時,其曝光量定義為 EV100 = 0

人物材質先算出一個 RGB,再把這個 RGB 轉成這張 1024×32 圖上的座標。

EV100 不是光照强度,也不是最终画面亮度。

它是一个以 ISO 100 为基准、用 log₂ 表示的相机曝光档位

EV100=log⁡2(N2t⋅100ISO)EV_{100}=\log_2\left(\frac{N^2}{t}\cdot\frac{100}{ISO}\right)EV100​=log2​(tN2​⋅ISO100​)

其中:

  • NNN:光圈 F-number,例如 f/2.8 中的 2.8

  • ttt:快门打开时间,单位为秒,例如 1/60 秒

  • ISOISOISO:感光度

UE 的物理相机曝光正是根据光圈、快门和 ISO 计算 EV100。​编辑Epic Games Developers+1


EV100 在 UE 渲染链中的位置

overflow-visible! 复制代码
复制代码
场景中的灯光
├─ Directional Light:Lux
├─ Point / Spot / Rect Light:Lumen 或 Candela
└─ Emissive / Sky:cd/m²
        ↓
表面接收到光,着色器计算 HDR Scene Color
        ↓
曝光测光
├─ Manual:相机参数或固定 EV100
└─ Auto Exposure:分析画面亮度直方图
        ↓
得到 Target EV100
        ↓
转换成线性 Exposure multiplier
        ↓
Scene Color × Exposure
        ↓
Tonemapper / Local Exposure / Color Grading
        ↓
SDR 或 HDR 显示输出

为什么 EV100 每增加 1,画面就暗一半

暂时忽略曝光补偿,曝光倍率的核心关系可以写成:

Exposure=2−EV100Exposure=2^{-EV_{100}}Exposure=2−EV100​

也就是:

EV100 曝光倍率
0 1
1 1/2
2 1/4
3 1/8
8 1/256
15 1/32768

Min EV100 / Max EV100

它们不是输出亮度,而是自动曝光允许移动的范围

overflow-visible! 复制代码
复制代码
自动曝光计算 Target EV100
└─ Clamp(Target EV100, Min EV100, Max EV100)
   └─ Actual EV100 逐渐向它移动

例如:

overflow-visible! 复制代码
复制代码
Min EV100 = 5
Max EV100 = 12

意味着自动曝光只能在 5 到 12 档之间适应。

Pre-Exposure 又是什么

UE 并不一定等到后处理阶段才把完整曝光乘上去。为了防止现实级灯光数值使低精度 Scene Color Render Target 溢出,UE 会把前一帧的曝光值提前应用到着色器计算中。

overflow-visible! 复制代码
复制代码
巨大 HDR 光照结果
└─ Shader 内应用 Previous Frame Pre-Exposure
   └─ 把数值搬到较安全的范围
      └─ 写入 Scene Color
         └─ 后续阶段恢复正确曝光语义

它的作用主要是数值范围重映射

  • 降低 Render Target 溢出风险

EVExposure Value 的縮寫。

感光度固定為 ISO 100 的基準下
現實世界的亮度跨度太過恐怖(太陽 vs. 螢幕)
電腦螢幕的發光能力和現實世界相比,弱小得像一根火柴。

  • 現實的太陽 :亮度高達 1,000,000,000 Nits(亮度單位)。
  • 現實的點燈室內 :大約只有 100 ~ 300 Nits
  • 您的普通電腦螢幕 :最大亮度頂多 300 ~ 500 Nits

如果電腦強制只用一個絕對亮度基準(不准像相機那樣調整快門和 ISO 100),當玩家從大白天的戶外走進山洞時,因為太陽和山洞的物理光線相差了百萬倍, 您的電腦螢幕根本沒有足夠的物理發光範圍來同時呈現這兩種極端 。如果不用 ISO/EV 去做動態「平移(壓縮)」,畫面不是全白(瞎掉)就是全黑。
「肆意調整」其實是舊時代的壞習慣(現在正在被校正)
您說的「不該肆意調」完全說中了現代遊戲開發的轉折點:

  • 以前的舊遊戲(非物理渲染) :美術覺得太暗就拼命調大燈光強度,覺得太亮就調低曝光。這導致同一個角色換張地圖就直接毀容、變色。
  • 現在的 UE5(Lumen 時代) :Epic Games 官方現在極度反對肆意調整。他們目前的標準工作流就是**「鎖定 EV/ISO基準」** 。
    • 官方要求開發者:太陽光必須死死固定在 100,000 Lux(大自然物理值)。
    • 鏡頭的 EV100 必須鎖定在符合大白天的 14
    • 不准任何人私自去調亮度 。如果畫面不對,代表你的材質反射率做錯了,必須回去修改材質。

HDR Render Target 使用浮点格式。它虽然可以存储大于 1.0 的值,但仍然不是无限大:

overflow-visible! 复制代码
复制代码
8-bit UNORM
└─ 通常只能表示 0~1
   └─ 超出时被 Clamp

16-bit Float
└─ 可以表示很大的 HDR 数值
   └─ 但仍然存在最大有限值
      └─ 超出后可能成为 Inf、NaN 或产生异常结果

所以"Render Target 溢出"准确来说是:

Shader 算出了一个数值,但目标纹理使用的像素格式没有足够的数值范围保存它。

现实尺度灯光之间的跨度非常大。暗室可能只有几个 Lux,日光可能达到数万甚至十万 Lux;高亮反射、自发光、Bloom 输入和多盏灯累积后,Scene Color 中的数字可能进一步放大。Epic 明确指出,极亮光源可能使低精度 Scene Color Render Target 出现算术溢出或黑斑,因此 UE 使用 Pre-Exposure 缩小写入 Scene Color 所需的数值范围。​编辑Epic Games Developers+1

EV100 是数字相机观察 HDR 场景时的曝光标尺,而不是数字世界灯光本身的亮度标尺。

变灰正是在告诉你:Manual 模式不使用这些参数。

需要纠正一个容易混淆的地方:这个 Post Process 面板在关闭 Apply Physical Camera Exposure 后,通常并没有一个单独让你填写"Manual EV100"的输入框。此时画面基准主要由 Manual 的固定曝光公式和 Exposure Compensation 决定。

所以对你最实用的理解是:

这三个设置负责固定相机;画面太黑或太亮以后优先调整灯光,不再让相机追着场景变化。

  • RD = Ramp DiffuseRamp/Diffuse
  • RS = Ramp SpecularRamp/Specular

后面的兰伯特、GGX 高光、阴影、灯光累加都由 UE 的 Default Lit Shading Model 在材质节点之后完成,所以你看不到那些节点,但它们仍然存在:

复制代码
Base Color
    ↓
UE Default Lit:N·L、阴影、间接光、Specular BRDF
    ↓
曝光与 Tonemapper

如果把 LUT 用来重映射 Base Color,流程只是:

复制代码
BaseColor Texture → LUT → Base Color → UE 默认光照

它只改变"送入光照计算的颜色",没有替代兰伯特,也没有决定明暗边界。

生效链路:材质重新编译 → Shader permutation / HLSL 生成 → RHI 创建 GPU shader 与管线状态 → 图形 API → 显卡驱动与操作系统 → GPU 执行。

Unlit 不是"不走虚幻的渲染管线",而是"不走虚幻的光照模型"。

放到 UE 裡看,就是這條鏈

overflow-visible! 复制代码
复制代码
Blender
└ 計算平滑法線
   └ 編碼成 float2
      └ 寫入 EXTRATUV0

FBX
└ 將額外 UV Channel 一起匯出

UE Skeletal Mesh
└ UV Channel 1 / 2 / 3

UE Material
└ TextureCoordinate
   └ Coordinate Index 指向該通道
      └ ×2 - 1
         └ 重建/解碼 Normal
            ├ 用於 World Position Offset 描邊
            └ 用於自訂頭髮光照

UE 官方也把 mesh UV 描述為逐頂點屬性;材質可以通過不同的 Texture Coordinate 通道取得它們。

因为法线向量的长度恒为1(Rho是球面坐标中到原点的距离),所以不需要存储Rho,只需存储表示方向的两个角度参数Theta(方位角,绕Z轴旋转的角度)和Phi(极角,与Z轴的夹角)。原文中球面坐标编码法线的方法正是利用了这一点,通过atan2(n.y, n.x)计算Theta,直接取n.z(对应Phi的余弦值)来简化存储,把三维向量压缩成二维数据。

实际工程中的选择

方法 存什么 优点 缺点
Stereo Projection n.x, n.y 解码快,简单 赤道附近精度差
球面坐标 θ, n.z 各方向精度较均匀 两极不稳定,解码需三角函数
八面体编码 (做了八面体投影后的 xy) 精度均匀,解码快,无极点问题 编码稍复杂

实际上在现代图形学中,​八面体编码反而是更主流的选择,因为它兼顾了精度均匀和解码效率,避免了前两种方法的各自痛点。

压缩方法(球面坐标、八面体等)

Vertex Color 與額外 UV 都是在增加頂點屬性,不是在增加幾何面數。它們都會增加 Vertex Buffer 資料;預設 RGBA8 Vertex Color 和 half2 UV 通常都是每個 Render Vertex 4 bytes,但 UV 把位元集中在兩個分量上,所以更適合高精度的二維法線編碼。

八面體壓縮,,,,https://www.bilibili.com/video/BV15csWzTEyV?

Geometry Nodes 可以找到:

overflow-visible! 复制代码
复制代码
Geometry Proximity
├ Position → 最近點 Q,float3
└ Distance → 最短距離 |Q-P|,float

Distance 只是例如:

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

它沒有方向,不能用作法線。Geometry Proximity 的確會計算目標幾何上的最近位置和距離。

Geometry Proximity 的確會計算目標幾何上的最近位置和距離。​编辑Blender Documentation

為什麼不能直接使用 Q - P

你可能會想到:

overflow-visible! 复制代码
复制代码
N = normalize(Q - P)

只有當模型頂點全部處於球體內部時,這個方向才剛好向外。

overflow-visible! 复制代码
复制代码
P 在球內
└ Q - P 指向球面外側
   └ 方向正確

P 在球外
└ Q - P 指回球面
   └ 方向與球面外法線相反

角色頭髮有些頂點可能在代理球外面,有些可能穿過球面。因此更穩定的做法不是計算 Q-P,而是直接取得代理模型在最近點 Q 上的法線。

Geometry Nodes 裡真正合理的節點鏈

使用一個球或經過調整的頭部代理 Mesh:

overflow-visible! 复制代码
复制代码
Object Info
└ Object:代理球/頭部代理模型
   └ Transform Space:Relative
      └ Geometry

然後:

overflow-visible! 复制代码
复制代码
代理模型 Geometry
└ Sample Nearest Surface
   ├ Mesh:代理模型
   ├ Value:Normal
   └ Sample Position:頭髮的 Position
      └ Value 輸出:代理表面最近點的插值法線

Sample Nearest Surface 的用途就是在來源 Mesh 的最近表面位置,取得並插值任意屬性;因此把來源幾何的 Normal 接入 Value,就能為每個頭髮位置取得代理表面的法線。​编辑Blender Documentation

如果代理物體是完美球,甚至不用真的建立球

完美球的法線只取決於球心,不取決於半徑。

overflow-visible! 复制代码
复制代码
P = 頭髮頂點
C = 球心

N = normalize(P - C)

所以可以只放一個 Empty 表示球心:

overflow-visible! 复制代码
复制代码
Position
└ Vector Math:Subtract
   ├ A:Position
   └ B:Empty 的 Location
      └ Vector Math:Normalize
         └ 球形法線 N

這裡不需要:

overflow-visible! 复制代码
复制代码
球的半徑
球面最近點
最短距離

因為不論球多大,沿同一條半徑方向的法線都是相同的。

  • 材质描述

    UMaterial 保存 Shading Model、Blend Mode 等信息,表达"这个材质需要什么"。

  • 编译期分支

    材质系统据此生成对应的 Shader permutation。Unlit 不只是运行时的一个 if,很多不需要的代码和资源会在编译时被排除。

它不需要 Sky Atmosphere 才能存在。官方資料也將 Exponential Height Fog 定義為獨立的高度密度霧;它還能根據主要 Directional Light 的方向使用不同的霧散射顏色。​编辑Epic Games Developers+1

相关推荐
ThsPool2 小时前
【ENVI二次开发学习整理 02】IDL语言基础与程序设计
学习·中间件
一只小菜鸡..3 小时前
南京大学 操作系统 (JYY) 学习笔记:文件系统实现——从 FAT、ext2 到现代 B-Tree 黑科技
笔记·科技·学习
深爱水瓶 血影S狂风3 小时前
Linux.NET学习手记(2)
linux·学习·.net
明明真系叻3 小时前
RAG学习(1)
学习·rag
✎ ﹏梦醒͜ღ҉繁华落℘3 小时前
单片机基础知识--LVGL学习-lv_obj_add_flag()
c语言·单片机·学习
春水碧于天,画船听雨眠3 小时前
LangChain学习笔记(一)
笔记·学习·langchain
zerwave3 小时前
Docker 学习:多容器网络互通——从 host 模式到自定义 bridge 网络
网络·学习·docker
Zzj_tju3 小时前
Instruction Tuning 论文精读路线:从 Supervised Fine-Tuning 到 Instruction Following
人工智能·笔记·学习·语言模型·自然语言处理
j7~4 小时前
【Linux】二十八.线程篇五《Linux多线程编程:线程同步之条件变量》---详解
linux·运维·c++·学习·条件变量·线程同步