2

複数のポイント ライトをサポートする十分に単純なシェーダーがあります。
ライトは Light 構造体の配列として (最大サイズまで) 格納され、変更されたときにアクティブなライトの数を渡します。
問題は 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();
    }
}
4

2 に答える 2

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 になります。もちろん、メリットは照明機能の複雑さに依存します。さらにプロファイリングを行う必要があります。

于 2013-03-07T18:53:04.670 に答える
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 つの要素のみを転送するほど十分にスマートです。コンパイラがその最適化を行わない場合 (定数を反復処理していないため)、フレームごとに WHOLE 配列をプッシュし、バスをあふれさせます。もしそうなら、計算を押し出し、より多くの結果を渡すという私の以前の提案は、問題を悪化させるだけです.

于 2013-03-07T19:05:11.200 に答える