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

[HLSL] 챕터4 - 조명셰이더 기초

by Minkyu Lee 2023. 3. 18.

// 직접광 vs 간접광

직접광은 광원으로부터 직접 받는 빛이다.

다른 물체에 반사되어 들어오는 빛을 간접광이라고 한다.

 

간접광은 직접광보다 계산이 어렵다.

계산법 중 하나로 광선추적 기법이 있다. (ray tracing)

 

이 장에서는 직접광만 다룬다.

 

// 조명모델

간접광까지도 다루는 조명모델을 전역조명모델이라고 한다. (global illumination model)

직접광만을 다루는 조명모델을 지역조명모델이라고 한다. (local illumination model)

 

// 빛을 구성하는 요소

난반사광, 정반사광이 있다. (diffuse, specular)

 

// 난반사광이란?

여러 방향으로 고르게 반사되는 빛을 말한다.

표면이 거칠수록 난반사가 심해진다.

입사광 중 일부는 난반사광이 되고, 일부는 정반사광이 된다.

 

// 난반사광 계산법

수학자마다 주장이 다르다.

람베르트 모델 (lambert)

표면법선과 입사광이 이루는 각의 코사인 값을 구하면 난반사광의 양을 구할 수 있다.

 

그러나 코사인 함수는 값싸지 않다.

내적이 코사인을 대신할 수 있다.

 

정점셰이더

float4x4 gWorldMatrix;
float4x4 gViewMatrix;
float4x4 gProjectionMatrix;

float4 gWorldLightingPosition;
float4 gWorldCameraPosition;

struct VS_INPUT
{
   float4 mPosition : POSITION;
   float3 mNormal : NORMAL;
};

struct VS_OUTPUT
{
   float4 mPosition : POSITION;
   float3 mDiffuse : TEXCOORD1;
   float3 mViewDir : TEXCOORD2;
   float3 mReflection : TEXCOORD3;
};

VS_OUTPUT vs_main(VS_INPUT Input)
{
   VS_OUTPUT Output;
   Output.mPosition = mul(Input.mPosition, gWorldMatrix);
   float3 lightDir = normalize(Output.mPosition.xyz-gWorldLightingPosition.xyz);
   Output.mViewDir = normalize(Output.mPosition.xyz-gWorldCameraPosition.xyz);
   
   Output.mPosition = mul(Output.mPosition, gViewMatrix);
   Output.mPosition = mul(Output.mPosition, gProjectionMatrix);
   
   // objectNormal to worldNormal
   float3 worldNormal = normalize(mul(Input.mNormal, (float3x3)gWorldMatrix));
   Output.mDiffuse = dot(-lightDir, worldNormal);
   
   Output.mReflection = reflect(lightDir, worldNormal);
   return Output;
}

// 법선 시맨틱

정점 버퍼에서 법선을 가리키는 시맨틱은 NORMAL이다.

 

// 난반사광 계산은 정점에서 하는가 픽셀에서 하는가?

어느쪽에서도 계산이 가능하다.

 

값싼 곳은 정점이다.

따라서 정점셰이더에서 계산하였다.

 

// 입사광 벡터 구하기

광원의 위치에서 현재 위치까지로 선을 긋는다.

주의할 점이 있다.

모든 변수의 공간이 일치해야한다.

광원은 월드공간이다.

정점은 투영공간에 있다.

따라서, 월드행렬을 곱한 직후에서 구해야한다.

 

법선을 가져온다.

법선은 정점버퍼에서 가져오므로 물체공간에 있다.

월드공간 변환이 필요하다.

법선은 float3형이니 월드행렬도 3x3으로 바꾸어야한다.

4x4행렬에서 4번째 행렬은 평행이동(translation)값이므로 영향이 없다.

이후, 단위 벡터로 만든다.

 

// -lightDir로 밑동이 만나도록 함에 주의해라.

 

// 용도에 맞는 시맨틱이 없는 경우

TEXCOORD를 사용하는게 일반적이다.

 

  • 정반사광 관련

// 정반사광의 특징

입사각과 출사각이 같은 것이 특징이다.

 

// 퐁 모델

여러 수학공식 중 퐁 모델이 있다.

 

반사광과 카메라벡터가 이루는 각도의 코사인 값을 구한다.

그 결과를 여러번 거듭제곱하면 정반사광을 구할 수 있다.

 

여러번 거듭제곱하는 이유는 타이트하게 하여 범위를 좁히는데 있다.

몇번이나 하는지는 재질에 따라 다르다.

거칠수록 덜 타이트하다.

보통 20번 정도는 해주어야한다.

 

// 카메라 위치

전역변수로 만든다.

 

// 정반사광 계산은 픽셀셰이더에서 한다.

정반사광 계산한 뒤 픽셀셰이더 전달이 불가하다.

거듭제곱한 뒤 보간과 보간 뒤 거듭제곱의 차이가 엄청나기 때문이다.

 

따라서 이 계산은 픽셀셰이더에서 한다.

그래서 방향벡터 R V 를 넘겨주어야한다.

 

// 반사벡터 구하기

reflect함수를 이용하면 된다.

reflect(입사광 벡터, 법선)으로 사용한다.

픽셀셰이더

struct PS_INPUT
{
   float3 mDiffuse : TEXCOORD1;
   float3 mViewDir : TEXCOORD2;
   float3 mReflection : TEXCOORD3;
};

float4 ps_main(PS_INPUT Input): COLOR
{
   float3 diffuse = saturate(Input.mDiffuse);
   float3 specular = 0.0f;
   if(diffuse.x > 0)
   {
      specular = saturate(dot(Input.mReflection, - Input.mViewDir));
      specular = pow(specular, 20);
   }
   float3 ambient = float3(0.1f, 0.1f, 0.1f);
   return float4(diffuse + specular + ambient, 1);
}

// saturate

0, 1로 클램핑해준다.

성능에 영향을 미치지 않는 공짜함수다.

 

  • 정반사광

// R과 V를 다시 정규화해준다.

이유는 보간기를 거치면 값이 흐트러질 수 있기 때문이다.

 

// 정반사광 표현

이 둘의 내적을 구한 뒤, 거듭제곱한다.

 

// if문의 이유

난반사광이 없는 표면은 빛이 닿지 않는다는 뜻이다.

따라서 정반사광도 존재할 수 없다.

그래서 if문을 넣었다.

 

// -viewDir한 것에 주의하라.

두 벡터의 밑동이 만나야한다.

 

// 환경광 추가

빛이 없는 곳이 간접광이 없어서 너무 어둡다.

ambient로 0.1만큼 추가해준다.

 

댓글