헤더파일
셰이더공부 - 정반사광 본문
정반사광
정반사광은 난반사광과는 달리 한 방향으로만 반사되는 빛으로 입사각이 출사각과 같은 것이 특징입니다. 따라서 정반사광의 효과를 보려면 빛이 반사되는 방향에서 물체를 바라봐야만 합니다. 정반사광을 계산하는 여러 기법중 게임업계에서 많이 사용하는 퐁모델은 반사광과 카메라벡터가 이루는 각도의 코사인값을 구하고 그 코사인 값의 거듭제곱한 값이 정반사광이 됩니다.
사실 코사인값을 제곱하는 것만 빼면 난반사광을 구하는 공식이랑 다르지 않습니다. 제곱하는 이유는 정반사광이 난반사광보다 더 빠르게 감소하기 때문입니다. 위 그래프에서 볼 수 있듯이 거듭제곱할 수록 코사인값이 더 빠르게 감소하는 걸 볼 수 있습니다. 제곱하는 수준은 표면에 재질에 따라 다릅니다. 거친 표면일수록 정반사광이 덜 타이트하므로 거듭제곱 수를 줄여줘야 합니다.
1.정점셰이더
struct VS_INPUT
{
float4 mPosition : POSITION0;
float3 mNormal : NORMAL;
};
struct VS_OUTPUT
{
float4 mPosition : POSITION0;
float3 mDiffuse : TEXCOORD1;
float3 mViewDir:TEXCOORD2;
float3 mReflection:TEXCOORD3;
};
float4x4 gWorldMatrix;
float4x4 gViewMatrix;
float4x4 gProjectionMatrix;
float4 gWorldLightPosition;
float4 gWorldCameraPosition;
VS_OUTPUT vs_main( VS_INPUT Input )
{
VS_OUTPUT Output;
Output.mPosition = mul( Input.mPosition, gWorldMatrix );
float3 lightDir = Output.mPosition.xyz - gWorldLightPosition.xyz;
lightDir = normalize(lightDir);
float3 viewDir = normalize(Output.mPosition.zyz - gWorldCameraPosition.xyz);
Output.mViewDir = viewDir;
Output.mPosition = mul( Output.mPosition, gViewMatrix );
Output.mPosition = mul( Output.mPosition, gProjectionMatrix );
float3 worldNormal = mul(Input.mNormal, (float3x3)gWorldMatrix);
worldNormal = normalize(worldNormal);
Output.mDiffuse = dot(-lightDir, worldNormal);
Output.mReflection = reflect(lightDir, worldNormal);
return( Output );
}
난반사광에서 추가된 코드는 몇줄 안됩니다.
float4 gWorldCameraPosition;
먼저 현재 카메라위치값을 전역변수로 받아옵니다.
struct VS_OUTPUT
{
float4 mPosition : POSITION0;
float3 mDiffuse : TEXCOORD1;
float3 mViewDir:TEXCOORD2;
float3 mReflection:TEXCOORD3;
};
정반사광의 계산은 거듭제곱이 필요한데 거듭제곱값은 보간기를 거쳐 나오는 것과 아닌 것의 차이가 큽니다. 따라서 올바른 계산을 위해 내적연산을 하지않은 단위벡터값을 픽셀셰이더로 넘겨줍니다.
float3 viewDir = normalize(Output.mPosition.zyz - gWorldCameraPosition.xyz);
Output.mViewDir = viewDir;
현재위치에서 카메라위치를 빼서 카메라벡터를 구하고 정규화시킵니다.
Output.mReflection = reflect(lightDir, worldNormal);
reflect 함수는 반사벡터값을 구해주는 함수입니다. 입사광 벡터와 기준이 되는 반사면의 법선벡터를 넘겨줘 반사 벡터값을 구해줍니다.
2.픽셀셰이더
struct PS_INPUT
{
float3 mDiffuse : TEXCOORD1;
float3 mViewDir: TEXCOORD2;
float3 mReflection: TEXCOORD3;
};
float specularAmount = 20.0f;
float4 ps_main(PS_INPUT Input) : COLOR0
{
float3 diffuse = 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, specularAmount);
}
float3 ambient = float3(0.1f,0.1f,0.1f);
return float4(ambient + diffuse + specular ,1);
}
struct PS_INPUT
{
float3 mDiffuse : TEXCOORD1;
float3 mViewDir: TEXCOORD2;
float3 mReflection: TEXCOORD3;
};
먼저 정점셰이더 받은 카메라벡터와 반사벡터값을 입력값으로 받습니다. 이 값들은 보간기를 거쳐서 나온 값으로 픽셀마다 적용할 수 있게 됩니다.
float3 reflection = normalize(Input.mReflection);
float3 viewDir = normalize(Input.mViewDir);
다만 보간기를 거칠 때 값이 흐트러질 수 있으므로 다시 한번 정규화를 해줍니다.
if(diffuse.x>0)
{
specular = saturate(dot(reflection,-viewDir));
specular = pow(specular, specularAmount);
}
계산을 줄이기 위해 난반사광이 0이하일 때는 계산하지 않습니다. 난반사광이 존재하지 않는 표면에는 이미 빛이 닿지 않기 때문입니다. 내적을 구할 때 viewDir에 -를 하는 것도 전과 같이 두 벡터의 밑동이 만나야하는 이유입니다.
float3 ambient = float3(0.1f,0.1f,0.1f);
return float4(ambient + diffuse + specular ,1);
ambient값은 주위가 너무 어두워서 모델이 아예 안보이는 경우가 없도록 최소한의 조명값을 넣어주는 것입니다. 이 때문에 값이 1이 넘을 수 있겠지만 친철한 hlsl이 1로 맞춰줍니다.
'DirectX' 카테고리의 다른 글
셰이더 공부 - 스페큘러 맵핑 (0) | 2018.02.26 |
---|---|
셰이더 공부 - 툰셰이더 (0) | 2018.02.25 |
셰이더 공부 - 난반사광 (0) | 2018.02.24 |
셰이더공부 - 텍스쳐 매핑 (0) | 2018.02.23 |
셰이더 공부 - Color Shader (0) | 2018.02.23 |