- Notifications
You must be signed in to change notification settings - Fork106
A Java math library for OpenGL rendering calculations
License
JOML-CI/JOML
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
JOML – Java OpenGL Math Library
A Java math library for OpenGL rendering calculations | use it on:Desktop /Android /GWT
The goal of JOML [ʤˈɒml̩] is to provide easy-to-use, feature-rich and efficient linear algebra operations, needed by any 3D application. At the same time, JOML tries to pose the lowest possible requirements to an execution environment by being compatible with Java 1.4 and not making use of JNI.
If you like to know more about JOML's design, see the correspondingWiki page.
All operations in JOML are designed to modify the object on which the operation is invoked. This helps in completely eliminating any object allocations, which the client could otherwise not control and which impact the GC performance resulting in small hickups.The client is responsible to allocate the needed working objects.
Vector3fv =newVector3f(0.0f,1.0f,0.0f);Vector3fa =newVector3f(1.0f,0.0f,0.0f);// v = v + av.add(a);// a = a x va.cross(v);// a = a/|a|a.normalize();
Using JOML you can build matrices out of basic transformations, such as scale, translate and rotate, using a fluent interface style. All such operations directly modify the matrix instance on which they are invoked.The following example builds a transformation matrix which effectively first scales all axes by 0.5and then translates x by 2.0:
Vector3fv = ...;newMatrix4f().translate(2.0f,0.0f,0.0f) .scale(0.5f) .transformPosition(v);// v is now transformed by the specified transformation
Common transformation idioms, such as rotating about a given axis using a specific rotation center, can be expressed in a simple way. The following example rotates the point (0, 4, 4) about the x-axis and uses (0, 3, 4) as the rotation center:
Vector3fcenter =newVector3f(0.0f,3.0f,4.0f);Vector3fpointToRotate =newVector3f(0.0f,4.0f,4.0f);newMatrix4f().translate(center) .rotate((float)Math.toRadians(90.0f),1.0f,0.0f,0.0f) .translate(center.negate()) .transformPosition(pointToRotate);
The vectorpointToRotate will now represent (0, 3, 5).
All transformation operations in the matrix and quaternion classes act in the same way as OpenGL and GLU by post-multiplying the operation's result to the object on which they are invoked. This allows to chain multiple transformations in the same way as with OpenGL's legacy matrix stack operations, and allows to decompose the resulting effective matrix as a decomposition of multiple matrix multiplications.One such common decomposition are theprojection andmodelview matrices, written as:P * MV
. Themodelview matrix of course can be further decomposed into individual matrix multiplications, as far as this seems necessary.
When invoking transformation methods in JOML's matrix classes, a convenient way now is to think of Java'sdot operator as a matrix multiplication. If multiple matrix operations are chained after one another, as shown in the above example, each individual operation/method creates its matrix which is then post-multiplied to the matrices built before.
In addition to the post-multiplying methods, there are still ways to set a matrix or quaternion to a given transformation regardless of what that matrix or quaternion was before:
Matrix4fm =newMatrix4f();Vector3fpoint =newVector3f(1.0f,2.0f,3.0f);Vector3foffset =newVector3f(1.0f,0.0f,0.0f);...m.translation(offset).transformPosition(point);
In the above example, the matrixm is being set to a translation, instead of applying the translation to it.These methods are useful when the same matrix is being used in a sequence of consecutive operations or repeatedly in a loop without having to set it to the identity each time.
In the same way that you can concatenate multiple simple affine transformations, you can use the methods perspective(), frustum() and ortho() to specify a perspective or orthographic projection and lookAt() to create an orthonormal transformation that mimics a cameralooking at a given point.Those methods resemble the ones known from GLU and act in the same way (i.e. they apply their transformations to an already existing transformation):
Matrix4fm =newMatrix4f() .perspective((float)Math.toRadians(45.0f),1.0f,0.01f,100.0f) .lookAt(0.0f,0.0f,10.0f,0.0f,0.0f,0.0f,0.0f,1.0f,0.0f);// the camera transformation is now in m
The above transformation can then be used as a "view-projection" matrix in a shader.
By convention, the methodsperspective()
andlookAt()
will assume a right-handed coordinate system. This convention was established for OpenGL and first realized in the OpenGL Utility Library (GLU). JOML follows this convention.
In addition, JOML also supports a left-handed coordinate system, as is used by Direct3D's matrix library. To use a left-handed coordinate system, there are the methodsperspectiveLH()
andlookAtLH()
, as well as others, whose names end withLH
:
Matrix4fm =newMatrix4f() .perspectiveLH((float)Math.toRadians(45.0f),1.0f,0.01f,100.0f) .lookAtLH(0.0f,0.0f, -10.0f,0.0f,0.0f,0.0f,0.0f,1.0f,0.0f);
Usually, instance methods operate on the object (matrix, vector, quaternion) on which they are invoked by writing the computation result back into that object. Most of the methods however also allow to specify another destination object to write the result into. This is useful if you do not want to overwrite the original object with the computation result.This can be useful for computing the view-projection matrix and its inverse in one go:
Matrix4fviewProj =newMatrix4f();Matrix4finvViewProj =newMatrix4f();viewProj.perspective((float)Math.toRadians(45.0f),1.0f,0.01f,100.0f) .lookAt(0.0f,1.0f,3.0f,0.0f,0.0f,0.0f,0.0f,1.0f,0.0f) .invert(invViewProj);
TheinvViewProj matrix now contains the inverse of theviewProj matrix, but the latter is still intact.
Using withLWJGL
JOML can be used together with LWJGL to build a transformation matrix and set it as a uniform mat4 in a shader. For this, the Matrix4f class provides a method to transfer a matrix into a Java NIO FloatBuffer, which can then be used by LWJGL when calling into OpenGL:
try (MemoryStackstack =MemoryStack.stackPush()) {FloatBufferfb =newMatrix4f() .perspective((float)Math.toRadians(45.0f),1.0f,0.01f,100.0f) .lookAt(0.0f,0.0f,10.0f,0.0f,0.0f,0.0f,0.0f,1.0f,0.0f) .get(stack.mallocFloat(16));glUniformMatrix4fv(mat4Location,false,fb);}
The above example first creates a transformation matrix and then uploads that matrix to a uniform variable of the active shader program using the LWJGL 3 methodglUniformMatrix4fv.
Also please readMemory Management in LWJGL 3 on how to properly manage native memory that JOML will store the matrices to.
Instead of using the uniform methods, one or multiple matrices can also be uploaded to an OpenGL buffer object and then sourced from that buffer object from within a shader when used as an uniform buffer object or a shader storage buffer object.The following uploads a matrix to an OpenGL buffer object which can then be used as an uniform buffer object in a shader:
Matrix4fm = ...;// <- the matrix to uploadintubo = ...;// <- name of a created and already initialized UBOtry (MemoryStackstack =MemoryStack.stackPush()) {glBindBuffer(GL_UNIFORM_BUFFER,ubo);glBufferSubData(GL_UNIFORM_BUFFER,0,m.get(stack.mallocFloat(16)));}
If you prefer not to use shaders but the fixed-function pipeline and want to use JOML to build the transformation matrices, you can do so. Instead of uploading the matrix as a shader uniform you can then use the OpenGL API callglLoadMatrixf() provided by LWJGL to set a JOML matrix as the current matrix in OpenGL's matrix stack:
Matrix4fm =newMatrix4f();m.setPerspective((float)Math.toRadians(45.0f),1.0f,0.01f,100.0f);glMatrixMode(GL_PROJECTION);try (MemoryStackstack =MemoryStack.stackPush()) {glLoadMatrixf(m.get(stack.mallocFloat(16)));}m.setLookAt(0.0f,0.0f,10.0f,0.0f,0.0f,0.0f,0.0f,1.0f,0.0f);glMatrixMode(GL_MODELVIEW);try (MemoryStackstack =MemoryStack.stackPush()) {glLoadMatrixf(m.get(stack.mallocFloat(16)));}
You can useVK10.vkMapMemory() provided by LWJGL to map a Vulkan memory object, which may be the backing store of a Uniform Buffer, into Java and use the returned Java NIO ByteBuffer to upload the matrix like you would with OpenGL by callingget() on the matrix:
Matrix4fm = ...;VkDevicedevice = ...;// <- the vulkan devicelongmemory = ...;// <- handle to the vulkan device memorytry (MemoryStackstack =MemoryStack.stackPush()) {PointerBufferpb =stack.mallocPointer(1);if (vkMapMemory(device,memory,0,16 <<2,0,pb) ==VK_SUCCESS) {m.get(MemoryUtil.memByteBuffer(pb.get(0),16 <<2));vkUnmapMemory(device,memory); }}
Since Vulkan uses a clip space z range between0 <= z <= w you need to tell JOML about it when creating a projection matrix. For this, the projection methods on the Matrix4f class have an additional overload taking a boolean parameter to indicate whether Z should be within [0..1] like in Vulkan or [-1..+1] like in OpenGL. The existing method overload without that parameter will default to OpenGL behaviour.
Alternatively, you can use Vulkan's Push Constants to quickly upload a matrix in a shader push-constant when recording a command buffer. The following code updates a Matrix4f used as a push-constant in the vertex shader:
Matrix4fm = ...;VkCommandBuffercmdBuf = ...;// <- the VkCommandBufferlonglayout = ...;// <- handle to a Vulkan VkPipelineLayouttry (MemoryStackstack =MemoryStack.stackPush()) {vkCmdPushConstants(cmdBuf,layout,VK_SHADER_STAGE_VERTEX_BIT,0,m.get(stack.mallocFloat(16)));}
Also, care must be taken regarding the difference between Vulkan's viewport transformation on the one side and Direct3D's and OpenGL's different viewport transformation on the other side. Since Vulkan does not perform any inversion of the Y-axis from NDC to window coordinates, NDC space and clip space will have its +Y axis pointing downwards (with regard to the screen).In order to account for this, you need to use a premultiplied scaling transformation that inverts the Y-axis.
In essence, to create a projection transformation which will work with Vulkan, use the following code:
Matrix4fm =newMatrix4f();m.scale(1.0f, -1.0f,1.0f)// <- inversion of Y axis .perspective((float)Math.toRadians(45.0f),1.0f,0.01f,100.0f,true);// <- true indicates Z in [0..1]
Using withJOGL
JOML can be used together with JOGL to build a transformation matrix and set it as a uniform mat4 in a shader (for example as a replacement of com.jogamp.opengl.util.glsl.fixedfunc.FixedFuncUtil and com.jogamp.opengl.util.PMVMatrix to emulate the fixed pipeline). For this, the Matrix4f class provides a method to transfer a matrix into a Java NIO FloatBuffer, which can then be used by JOGL when calling into OpenGL:
FloatBufferfb =Buffers.newDirectFloatBuffer(16);newMatrix4f().perspective((float)Math.toRadians(45.0f),1.0f,0.01f,100.0f) .lookAt(0.0f,0.0f,10.0f,0.0f,0.0f,0.0f,0.0f,1.0f,0.0f).get(fb);gl.glUniformMatrix4fv(mat4Location,1,false,fb);
The above example first creates a transformation matrix and then uploads that matrix to a uniform variable of the active shader program using the JOGL 2 methodglUniformMatrix4fv.
If you prefer not to use shaders but the fixed-function pipeline and want to use JOML to build the transformation matrices, you can do so. Instead of uploading the matrix as a shader uniform you can then use the OpenGL API callglLoadMatrixf() provided by JOGL to set a JOML matrix as the current matrix in OpenGL's matrix stack:
FloatBufferfb =Buffers.newDirectFloatBuffer(16);Matrix4fm =newMatrix4f();m.setPerspective((float)Math.toRadians(45.0f),1.0f,0.01f,100.0f);gl.glMatrixMode(GLMatrixFunc.GL_PROJECTION);gl.glLoadMatrixf(m.get(fb));m.setLookAt(0.0f,0.0f,10.0f,0.0f,0.0f,0.0f,0.0f,1.0f,0.0f);gl.glMatrixMode(GLMatrixFunc.GL_MODELVIEW);gl.glLoadMatrixf(m.get(fb));
Using withJava 2D
Java 2D holds the transformation applied to drawn primitives in an instance of theAffineTransform class, which can be manipulated directly or operated on via methods on theGraphics2D class, such asrotate() ortranslate().Instead of the AffineTransformation, JOML's matrix classes can be used to build the transformations. When a matrix has been built in JOML, its represented transformation can be copied to an AffineTransform instance like so:
Matrix4fm = ...;// <- any affine 2D transformation built with JOMLAffineTransformt = ...;// <- any Java 2D AffineTransform (e.g. Graphics2D.getTransform())t.setTransform(m.m00(),m.m01(),m.m10(),m.m11(),m.m30(),m.m31());
The above will copy any affine 2D transformation, represented by the Matrix4f, to the provided AffineTransform instance.
Since Java 2D cannot represent 3D transformations, using a Matrix4f in JOML is not necessary. For 2D transformations JOML provides the Matrix3x2f and Matrix3x2d classes. If those are used, copying the 2D transformation into a Java 2D AffineTransform instance works like this:
Matrix3x2fm = ...;// <- any 2D transformation built with JOMLAffineTransformt = ...;// <- any Java 2D AffineTransformt.setTransform(m.m00,m.m01,m.m10,m.m11,m.m20,m.m21);
(Note: The AffineTransform class uses a different order for the row and column indices of a matrix element. Here, the row index comes first, and then the column index)
Using withJavaFX
JavaFX holds arbitrary affine transformations in an instance of theAffine class.Instead of operating on Affine objects, JOML's matrix classes can be used to build the transformations. When a matrix has been built in JOML, its represented transformation can be copied to an Affine instance like so:
Matrix4fm = ...;// <- any affine transformation built with JOMLAffinet = ...;// <- any JavaFX Affine instancet.setToTransform(m.m00(),m.m10(),m.m20(),m.m30(),m.m01(),m.m11(),m.m21(),m.m31(),m.m02(),m.m12(),m.m22(),m.m32());
The above will copy any affine transformation, represented by the Matrix4f, to the provided Affine instance.
(Note: The Affine class uses a different order for the row and column indices of a matrix element. Here, the row index comes first, and then the column index)
JOML is designed to be completely allocation-free for all methods. That means JOML will never allocate Java objects on the heap unless you as the client specifically requests to do so via thenew keyword when creating a new matrix or vector or calling thetoString() method on them.
JOML also does not allocate any unexpected internal helper/temporary/working objects itself, neither in instance nor static fields, thus giving you full control over object allocations.
Since you have to create a matrix or a vector at some point in order to make any computations with JOML on them, you are advised to do so once at the initialization of your program. Those objects will then be theworking memory/objects for JOML. These working objects can then be reused in the hot path of your application without incurring any additional allocations. The following example shows a typical usecase with LWJGL:
FloatBufferfb;Matrix4fm;voidinit() {fb =MemoryUtil.memAllocFloat(16);m =newMatrix4f(); ...}voiddestroy() {MemoryUtil.memFree(fb);}voidframe() { ...// compute view-projection matrixm.identity() .perspective((float)Math.toRadians(45.0f), (float)width/height,0.01f,100.0f) .lookAt(0.0f,0.0f,10.0f,0.0f,0.0f,0.0f,0.0f,1.0f,0.0f);// possibly apply more model transformationsm.rotateY(angle);// get matrix into FloatBuffer and upload to OpenGLglUniformMatrix4fv(mat4Location,false,m.get(fb)); ...}
In the example above, a single Matrix4f is allocated during some initialization time when theinit() method is called. Then eachframe() we reinitialize the same matrix with theidentity() and recompute the camera transformation based on some other parameters.
In more complex applications with multiple API providers and consumers/clients, it is sometimes desirable to communicate the intent that a certain object returned by an API call or consumed by a call should not get modified by the caller or the callee, respectively. This can be helpful to encourage efficient memory usage by sharing objects and to discourage/disallow unintended object mutations.
For this, JOML provides read-only views on each class, realized via a Java interface implemented by each class. This interface only contains methods that will not mutate the object on which it is called.
privateVector4fsharedVector;publicVector4fcgetVector() {returnsharedVector;}publicvoidconsume(Vector4fcv) {// ...}
Using read-only views, it is possible to declare who is responsible of mutating an object and who is not allowed to.
Due to JOML not using any internal temporary objects during any computations, you can use JOML in a multithreaded application. You only need to make sure not to call a method modifying the same matrix or vector from two different threads. Other than that, there is no internal or external synchronization necessary.
JOML also features an interface that resembles the matrix stack from legacy OpenGL.This allows you to use all of the legacy OpenGL matrix stack operations even in modern OpenGL applications,but without the otherwise necessary JNI calls into the graphics driver.Note that JOML does not interface in any way with the OpenGL API. It merely provides matrix and vector arithmetics.
Matrix4fStacks =newMatrix4fStack(2);s.translate(2.0f,0.0f,0.0f);s.pushMatrix();{s.scale(0.5f,0.5f,0.5f);// do something with the Matrix4f of 's'}s.popMatrix();s.rotate((float)Math.toRadians(45.0f),0.0f,0.0f,1.0f);// do something with the Matrix4f of 's'
This section contains a list of books/tutorials/articles featuring JOML.
About
A Java math library for OpenGL rendering calculations