농담곰담곰이의곰담농

[ShaderMonkey] 기초적인 조명 셰이더

by 브이담곰

📄 HSLS

- NORMAL : 정점의 법선정보를 불러올 때 사용하는 시멘틱

- normalize(): 벡터 정규화 함수

- dot(): 내적 함수

- saturate(): 0~1을 넘어서는 값의 범위를 짤라 냄

- reflect(): 벡터반사 함수

- pow(): 거듭제곱 함수

 

📄 수학

- 내적: 코사인 함수를 빠르게 계산할 때 사용할 수 있다.

- 정규화: 벡터를 단위벡터로 만든다.


🤍 직접광과 간접광

     - 직접광: 광원으로부터 직접 받는 빛.

     - 간접광: 다른 물체에 반사되서 들어오는 빛.

직접광과 간접광

간접광은 수없이 반사의 반사를 거치기 때문에 직접광보다 계산하기 어렵다. 간접광을 계산하는 방법에는 "광선추적(ray-tracing)"이라는 기법이 있다. 이러한 기법을 사용하는 이유는 하드웨어 사양이 따라주지 않기 때문에, 직접광만 제대로 계산하고 간접광은 흉내내기 정도에 그친다.

 

빛을 구성하는 요소 : 난반사광(diffuse light), 정반사관(specular light)


📌 난반사광(diffuse light)

우리가 물체를 볼 수 있는 이유 -> 빛이 물체의 표면에서 반사되기  때문.

1. 의미: 여러 방향으로 반사되는 빛

2. 특징 : 여러방향으로 고르게 펴진다. -> 어느 방향에서 바라봐도 물체의 명암이나 색조가 변하지 않는다.

난반사광

3. 람베르트 모델

    1.  게임에서는 주로 난반사광을 계산 할 때 "람베르트 모델"을 사용한다.

    2.  정의 및 특징 : 표면법선과 입사광이 이루는 각의 코사인 값을 구하면 난반사광의 양이다.

y = cos(x) 그래프

 

실제 입사광과 법선의 각도

해가 중천에 떠있을 때(90도) 가장 밝고, 해가 지평선에 있을 때(0도) 가장 어둡다.

위의 이미지를 그래프로 치환

-90~90사이에 ?인 이유는 각도에 따라 얼마나 빠르게 어두워지는 모르기 때문이다. 하지만  y=cos(x) 그래프와 추이가 비슷하다. 따라서 람베르트 모델을 사용하면 코사인 함수로 난반사광을 구할 수 있다.

 

하지만 코사인함수를 매번 호출하기엔 비용이 너무 든다.

내적을 이용해 코사인을 대신하자!

코사인계산을 내적으로 유도하는 과정

 

4. 셰이더 구현

  (1) Vertex Shader : 전역변수 선언

float4x4 gWorldMatirx;
float4x4 gViewMatrix;
float4x4 gProjectionMatrix;//월드,뷰,프로젝션 세가지 행렬 선언

float4 gWorldLightPosition; //광원의 위치

  (2) Vertex Shader : 구조체 정의

     기본 Input, Output 구조체에서, Input에는 mNormal이라는 NORMAL시멘틱을 갖고있는 변수를 추가해주고,

 Output에는 mDiffuse라는 TEXCOORD1이라는 시멘틱을 갖고있는 변수를 추가해준다.

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

struct VS_OUTPUT
{
  float4 mPosition: POSITION;
  float3 mDiffuse: TEXCOORD1;
};

    (3) Vertex Shader : vs_main 작성

VS_OUTPUT vs_main(VS_INPUT Input)
{
   VS_OUTPUT Output; //반환할 Output변수 선언
   
   //1. 입사광 벡터 계산
   //World좌표를 지역공간에 곱해줘서 월드공간으로 만든다.
   Output.mPosition = mul(Input.mPosition, gWorldMatirx);
   /*Output.mPositon은 투영공간(우리가 보는 공간)의 좌표이다.
   Input.mPositon은 지역공간의 좌표이다.
   3D행렬계산은 모든 변수의 공간이 일치해야한다.*/
   
   
   //현재위치(월드공간 위치)에서 광원의 위치(전역변수)를 빼준다.
   float3 lightDir = Output.mPosition.xyz - gWorldLightPosition.xyz;
   lightDir = normalize(lightDir);//백터의 길이를 1로(내적을위해서)만들어야 하므로
   // 정규화 시켜준다 -> 단위벡터로 만든다. HSLS내장함수 사용.
   Output.mPosition = mul(Output.mPosition, gViewMatrix);
   Output.mPosition = mul(Output.mPosition, gProjectionMatrix);
   
   
   //2. 법선 벡터 계산
   /* 법선 벡터는 정점버퍼에서 곧바로 오기는 데이터이기 때문에 물체공간에 존재한다.
   따라서, 월드공간으로 변환해주어야 한다.*/  
   float3 worldNormal = mul(Input.mNormal, (float3x3)gWorldMatirx);
   //gWorldMatrix는 4x4인데, Input.mNormal은 3x3이므로 형변환 후 계산한다.
   //4x4의 4번째 행렬은 평행이동 값이므로 방향벡터에 아무런 영향도 미치지 않는다.
   worldNormal = normalize(worldNormal); //단위벡터로 만들기: 정규화
   
   Output.mDiffuse = dot(-lightDir, worldNormal); // 내적
   //-lightDir인 이유는, 두 벡터의 내적을 구할 때는 화살표의 밑동이 서로 만나야 하기 때문.
   
   return Output;
}

 

