Currently, I'm attempting to create a Camera class in LWJGL, but I've been running into a problem with the projection matrix. For some reason, when I try to multiply the vertices by the projection matrix, nothing at all appears on screen.
Camera class
public class Camera {
private Vector3f position, rotation;
private Matrix4f view;
private final Vector3f xAxis, yAxis, zAxis;
private float fov, aspect, zNear, zFar;
private Matrix4f projection;
public Camera(float fov, float aspect, float zNear, float zFar){
this.fov = fov;
this.aspect = aspect;
this.zNear = zNear;
this.zFar = zFar;
projection = createPerspectiveProjection(fov, aspect, zNear, zFar);
position = new Vector3f();
rotation = new Vector3f();
view = new Matrix4f();
view.setIdentity();
xAxis = new Vector3f(1, 0, 0);
yAxis = new Vector3f(0, 1, 0);
zAxis = new Vector3f(0, 0, 1);
}
public void addRotation(float x, float y, float z){
rotation.x += x;
rotation.y += y;
rotation.z += z;
apply();
}
public void move(float x, float y, float z){
position.x += x;
position.y += y;
position.z += z;
apply();
}
public Matrix4f getView(){
return view;
}
public Matrix4f getProjection(){
return projection;
}
private void apply(){
view.setIdentity();
view.rotate(rotation.x, xAxis);
view.rotate(rotation.y, yAxis);
view.rotate(rotation.z, zAxis);
view.translate(position);
}
private Matrix4f createPerspectiveProjection(float fov, float aspect, float zNear, float zFar){
Matrix4f mat = new Matrix4f();
float yScale = (float) (1 / (Math.tan(Math.toRadians(fov / 2))));
float xScale = yScale / aspect;
float frustrumLength = zFar - zNear;
mat.m00 = xScale;
mat.m11 = yScale;
mat.m22 = -((zFar + zNear) / frustrumLength);
mat.m23 = -1;
mat.m32 = -((2 * zFar * zNear) / frustrumLength);
mat.m33 = 0;
return mat;
}
}
Main class
public class Game implements Runnable {
public static final int WIDTH = 800;
public static final int HEIGHT = 600;
public static final DisplayMode dm = new DisplayMode(WIDTH, HEIGHT);
int vaoID;
ShaderProgram program;
Camera camera;
Model model;
public Game(){
new Thread(this).start();
}
public void run(){
init();
while(true){
if(Display.isCloseRequested())
break;
update(Timer.getElapsedTime());
render();
Display.sync(60);
Display.update();
}
Display.destroy();
}
public void init(){
try{
Display.setTitle("Ludum Dare!");
Display.setDisplayMode(dm);
Display.create();
}catch(LWJGLException e){
e.printStackTrace();
System.exit(1);
}
Timer.start();
camera = new Camera(60.0f, WIDTH / HEIGHT, 0.1f, 100.0f);
program = new ShaderProgram("res/shader/defaultshader.vert", "res/shader/defaultshader.frag");
glClearColor(0.0f, 0.0f, 0.4f, 0.0f);
vaoID = glGenVertexArrays();
glBindVertexArray(vaoID);
model = new Model(new float[] {
-1, -1, 1,
1, -1, 1,
0, 1, 1
});
}
public void update(float delta){
}
public void render(){
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
program.bind();
program.setUniform("model_matrix", model.getModel());
program.setUniform("view_matrix", camera.getView());
program.setUniform("projection_matrix", camera.getProjection());
model.render();
program.unbind();
}
public static void main(String[] args){
new Game();
}
}
Vertex Shader
#version 330 core
layout(location = 0) in vec3 vertex_modelspace;
uniform mat4 model_matrix;
uniform mat4 view_matrix;
uniform mat4 projection_matrix;
void main(){
mat4 modelviewprojection_matrix = projection_matrix * view_matrix * model_matrix;
vec4 vertex = vec4(vertex_modelspace, 1.0);
gl_Position = modelviewprojection_matrix * vertex;
}
ShaderProgram class
public class ShaderProgram {
private int vertexShaderID, fragmentShaderID;
private int programID;
public ShaderProgram(String vertPath, String fragPath){
programID = glCreateProgram();
vertexShaderID = attachShader(vertPath, GL_VERTEX_SHADER);
fragmentShaderID = attachShader(fragPath, GL_FRAGMENT_SHADER);
link();
}
private int attachShader(String path, int type){
StringBuilder shaderSource = new StringBuilder();
try{
BufferedReader reader = new BufferedReader(new FileReader(new File(path)));
String line;
while((line = reader.readLine()) != null){
shaderSource.append(line).append("\n");
}
reader.close();
}catch(IOException e){
e.printStackTrace();
System.out.println("Error reading from shader " + path);
System.exit(1);
}
System.out.println("Compiling shader " + path);
int id = glCreateShader(type);
glShaderSource(id, shaderSource);
glCompileShader(id);
if(glGetShaderi(id, GL_COMPILE_STATUS) == GL_FALSE){
System.out.println(glGetShaderInfoLog(id, 1000));
System.exit(1);
}
return id;
}
private void link(){
System.out.println("Linking program...");
glAttachShader(programID, vertexShaderID);
glAttachShader(programID, fragmentShaderID);
glLinkProgram(programID);
}
public void bind(){
glUseProgram(programID);
}
public void unbind(){
glUseProgram(0);
}
public void setUniform(String name, Matrix4f value){
FloatBuffer matrix = BufferUtils.createFloatBuffer(16);
value.store(matrix); matrix.flip();
glUniformMatrix4(glGetUniformLocation(programID, name), false, matrix);
}
}
When I run this, the VBO that I create (A triangle) doesn't appear, but when I leave the projection matrix out of the multiplication in the vertex shader, it runs just fine. Am I missing something?
I'm making the assumption that
model.getModel()
will return the identity matrix (just like your view matrix is identity). In that case, you have the following situation: You draw a triangle in the plane z=1.0. If you use identity as projection matrix also, you directly draw in clip space, and the trianlge will be on the far plane, so it is visible.However, your
createPerspectiveProjection
function seems to be written with the standard OpenGL conventions in mind, so it is almost like whatglFrustum()
. (Your code is missing the parts which are labeled A and B in that manpage, so you are limited to a symmetric frustum, but that is fine in most cases.) The conventions used for this matrix were that the camera is looking into the -z direction, and thezNear
andzFar
parameters are actually maped such that a point atz_eye=-zNear
is projected to the near plane (z_ndc=-1
), and a point atz_eye=-zFar
is projected to the far plane (z_ndc=1
). So your triangle at z=1 is just behind the camera, if you apply that projection matrix.