게임 개발/그래픽스
조명(GLM행렬), 노멀 벡터 변환
- -
복잡한 변환을 여태까지 배운 어파인 변환을 이용해서 행렬 하나로 처리할 수 있다.
mat4 modelMatrix // worldMatrix 라고도 부른다.
mat4 invTranspose // Normal 벡터에 사용한다.
왜 normals에는 따로 다른 연산법을 적용할까?
-> normal에 non-uniform scale을 적용하면 normal의 의미가 훼손되어버린다.
쉽게말하면 서로 수직인 두 벡터를 한 평면에 두고 그 평면을 스케일링하는데 정확히 1대1이 유지되게 스케일링하지 않으면 수직이 아니게 되어버린다. 즉, normal이 아니게 되어버린다.
그럼 normal은 어떻게 transform해야 하는가?
-> 변환행렬에 inverse 에 Transpose를 하면 된다.
void Rasterization::Render(vector<vec4> &pixels) {
// 깊이 버퍼 초기화
this->depthBuffer.resize(pixels.size());
// 깊이 버퍼의 초기값은 렌더링할 가장 먼 거리를 설정해준다는 의미도
// 있습니다. 즉, 그 거리보다 더 멀리있는 픽셀은 무시하게 됩니다.
// DirectX에서는 내부적으로 렌더링할 공간을 스케일해서 가장 먼
// 거리를 1.0f으로 만들기 때문에 보통 1.0으로 초기화하지만,
// 여기서는 편의상 1.0보다 큰 값(예: 10.0f)을 사용하겠습니다.
fill(this->depthBuffer.begin(), this->depthBuffer.end(), 10.0f);
for (const auto &mesh : this->meshes) {
// 렌더링 하기 전에 필요한 데이터를
// GPU 메모리로 복사하는 것처럼 생각해주세요.
// constants.transformation = mesh->transformation;
// Vertex Shader에서 변환에 사용했던 코드
// vsOutput.position =
// RotateAboutX(
// RotateAboutY(vsInput.position * constants.transformation.scale,
// constants.transformation.rotationY),
// constants.transformation.rotationX) +
// constants.transformation.translation;
// 여기서 GPU에게 보내줄 변환 행렬을 만들어줘야 합니다.
// 순서 주의 (GLM은 column major 사용)
// constants.modelMatrix = ...;
constants.modelMatrix =
glm::translate(mesh->transformation.translation) *
glm::rotate(mesh->transformation.rotationX,
vec3(1.0, 0.0f, 0.0f)) *
glm::rotate(mesh->transformation.rotationY,
vec3(0.0f, 1.0f, 0.0f)) *
glm::scale(mesh->transformation.scale);
// column major에서는 먼저 적용되어야 하는 연산이 오른쪽에 와야 한다.
// 즉, scale, rotate, rotate, translate 순으로 연산되는 중.
// Non-uniform scale인 경우에만 필요
// constants.invTranspose = ...;
constants.invTranspose = constants.modelMatrix;
// 벡터는 어차피 translation이 적용이 안되기 때문에 안해도 되긴 하는데
// translation부분을 0으로 초기화 해주는것이 실무에서 수치에러를 줄이는데 도움이 된다.
constants.invTranspose[3] = vec4(0.0f, 0.0f, 0.0f, 1.0f);
constants.invTranspose = glm::inverseTranspose(constants.invTranspose);
// glm::transpose(glm::inverse(constants.invTranspose)); 이것과 같음
// 모델 변환 이외에도 시점 변환, 프로젝션 변환을 행렬로 미리 계산해서
// 쉐이더로 보내줄 수 있습니다.
constants.material = mesh->material;
constants.light = light;
constants.lightType = this->lightType;
this->vertexBuffer.resize(mesh->vertexBuffer.size());
this->normalBuffer.resize(mesh->normalBuffer.size());
this->colorBuffer.resize(mesh->vertexBuffer.size());
// this->uvBuffer.resize(mesh->uvBuffer.size());
// GPU 안에서는 멀티쓰레딩으로 여러 버텍스를 한꺼번에 처리합니다.
for (size_t i = 0; i < mesh->vertexBuffer.size(); i++) {
VSInput vsInput;
vsInput.position = mesh->vertexBuffer[i];
vsInput.normal = mesh->normalBuffer[i];
// vsInput.color = mesh->colorBuffer[i];
// vsInput.uv = mesh->uvBuffer[i];
auto vsOutput = MyVertexShader(vsInput);
this->vertexBuffer[i] = vsOutput.position;
this->normalBuffer[i] = vsOutput.normal;
// this->colorBuffer[i] = vsOutput.color;
// this->uvBuffer[i] = vsOutput.uv;
}
this->indexBuffer = mesh->indexBuffer;
for (size_t i = 0; i < this->indexBuffer.size(); i += 3) {
DrawIndexedTriangle(i, pixels);
}
}
}
VSOutput MyVertexShader(const VSInput vsInput) {
VSOutput vsOutput;
// 여기서 여러가지 변환 가능
// vsOutput.position =
// RotateAboutX(
// RotateAboutY(vsInput.position * constants.transformation.scale,
// constants.transformation.rotationY),
// constants.transformation.rotationX) +
// constants.transformation.translation;
// 마지막에 1.0f 추가
vec4 point =
vec4(vsInput.position.x, vsInput.position.y, vsInput.position.z, 1.0f);
// point = ...; // 주의: column-major
point = constants.modelMatrix * point;
vsOutput.position = vec3(point.x, point.y, point.z);
// 주의: 노멀 벡터도 물체와 같이 회전시켜줘야 합니다.
// 더 편하게 구현할 방법은 없을까요?
// vsOutput.normal = RotateAboutX(
// RotateAboutX(
// RotateAboutY(vsInput.normal, constants.transformation.rotationY),
// constants.transformation.rotationX),
// constants.transformation.rotationX);
// 마지막에 0.0f 추가
vec4 normal =
vec4(vsInput.normal.x, vsInput.normal.y, vsInput.normal.z, 0.0f);
// Unon-uniform transformation인 경우에는 보정 필요
// normal = ...; // 주의: column-major
normal = glm::normalize(constants.invTranspose * normal);
// 수학적으로는 constants.invTranspose * normal을 해도 길이가 1이 유지가 되어야 하지만,
// 수치 오류때문에 길이가 1이 아니게 될 가능성이 있어서 또 normalize를 해주는것이 좋다.
vsOutput.normal = vec3(normal.x, normal.y, normal.z);
return vsOutput;
return vsOutput;
}
'게임 개발 > 그래픽스' 카테고리의 다른 글
조명(SimpleMath) (0) | 2023.11.23 |
---|---|
DirectXMath(SimpleMath) (1) | 2023.11.22 |
행렬 (GLM) (1) | 2023.11.22 |
좌표계 변환 (0) | 2023.11.22 |
애파인 변환 (Affine Transformation) (0) | 2023.11.21 |
Contents
소중한 공감 감사합니다