-lightDir로 계산하는 이유

    (4) Pixel Shader 

struct PS_INPUT
{
  float3 mDiffuse : TEXCOORD1;
};

float4 ps_main(PS_INPUT Input) :COLOR
{
  float3 diffuse = saturate(Input.mDiffuse);
  return float4(diffuse, 1);
}

난반사광 효과

📌 정반사광(specular light)

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

    1. 특징 : 한 방향으로반 반사되는 빛. 입사각이 출사각과 같다.

      -> 정반사광의 효과를 보려면 빛이 반사되는 방향에서 물체를 바라봐야 한다.

    2. 예시 : 모니터에 빛이 반사되어 보기 힘들었던 경우.

    3. 퐁(Phong) 모델

      - 반사광과 카메라벡터(카메라에서 현재위치까지 선을 그은 벡터)가 이루는 각도의 코사인 값을 구하고, 그 결과값을 여러번 거듭제곱하면 구할 수 있다.

반사광R과 카메라벡터V가 이루는 각도의 코사인 값을 구하면 된다.

하지만, 정반사광은 난반사광과 다르게 폭이 매우 좁다. 따라서 이를 구현하기 위해서 코사인값을 거듭제곱 하면.

코사인 값의 폭이 빠르게 줄어든다.

* 표면의 재질에 따라 거듭제곱의 횟수는 달라질 수 있다.

   

   4. 셰이더 구현

      (1) Vertex Shader : 전역변수 추가

float4 gWorldCameraPosition; //카메라 위치 값

      (2) Vertex Shader : 구조체 VS_OUTPUT에 멤버변수 추가

float3 mViewDir: TEXCOORD2; //카메라V 방향벡터
float3 mReflection: TEXCOORD3;//반사광R 벡터

      (3) Vertex Shader : 카메라벡터 구하기

float3 viewDir = normalize(Output.mPosition.xyz - gWorldCameraPosition.xyz);
//동시에 정규화도 해주기. 월드 - 카메라 = 카메라 벡터
Output.mViewDir = viewDir;

     (4) Vertex Shader : reflect() 함수 사용

Output.mReflection = reflect(lightDir, worldNormal);
//첫번째 인자: 입사광의 방향벡터/ 두번째 인자: 반사면의 법선

     (5) Pixel Shader : 구조체 PS_INPUT에 멤버변수 추가

 float3 mViewDir: TEXCOORD2;
 float3 mReflection: TEXCOORD3;

     (6) Pixel Shader : 값 재 정규화

  보간기를 거치는 동안 값이 흐트러질 수 있기 때문에, 백터들을 다시 정규화 시켜준다.

float3 reflection = normalize(Input.mReflection);
float3 viewDir = normalize(Input.mViewDir);

 

     (7) Pixel Shader : 내적 후 제곱 계산

float3 specular = 0;
  if(diffuse.x > 0)/*난반사가 없는 표면에는 정반사도 존재하지 않으므로 난반사광이 0%이상일때만
                     계산*/
  {
    specular = saturate(dot(reflection, -viewDir));//내적
    specular = pow(specular, 20.0f);//거듭제곱
  }

 

정반사광의 효과 확인

return float4(specular, 1);

 

 

 

난반사 + 정반사

return float4(diffuse+specular, 1);

 

 

 

주변광+난반사+정반사

float3 ambient = float3(0.1f, 0.1f, 0.1f); //주변광
  
return float4(ambient + diffuse + specular, 1);//주변광+난반사+정반사

 

📌 DirextX11에 셰이더 적용하기

'Technical Artist > ShaderMonkey' 카테고리의 다른 글

[ShaderMonkey] Texture Mapping  (0) 2021.11.23
[ShaderMonkey] 빨강 셰이더  (0) 2021.11.23
[shader monkey] 첫 셰이더 만들기  (0) 2021.11.09

블로그의 정보

농담곰담곰이의곰담농

브이담곰

활동하기