momodudu.zip

Metal 스터디(7) - Matrix 적용해보기 ( Projection Transform ) 본문

ios/Metal

Metal 스터디(7) - Matrix 적용해보기 ( Projection Transform )

ally10 2022. 2. 10. 16:07

 

이전의 Model Transform이 매쉬를 이루는 vertex들을 Local Cooridnate -> World Coordinate로 바꾸는 변환이었다면, Projection Transform은 camera coordinate -> normalized coordinate로 바꾸어주는 변환이다.

 

projection type에는 두가지가 있는데, orthogonal과 perspective 변환이 있다. orthogonal은 말그대로 직교 변환이고, perspective는 원근법을 구사한 변환이다.

 

앞전에 생성했던 Cube에 perspective projection을 적용해보자.

 

    var matrix: Matrix4
    var projectionMatrix: Matrix4

projectionMatrix를 저장해둘 변수를 하나 만들고,

 

        // uniform buffer 고정길이이므로, 미리 생성만 해둔다.
        uniformBuffer = device.makeBuffer(length: MemoryLayout<Float>.size * Matrix4.numberOfElements() * 2, options: [])!

앞전에 생성했던 uniform buffer를 생성할때 길이를 * 2를 해준다.

왜냐하면.. 이 유니폼 버퍼에 앞 포스팅에서 생성했던 model transform matrix와 projection matrix를 함께 넣어줄것이기 때문이다.

 

그래서 uniform buffer를 반환할때도 

 

    public var uniformBufferData: MTLBuffer {
        get {
            let bufferPointer = uniformBuffer.contents()
            let size = MemoryLayout<Float>.size * Matrix4.numberOfElements()
            
            memcpy(bufferPointer, matrix.raw(), size * 2)
            memcpy(bufferPointer + size, projectionMatrix.raw(), size)
            
            return uniformBuffer
        }
    }

이렇게 두번 copy가 필요하다.

최초로는 model transformation, 그 뒤에 바로 붙여서 projection matrix를 카피한다.

 

그리고 쉐이더도 살짝 수정해주자.

struct Uniforms {
    float4x4 modelMatrix;   // 매트릭스를 전달받을 타입 선언
    float4x4 projMatrix;    // projection Matrix
};

vertex VertexOut vertex_main(
                             const device VertexIn* vertex_array [[buffer(0)]],
                             const device Uniforms& uniforms [[buffer(1)]],
                             unsigned int vid [[vertex_id]] ) {
    
    float4x4 mvMat = uniforms.modelMatrix;
    float4x4 projMat = uniforms.projMatrix;
                                 
    VertexIn vertexIn = vertex_array[vid];
    VertexOut vertexOut;
                                 
    vertexOut.position = projMat * mvMat * float4(vertexIn.position, 1.0);
    vertexOut.color = vertexIn.color;

    return vertexOut;
}

 

Uniforms 구조체에 projection matrix가 추가됐고, vertexOut에도 projection 매트릭스를 곱해준다.

 

이렇게하면 projection을 구현할 준비가 완료됐다.

 

extension Renderable {
    public func translate(x: Float, y: Float, z: Float) {
        matrix.translate(x, y: y, z: z)
    }
    
    public func rotate(x: Float, y: Float, z: Float) {
        matrix.rotateAroundX(x, y: y, z: z)
    }
    
    public func scale(_ scale: Float) {
        matrix.scale(scale, y: scale, z: scale)
    }
    
    public func makePerspectiveViewAngle(degree: Float, viewRatio: Float, nearZ: Float, farZ: Float) {
        projectionMatrix = Matrix4.makePerspectiveViewAngle(Matrix4.degrees(toRad: degree), aspectRatio: viewRatio, nearZ: nearZ, farZ: farZ)
    }
}

그리고 마지막으로 Renderable에 perspective view를 생성하는 함수도 추가해준다.

 

인자에 해당하는 persepctive view를 생성하는 함수인데, degree는 fovy를 degree로 받고, viewRatio는 aspectRatio 즉 뷰의 종횡비를 넣어주면 된다. 마지막 nearZ와 farZ는 projection plan의 Z값을 설정한다.

 

이렇게 작성을 완료했으면, 큐브를 여러가지 변환을 이용해서 표현을 해볼건데

        renderable?.translate(x: 0.0, y: 0.0, z: -2.0)
        renderable?.rotate(x: 0.0, y: 0.0, z: Matrix4.degrees(toRad: 45))
        renderable?.scale(0.5)
        
        renderable?.makePerspectiveViewAngle(degree: 30, viewRatio: viewRatio, nearZ: 0.01, farZ: 100.0)

위와 같이 변환이 적용되었을 때 결과값이 어떻게 될지 예측해보자.

 

먼저, 큐브의 좌표들은 아래와 같다.

        let A = Vertex(x: -1.0, y: 1.0, z: 1.0, color: NSColor.red)
        let B = Vertex(x: -1.0, y: -1.0, z: 1.0, color: NSColor.green)
        let C = Vertex(x: 1.0, y: -1.0, z: 1.0, color: NSColor.blue)
        let D = Vertex(x: 1.0, y: 1.0, z: 1.0, color: NSColor.brown)
        
        let Q = Vertex(x: -1.0, y: 1.0, z: -1.0, color: NSColor.red)
        let R = Vertex(x: 1.0, y: 1.0, z: -1.0, color: NSColor.green)
        let S = Vertex(x: -1.0, y: -1.0, z: -1.0, color: NSColor.blue)
        let T = Vertex(x: 1.0, y: -1.0, z: -1.0, color: NSColor.blue)

이걸 그림으로 표현해보면 아래와 같다. 알기 쉽게 그림으로 표현한것이지만, 실제로는 perspective projection을 할 것이므로 앞면은 작게, 뒷면은 더 크게 나온다.

 

먼저 전체 점이 z축으로 -2만큼 이동변환을 했으니 뒤로 간다. 이걸 뒤로 이동시키는 이유는...

 

정의한 projection matrix는 먼저 near가 0.1 이고 far가 100이다. 카메라의 z값이 0에 위치해있고, projection의 view frustum은 -Z축을 향하고 있다. 그래서 저 좌표 그대로 transformation없이 z가 [1,0~-1.0]인걸 띄워버리면, view frustum영역 밖으로 벗어나게 되어버린다.

 

그래서 z축으로 -2.0만큼 이동시켜준것이다. 뷰 프러스텀 내에 큐브가 들어올 수 있도록..

 

그리고 z축에 대해 45도만큼 돌린다.

그래서 45도만큼 기울어진 , 정면에서 봣을 때 마름모와 비슷한 형태를 가지게 되고, scaling도 곱해줬으니 사이즈가 좀 더 작아진다.

 

projection matrix는 fovy가 degree로 85.0, nearZ = 0.1, farZ = 100.0인 projection 매트릭스가 완성되어 결과물은 아래와 같이 표현된다.

 

응? 큐브가 아닌데..? 싶겠지만 원근법이 적용되었고, 큐브 내부가 안그릴부분은 안그려서 모양이 조금 이상하다.