momodudu.zip

Metal 스터디(6) - matrix 적용해보기 ( Model Transformation ) 본문

ios/Metal

Metal 스터디(6) - matrix 적용해보기 ( Model Transformation )

ally10 2022. 2. 10. 11:12

 

이제 matrix를 적용해보자.

 

matrix 적용 여부를 쉽게 알 수 있도록 3차원의 큐브 모양의 mesh를 하나 만든다.

matrix를 적용하기 전에는 아래처럼 그냥 평면에서 보고 있으므로, 네모난 모양만 보인다.

 

 

큐브처럼 그럴싸하게 보이려면.. 이제 matrix를 적용해줘야한다.

Transformation / Rotation / Scale등의 과정을 거쳐서 화면에서 봤을 때 큐브를 입체적으로 표시할 수 있다.

 

1) Model Transformation

현재 큐브를 구성하는 정점들의 좌표는 모두 Metal의 NDC범위 내에 있다. 그래서 x,y는 [-1,1] z는 [1,0]으로만 이루어져 있다.

하지만 이 좌표들이 wgs84좌표라면 ..? 이걸 어떻게 그려야지?

어떤 Mesh는 wgs84인데 또 어떤 mesh는 전혀 다른 좌표계를 가지고 있는데, 이걸 하나의 화면에 표현하려면 ?

싶을 때 Model transformation을 해줄 수 있다. 즉 local coordinate -> world coordinate로의 변환이 이루어진다.

 

자세한 포스팅은 아래 참고.

https://ally10.tistory.com/19?category=827689 

 

#5 Vertex Processing - 변환 매트릭스

행렬 관련 포스팅은 전에도 glm 라이브러리를 쓰면서 간단하게 썼었는데, 변환 과정은 봐도봐도 부족하니까 오늘은 좀 더 응용단계? 그래픽스 이론 관점에서 보는 변환 행렬이다. 우리가 흔히 말

ally10.tistory.com

 

2) Uniform Data를 쉐이더에 전달하기

model matrix는 작성이 됐다면, Metal에 이걸 uniform으로 넘겨주어야 한다.

matrix는 vertex마다 데이터를 보내는게 아니라, mesh하나에 일괄적으로 matrix가 적용되므로,  shader에 유니폼 데이터로 넣어준다.

 

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

 

매트릭스는 일단 위와 같이 간단하게 만들었다. Matrix4는 튜토리얼에서 제공하는 라이브러리이고, positionX, Y, Z등 matrix를 구성하는 요소들은 일단 다 0, scale은 1.0으로 설정해두었다. vertex buffer와 유사하게 이니셜라이징 단계에서 미리 생성해두는데, 유니폼 데이터의 경우 고정사이즈이므로 buffer 사이즈만큼 미리 만들어놓기만 한다.

 

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)
    }
}

그리고 변환을 위한 함수들을 추가한다. 각각 변환에 따라서 매트릭스를 수정한다.

 

여기서 Renderable은 내가 임의로 만든 클래스 타입인데.. draw call이 발생하는 렌더링이 가능한 mesh의 단위 정도라고 보면 되겠다. renderable을 한번 생성할 때 vertex buffer와 uniform buffer를 생성한다. 매쉬를 이루는 좌표계가 동일한 좌표계이기 때문에 uniform buffer는 매쉬 단위로 생성하지 않아도 되지 않나..?라는 생각이 들지만, 일단은 그건 나중에 개선하는걸로.

 

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

그리고 이 렌더러블로부터 uniform buffer를 가져올 때 현재까지 누적 변환한 매트릭스를 일전에 생성해두었던 유니폼버퍼에 카피해서 반환한다.

 

        renderEncoder.setVertexBuffer(renderable!.vertexBuffer, offset: 0, index: 0)
        renderEncoder.setVertexBuffer(renderable!.uniformBuffer, offset: 0, index: 1)

그리고 렌더링 루프에 위와 같이 유니폼 버퍼를 세팅해주는 코드를 추가한다. 이렇게 되면 uniform buffer를 렌더러블로부터 가지고 올때 변환이 누적된 matrix가 담긴 유니폼 버퍼를 가져올 수 있다.

윗줄은 vertex buffer를 세팅하는 부분이고, 밑에는 uniformBuffer를 세팅하는 부분이다. 여기서 유니폼 버퍼의 경우 인덱스가 1로 세팅이 된 것을 볼 수 있다.

 

 

3) 쉐이더 수정하기

이제 앞단에서 유니폼 버퍼를 전달했으니, 쉐이더를 수정한다.

 

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

struct VertexIn {
    packed_float3 position;
    packed_float4 color;
};

struct VertexOut {
    float4 position[[ position ]];
    float4 color;
};

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;
                                 
    VertexIn vertexIn = vertex_array[vid];
    VertexOut vertexOut;
                                 
    vertexOut.position = mvMat * float4(vertexIn.position, 1.0);
    vertexOut.color = vertexIn.color;

    return vertexOut;
}

 

먼저 유니폼으로 전달받을 매트릭스 타입을 선언하고, vertex function에 파라미터로 추가한다.

앞에서 buffer index 1에 유니폼 데이터를 전달했으니, 그대로 [[ buffer(1) ]]의 인덱스로 전달받는다.

 

그리고 vertexOut으로 나갈 vertex position에 해당 매트릭스를 곱해준다.

 

        renderable?.translate(x: -0.25, y: 0, z: 0)
        renderable?.rotate(x: 0.0, y: 0.0, z: Matrix4.degrees(toRad: 45))
        renderable?.scale(0.25)

이렇게 살짝 그리고자 하는 매쉬에 몇가지 변환 테스트를 해보면,

 

이렇게 처음과는 다른 모습을 볼 수 있다.

 

다만, 아직 Projection변환이 적용되지 않았으므로 원근법이 적용되지 않아 읭? 이게 뭐지 싶을 수 있다.

Projection에 대해서는 다음 포스팅에서~