Fastest way to render 2D tiles using LWJGL? - java

I started watching these tutorials for creating a 2d top-down game using LWJGL and I read that VBO's should be fast but for rendering 48*48 tiles per frame I get only about 100FPS which is pretty slow because I will add a lot more stuff to the game than just some static, not moving or changing, tiles.
What can I do to make this faster? Keep in mind that I just started learning lwjgl and opengl so I probably won't know many things.
Anyways, here are some parts of my code (I removed some parts from the code that were kinda meaningless and replaced them with some descriptions):
The main loop
double targetFPS = 240.0;
double targetUPS = 60.0;
long initialTime = System.nanoTime();
final double timeU = 1000000000 / targetUPS;
final double timeF = 1000000000 / targetFPS;
double deltaU = 0, deltaF = 0;
int frames = 0, updates = 0;
long timer = System.currentTimeMillis();
while (!window.shouldClose()) {
long currentTime = System.nanoTime();
deltaU += (currentTime - initialTime) / timeU;
deltaF += (currentTime - initialTime) / timeF;
initialTime = currentTime;
if (deltaU >= 1) {
// --- [ update ] ---
--INPUT HANDLING FOR BASIC MOVEMENT, CLOSING THE GAME AND TURNING VSYNC ON AND OFF USING A METHOD FROM THE INPUT HANDLER CLASS--
world.correctCamera(camera, window);
window.update();
updates++;
deltaU--;
}
if (deltaF >= 1) {
// --- [ render ] ---
glClear(GL_COLOR_BUFFER_BIT);
world.render(tileRenderer, shader, camera, window);
window.swapBuffers();
frames++;
deltaF--;
}
--PRINTING THE FPS AND UPS EVERY SECOND--
}
The input handler methods used:
I have this in my constructor:
this.keys = new boolean[GLFW_KEY_LAST];
for(int i = 0; i < GLFW_KEY_LAST; i++)
keys[i] = false;
And here are the methods:
public boolean isKeyDown(int key) {
return glfwGetKey(window, key) == 1;
}
public boolean isKeyPressed(int key) {
return (isKeyDown(key) && !keys[key]);
}
public void update() {
for(int i = 32; i < GLFW_KEY_LAST; i++)
keys[i] = isKeyDown(i);
}
This is the render method from the World class:
public void render(TileRenderer renderer, Shader shader, Camera camera, Window window) {
int posX = ((int) camera.getPosition().x + (window.getWidth() / 2)) / (scale * 2);
int posY = ((int) camera.getPosition().y - (window.getHeight() / 2)) / (scale * 2);
for (int i = 0; i < view; i++) {
for (int j = 0; j < view; j++) {
Tile t = getTile(i - posX, j + posY);
if (t != null)
renderer.renderTile(t, i - posX, -j - posY, shader, world, camera);
}
}
}
This is the renderTile() method from TileRenderer:
public void renderTile(Tile tile, int x, int y, Shader shader, Matrix4f world, Camera camera) {
shader.bind();
if (tileTextures.containsKey(tile.getTexture()))
tileTextures.get(tile.getTexture()).bind(0);
Matrix4f tilePosition = new Matrix4f().translate(new Vector3f(x * 2, y * 2, 0));
Matrix4f target = new Matrix4f();
camera.getProjection().mul(world, target);
target.mul(tilePosition);
shader.setUniform("sampler", 0);
shader.setUniform("projection", target);
model.render();
}
This is the constructor and render method from Model class:
public Model(float[] vertices, float[] texture_coords, int[] indices) {
draw_count = indices.length;
v_id = glGenBuffers();
glBindBuffer(GL_ARRAY_BUFFER, v_id);
glBufferData(GL_ARRAY_BUFFER, createBuffer(vertices), GL_STATIC_DRAW);
t_id = glGenBuffers();
glBindBuffer(GL_ARRAY_BUFFER, t_id);
glBufferData(GL_ARRAY_BUFFER, createBuffer(texture_coords), GL_STATIC_DRAW);
i_id = glGenBuffers();
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_id);
IntBuffer buffer = BufferUtils.createIntBuffer(indices.length);
buffer.put(indices);
buffer.flip();
glBufferData(GL_ELEMENT_ARRAY_BUFFER, buffer, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
public void render() {
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, v_id);
glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0);
glBindBuffer(GL_ARRAY_BUFFER, t_id);
glVertexAttribPointer(1, 2, GL_FLOAT, false, 0, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_id);
glDrawElements(GL_TRIANGLES, draw_count, GL_UNSIGNED_INT, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
}
I store the vertices, texture coords and indices in the tile renderer:
float[] vertices = new float[]{
-1f, 1f, 0, //top left 0
1f, 1f, 0, //top right 1
1f, -1f, 0, //bottom right 2
-1f, -1f, 0, //bottom left 3
};
float[] texture = new float[]{
0, 0,
1, 0,
1, 1,
0, 1,
};
int[] indices = new int[]{
0, 1, 2,
2, 3, 0
};
I don't know what else to put here but the full source code and resources + shader files are available on github here.

