I'm working on my very first openGL game, inspired by the game "Greed Corp" on the playstation network. It's a turn based strategy game that is based on a hex grid. Each hexagon tile has it's own height and texture.
I'm currently drawing a hexagon based on some examples and tutorials I've read. Here's my hextile class:
public class HexTile
{
private float height;
private int[] textures = new int[1];
private float vertices[] = { 0.0f, 0.0f, 0.0f, //center
0.0f, 1.0f, 0.0f, // top
-1.0f, 0.5f, 0.0f, // left top
-1.0f, -0.5f, 0.0f, // left bottom
0.0f, -1.0f, 0.0f, // bottom
1.0f, -0.5f, 0.0f, // right bottom
1.0f, 0.5f, 0.0f, // right top
};
private short[] indices = { 0, 1, 2, 3, 4, 5, 6, 1};
//private float texture[] = { };
private FloatBuffer vertexBuffer;
private ShortBuffer indexBuffer;
//private FloatBuffer textureBuffer;
public HexTile()
{
ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
vbb.order(ByteOrder.nativeOrder());
vertexBuffer = vbb.asFloatBuffer();
vertexBuffer.put(vertices);
vertexBuffer.position(0);
ByteBuffer ibb = ByteBuffer.allocateDirect(indices.length * 2);
ibb.order(ByteOrder.nativeOrder());
indexBuffer = ibb.asShortBuffer();
indexBuffer.put(indices);
indexBuffer.position(0);
/*ByteBuffer tbb = ByteBuffer.allocateDirect(texture.length * 4);
tbb.order(ByteOrder.nativeOrder());
textureBuffer = tbb.asFloatBuffer();
textureBuffer.put(texture);
textureBuffer.position(0);*/
}
public void setHeight(float h)
{
height = h;
}
public float getHeight()
{
return height;
}
public void draw(GL10 gl)
{
gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
//gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, textureBuffer);
gl.glDrawElements(GL10.GL_TRIANGLE_FAN, indices.length, GL10.GL_UNSIGNED_SHORT, indexBuffer);
}
public void loadGLTexture(GL10 gl, Context context)
{
textures[0] = -1;
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.hex);
while(textures[0] <= 0)
gl.glGenTextures(1, textures, 0);
gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
bitmap.recycle();
}
}
Every frame I'm looping through all visible tiles to draw them, that would be 11 * 9 tiles MAX. This however drops my framerate to 38, and that is without even drawing textures on them, just the flat hexagons.
Now I'm trying to figure out how to increase performance. I figured drawing the whole grid at once could be faster, but I have no idea how to do that, since each tile can have a different height, and will most likely have a different texture than a neighboring tile.
I'd really appreciate some help on this, because I'd like to get started on the actual game ^.^
Assuming your hex grid is static you can just spin over all your hexagons once, generate their geometry, and append everything to one (or more, if you have more than 2^16 vertices) large VBO that you can draw in one go.
As far as textures go you may be able to use a texture atlas.
I'm currently learning OpenGL as well, I've produced one Android OpenGL application called 'Cloud Stream' where I found similar issues with performance.
As a general answer to performance concerns, there are a few things which can help. The Vertex pipeline for Graphics at a hardware level apparently is more efficient when passed larger amounts of vertices at once. Calling glVertexPointer for each Hex tile is not as efficient as calling it once with the vertices of all tiles.
This makes things harder to code as you essentially draw all your tiles at once, but it does seem to speed things up a bit. In my application, all of the clouds are drawn in the one call.
Other avenues to try would be to save the Vertice positions in a VBO which I found to be quite tricky, at least it was when trying to cater for 2.1 users. These days things might be easier, I'm not sure. With that the idea is to save the Vertice array for your tile into Video Memory and you get back a pointer like you do with your Textures. As you can imagine, not sending your Vertice Array Buffer up each frame speeds things up a little for each tile draw. Even if things aren't static its a good approach as I doubt things are changing for each frame.
Another suggestion I came across online was to use Short instead of Float for your Vertices. And then to change the scale of your finished rendering to get your desired size. This would lower the size of your vertices and speed things up a little... not a huge amount but still worth trying I would say.
One last thing I would like to add, if you end up using any Transparency... be aware that you must paint back to front for it to work which has a performance impact as well. If you draw front to back, the Rendering engine automatically knows not to draw when coordinates are not visible... drawing back to front means everything is drawn. Keep this in mind and always try to draw front to back when possible.
Good luck with your game, I'd love to know how you got on... I'm just starting my game and I'm quite excited to start. If you haven't already come across this... I think it's worth a read. http://www.codeproject.com/KB/graphics/hexagonal_part1.aspx
Related
I am developing an OpenGL 2.0 live wallpaper for android and the rendering is laggy (~11fps). In no means am I an expert, but i couldn't find any online resources to answer my question. Benchmarking, I found out that 'onDrawFrame(GL10 glUnused)' stalls because of how many objects I use. I get an OK framerate if i restrict it to rendering ~100 objects, but my wallpaper consists of a raster of ~1000 triangles. Is this the problem? Here is the code:
public void onDrawFrame(GL10 glUnused) {
if (para.xOffset > f) {
Matrix.translateM(mViewMatrix, 0, (para.xOffset - f) * 10, 0, 0);
f = para.xOffset;
} else {
Matrix.translateM(mViewMatrix, 0, (para.xOffset - f) * 10, 0, 0);
f = para.xOffset;
}
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
GLES20.glUseProgram(mPerVertexProgramHandle);
mMVPMatrixHandle = GLES20.glGetUniformLocation(mPerVertexProgramHandle,
"u_MVPMatrix");
mMVMatrixHandle = GLES20.glGetUniformLocation(mPerVertexProgramHandle,
"u_MVMatrix");
mLightPosHandle = GLES20.glGetUniformLocation(mPerVertexProgramHandle,
"u_LightPos");
mPositionHandle = GLES20.glGetAttribLocation(mPerVertexProgramHandle,
"a_Position");
mColorHandle = GLES20.glGetAttribLocation(mPerVertexProgramHandle,
"a_Color");
mNormalHandle = GLES20.glGetAttribLocation(mPerVertexProgramHandle,
"a_Normal");
Matrix.setIdentityM(mLightModelMatrix, 0);
Matrix.translateM(mLightModelMatrix, 0, 0.0f, 0.0f, -5.0f);
Matrix.translateM(mLightModelMatrix, 0, 0.0f, 0.0f, 2.0f);
Matrix.multiplyMV(mLightPosInWorldSpace, 0, mLightModelMatrix, 0,
mLightPosInModelSpace, 0);
for (newHexagon nha[] : h.hfgrd) {
for (newHexagon nh : nha) {
Matrix.setIdentityM(mModelMatrix, 0);
Matrix.translateM(mModelMatrix, 0, nh.PosX, nh.PosY, -5);
final FloatBuffer colors;
colors = ByteBuffer
.allocateDirect(nh.colors.length * mBytesPerFloat)
.order(ByteOrder.nativeOrder()).asFloatBuffer();
colors.put(nh.colors).position(0);
drawthis(colors);
}
}
GLES20.glUseProgram(mPointProgramHandle);
h.draw(0, 0);
}
edit: Additional Information: the triangles are unisize and arranged in a flat hexagonal tiling of the screen.
CPU: Dual-core 1.2 GHz Scorpion
GPU: Adreno 220
Device: HTC Sensation
Android v4.0.3
There are at least 3 major problems in your code:
Don't call glGetUniformLocation on each frame - assign them to fields once after shader compilation. Getting uniforms/attributes is a rather expensive operation.
You may generate too much draw calls (glDrawElements or glDrawArrays). Your drawthis() method supposedly issues at least one draw call. It is OK for modern hardware to handle max 200-500 draw calls. Each draw call has a small overhead of driver-to-GPU communication so the best practice is to batch multiple draw calls as much as possible. But please provide code of drawthis() for better understanding of problem.
ByteBuffer.allocateDirect() on each draw call. You have to avoid any memory allocations, and you are doing it in loop.
I'm trying to create a simple game in android. By road I meant games like Temple Run or Subway Surf but much simpler and abstract so I could do it only with the OpenGL ES without any other libraries.
So I've read a lot of basic tutorials that explains the 3D construction logic and used the basic sample of creating a 3D cube that rotates.
I am now trying to use that sample to create the game road. I made the square to look more like a rectangle and duplicate it to a 30x5 square road. I've tried many combinations and the internet to find a solution and yet I have this problems\questions:
How do I set all 30x5 squares to be one next to another? I'm always
getting the squares with some unwanted gap
I want to set the vieweye point (the "camera") 45 degrees to the
middle of the first row, so the player could see the road upon him
Next, I would want to move along the road. So Iv'e seen the rotate
and how it works. Is there a way to do the same to the viewpoint or
do I need to change the squares drawing Z's?
I see that onDrawFrame() is calling over and over many times. To
control the FPS, I've seen on the internet that people have used
there own FPS calculation with a sleep(). Isn't there a built one
already?
GLRenderer code:
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.content.Context;
import android.opengl.GLSurfaceView;
import android.opengl.GLU;
import android.util.Log;
class GLRenderer implements GLSurfaceView.Renderer {
private static final String TAG = "GLRenderer" ;
private final Context context;
private float mCubeRotation = 70.0f;
private Triangle triangle;
private Cube[][] cube;
GLRenderer(Context context) {
this.context = context;
}
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
gl.glClearColor(0.0f, 0.0f, 0.0f, 0.5f);
gl.glClearDepthf(1.0f);
gl.glEnable(GL10.GL_DEPTH_TEST);
gl.glDepthFunc(GL10.GL_LEQUAL);
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,GL10.GL_NICEST);
}
public void onSurfaceChanged(GL10 gl, int width, int height) {
Log.d("MyOpenGLRenderer", "Surface changed. Width=" + width
+ " Height=" + height);
System.out.println("arg");
//get map
cube = new Cube[30][5];
for(int i = 0; i < cube.length; i++)
for(int j = 0; j < cube[i].length; j++)
cube[i][j] = new Cube();
//draw triangle
triangle = new Triangle(0.5f, 1, 0, 0);
// Define the view frustum
gl.glViewport(0, 0, width, height);
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();
float ratio = (float) width / height;
GLU.gluPerspective(gl, 45.0f, ratio, 0.1f, 100.0f);
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
}
public void onDrawFrame(GL10 gl) {
// Clear the screen to black
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
//translate(dx, dy, dz)
// Position model so we can see it
//gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glTranslatef(0.0f, 0.0f, -10.0f);
gl.glRotatef(mCubeRotation, 1.0f, 1.0f, 1.0f);
gl.glTranslatef(0.0f, 0.0f, -10.0f);
cube[0][0].draw(gl);
gl.glTranslatef(0.0f, 0.0f, -10.0f);
cube[0][1].draw(gl);
gl.glTranslatef(0.0f, 0.0f, -10.0f);
cube[0][2].draw(gl);
gl.glLoadIdentity();
//set rotation
mCubeRotation -= 0.15f;
System.out.println("mCubeRotation: "+mCubeRotation);
}
}
Cube code:
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import javax.microedition.khronos.opengles.GL10;
class Cube {
private FloatBuffer mVertexBuffer; //vertex
private FloatBuffer mColorBuffer; //color
private ByteBuffer mIndexBuffer; //face indices
float width = 1.0f;
float height = 0.5f;
float depth = 1.0f;
private float vertices[] = {
-width, -height, -depth, // 0
width, -height, -depth, // 1
width, height, -depth, // 2
-width, height, -depth, // 3
-width, -height, depth, // 4
width, -height, depth, // 5
width, height, depth, // 6
-width, height, depth, // 7
};
private float colors[] = {
0.0f, 1.0f, 0.0f,
1.0f, 0.0f, 1.0f,
0.0f, 1.0f, 1.0f,
0.5f, 0.0f, 1.0f,
1.0f, 0.5f, 0.0f,
1.0f, 1.0f, 0.0f,
0.0f, 1.0f, 1.0f,
0.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
1.0f, 1.0f, 0.0f,
1.0f, 1.0f
};
private byte indices[] = {
0, 4, 5,
0, 5, 1,
1, 5, 6,
1, 6, 2,
2, 6, 7,
2, 7, 3,
3, 7, 4,
3, 4, 0,
4, 7, 6,
4, 6, 5,
3, 0, 1,
3, 1, 2
};
public Cube() {
ByteBuffer byteBuf = ByteBuffer.allocateDirect(vertices.length * 4);
byteBuf.order(ByteOrder.nativeOrder());
mVertexBuffer = byteBuf.asFloatBuffer();
mVertexBuffer.put(vertices);
mVertexBuffer.position(0);
byteBuf = ByteBuffer.allocateDirect(colors.length * 4);
byteBuf.order(ByteOrder.nativeOrder());
mColorBuffer = byteBuf.asFloatBuffer();
mColorBuffer.put(colors);
mColorBuffer.position(0);
mIndexBuffer = ByteBuffer.allocateDirect(indices.length);
mIndexBuffer.put(indices);
mIndexBuffer.position(0);
}
public void draw(GL10 gl) {
gl.glFrontFace(GL10.GL_CW);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mVertexBuffer);
gl.glColorPointer(4, GL10.GL_FLOAT, 0, mColorBuffer);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
gl.glDrawElements(GL10.GL_TRIANGLES, 36, GL10.GL_UNSIGNED_BYTE,
mIndexBuffer);
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
}
}
Eventually I'll draw the square array using glDrawArrays() or glDrawElements() but for now I've used only 3 objects.
There's a lot of questions here. I can't cover everything in detail, but hopefully I can give you some pointers to steer you in the right direction.
To draw 150 squares, you have a number of options:
Create a vertex buffer with a single square, and draw it 150 times, with translations applied. This is probably the easiest to get you off the ground, so I would recommend getting it working first. It's a reasonable approach if all your squares look the same.
Create 150 vertex buffers, with different coordinates. I wouldn't recommend it because it's the least efficient, and doesn't have any benefits over other approaches.
Store the vertices for all 150 squares in a single vertex buffer. This will be the most efficient of the first 3 options, but only works well as long as the relative orientation of the squares remains the same. You may want to try this once you have the basics working.
Use instanced rendering. This is a more advanced feature, and only available in ES 3.0. Just mentioning it for future reference.
What you attempted is sort of a hybrid between option 1 and 2. If you want to go for option 1, you only need one instance of your Cube class. If you look at what you did, this makes sense. You created 150 objects that are all exactly the same, which is not very useful.
Now, on your questions:
To draw the squares without gaps between them, the amount of your translations needs to be the same as the size of each square. Your squares are 2 units wide, but you translate each one by 10 units. You also translate them in the z-direction, which I don't quite understand.
If you want to stick with the kind of functionality you have been using, check out GLU.gluLookAt(). It allows you to place your camera where you want it, and point it in any direction.
Same as 2. Call GLU.gluLookAt() every time you want to move the viewpoint.
Android caps the frame rate at 60 frames per second. That's normally what you should be shooting for anyway, IMHO. If you want to limit it to 30 fps later to save power, I think you can cross that bridge when you get there. Based on what I researched recently, there's no clean and portable way to do this on Android. The proposed solutions I have seen all look kind of hacky to me.
A couple more things on your code:
Your color definitions look odd. You specify colors in 4 components, and the size of the array is correct for that. But you write the array with 3 values per line, which makes it look like you want 3 component colors. Either one can be done, but you need to make sure that you're consistent. 3 components are enough, unless you need transparency.
You are using ES 1.0. That's valid, and might be easier to get started with. But you should be aware that many of its features are considered obsolete, and using ES 2.0 would let you learn more modern and current OpenGL features. The initial hurdle will be higher, so there's definitely a tradeoff here.
I've posted my drawing method which is called each frame.
I change the vertices each frame to move the object (which is basically a sprite/textured quad).
As you can see I was initially creating an array each frame, but I have changed this now and I create the array initially and just update it every frame, however I'm wondering if I can do anything more to improve the efficiency? (Although I'm getting about 90fps the sprite does not move smoothly all the time, every now and then it just pauses for a split second). I can't see garbage collector running but I'm guessing it's due to allocation).
As I add more sprites/quads the jerkiness gets worse, but event at 100+ quads, although the smoothness has all but gone, my frame rate is still around 60fps so I can't understand what is slowing this down?
I've also added a screencap from Allocation Tracker
Any help would be appreciated.
public void drawTest(float x, float y, float[] mvpMatrix){
//Convert Co-ordinates
//Left
xPlotLeft = (-MyGLRenderer.ratio)+((x)*MyGLRenderer.coordStepAmountWidth);
//Top
yPlotTop = +1-((y)*MyGLRenderer.coordStepAmountHeight);
//Right
xPlotRight = xPlotLeft+((quadWidth)*MyGLRenderer.coordStepAmountWidth);
//Bottom
yPlotBottom = yPlotTop-((quadHeight)*MyGLRenderer.coordStepAmountHeight);
// Following has been changed as per below. I am now declaring the array initially and just updating it every frame.
// float[] vertices = {
//Top Left
// xPlotLeft,yPlotTop,0, 0,0,
//Top Right
// xPlotRight,yPlotTop,0, 1,0,
//Bottom Left
// xPlotLeft,yPlotBottom,0, 0,1,
//Bottom Right
// xPlotRight,yPlotBottom,0, 1,1
// };
vertices[0]=xPlotLeft;
vertices[1]=yPlotTop;
vertices[2]=0;
vertices[3]=0;
vertices[4]=0;
vertices[5]=xPlotRight;
vertices[6]=yPlotTop;
vertices[7]=0;
vertices[8]=1;
vertices[9]=0;
vertices[10]=xPlotLeft;
vertices[11]=yPlotBottom;
vertices[12]=0;
vertices[13]=0;
vertices[14]=1;
vertices[15]=xPlotRight;
vertices[16]=yPlotBottom;
vertices[17]=0;
vertices[18]=1;
vertices[19]=1;
vertexBuf = ByteBuffer.allocateDirect(vertices.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
vertexBuf.put(vertices).position(0);
//GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
//Bind texture
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texID);
//Use program
GLES20.glUseProgram(iProgId);
// Combine the rotation matrix with the projection and camera view
Matrix.multiplyMM(mvpMatrix2, 0, mvpMatrix, 0, mRotationMatrix, 0);
// get handle to shape's transformation matrix
mMVPMatrixHandle = GLES20.glGetUniformLocation(iProgId, "uMVPMatrix");
// Apply the projection and view transformation
GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix2, 0);
//Set starting position for vertices (0 for position)
vertexBuf.position(0);
//Specify attributes for vertex
GLES20.glVertexAttribPointer(iPosition, 3, GLES20.GL_FLOAT, false, 5 * 4, vertexBuf);
//Enable attribute for position
GLES20.glEnableVertexAttribArray(iPosition);
//Set starting position for vertices (3 for texture)
vertexBuf.position(3);
//Specify attributes for vertex
GLES20.glVertexAttribPointer(iTexCoords, 2, GLES20.GL_FLOAT, false, 5 * 4, vertexBuf);
//Enable attribute for texture
GLES20.glEnableVertexAttribArray(iTexCoords);
//Enable Alpha blending and set blending function
GLES20.glEnable(GLES20.GL_BLEND);
GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);
//Draw
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
//Disable Alpha blending
GLES20.glDisable(GLES20.GL_BLEND);
}
ByteBuffer.allocateDirect() allocates a new buffer in memory every frame, you can create an initial buffer and overwrite the contents instead. Just use rewind() or position(0) before put().
To improve matters further, use a VBO (vertex buffer object, there are many tutorials online, and several questions on SO on this topic) and glBufferSubData to update the buffer.
I am examining an interesting problem I'm facing with OpenGL lighting on Android. I'm working on a 3D Viewer where you can add and manipulate 3D objects. You can also set a light with different attributes. The problem I was facing with my Viewer was that the highlight on the 3D objects from the light (it is a point light) behaved strangely. If the light source was in the exact same point as the camera, the highlight would move in the opposite direction you would expect. (So if you move the object to the left, the highlight moves to the leftedge of the object as well, instead of the right, which is what I was expecting.)
So to further narrow the problem down I've created a small sample application that only renders a square and then I rotate that square around the camera position (the origin), which is also where the light is placed. This should result in all squares facing the camera directly, so that they would be completely highlighted. The result though looked like that:
Can it be that these artifacts appear because of the distortion you get on the border due to the projection?
In the first image the distance between the sphere and the camera is about 20 units and the size of the sphere is about 2. If I move the light closer to the object the highlight looks a lot better, in the way I'm expecting it.
In the second image the radius in which the squares are located is 25 units.
I'm using OpenGL ES 1.1 (since I was struggling to get it to work with shaders in ES 2.0) on Android 3.1
Here is some of the code I'm using:
public void onDrawFrame(GL10 gl) {
// Setting the camera
GLU.gluLookAt(gl, 0, 0, 0, 0f, 0f, -1f, 0f, 1.0f, 0.0f);
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
for (int i = 0; i < 72; i++) {
gl.glPushMatrix();
gl.glRotatef(5f * i, 0, 1, 0);
gl.glTranslatef(0, 0, -25);
draw(gl);
gl.glPopMatrix();
}
}
public void draw(GL10 gl) {
setMaterial(gl);
gl.glEnable(GL10.GL_NORMALIZE);
gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glFrontFace(GL10.GL_CCW);
// Enable the vertex and normal state
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mVertexBuffer);
gl.glNormalPointer(GL10.GL_FLOAT, 0, mNormalBuffer);
gl.glDrawElements(GL10.GL_TRIANGLES, mIndexBuffer.capacity(), GL10.GL_UNSIGNED_SHORT, mIndexBuffer);
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
gl.glDisableClientState(GL10.GL_NORMAL_ARRAY);
}
// Setting the light
private void drawLights(GL10 gl) {
// Point Light
float[] position = { 0, 0, 0, 1 };
float[] diffuse = { .6f, .6f, .6f, 1f };
float[] specular = { 1, 1, 1, 1 };
float[] ambient = { .2f, .2f, .2f, 1 };
gl.glEnable(GL10.GL_LIGHTING);
gl.glEnable(GL10.GL_LIGHT0);
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
gl.glLightfv(GL10.GL_LIGHT0, GL_POSITION, position, 0);
gl.glLightfv(GL10.GL_LIGHT0, GL_DIFFUSE, diffuse, 0);
gl.glLightfv(GL10.GL_LIGHT0, GL_AMBIENT, ambient, 0);
gl.glLightfv(GL10.GL_LIGHT0, GL_SPECULAR, specular, 0);
}
private void setMaterial(GL10 gl) {
float shininess = 30;
float[] ambient = { 0, 0, .3f, 1 };
float[] diffuse = { 0, 0, .7f, 1 };
float[] specular = { 1, 1, 1, 1 };
gl.glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse, 0);
gl.glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, ambient, 0);
gl.glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular, 0);
gl.glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, shininess);
}
I'm setting the light in the beginning, when the activity is started (in onSurfaceCreated) and the material everytime I draw a square.
The effect in your second example (with the squares) is rather due to the default non-local viewer that OpenGL uses. By default the eye-space view vector (the vector from vertex to camera, used for the specular highlight computation) is just taken to be the (0, 0, 1)-vector, instead of the normalized vertex position. This approximation is only correct if the vertex is in the middle of the screen, but gets more and more incorrect the farther you move to the boundary of the srceen.
To change this and let OpenGL use the real vector from the vertex to the camera, just use the glLightModel function, especially
glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);
I'm not sure if this is also the cause for your first problem (with the sphere), but maybe, just try it.
EDIT: It seems you cannot use GL_LIGHT_MODEL_LOCAL_VIEWER in OpenGL ES. In this case there is no way around this problem, except switching to OpenGL ES 2.0 and doing all lighting computations yourself, of course.
Your light is probably moving when you're moving your object.
Take a look at this answer http://www.opengl.org/resources/faq/technical/lights.htm#ligh0050
I've been beating my head against the desk trying to figure this out for days now, and after scouring Stack Overflow and the web, I haven't found any examples that have worked for me. I've finally got code that's seems close, so maybe you guys (and gals?) can help me figure this out.
My first problem is that I'm trying to implement a motion blur by taking a screen grab as a texture, then drawing the texture over the next frame with transparency -- or use more frames for more blur. (If anyone's interested, this is the guide I followed: http://www.codeproject.com/KB/openGL/MotionBlur.aspx)
I've got the screen saving as a texture working fine. The issue I'm having is drawing in Ortho mode on top of the screen. After much head banging, I finally got a basic square drawing, but my lack of OpenGL ES understanding and an easy to follow example are holding me back now. I need to take the texture I saved, and draw it into the square I drew. Nothing I've been doing seems to work.
Also, my second problem, is drawing more complex 3d models into Ortho mode. I can't seem to get any models to draw. I'm using the (slightly customized) min3d framework (http://code.google.com/p/min3d/), and I'm trying to draw Object3d's in Ortho mode just like I draw them in Perspective mode. As I understand it, they should draw the same, they should just not have depth. Yet I don't seem to see them at all.
Here's the code I'm working with. I've tried a ton of different things and this is the closest I've gotten (actually drawing something on the screen that can be seen). I still have no idea how to get a proper 3d model drawing in the ortho view. I'm sure I'm doing something horribly wrong and probably completely misunderstanding some basic aspects of OpenGL drawing. Let me know if there's any other code I need to post.
// Gets called once, before all drawing occurs
//
private void reset()
{
// Reset TextureManager
Shared.textureManager().reset();
// Do OpenGL settings which we are using as defaults, or which we will not be changing on-draw
// Explicit depth settings
_gl.glEnable(GL10.GL_DEPTH_TEST);
_gl.glClearDepthf(1.0f);
_gl.glDepthFunc(GL10.GL_LESS);
_gl.glDepthRangef(0,1f);
_gl.glDepthMask(true);
// Alpha enabled
_gl.glEnable(GL10.GL_BLEND);
_gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
// "Transparency is best implemented using glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
// with primitives sorted from farthest to nearest."
// Texture
_gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST); // (OpenGL default is GL_NEAREST_MIPMAP)
_gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); // (is OpenGL default)
// CCW frontfaces only, by default
_gl.glFrontFace(GL10.GL_CCW);
_gl.glCullFace(GL10.GL_BACK);
_gl.glEnable(GL10.GL_CULL_FACE);
// Disable lights by default
for (int i = GL10.GL_LIGHT0; i < GL10.GL_LIGHT0 + NUM_GLLIGHTS; i++) {
_gl.glDisable(i);
}
//
// Scene object init only happens here, when we get GL for the first time
//
}
// Called every frame
//
protected void drawScene()
{
if(_scene.fogEnabled() == true) {
_gl.glFogf(GL10.GL_FOG_MODE, _scene.fogType().glValue());
_gl.glFogf(GL10.GL_FOG_START, _scene.fogNear());
_gl.glFogf(GL10.GL_FOG_END, _scene.fogFar());
_gl.glFogfv(GL10.GL_FOG_COLOR, _scene.fogColor().toFloatBuffer() );
_gl.glEnable(GL10.GL_FOG);
} else {
_gl.glDisable(GL10.GL_FOG);
}
// Sync all of the object drawing so that updates in the mover
// thread can be synced if necessary
synchronized(Renderer.SYNC)
{
for (int i = 0; i < _scene.children().size(); i++)
{
Object3d o = _scene.children().get(i);
if(o.animationEnabled())
{
((AnimationObject3d)o).update();
}
drawObject(o);
}
}
//
//
//
// Draw the blur
// Set Up An Ortho View
_switchToOrtho();
_drawMotionBlur();
// Switch back to the previous view
_switchToPerspective();
_saveScreenToTexture("blur", 512);
}
private void _switchToOrtho()
{
// Set Up An Ortho View
_gl.glDisable(GL10.GL_DEPTH_TEST);
_gl.glMatrixMode(GL10.GL_PROJECTION); // Select Projection
_gl.glPushMatrix(); // Push The Matrix
_gl.glLoadIdentity(); // Reset The Matrix
_gl.glOrthof(0f, 480f, 0f, 800f, -1f, 1f);
//_gl.glOrthof(0f, 480f, 0f, 800f, -100f, 100f);
_gl.glMatrixMode(GL10.GL_MODELVIEW); // Select Modelview Matrix
_gl.glPushMatrix(); // Push The Matrix
_gl.glLoadIdentity(); // Reset The Matrix
}
private void _switchToPerspective()
{
// Switch back to the previous view
_gl.glEnable(GL10.GL_DEPTH_TEST);
_gl.glMatrixMode(GL10.GL_PROJECTION);
_gl.glPopMatrix();
_gl.glMatrixMode(GL10.GL_MODELVIEW);
_gl.glPopMatrix(); // Pop The Matrix
}
private void _saveScreenToTexture(String $textureId, int $size)
{
// Save the screen as a texture
_gl.glViewport(0, 0, $size, $size);
_gl.glBindTexture(GL10.GL_TEXTURE_2D, _textureManager.getGlTextureId($textureId));
_gl.glCopyTexImage2D(GL10.GL_TEXTURE_2D,0,GL10.GL_RGB,0,0,512,512,0);
_gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
_gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
_gl.glViewport(0, 0, 480, 800);
}
private void _drawMotionBlur()
{
// Vertices
float squareVertices[] = {
-3f, 0f, // Bottom Left
475f, 0f, // Bottom Right
475f, 800f, // Top Right
-3f, 800f // Top Left
};
ByteBuffer vbb = ByteBuffer.allocateDirect(squareVertices.length * 4);
vbb.order(ByteOrder.nativeOrder());
FloatBuffer vertexBuffer = vbb.asFloatBuffer();
vertexBuffer.put(squareVertices);
vertexBuffer.position(0);
//
//
// Textures
FloatBuffer textureBuffer; // buffer holding the texture coordinates
float texture[] = {
// Mapping coordinates for the vertices
0.0f, 1.0f, // top left (V2)
0.0f, 0.0f, // bottom left (V1)
1.0f, 1.0f, // top right (V4)
1.0f, 0.0f // bottom right (V3)
};
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(squareVertices.length * 4);
byteBuffer.order(ByteOrder.nativeOrder());
byteBuffer = ByteBuffer.allocateDirect(texture.length * 4);
byteBuffer.order(ByteOrder.nativeOrder());
textureBuffer = byteBuffer.asFloatBuffer();
textureBuffer.put(texture);
textureBuffer.position(0);
//
//
//
_gl.glLineWidth(3.0f);
_gl.glTranslatef(5.0f, 0.0f, 0.0f);
_gl.glVertexPointer(2, GL10.GL_FLOAT, 0, vertexBuffer);
_gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
_gl.glDrawArrays(GL10.GL_LINE_LOOP, 0, 4);
//_gl.glTranslatef(100.0f, 0.0f, 0.0f);
//_gl.glDrawArrays(GL10.GL_LINE_LOOP, 0, 4);
//_gl.glTranslatef(100.0f, 0.0f, 0.0f);
//_gl.glDrawArrays(GL10.GL_LINE_LOOP, 0, 4);
_gl.glEnable(GL10.GL_TEXTURE_2D);
_gl.glEnable(GL10.GL_BLEND);
_gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
_gl.glLoadIdentity();
//
//
//
_gl.glBindTexture(GL10.GL_TEXTURE_2D, _textureManager.getGlTextureId("blur"));
_gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, textureBuffer);
_gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4);
//
//
//
_gl.glDisable(GL10.GL_BLEND);
_gl.glDisable(GL10.GL_TEXTURE_2D);
_gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
}
EDIT: Here's a simpler example, it's all in the one function and doesn't include any of the saving the screen to a texture stuff. This is just drawing a 3d scene, switching to Ortho, drawing a square with a texture, then switching back to perspective.
// Called every frame
//
protected void drawScene()
{
// Draw the 3d models in perspective mode
// This part works (uses min3d) and draws a 3d scene
//
for (int i = 0; i < _scene.children().size(); i++)
{
Object3d o = _scene.children().get(i);
if(o.animationEnabled())
{
((AnimationObject3d)o).update();
}
drawObject(o);
}
// Set Up The Ortho View to draw a square with a texture
// over the 3d scene
//
_gl.glDisable(GL10.GL_DEPTH_TEST);
_gl.glMatrixMode(GL10.GL_PROJECTION); // Select Projection
_gl.glPushMatrix(); // Push The Matrix
_gl.glLoadIdentity(); // Reset The Matrix
_gl.glOrthof(0f, 480f, 0f, 800f, -1f, 1f);
_gl.glMatrixMode(GL10.GL_MODELVIEW); // Select Modelview Matrix
_gl.glPushMatrix(); // Push The Matrix
_gl.glLoadIdentity(); // Reset The Matrix
// Draw A Square With A Texture
// (Assume that the texture "blur" is already created properly --
// it is as I can use it when drawing my 3d scene if I apply it
// to one of the min3d objects)
//
float squareVertices[] = {
-3f, 0f, // Bottom Left
475f, 0f, // Bottom Right
475f, 800f, // Top Right
-3f, 800f // Top Left
};
ByteBuffer vbb = ByteBuffer.allocateDirect(squareVertices.length * 4);
vbb.order(ByteOrder.nativeOrder());
FloatBuffer vertexBuffer = vbb.asFloatBuffer();
vertexBuffer.put(squareVertices);
vertexBuffer.position(0);
FloatBuffer textureBuffer; // buffer holding the texture coordinates
float texture[] = {
// Mapping coordinates for the vertices
0.0f, 1.0f, // top left (V2)
0.0f, 0.0f, // bottom left (V1)
1.0f, 1.0f, // top right (V4)
1.0f, 0.0f // bottom right (V3)
};
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(squareVertices.length * 4);
byteBuffer.order(ByteOrder.nativeOrder());
byteBuffer = ByteBuffer.allocateDirect(texture.length * 4);
byteBuffer.order(ByteOrder.nativeOrder());
textureBuffer = byteBuffer.asFloatBuffer();
textureBuffer.put(texture);
textureBuffer.position(0);
_gl.glLineWidth(3.0f);
_gl.glTranslatef(5.0f, 0.0f, 0.0f);
_gl.glVertexPointer(2, GL10.GL_FLOAT, 0, vertexBuffer);
_gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
_gl.glDrawArrays(GL10.GL_LINE_LOOP, 0, 4);
_gl.glEnable(GL10.GL_TEXTURE_2D);
_gl.glEnable(GL10.GL_BLEND);
_gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
_gl.glLoadIdentity();
_gl.glBindTexture(GL10.GL_TEXTURE_2D, _textureManager.getGlTextureId("blur"));
_gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, textureBuffer);
_gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4);
_gl.glDisable(GL10.GL_BLEND);
_gl.glDisable(GL10.GL_TEXTURE_2D);
_gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
// Switch Back To The Perspective Mode
//
_gl.glEnable(GL10.GL_DEPTH_TEST);
_gl.glMatrixMode(GL10.GL_PROJECTION);
_gl.glPopMatrix();
_gl.glMatrixMode(GL10.GL_MODELVIEW);
_gl.glPopMatrix(); // Pop The Matrix
}
EDIT2: Thanks to Christian's answer, I removed the second glVertexPointer and _gl.glBlendFunc (GL10.GL_ONE, GL10.GL_ONE); (I deleted them from the sample code above as well so it wouldn't confuse the question). I now have a texture rendering, but only in one of the triangles that make up the square. So I'm seeing a triangle in the left portion of the screen that has the texture applied. Why is it not being applied to both halves of the square? I think it's because I have only one of these calls: gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4); so I'm literally only drawing one triangle.
First, you set the blend function to (GL_ONE, GL_ONE), which will just add the blur texture to the framebuffer and make the whole scene overbright. You probalby want to use (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA), but then you have to make sure your blur texture has the correct alpha, by configuring the texture environment to use a constant value for the alpha (instead of the texture's) or use GL_MODULATE with a (1,1,1,0.5) coloured square. Alternatively use a fragment shader.
Second, you specify a size 3 in the second call to glVertexPointer, but your data are 2d vectors (the first call is right).
glOrtho is not neccessarily 2D, its just a camera without perspective distortion (farther objects don't get smaller). The parameters to glOrtho specify your screen plane size in view coordinates. Thus if your scene covers the world in the unit cube, an ortho of 480x800 is just too large (this is no problem if you draw other objects than in perspective, as your square or UI elements, but when you want to draw your same 3d objects the scales have to match). Another thing is that in ortho the near and far distances still matter, everything that falls out is clipped away. So if your camera is at (0,0,0) and you view along -z with a glOrtho of (0,480,0,800,-1,1), you will only see those objects that intersect the (0,0,-1)-(480,800,1)-box.
So keep in mind, that glOrtho and glFrustum (or gluPerspective) all define a 3d viewing volume. In ortho its a box and in frustum its, guess a frustum (capped pyramid). consult some more introductory texts on transformations and viewing if this was not clear enough.