momodudu.zip
Metal 스터디 (8) - View Transformation, Viewport transformation 본문
이전에 렌더링 파이프라인에서 가장 기본적인 Model Transformation(Translate, Scale, Rotate)와 입체적으로 표현할 수 있게 해주는 Projection transformation에 대해서 알아보았다. 이 두가지 기본 변환 이외에도, 렌더링 파이프라인 내에서는 추가적인 변환이 일어나는데, 그것이 바로 View translation과 Viewport transformation이다.
- View transformation
이 변환은 예전에도 설명을 했었다. 지금까지의 예제는 전부 metal의 스크린 좌표 기준으로 모든 정점을 구성했다. 그렇지만 우리가 그릴건 특정 world좌표에 있는 object일것이고, 그 기준들은 무수히 많다. 이런것들을 하나로 모아서 카메라 안에 담기게 어떻게 그릴까? 이때 필요한것이 바로 View transformation이다. 이 변환은 Model, Projection변환과 함께 이루어지는데 주로 세개의 변환을 합해서 mvp변환이라고 많이들 말한다. 자세한건 아래 포스팅 참조.
https://ally10.tistory.com/19?category=827689
- Viewport transformation
이 변환은 개발자가 직접 하는건 아니고, 메탈 내부 렌더링 파이프라인 내에서 일어난다. 내가 만든 화면안에 들어올것들을 모두 NDC라는 공간 안에 담아서, 기기 화면에 표현할 수 있게 해주는 변환이다. 우리가 projection 변환으로 얻어낸 뷰포트를 NDC로 변환해서, 화면에 표현할 수 있는 볼륨에 맞는 크기로 변환하는 과정이다.
1. View Transformation
View Transformation은 각각 그려진 매쉬들을 카메라 좌표계로 변환해준다. 즉, 카메라 좌표를 정의해줄 필요가 있다.
// matrix 생성
modelMatrix = Matrix4()
projectionMatrix = Matrix4()
// camera
cameraPosition = Vertex(x: 0, y: 0, z: 0)
기존의 매트릭스를 modelMatrix로 구별해놓고, camera위치를 정의해줄 cameraPosition변수를 설정한다.
public func setCamera(x: Float, y: Float, z: Float) {
cameraPosition.x = x
cameraPosition.y = y
cameraPosition.z = y
}
그리고 위와 같이 camera를 정의하는 함수를 하나 판다.
그리고 이제 view matrix를 쉐이더에 넘겨줘야하는데.. view matrix의 경우, 별도로 쉐이더에 재정의하지는 않고 model matrix에 행렬곱을 통해서 아래와 같이 기존의 Model matrix를 넘겨주던 유니폼버퍼에 같이 넘겨준다. 주의할점은 modelMatrix는 이미 설정해놓은 내부 변수이고, modelMatrix.multiplyLeft(viewMatrix)를 할 경우 연산이 누적되므로, 현재 modelMatrix를 카피한 임시 변수를 만들어서 거기다가 viewMatirx를 곱해줘야 한다는 점이다. modelMatrix에 바로 행렬곱을 할 경우, 연산이 누적되므로 주의!
public var uniformBufferData: MTLBuffer {
get {
let bufferPointer = uniformBuffer.contents()
let size = MemoryLayout<Float>.size * Matrix4.numberOfElements()
var viewMatrix = Matrix4()
viewMatrix.translate(cameraPosition.x, y: cameraPosition.y, z: cameraPosition.z)
let tmp = modelMatrix.copy()
tmp.multiplyLeft(viewMatrix)
memcpy(bufferPointer, tmp.raw(), size)
memcpy(bufferPointer + size, projectionMatrix.raw(), size)
return uniformBuffer
}
}
원래는 쉐이더에 아래와 같이 매트릭스를 집어넣을 유니폼을 정의했었다.
struct Uniforms {
float4x4 modelMatrix; // 매트릭스를 전달받을 타입 선언
float4x4 projMatrix; // projection Matrix
};
위에 코드에서 봤듯이, view matrix를 곱해준 model-view transformation matrix를 위 코드의 modelMatrix에 해당하는 유니폼에 전달할것이다. 헷갈리지 않도록 modelMatrix라는 변수명을 mvMatrix로 바꾸자. 그럼 쉐이더는 변수명을 제외하고는 아무것도 바뀌지 않았다.
renderable?.translate(x: 0.0, y: 0.0, z: -2.0)
renderable?.rotate(x: 0, y: 0, z: (Matrix4.degrees(toRad: 45)))
renderable?.scale(0.5)
기존에 정의해뒀던 매트릭스들을 잘 보이게 하기위해서 위와 같은 여러가지 변환들을 했었다. ( model transformation ) 즉, 물체를 잘 보이게 하기 위해서 물체를 직접 이동시켰다.
이제 카메라 시점을 정의할 수 있게 되었으니, 위 코드는 다 지우고 카메라 시점만 정의를 해보자.
// renderable?.translate(x: 0.0, y: 0.0, z: -2.0)
// renderable?.rotate(x: 0, y: 0, z: (Matrix4.degrees(toRad: 45)))
// renderable?.scale(0.5)
renderable?.setCamera(x: 0, y: 0, z: 0.0)
카메라의 위치 뿐만 아니라 카메라의 각도를 조절하면서 좀 더 다양하게 보여줄수도 있다. 카메라 각도를 설정할 수 있는 함수를 파고,
public func setCameraOrientation(x: Float, y: Float, z: Float) {
roll = x
pitch = y
yaw = z
}
유니폼 버퍼를 가져올때 view matrix에 translate 변환 이외에도 회전 변환을 추가해준다.
public var uniformBufferData: MTLBuffer {
get {
let bufferPointer = uniformBuffer.contents()
let size = MemoryLayout<Float>.size * Matrix4.numberOfElements()
let viewMatrix = Matrix4()
viewMatrix.translate(cameraPosition.x, y: cameraPosition.y, z: cameraPosition.z)
viewMatrix.rotateAroundX(roll, y: pitch, z: yaw)
let tmp = modelMatrix.copy()
tmp.multiplyLeft(viewMatrix)
memcpy(bufferPointer, tmp.raw(), size)
memcpy(bufferPointer + size, projectionMatrix.raw(), size)
return uniformBuffer
}
}
기존에는 고정된 카메라 시점에 물체를 움직여서 잘 보이게 했다면, 이번에는 물체를 이동시키거나 작게하거나 하지 않고 카메라 시점만을 바꿔가면서 물체를 잘 보이게 한다. 이 자체가 바로 view transformation이다.
2. Backface culling
그리고 여기서 돌리다보면 큐브가 모양이 이상하게 보일 수 있는데, 그릴부분과 안그릴부분을 명확하게 정의해주지 않아서 그렇다. Metal은 큐브의 앞면보다 뒷면을 먼저 그린다. 그러다보니 모양이 조금 이상해보인다.
이걸 고칠 수 있는 방법은 두가지인데, 첫번째는 바로 Depth testing이다. 즉, depth 값을 통해서 화면에 똑같은 위치에 뭔가가 그려졌을 때, depth값이 더 작은애를 그린다. 두번째 방법은 backface culling이다. 직역하자면, "뒷면 걸러내기"인데, 그릴 매쉬를 구성하는 각 traingle의 정점 구성 방향을 보고, 향하는 노말벡터를 구한다. 그래서 이 노말벡터와 카메라의 방향을 체크해서, 렌더링 파이프라인에서 알아서 버려준다.
정점은 다 구성되어 있으니, backface culling 설정만 해주면 된다.
renderEncoder.setCullMode(MTLCullMode.front)
렌더 인코더 생성할 때 컬모드를 설정만 해주면 된다. front face만 걸러내서 그려준다는 의미이다.
이제 불필요한 뒷면까지 그리지 않아서 깔끔한 큐브가 되었다.
'ios > Metal' 카테고리의 다른 글
Metal 스터디(7) - Matrix 적용해보기 ( Projection Transform ) (0) | 2022.02.10 |
---|---|
Metal 스터디(6) - matrix 적용해보기 ( Model Transformation ) (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 |