1、首先我们要去了解custom hlsl的include file paths

Include File Paths和 C/C++ 的#include指令功能相同,用于在编译当前材质的 HLSL 代码时,将指定的文件内容"粘贴"进来。
include file 的内容:
#pragma once
#ifndef IS_BASE_PASS
#define IS_BASE_PASS 0
#endif
#ifndef SHADING_PATH_DEFERRED
#define SHADING_PATH_DEFERRED 0
#endif
#ifndef MATERIALBLENDING_ANY_TRANSLUCENT
#define MATERIALBLENDING_ANY_TRANSLUCENT 0
#endif
#define GM_BASEPASS_FORWARD_LIGHT_ACCESS (IS_BASE_PASS && SHADING_PATH_DEFERRED)
#if GM_BASEPASS_FORWARD_LIGHT_ACCESS
#ifndef ForwardLightStruct
#define GM_DEFINED_FORWARD_LIGHT_STRUCT 1
#if MATERIALBLENDING_ANY_TRANSLUCENT
#define ForwardLightStruct TranslucentBasePass.Shared.Forward
#else
#define ForwardLightStruct OpaqueBasePass.Shared.Forward
#endif
#else
#define GM_DEFINED_FORWARD_LIGHT_STRUCT 0
#endif
#include "/Engine/Private/LightGridCommon.ush"
#endif
uint GM_GetBasePassForwardEyeIndex(FMaterialPixelParameters Parameters)
{
#if MOBILE_MULTI_VIEW
return GetViewId(Parameters);
#else
// 当前材质参数里没有直接等价于 BasePassPixelShader.usf 中 EyeIndex 的通用字段。
// 非 multiview / 非 instanced stereo 路径用 0。
return 0;
#endif
}
uint GM_GetForwardLightGridIndex(FMaterialPixelParameters Parameters)
{
#if GM_BASEPASS_FORWARD_LIGHT_ACCESS && FEATURE_LEVEL >= FEATURE_LEVEL_SM5
const uint EyeIndex = GM_GetBasePassForwardEyeIndex(Parameters);
const uint2 PixelPos = (uint2)((Parameters.SvPosition.xy - ResolvedView.ViewRectMin.xy) * View.LightProbeSizeRatioAndInvSizeRatio.zw);
return ComputeLightGridCellIndex(PixelPos, Parameters.SvPosition.w, EyeIndex);
#else
return 0;
#endif
}
#if GM_BASEPASS_FORWARD_LIGHT_ACCESS && defined(GM_DEFINED_FORWARD_LIGHT_STRUCT) && GM_DEFINED_FORWARD_LIGHT_STRUCT
#undef ForwardLightStruct
#endif
#ifdef GM_DEFINED_FORWARD_LIGHT_STRUCT
#undef GM_DEFINED_FORWARD_LIGHT_STRUCT
#endif
写入gbuffer的流程就是BasePass
|------------------------------------|------------------------------------------------|
| IS_BASE_PASS | 当前是否在 BasePass 中渲染 |
| SHADING_PATH_DEFERRED | 当前是否使用延迟渲染路径 |
| MATERIALBLENDING_ANY_TRANSLUCENT | 当前材质是否是透明材质 |
| GM_BASEPASS_FORWARD_LIGHT_ACCESS | 最终开关:是否在延迟渲染的 BasePass 中允许访问 Forward+ 灯光网格 |
#if GM_BASEPASS_FORWARD_LIGHT_ACCESS
#ifndef ForwardLightStruct
#define GM_DEFINED_FORWARD_LIGHT_STRUCT 1
#if MATERIALBLENDING_ANY_TRANSLUCENT
#define ForwardLightStruct TranslucentBasePass.Shared.Forward
#else
#define ForwardLightStruct OpaqueBasePass.Shared.Forward
#endif
#else
#define GM_DEFINED_FORWARD_LIGHT_STRUCT 0
#endif
#include "/Engine/Private/LightGridCommon.ush"
#endif
-
如果开关开启,且
ForwardLightStruct没有被外部定义过,就定义它 -
根据材质类型(透明 / 不透明),指向不同的 Uniform Buffer
-
Uniform Buffer 是 CPU 传递给 GPU 的数据块,这里包含灯光网格的索引、光源列表等
-
然后包含
LightGridCommon.ush------这个文件里定义了ComputeLightGridCellIndex、GetForwardLightingDataFromGrid等核心函数
在材质编辑器的 Custom 节点中,HLSL 代码会被封装在一个函数里。这种"隔离"环境限制了你直接访问很多引擎内部功能,而这些功能通过 #include 相应的头文件就能解锁。
当你通过 Include File Paths 引入一个头文件时,就相当于把那个文件的全部内容"粘贴"到了 Custom 节点的代码中,从而获得了访问其中所有声明的能力。这正是你突破 Custom 节点限制的关键。例如,通过包含 /Engine/Private/LightGridCommon.ush,你可以:
-
调用内部函数 :你可以使用
ComputeLightGridCellIndex()来获取当前像素的灯光网格索引,或者使用GetLocalLightDataFromGrid()来获取光源数据。这些函数是引擎内部多光源算法的核心,通常无法直接调用。 -
获取核心数据结构 :可以访问
ForwardLightStruct这样的 Uniform Buffer。这个结构体里存储着所有经过剔除(Culling)的光源列表,是 Forward+ 渲染的核心数据。 -
引用全局变量 :除了
View.XXX,还能访问ResolvedView等更丰富的渲染状态变量。
float3 Sum = 0.0.xxx;
#if GM_BASEPASS_FORWARD_LIGHT_ACCESS && FEATURE_LEVEL >= FEATURE_LEVEL_SM5
const uint GridIndex = GM_GetForwardLightGridIndex(Parameters);
const FCulledLightsGridHeader Header = GetCulledLightsGridHeader(GridIndex);
const uint NumLights = min(Header.NumLights, GetMaxLightsPerCell());
const uint EyeIndex = GM_GetBasePassForwardEyeIndex(Parameters);
LOOP
for (uint LightIndex = 0; LightIndex < NumLights; ++LightIndex)
{
const FLocalLightData LocalLight = GetLocalLightDataFromGrid(Header.DataStartIndex + LightIndex, EyeIndex);
float3 LightPos = UnpackLightTranslatedWorldPosition(LocalLight);
float3 LightDir = normalize(LightPos - GetTranslatedWorldPosition(Parameters));
Sum += saturate(dot(LightDir,WorldNormal));
}
#endif
return Sum;
-
判断是否可用 :首先检查
GM_BASEPASS_FORWARD_LIGHT_ACCESS和FEATURE_LEVEL >= FEATURE_LEVEL_SM5,确保当前渲染环境支持访问 Forward+ 灯光网格数据。 -
获取当前像素的光源列表:
-
GridIndex = GM_GetForwardLightGridIndex(Parameters):根据像素位置计算它在 3D 灯光网格中的格子索引 -
Header = GetCulledLightsGridHeader(GridIndex):获取该格子包含的光源列表元数据(起始位置、数量等) -
NumLights = min(Header.NumLights, GetMaxLightsPerCell()):获取实际需要处理的光源数量
-
-
遍历所有影响该像素的光源:
-
对于每个光源,获取其完整属性数据(位置、颜色、半径等)
-
将光源位置从编码格式解包到世界空间
-
计算从像素指向光源的方向向量
-
通过
saturate(dot(LightDir, WorldNormal))计算兰伯特漫反射强度(光线与法线的夹角余弦值,限制在 0-1 范围)
-
-
输出结果:将所有光源的漫反射强度累加后返回,这个值可以连接到材质的自发光或基础颜色等引脚。
以上是HLSL代码,其中GM_BASEPASS_FORWARD_LIGHT_ACCESS是include file path的文件里面定义的宏

