Camera and View Matrix (Metal Part 10)

z4gon
z4gon

Implementing a Camera Component to calculate a view matrix. Updating the view matrix and passing it down to the GPU. Using the view matrix during the vertex shader function to transform the vertex coordinates to view space.

Source Code

See Project in GitHub 👩‍💻

References


Table of Content


Camera Component

The Camera is implemented as a component attached to a game object, it reuses the position of the transform to calculate the view matrix.

The calculation of the view matrix is done in the early update method.

1class Camera : Component, EarlyUpdatable { 2 3 public var viewMatrix: float4x4 = matrix_identity_float4x4 4 public var type: CameraType = CameraType.Perspective 5 6 func updateViewMatrix() { 7 var result: float4x4 = matrix_identity_float4x4 8 9 // the world needs to move in the opposite direction than the camera 10 result.translate(position: -gameObject.position) 11// result.scale(scale: gameObject.scale) 12// 13// result.rotateX(angle: gameObject.rotation.x) 14// result.rotateY(angle: gameObject.rotation.y) 15// result.rotateZ(angle: gameObject.rotation.z) 16 17 viewMatrix = result 18 } 19 20 // to ensure all other components get the accurate camera position 21 func doEarlyUpdate(deltaTime: Float) { 22 updateViewMatrix() 23 } 24}

The Debug Camera will use the arrow keys to move horizontally and vertically.

This is done in the early update as well, so that other components can use the accurate camera position. (Such as getting the world position corresponding to the mouse position)

1class DebugCameraComponent : Camera { 2 3 override func doEarlyUpdate(deltaTime: Float) { 4 5 if(Keyboard.isKeyPressed(KeyCodes.upArrow)){ 6 gameObject.position.y += deltaTime 7 } 8 9 if(Keyboard.isKeyPressed(KeyCodes.downArrow)){ 10 gameObject.position.y -= deltaTime 11 } 12 13 if(Keyboard.isKeyPressed(KeyCodes.leftArrow)){ 14 gameObject.position.x -= deltaTime 15 } 16 17 if(Keyboard.isKeyPressed(KeyCodes.rightArrow)){ 18 gameObject.position.x += deltaTime 19 } 20 21 super.doEarlyUpdate(deltaTime: deltaTime) 22 } 23}

The components can then use the camera position when trying to get the mouse position in the screen.

1public static func getMouseViewportPosition(_ camera: Camera)->float2{ 2 let viewportPosition = getMouseViewportPosition() 3 return float2(viewportPosition.x + camera.gameObject.position.x, viewportPosition.y + camera.gameObject.position.y) 4}

Scene

The Scene now holds a struct with the Scene Constants, which will include the view matrix and the projection matrix for the perspective projection.

The view matrix is updated by the camera itself, and since the camera will be used as the main camera in the scene, it will be used to update the view matrix in the scene.

Once per render cycle, the scene will pass the Scene Constants with the view matrix to the GPU as bytes by value. Similarly to how the Mesh Renderer passes the model constants with the model matrix to the GPU.

1class Scene : Transform { 2 3 private var _sceneConstants: SceneConstants! = SceneConstants() 4 5 func updateSceneConstants() { 6 _sceneConstants.viewMatrix = CameraManager.mainCamera.viewMatrix 7 } 8 9 override func render(renderCommandEncoder: MTLRenderCommandEncoder) { 10 11 updateSceneConstants() 12 13 // set the view matrix 14 renderCommandEncoder.setVertexBytes(&_sceneConstants, length: SceneConstants.stride, index: 2) 15 16 super.render(renderCommandEncoder: renderCommandEncoder) 17 } 18}

Shader

The Vertex Shader function will pick up the Scene Constants and the view matrix off of the buffer.

It will multiply the matrices with the object space position of the vertex, to transform the coordinates to view space.

1float4 viewSpaceCoordinates = viewMatrix * modelMatrix * objectSpaceCoordinates
1vertex FragmentData basic_vertex_shader( 2 // metal can infer the data because we are describing it using the vertex descriptor 3 const VertexData IN [[ stage_in ]], 4 constant ModelConstants &modelConstants [[ buffer(1) ]], 5 constant SceneConstants &sceneConstants [[ buffer(2) ]] 6){ 7 FragmentData OUT; 8 9 // return the vertex position in homogeneous screen space 10 // ProjectionMatrix * ViewMatrix * ModelMatrix * ObjectPosition = HSCPosition 11 OUT.position = sceneConstants.viewMatrix * modelConstants.modelMatrix * float4(IN.position, 1); 12 13 OUT.color = IN.color; 14 15 return OUT; 16}

Result

Pressing the keyboard arrows now moves the camera along the x and y coordinates.

Picture