본문 바로가기
게임 개발/[D3D_Portfolio] DirectX3D 팀 포트폴리오 작업일지

14. HDR 구현 및 림 라이트 구현

by 헛둘이 2023. 8. 18.

HDR 구현

- HDR이란 화면 픽셀들의 평균 휘도 값을 구해서 LDR에서 표현할 수 없는 색의 범위를 표현할 수 있게 해주는 기술이다.

- 우리 게임에서 HDR을 구현하는 이유는 색의 표현 범위를 넓혀서 화면 후처리 기술들을 돋보이게 하기 위함이다.

- HDR은 총 세 단계의 과정을 거치는데, 그 과정은 아래와 같다.

 

1. 다운스케일 - 다운 스케일을 하는 이유는 이후 블러나 블룸 효과를 넣을 때 성능 향상을 위한 것도 있지만,

- 가장 중요한 목적은 HDR에 꼭 필요한 평균 휘도값을 구하는 것

- DownScale4to4, DownScale1024to4, DownScale4to1 등과 같은 함수들을 통해  다운스케일을 진행하고 평균 휘도값을 계산한다.

- 평균 휘도값을 구할 때 LUM_FACTOR라는 상수값을 이용해서 각 R, G, B 색상에 지정된 값을 곱해주는데, 각각의 값이 사람이 보기에 느끼는 밝기가 다르기 때문이라고 한다.

- 이 모든 과정은 컴퓨트 셰이더로 진행된다.

평균 휘도값을 왜 구해야 하는가?

- 예를 들어 LDR에서 어떤 물체에 강한 빛을 쏘는 경우 물체가 흐릿하게 보이는 등 세부 정보가 날아가는 현상이 생긴다.

- 이 때 평균 휘도값을 사용해서 너무 밝은 부분은 어둡게 조정하고, 너무 어두운 부분은 밝게 조정하는 식으로 화면의 전체적인 균형을 유지할 수 있다.

 

2. 블룸(Bloom) & 블러(Blur)

- 블룸 효과는 밝은 쪽에서 어두운 쪽으로 빛을 흘리는 효과라고 표현하곤 한다.

- 정확히는 이미지에서 밝은 부분을 추출해서 그 부분을 블러처리한 후 기존 이미지에 입히는 일련의 과정이다.

- 이미지의 밝은 부분은 상수버퍼로 threshold 값을 넘겨주어 특정 밝기 이상이면 RWTexture2D에 저장하는 식으로 구현된다.

- 홍정모 그래픽스 영상 강의를 들을때도 실습한 내용이지만 다운스케일되지않은 이미지를 블러처리를 통해 부드럽게 만들려면 50번의 반복 과정이 필요했지만 다운스케일한 후 블러처리하니 5번만에 50번한만큼의 부드러움이 완성되었다. 

- 이 과정 또한 컴퓨트 셰이더를 통해 이루어진다.

밝은 부분 추출
블러 적용

3. 톤매핑

- HDR의 넓은 범위의 밝기를 LDR의 범위에 맞고 조정하여 출력한다.

- HDR 색의 범위를 화면에 출력할 수 없기 때문

- 아래 공식을 통해 HDR을 LDR로 변경해서 출력하게 된다.

    // 현재 픽셀에 대한 휘도 스케일 계산
    // LScale : 스케일 조정된 휘도 값
    float LScale = dot(_HDRColor, LUM_FACTOR);

    // HDR -> LDR로 변환 공식    : 픽셀의 휘도값을 원하는 중간 휘도 값으로 스케일을 조정한다.
    LScale *= MiddleGrey / AvgLum[0];
    LScale = (LScale + (LScale * LScale / LumWhiteSqr)) / (1.0 + LScale);

    // 휘도 스케일을 픽셀 색상에 적용
    return _HDRColor * LScale;

threshold값을 일부러 0.4를 주어 많은 부분이 블룸 처리되도록 적용했다.


림 라이트 구현

- 림 라이트란 물체의 가장자리 부분에 밝은 테두리를 만드는 기술

- 디퍼드 렌더링 방식으로 각 정점에 저장된 Position, Normal 값이 저장된 텍스쳐를 이용해서 구현했다.

- 가장자리 부분을 밝게 만드려면 가장자리 부분을 인식하는 로직을 짜야 한다.

- 여기서 가장자리 부분이란 내가 보는 시점, 즉 카메라의 시점에서 가장자리이다.

- 각 정점들은 노멀벡터를 가지고 있는데, 노멀벡터의 방향은 가장자리일수록 그 정점이 카메라를 향하는 벡터와 수직에 가깝다는 특성을 이용해서 두 벡터의 내적으로 나오는 cos값을 통해 픽셀에 특정 색상을 더해주는 식으로 구현했다.

- 내가 G-BUFFER에 저장한 Position과 Normal은 모두 뷰 좌표계에서의 값들인데, 뷰 좌표계에서의 Position값은 카메라에서 물체를 향하는 벡터이므로, 이 값을 노말라이징하고 역벡터로 만들면 그대로 카메라를 향하는 노멀벡터가 된다.

그러므로 아래와 같이 쉽게 구현이 가능했다.

struct PS_OUT
{
    float4 lim : SV_Target0;
};

PS_OUT PS_Main(VS_OUT _in)
{
    PS_OUT output = (PS_OUT) 0;
    
    float3 rimColor = float3(1.f, 1.f, 1.f);
    float rimStrength = g_float_0;
    float rimPower = g_float_1;
    
    float4 viewPos = g_tex_0.Sample(g_sam_0, _in.uv);
    float4 viewNormal = g_tex_1.Sample(g_sam_0, _in.uv);
    
    float isViewNormal = viewNormal.x + viewNormal.y + viewNormal.z;
    if (0.0f == isViewNormal)
        discard;
    
    float4 viewDir = -normalize(viewPos);
    float rim = 1.f - dot(viewDir, viewNormal);
    rim = smoothstep(0.f, 1.f, rim);
    rim = pow(rim, rimPower);
    output.lim = float4(rimColor * rim * rimStrength, 1.f);
   
    return output;
}

- viewDir을 구한 후 1에서 viewDir과 viewNormal을 내적한 결과를 뺀 후 보기 좋게 적절한 처리를 해주었다.

 

 


내일 할 일

- 플레이어 공격 모션 수정

- 몬스터 균열 림라이트 적용

 

댓글