헤더파일

HLSL 프로그래밍 - Forward Lighting 종합 본문

DirectX

HLSL 프로그래밍 - Forward Lighting 종합

헤더파일 2018. 3. 5. 18:07
ForwardLightCommon.hlsl


cbuffer cbPerObjectVS : register( b0 )
{
float4x4 WorldViewProjection    : packoffset( c0 );
    float4x4 World                  : packoffset( c4 );
}

register는 상수버퍼와 리소들을 등록하는 시멘틱입니다. 숫자는 옵션입니다.
b - 상수버퍼
t - 텍스쳐
s - 샘플러

packoffset은 한 번에 부동소수점 4개를 처리할 수 있는 gpu를 고려해 상수버퍼의 맴버들을 묶는 옵션입니다.
float3 과 float 을 이 옵션으로 묶을 수 있습니다.

cbuffer cbPerObjectPS : register( b0 )
{
float3 EyePosition  : packoffset( c0 );
    float specExp       : packoffset( c0.w );
    float specIntensity : packoffset( c1 );
}

정반사 강도, 정반사 확산 범위, 카메라 위치를 입력 받습니다.

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
    float3 WorldPos : TEXCOORD2;    // vertex world position
};

기본적으로 위치, 노멀, uv를 정점데이터로 입력받고 픽셀 셰이더에는 투영공간에서의 정점, uv 월드공간에서의 노멀,
월드공간에서의 정점을 넘겨줍니다.

float4 DepthPrePassVS(float4 Position : POSITION) : SV_POSITION
{
    return mul( Position, WorldViewProjection );
}

z좌표를 미리 알아서 뎁스 스텐실 버퍼에 씀으로서 픽셀 셰이더에서 가려진 픽셀을 거를 수 있게 됩니다.

VS_OUTPUT RenderSceneVS( VS_INPUT input )
{
VS_OUTPUT Output;
float3 vNormalWorldSpace;
Output.Position = mul( input.Position, WorldViewProjection );
    // Transform the position to world space
    Output.WorldPos = mul(input.Position, World).xyz;

// Just copy the texture coordinate through
Output.UV = input.UV;

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


픽셀셰이더로 값을 주기 위한 변환 과정입니다.

struct Material
{
float3 normal;
float4 diffuseColor;
float specExp;
float specIntensity;
};

Material PrepareMaterial(float3 normal, float2 UV)
{
    Material material;

    // Normalize the interpulated vertex normal
    material.normal = normalize(normal);

    // Sample the texture and convert to linear space
material.diffuseColor = DiffuseTexture.Sample( LinearSampler, UV );
    material.diffuseColor.rgb *= material.diffuseColor.rgb;

    // Copy the specular values from the constant buffer
    material.specExp = specExp;
    material.specIntensity = specIntensity;

    return material;
}

조명계산을 쉽게 하기 위한 머테리얼 구조체를 만듭니다.
보간기를 거쳐나오면서 값이 흐뜨러 졌을 노멀을 다시 정규화하고, uv좌표로 디퓨즈맵의 텍셀을 구합니다.


DirectionalLight.hlsl


cbuffer cbDirLightPS : register( b1 )
{
    float3 AmbientDown      : packoffset( c0 );
    float3 AmbientRange     : packoffset( c1 );
    float3 DirToLight       : packoffset( c2 );
    float3 DirLightColor    : packoffset( c3 );
}


픽셀 셰이더에서 쓸 상수버퍼입니다.

  • 반구형 엠비언트 라이트를 위한 아래쪽 색깔과 색깔 범위
  • 디렉셔널 라이트 계산을 위한 입사광 벡터와 입사광 색깔
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;
}


엠비언트라이트 계산입니다.

색상값은 0~1이므로 노멀 값을 색상값 범위로 변경합니다. 노멀의 y좌표를 상하축이랑 일치한다고 가정하고 계산하는 것으로 다르다면 바꿔야합니다. 이 숫자 값으로 위 색깔이 얼마나 영향을 줄지 결정 합니다.

AmbientDown * (1 - a) + AmbientUp * a =  AmbientDown + ( AmbientUp - AmbientDown) * a

