Related
I am trying to render a triangle to see how LWJGL works. Each frame, I reset the vertex data ByteBuffer and write 3 vertices to it directly. Then I call buffer.flip() to ready the data to be uploaded to the GPU and call glBufferData(...) and finally glDrawArrays(...), but no triangle shows. Using the debug program RenderDoc I was able to look at the vertex data that was supposedly uploaded and it definitely doesn't seem right.
As you can see, each position is extremely small (like 41 zero's after the the .). I don't see any errors, even with the GLFW error callbacks and debug context set up.
All Java code:
import org.lwjgl.glfw.GLFW;
import org.lwjgl.glfw.GLFWErrorCallback;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GLUtil;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
public class MinimalExample {
private static void debugPrintErrors() {
System.out.println("-> DEBUG PRINT ERRORS");
int error;
while ((error = GL30.glGetError()) != GL30.GL_NO_ERROR) {
StringBuilder b = new StringBuilder(" ");
switch (error) {
case GL30.GL_INVALID_ENUM -> b.append("INVALID_ENUM");
case GL30.GL_INVALID_VALUE -> b.append("INVALID_VALUE");
case GL30.GL_INVALID_OPERATION -> b.append("INVALID_OP");
case GL30.GL_INVALID_FRAMEBUFFER_OPERATION -> b.append("INVALID_FB_OP");
}
System.out.println(b);
}
}
private static String readResource(String res) {
try {
InputStream is = MinimalExample.class.getResourceAsStream(res);
String s = new String(is.readAllBytes(), StandardCharsets.UTF_8);
is.close();
return s;
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
// vertex data buffer
private static final ByteBuffer buf = ByteBuffer.allocateDirect(4096);
// shader program
static int program;
// render objects
static int vao;
static int vbo;
public static void main(String[] args) {
// set buffer limit
buf.limit(4096).position(0);
// init glfw and create window
GLFW.glfwInit();
long window = GLFW.glfwCreateWindow(500, 500, "Hello", 0, 0);
// create GL
GLFW.glfwMakeContextCurrent(window);
GL.createCapabilities();
GLUtil.setupDebugMessageCallback(System.out);
GLFW.glfwSetErrorCallback(GLFWErrorCallback.createPrint(System.out));
// create vertex objects
vao = GL30.glGenVertexArrays();
vbo = GL30.glGenBuffers();
GL30.glBindVertexArray(vao);
GL30.glBindBuffer(GL30.GL_ARRAY_BUFFER, vbo);
GL30.glVertexAttribPointer(0, 3, GL30.GL_FLOAT, false, 7 * 4, 0);
GL30.glVertexAttribPointer(1, 4, GL30.GL_FLOAT, false, 7 * 4, 7 * 3);
GL30.glEnableVertexAttribArray(0);
GL30.glEnableVertexAttribArray(1);
// compile and link shaders
int vertexShader = GL30.glCreateShader(GL30.GL_VERTEX_SHADER);
int fragmentShader = GL30.glCreateShader(GL30.GL_FRAGMENT_SHADER);
GL30.glShaderSource(vertexShader, readResource("/test.vsh"));
GL30.glShaderSource(fragmentShader, readResource("/test.fsh"));
GL30.glCompileShader(vertexShader);
GL30.glCompileShader(fragmentShader);
program = GL30.glCreateProgram();
GL30.glAttachShader(program, vertexShader);
GL30.glAttachShader(program, fragmentShader);
GL30.glLinkProgram(program);
// render loop
while (!GLFW.glfwWindowShouldClose(window)) {
// poll events
GLFW.glfwPollEvents();
// clear screen
GL30.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
GL30.glClear(GL30.GL_COLOR_BUFFER_BIT);
// render
render();
// swap buffers
GLFW.glfwSwapBuffers(window);
}
}
static void render() {
// put vertex data
// manual to simulate graphics library
putVec3(0.25f, 0.25f, 1f); putVec4(1.0f, 0.0f, 0.0f, 1.0f);
putVec3(0.75f, 0.25f, 1f); putVec4(0.0f, 1.0f, 0.0f, 1.0f);
putVec3(0.50f, 0.75f, 1f); putVec4(0.0f, 0.0f, 1.0f, 1.0f);
buf.flip();
// bind program
GL30.glUseProgram(program);
// bind vertex array
GL30.glBindVertexArray(vao);
GL30.glEnableVertexAttribArray(0);
GL30.glEnableVertexAttribArray(1);
// upload graphics data and draw
GL30.glBindBuffer(GL30.GL_ARRAY_BUFFER, vbo);
GL30.glBufferData(GL30.GL_ARRAY_BUFFER, buf, GL30.GL_STATIC_DRAW);
GL30.glDrawArrays(GL30.GL_TRIANGLES, 0, 3);
// reset vertex data buffer
buf.position(0);
buf.limit(buf.capacity());
}
//////////////////////////////////////////
static void putVec3(float x, float y, float z) {
buf.putFloat(x);
buf.putFloat(y);
buf.putFloat(z);
}
static void putVec4(float x, float y, float z, float w) {
buf.putFloat(x);
buf.putFloat(y);
buf.putFloat(z);
buf.putFloat(w);
}
}
All shader code (merged into one block for convenience, actually two files in reality):
/*
test.vsh
*/
#version 330 core
in layout(location = 0) vec3 position;
in layout(location = 1) vec4 col;
out layout(location = 0) vec4 fColor;
void main() {
gl_Position = vec4(position * 1000, 1);
fColor = col;
}
/*
test.fsh
*/
#version 330 core
in layout(location = 0) vec4 fColor;
out vec4 outColor;
void main() {
outColor = fColor;
}
Edit: I know the contents of the ByteBuffer are correct, checking them each frame yields:
[ 0.25, 0.25, 1.0, 1.0, 0.0, 0.0, 1.0, 0.75, 0.25, 1.0, 0.0, 1.0, 0.0, 1.0, 0.5, 0.75, 1.0, 0.0, 0.0, 1.0, 1.0, ]
There are 3 problems.
The first problem is buf. ByteBuffer::allocateDirect allocates a BIG_ENDIAN buffer, but OpenGL is a C library, which is LITTLE_ENDIAN. So it must be allocated with BufferUtils::createByteBuffer or MemoryUtil::memAlloc.
The second problem is the offset of glVertexAttribPointer. It should be 4 * 3 for 3 floats.
The third problem is your vertex shader. The input position was multiplied by 1000, which is out of the viewport. To avoid this, you just have to remove the multiplication or use a projection matrix.
gl_Position = vec4(position /* * 1000 */, 1);
BTW I recommend to use ByteBuffer::clear to reset the vertex data buffer, because it is a builtin method.
Fixed Java code:
import org.lwjgl.BufferUtils;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.glfw.GLFWErrorCallback;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GLUtil;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
public class MinimalExample {
private static void debugPrintErrors() {
System.out.println("-> DEBUG PRINT ERRORS");
int error;
while ((error = GL30.glGetError()) != GL30.GL_NO_ERROR) {
StringBuilder b = new StringBuilder(" ");
switch (error) {
case GL30.GL_INVALID_ENUM -> b.append("INVALID_ENUM");
case GL30.GL_INVALID_VALUE -> b.append("INVALID_VALUE");
case GL30.GL_INVALID_OPERATION -> b.append("INVALID_OP");
case GL30.GL_INVALID_FRAMEBUFFER_OPERATION -> b.append("INVALID_FB_OP");
}
System.out.println(b);
}
}
private static String readResource(String res) {
try {
InputStream is = MinimalExample.class.getResourceAsStream(res);
String s = new String(is.readAllBytes(), StandardCharsets.UTF_8);
is.close();
return s;
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
// vertex data buffer
private static final ByteBuffer buf = BufferUtils.createByteBuffer(4096);
// shader program
static int program;
// render objects
static int vao;
static int vbo;
public static void main(String[] args) {
// set buffer limit
buf.limit(4096).position(0);
// init glfw and create window
GLFW.glfwInit();
long window = GLFW.glfwCreateWindow(500, 500, "Hello", 0, 0);
// create GL
GLFW.glfwMakeContextCurrent(window);
GL.createCapabilities();
GLUtil.setupDebugMessageCallback(System.out);
GLFW.glfwSetErrorCallback(GLFWErrorCallback.createPrint(System.out));
// create vertex objects
vao = GL30.glGenVertexArrays();
vbo = GL30.glGenBuffers();
GL30.glBindVertexArray(vao);
GL30.glBindBuffer(GL30.GL_ARRAY_BUFFER, vbo);
GL30.glVertexAttribPointer(0, 3, GL30.GL_FLOAT, false, 7 * 4, 0);
GL30.glVertexAttribPointer(1, 4, GL30.GL_FLOAT, false, 7 * 4, 4 * 3);
GL30.glEnableVertexAttribArray(0);
GL30.glEnableVertexAttribArray(1);
// compile and link shaders
int vertexShader = GL30.glCreateShader(GL30.GL_VERTEX_SHADER);
int fragmentShader = GL30.glCreateShader(GL30.GL_FRAGMENT_SHADER);
GL30.glShaderSource(vertexShader, readResource("/test.vsh"));
GL30.glShaderSource(fragmentShader, readResource("/test.fsh"));
GL30.glCompileShader(vertexShader);
GL30.glCompileShader(fragmentShader);
program = GL30.glCreateProgram();
GL30.glAttachShader(program, vertexShader);
GL30.glAttachShader(program, fragmentShader);
GL30.glLinkProgram(program);
// render loop
while (!GLFW.glfwWindowShouldClose(window)) {
// poll events
GLFW.glfwPollEvents();
// clear screen
GL30.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
GL30.glClear(GL30.GL_COLOR_BUFFER_BIT);
// render
render();
// swap buffers
GLFW.glfwSwapBuffers(window);
}
}
static void render() {
// put vertex data
// manual to simulate graphics library
putVec3(0.25f, 0.25f, 1f); putVec4(1.0f, 0.0f, 0.0f, 1.0f);
putVec3(0.75f, 0.25f, 1f); putVec4(0.0f, 1.0f, 0.0f, 1.0f);
putVec3(0.50f, 0.75f, 1f); putVec4(0.0f, 0.0f, 1.0f, 1.0f);
buf.flip();
// bind program
GL30.glUseProgram(program);
// bind vertex array
GL30.glBindVertexArray(vao);
GL30.glEnableVertexAttribArray(0);
GL30.glEnableVertexAttribArray(1);
// upload graphics data and draw
GL30.glBindBuffer(GL30.GL_ARRAY_BUFFER, vbo);
GL30.glBufferData(GL30.GL_ARRAY_BUFFER, buf, GL30.GL_STATIC_DRAW);
GL30.glDrawArrays(GL30.GL_TRIANGLES, 0, 3);
// reset vertex data buffer
buf.clear();
}
//////////////////////////////////////////
static void putVec3(float x, float y, float z) {
buf.putFloat(x);
buf.putFloat(y);
buf.putFloat(z);
}
static void putVec4(float x, float y, float z, float w) {
buf.putFloat(x);
buf.putFloat(y);
buf.putFloat(z);
buf.putFloat(w);
}
}
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.
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.
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.
I've spent days searching, trying tutorials, and not actually getting results in this, so here I am.
I'm trying, simply put, to animate a collection of objects (Android Studio) on the screen, in a 2D format, with each independent movements and rotations. However, when I try this, I'm either not getting the object rendered, or its rendering skewed (as if rotated through the vertical Y-axis)
I know the importance of the order in which objects are drawn too (to give correct Z-ordering appearance) however, I'm at a bit of a loss with the matrix manipulation.
Here is what I have so far:
Main Activity - standard stuff
private GLSurfaceView mGLSurfaceView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mGLSurfaceView = new GLSurfaceView(this);
//check if device supports ES 2.0
final ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
final ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
final boolean supportsEs2 = configurationInfo.reqGlEsVersion >= 0x20000;
if (supportsEs2) {
//Get the ES2 compatible context
mGLSurfaceView.setEGLContextClientVersion(2);
//set renderer to my renderer below
mGLSurfaceView.setRenderer(new MyGL20Renderer(this));
} else {
//no support
return;
}
//setContentView(R.layout.activity_main);
setContentView(mGLSurfaceView);
}
GL20Renderer class - Notice I'm now just manually adding 2 objects to my collection to render
public class MyGL20Renderer implements GLSurfaceView.Renderer
{
private final Context mActivityContext;
//Matrix Initializations
private final float[] mMVPMatrix = new float[16];
private final float[] mProjMatrix = new float[16];
private final float[] mVMatrix = new float[16];
private float[] mRotationMatrix = new float[16];
private final float[] mRotateMatrix = new float[16];
private final float[] mMoveMatrix = new float[16];
private final float[] mTempMatrix = new float[16];
private final float[] mModelMatrix = new float[16];
private int numObjects = 2;
private ArrayList<Sprite> spriteList = new ArrayList<Sprite>();
//Declare as volatile because we are updating it from another thread
public volatile float mAngle;
//private Triangle triangle;
//private Sprite sprite;
public MyGL20Renderer(final Context activityContext)
{
mActivityContext = activityContext;
}
public void onSurfaceCreated(GL10 unused, EGLConfig config)
{
//Set the background frame color
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
//Set the camera position (View Matrix) //mtx, offset, eyex,y,z, centrex,y,z, upx,y,z
Matrix.setLookAtM(mVMatrix, 0,
0, 0, -1.5f, //Eye XYZ - position eye behind the origin
0f, 0f, -5.0f, //Look XYZ - We are looking toward the distance
0f, 1.0f, 0.0f); //Up XYZ - Up vector - where head would be pointing if holding the camera
//Initialize Shapes
//triangle = new Triangle();
//sprite = new Sprite(mActivityContext);
//Sprite newSprite;
float xMax = 2.0f;
float yMax = 2.0f;
//rand = 0->1
float newX = (new Random().nextFloat() * xMax * 2) - xMax; //2.0f; //-2 -> +2
float newY = (new Random().nextFloat() * yMax * 2) - yMax; //-3 -> +3
float newZ = 0f;
//for (int i=0; i<numObjects; i++) {
//newSprite = new Sprite(mActivityContext);
//spriteList.add(new Sprite(mActivityContext, newX, newY, newZ));
//}
spriteList.add(new Sprite(mActivityContext, -0.0f, -0.0f, 0.0f));
spriteList.add(new Sprite(mActivityContext, +0.5f, -0.5f, 0.0f));
//spriteList.add(new Sprite(mActivityContext, -1.0f, +1.0f, 0.0f));
//spriteList.add(new Sprite(mActivityContext, +1.0f, +1.0f, 0.0f));
}
public void onDrawFrame(GL10 unused)
{
//init
Sprite currSprite;
//Redraw background color
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
//timing
float jFactor = 0.1f;
long time = SystemClock.uptimeMillis() % 10000L;
float angleInDegrees = (360.0f / 1000.0f) * ((int) time) * jFactor;
/*
//number 1
//Matrix.setIdentityM(mModelMatrix, 0);
//currSprite = spriteList.get(0);
//Matrix.rotateM(mModelMatrix, 0, angleInDegrees, 0.0f, 0.0f, 1.0f);
//currSprite.Draw(mModelMatrix);
//number 2
Matrix.setIdentityM(mModelMatrix, 0);
currSprite = spriteList.get(1);
Matrix.translateM(mModelMatrix, 0, 0.0f, -0.1f, 0.0f);
//Matrix.rotateM(mModelMatrix, 0, 90.0f, 1.0f, 0.0f, 0.0f);
Matrix.rotateM(mModelMatrix, 0, angleInDegrees, 0.0f, 0.0f, 1.0f);
currSprite.Draw(mModelMatrix);
//Matrix.translateM(mModelMatrix, 0, 0, 0, 4.0f);
*/
//Calculate the projection and view transformation
Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mVMatrix, 0);
//zoom out a bit?
Matrix.translateM(mMVPMatrix, 0, 0, 0, 4.0f);
//number 1
//currSprite = spriteList.get(0);
//Matrix.setIdentityM(mMVPMatrix, 0);
//Matrix.rotateM(mMVPMatrix, 0, angleInDegrees, 0.0f, 0.0f, 1.0f);
//Matrix.translateM(mMVPMatrix, 0, currSprite.coordX, 0.0f, 0.0f);
//currSprite.coordX += 0.01f;
//currSprite.Draw(mMVPMatrix);
//number 2
currSprite = spriteList.get(0);
Matrix.setIdentityM(mMVPMatrix, 0);
Matrix.translateM(mMVPMatrix, 0, 0.0f, 0.0f, 0.0f);
Matrix.rotateM(mMVPMatrix, 0, angleInDegrees, 0.0f, 0.0f, +1.0f);
//float[] mTempMatrix = new float[16];
//mTempMatrix = mModelMatrix.clone();
//Matrix.multiplyMM(mMVPMatrix, 0, mMVPMatrix, 0, mRotateMatrix, 0);
//mTempMatrix = mMVPMatrix.clone();
//Matrix.multiplyMM(mMVPMatrix, 0, mTempMatrix, 0, mModelMatrix, 0);
//Matrix.setIdentityM(mMVPMatrix, 0);
currSprite.Draw(mMVPMatrix);
/*
//Set the camera position (View Matrix) //mtx, offset, eyex,y,z, centrex,y,z, upx,y,z
Matrix.setLookAtM(mVMatrix, 0,
0, 0, -10,
0f, 0f, 0f,
0f, 1.0f, 0.0f);
//Calculate the projection and view transformation
Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mVMatrix, 0);
//zoom out a bit?
Matrix.translateM(mMVPMatrix, 0, 0, 0, 4.0f);
for (int i=0; i<numObjects; i++) {
//Create a rotation transformation for the triangle
//Matrix.setRotateM(mRotationMatrix, 0, mAngle, 0, 0, -1.0f);
Matrix.setRotateM(mRotationMatrix, 0, 0, 0, 0, -1.0f); //-1.0 = Z, for some reason need this. Grr
//Combine the rotation matrix with the projection and camera view
Matrix.multiplyMM(mMVPMatrix, 0, mRotationMatrix, 0, mMVPMatrix, 0);
//Draw Shape
//triangle.Draw(mMVPMatrix);
//sprite.Draw(mMVPMatrix);
currSprite = spriteList.get(i);
//Move the object to the passed initial coordinates?
//Matrix.translateM(mMVPMatrix, 0, currSprite.coordX, currSprite.coordY, currSprite.coordZ);
currSprite.Draw(mMVPMatrix);
}
*/
}
public void onSurfaceChanged(GL10 unused, int width, int height)
{
GLES20.glViewport(0, 0, width, height);
if (height == 0) {
height = 1; //incase of div 0 errors
}
float ratio = (float) width / height;
final float left = -ratio;
final float right = ratio;
final float bottom = -1.0f;
final float top = 1.0f;
final float near = 1.0f;
final float far = 10.f;
//This Projection Matrix is applied to object coordinates in the onDrawFrame() method
//Matrix.frustumM(mProjMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
Matrix.frustumM(mProjMatrix, 0, left, right, bottom, top, near, far);
}
public static int loadShader(int type, String shaderCode)
{
//Create a Vertex Shader Type Or a Fragment Shader Type (GLES20.GL_VERTEX_SHADER OR GLES20.GL_FRAGMENT_SHADER)
int shader = GLES20.glCreateShader(type);
//Add The Source Code and Compile it
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
return shader;
}
}
Please excuse the commented code in OnDrawFrame() where I've been experimenting, and failing.
Sprite Class
public class Sprite
{
//Reference to Activity Context
private final Context mActivityContext;
//Added for Textures
private final FloatBuffer mCubeTextureCoordinates;
private int mTextureUniformHandle;
private int mTextureCoordinateHandle;
private final int mTextureCoordinateDataSize = 2;
private int mTextureDataHandle;
private final String vertexShaderCode =
//Test
"attribute vec2 a_TexCoordinate;" +
"varying vec2 v_TexCoordinate;" +
//End Test
"uniform mat4 uMVPMatrix;" +
"attribute vec4 vPosition;" +
"void main() {" +
" gl_Position = vPosition * uMVPMatrix;" +
//Test
"v_TexCoordinate = a_TexCoordinate;" +
//End Test
"}";
private final String fragmentShaderCode =
"precision mediump float;" +
"uniform vec4 vColor;" +
//Test
"uniform sampler2D u_Texture;" +
"varying vec2 v_TexCoordinate;" +
//End Test
"void main() {" +
//"gl_FragColor = vColor;" +
"gl_FragColor = (vColor * texture2D(u_Texture, v_TexCoordinate));" +
"}";
private final int shaderProgram;
private final FloatBuffer vertexBuffer;
private final ShortBuffer drawListBuffer;
private int mPositionHandle;
private int mColorHandle;
private int mMVPMatrixHandle;
public float coordX;
public float coordY;
//public float coordZ;
// number of coordinates per vertex in this array
static final int COORDS_PER_VERTEX = 2;
static float spriteCoords[] = { -0.5f, 0.5f, // top left
-0.5f, -0.5f, // bottom left
0.5f, -0.5f, // bottom right
0.5f, 0.5f }; //top right
private short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; //Order to draw vertices
private final int vertexStride = COORDS_PER_VERTEX * 4; //Bytes per vertex
// Set color with red, green, blue and alpha (opacity) values
//float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f };
float color[] = { 1f, 1f, 1f, 1.0f };
public Sprite(final Context activityContext, float initX, float initY, float initZ)
{
mActivityContext = activityContext;
this.coordX = initX;
this.coordY = initY;
//this.coordZ = initZ;
//ergh - will do manually for now. Paxo n00b
//just a 2D array, no need for Z nonsense
for (int i=0; i<spriteCoords.length; i++) {
spriteCoords[i] -= (i%2==0) ? coordX : coordY; //- works better than +
}
//float newPosMatrix[] = { initX, initY, 0f };
//adjust the vector coords accordingly
//Matrix.multiplyMV(spriteCoords, 0, newPosMatrix, 0, spriteCoords, 0);
//Initialize Vertex Byte Buffer for Shape Coordinates / # of coordinate values * 4 bytes per float
ByteBuffer bb = ByteBuffer.allocateDirect(spriteCoords.length * 4);
//Use the Device's Native Byte Order
bb.order(ByteOrder.nativeOrder());
//Create a floating point buffer from the ByteBuffer
vertexBuffer = bb.asFloatBuffer();
//Add the coordinates to the FloatBuffer
vertexBuffer.put(spriteCoords);
//Set the Buffer to Read the first coordinate
vertexBuffer.position(0);
// S, T (or X, Y)
// Texture coordinate data.
// Because images have a Y axis pointing downward (values increase as you move down the image) while
// OpenGL has a Y axis pointing upward, we adjust for that here by flipping the Y axis.
// What's more is that the texture coordinates are the same for every face.
final float[] cubeTextureCoordinateData =
{
//Front face
/*0.0f, 0.0f,
0.0f, 1.0f,
1.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f,
1.0f, 0.0f*/
/*-0.5f, 0.5f,
-0.5f, -0.5f,
0.5f, -0.5f,
0.5f, 0.5f*/
0f, 1f,
0f, 0f,
1f, 0f,
1f, 1f
};
mCubeTextureCoordinates = ByteBuffer.allocateDirect(cubeTextureCoordinateData.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
mCubeTextureCoordinates.put(cubeTextureCoordinateData).position(0);
//Initialize byte buffer for the draw list
ByteBuffer dlb = ByteBuffer.allocateDirect(spriteCoords.length * 2);
dlb.order(ByteOrder.nativeOrder());
drawListBuffer = dlb.asShortBuffer();
drawListBuffer.put(drawOrder);
drawListBuffer.position(0);
int vertexShader = MyGL20Renderer.loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
int fragmentShader = MyGL20Renderer.loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
shaderProgram = GLES20.glCreateProgram();
GLES20.glAttachShader(shaderProgram, vertexShader);
GLES20.glAttachShader(shaderProgram, fragmentShader);
//Texture Code
GLES20.glBindAttribLocation(shaderProgram, 0, "a_TexCoordinate");
GLES20.glLinkProgram(shaderProgram);
//Load the texture
mTextureDataHandle = loadTexture(mActivityContext, R.drawable.cube);
}
public void Draw(float[] mvpMatrix)
{
//Add program to OpenGL ES Environment
GLES20.glUseProgram(shaderProgram);
//Get handle to vertex shader's vPosition member
mPositionHandle = GLES20.glGetAttribLocation(shaderProgram, "vPosition");
//Enable a handle to the triangle vertices
GLES20.glEnableVertexAttribArray(mPositionHandle);
//Prepare the triangle coordinate data
GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, vertexBuffer);
//Get Handle to Fragment Shader's vColor member
mColorHandle = GLES20.glGetUniformLocation(shaderProgram, "vColor");
//Set the Color for drawing the triangle
GLES20.glUniform4fv(mColorHandle, 1, color, 0);
//Set Texture Handles and bind Texture
mTextureUniformHandle = GLES20.glGetAttribLocation(shaderProgram, "u_Texture");
mTextureCoordinateHandle = GLES20.glGetAttribLocation(shaderProgram, "a_TexCoordinate");
//Set the active texture unit to texture unit 0.
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
//Bind the texture to this unit.
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureDataHandle);
//Tell the texture uniform sampler to use this texture in the shader by binding to texture unit 0.
GLES20.glUniform1i(mTextureUniformHandle, 0);
//Pass in the texture coordinate information
mCubeTextureCoordinates.position(0);
GLES20.glVertexAttribPointer(mTextureCoordinateHandle, mTextureCoordinateDataSize, GLES20.GL_FLOAT, false, 0, mCubeTextureCoordinates);
GLES20.glEnableVertexAttribArray(mTextureCoordinateHandle);
//Get Handle to Shape's Transformation Matrix
mMVPMatrixHandle = GLES20.glGetUniformLocation(shaderProgram, "uMVPMatrix");
//Apply the projection and view transformation
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);
//glTranslatef(0f, 0f, 0f);
//Draw the triangle
GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawOrder.length, GLES20.GL_UNSIGNED_SHORT, drawListBuffer);
//Disable Vertex Array
GLES20.glDisableVertexAttribArray(mPositionHandle);
}
public static int loadTexture(final Context context, final int resourceId)
{
final int[] textureHandle = new int[1];
GLES20.glGenTextures(1, textureHandle, 0);
if (textureHandle[0] != 0)
{
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false; // No pre-scaling
// Read in the resource
final Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resourceId, options);
// Bind to the texture in OpenGL
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle[0]);
// Set filtering
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
// Load the bitmap into the bound texture.
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
// Recycle the bitmap, since its data has been loaded into OpenGL.
bitmap.recycle();
}
if (textureHandle[0] == 0)
{
throw new RuntimeException("Error loading texture.");
}
return textureHandle[0];
}
}
Now, I don't know if I'm going about this the right way at all, but I simply want to just animate the collection of Sprite objects in spriteList.
More specifically, have a collection of 3 objects and then respond to screen touch, and animate the objects to that location (but that will come later)
Initially, I just want to be able to correctly render these objects (with initial locations) and then rotate them on the centre point (about the Z axis).
For some reason, TranslateM is warping the texture (as if about the Y axis) and not actually moving an object along the X/Y planes
Many thanks for any help you can offer. As you can see I'm fairly new to OpenGL and have had little luck with the limited tutorials out there that support Android Studio and GLES2.0.
Kind regards,
James
I think the problem is that you have not multiplied the translation matrices into your rotation matrices. A matrix multiply is required to combine those.