헤더파일

HLSL 프로그래밍 - 반구형 엠비언트 라이트 본문

DirectX

HLSL 프로그래밍 - 반구형 엠비언트 라이트

헤더파일 2018. 2. 28. 18:29

화면 전체에 똑같은 색상값을 준 그냥 엠비언트 라이트와는 달리 반구형 엠비언트 라이트는 위쪽과 아래쪽에서 오는 빛에 대응하는 두가지 색상에 기반을 두고 메시를 렌더링 합니다. 일부 빛은 광원에서 눈르로 바로 들어오지만 대부분 표면에서 반사돼 여러 방향에서 들어오며 표면의 재질과 색에 따라 그 빛의 경로도 조금씩 변하게 됩니다. 이렇게 빛이 표면에서 반사되어 보이는 현상을 산란이라 하고 반구형 엠비언트 라이트는 두번 이상 산란되어 보이는 빛을 보여주는 방식입니다.







LightManager 클래스


#pragma pack(push,1)

struct CB_AMBIENT

{

    D3DXVECTOR3 vAmbientLower;

float pad;

    D3DXVECTOR3 vAmbientRange;

float pad2;

};

#pragma pack(pop)


#pragma pack(push,1) , #pragma pack(pop) 은 구조체가 무조건 4바이트 단위로 맴버변수를 끝어서 구조체를 저장하는 걸 1바이트 단위로 끝으라고 이야기 하는 선언입니다. 하지만 모든 코드에서 1바이트 기준으로 처리하는건 32비트 명령체계에서 효율이 안 나올 수 있으므로 pop해서 다시 돌려줍니다.


vAmbientLower 아래쪽에서 오는 빛의 색상입니다.

vAmbientRange는 위쪽 색상 - 아래쪽 색상한 결과인데 위쪽 색깔을 안 넣고 이걸 넘겨주는 것은 계산을 절약할 수 있기 때문입니다.

DownColor * (1-a) + UpColor * a 는

DownColor + a * (UpColor - DownColor) 와 같습니다.


float pad, pad2 는 상수버퍼는 무조건 16바이트 단위로 정의되야 하기 때문에 그렇습니다.



void CLightManager::ForwardSetup(ID3D11DeviceContext* pd3dImmediateContext)

{

HRESULT hr;


pd3dImmediateContext->OMSetDepthStencilState(m_pForwardLightDS, 0);


// Fill the ambient values constant buffer

D3D11_MAPPED_SUBRESOURCE MappedResource;

V( pd3dImmediateContext->Map( m_pAmbientLightPixelCB, 0, D3D11_MAP_WRITE_DISCARD, 0, &MappedResource ) );

CB_AMBIENT* pAmbientValuesCB = ( CB_AMBIENT* )MappedResource.pData;

pAmbientValuesCB->vAmbientLower = GammaToLinear(m_vAmbientLowerColor);

pAmbientValuesCB->vAmbientRange = GammaToLinear(m_vAmbientUpperColor) - GammaToLinear(m_vAmbientLowerColor);

pd3dImmediateContext->Unmap( m_pAmbientLightPixelCB, 0 );


// Set the ambient values as the second constant buffer

pd3dImmediateContext->PSSetConstantBuffers( 1, 1, &m_pAmbientLightPixelCB );


// Set the vertex layout

pd3dImmediateContext->IASetInputLayout( m_pForwardLightVSLayout );


pd3dImmediateContext->VSSetShader(m_pForwardLightVertexShader, NULL, 0);

pd3dImmediateContext->PSSetShader(m_pAmbientLightPixelShader, NULL, 0);

}



D3D11_MAPPED_SUBRESOURCE MappedResource


D3D11_MAPPED_SUBRESOURCE 변수를 만들어서 상수버퍼에 값을 넣을 곳의 넣을 포인터를 가져옵니다.


V( pd3dImmediateContext->Map( m_pAmbientLightPixelCB, 0, D3D11_MAP_WRITE_DISCARD, 0, &MappedResource ) );

Map 함수로 상수버퍼에 락을 걸고 값을 쓸 준비를 합니다.


CB_AMBIENT* pAmbientValuesCB = ( CB_AMBIENT* )MappedResource.pData;


pAmbientValuesCB->vAmbientLower = GammaToLinear(m_vAmbientLowerColor);

pAmbientValuesCB->vAmbientRange = GammaToLinear(m_vAmbientUpperColor) - GammaToLinear(m_vAmbientLowerColor);


현재 LightManager의 상수 버퍼를 정의한 구조체로 캐스팅을 한후 값을 넣습니다.


pd3dImmediateContext->Unmap( m_pAmbientLightPixelCB, 0 );


