[셰이더 프로그래밍 입문] Part2 | 셰이더 프로그래밍 응용 (Chapter5~6)

jungizz_·2023년 5월 11일
0

Shader Programming

목록 보기
3/5
post-thumbnail

Chapter 5 | 물체에 색을 입히는 diffuse/specular 매핑

◾DiffuseMap

  • 표면이 빛을 흡수하는 성질을 셰이더에서 표현하기 (색 입히기)
  • 텍스쳐 이미지를 픽셀 셰이더에서 읽어와 각 픽셀마다 색을 정해주면 된다.
  • 전체적인 빛을 결정하는 난반사광의 결과에 텍스쳐를 적용한다. 이렇게 난반사광에 적용하는 텍스쳐를 DiffuseMap이라고 한다.

◾SpecularMap

  • 정반사광용 텍스쳐맵을 만드는 경우도 있다.
  1. 난반사광이 반사하는 빛과 정반사광이 반사하는 빛의 스펙트럼이 다른 경우
  2. 각 픽셀이 반사하는 정반사광의 정도를 조절하는 경우 (이마, 코에 듬성듬성 보이는 정반사광 효과 등)

❗ 따라서

  • 난반사광 = 빛의 색상 X 난반사광의 양 X 디퓨즈맵의 값
  • 정반사광 = 빛의 색상 X 정반사광의 양 X 스페큘러맵의 값
  • 스페큘러맵 틈새의 검정색은 정반사광을 전혀 반사하지 않음을 나타낸다. 스페큘러맵은 색상정보가 아닌 각 픽셀이 반사하는 정반사광의 양이다. (픽셀 수준에서 제어하고 싶은 경우 사용한다.)

📝 기초설정

  • 이전 프로젝트의 사본을 만든 후 셰이더의 이름을 SpecularMapping으로 수정한다.
  • 텍스쳐맵으로 사용할 이미지를 추가하고, DiffuseMap으로 이름을 수정한다.
  • Pass0에 DiffuseMap을 추가하고 DiffuseSampler로 이름을 수정하여 텍스처 개채를 생성한다.
  • 부록으로 제공하는 Samples폴더에서 스페큘러맵(Fieldstone_SM.tga)을 렌더몽키에 드래그하여 가져온 뒤 SpecularMap로 이름을 수정한다. 텍스처 개체도 위와 동일한 방법으로 SpecularSampler로 생성한다.

  • 빛의 색을 추가하기 위해 gLightColor를 생성하고 더블클릭하여 변수값(0.7, 0.7, 1.0)을 대입한다.
  • 정점데이터에서 UV좌표를 읽어오기 위해 StreamMapping을 더블클릭하여 TEXCOORD(float2)를 추가한다.

📝 정점셰이더

  • 텍스처매핑을 위한 UV좌표 변수를 선언하고 정점버퍼에서 UV좌표를 가져온다. (픽셀셰이더로 전달될 것)
. . .

struct VS_INPUT{
   float4 mPosition : POSITION;
   float3 mNormal : NORMAL;
   float2 mUV: TEXCOORD0; //텍스처매핑을 위한 UV좌표
};

struct VS_OUTPUT{
   float4 mPosition : POSITION;
   float2 mUV: TEXCOORD0; //텍스처매핑을 위한 UV좌표 
   float3 mDiffuse : TEXCOORD1;
   float3 mViewDir : TEXCOORD2;
   float3 mReflection : TEXCOORD3;
};


VS_OUTPUT vs_main(VS_INPUT Input){
   
   . . .
   
   Output.mUV = Input.nUV; //정점버퍼에서 UV좌표 가져오기 
   return Output;
}

📝 픽셀셰이더

struct PS_INPUT{
   float2 mUV : TECOORD0; //UV좌표
   . . .
};

//렌더몽키에 추가한 세 변수를 전역적으로 선언
sampler2D DiffuseSampler;
sampler2D SpecularSampler;
float3 gLightColor;

float4 ps_main(PS_INPUT Input) : COLOR
{   
   //디퓨즈맵 샘플링
   float4 albedo = tex2D(DiffuseSampler, Input.mUV); //현재 픽셀이 반사하는 색
   float3 diffuse = gLightColor * albedo.rgb * saturate(Input.mDiffuse);; //albedo에 난반사광의 양과 빛의 색상을 곱하기
   . . .
   
   float3 specular = 0;
   if(diffuse.x > 0){
      specular = saturate(dot(reflection, -viewDir));
      specular = pow(specular, 20.0f);
      
      //스페큘러맵 샘플링
      float4 specularIntensity = tex2D(SpecularSampler, Input.mUV);
      specular *= specularIntensity.rgb * gLightColor;//빛의 색상을 곱하기
   }
   
   float3 ambient = float3(0.1f, 0.1f, 0.1f) * albedo;
   
   return float4(ambient + diffuse + specular, 1);
   
}


