I am trying to correctly translate and project points of a mesh to the surface of a window using shaders (the point of interest is the vertex shader which is responsible for outputting the final vertex positions).
The model (quad) is represented by the following vertices: [-0.5f, 0.5f, 0f] (V0), [-0.5f, -0.5f, 0f] (V1), [0.5f, -0.5f, 0f] (V2), [0.5f, 0.5f, 0].
I have the following pair of methods to create a perspective projection matrix:
public static Matrix4f getProjectionMatrix(float fovy, int width, int height, float zNear, float zFar) {
float aspectRatio = (float) width / height;
projectionMatrix.perspective(fovy, aspectRatio, zNear, zFar);
return projectionMatrix;
}
This method is found in the Transformations class creates and returns a perspective projection matrix.
fovy = The vertical field of view
width = The width of the window
height = The height of the window
zNear = The near clipping plane
zFar = The far clipping plane
public Matrix4f perspective(float fovy, float aspectRatio, float zNear, float zFar) {
float scale = (float) (Math.tan(fovy * 0.5) * zNear);
float top = scale;
float right = top * aspectRatio;
float bottom = -top;
float left = bottom * aspectRatio;
this.m00 = 2*zNear / (right - left);
this.m03 = (right + left) / (right - left);
this.m11 = 2*zNear / (top - bottom);
this.m12 = (top + bottom) / (top - bottom);
this.m22 = -(zFar + zNear) / (zFar - zNear);
this.m23 = -2*zFar*zNear / (zFar - zNear);
this.m32 = -1;
return this;
} This method is in the Matrix4f class.
Here is the beginning of the Matrix4f class, with the Matrix4f instance variables and the constructor:
public class Matrix4f {
float m00, m01, m02, m03;
float m10, m11, m12, m13;
float m20, m21, m22, m23;
float m30, m31, m32, m33;
public Matrix4f() {
m00 = 1.0f; m01 = 0.0f; m02 = 0.0f; m03 = 0.0f;
m10 = 0.0f; m11 = 1.0f; m12 = 0.0f; m13 = 0.0f;
m20 = 0.0f; m21 = 0.0f; m22 = 1.0f; m23 = 0.0f;
m30 = 0.0f; m31 = 0.0f; m32 = 0.0f; m33 = 1.0f;
}
After the constructor is called, the identity matrix is already created. The only thing left is to know how to initialize the perspective projection matrix. This is done when calling the constructor of class Transformations:
public class Transformations {
private Matrix4f translationMatrix;
private static Matrix4f projectionMatrix;
public Transformations() {
translationMatrix = new Matrix4f();
projectionMatrix = new Matrix4f();
}
Now for the translation matrix. The functionality for it's creation is provided in the Transformations class via the method getTranslationMatrix(float x, float y, float z)
which creates and returns a translation matrix using the parameters:
public Matrix4f getTranslationMatrix(float x, float y, float z) {
translationMatrix.m03 = x;
translationMatrix.m13 = y;
translationMatrix.m23 = z;
return translationMatrix;
}
To actually have something that represents a game model, I've created a class called GameEntity. It represents the model's mesh and position:
public class GameEntity {
private final Mesh mesh;
private Vector3f position;
public GameEntity(Mesh mesh) {
this.mesh = mesh;
position = new Vector3f(0, 0, 0);
}
public Vector3f getPosition() {
return position;
}
public void updatePosition(float x, float y, float z) {
position.x += x;
position.y += y;
position.z += z;
}
public Mesh getMesh() {
return mesh;
}
}
The updatePosition
method is supposed to move the entity around the window as a whole. I won't include code explanation for the Mesh class in here. All you need to know is that it holds mesh (vertex) data about a GameEntity, for example vertex positions, vertex colours, indices, etc, which are all stored inside a vertex array object which is then in turn used for rendering that Mesh instance onto the window. In my case, the two triangles which form a quad are represented as a GameEntity instance.
Moving the model: The updatePosition method is called every time any of the keys W, A, S, D, space or left shift are pressed. It updates the positon of the GameEntity instance (which is stored inside GameEntity[] entities
array at index 0) by a set amount:
private void processInput() {
glfwPollEvents();
if (window.keys[GLFW_KEY_W]) {
entities[0].updatePosition(0, 0, -1.0f);
} else if (window.keys[GLFW_KEY_S]){
entities[0].updatePosition(0, 0, 1.0f);
} else if (window.keys[GLFW_KEY_A]) {
entities[0].updatePosition(1.0f, 0, 0);
} else if (window.keys[GLFW_KEY_D]) {
entities[0].updatePosition(-1.0f, 0, 0);
} else if (window.keys[GLFW_KEY_SPACE]) {
entities[0].updatePosition(0, 1.0f, 0);
} else if (window.keys[GLFW_KEY_LEFT_SHIFT]) {
entities[0].updatePosition(0, -1.0f, 0);
}
}
This method is called inside the main game loop.
Then, inside the Renderer class, the translation matrix for the model is constructed based on it's position and the projection matrix is constructed based on the properties of the window object:
private ShaderProgram shaderProgram;
private Window window = new Window();
private final Transformations transformation;
private Matrix4f translationMatrix = new Matrix4f();
private Matrix4f projectionMatrix = new Matrix4f();
private static double angleOfView = 60.0;
private static final float FOVY = (float) Math.toRadians(angleOfView);
private static final float zNear = 0.01f;
private static final float zFar = 1000.0f;
shaderProgram.createUniform("translationMatrix");
shaderProgram.createUniform("projectionMatrix");
public void render(Window window, GameEntity[] entities) {
i++;
clear();
if (window.isResized()) {
glViewport(0, 0, window.getWidth(), window.getHeight());
window.setResized(false);
}
//make the shaders active
shaderProgram.bind();
//update the projection matrix
Matrix4f projectionMatrix = transformation.getProjectionMatrix(FOVY, window.getWidth(), window.getHeight(), zNear, zFar);
shaderProgram.setUniformMatrix("projectionMatrix", projectionMatrix);
//render each game item
for(GameEntity entity : entities) {
Matrix4f translationMat = transformation.getTranslationMatrix(entity.getPosition());
shaderProgram.setUniformMatrix("translationMatrix", translationMat);
entity.getMesh().render();
}
shaderProgram.unbind();
}
First, all of the uniform locations are defined (above the render()
method).
The clear()
method clears the rendering buffer - it prepares it to render a new image. In the following if clause, window resize operations are handled. If the window is resized, the if clause with corresponding methods will update the width
and height
of the window to match the resized window.
The projectionMatrix
is constructed out of variables which are defined as instance variables of the Renderer class (FOVY
, zNear
, zFar
) and two variables which get the current width and height of the window object (window.getWidth()
, window.getHeight()
).
Then, the projection matrix is "sent" to the vertex shader by callingshaderProgram.setUniformMatrix("projectionMatrix", projectionMatrix);
:
private final Map<String, Integer> uniforms;
public void createUniform(String uniformName) throws Exception {
int uniformLocation = glGetUniformLocation(programID, uniformName);
if (uniformLocation < 0) {
throw new Exception("[ShaderProgram.createUniform]: Couldn't find uniform: " + uniformName);
}
uniforms.put(uniformName, uniformLocation);
}
This method is located in the ShaderProgram class, which holds references to the active shader porgram and the uniform variables associated with it which are stored in the uniforms
Hashmap..
Then, inside the for loop, the two quads are rendered. First, a translation matrix is constructed based on the value of GameInstance's position value, which is represented as a 3-tuple vector (x, y, z). Then, that created matrix is "sent" to the vertex shader.
Now, having sent both the perspective projection (projectionMatrix
) and translation matrix (translationMatri
x) to the vertex shader, it's time to call the render method on our Mesh instance to render it. The code for the render()
method (in the context of entity.getMesh().render()
) is:
public void render() {
glBindVertexArray(getVaoID());
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, getIndicesVboID());
glDrawElements(GL_TRIANGLES, getVertexCount(), GL_UNSIGNED_INT, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glDisableVertexAttribArray(0);
glBindVertexArray(0);
}
The results are not satisfactory, however. When W or S are pressed, the quad (two triangles) goes off farther or closer to "us", which is correct. However, when A or D are pressed (which should translate the model on the x axis), the quad (two triangles) isn't translated. It just rotates around it's center. How to fix the issue?
Seems there's something wrong with row/column nomenclature.
As I understand your code cell mij
in the matrix is col=i, row=j
OpenGL expects a 16 values array, being the translation the 13,14,15 positions. Usually this is called "column major order", a translation is represented in the 4th column.
So Try this:
public Matrix4f getTranslationMatrix(float x, float y, float z) {
translationMatrix.m30 = x;
translationMatrix.m31 = y;
translationMatrix.m32 = z;
return translationMatrix;
}
Also, review your perspective
. I think you have some mistakes in ij
indices. ii
cells look good.