HLSL - 组织着色器和技术而不迷路
HLSL - organizing shaders and techniques and not getting lost
我在组织游戏中的着色器方面需要帮助。
该游戏使用顶点和像素着色器进行纹理和照明。有些对象是有纹理的,有些只是着色的,然后有几种光照算法——光照贴图、漫反射和镜面光照、一些只有一些对象才能获得的阴影生成,等等。
我的第一个想法是拥有一个大而漂亮的着色器技术,它可以根据参数处理这一切。这很好维护(提供想法的伪代码):
float4 PixelShaderFunction(input)
{
if (UseTexture)
objectColor = tex2D(...)
else
objectColor = ObjectColor;
if (UseAmbient)
...
if (UseDiffuseLight)
...
// etc...
}
然而表现不佳。
然后我创建了多种技术来避免 if
分支,因此每种技术只使用特定对象需要的东西。因此,如果对象不接受阴影,则那里没有代码。如果它不产生镜面光,则没有代码。另外,我将常用功能分组为函数。像这样(现在是真正的代码):
float4 Textured_PixelShader(Textured_VsOut input) : COLOR0
{
float4 lightLevel = 0;
float4 objectColor = GetObjColorTex(input.TexUV);
// calculate main table lighting
PsAddTableLight(input.WorldPosition, input.Normal, lightLevel);
// calculate cloth lighting
PsAddClothLight(input.WorldPosition, input.Normal, lightLevel);
// calculate fill light - do we need it?
PsAddFillLight(input.Normal, lightLevel);
// add ambient light
PsAddAmbientLight(lightLevel);
// apply light
PsApplyDiffuseLight(lightLevel, objectColor);
// add speculars
PsAddSpecularBlurred(input.WorldPosition, input.Normal, objectColor);
// final work
return PsFinalize(objectColor);
}
性能提升巨大。
但是我在维护这个着色器时迷失了方向。每隔一天,我就需要添加一项新技术,因为还没有可以满足 texture + lightmap + this_kind_of_shadow + specular
或任何我需要的组合。它们中的一些得到这样的名字,其他的以它们所使用的对象命名,因为只有一个对象具有这样的组合。它变得一团糟。
我有两个问题:
- 为什么我不能有那些
if
语句?我读了很多关于条件执行如何伤害 GPU 的文章,但我的 ifs 只依赖于所有渲染像素(或顶点)具有相同值的着色器参数。为什么他们不能快?我真的很想念他们。
- 将这段代码分成不同的着色器/技术/文件的最佳方法是什么。有什么好的标准或规则吗?
Why cannot I have those if statements? I read a lot about how conditional execution hurts GPUs, but my ifs only depend on shader parameters which have same value for all rendered pixels (or verts). Why cannot they be fast? I really miss them.
想法:
主要问题在这里,编译器只看到一个布尔表达式,而不是你的变量是着色器常量的语义信息。这只是一种特殊情况并且可能会变得复杂,例如,如果您使用更复杂的布尔表达式和函数,它也只使用着色器常量。我认为为了防止着色器编译器开发人员的很多麻烦,他们选择让 if 尽可能通用。
事实:
如HLSL-Documentation for if
所述,if有两种模式,flatten
或branch
。使用 flatten
时,编译器会推出 if 的两边,因此首先计算它们,然后从右侧获取结果。使用 branch
时,只执行右侧,因为首先计算布尔值,但这种模式只能在您不使用任何梯度函数(如 tex2D)时使用,因为它们依赖于相邻片段,因此需要在每个片段上执行,不得跳过。在你的情况下,我很确定你正在使用这样的函数,所以编译器为你的 ifs 选择 flatten
,导致完全执行和缓慢的着色器。
What is the best way to divide this code into different shaders / techniques / files. Are there any good standards or rules?
据我所知,没有好的标准,但我认为像 Unity 或 Unreal 这样的引擎是深入了解它们如何管理着色器的好地方。上次我查看 Unreal 时,他们会在需要时动态生成每个着色器,因此着色器代码是从他们的着色器构建器生成的,然后进行编译。
在我自己的小引擎中,我使用了与您类似的方法,但我使用的不是动态分支,而是预处理器指令 #if, #elif, #else, and #endif
. If the engine encounters a needeed shaders, it sets the right defines and then compiles them on the fly with D3DCompile
。为了防止卡顿,我将编译好的着色器保存到光盘,在编译之前,我正在寻找着色器,如果它之前已经编译过的话。
我在组织游戏中的着色器方面需要帮助。
该游戏使用顶点和像素着色器进行纹理和照明。有些对象是有纹理的,有些只是着色的,然后有几种光照算法——光照贴图、漫反射和镜面光照、一些只有一些对象才能获得的阴影生成,等等。
我的第一个想法是拥有一个大而漂亮的着色器技术,它可以根据参数处理这一切。这很好维护(提供想法的伪代码):
float4 PixelShaderFunction(input)
{
if (UseTexture)
objectColor = tex2D(...)
else
objectColor = ObjectColor;
if (UseAmbient)
...
if (UseDiffuseLight)
...
// etc...
}
然而表现不佳。
然后我创建了多种技术来避免 if
分支,因此每种技术只使用特定对象需要的东西。因此,如果对象不接受阴影,则那里没有代码。如果它不产生镜面光,则没有代码。另外,我将常用功能分组为函数。像这样(现在是真正的代码):
float4 Textured_PixelShader(Textured_VsOut input) : COLOR0
{
float4 lightLevel = 0;
float4 objectColor = GetObjColorTex(input.TexUV);
// calculate main table lighting
PsAddTableLight(input.WorldPosition, input.Normal, lightLevel);
// calculate cloth lighting
PsAddClothLight(input.WorldPosition, input.Normal, lightLevel);
// calculate fill light - do we need it?
PsAddFillLight(input.Normal, lightLevel);
// add ambient light
PsAddAmbientLight(lightLevel);
// apply light
PsApplyDiffuseLight(lightLevel, objectColor);
// add speculars
PsAddSpecularBlurred(input.WorldPosition, input.Normal, objectColor);
// final work
return PsFinalize(objectColor);
}
性能提升巨大。
但是我在维护这个着色器时迷失了方向。每隔一天,我就需要添加一项新技术,因为还没有可以满足 texture + lightmap + this_kind_of_shadow + specular
或任何我需要的组合。它们中的一些得到这样的名字,其他的以它们所使用的对象命名,因为只有一个对象具有这样的组合。它变得一团糟。
我有两个问题:
- 为什么我不能有那些
if
语句?我读了很多关于条件执行如何伤害 GPU 的文章,但我的 ifs 只依赖于所有渲染像素(或顶点)具有相同值的着色器参数。为什么他们不能快?我真的很想念他们。 - 将这段代码分成不同的着色器/技术/文件的最佳方法是什么。有什么好的标准或规则吗?
Why cannot I have those if statements? I read a lot about how conditional execution hurts GPUs, but my ifs only depend on shader parameters which have same value for all rendered pixels (or verts). Why cannot they be fast? I really miss them.
想法:
主要问题在这里,编译器只看到一个布尔表达式,而不是你的变量是着色器常量的语义信息。这只是一种特殊情况并且可能会变得复杂,例如,如果您使用更复杂的布尔表达式和函数,它也只使用着色器常量。我认为为了防止着色器编译器开发人员的很多麻烦,他们选择让 if 尽可能通用。
事实:
如HLSL-Documentation for if
所述,if有两种模式,flatten
或branch
。使用 flatten
时,编译器会推出 if 的两边,因此首先计算它们,然后从右侧获取结果。使用 branch
时,只执行右侧,因为首先计算布尔值,但这种模式只能在您不使用任何梯度函数(如 tex2D)时使用,因为它们依赖于相邻片段,因此需要在每个片段上执行,不得跳过。在你的情况下,我很确定你正在使用这样的函数,所以编译器为你的 ifs 选择 flatten
,导致完全执行和缓慢的着色器。
What is the best way to divide this code into different shaders / techniques / files. Are there any good standards or rules?
据我所知,没有好的标准,但我认为像 Unity 或 Unreal 这样的引擎是深入了解它们如何管理着色器的好地方。上次我查看 Unreal 时,他们会在需要时动态生成每个着色器,因此着色器代码是从他们的着色器构建器生成的,然后进行编译。
在我自己的小引擎中,我使用了与您类似的方法,但我使用的不是动态分支,而是预处理器指令 #if, #elif, #else, and #endif
. If the engine encounters a needeed shaders, it sets the right defines and then compiles them on the fly with D3DCompile
。为了防止卡顿,我将编译好的着色器保存到光盘,在编译之前,我正在寻找着色器,如果它之前已经编译过的话。