계산을 줄이기 위해 ( AmbientUp - AmbientDown) 을 AmbientRange로 입력 받습니다.



float3 CalcDirectional(float3 position, Material material)
{
// Phong diffuse
float NDotL = dot(DirToLight, material.normal);
float3 finalColor = DirLightColor.rgb * saturate(NDotL);


// Blinn specular
float3 ToEye = EyePosition.xyz - position;
ToEye = normalize(ToEye);
float3 HalfWay = normalize(ToEye + DirToLight);
float NDotH = saturate(dot(HalfWay, material.normal));
finalColor += DirLightColor.rgb * pow(NDotH, material.specExp)
* material.specIntensity;
return finalColor * material.diffuseColor.rgb;
}


퐁의 난반사 공식에 따르면 난반사광의 양은 입사광 벡터와 법선 벡터의 코사인 값과 같습니다. 벡터는 단위벡터로 입력받아 내적을 이용해 난반사광의 양을 구합니다.


블린의 정반사광 공식은 반사 벡터를 쉽게 계산하는 공식입니다. 반사벡터는 계산하는데 많은 리소스가 필요하므로 카메라 위치에서 현재 정점위치를 빼서 카메라 벡터를 구합니다. 


카메라벡터와 입사광벡터를 더해 중간값벡터를 구합니다. 중간값벡터와 법선벡터의 코사인 값을 구하면 카메라 벡터와 반사벡터로 구하는 정반사광 양의 근사값을 구할 수 있습니다.


정반사광의 양에 제곱을 하는 이유는 정반사광은 난반사광보다 더 좁은 영역을 밝게 비추기 때문입니다. specExp를 올릴 수록 정반사광이 비추는 범위는 줄어듭니다. 입사광 색깔 * 계수 * 강도를 계산해 최종 정반사광을 계산합니다.


float4 DirectionalLightPS( VS_OUTPUT In ) : SV_TARGET0
{
    // Prepare the material structure
    Material material = PrepareMaterial(In.Normal, In.UV);

    // Calculate the ambient color
    float3 finalColor = CalcAmbient(material.normal,
material.diffuseColor.rgb);
    // Calculate the directional light
    finalColor += CalcDirectional(In.WorldPos, material);

    // Return the final color
    return float4(finalColor, 1.0);
}

 

픽셀셰이더는 간단합니다. 메터리얼 구조체를 만들고 엠비언트 라이트와 디렉셔널 라이트를 더해서 최종 픽셀을 구합니다. 카메라 벡터는 월드공간 기준으로 계산해야 하므로 월드공간에서의 좌표를 넘겨줍니다.


FourLight.hlsl


cbuffer FourLightsConstants : register( b1 )
{
    float4 LightPosX            : packoffset( c0 );
    float4 LightPosY            : packoffset( c1 );
    float4 LightPosZ            : packoffset( c2 );
    float4 LightDirX            : packoffset( c3 );
    float4 LightDirY            : packoffset( c4 );
    float4 LightDirZ            : packoffset( c5 );
    float4 LightRangeRcp        : packoffset( c6 );
    float4 SpotCosOuterCone     : packoffset( c7 );
    float4 SpotCosInnerConeRcp  : packoffset( c8 );
    float4 CapsuleLen           : packoffset( c9 );
    float4 LightColorR          : packoffset( c10 );
    float4 LightColorG          : packoffset( c11 );
    float4 LightColorB          : packoffset( c12 );
}


네 개의 조명을 한번에 처리하기 위해 그래픽카드는 부동소수점을 4개 동시에 처리한다는 걸 이용합니다. 조명 위치, 조명 방향, 조명 색깔은 각 요소를 따로 4개씩 보관합니다. (ex)X좌표 4개씩)

그 외에 다른 상수들은 단일 값이므로 단일 변수에 4개 보관합니다.


각 변수가 없을 때 값

CapsuleLen  0

SpotCosOuterCone -2

SpotCosInnerCone 1


