TiXL NormalMap 操作符详解:将图像亮度实时转换为可用于 PBR 材质的法线贴图
【免费下载链接】t3TiXL is an open source software to create realtime motion graphics.项目地址: https://gitcode.com/GitHub_Trending/t3/t3
本篇技术指南围绕 TiXL(t3)开源实时动态图形软件中的NormalMap图像操作符展开:它位于 Lib.image.use 算子库中,功能是把输入纹理的亮度信息实时转换为法线贴图(Normal Map),供 SetMaterial 等 PBR 材质系统使用。读完本文,你将掌握 NormalMap 的全部 8 个输入参数的作用与取值范围、4 种输出模式的区别,以及其底层 HLSL 着色器的梯度差分与法线重建算法,能够在自己的 TiXL 工程中快速搭建"灰度高度图 → 法线贴图 → PBR 材质"的完整链路。
NormalMap 是什么
在实时渲染中,法线贴图通过在纹理的 RGB 通道中编码表面法线方向,让低面数网格呈现出远超其几何细节的表面凹凸质感。法线贴图通常由高模烘焙生成,但 TiXL 的NormalMap操作符提供了一条完全实时、无需预烘焙的路径:直接以任意图像的亮度(Brightness)作为高度场输入,在 GPU 上逐像素计算梯度并重建法线。
根据官方文档描述,该操作符"将图像的亮度转换为法线贴图,可用于 SetMaterial"。其典型应用场景包括:
- 把噪声纹理、云纹、液态/流动贴图实时转换为动态法线,赋予表面流动感;
- 用音频驱动的灰度图形生成随节拍起伏的凹凸表面;
- 在 SetMaterial 的
NormalMap输入上动态叠加细节,无需更换几何体。
输入参数总览
NormalMap共暴露 8 个输入参数,官方文档给出的完整参数表如下:
| 名称(相关度与类型) | 说明 |
|---|---|
| LightMap(Texture2D) | 作为高度场来源的输入纹理 |
| Impact(Single) | 法线凸起强度(梯度缩放系数) |
| SampleRadius(Single) | 梯度差分采样半径(像素) |
| Resolution(Int2) | 输出分辨率控制 |
| Twist(Single) | 法线方向旋转角(度) |
| Mode(Int32) | 输出模式选择(映射为枚举) |
| OutputFormat(Format) | 输出纹理的 DXGI 格式 |
| TextureRepeat(TextureAddressMode) | 采样时的纹理寻址模式 |
对应地,NormalMap.cs 中定义了这些插槽及各自稳定的 GUID,其中Mode插槽带有MappedType = typeof(Modes)标注,意味着在 TiXL 编辑器中它以下拉枚举的形式呈现,而不是裸露的整数。
输出
| 名称 | 类型 |
|---|---|
| Output | T3.Core.DataTypes.Texture2D |
输出为一张Texture2D,可直接连接给 SetMaterial 的NormalMap输入(Texture2D类型),也可继续交给其他图像操作符做后处理。
各参数深入解析
LightMap:高度场来源
作为唯一的纹理输入,LightMap提供高度场数据。从 NormalMap.hlsl 的实现看,着色器将其绑定为Texture2D<float4> DisplaceMap : register(t0),通过DisplaceMap.GetDimensions()获取实际纹理尺寸用于归一化采样步长。它既可以连接图片加载算子,也可以是任意实时生成的纹理。
Impact:法线强度
Impact(默认值 1.0,见 NormalMap.t3)直接缩放梯度向量:在着色器中表现为len * direction * Impact,随后才被用于重建法线。数值越大,高度差被放大得越明显,法线越"陡峭",表面凹凸感越强;设为 0 时梯度长度归零,法线退化为垂直表面方向,凹凸效果消失。它适合作为主强度旋钮或由音频/动画曲线驱动。
SampleRadius:差分采样半径
SampleRadius(默认值 2.0)控制梯度差分时在 X/Y 方向上偏离中心像素的距离。HLSL 中的归一化逻辑为:
float sx = SampleRadius / (float)displaceMapWidth; float sy = SampleRadius / (float)displaceMapHeight;即以输入纹理的宽、高分别归一化该像素偏移量,随后采样中心点左右两侧的四个邻域像素(cx1/cx2/cy1/cy2)做有限差分。半径越大,捕捉到的低频起伏越多(细节被平滑),法线越柔和;半径越小,越能保留高频细节但噪声也更明显。
Resolution:输出分辨率
Resolution(Int2)用于控制输出的尺寸。默认配置为X: 0, Y: 0,结合 TiXL 图像链路的惯例,可推断该默认值表示不强制指定、沿用着色器目标尺寸(即输入/目标纹理分辨率);显式给定 X/Y 值则可固定法线贴图的分辨率。对于大尺寸纹理上的高频细节,适当提高分辨率可避免采样混叠。
Twist:法线方向旋转
Twist(默认值 180.0)以度为单位对法线方向做整体旋转。着色器中将其转换为弧度后叠加到梯度角度上:
float angle = (d.x == 0 && d.y == 0) ? 0 : atan2(d.x, d.y) + Twist / 180 * 3.141592;当d.x与d.y同时为 0(平坦区域)时角度取 0,避免atan2出现不确定结果。默认 180° 意味着方向向量整体翻转半个圆周,可用来修正不同来源高度图(如左手/右手习惯、UV 朝向)带来的法线方向不一致问题。
Mode:四种输出模式
Mode是 Int32,在编辑器中映射为 NormalMap.cs 中定义的枚举:
| 枚举值 | 名称 | 含义 |
|---|---|---|
| 0 | Gray_ToNormalizedRGB | 灰度高度 → 归一化到 [0,1] 的 RGB 法线 |
| 1 | Gray_ToNormalizedRGBSigned | 灰度高度 → 带符号([-1,1])的 RGB 法线 |
| 2 | Gray_ToAngleAndMagnitude | 输出角度 + 幅度(双通道编码) |
| 3 | Red_ToRG_KeepBA | 基于红色通道差分,RG 存方向、保留原图 BA |
HLSL 侧用四个哨兵常量(0.5 / 1.5 / 2.5 / 3.5)划分区间实现多分支:
- Mode 0(Gray_ToNormalizedRGB):计算法线
normalize(float3(len * direction * Impact, 1)),翻转 Y 通道(normal.y = -normal.y)以匹配常见引擎的法线空间约定,再执行normal / 2 + 0.5映射到 [0,1],适合存放到无符号格式并直接接入大多数渲染管线的NormalMap槽; - Mode 1(Gray_ToNormalizedRGBSigned):同样重建法线并翻转 Y,但不做[0,1] 映射,直接输出带符号法线,适合需要保留负值精度的场景(配合半浮点
OutputFormat使用); - Mode 2(Gray_ToAngleAndMagnitude):不再输出法线向量,而是输出
(mod(-angle, 2π), len * Impact, 0, 1),将梯度方向编码为角度、梯度长度编码为幅度,适合在自定义着色器中二次处理(如扭曲方向场); - Mode 3(Red_ToRG_KeepBA):梯度差分改基于红色通道(详见下文算法解析),RG 通道写入方向分量(
len * direction * Impact + 0.5),而 B、A 通道直接透传原图uvImage.ba,适合把法线方向与原有数据(如透明度、材质 ID)打包在同一张纹理里。
OutputFormat:输出纹理格式
OutputFormat(默认R16G16B16A16_Float)决定输出纹理的 DXGI 格式。默认的半浮点格式对法线贴图尤其友好:既能以更高精度存储 Mode 1 输出的负值法线,也能在角度/幅度模式下保留更宽的数值范围;若只需把结果喂给对精度不敏感的显示链路,也可切换为更省显存的 8 位格式。
TextureRepeat:纹理寻址模式
TextureRepeat(默认MirrorOnce)控制着色器采样时的寻址行为。从 NormalMap.t3 的连接关系看,该参数会覆盖内部_ImageFxShaderSetupStatic的Wrap输入(内部默认Wrap)。当SampleRadius导致采样点越过纹理边界时,MirrorOnce采用镜像平铺,可避免接缝处出现突兀的梯度跳变——对于需要无缝循环的高度场(如平铺噪声)非常实用。
底层实现:NormalMap.hlsl 的梯度差分与法线重建
NormalMap在架构上是一个复合算子:其符号定义 NormalMap.t3 内部组合了_ImageFxShaderSetupStatic与IntToFloat子节点,将着色器源指向Lib:shaders/img/fx/NormalMap.hlsl,各输入参数通过命名插槽绑定到着色器ParamConstants常量缓冲区。
核心算法位于 NormalMap.hlsl 的psMain,可概括为三步:
1. 四邻域有限差分。以当前像素为中心,沿 X/Y 各偏移sx/sy采样四个点,计算梯度:
float2 d = Mode > Red_ToRG_KeepBA ? float2((grayX1 - grayX2), (grayY1 - grayY2)) : float2((cx1.r - cx2.r), (cy1.r - cy2.r));从源码结构可以推断:默认(Mode ≤ 3)时差分基于红色通道(Red_ToRG_KeepBA的命名即源于此);仅当 Mode 被驱动到 3.5 以上时,才切换为 RGB 平均灰度差分(grayX/grayY)。换言之,"亮度转法线"在实现上以红通道作为亮度代理,如需真正的全通道亮度转换,可通过参数动画将 Mode 推过 3.5 边界。
2. 角度与幅度分解。梯度向量d被分解为方向角atan2(d.x, d.y)(叠加Twist)与模长len,方向单位向量由float2(sin(angle), cos(angle))重建。
3. 按 Mode 输出。法线模式(0/1)将len * direction * Impact作为 XY 分量与 Z=1 组成三维向量并归一化(这正是"高度场越陡、法线越倾斜"的数学本质),随后按模式决定是否做 [0,1] 映射与 Y 翻转。
默认配置一览
以下为 NormalMap.t3 中记录的出厂默认值,可直接作为参数调优的起点:
| 参数 | 默认值 |
|---|---|
| LightMap | null(需手动连接) |
| SampleRadius | 2.0 |
| OutputFormat | R16G16B16A16_Float |
| Twist | 180.0 |
| Impact | 1.0 |
| Resolution | 0 × 0 |
| TextureRepeat | MirrorOnce |
| Mode | 0 |
与 SetMaterial 配合构建 PBR 材质
NormalMap的输出专为 SetMaterial 设计。在其 C# 实现 SetMaterial.cs 中,NormalMap输入槽接收Texture2D,并在每帧更新时通过UpdateSrv将纹理封装为ShaderResourceView挂载到PbrMaterial.NormalSrv;纹理发生脏标记变化或 SRV 尚不存在时才重建,未连接或失效时回退到默认法线 SRV。这意味着你可以把NormalMap.Output直接接到SetMaterial.NormalMap,得到一套完整的实时 PBR 材质管线:
任何灰度/彩色纹理 → NormalMap → SetMaterial.NormalMap → PBR 渲染仓库中还提供了官方示例算子 NormalMapExample.cs(位于Examples.Lib.image.transform命名空间),展示了该算子的引用与集成方式,可作为搭建自己演示工程的参考。
小结
NormalMap是 TiXL 图像处理链路中把"任意纹理"升级为"PBR 法线贴图"的关键桥梁:8 个输入参数覆盖了来源(LightMap)、强度(Impact)、细节尺度(SampleRadius)、方向(Twist)、输出格式与寻址方式,4 种 Mode 则分别面向常规渲染、带符号法线、方向场数据与通道打包四类需求。配合本文梳理的 NormalMap.hlsl 梯度差分算法与 SetMaterial.cs 的 SRV 挂载机制,你可以完全基于实时生成的纹理构建动态、可参数化驱动的法线细节,而不必依赖任何离线烘焙流程。
【免费下载链接】t3TiXL is an open source software to create realtime motion graphics.项目地址: https://gitcode.com/GitHub_Trending/t3/t3
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考