With your current system, what I would recommend doing is grouping your tiles based on texture. Create something like this:
Map<Texture, List<Tile>> tiles = new HashMap<Texture, List<Tile>>()
Then when you go to render your map of tiles, you will only need to set the texture once per group of tiles, rather than once per tile. This saves PCI-E bandwidth for pushing textures/texture ids to the GPU. You would achieve that like this (pseudo code):
for (Texture tex : tile.keySet())
{
BIND TEXTURE
for (Tile tile : tiles.get(tex))
{
SET UNIFORMS
RENDER
}
}
Something else I see along these lines is that you are pushing the projection matrix to each tile individually. When you are running a shader program, the value of a given uniform stays the same until you change it or until the program ends. Set the projection matrix uniform once.
It also appears that you are calling this every renderTile(...). Given the value does not change, calculate it once before the render pass, then pass it in as a variable in the renderTile(...) method rather than passing in camera and world.

Related

Trying to draw a model with Gouraud shading in jogl for desktop, but is flat

I already have a drawn model, but it has flat shading (for what I understand it should be smooth by default...)
This is the initial config:
private void SetLightningAndMaterials(){
//float[] lightPos = {1, 1, 1, 0};
float[] lightPos = {0, 0, 1, 0};
float[] lightColorDiffuse = {1, 1, 1, 1};
float[] lightColorAmbient = {0.2f, 0.2f, 0.2f, 1};
gl.glShadeModel(GL.GL_SMOOTH);
gl.glLightfv(GL.GL_LIGHT1, GL.GL_POSITION, lightPos, 0);
gl.glLightfv(GL.GL_LIGHT1, GL.GL_DIFFUSE, lightColorDiffuse, 0);
gl.glLightfv(GL.GL_LIGHT1, GL.GL_AMBIENT, lightColorAmbient, 0);
gl.glEnable(GL.GL_LIGHT1);
gl.glEnable(GL.GL_LIGHTING);
gl.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT, ambientColour, 0);
gl.glMaterialfv(GL.GL_FRONT, GL.GL_DIFFUSE, mesh.colour, 0);
gl.glEnable(GL.GL_LIGHTING);
gl.glEnable(GL.GL_LIGHT0);
float[] noAmbient =
{ 0.1f, 0.1f, 0.1f, 1f }; // low ambient light
float[] spec =
{ 1f, 0.6f, 0f, 1f }; // low ambient light
float[] diffuse =
{ 0.5f, 0.5f, 0.5f, 1f };
gl.glLightfv(GL.GL_LIGHT0, GL.GL_AMBIENT, noAmbient, 0);
gl.glLightfv(GL.GL_LIGHT0, GL.GL_SPECULAR, spec, 0);
gl.glLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, diffuse, 0);
gl.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, new float[]{0,0,10,1}, 0);
}
And this is how I draw the model:
public void Draw(GL gl, GLU glu){
Vec3d normal;
MassPoint vertex1, vertex2, vertex3;
int faceIndex=0;
Face surfaceFace;
for (faceIndex=0; faceIndex<surfaceFaces.size();faceIndex++){
surfaceFace = surfaceFaces.get(faceIndex);
surfaceFace.recalculateNormal();
vertex1 = surfaceFace.vertex1;
vertex2 = surfaceFace.vertex2;
vertex3 = surfaceFace.vertex3;
normal = surfaceFace.normal;
gl.glBegin(gl.GL_TRIANGLES);
gl.glNormal3d(normal.x, normal.y, normal.z);
gl.glMaterialfv(GL.GL_FRONT, GL.GL_DIFFUSE, colour, 0);
gl.glVertex3d(vertex1.position.x, vertex1.position.y, vertex1.position.z);
gl.glVertex3d(vertex2.position.x, vertex2.position.y, vertex2.position.z);
gl.glVertex3d(vertex3.position.x, vertex3.position.y, vertex3.position.z);
gl.glEnd();
}
}
I want to believe there's an easy way of solving this without having to create a shader (I don't have any idea how to set these in Java).
I'm using JOGL 1 by the way, and is probably an old version (the imports are like javax.media.opengl.*).
I managed to solve the problem. For smoothness to work, the drawing expects 3 normals (one per vertex), I was only passing 1 normal (one per face).
Here's the new code for the drawing:
public void Draw(GL gl, GLU glu) {
Vec3d[] normalsPerVertex = new Vec3d[3];
MassPoint vertex1, vertex2, vertex3;
int faceIndex=0;
Face surfaceFace;
for (faceIndex=0; faceIndex<surfaceFaces.size();faceIndex++){
surfaceFace = surfaceFaces.get(faceIndex);
vertex1=surfaceFace.vertex1;
normalsPerVertex[0] = vertex1.CalcNormal();
vertex2=surfaceFace.vertex2;
normalsPerVertex[1] = vertex2.CalcNormal();
vertex3=surfaceFace.vertex3;
normalsPerVertex[2] = vertex3.CalcNormal();
gl.glBegin(GL.GL_TRIANGLES);
gl.glNormal3d(normalsPerVertex[0].x, normalsPerVertex[0].y, normalsPerVertex[0].z);
gl.glVertex3d(vertex1.position.x, vertex1.position.y, vertex1.position.z);
gl.glNormal3d(normalsPerVertex[1].x, normalsPerVertex[1].y, normalsPerVertex[1].z);
gl.glVertex3d(vertex2.position.x, vertex2.position.y, vertex2.position.z);
gl.glNormal3d(normalsPerVertex[2].x, normalsPerVertex[2].y, normalsPerVertex[2].z);
gl.glVertex3d(vertex3.position.x, vertex3.position.y, vertex3.position.z);
gl.glEnd();
}
}
The calculated normal for each vertex is the media of all the faces connected to that vertex. Here's the code for that:
public Vec3d CalcNormal() {
Vec3d normalMedia = new Vec3d();
for (Face face : facesRelated) {
face.recalculateNormal();
normalMedia.add(face.normal);
}
normalMedia.mul(1d/facesRelated.size());
return normalMedia;
}
Hope this helps someone else.

