Vulkan is the next generation API for high-performance graphics and compute, and JOML now fully supports it.
Where and how does JOML have to be compatible with a graphics/compute API and what is different compared to OpenGL?
OpenGL uses a NDC-space z/depth range of [-1..+1]. -1 is at the near clipping plane and +1 at the far clipping plane.
The matrices generated by JOML's various perspective and orthogonal projection methods in Matrix4f and Matrix4d assumed this depth range.
Vulkan on the other hand uses the different range [0..+1], with 0 being at the near clipping plane.
JOML supports this with an additional optional boolean parameter to all those matrix methods, indicating whether the zero-to-one depth range should be used.
Matrix4f proj = new Matrix4f()
(float) width / height, 0.1f, 10.0f, true); // <- true for zero-to-one depth range
OpenGL specifies that by default the viewport's origin is at the bottom-left. If that is the case, OpenGL gives a clip space to NDC space transformation which inverts the +Y axis to become -Y (see section "13.6 Coordinate Transformations" in the OpenGL specification) to properly transform into window coordinates, which have their +Y axis pointing downwards.
In an everyday OpenGL application this results in clip space having its +Y pointing upwards.
In Vulkan, this is not the case anymore. Vulkan does not do the +Y to -Y axis inversion anymore in its own clip space to NDC space transformation (see section "23.4 Coordinate Transformations" in the Vulkan specification).
Also, Vulkan assumes a viewport origin at the top-left by default.
This leads to the clip space having its +Y axis pointing downwards, contrary to OpenGL's behaviour.
When porting an OpenGL application to Vulkan, because of this, everything will be rendered upside-down.
To correct this, the Y-axis needs to be inverted manually when building the clip space transformation matrix, like so:
Matrix4f m = new Matrix4f()
.scale(1, -1, 1) // <- invert Y axis
(float) width / height, 0.1f, 10.0f, true) // <- true for zero-to-one depth range
.lookAt(0, 1, 3, // <- your normal lookAt
0, 0, 0,
0, 1, 0)
.rotateY(angle); // <- your normal model transformations
This still handles vertex winding order correctly (does not change it) and therefore also does not affect backface culling.