2013-03-07 73 views
2

我有一個足夠簡單的着色器,支持多個點光源。
燈光存儲爲一個Light結構陣列(達到最大尺寸),當燈光發生變化時,我傳入活動燈光的數量。
問題是在PixelShader函數中:
它是基本的東西,從紋理中獲取基色,通過燈陣列0循環到numActiveLights並添加效果,並且它工作正常,但性能很糟糕!
但如果我用同一個值的常量替換全局var numActiveLights的引用性能很好。
我只是無法理解爲什麼引用該變量會產生30+ fps的差異。HLSL像素着色器照明性能(XNA)

任何人都可以解釋一下嗎?

完全Shader代碼:

#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

我的猜測是,改變環路約束是一個編譯時間常數允許HLSL編譯器展開循環。也就是說,不是這樣的:

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

它越來越變成了這樣:

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

循環和條件分支可以是一個顯著的表現打了shader代碼內,並應儘可能避免。

編輯

這只是從我的頭頂,但也許你可以嘗試這樣的事情?

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

這樣你計算所有可能的燈,但始終不活躍燈得到的值爲0。當然,好處將取決於照明功能的複雜性;你需要做更多的分析。

+0

這看起來像它。 我仍然覺得很難相信,但是如果(activeLights> 0)color + = [light function],我用一個負載代替了一個很好的緊湊環: ;如果(activeLights> 1)color + = [light function]; if(activeLights> 2)color + = [light function]; ... 對於最大數量的燈光和表現回來劃傷:/ 希望我能找到一些好的表現和代碼的中間地帶,我不會覺得羞愧的顯示某人的代碼。 – DFreeman 2013-03-08 11:54:59

+0

看到我的編輯一種可能性。 – 2013-03-08 14:44:27

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個元素。當編譯器沒有進行優化(因爲你沒有使用常量進行迭代)時,它會每幀推動整個數組,並且你正在充斥着總線。如果是這樣的話,那麼我先前提出的推算計算結果並傳遞更多結果的建議只會使問題變得更糟,嘿。

+0

關於在像素着色器中預先計算一些昂貴的計算的優秀技巧,毫無疑問,這絕對是我的問題的一部分,謝謝。 – DFreeman 2013-03-08 12:02:06