코드가 방대해졌을 때 버그가 발생하면 어떻게 찾을 수 있을까?
00. Light.fx의 Emissive가 잘못 동작하는데 공식이 이상하게 아닌가 제보가 왔다.
1. Emissive 적용해 실행하고 문제점 파악하기
NormalMappingDemo::Init에서
MaterialDesc& desc = material->GetMaterialDesc();
desc.ambient = Vec4(1.f);
desc.diffuse = Vec4(1.f);
desc.specular = Vec4(1.f);
desc.emissive = Vec4(1.f);
이렇게 material에서 emissive를 1로 켜주고
NormalMappingDemo::Update에서
{
LightDesc lightDesc;
lightDesc.ambient = Vec4(0.f);
lightDesc.diffuse = Vec4(0.f);
lightDesc.specular = Vec4(0.f);
lightDesc.emissive = Vec4(1.f, 0.f, 0.f, 1.f);
lightDesc.direction = Vec3(1.f, 0.f, 1.f);
RENDER->PushLightData(lightDesc);
}
light에서 emission을 빨간색으로 켜주고
나머지는 0.f로 꺼준다.
실행을 해 보면
이렇게 외곽만 보인다. 정상이다.
하지만 카메라를 움직여보면 외곽선으로 보여야 정상인데 그러지 않고 있다.
2. 마지막으로 작업한 파일에서 이상한 점이 있는지 확인하고 고치기
일단 마지막에 작업한 파일에 가서 잘못한 게 없는지 살펴본다. 14. NormalMapping.fx에 가서 VS를 보니 잘못된 게 있었다.
MeshOutput VS(VertexTextureNormalTangent input)
{
MeshOutput output;
output.position = mul(input.position, W);
output.worldPosition = input.position.xyz;
output.position = mul(output.position, VP);
output.uv = input.uv;
output.normal = mul(input.normal, (float3x3)W);
output.tangent = mul(input.tangent, (float3x3)W);
return output;
}
input.position이라고 들어오는게 사실 world 좌표가 아니라 local 좌표였고 거기서 W를 곱해줘서 world position으로 변화했으니까 그걸 output.worldPosition에 저장하고 싶었던 건데 input.position.xyz;을 저장하고 있었다. 이걸 output.position.xyz; 로 해야 한다.
output.worldPosition = output.position.xyz;
이것만으로는 아직 해결이 안되는 걸 테스트해보면 알 수 있다.
CPU연산하는 건 break point 걸어서 볼 수 있지만 shader는 break point가 걸리지 않는다.
GPU가 실행하는 것이라 디버깅을 쉽게 할 수 없다 .
외부 프로그램을 깔아서 메모리 상태를 살펴보는 건 가능하다.
근데 그렇게 하느니 정상적 수치가 들어갔는지 볼 수 있는 무언가를 하는 게 나을 것이다.
3. 가설을 세우고 후보를 줄여가기
다음으로 생각하는 건 Effect11 라이브러리가 문제가 있는 건가 가설을 세워본다. 카메라 좌표가 갱신이 안되고 있는 거 같은데 갱신이 되려면 00. Global.fx에서
//////////////
// Function //
//////////////
float3 CameraPosition()
{
return -V._41_42_43;
}
여기에 넣어주고 있었다.
V행렬이 갱신이 안되고 있던 게 아닐까 의심을 할 수 있다.
하지만 14. NormalMapping.fx의 VS에서
output.position = mul(output.position, VP);
이건 정상적으로 되고 있다.
V문제는 또 아닌 거 같다.
이런 식으로 가설을 세우고 후보를 줄여가고 하면서 쭉 가면 된다.
RenderManager::PushGlobalData를 보면
void RenderManager::PushGlobalData(const Matrix& view, const Matrix& projection)
{
_globalDesc.V = view;
_globalDesc.P = projection;
_globalDesc.VP = view * projection;
_globalBuffer->CopyData(_globalDesc);
_globalEffectBuffer->SetConstantBuffer(_globalBuffer->GetComPtr().Get()); // 00. Global.fx의 GlobalBuffer로 밀어 넣는 작업을 해주고 있는 거다.
}
V, P가 정상적으로 들어간 거 같다.
마지막으로 카메라 좌표 구하는 공식이 문제가 있는 게 아닐까 의심을 할 수 있다.
Camera::UpdateMatrix에서
void Camera::UpdateMatrix()
{
Vec3 eyePosition = GetTransform()->GetPosition();
Vec3 focusPosition = eyePosition + GetTransform()->GetLook();
Vec3 upDirection = GetTransform()->GetUp();
S_MatView = ::XMMatrixLookAtLH(eyePosition, focusPosition, upDirection);
//S_MatView = GetTransform()->GetWorldMatrix().Invert();
S_MatProjection = ::XMMatrixPerspectiveFovLH(_fov, _width / _height, _near, _far);
}
이 부분은 정상적으로 되는 거 맞고
void RenderManager::PushGlobalData(const Matrix& view, const Matrix& projection)
{
_globalDesc.V = view;
_globalDesc.P = projection;
_globalDesc.VP = view * projection;
_globalBuffer->CopyData(_globalDesc);
_globalEffectBuffer->SetConstantBuffer(_globalBuffer->GetComPtr().Get()); // 00. Global.fx의 GlobalBuffer로 밀어 넣는 작업을 해주고 있는 거다.
}
여기도 다 정상적인데 왜 문제가 되는 걸까
4. 의심 가는 부분을 제어하며 출력값 확인해서 문제 지점 찾기
00. Global.fx의
float3 CameraPosition()
{
return -V._41_42_43;
}
을 디버깅 하고 싶은데 쉐이더의 유용한 기능 중 하나는 색상을 이용해 건네주기 때문에
14. NormalMapping.fx의 PS에서
float4 PS(MeshOutput input) : SV_TARGET
{
ComputeNormalMapping(input.normal, input.tangent, input.uv);
float4 color = ComputeLight(input.normal, input.uv, input.worldPosition);
return color;
}
원래는 ComputeLight 해서 결과물을 return 해주고 있었는데
그게 아니라 임시적으로
float4 PS(MeshOutput input) : SV_TARGET
{
ComputeNormalMapping(input.normal, input.tangent, input.uv);
float4 color = ComputeLight(input.normal, input.uv, input.worldPosition);
float3 pos = CameraPosition();
return float4(pos.xyz, 1);
}
CameraPosition을 색상으로 해서 출력을 해본다.
카메라의 위치를 전후로 움직일 때는 색이 안 변하는데(많이 움직여야 변한다) 회전을 하면 색이 변하는 것을 볼 수 있다.
로테이션을 해야 카메라의 포지션 값이 즉각 변한다는 걸 알 수 있다.
이로서 카메라 position에 문제가 있다는 걸 알게 되었다.
Camera Position 공식에 문제가 없었을까 의심을 할 수 있다.
5. 문제 원인 발견 후 해결책 세우기
00. Global.fx의
float3 CameraPosition()
{
return -V._41_42_43;
}
에서 기억을 살려서 보니까
V에서 Camera position을 추출할 수 있는 건 있지만
공식을 살펴보면 잘못된 게 있다.
뷰 변환 행렬이란 게
로컬→월드→카메라 기준으로 좌표 변환 하는 게 뷰 변환이었다.
카메라의 위치를 0, 0, 0으로 하고 카메라가 바라보는 룩벡터를 z 축으로 만드는 걸 View space라 했었다.
카메라를 0, 0, 0 기준으로 하는게 다시 말하면 카메라의 로컬 스페이스 이기도 하기 때문에 View로 가는 것의 반대로 가는 게 역행렬인데, 뷰의 역행렬이라는 게 카메라 기준으로 봤을 때 로컬에서 월드로 가는 것이기 때문에 월드이기도 했다.
만능 공식으로 최종 식을 구할 수 있었는데
이렇게 나온다.
간과한 사실이 있었다.
카메라가 오른쪽으로 이동하면 모든 애들이 왼쪽으로 이동하고 하는 건 맞기 때문에 _41_42_43에 -Cx -Cy -Cz이 들어가는 게 맞다는 생각이 든다.
하지만 곱셈의 결과로 [_41_42_43]에 [-C벡터와 Right벡터의 내적, -C벡터와 Up벡터의 내적, -C벡터와 Look 벡터의 내적]이 들어간다. 즉 카메라가 회전하게 되면 이런 식으로 회전 성분이 섞여 들어가기 때문에 V행렬의 _41_42_43을 꺼내 -를 한다고 해서 카메라의 좌표가 되는 건 아니었다.
좀 더 자세한 설명
카메라 위치를 구하는 함수 CameraPosition()에서 -V._41_42_43를 사용하는 것과 관련된 설명이 타당한지 여부를 논하는 것입니다. 여기서 V는 카메라의 뷰 행렬(View Matrix)을 나타내며, _41_42_43는 이 행렬의 마지막 열(일반적으로 변환 성분을 나타냄)을 지칭합니다.
카메라의 위치를 얻기 위해 -V._41_42_43를 사용하는 것은 일반적으로 뷰 행렬의 작동 방식을 고려할 때 오해를 불러일으킬 수 있습니다. 뷰 행렬은 월드 공간에서 카메라 공간으로 좌표를 변환하기 위해 사용됩니다. 이 변환 과정은 카메라의 위치와 방향을 기반으로 합니다. 그러나, 뷰 행렬의 마지막 열 _41_42_43에 -Cx -Cy -Cz (여기서 C는 카메라 위치)를 단순히 넣는 것만으로는 카메라의 실제 위치를 정확하게 반영하지 않습니다. 이는 뷰 행렬이 카메라의 월드 공간에서의 위치와 방향을 기반으로 생성되기 때문입니다.
카메라가 오른쪽으로 이동할 경우, 실제로는 월드 공간에서의 모든 객체가 카메라와 반대 방향으로 이동하는 것처럼 보이게 만들어야 합니다. 이는 뷰 행렬을 통해 실현됩니다. 뷰 행렬은 카메라의 위치와 방향에 따라 월드 공간의 좌표를 카메라 공간의 좌표로 변환합니다. 이 과정에서 카메라의 움직임은 실제로는 세계를 반대 방향으로 움직이는 것처럼 처리됩니다.
뷰 행렬의 마지막 열 _41_42_43는 원점에서 카메라 위치까지의 변환을 나타내지만, 이는 카메라가 월드 공간에서 어떻게 위치하고 있는지를 직접적으로 나타내는 것이 아니라, 카메라 공간으로의 변환 과정에서 어떻게 계산되는지를 나타냅니다. 따라서 -V._41_42_43가 카메라의 실제 위치를 나타낸다고 단정 지을 수 없습니다. 특히 카메라가 회전할 때, -V._41_42_43에는 회전으로 인해 변환된 좌표가 포함되기 때문에, 이 값이 단순히 카메라의 월드 공간에서의 위치를 나타내는 것은 아닙니다.
결론적으로, 설명에서 언급된 것처럼 -V._41_42_43를 사용하는 것이 카메라의 위치를 정확하게 반영하지 않는 이유는, 카메라가 회전할 때 회전 성분이 이 값에 섞여 들어가기 때문입니다. 카메라의 실제 위치를 얻기 위해서는 뷰 행렬을 더 정밀하게 분석하거나 다른 방법을 사용해야 합니다.
실제로 카메라의 좌표를 구하려면 뷰행렬의 역행렬을 구해야 한다. 뷰행렬의 역행렬이 결국 World행렬이고, 월드 행렬에는 _41_42_43위치에 카메라의 포지션이 들어 있다.
00. Global.fx의
float3 CameraPosition()
{
return -V._41_42_43;
}
이것에 문제가 있었다는 게 결론이고
View의 역행렬을 CPU에서 넘겨주는 게 괜찮다는 생각이 든다.
6. 해결책 실행하기
00. Global.fx의 cbuffer GlobalBuffer에 matrix VInc;를 추가한다.
cbuffer GlobalBuffer
{
matrix V;
matrix P;
matrix VP;
matrix VInv;
};
그리고
float3 CameraPosition()
{
return VInv._41_42_43;
}
이렇게 바꿔준다.
이거에 따라서 나머지 부분도 바꿔야 한다.
RenderManager.h에서
struct GlobalDesc
{
Matrix V = Matrix::Identity;
Matrix P = Matrix::Identity;
Matrix VP = Matrix::Identity;
Matrix VInv = Matrix::Identity;
};
VInv를 넣어 줄 것이고,
RenderManager::PushGlobalData에서
void RenderManager::PushGlobalData(const Matrix& view, const Matrix& projection)
{
_globalDesc.V = view;
_globalDesc.P = projection;
_globalDesc.VP = view * projection;
_globalDesc.VInv = view.Invert();
_globalBuffer->CopyData(_globalDesc);
_globalEffectBuffer->SetConstantBuffer(_globalBuffer->GetComPtr().Get()); // 00. Global.fx의 GlobalBuffer로 밀어 넣는 작업을 해주고 있는 거다.
}
이렇게 Vinv에 값을 넣고 Copy 하는 식으로 동작을 시키면 나머지 부분들은 문제가 없다.
물론 굳이 View의 역행렬을 넣어주는 게 별로다 싶으면
카메라 자체의 position을 Pos4 같은 걸로 넘겨줘도 무방하다.
Engine을 리빌드 하고
실행을 하면
이제 카메라를 이동해도 Emissive가 유지된다.
지금까지 카메라 좌표가 문제가 있었구나가 결론이 될 수 있다.
7. 맺음말_ 가설을 세우고 헤딩하는 게 중요하다
버그는 아니지만 결이 비슷한 게 있는데
앞으로 라이브러리를 많이 사용할 예정이다.
async라는 fbx나 여러 아트 리소스를 로딩하는 그런 라이브러리를 추가할 것이고, imgui도 추가할 것인데 처음에 아무것도 모르는 상태에서 라이브러리, 엔진을 공부할 때 두려움을 없애는 것도 버그를 찾는 것과 결이 비슷하다.
신입때와 지금과를 비교해 보면 코딩 능력으론 그다지 성장하지 않았다.
알고스팟이나 알고리즘 사이트를 많이 풀었기 때문에 코딩 능력은 가장 절정이었고,
현업에 가면 오히려 간단한 걸 하기 때문에 알고리즘 적으로는 줄어들고
설계적인 능력은 좋아진다.
신입 땐 어떤 기능을 만들 때 겁이 났다.
결국 버그 찾는 거랑 비슷한 게 가설을 하나 새우고 용감하게 가면 되는 것처럼 경험이 쌓이면 어떻게는 넘어갈 수 있구나라는 그 경험이 축적이 되다 보면 자연스럽게 넘어갈 수 있게 된다.
라이브러리를 사용하는 것도 막막하다.
async나 imgui도 튜토리얼도 별로 없다. 그러면 그냥 헤딩을 해서 가설을 세우면서 이건 이렇게 되지 않을까라고 생각을 하고 일단은 부딪혀 보다 보면 그러면서 기능을 하나씩 넣다 보면 많은 것을 얻게 된다.
뭔가 완벽하게 하려 하기보다는 실시간으로 이것저것 해보고 마치 내가 프로그래머가 아니라 화학자가 된 느낌으로 실험을 통해 하다 보면 해결되는 문제가 많다.
남의 자료에 의존하면 안 된다.
본인이 헤딩하는 게 중요하다. 는게 결론이다.
'DirectX' 카테고리의 다른 글
53. 모델_Material 로딩 (0) | 2024.02.21 |
---|---|
52. 모델_Assimp (0) | 2024.02.18 |
50. Light, Material_Normal Mapping (0) | 2024.02.17 |
49. Light, Material_Material (0) | 2024.02.16 |
48. Light, Material_Light 통합 (0) | 2024.02.15 |
댓글