我需要帮助在游戏中组织着色器。
游戏使用顶点和像素着色器进行纹理和照明。有些物体是纹理的,有些只是有色的,然后有几种光照算法 - 光照贴图,漫反射和高光照明,一些只有一些物体得到的阴影生成等等。
我的第一个想法是拥有一个很棒的,漂亮的着色器技术,它可以根据参数处理所有这些。这很好维护(伪代码提供想法):
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仅依赖于着色器参数,这些参数对于所有渲染像素(或顶点)具有相同的值。为什么他们不能快?我真的很想念他们。答案 0 :(得分:0)
为什么我不能拥有那些if语句?我阅读了很多关于条件执行如何伤害GPU的内容,但我的ifs仅依赖于着色器参数,这些参数对于所有渲染像素(或顶点)具有相同的值。为什么他们不能快?我真的很想他们。
思想:
主要问题在于,编译器只能看到一个布尔表达式,而不是您的变量是着色器常量的语义信息。它只是一个特殊情况,并且可能变得复杂,例如,如果您使用带有函数的更复杂的布尔表达式,它也只使用着色器常量。我认为为了防止着色器编译器开发人员的许多麻烦,他们选择让if尽可能通用。
事实:
正如HLSL-Documentation for if
中所述,if有两种模式:flatten
或branch
。使用flatten
,编译器会推出if的两侧,因此首先计算它们,然后从右侧获取结果。使用branch
时,只执行右侧,因为首先计算布尔值,但只有在不使用任何梯度函数(如tex2D)时才能使用此模式,因为它们依赖于邻居片段,并且所以需要在每个片段上执行,不得跳过。在您的情况下,我非常确定您正在使用此类函数,因此编译器会为您的ifs选择flatten
,从而导致完全执行且缓慢的着色器。
将此代码划分为不同着色器/技术/文件的最佳方法是什么。有没有好的标准或规则?
据我所知,没有好的标准,但我认为像Unity或Unreal这样的引擎是深入了解它们如何管理着色器的好地方。我最后一次查看Unreal时,他们会在需要时动态生成每个着色器,因此着色器代码是从其着色器构建器生成的,然后进行编译。
在我自己的小引擎中,我使用了与您类似的方法,但我没有使用动态分支,而是使用预处理程序指令#if, #elif, #else, and #endif
。如果引擎遇到了一个needeed着色器,它会设置正确的定义,然后使用D3DCompile
动态编译它们。为了防止口吃,我将已编译的着色器保存到光盘,在编译之前,我正在寻找着色器,如果之前已编译过。