momodudu.zip
Metal 스터디(6) - matrix 적용해보기 ( Model Transformation ) 본문
이제 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
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에 대해서는 다음 포스팅에서~
'ios > Metal' 카테고리의 다른 글
Metal 스터디 (8) - View Transformation, Viewport transformation (0) | 2022.03.19 |
---|---|
Metal 스터디(7) - Matrix 적용해보기 ( Projection Transform ) (0) | 2022.02.10 |
Metal 스터디(5) - MSL에서 packed_float 과 non-packed_float의 차이 (0) | 2022.02.09 |
Metal 스터디 (4) - MSL (0) | 2022.02.08 |
Metal 스터디 (3) - Rendering Pipeline (0) | 2021.11.26 |