조명 셰이더

2023. 4. 30. 00:27컴퓨터 그래픽스/HLSL

조명은 난반사, 정반사를 이용하여 표현할 수 있다.

 

난반사는 diffuse 이다.

각 vertex별로 normal vector와 해당 vertex와 light 사이의 방향 vector 를 구해서 내적을 통해 diffuse를 구한다.

정반사는 specular로 

view vector는 다른 말로 카메라 vector로 볼 수 있다. view vector와 reflection vector의 내적으로 specular 를 구할 수 있다.

이러한 빛의 계산은 어느 과정에서 하는 것이 좋을까 생각해보면

 

vertex, fragment 둘 중 하나일텐데 vertex에서 계산하면 결과 값들을 fragment로 보내서 fragment 결과로 ambient, diffuse, specular를 더한 색을 return하게 될 것이다.

fragment에서 계산을 하게 되면 vertex에서 normal정보를 가져온 다음 내적을 구해서 결과를 낼 것이다.

 

vertex shader에서는 정점 정보만 가져오게 되므로 삼각형을 그리면 3번의 함수 호출이 일어날 것이고, fragment에서는 삼각형이 차지하는 픽셀의 수 만큼 함수 호출이 일어나게 되므로 성능 상 유리한 것은 vertex shader에서 계산하여 fragment로 보내는 것이 더 좋다.

 

--vertex shader

float4x4 gWorldMatrix;
float4x4 gViewMatrix;
float4x4 gProjectionMatrix;

float4 gWorldLightPosition; // 빛의 위치 vector

float4 gWorldCameraPosition; // 카메라 위치 vector

struct VS_INPUT 
{
   float4 mPosition : POSITION;
   float3 mNormal : NORMAL; // vertex의 normal -> semantic 3차원 공간 내
   
};

struct VS_OUTPUT 
{
   float4 mPosition : POSITION;
   float3 mDiffuse : TEXCOORD1; // semantic으로 texture로 나타내는 것이 일반적이다 diffuse 
   float3 mViewDir : TEXCOORD2;
   float3 mReflection : TEXCOORD3;
};



VS_OUTPUT vs_main( VS_INPUT Input )
{
   VS_OUTPUT Output;

   Output.mPosition = mul( Input.mPosition, gWorldMatrix );
   
   float3 lightDir = Output.mPosition.xyz - gWorldLightPosition.xyz; // 빛과 vertex 간의 방향 vector 
   lightDir = normalize(lightDir); // normalize하기 크기가 1이다
   
   float3 viewDir = normalize(Output.mPosition.xyz - gWorldCameraPosition.xyz); // 카메라 위치와 vertex 간의 방향 vector
   Output.mViewDir = viewDir; 
   Output.mPosition = mul(Output.mPosition, gViewMatrix); 
   Output.mPosition = mul(Output.mPosition, gProjectionMatrix);
   
   float3 worldNormal = mul( Input.mNormal, (float3x3)gWorldMatrix); // world 좌표계에서의 vertex normal 
   worldNormal = normalize(worldNormal); 
   
   Output.mDiffuse = dot(-lightDir, worldNormal); // diffuse는 normal과 빛 방향 벡터인데 위에서 vertex로 떨어지는 방향인 vector이므로 -를 붙혀서 반대 방향으로 바꿔줌
   Output.mReflection = reflect(lightDir, worldNormal); // reflect 함수를 이용하여 light 벡터와 normal 간의 reflect를 구할 수 있다.
   return( Output );
   
}

 

--fragment shader

 

색을 결정해야한다. input으로 diffuse와 specular 계산을 위한 view 방향 벡터와 reflect 방향 벡터를 가져온다.

diffuse는 -1~1 사이의 값이고 음수 값은 존재하지 않는 각도이므로 saturate 함수를 통해 0~ 1까지로 바꿔준다.

view벡터와 reflect 벡터를 normalize해준 후에 서로 내적을 통해 specular를 구해준다. 

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 reflection = normalize(Input.mReflection);
   float3 viewDir = normalize(Input.mViewDir);
   float3 specular = 0;
   if(diffuse.y > 0) // diffuse가 범위 또한 0~ 1 일 수 밖에 없음
   {
      specular = saturate(dot(reflection, -viewDir));
      specular = pow(specular, 20.0f); // specular는 자연스러운 표현을 위해 cos의 n 제곱을 통해 현실에서의 강한 반사 표현을 구현해낸다. cos ^ 20 을 나타낸 것이다.
   }
   float3 ambient = float3(0.1f,0.1f,0.1f); // object의 본래 색을 나타냄 ambient라고 부름
   return float4(ambient+ diffuse + specular, 1.0f); // return으로 ambient, diffuse, specular를 다 더한 값이 rgba fragment로 들어간다
   
}

 

'컴퓨터 그래픽스 > HLSL' 카테고리의 다른 글

normal mapping, 환경 mapping  (0) 2023.07.14
재질에 따른 반사양 조절, toon shading  (0) 2023.07.14
텍스쳐매핑  (0) 2023.04.29
HLSL 원 그리기  (0) 2023.04.29