값을 다 넣었으니 상수버퍼의 락을 해제 합니다.


  • 셰이더 파일


/////////////////////////////////////////////////////////////////////////////

// Constant Buffers

/////////////////////////////////////////////////////////////////////////////

cbuffer cbPerObjectVS : register( b0 ) // Model constants

{

    matrix  WorldViewProjection  : packoffset( c0 );

matrix  World : packoffset( c4 );

}


cbuffer cbDirLightPS : register( b1 ) // Ambient light constants

{

float3 AmbientDown : packoffset( c0 );

float3 AmbientRange : packoffset( c1 );

}


/////////////////////////////////////////////////////////////////////////////

// Diffuse texture and linear sampler

/////////////////////////////////////////////////////////////////////////////

Texture2D    DiffuseTexture : register( t0 );

SamplerState LinearSampler : register( s0 );


/////////////////////////////////////////////////////////////////////////////

// shader input/output structure

/////////////////////////////////////////////////////////////////////////////

struct VS_INPUT

{

    float4 Position : POSITION; // vertex position 

    float3 Normal : NORMAL; // vertex normal

    float2 UV : TEXCOORD0; // vertex texture coords 

};


struct VS_OUTPUT

{

    float4 Position : SV_POSITION; // vertex position 

    float2 UV : TEXCOORD0; // vertex texture coords

float3 Normal : TEXCOORD1; // vertex normal

};


/////////////////////////////////////////////////////////////////////////////

// Vertex shader

/////////////////////////////////////////////////////////////////////////////

VS_OUTPUT RenderSceneVS( VS_INPUT input )

{

    VS_OUTPUT Output;

    float3 vNormalWorldSpace;

    

    // Transform the position from object space to homogeneous projection space

    Output.Position = mul( input.Position, WorldViewProjection );

    

    // Just copy the texture coordinate through

    Output.UV = input.UV; 


Output.Normal = mul(input.Normal, (float3x3)World);

    

    return Output;    

}


/////////////////////////////////////////////////////////////////////////////

// Pixel shaders

/////////////////////////////////////////////////////////////////////////////


// Ambient calculation helper function

float3 CalcAmbient(float3 normal, float3 color)

{

// Convert from [-1, 1] to [0, 1]

float up = normal.y * 0.5 + 0.5;


// Calculate the ambient value

float3 ambient = AmbientDown + up * AmbientRange;


// Apply the ambient value to the color

return ambient * color;

}


float4 AmbientLightPS( VS_OUTPUT In ) : SV_TARGET0

    // Sample the texture and convert to linear space

    float3 DiffuseColor =  DiffuseTexture.Sample( LinearSampler, In.UV ).rgb;

DiffuseColor *= DiffuseColor;


// Calculate the ambient color

float3 AmbientColor = CalcAmbient(In.Normal, DiffuseColor);


// Return the ambient color

return float4(AmbientColor, 1.0);

}


정점셰이더는 입력받은 정점을 투영공간으로 변환하고 텍스쳐좌표는 그대로 전달합니다. 노말은 월드행렬을 곱해 월드공간에서의 노말로 변환하여 넘겨주는데 이는 빛의 위치가 월드공간에서 표현되어 있기 때문에 같은 공간에서 계산해야 합니다.


float3 DiffuseColor =  DiffuseTexture.Sample( LinearSampler, In.UV ).rgb;

DiffuseColor *= DiffuseColor;


DiffuseColor는 텍스쳐에서 텍셀을 얻어와서 구합니다. 제곱하는 이유는 텍스쳐의 색상은 감마공간에서 정의되어 있기 때문에 모니터가 인식하는 선형공간으로 변환해야 올바른 빛계산을 할 수 있기 때문입니다. (참고 https://docs.unity3d.com/kr/current/Manual/LinearLighting.html)


float3 CalcAmbient(float3 normal, float3 color)

{

// Convert from [-1, 1] to [0, 1]

float up = normal.y * 0.5 + 0.5;


// Calculate the ambient value

float3 ambient = AmbientDown + up * AmbientRange;


// Apply the ambient value to the color

return ambient * color;

}


반구형 엠비언트 라이트를 계산하는 함수로 노말과 선형공간의 색상값을 인자로 받습니다.


float up = normal.y * 0.5 + 0.5 ;


노말의 y축이 상하 축과 일치 했다고 가정했을 때 노멀의 y 값을 0~1 범위, 즉 색상 범위로 바꿉니다.


float3 ambient = AmbientDown + up * AmbientRange;


위에서 본 반구형 엠비언트라이트를 구하는 공식을 이용해 위 아래 빛을 적절히 혼합해 엠비언트 라이를 구하고 이 값을 color 값(텍셀)과 곱해 최종 픽셀 색깔을 결정합니다.


Comments