JOGL display using Buffer

I have 3 FloatBuffers - vertices, normals and colors which contain what the name suggests. I also have an IntBuffer to keep track of the indexing.
The data looks correct, but I'm having trouble displaying it. I just see a blank canvas. I'm not sure what I am doing wrong. I'm guessing something is being overlooked in init(), display(), reshape() and dispose(). Can anyone tell me if you find something glaringly wrong in the code below, and why you think nothing is being displayed?
#Override
public void init(GLAutoDrawable drawable) {
GL2 gl = drawable.getGL().getGL2();
gl.glClearColor(.0f, .0f, .2f, 0.9f);
gl.glEnable(GL2.GL_DEPTH_TEST);
gl.glDepthFunc(GL2.GL_LESS);
gl.glEnable(GL2.GL_CULL_FACE);
gl.glEnable(GL2.GL_LIGHTING);
gl.glEnable(GL2.GL_LIGHT0);
gl.glEnable(GL2.GL_AUTO_NORMAL);
gl.glEnable(GL2.GL_NORMALIZE);
gl.glFrontFace(GL2.GL_CCW);
gl.glCullFace(GL2.GL_BACK);
gl.glHint(GL2.GL_PERSPECTIVE_CORRECTION_HINT, GL2.GL_NICEST);
gl.glShadeModel(GL2.GL_SMOOTH);
if (viewMesh) {
gl.glPolygonMode(GL2.GL_FRONT_AND_BACK, GL2.GL_LINE);
} else {
gl.glPolygonMode(GL2.GL_FRONT_AND_BACK, GL2.GL_FILL);
}
glu = new GLU();
// Build the VBOs
VBO = IntBuffer.allocate(4);
gl.glGenBuffers(4, VBO);
vertVBOID = VBO.get(0);
normalVBOID = VBO.get(1);
colorVBOID = VBO.get(2);
indexVBOID = VBO.get(3);
// vertices
int vsize = sd.verts.capacity() * BufferUtil.SIZEOF_FLOAT;
gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, vertVBOID); // get a valid name
gl.glBufferData(GL2.GL_ARRAY_BUFFER, vsize, sd.verts, GL2.GL_STATIC_DRAW);
gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, 0); // reset
// normals
int nsize = sd.normals.capacity() * BufferUtil.SIZEOF_FLOAT;
gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, normalVBOID);
gl.glBufferData(GL2.GL_ARRAY_BUFFER, nsize, sd.normals, GL2.GL_STATIC_DRAW);
gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, 0);
// colors
int csize = sd.colors.capacity() * BufferUtil.SIZEOF_FLOAT;
gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, colorVBOID);
gl.glBufferData(GL2.GL_ARRAY_BUFFER, csize, sd.colors, GL2.GL_STATIC_DRAW);
gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, 0);
int isize = sd.indices.capacity() * BufferUtil.SIZEOF_INT;
gl.glBindBuffer(GL2.GL_ELEMENT_ARRAY_BUFFER, indexVBOID);
gl.glBufferData(GL2.GL_ELEMENT_ARRAY_BUFFER, isize, sd.indices, GL2.GL_STATIC_DRAW);
gl.glBindBuffer(GL2.GL_ELEMENT_ARRAY_BUFFER, 0);
// sd.verts = null; // copy of data is no longer necessary, it is in the graphics card now.
// sd.colors = null;
// sd.normals = null;
// sd.indices = null;
}
#Override
public void display(GLAutoDrawable drawable) {
GL2 gl = drawable.getGL().getGL2();
gl.glMatrixMode(GL2.GL_MODELVIEW);
gl.glLoadIdentity();
glu.gluLookAt(45, 0, 0, 0, 0, 0, 0.0, 1.0, 0.0);
gl.glScalef(scale, scale, scale);
gl.glRotatef(rot, 0, 1, 0);
gl.glTranslatef(-sd.tr_x, -sd.tr_y, -sd.tr_z);
gl.glEnableClientState(GLPointerFunc.GL_VERTEX_ARRAY);
gl.glEnableClientState(GLPointerFunc.GL_NORMAL_ARRAY);
gl.glEnableClientState(GLPointerFunc.GL_COLOR_ARRAY);
gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, normalVBOID);
gl.glNormalPointer(GL2.GL_FLOAT, 0, 0);
gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, colorVBOID);
gl.glColorPointer(3, GL2.GL_FLOAT, 0, 0);
gl.glBindBuffer(GL2.GL_ARRAY_BUFFER, vertVBOID);
gl.glVertexPointer(3, GL2.GL_FLOAT, 0, 0);
gl.glBindBuffer(GL2.GL_ELEMENT_ARRAY_BUFFER, indexVBOID);
gl.glDrawElements(GL2.GL_TRIANGLES, sd.indices.capacity(), GL2.GL_INT, 0);
gl.glBindBuffer(GL2.GL_ELEMENT_ARRAY_BUFFER, 0); // unbind it
gl.glDisableClientState(GLPointerFunc.GL_VERTEX_ARRAY);
gl.glDisableClientState(GLPointerFunc.GL_NORMAL_ARRAY);
gl.glDisableClientState(GLPointerFunc.GL_COLOR_ARRAY);
}
#Override
public void reshape(GLAutoDrawable drawable, int x, int y, int w, int h) {
GL2 gl = drawable.getGL().getGL2();
double fov = (Math.PI / 4.0);
double zmm = Math.abs(sd.min_z - sd.max_z);
camdist = (zmm / 2.0) / Math.tan(fov / 2.0);
h = (h == 0) ? 1 : h;
gl.glViewport(0, 0, w, h);
gl.glMatrixMode(GL2.GL_PROJECTION);
gl.glLoadIdentity();
glu.gluPerspective(camdist, w / (float) h, 0.1f, 1000.0);
}
/**
* Initilaize graphics
*/
public void initOGL() {
profile = GLProfile.get(GLProfile.GL2);
caps = new GLCapabilities(profile);
caps.setHardwareAccelerated(true);
canvas = new GLCanvas(caps);
canvas.addGLEventListener(this);
canvas.requestFocusInWindow();
getContentPane().add(canvas); // add to the frame
}
#Override
public void dispose(GLAutoDrawable drawable) {
}
One clear problem is in the draw call:
gl.glDrawElements(GL2.GL_TRIANGLES, sd.indices.capacity(), GL2.GL_INT, 0);
GL_INT is not valid for the type argument of glDrawElements(). The only valid values are GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT and GL_UNSIGNED_INT. So the call needs to be:
gl.glDrawElements(GL2.GL_TRIANGLES, sd.indices.capacity(), GL2.GL_UNSIGNED_INT, 0);
Anytime you have a problem with OpenGL code not working as expected, make sure that you call glGetError(). With the call from your code, you should immediately get a GL_INVALID_ENUM error.
Also, your perspective setup code looks somewhat suspicious. I don't fully understand what you're trying to do there, but even just the naming suggests a possible misunderstanding:
glu.gluPerspective(camdist, w / (float) h, 0.1f, 1000.0);
The first argument of gluPerspective() is the field-of-view angle (in degrees), not a distance.

