HLSL像素着色器照明性能(XNA)

时间:2013-03-07 17:29:46

标签: xna hlsl pixel-shader

我有一个足够简单的着色器,支持多个点光源 灯光存储为灯光结构阵列(最大尺寸),并在更改时传递活动灯光数量。
问题在于PixelShader功能:
这是基本的东西,从纹理中获取基色,循环通过灯光阵列0到numActiveLights并添加效果,它工作正常,但性能很糟糕!
但是,如果我用相同值的常量替换对全局var numActiveLights的引用,则性能很好 我无法理解为什么引用变量会产生30+ fps的差异。

有人可以解释一下吗?

完整着色器代码:

#define MAX_POINT_LIGHTS 16

struct PointLight
{
    float3      Position;
    float4      Color;
    float       Radius;
};

float4x4    World;
float4x4    View;
float4x4    Projection;
float3  CameraPosition;

float4  SpecularColor;
float   SpecularPower;
float   SpecularIntensity;
float4      AmbientColor;
float   AmbientIntensity;
float   DiffuseIntensity;   

int     activeLights;
PointLight  lights[MAX_POINT_LIGHTS];

bool    IsLightingEnabled;
bool    IsAmbientLightingEnabled;
bool    IsDiffuseLightingEnabled;
bool    IsSpecularLightingEnabled;


Texture Texture;
sampler TextureSampler = sampler_state
{
    Texture = <Texture>;

    Magfilter = POINT;
    Minfilter = POINT;
    Mipfilter = POINT;

    AddressU = WRAP;
    AddressV = WRAP;
};

struct VS_INPUT
{
    float4 Position : POSITION0;
    float2 TexCoord : TEXCOORD0;
    float3 Normal : NORMAL0;
};

struct VS_OUTPUT
{
    float3 WorldPosition : TEXCOORD0;
    float4 Position : POSITION0;
    float3 Normal : TEXCOORD1;
    float2 TexCoord : TEXCOORD2;
    float3 ViewDir : TEXCOORD3;

};

VS_OUTPUT VS_PointLighting(VS_INPUT input)
{
    VS_OUTPUT output;

    float4 worldPosition = mul(input.Position, World);
    output.WorldPosition = worldPosition;

    float4 viewPosition = mul(worldPosition, View);
    output.Position = mul(viewPosition, Projection);

    output.Normal = normalize(mul(input.Normal, World));
    output.TexCoord = input.TexCoord;
    output.ViewDir = normalize(CameraPosition -  worldPosition);

    return output;
}

float4 PS_PointLighting(VS_OUTPUT IN) : COLOR
{
    if(!IsLightingEnabled) return tex2D(TextureSampler,IN.TexCoord);

    float4 color = float4(0.0f, 0.0f, 0.0f, 0.0f);

    float3 n = normalize(IN.Normal);
    float3 v = normalize(IN.ViewDir);
    float3 l = float3(0.0f, 0.0f, 0.0f);
    float3 h = float3(0.0f, 0.0f, 0.0f);

    float atten = 0.0f;
    float nDotL = 0.0f;
    float power = 0.0f;

    if(IsAmbientLightingEnabled) color += (AmbientColor*AmbientIntensity);

    if(IsDiffuseLightingEnabled || IsSpecularLightingEnabled)
    {
        //for (int i = 0; i < activeLights; ++i)//works but perfoemnce is terrible
        for (int i = 0; i < 7; ++i)//performance is fine but obviously isn't dynamic
        {
            l = (lights[i].Position - IN.WorldPosition) / lights[i].Radius;
            atten = saturate(1.0f - dot(l, l));

            l = normalize(l);

            nDotL = saturate(dot(n, l));

            if(IsDiffuseLightingEnabled) color += (lights[i].Color * nDotL * atten);
            if(IsSpecularLightingEnabled) color += (SpecularColor * SpecularPower * atten);
        }
    }

    return color * tex2D(TextureSampler, IN.TexCoord);
}

technique PerPixelPointLighting
{
    pass
    {
        VertexShader = compile vs_3_0 VS_PointLighting();
        PixelShader = compile ps_3_0 PS_PointLighting();
    }
}

2 个答案:

答案 0 :(得分:2)

我的猜测是将循环约束更改为编译时常量允许HLSL编译器展开循环。也就是说,而不是:

for (int i = 0; i < 7; i++)
    doLoopyStuff();

它变成了这个:

doLoopyStuff();
doLoopyStuff();
doLoopyStuff();
doLoopyStuff();
doLoopyStuff();
doLoopyStuff();
doLoopyStuff();

循环和条件分支在着色器代码中可能是重要的性能影响,应尽可能避免。

修改

这只是我的头脑,但也许你可以试试这样的东西?

for (int i = 0; i < MAX_LIGHTS; i++)
{
    color += step(i, activeLights) * lightingFunction();
}

这样您可以计算所有可能的灯光,但对于非活动灯光,总是得到0值。当然,好处取决于照明功能的复杂程度;你需要做更多的分析。

答案 1 :(得分:1)

尝试使用PIX对其进行分析。 http://wtomandev.blogspot.com/2010/05/debugging-hlsl-shaders.html

或者,请阅读这种漫无边际的猜测:

也许因为使用常量,编译器可以解开并折叠循环的指令。当您用变量替换它时,编译器就无法做出相同的假设。

虽然与您的实际问题有点无关,但我会将大量条件/计算推向软件级别。

if(IsDiffuseLightingEnabled || IsSpecularLightingEnabled)

^ - 就像那样。

另外,我认为你也可以在调用着色器程序之前预先计算一些东西。像l = (lights[i].Position - IN.WorldPosition) / lights[i].Radius;一样,传递一个预先计算好的数组,而不是计算每个像素的每次。

我可能会误解HLSL编译器的优化,但我认为你在像素着色器上做的每个计算都会被执行屏幕w * h次(虽然这是疯狂地并行完成的),我依稀记得那里对着色器中可能有的指令数量有一些限制(如72?)。 (尽管我认为在更高版本的HLSL中限制已经自由化了)。也许你的着色器会生成如此多的指令 - 也许它会破坏你的程序并在编译时将其转换为多遍像素着色器。如果是这种情况,那可能会增加很多开销。

实际上,这是另一个可能是愚蠢的想法:将变量传递给着色器,它将数据传输到GPU。传输发生在带宽有限的情况下。也许编译器足够智能,当你只是静态索引数组中的前7个元素时,只传输7个元素。当编译器没有进行优化时(因为你没有使用常量进行迭代),它会每帧推送整个数组,并且你会泛滥总线。如果是这样的话,那么我之前提出的推算计算并传递更多结果的建议只会让问题变得更糟,呵呵。