Chapter 6 | 만화 같은 명암을 입히는 툰셰이더

🆕 HLSL

  • ceil - 무조건 올림 함수
  • tex2D() - 텍스처 샘플링에 사용하는 HLSL 함수
  • swizzle - 벡터 성분의 순서를 마움대로 뒤섞을 수 있는 방법

🆕 수학

  • 여러 개의 행렬을 미리 곱해 놓은 뒤, 그 결과를 정점 변환에 사용해도 결과는 동일하고 속도는 더 빠르다.
  • 반대방향으로 공간변환을 할 때 역행렬을 사용한다.

◾ Toon Shading

  • 명암을 단계적으로 끊어서 만화같은 명암을 나타낸다.
  • 난반사광의 값을 무조건 0.2단위로 올림을 하여 단계를 나타낼 것이다.

📝 기초설정

  • ToonShader이름을 가지는 DirectX 이펙트를 추가한다. (matViewProjection 행렬은 삭제한다.)
  • Model은 Teapot 모델로 바꿔준다.
  • 난반사광 계산을 위해 빛의 위치정점의 법선정보가 필요하다.
  1. 빛의 위치 변수는 ToonShader에 float4 변수gWorldLightPosition를 추가한 뒤 (500, 500, -500)으로 설정
  2. 법선정보는 StreamMapping을 더블클릭하여 Normal필드 추가 (FLOAT3, Index: 0)
  • 주전자의 전체 표면의 색을 지정하기 위한 float3 전역변수 gSurfaceColor를 ToonShader에 추가한 뒤 (0, 1, 0)으로 변경한다.

  • 공간변환을 더 빠르게 할 수 있도록 월드, 뷰, 투영행렬을 미리 곱해둘 것이다.
  • 합쳐진 행렬을 받을 Float4X4 전역변수 gWorldViewProjectionMatrix를 생성하고 WorldViweProjection으로 시맨틱을 설정한다.

  • 하지만 난반사광 계산을 위해서는 월드공간에 정의된 빛의 위치를 사용해야되기 때문에 월드행렬이 필요하긴하다. 그래서 번거로움을 줄이기 위해 그냥 빛의 위치를 지역공간으로 변환해버린다.
  • 월드행렬의 역행렬 Float4x4전역변수 gInvWorldMatrix를 생성하고 WorldInverse로 시맨틱을 설정한다.

📝 정점셰이더

1. 입력데이터

struct VS_INPUT 
{
   float4 mPosition : POSITION; //위치
   float3 mNormal: NORMAL; //법선
};

2. 출력데이터

struct VS_OUTPUT 
{
   float4 mPosition : POSITION; 
   float3 mDiffuse : TEXCOORD1; //계산이 끝난 난반사광
};

3. 전역변수

float4x4 gWorldViewProjectionMatrix; //공간변환 행렬
float4x4 gInvWorldMatrix; //월드역행렬

float4 gWorldLightPosition; //광원의 위치

4. 함수

VS_OUTPUT vs_main( VS_INPUT Input )
{
   VS_OUTPUT Output;

   //정점의 위치를 투영공간으로 한번에 옮기기
   Output.mPosition = mul( Input.mPosition, gWorldViewProjectionMatrix );
   
   //난반사광의 양 계산
   float3 objectLightPosition = mul(gWorldLightPosition, gInvWorldMatrix); //빛의 위치를 지역공간으로 변환
   float3 lightDir = normalize(Input.mPosition.xyz - objectLightPosition); //광원의 위치에서 현재위치를 가리키는 방향벡터
   
   Output.mDiffuse = dot(-lightDir, normalize(Input.mNormal)); //내적을 구해 난반사광의 양 구하기
   
   return( Output );
}

📝픽셀셰이더

1. 전역변수와 입력데이터

float3 gSurfaceColor; //표면 색상

struct PS_INPUT
{
   float3 mDiffuse : TEXCOORD1; //정점셰이더에서 계산을 마친 난반사광의 양
}

2. 픽셀셰이더 함수

  • 난반사광의 양을 0~1의 범위로 만든 뒤, 0.2단위로 자를 수 있도록 5를 곱하여 올림ceil을 적용한다. 그 결과 값은 0, 1, 2, 3, 4, 5 중 하나가 될 것이며 결과 값을 5로 나누어 최종 결과 값이 0, 0.2, 0.4, 0.6, 0.8, 1 이 되도록 한다.
float4 ps_main(PS_INPUT Input) : COLOR
{
   float3 diffuse = saturate(Input.mDiffuse);
   diffuse = ceil(diffuse * 5) / 5.0f; //0.2단위로 자르기
   return float4 : gSurfaceColor * diffuse.xyz, 1; //표면 색 곱하기
}

profile
( •̀ .̫ •́ )✧

0개의 댓글