헤더파일

셰이더 공부 - 스페큘러 맵핑 본문

DirectX

셰이더 공부 - 스페큘러 맵핑

헤더파일 2018. 2. 26. 16:02

 전에 만들었던 조명 셰이더에선 표면에 일괄적인 조명을 만들었습니다. 하지만 실제 물체는 표면의 재질에 따라 흡수하는 빛의 양이 다르고 우리가 사용할 모델들도 일괄적인 색이 아닌 텍스쳐로 이루어져 있습니다.  우리가 물체를 지각할 수 있는 이유는 대부분 난반사광 덕분이므로 텍스쳐의 텍셀에 난반사광의 양을 곱해서 조명과 텍스쳐를 합칠 수 있습니다. 이 기본 텍스쳐를 디퓨즈 맵이라고 합니다. 

그렇다면 정반사광은 어떻할까요? 디퓨즈 맵의 텍셀을 정반사광에 그대로 사용할 수도 있지만 두가지 이유때문에 정반사광용으ㅗ 스페큘러맵을 따로 만드는 경우가 있습니다.

1. 난반사광이 반사하는 빛과 정반사광이 반사하는 빛의 스펙트럼이 다른경우

2.각 픽셀이 반사하는 정반사광의 정도를 조절하는 경우

 예를 들어 사람에 얼굴에 조명을 비춘다 했을 때 다른 부분보다 이마나 코가 더 정반사광에 크게 반응하는 걸 볼 수 있고 피부가 매끄럽지 않다면 정반사광이 듬성듬성 보일 수도 있습니다. 

디퓨즈맵




스페큘러 맵




난반사광 = 빛의 색상 x 난반사광의 양 x 디퓨즈 맵의 값

정반사광 = 빛의 색상 x 정반사광의 양 x 스페큘러 맵의 값


1. 정점 셰이더


float4x4 gWorldViewProjectionMatrix;

float4x4 gWorldInvMatrix;



float4 gWorldLightPosition;

float4 gWorldCameraPosition;



struct VS_INPUT

{

   float4 mPosition : POSITION;

   float3 mNormal: NORMAL;

   float2 mUV:TEXCOORD0;

};



struct VS_OUTPUT

{

   float4 mPosition : POSITION;

   float2 mUV:TEXCOORD0;

   float3 mDiffuse : TEXCOORD1;

   float3 mViewDir: TEXCOORD2;

   float3 mReflection: TEXCOORD3;

};



VS_OUTPUT vs_main( VS_INPUT Input )

{

   VS_OUTPUT Output;



   Output.mPosition = mul( Input.mPosition, gWorldViewProjectionMatrix );



   float3 localLightPosition = mul(gWorldLightPosition, gWorldInvMatrix);


   float3 lightDir = Output.mPosition.xyz - localLightPosition.xyz;

   lightDir = normalize(lightDir);


   float3 viewDir = normalize(Output.mPosition.xyz - gWorldCameraPosition.xyz);

   Output.mViewDir = viewDir;



   Output.mDiffuse = dot(-lightDir, Input.mNormal);

   Output.mReflection = reflect(lightDir, Input.mNormal);

   Output.mUV = Input.mUV;


   return Output;

}



정점셰이더에서 달라진 것은 uv의 좌표를 정점데이터로 받아 픽셀셰이더로 넘겨 주는 것 밖에 없습니다.



2. 픽셀 셰이더


struct PS_INPUT

{

   float2 mUV : TEXCOORD0;

   float3 mDiffuse : TEXCOORD1;

   float3 mViewDir: TEXCOORD2;

   float3 mReflection: TEXCOORD3;

};


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);

   

   float3 reflection = normalize(Input.mReflection);

   float3 viewDir = normalize(Input.mViewDir); 

   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);

}


struct PS_INPUT

{

   float2 mUV : TEXCOORD0;

   float3 mDiffuse : TEXCOORD1;

   float3 mViewDir: TEXCOORD2;

   float3 mReflection: TEXCOORD3;

};



sampler2D DiffuseSampler;

sampler2D SpecularSampler;


픽셀 셰이더에서 달라진 점은 보간기를 거쳐온 텍스쳐의 UV좌표를 받는 것과 두 개의 텍스쳐 오브젝트를 받는 것입니다.


float4 albedo = tex2D(DiffuseSampler, Input.mUV);

float3 diffuse = gLightColor * albedo.rgb * saturate(Input.mDiffuse);


albedo 값이 디퓨즈 맵의 텍셀입니다. 이 값을 빛의 색과 난반사광의 양에 곱해 최종 난반사광의 양을 구합니다.


      float4 specularIntensity  = tex2D(SpecularSampler, Input.mUV);

      specular *= specularIntensity.rgb * gLightColor;


정반사광을 계산하는 것도 난반사광을 계산하는 것과 똑같습니다. specularIntensity 값이 스페큘러 맵의 텍셀이고 이 값과 빛의 양, 정반사광의 양을 곱해 최종 정반사광의 양을 구합니다.


   float3 ambient = float3(0.1f, 0.1f, 0.1f) * albedo;

   

   return float4(ambient + diffuse + specular, 1);


주변광인 ambient에 디퓨즈맵의 텍셀을 곱하는 이유는 난반사광이 아예 없어도 물체의 텍스쳐는 보여야 하기 때문입니다. 최종 픽셀의 색상은 주변광 + 난반사광 + 정반사광으로 정합니다.



'DirectX' 카테고리의 다른 글

셰이더 공부 - 환경매핑  (0) 2018.02.27
셰이더 공부 - 법선 매핑  (1) 2018.02.26
셰이더 공부 - 툰셰이더  (0) 2018.02.25
셰이더공부 - 정반사광  (0) 2018.02.25
셰이더 공부 - 난반사광  (0) 2018.02.24
Comments