Why are my VBOs slower than display lists?

I created two simple voxel engines, literally just chunks that hold cubes. For the first one, I use display lists and can render hundreds of chunks at 60 FPS no problem, despite the fact that the technology behind it is years old and deprecated by now. With my VBO version, I try to render 27 chunks and I suddenly drop to less than 50 FPS. What gives? I use shaders for my VBO version, but not for display list one. Without shaders for the VBO version, I still get the same FPS rate. I'll post some relevant code:
VBO
Initialization of chunk:
public void initGL() {
rand = new Random();
sizeX = (int) pos.getX() + CHUNKSIZE;
sizeY = (int) pos.getY() + CHUNKSIZE;
sizeZ = (int) pos.getZ() + CHUNKSIZE;
tiles = new byte[sizeX][sizeY][sizeZ];
vCoords = BufferUtils.createFloatBuffer(CHUNKSIZE * CHUNKSIZE * CHUNKSIZE * (3 * 4 * 6));
cCoords = BufferUtils.createFloatBuffer(CHUNKSIZE * CHUNKSIZE * CHUNKSIZE * (4 * 4 * 6));
createChunk();
verticeCount = CHUNKSIZE * CHUNKSIZE * CHUNKSIZE * (4 * 4 * 6);
vCoords.flip();
cCoords.flip();
vID = glGenBuffers();
glBindBuffer(GL_ARRAY_BUFFER, vID);
glBufferData(GL_ARRAY_BUFFER, vCoords, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
cID = glGenBuffers();
glBindBuffer(GL_ARRAY_BUFFER, cID);
glBufferData(GL_ARRAY_BUFFER, cCoords, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
private void createChunk() {
for (int x = (int) pos.getX(); x < sizeX; x++) {
for (int y = (int) pos.getY(); y < sizeY; y++) {
for (int z = (int) pos.getZ(); z < sizeZ; z++) {
if (rand.nextBoolean() == true) {
tiles[x][y][z] = Tile.Grass.getId();
} else {
tiles[x][y][z] = Tile.Void.getId();
}
vCoords.put(Shape.createCubeVertices(x, y, z, 1));
cCoords.put(Shape.getCubeColors(tiles[x][y][z]));
}
}
}
}
And then rendering:
public void render() {
glBindBuffer(GL_ARRAY_BUFFER, vID);
glVertexPointer(3, GL_FLOAT, 0, 0L);
glBindBuffer(GL_ARRAY_BUFFER, cID);
glColorPointer(4, GL_FLOAT, 0, 0L);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
shader.use();
glDrawArrays(GL_QUADS, 0, verticeCount);
shader.release();
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
}
I know I use quads, and that's bad, but I'm also using quads for my display list engine. The shaders are very simple, all they do is take a color and apply it to the vertices, I won't even post them they are that simple.
Display List
Initialization:
public void init() {
rand = new Random();
opaqueID = glGenLists(1);
tiles = new byte[(int) lPosition.x][(int) lPosition.y][(int) lPosition.z];
genRandomWorld();
rebuild();
}
public void rebuild() {
glNewList(opaqueID, GL_COMPILE);
glBegin(GL_QUADS);
for (int x = (int) sPosition.x; x < (int) lPosition.x; x++) {
for (int y = (int) sPosition.y; y < (int) lPosition.y; y++) {
for (int z = (int) sPosition.z; z < (int) lPosition.z; z++) {
if (checkCubeHidden(x, y, z)) {
// check if tiles hidden. if not, add vertices to
// display list
if (type != 0) {
Tile.getTile(tiles[x][y][z]).getVertices(x, y, z, 1, spritesheet.getTextureCoordsX(tiles[x][y][z]), spritesheet.getTextureCoordsY(tiles[x][y][z]));
} else {
Tile.getTile(tiles[x][y][z]).getVertices(x, y, z, 1);
}
}
}
}
}
glEnd();
glEndList();
spritesheet.bind();
}
I should note that in my display list version, I only add in the visible cubes. So, that may be an unfair advantage, but it should not bring the VBO version down to that FPS with just 27 chunks versus 500 chunks for the display list version.
I render like this:
public void render() {
if (tiles.length != -1) {
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glCallList(opaqueID);
}
}
So, after all of that code, I really still wonder why my VBO version is just so darn slow? I do have a one dimensional list of chunks in my display list version for when I'm calling them to render, and a 3 dimensional one in my VBO version, but I think the JVM pretty much eliminates any lag with the extra dimensions. So, what am I doing wrong?
It is hard to answer such question without having an actual project and a profiler at hand, so these are theories:
You don't show your Display Lists generation code in detail, so I'm assuming you are doing something alike glColor(); glVertex3f(); in a loop (not that you declared color once and done with it).
Display List implementation is implementation-specific, but usually that is interleaved array of vertex properties, because that is much more friendly to a cache (all vertice props are tightly aligned by 16bytes instead of being spread by a size of array). On the other hand, VBO you use is coming in two non-interleaved chunks - Coordinates and Colors. This could cause excessive unfriendly cache usage (especially with big amounts of data).
As noted in comments:
try interleaving your position and colour data in a single buffer. That is the usual recommendation for static data as it gives better memory access patterns during rendering. – GuyRT`

Vertex Attribute Arrays\not receiving vertex data in LWJGL

Cannot seem to get Vertex Attribute Arrays working properly for per vertex data.
Here's the SSCCE:
private static void createDisplay(int w, int h) {
try {
Display.create();
Display.setDisplayMode(new DisplayMode(w, h));
}
catch (LWJGLException e) {
e.printStackTrace();
}
float size = 1;
float aspect = (float) Display.getWidth() / Display.getHeight();
GL11.glMatrixMode(GL11.GL_PROJECTION);
GL11.glLoadIdentity();
GL11.glOrtho(-size * aspect, size * aspect, -size, size, -1, 1);
GL11.glMatrixMode(GL11.GL_MODELVIEW);
GL11.glLoadIdentity();
}
public static void main(String[] args) {
createDisplay(1200, 800);
GL11.glViewport(0, 0, Display.getWidth(), Display.getHeight());
ShaderManager.createShader("2Dv", new File("src/Shaders/2D.vert"), SHADER_VERT);
ShaderManager.createShader("2Df", new File("src/Shaders/2D.frag"), SHADER_FRAG);
ShaderManager.createProgram("2D", "2Dv", "2Df");
// Shader compiles and links correctly.
ShaderManager.useProgram("2D");
// Calls glUseProgram(programID);
float[] vertexData = new float[] {-0.5f, -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f};
int vao = GL30.glGenVertexArrays();
if (vao == 0)
System.exit(-1);
GL30.glBindVertexArray(vao);
int vertexBuffer = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vertexBuffer);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, BufferUtil.asDirectFloatBuffer(vertexData), GL15.GL_DYNAMIC_DRAW);
// GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY);
// GL11.glVertexPointer(2, GL11.GL_FLOAT, 2 * 4, 0);
int loc = ShaderManager.currentProgram.getAttribute("vertex");
if (loc == -1)
Debug.log(Debug.INSTANCE_MANAGEMENT, "Attribute [", "", "] not found in Shader [",
ShaderManager.currentProgram.toString(), "].");
else {
GL20.glVertexAttribPointer(loc, 2, GLCONST.TYPE_FLOAT, false, 2 * 4, 0);
GL20.glEnableVertexAttribArray(loc);
}
GL30.glBindVertexArray(0);
GL11.glColor3f(1, 0, 0);
GL11.glClearColor(0.5f, 0.5f, 0.8f, 1);
int indexBuffer = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, BufferUtil.asDirectFloatBuffer(new float[] {0, 1, 2, 3}),
GL15.GL_DYNAMIC_DRAW);
while (!Display.isCloseRequested()) {
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
GL30.glBindVertexArray(vao);
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
GL11.glDrawElements(GL11.GL_QUADS, 4, GL11.GL_UNSIGNED_INT, 0);
GL30.glBindVertexArray(0);
Display.update();
int error = GL11.glGetError();
if (error != GL11.GL_NO_ERROR)
System.out.println(GLU.gluErrorString(error));
}
}
The problem lies in the usage of vertex Attribute arrays. The old code I used was:
GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY);
GL11.glVertexPointer(2, GL11.GL_FLOAT, 2 * 4, 0);
The new version is:
int loc = ShaderManager.currentProgram.getAttribute("vertex");//Call to glGetAttribLocation();
if (loc == -1){
System.exit(-1);
}
GL20.glVertexAttribPointer(loc, 2, GL11.GL_FLOAT, false, 2 * 4, 0);
GL20.glEnableVertexAttribArray(loc);
The original code was the commented 2 lines. Upon running, this correctly sent vertex data to gl_Vertex and rendered a square of size 1.
The new code shuld send vertex data to the vertex attribute, but it gets nothing.
When the original code is uncommented and both old and new code used, both gl_Vertex and vertex attribute get vertex data.
What is going wrong here?
So I figured out the problem after a while. The problem is due to an AMD driver bug when using a OpenGL 3.0+ core profile.
The "vertex" attribute array was assigned a location of 1.
The bug occurs when the attribute array 0 is unused. Nothing is rendered if array 0 is not enabled.
To fix this probelm I simply explicitly assigned "vertex" to location 0.
layout(location = 0) in vec4 vertex;

Transformations are weird in OpenGL ES 2.0

I'm developing an application for Android that uses OpenGL ES 2.0
Since it's my first time with OpenGL (I used to use WebGL), I made a custom and pretty simple API like THREE.js, which consists of a Object3D and Geometry objects.
Basicaly, what I did was: Store shapes inside the Geometry object, and create Mesh objects with the the geometry instance inside. Also, inside Mesh, I have: Vector3 object for: position, scale, rotation.
I created a circle to test, and here is what is happening
If I don't change ANY thing, the circle is perfect on the screen. If I change the vertices positions on the creation of the circle, the circle is still Ok also.
But, when I do some transformation (change the attribute position, scale or rotation) or Object3D (in this case, Mesh), the circle becomes "strech".
So, I think that there is some problem with the projectionMatrix, but the circle it's ok if I don't transform it.
Is there a problem with my matrix code? Should I send the Rotation, Translation and Scale matrix to the GPU?
Perhaps I'm complicating things, but since this is the first time I use OpenGL after reading lot's of information, it's acceptable...
Here is the Object3D code:
public class Object3D {
public Vector3 position = new Vector3();
public Vector3 rotation = new Vector3();
public Vector3 scale = new Vector3();
public Color color = new Color();
public float[] getMVMatrix(){
// Initialize matrix with Identity
float[] mvMatrix = new float[16];
Matrix.setIdentityM(mvMatrix, 0);
// apply scale
Matrix.scaleM(mvMatrix, 0, scale.x, scale.y, scale.z);
// set rotation
Matrix.setRotateM(mvMatrix, 0, rotation.x, 1f, 0, 0);
Matrix.setRotateM(mvMatrix, 0, rotation.y, 0, 1f, 0);
Matrix.setRotateM(mvMatrix, 0, rotation.z, 0, 0, 1f);
// apply translation
Matrix.translateM(mvMatrix, 0, position.x, position.y, position.z);
return mvMatrix;
}
}
This is the Geometry class, that simplifies the use of Triangles:
public class Geometry {
// Public, to allow modifications
public ArrayList<Vector3> vertices;
public ArrayList<Face3> faces;
// Type of Geometry
public int triangleType = GLES20.GL_TRIANGLES;
[...]
public FloatBuffer getVerticesBuffer(){
if(verticesBuffer == null || verticesBufferNeedsUpdate){
/*
* Cache faces
*/
int size = vertices.size();
// (size of Vector3 list) * (3 for each object) * (4 bytes per float)
ByteBuffer bb = ByteBuffer.allocateDirect( size * 3 * 4 );
// use the device hardware's native byte order
bb.order(ByteOrder.nativeOrder());
// Get the ByteBuffer as a floatBuffer
verticesBuffer = bb.asFloatBuffer();
for(int i = 0; i < size; i++)
verticesBuffer.put(vertices.get(i).toArray());
verticesBufferNeedsUpdate = false;
}
verticesBuffer.position(0);
return verticesBuffer;
}
public ShortBuffer getFacesBuffer(){
if(facesBuffer == null || facesBufferNeedsUpdate){
/*
* Cache faces
*/
int size = faces.size();
// Log.i(TAG, "FACES Size: "+size);
// (size of Vector3 list) * (3 for each object) * (2 bytes per short)
ByteBuffer bb = ByteBuffer.allocateDirect( size * 3 * 2 );
// use the device hardware's native byte order
bb.order(ByteOrder.nativeOrder());
// Get the ByteBuffer as a floatBuffer
facesBuffer = bb.asShortBuffer();
for(int i = 0; i < size; i++)
facesBuffer.put(faces.get(i).toArray());
facesBufferNeedsUpdate = false;
}
facesBuffer.position(0);
return facesBuffer;
}
}
Also, The Mesh class, responsable for resndering Geometry objects:
public class Mesh extends Object3D{
[...]
public void draw(float[] projectionMatrix, int shaderProgram){
float[] MVMatrix = getMVMatrix();
Matrix.multiplyMM(projectionMatrix, 0, projectionMatrix, 0, MVMatrix, 0);
// Check if geometry is set
if(geometry == null){
Log.i(TAG, "Geometry is null. skiping");
return;
}
// Add program to OpenGL environment
GLES20.glUseProgram(shaderProgram);
// Get, enable and Set the position attribute
positionHandle = GLES20.glGetAttribLocation(shaderProgram, "vPosition");
GLES20.glEnableVertexAttribArray(positionHandle);
// Prepare the triangles coordinate data
Buffer vertexBuffer = geometry.getVerticesBuffer();
GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX,
GLES20.GL_FLOAT, false,
COORDS_PER_VERTEX*4,
vertexBuffer);
// get handle to fragment shader's vColor member
int mColorHandle = GLES20.glGetUniformLocation(shaderProgram, "vColor");
// Set color for drawing the triangle
GLES20.glUniform4fv(mColorHandle, 1, color.toArray(), 0);
// get handle to shape's transformation matrix
int mMVPMatrixHandle = GLES20.glGetUniformLocation(shaderProgram, "uMVPMatrix");
ChwaziSurfaceView.checkGlError("glGetUniformLocation");
// Apply the projection and view transformation
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, projectionMatrix, 0);
ChwaziSurfaceView.checkGlError("glUniformMatrix4fv");
// Draw the triangles
if(geometry.triangleType == GLES20.GL_TRIANGLES){
Buffer indexesBuffer = geometry.getFacesBuffer();
GLES20.glDrawElements(
GLES20.GL_TRIANGLES,
geometry.faces.size()*3,
GL10.GL_UNSIGNED_SHORT,
indexesBuffer);
}else{
GLES20.glDrawArrays(geometry.triangleType, 0, geometry.vertices.size());
ChwaziSurfaceView.checkGlError("glDrawArrays");
}
// Disable vertex array
GLES20.glDisableVertexAttribArray(positionHandle);
}
}
This is the sample code I made to test if it's working properly (just translation)
// Inside my Renderer...
#Override
public void onDrawFrame(GL10 unused) {
// Draw background color
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glCullFace(GLES20.GL_FRONT_AND_BACK);
// Set the camera position (View matrix)
Matrix.setLookAtM(mVMatrix, 0,
0, 0, -3,
0f, 0f, 0f,
0f, 1.0f, 0.0f);
// Calculate the projection and view transformation
Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mVMatrix, 0);
// Create a rotation for the triangle
long time = SystemClock.uptimeMillis();// % 4000L;
myMesh.position.x = (time%4000)/4000f;
myMesh.draw(mMVPMatrix, shaderProgram.getProgram());
}
#Override
public void onSurfaceChanged(GL10 unused, int width, int height) {
GLES20.glViewport(0, 0, width, height);
float ratio = (float) width / height;
// this projection matrix is applied to object coordinates
Matrix.orthoM(mProjMatrix, 0, -1, 1, -1, 1, 0, 10);
}
EDIT
Shader code:
private final String vertexShaderCode =
// This matrix member variable provides a hook to manipulate
// the coordinates of the objects that use this vertex shader
"uniform mat4 uMVPMatrix;" +
"attribute vec4 vPosition;" +
"void main() {" +
// the matrix must be included as a modifier of gl_Position
" gl_Position = vPosition * uMVPMatrix;" +
"}";
private final String fragmentShaderCode =
"precision mediump float;" +
"uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}";

Categories

Resources