2017-01-06 61 views
0

我需要帮助,在我的游戏中组织着色器。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只依赖于所有渲染像素(或vert)具有相同值的着色器参数。为什么他们不能很快?我真的很想念他们。
  • 将此代码分成不同的着色器/技术/文件的最佳方式是什么?有没有好的标准或规则?

回答

0

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

思考:

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

事实:

HLSL-Documentation for if所述,有两种模式,如果,无论是flattenbranch。通过编译器flatten,编译器会翻出if的两侧,所以首先计算它们,然后从右侧获取结果。由于branch只执行右侧,因为首先评估布尔值,但只有在不使用像tex2D这样的任何渐变函数时才能使用此模式,因为它们依赖于相邻片段,因此需要在每个片段不得跳过。在你的情况下,我非常确定你正在使用这样的函数,所以编译器为你的ifs选择了flatten,这导致了一个完全执行和缓慢的着色器。

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

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

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

+0

不幸的是,我似乎无法按照自己的方式(动态编译),因为monogame不支持它,即使它只会限于某些平台。但这是我认可的伟大而有价值的答案 - 谢谢。 – Arek

+0

至于为什么'如果'很慢 - 我仍然不确定,但很快就会做一些复验来验证性能放缓。根据所有来源,它应该是快速的,因为对于所有呈现的东西,“if”条件是相同的。 – Arek