0
votes

Question

I'm working on porting from OpenGL (OGL) to MetalKit (MTK) on iOS. I'm failing to get identical display in the MetalKit version of the app. I modified the projection matrix to account for differences in Normalized Device Coordinates between the two frameworks, but don't know what else to change to get identical display. Any ideas what else needs to be changed to port from OpenGL to MetalKit?

Projection Matrix Changes so far...

I understand that the Normalized Device Coordinates (NDC) are different in OGL vs MTK:

  • OGL NDC: -1 < z < 1
  • MTK NDC: 0 < z < 1

I modified the projection matrix to address the NDC difference, as indicated here. Unfortunately, this modification to the projection matrix doesn't result in identical display to the old OGL code.

I'm struggling to even know what else to try.

Background

For reference, here's some misc background information:

  • The view matrix is very simple (identity matrix); i.e. camera is at (0, 0, 0) and looking toward (0, 0, -1)
  • In the legacy OpenGL code, I used GLKMatrix4MakeFrustum to produce the projection matrix, using the screen bounds for left, right, top, bottom, and near=1, far=1000

I stripped the scene down to bare bones while debugging and below are 2 images, the first from legacy OGL code and the second from MTK, both just showing the "ground" plane with a debug texture and a black background.

Any ideas about what else might need to change to get to identical display in MetalKit would be greatly appreciated.

Screenshots

OpenGL (legacy)

OpenGL Scene

MetalKit

MetalKit Scene

Edit 1

I tried to extract code relevant to calculation and use of the projection matrix:

float aspectRatio = 1.777; // iPhone 8 device
float top = 1;
float bottom = -1;
float left = -aspectRatio;
float right = aspectRatio;
float RmL = right - left;
float TmB = top - bottom;
float nearZ = 1;
float farZ = 1000;

GLKMatrix4 projMatrix = {   2 * nearZ / RmL,     0,                      0,                               0,
                            0,                   2 * nearZ / TmB,        0,                               0,
                            0,                   0,                      -farZ / (farZ - nearZ),          -1,
                            0,                   0,                      -farZ * nearZ / (farZ - nearZ),  0 };

GLKMatrix4 viewMatrix = ...; // Identity matrix: camera at origin, looking at (0, 0, -1), yUp=(0, 1, 0);
GLKMatrix4 modelMatrix = ...; // Different for various models, but even when this is the identity matrix in old/new code the visual output is different
GLKMatrix4 mvpMatrix = GLKMatrix4Multiply(projMatrix, GLKMatrix4Multiply(viewMatrix, modelMatrix));

...

GLKMatrix4 x = mvpMatrix; // rename for brevity below
float mvpMatrixArray[16] = {x.m00, x.m01, x.m02, x.m03, x.m10, x.m11, x.m12, x.m13, x.m20, x.m21, x.m22, x.m23, x.m30, x.m31, x.m32, x.m33};

// making MVP matrix available to vertex shader
[renderCommandEncoder setVertexBytes:&mvpMatrixArray
                              length:16 * sizeof(float)
                             atIndex:1]; // vertex data is at "0"

[renderCommandEncoder setVertexBuffer:vertexBuffer
                               offset:0
                              atIndex:0];

...

[renderCommandEncoder drawPrimitives:MTLPrimitiveTypeTriangleStrip
                         vertexStart:0
                         vertexCount:4];
2

2 Answers

1
votes

Sadly this issue ended up being due to a bug in the vertex shader that was pushing all geometry +1 on the Z axis, leading to the visual differences.

For any future OpenGL-to-Metal porters: the projection matrix changes above, accounting for the differences in normalized device coordinates, are enough.

0
votes

Without seeing the code it's hard to say what the problem is. One of the most common issues could be a wrongly configured viewport:

// Set the region of the drawable to draw into.
[renderEncoder setViewport:(MTLViewport){0.0, 0.0, _viewportSize.x, _viewportSize.y, 0.0, 1.0 }];

The default values for the viewport are:

originX = 0.0
originY = 0.0
width = w
height = h
znear = 0.0
zfar = 1.0

enter image description here

*Metal: znear = minZ, zfar = maxZ.

MinZ and MaxZ indicate the depth-ranges into which the scene will be rendered and are not used for clipping. Most applications will set these members to 0.0 and 1.0 to enable the system to render to the entire range of depth values in the depth buffer. In some cases, you can achieve special effects by using other depth ranges. For instance, to render a heads-up display in a game, you can set both values to 0.0 to force the system to render objects in a scene in the foreground, or you might set them both to 1.0 to render an object that should always be in the background.

Applications typically set MinZ and MaxZ to 0.0 and 1.0 respectively to cause the system to render to the entire depth range. However, you can use other values to achieve certain affects. For example, you might set both values to 0.0 to force all objects into the foreground, or set both to 1.0 to render all objects into the background.