Diffuse랑 비슷한데 다른 부분이 있다.
Diffuse에서는 normal 벡터와 Light의 각도가 핵심이었다.
Specular는 반사된 빛과 눈이 바라보는 방향 사이의 각도가 핵심이다.
마찬가지로 이 각도가 0도 가 되어서 일치가 된다면 눈부실 것이다.
각도가 커질 수록 금속에서 보이는 흰색으로 보이는 반사 부분이 옅어진다.
반사된 빛의 벡터를 어떻게 구할 수 있을까?
normal 벡터와 Light벡터의 내적을 해서 크기를 구한 다음에 그 크기 만큼을 두번 위로 이동하는 효과를 나타낼 수 있으면 된다.
물체에서 눈까지 가는 방향벡터를 구하려면 어떻게 해야 할까?
물체의 위치는 안다고 가정을 한다.
카메라의 위치가 결국엔 눈의 위치니까 월드좌표에서 카메라의 위치를 알면 카메라에서 물체의 위치 벡터의 뺄셈을 해주면 방향벡터가 나온다.
그렇게 되면 눈의 벡터와 반사된 빛의 벡터를 구할 수 있으니까
지금까지 했던 람베르트 공식 같은 걸 비슷하게 흘러가게 만들어 주면 코드가 완성이 된다.
카메라의 위치를 구하는 방법중 대표적으로 쉬운 방법중 하나가 뷰의 행렬을 이용하는 것이다.
View 행렬의 4행에 위치에 관련된 값이 있다.
View 행렬이라 함은 모든 물체들이 월드 좌표로 있었던 것을 카메라 기준으로 하는 좌표계로 변환하는 것을 View 변환행렬 이라고 했다.
카메라 위치로 이동한다는 건 모든 물체가 반대방향으로 이동한다는 얘기가 된다. 실제로 카메라의 좌표에 -를 붙인 값들이 들어가게 된다. 그래서 뷰 행렬에 4행에 있는 _41, _42, _43 값들을 추출해서 음수를 해주면 그게 카메라 좌표가 될 수 있다. 이는 카메라가 world의 어떤 위치에 있든지 간에, View 변환을 통해 카메라를 원점에 위치시키고, world를 카메라와 반대 방향으로 이동시키기 때문이다.
아니면 VIew의 역행렬을 구하면 4행의 _41, _42, _43 로도 구할 수 있다.
부품들이 여러가지가 있는데 하나씩 조립해서 만들면 된다. 라고 보면 된다.
그래서 Specular가 다른 아이들에 비해 어렵다.
작업을 해본다.
1. Lighting_Diffuse.fx와 DiffuseDemo클래스를 생성하고, SpecularDemo::Init와 Main에서 각각 세팅하고 사전 작업 하기
10. Lighting_Diffuse.fx를 복제해서 이름을 11. Lighting_Specular.fx로 한다. Client/Shaders/Week2에 배치한다.
13. DiffuseDemo클래스도 복제해서 14. SpecularDemo라고 하고 Client/Game/Week2에 넣는다.
늘 하던 사전 작업을 한다.
SpecularDemo::Init에서
_shader = make_shared<Shader>(L"11. Lighting_Specular.fx");
세팅한다.
Main에 가서
#include "14. SpecularDemo.h"
desc.app = make_shared<SpecularDemo>(); // 실행 단위
이렇게 바꿔준다.
Client를 빌드하면 빌드가 된다.
2. 11. Lighting_Specular.fx 쉐이더 작성하기
1) LightDiffuse, MaterialDiffuse 선언하기
11. Lighting_Specular.fx로 가서
float4 LightDiffuse;
float4 MaterialDiffuse;
는 삭제한다.
float4 LightSpecular; // 색상
float4 MaterialSpecular; // 물체가 받아주는 Specular의 양
를 추가한다.
원래는 VS에서
VertexOutput VS(VertexTextureNormal input)
{
VertexOutput output;
output.position = mul(input.position, W);
output.position = mul(output.position, VP);
output.uv = input.uv;
output.normal = mul(input.normal, (float3x3)W);
return output;
}
position에 WVP를 곱하고 있는데 뷰와 프로젝션까지 적용된 좌표계인데 World좌표가 아니라 Screen좌표가 된다.
원하는 정보는 World다.
World좌표를 넣어주고 싶다고 한다면 VP를 곱해서 날아가기 전에 기입을 해서 넘겨줘야 한다.
2) VS에서 worldPosition을 따로 저장하기 위해 00. Global.fx에 struct MeshOutput 정의하기
00. Global.fx 를 보면
struct VertexOutput
{
float4 position : SV_POSITION;
float2 uv : TEXCOORD;
float3 normal : NORMAL;
};
이렇게 VertexOutput이 있는데
MeshOutput이라고 해서 일반적인 Mesh가 튀어나올 떄의 정보를 적어 줄 것이다.
struct MeshOutput
{
float4 position : SV_POSITION;
float3 worldPosition : POSITION1;
float2 uv : TEXCOORD;
float3 normal : NORMAL;
};
이렇게 float3 worldPosition : POSITION1;을 추가했다.
조명과 관련된 연산을 할 때 world 좌표가 하나가 더 필요하기 떄문에 적어주는 것이다.
3) VS에서 worldPosition을 저장하기
VS에 돌아가서
MeshOutput VS(VertexTextureNormal input)
{
MeshOutput output;
output.position = mul(input.position, W);
output.worldPosition = input.position;
output.position = mul(output.position, VP);
output.uv = input.uv;
output.normal = mul(input.normal, (float3x3)W);
return output;
}
이렇게 넣어준 worldPosition을 받아서 작업을 해본다.
PS에서 앞으로 어떤 영역에서 연산을 할지를 골라줘야 한다.
월드 좌표를 할 거면 모든 좌표를 월드로 받아 준 다음에 하는 것도 방법이 될 수는 있겠지만,
일반적인 world좌표로 받는게 아니라 VIewSpace로 모든 좌표를 반환해가지고 그걸로 연산해 주는 경우도 있다. 상황에 따라 다르다.
VIewSpace하면 장점은 모든 물체들이 카메라 기준으로 하는 좌표로 되어 있으니까 굳이 카메라 위치를 월드 좌표로 빼서 하는 부분들이 생략될 수 있다.
경우에 따라서는 ViewSpace로 맞춰서 작업을 해주는 경우가 있다.
오늘은 world좌표에서 작업을 하는게 일반적이니까 world좌표에서 작업을 할 것이다.
4) PS에서 Reflect, CameraPosition, Eye, R과 E를 내적한 value를 제곱한 값을 LightSpecular, MaterialSpecular에 곱해서 color를 구해 return 하기
// Specular (반사광)
// 한방향으로 완전히 반사되는 빛 (Phong)
float4 PS(MeshOutput input) : SV_TARGET
{
// float3 R = reflect(LightDir, input.normal);
float3 R = LightDir - (2 * input.normal * dot(LightDir, input.normal));
R = normalize(R);
float3 cameraPosition = -V._41_42_43; // 아니면 View의 역행렬을 한 다음에 _41_42_43
float3 E = normalize(cameraPosition - input.worldPosition);
float value = saturate(dot(R, E)); // clamp(0~1)
float specular = pow(value, 10); // 제곱을 안하면 조금 더 영역이 넓게 되고, 좁힐 수록 효과가 커진다.
float4 color = LightSpecular * MaterialSpecular * specular;
return color;
}
이것도 정해진 공식은 없다.
Texture색상을 구해와서 샘플링 해줘서 거기에 곱해줘도 안되는 건 아니지만 보통은 눈뽕 효과 나타내는 경우가 많아서 보통은 텍스쳐랑 무관하게 가는 경우가 많다.
LightSpecular(빛) * MaterialSpecular(빛을 받아주는 비율) * specular(공식으로 구한 각도에 따른 값)
을 이용해서 테스트를 해본다.
3. SpecularDemo::Updated에서 11. Lighting_Specular.fx 쉐이더의 LightSpecular, MaterialSpecular, LightDir에 값을 세팅하기
SpecularDemo::Update에서
Specular에 대한 세팅을 다 해준 다음에 그걸 보고 정상적으로 동작하는지 본다.
lightDiffuse를 light로 rename한다.
void SpecularDemo::Update()
{
_camera->Update();
RENDER->Update();
//
Vec4 light{ 1.f};
_shader->GetVector("LightSpecular")->SetFloatVector((float*)&light);
Vec3 lightDir{ 1.f, 0.f, 0.f };
lightDir.Normalize();
_shader->GetVector("LightDir")->SetFloatVector((float*)&lightDir);
{
Vec4 material(1.f); // 비율
_shader->GetVector("MaterialSpecular")->SetFloatVector((float*)&material);
_obj->Update();
}
{
Vec4 material(1.f);
_shader->GetVector("MaterialSpecular")->SetFloatVector((float*)&material);
_obj2->Update();
}
}
4. 테스트 하기
실행을 하면
1) LightSpecular{1.f} ,lightDir{1.f 0.f, 0.f}, MaterialSpecular{1.f}일 경우 테스트하기
이렇게 보인다.
2) LightSpecular{1.f} ,lightDir{1.f -1.f, 0.f}, MaterialSpecular{1.f}일 경우 테스트하기
빛의 방향을 오른쪽 아래로 가는
Vec3 lightDir{ 1.f, -1.f, 0.f };
이렇게 해주면
왼쪽 위 부분만 밝게 빛난다.
Ambient, Diffuse와 결이 다르다.
3) PS에서 float specular = pow(value, 1); 이렇게 10에서 1로 바꾸고 테스트 해보기
PS에서 pow를
float specular = pow(value, 1); // 제곱을 안하면 조금 더 영역이 넓게 되고, 좁힐 수록 효과가 커진다.
이렇게 10에서 1로 바꿨을 때를 보면
1로 했을 떄는 영역이 넓어진다.
눈뽕의 효과를 나타내려면 이 영역이 좁아져야 하는데
pow를 하면 좋아지는 이유는
float value = saturate(dot(R, E)); // clamp(0~1)
float specular = pow(value, 1); // 제곱을 안하면 조금 더 영역이 넓게 되고, 좁힐 수록 효과가 커진다.
dot의 결과물 자체가 saturate까지 해서 0~1 숫자로 한 거다. 제곱해서 N승하면 작아질 수 밖에 없다.
그런 부분을 극대화 시키기 위해서 pow를 적용시킨 것이다.
제곱을 할 수록 좁아지면서 보정 효과를 낸다고 보면 된다.
5. 맺음말
수식을 이해해야 원하는 것을 응용해서 구현할 수 있다.
엔진에서 작업할 때도 같은 공식으로 이용한다.
중국에서 나온 책들이 수학 공식 이용해서 셰이더 분석한 책도 있다.
오늘 한 부분은 면접에서 자주 나온다.
abident, diffuse, specular 이 3가지 타입을 이제 합쳐주면 된다 .
3총사만 해도 그럴싸한 효과를 줄 수 있다.
3가지 각각의 특징을 잘 기억을 해야 한다.
'DirectX' 카테고리의 다른 글
48. Light, Material_Light 통합 (0) | 2024.02.15 |
---|---|
47. Light, Material_Emissive (0) | 2024.02.14 |
45. Light, Material_Diffuse (0) | 2024.02.13 |
44. Light, Material_Ambient (0) | 2024.02.13 |
43. Light, Material_Depth Stencil View (0) | 2024.02.11 |
댓글