본문 바로가기
셰이더 (Shader)/셰이더 프로그래밍 입문 - Pope Kim (완)

[HLSL] 챕터11 - 흑백/세피아 사진 만들기

by Minkyu Lee 2023. 3. 21.

// 원리

2D 이미지를 가져다 한번에 고쳐버리기

마치 포토샵에 여러가지 필터 적용하듯이.

이를 포스트프로세싱이라고 한다. (post processing)

 

렌더링 결과 2D 이미지로 저장하려면?

렌더타킷 사용한다.

여기에 그려두고 다른 색으로 인화하는 것이 쉽다.

 

포스트프로세싱 기법을 어떻게 입히나?

3D 그래픽 파이프라인 관점에서.

화면을 차지하는 모든 픽셀마다 픽셀셰이더를 호출한다.

 

하지만 임의로 픽셀셰이더 호출할 수 있는 방법 없다.

래스터라이저의 도움을 받아야하기 때문이다.

래스터라이저가 작동하려면 정점셰이더에서 정점 출력해주어야한다.

그래서 화면을 꽉 채우는 사각형을 그린다.

여기에 2D 텍스쳐를 씌운다.

 

화면 가득 채우는 사각형은 곧바로 화면공간(투영공간)에 있다고 가정한다.

개념상 화면에 존재하는 것이다.

 

투영공간에 사각형 정의를 어떻게 하나

네 꼭지점의 좌표는?

투영 공간의 x,y 범위는 -1, 1 ~ 1, -1 이다.

따라서 다음과 같이 꼭지점을 정의한다.

v0 = -1, 1, 0

v1 = -1, -1, 0

v2 = 1, 1, 0

v3 = 1, -1, 0

 

// 기초 설정

환경매핑에서 사용한 패스를 그대로 가져와서 사용한다.

반사되는 주전자.

 

이하 내용만 조금 변경한다.

렌더타깃의 크기도 화면을 꽉 채우게 설정한다.

use viewport dimensions

 

패스에서 렌더링 결과를 렌더타깃에 그리도록 설정한다.

add rendertarget - scene texture

화면에는 아무것도 그려지지 않게 된다.

 


  • 무효과 셰이더

화면을 꽉채우는 사각형을 추가한다.

add model - screen aligned quad

 

기초 설정에서 만든 렌더 타깃을 개체로 추가한다.

add texture object

 

정점셰이더

struct VS_INPUT
{
   float4 mPosition: POSITION;
   float2 mUV: TEXCOORD0;
};

struct VS_OUTPUT
{
   float4 mPosition: POSITION;
   float2 mUV: TEXCOORD0;
};

VS_OUTPUT vs_main(VS_INPUT Input)
{
   VS_OUTPUT Output;
   Output.mPosition = Input.mPosition;
   Output.mUV = Input.mUV;
   
   return Output;
}

 

렌더타깃을 읽어오기 위해 UV좌표가 필요하다.

 

사각형 메시는 이미 투영공간에 존재한다.

따라서 아무런 변환이 필요없다.

 

픽셀셰이더

struct PS_INPUT
{
   float2 mUV: TEXCOORD0; 
};

sampler2D SceneSampler; // render target texture sampler

float4 ps_main(PS_INPUT Input) : COLOR
{
   float4 tex = tex2D(SceneSampler, Input.mUV);
   return tex;
}

// back-face culling off ! (render state -> D3DRS_CULLMODE -> D3DCULL_NONE)

렌더타깃 텍스쳐를 불러와 그대로 보여준다.

 

화면을 채우는 사각형 메시 손봐줘야한다.

메시의 삼각형 잘못돼있다. 뒷면추리기를 꺼준다. (back-face culling)

add render state block - D3DRS_CULLMODE - D3DCULL_NONE

 


  • 흑백셰이더

무효과 셰이더에서 색만 흑백으로 바꾸면 된다.

R,G,B값이 동일해진다는 의미이다.

픽셀셰이더만 변경한다.

 

R, G, B의 평균을 구한다.

tex.rgb = (tex.r + tex.g + tex.b) / 3;

하지만 더 좋은 방법있다.

 

인간의 눈 R,G,B 명암 지각 정도가 다르다.

위키피디아 참고하면 빨강 30%, 녹색 59%, 파랑 11%로 지각한다.

tex.rgb = tex.r * 0.3 + tex.g * 0.59 + tex.b * 0.11;

위 코드로 변경한다.

 

이를 최적화한다. 곱한 뒤 더하는 연산이므로 내적으로 바꾼다.

tex.rgb = dot(tex.rgb, float3(0.3, 0.59, 0.11));

 

픽셀셰이더

struct PS_INPUT
{
   float2 mUV: TEXCOORD0; 
};

sampler2D SceneSampler; // render target texture sampler

float4 ps_main(PS_INPUT Input) : COLOR
{
   float4 tex = tex2D(SceneSampler, Input.mUV);
   tex.rgb = dot(tex.rgb, float3(0.3, 0.59, 0.11)); // multiply and plus == dot
   return tex;
}

  • 세피아셰이더

붉그스름한 갈색 빛 도는 톤을 세피아톤이라고 한다.

위키피디아에서 공식을 찾아본다.

이하는 마이크로소프트가 권하는 공식이다.

R` = (R * 0.393 + G * 0.769 + B * 0.189);

G` = (R * 0.349 + G * 0.686 + B * 0.168);

B` = (R * 0.272 + G * 0.534 + B * 0.131);

 

픽셀셰이더

struct PS_INPUT
{
   float2 mUV: TEXCOORD0; 
};

sampler2D SceneSampler; // render target texture sampler

float4 ps_main(PS_INPUT Input) : COLOR
{
   float4 tex = tex2D(SceneSampler, Input.mUV);
   
   float4 sepia;
   sepia.a = tex.a;
   sepia.r = dot(tex.rgb, float3(0.393f, 0.769f, 0.189f));
   sepia.g = dot(tex.rgb, float3(0.349f, 0.686f, 0.168f));
   sepia.b = dot(tex.rgb, float3(0.272f, 0.534f, 0.131f));
   
   return sepia;
}

상수를 곱해서 더해주는 공식이다.

따라서 내적 사용하게 변경한다.

 

댓글