FEATURE_LEVEL 是 UE5 着色器系统中的一个全局宏 。它并不是一个普通的变量,而是在着色器编译时,由引擎根据当前渲染平台的功能级别预定义并注入到代码中的。
GM_GetForwardLightGridIndex(Parameters);
是include file path的文件BasePassForwardLightGrid.ush声明的自定义函数
GetCulledLightsGridHeader
GetCulledLightsGridHeader和 FCulledLightsGridHeader 在BasePassForwardLightGrid.ush文件中的LightGridCommon.ush文件下:


GM_GetBasePassForwardEyeIndex(Parameters);
是include file path的文件BasePassForwardLightGrid.ush声明的自定义函数
FLocalLightData
是include file path的文件BasePassForwardLightGrid.ush里面包含了的头文件:
#include "/Engine/Private/LightGridCommon.ush"

头文件包含了LightData.ush

LightData.ush里面定义了FLocalLightData
0.0.xxx 是 HLSL 中的一种 swizzle(分量重组) 写法,意思是:将标量 0.0 当作一个只有 x 分量的向量,然后通过 .xxx 将其复制三份,构成一个 float3 向量,效果等同于 float3(0.0, 0.0, 0.0) 或 0.0.xxx。
在 HLSL 中,任何标量(如 0.0)都可以隐式地当作一个只有 .x 分量的向量,因此可以像向量一样使用 .x、.y、.z、.w 等 swizzle 操作。例如:
-
(0.0).xxx→float3(0,0,0) -
(1.0).xxy→float3(1,1,0) -
(2.5).x→ 还是2.5(标量)
所以 0.0.xxx 实际上等价于 (0.0).xxx。由于编译器允许省略括号,就写成了 0.0.xxx。
struct FCulledLightsGridHeader
{
uint NumLights;
uint NumMegaLights;
uint DataStartIndex;
uint MegaLightsDataStartIndex;
bool bHasRectLight;
bool bHasTexturedLight;
};
bHasTexturedLight:"纹理光源"广义上指其光照属性由纹理决定的光源。在UE中,这通常指使用了光源函数(Light Function) 的光源。通过将材质应用到光源上,可以创造出类似水底焦散、云层阴影或霓虹灯图案等丰富的光照效果。此外,它也可以指使用IES描述文件的光源,以模拟真实灯光的分布曲线。