2013-03-07 5 views
2

여러 점 광원을 지원하는 쉐이더가 간단합니다.
라이트는 라이트 구조체의 배열 (최대 크기까지)로 저장되며 변경 될 때 활성 라이트 수를 전달합니다.
PixelShader 함수에 문제가 있습니다 :
기본 요소입니다. 텍스처에서 기본 색상을 가져오고 numActiveLights에 0을위한 광원 배열을 반복하고 효과를 추가하면 효과는 있지만 성능은 끔찍합니다!
그러나 전역 var numActiveLights에 대한 참조를 동일한 값의 상수로 바꾸면 성능이 좋습니다.
왜 변수를 참조하면 30+ fps 차이가 나는지 알 수 없습니다.HLSL 픽셀 쉐이더 조명 성능 (XNA)

아무도 설명해 주시겠습니까?

전체 쉐이더 코드 :

#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(); 

루프와 조건 분기가 쉐이더 코드의 내부 충돌 성능이 크게 될 수 있으며, 가능하면 피해야한다.

편집

이건 그냥 내 머리 위로 떨어져이지만, 어쩌면 당신이 뭔가를 시도 할 수?

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

이렇게하면 가능한 모든 표시등을 계산하지만 비활성 표시등은 항상 0으로 표시됩니다. 물론 이점은 조명 기능의 복잡성에 달려 있습니다. 당신은 더 많은 프로파일 링을해야 할 것입니다.

+0

이렇게 보입니다. 나는 아직도 믿기 어렵다. 그러나 루프의 멋진 콤팩트를 다음과 같은로드로 대체했다 : if (activeLights> 0) color + = [light function]; if (activeLights> 1) color + = [조명 기능]; if (activeLights> 2) color + = [조명 기능]; ... 최대 조명 수와 성능이 처음으로 돌아갔습니다./ 좋은 성능과 코드의 중간 지점을 찾고 누군가를 보여주기가 부끄럽지 않기를 바랍니다. – DFreeman

+0

한 가지 가능성에 대해 제 편집을 참조하십시오. –

1

프로필로 PIX를 사용해보세요.

은 아마 상수 때문에, 컴파일러는 해명과 루프의 지시를 축소 할 수 있습니다 :

http://wtomandev.blogspot.com/2010/05/debugging-hlsl-shaders.html

는 다른 방법으로,이 두서 추측을 읽어 보시기 바랍니다. 변수로 바꾸면 컴파일러는 동일한 가정을 할 수 없게됩니다.

실제 질문과는 다소 관련이 없지만 많은 조건/계산을 소프트웨어 수준으로 밀어 넣었습니다.

if(IsDiffuseLightingEnabled || IsSpecularLightingEnabled)

^- 그처럼.

또한 셰이더 프로그램을 호출하기 전에 몇 가지 사항을 미리 계산할 수 있다고 생각합니다. like l = (lights[i].Position - IN.WorldPosition)/lights[i].Radius; 매 픽셀마다 매번 계산하지 않고 사전 계산 된 배열을 전달합니다.

HLSL 컴파일러가 수행하는 최적화에 대해 잘못된 정보를 얻을 수도 있지만 픽셀 쉐이더에서 수행하는 각 계산이 화면 w * h 번 실행된다고 생각합니다. (이것은 평행하게 수행되었지만) 나는 막연하게 기억합니다. 셰이더 (72와 같은)에서 가질 수있는 명령어의 수에 제한이 있습니다. (비록 제한이 HLSL의 상위 버전에서 많이 자유화되었다고 생각하지만). 어쩌면 셰이더가 많은 명령어를 생성합니다. 아마도 프로그램을 깨뜨리고 컴파일 할 때 다중 패스 픽셀 쉐이더로 변환 할 수 있습니다. 그렇다면 상당한 비용이 추가 될 것입니다.

실제로 바보 같을 수도있는 또 다른 아이디어가 있습니다. 변수를 셰이더에 전달하면 데이터가 GPU로 전송됩니다. 이 전송은 제한된 대역폭으로 발생합니다. 아마도 컴파일러는 똑똑해서 배열의 처음 7 개 요소를 정적으로 인덱싱 할 때 7 개 요소 만 전송할 수 있습니다. 컴파일러가 (당신이 상수로 반복하지 않기 때문에) 최적화를하지 않으면, 그것은 모든 프레임을 모든 프레임으로 푸시합니다. 그러면 여러분은 버스를 넘치게됩니다. 그렇다면 계산을 밀어 내고 더 많은 결과를 전달한다는 내 제안은 문제를 악화시킬뿐입니다.

+0

픽셀 쉐이더에서 값 비싼 계산을 사전 계산하는 데 유용한 팁, 의심 할 여지없이 이것은 내 문제의 일부분입니다. 감사합니다. – DFreeman