새소식

인기 검색어

게임 개발/그래픽스

조명(GLM행렬), 노멀 벡터 변환

  • -

복잡한 변환을 여태까지 배운 어파인 변환을 이용해서 행렬 하나로 처리할 수 있다.

mat4 modelMatrix // worldMatrix 라고도 부른다.

mat4 invTranspose // Normal 벡터에 사용한다.

https://www.scratchapixel.com/lessons/mathematics-physics-for-computer-graphics/geometry/transforming-normals.html

 

Geometry

We briefly mentioned what normals were in the first chapter of this lesson. A surface normal from a surface at P is a vector perpendicular to the tangent plane to that surface at P. We will learn more about how to calculate normals as we get to the lessons

www.scratchapixel.com

왜 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

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.