float3 CalcFourLights(float3 position, Material material)
{
    float3 ToEye = EyePosition.xyz - position;


카메라 벡터를 구해줍니다.


    float4 ToCapsuleStartX = position.xxxx - LightPosX;
    float4 ToCapsuleStartY = position.yyyy - LightPosY;
    float4 ToCapsuleStartZ = position.zzzz - LightPosZ;
    float4 DistOnLine = dot4x4(ToCapsuleStartX, ToCapsuleStartY, ToCapsuleStartZ, LightDirX, LightDirY, LightDirZ);
    float4 CapsuleLenSafe = max(CapsuleLen, 1.e-6);
    DistOnLine = CapsuleLen * saturate(DistOnLine / CapsuleLenSafe);
    float4 PointOnLineX = LightPosX + LightDirX * DistOnLine;
    float4 PointOnLineY = LightPosY + LightDirY * DistOnLine;
    float4 PointOnLineZ = LightPosZ + LightDirZ * DistOnLine;
    float4 ToLightX = PointOnLineX - position.xxxx;
    float4 ToLightY = PointOnLineY - position.yyyy;
    float4 ToLightZ = PointOnLineZ - position.zzzz;
    float4 DistToLightSqr = dot4x4(ToLightX, ToLightY, ToLightZ, ToLightX, ToLightY, ToLightZ);
    float4 DistToLight = sqrt(DistToLightSqr);
    // Phong diffuse
    ToLightX /= DistToLight; // Normalize
    ToLightY /= DistToLight; // Normalize
    ToLightZ /= DistToLight; // Normalize
    float4 NDotL = saturate(dot4x1(ToLightX, ToLightY, ToLightZ, material.normal));
    //float3 finalColor = float3(dot(LightColorR, NDotL), dot(LightColorG, NDotL), dot(LightColorB, NDotL));
    // Blinn specular
    ToEye = normalize(ToEye);
    float4 HalfWayX = ToEye.xxxx + ToLightX;
    float4 HalfWayY = ToEye.yyyy + ToLightY;
    float4 HalfWayZ = ToEye.zzzz + ToLightZ;
    float4 HalfWaySize = sqrt(dot4x4(HalfWayX, HalfWayY, HalfWayZ,
HalfWayX, HalfWayY, HalfWayZ));
    float4 NDotH = saturate(dot4x1(HalfWayX / HalfWaySize, HalfWayY
/ HalfWaySize, HalfWayZ / HalfWaySize, material.normal));
    float4 SpecValue = pow(NDotH, material.specExp.xxxx) * material.specIntensity;
    
    // Cone attenuation
    float4 cosAng = dot4x4(LightDirX, LightDirY, LightDirZ, ToLightX,
ToLightY, ToLightZ);
    float4 conAtt = saturate((cosAng - SpotCosOuterCone) *
SpotCosInnerConeRcp);
    conAtt *= conAtt;
    // Attenuation
    float4 DistToLightNorm = 1.0 - saturate(DistToLight * LightRangeRcp);
    float4 Attn = DistToLightNorm * DistToLightNorm;
    Attn *= conAtt; // Include the cone attenuation

    // Calculate the final color value
    float4 pixelIntensity = (NDotL + SpecValue) * Attn;
    float3 finalColor = float3(dot(LightColorR, pixelIntensity),
dot(LightColorG, pixelIntensity), dot(LightColorB, pixelIntensity));

    finalColor *= material.diffuseColor;
    return finalColor;
}


finalColor에서 내적한 것은 색상값을 픽셀당 색상표현 강도에 투영해 얼마큼의 강도를 가지는지 보기 위함입니다.


float4 dot4x4(float4 aX, float4 aY, float4 aZ, float4 bX, float4 bY, float4 bZ)
{
    return aX * bX + aY * bY + aZ * bZ;
}

float4 dot4x1(float4 aX, float4 aY, float4 aZ, float3 b)
{
    return aX * b.xxxx + aY * b.yyyy + aZ * b.zzzz;
}


8개의 벡터를 동시에 내적할 수 있는 함수와 4개의 벡터와 1개의 벡터를 내적하는 함수를 만들어줍니다. 

Comments