HLSL - 组织着色器和技术,而不是迷路

时间:2017-01-06 09:24:12

标签: monogame hlsl

我需要帮助在游戏中组织着色器。

游戏使用顶点和像素着色器进行纹理和照明。有些物体是纹理的,有些只是有色的,然后有几种光照算法 - 光照贴图,漫反射和高光照明,一些只有一些物体得到的阴影生成等等。

我的第一个想法是拥有一个很棒的,漂亮的着色器技术,它可以根据参数处理所有这些。这很好维护(伪代码提供想法):

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仅依赖于着色器参数,这些参数对于所有渲染像素(或顶点)具有相同的值。为什么他们不能快?我真的很想念他们。
  • 将此代码划分为不同着色器/技术/文件的最佳方法是什么。有没有好的标准或规则?

1 个答案:

答案 0 :(得分:0)

  

为什么我不能拥有那些if语句?我阅读了很多关于条件执行如何伤害GPU的内容,但我的ifs仅依赖于着色器参数,这些参数对于所有渲染像素(或顶点)具有相同的值。为什么他们不能快?我真的很想他们。

思想:

主要问题在于,编译器只能看到一个布尔表达式,而不是您的变量是着色器常量的语义信息。它只是一个特殊情况,并且可能变得复杂,例如,如果您使用带有函数的更复杂的布尔表达式,它也只使用着色器常量。我认为为了防止着色器编译器开发人员的许多麻烦,他们选择让if尽可能通用。

事实:

正如HLSL-Documentation for if中所述,if有两种模式:flattenbranch。使用flatten,编译器会推出if的两侧,因此首先计算它们,然后从右侧获取结果。使用branch时,只执行右侧,因为首先计算布尔值,但只有在不使用任何梯度函数(如tex2D)时才能使用此模式,因为它们依赖于邻居片段,并且所以需要在每个片段上执行,不得跳过。在您的情况下,我非常确定您正在使用此类函数,因此编译器会为您的ifs选择flatten,从而导致完全执行且缓慢的着色器。

  

将此代码划分为不同着色器/技术/文件的最佳方法是什么。有没有好的标准或规则?

据我所知,没有好的标准,但我认为像Unity或Unreal这样的引擎是深入了解它们如何管理着色器的好地方。我最后一次查看Unreal时,他们会在需要时动态生成每个着色器,因此着色器代码是从其着色器构建器生成的,然后进行编译。

在我自己的小引擎中,我使用了与您类似的方法,但我没有使用动态分支,而是使用预处理程序指令#if, #elif, #else, and #endif。如果引擎遇到了一个needeed着色器,它会设置正确的定义,然后使用D3DCompile动态编译它们。为了防止口吃,我将已编译的着色器保存到光盘,在编译之前,我正在寻找着色器,如果之前已编译过。