I have a simple OpenGL app that displays arbitrary 3D models. I'd like to implement zoom. what I have now uses glScale, and works to some degree. However, I'm having two issues.
Any sort of zoom (+) quickly gets to the point where the edges of the object are inside the near clipping plane. Right now, my zNear is something like 0.1, so it makes sense that increasing the scale of the object will cause clipping. I am wondering if there are any other approaches for achieving a better effect.
As I zoom in, the object gets dimmer. Zoom out and it gets brighter. I have a light position at around 0, 0, 100. I have very simple lighting positioned at 0,0,100 and using only diffuse.
gl.glEnable(GL10.GL_LIGHTING);
gl.glEnable(GL10.GL_LIGHT0);
gl.glEnable(GL10.GL_COLOR_MATERIAL);
float[] lights;
lights = new float[] { 0f, 0f, 0f, 1f };
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, lights, 0);
lights = new float[] { 1f, 1f, 1f, 1f };
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, lights, 0);
lights = new float[] { 0f, 0f, 0f, 1f };
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_SPECULAR, lights, 0);
float matAmbient[] = { 0f, 0f, 0f, 1f };
float matDiffuse[] = { 1f, 1f, 1f, 1f };
float matSpecular[] = { 0f, 0f, 0f, 1f };
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, matAmbient, 0);
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, matDiffuse, 0);
gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_SPECULAR, matSpecular, 0);
float lightPosition[] = { mesh.mid.vertex[X], mesh.mid.vertex[Y], 100f,
1f };
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, lightPosition, 0);
I do not have attenuation settings, which I believe needs to be enabled to cause the light to be affected by distance. Regardless, I'm not changing the distance of the object, just scaling it. Sure the position of the faces are changing but not significantly. Anyway, I'd thinking zooming in would cause it to get brighter, not dimmer.
This happens to be using opengl-es 1.0 on the Android platform.
Scaling will change the way your object is lit as the normals are also scaled (which as you pointed out in your own answer can be forced with a call to glEnable(GL_NORMALIZE)). Note that depending on when they are specified, the lights themselves may not have the equivalent transformation:
http://www.opengl.org/resources/faq/technical/lights.htm
A light's position is transformed by the current ModelView matrix at the time the position is specified with a call to glLight*().
Depending on the kind of zoom effect you want, you could achieve your zoom in different ways.
If you literally want to 'zoom' in the way that a zoom lens on a camera does, then you could change the field of vision parameter passed in to gluPerspective. This will mean that you have the effect of flattened or exaggerated perspective, as you do with a real camera.
What is more commonly desired by typical applications, is to change the position of the camera in relation to the object. The simplest way to do this is with gluLookAt.
Beware of the difference between projection and modelview matrices; changing perspective should be done to projection, while positioning the camera should effect the modelview. See http://www.sjbaker.org/steve/omniv/projection_abuse.html
nb... I've just realised that the OpenGL-es you're using might not support those exact functions; you should be able to find how to achieve the same results quite easily.
The partial answer to #2 is that I was not scaling my normals. In other words, the previously normalized normal values have a greater (relative) magnitude when the object is scaled smaller, causing the faces to reflect more light ... and vice versa.
You can set a parameter,
glEnable(GL_NORMALIZE)
and it solves the problem, at the expense of some extra calculations. the full story is here,
http://www.opengl.org/resources/features/KilgardTechniques/oglpitfall/
(see #16).
Related
I'm rendering a simple rectangle mesh using libgdx, and other geometric elements that are similar in simplicity. Therse are going to interact with the sprites I have setup in my game. The sprites' position and other properties are setup in world units and before each sprite draw session I setup the camera like this:
camera.update();
batch.setProjectionMatrix(camera.combined);
It all works well but I need to draw meshes using world units. How can I feed the shader program world coordinates(12.5f, 30f, etc, based on my game world data) instead of (0f, 1f) ranges? I want to draw several textured meshes so I need coordinates that are in relation with the other elements in the game.
Here is how I draw a simple rectangle mesh :
mesh = new Mesh(true, 4, 6,
new VertexAttribute(Usage.Position, 3, "a_position"),
new VertexAttribute(Usage.TextureCoordinates, 2, "a_texCoords"));
mesh.setVertices(new float[] {
-1.0f, -1.0f, 0, 0,1,
0.0f, -1.0f, 0,1,1,
0.0f, 0.0f, 0, 1,0,
-1.0f, 0.0f, 0, 0,0 });
mesh.setIndices(new short[] { 0, 1, 2, 2, 3, 0});
Gdx.graphics.getGL20().glEnable(GL20.GL_TEXTURE_2D);
Gdx.gl20.glActiveTexture(GL20.GL_TEXTURE);
createShader();
shader.begin();
mesh.render(shader, GL20.GL_TRIANGLES);
shader.end();
Is there any way I can feed world units to the mesh vertices array ?
You can transform the vertices in the vertex shader. This allows you to project world coordinates onto the -1 to 1 range required for rendering. This is typically done by multiplying the position vertex attribute with a (projection) matrix. Have a look at the default spritebatch shader, for an example of how to implement this.
You can use the camera.combined matrix to multiply these vertices in vertex shader. Just like you did when specifying the projection matrix for the spritebatch. You'll have to assign this matrix to the uniform you've used in your vertex shader. An example of how to do this can also be found in default spritebatch implementation.
However, you might want to reconsider your approach. Since you're using a spritebatch, you can profit from a performance gain by using the spritebatch instead of manually rendering. This will also simplify the rendering for you, because you dont have to mess with the shader and matrices yourself. Spritebatch contains a method (javadoc) which allows you to specify a manually created mesh (or vertices actually). Each vertex is expected to be 5 floats (x, y, u, v, color) in size and a multiple of four vertices (doesn't have to be a rectangle shape though) must provided (you can use Color.WHITE.toFloatBits() for the color).
But, since you're trying to render a simple rectangle, you might as well use one of the more convenient methods that allows you to render a rectangle without having to create a mesh all together (javadocs). Or, even easier, use it how it is designed by creating a Sprite for your rectangle (wiki page).
Now, if you're still certain that you do want to create a mesh and shader manually, then I'd suggest learning that using e.g. a tutorial, instead of just diving into it. E.g. these tutorials might help you get started. The wiki also contains an article describing how to do this, including transforming vertices in the vertex shader.
I've been trying to figure this out for a while now. Is there a way to have multiple shapes you have rendered to become one single object? So you could manipulate the object in any way you want? Currently if I try to use the glcolor method it doesn't change anything because I have multiple colors changing the objects already. Maybe I'm not realizing something obvious, or it's just something that I can't find in any tutorial. But simply put I'm looking for a way to change just the alpha value of multiple shapes draw together to make a more complex shape, but keeping the colors that have already been predefined.
If you want multiple shapes to become one single object you simply calculate or write the correct X, Y, Z numbers into each vertex, so they fit together.
For instance you can make 2 triangles become a square, like this.
To do the same but in code it would look something like this.
glBegin(GL_TRIANGLES);
glVertex3f(0f, 0f, 0f);
glVertex3f(0f, 100f, 0f);
glVertex3f(100f, 0f, 0f);
glVertex3f(100f, 100f, 0f);
glVertex3f(100f, 0f, 0f);
glVertex3f(0f, 100f, 0f);
glEnd();
You can of course just use GL_QUADS, though triangles are usually better in a lot of ways. If you want to color the shape 1 color, you need and can only write one color before.
So for instance this will work and gives a whole red square/quad.
glColor3f(1f, 0f, 0f);
glBegin(GL_TRIANGLES);
glVertex3f(0f, 0f, 0f);
glVertex3f(0f, 100f, 0f);
glVertex3f(100f, 0f, 0f);
glVertex3f(100f, 100f, 0f);
glVertex3f(100f, 0f, 0f);
glVertex3f(0f, 100f, 0f);
glEnd();
Though if you add more glColor in between that will end up giving another result.
If you want to use transparency/opacity/alpha you will need to use glColor4f where the last number is the amount of transparency. But before you are able to use transparency you need to enable blending mode within OpenGL. You do that by calling.
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
I wasn't a 100% sure about your questions so if I've forgotten to answer something, just write a comment and tell me.
Info
If you don't statically import your classes then remember that glEnable(GL_BLEND); is actually GL11.glEnable(GL11.GL_BLEND); and the same goes for the rest of the code.
Also take in mind that the glVertex, glNormal, glColor, etc. are deprecated methods and should not be used for that reason, also they are extremely slow when rendering a huge amount of vertices, Though they are really good for the purpose of learning OpenGL, and should only be used for learning. The new and better alternative is to use VBOs and Shaders.
Which parameters for especially s and t would I use to text map a sphere? I've tried various different options, but there's always a little portion that's distorted no matter what values I choose for float[] s and t. I can do planes and cylinders, but I'm not sure about spheres. Any help would be appreciated.
gl.glTexGeni(GL2.GL_T, GL2.GL_TEXTURE_GEN_MODE, GL2.GL_OBJECT_LINEAR);
float[] s = {1f, 0f, 0f, 0};
gl.glTexGenfv(GL2.GL_S, GL2.GL_OBJECT_PLANE, s, 0);
float[] t = {0f, 1f, 0f, 0};
gl.glTexGenfv(GL2.GL_T, GL2.GL_OBJECT_PLANE, s, 0);
You can not texture map a sphere using a single rectangular texture without creating some serious distortion at some place. It's mathematically impossible. That being said, you should not use the glTexGen functionality for this either, because a) it's been deprecated, and b) only creates linear planar mappings, whereas for texturing a sphere you need curvilinear coordinates. Use a vertex shader to generate the texture coordinates from vertex position.
How can I correctly figure out what values I must use for gl.glTranslatef(x,y,z), and similar methods. Example: I've got an square, and want to display it in the upper left corner, at about 1/4th of the screen. I figured it would be glTranslate() with values -0.5 and 0.5, but this doens't display where I expected it.
So basically I wan't to know how to find the right coordinates for objects in OpenGL-ES.
Unfortunately haven't developed opengl-es content for android yet, but AFAIK you need to convert screen coordinates (e.g. upper left corner on your screen) to world coordinates(coordinates in your 3D world in OpenGL).
For 3D you could do this would be through ray projection. You will find plenty of examples through google search and maybe a OpenGL implementation too.
For 2D you can get away bit using an orthogonal projection matrix(with no perspective distortion basically) and rotating it as needed (e.g. for lanscape mode):
// Initialize your projection matrix - current number are half the dimensions for the G1 I borrowed(320x480)
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrthof(-240.0f, 240.0f, -160.0f, 160.0f, -1.0f, 1.0f);
// Rotate everything by 90 degrees
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glRotatef(-90.0f, 0.0f, 1.0f, 0.0f);
HTH
I've written several Android apps, but this is my first experience with 3D programming.
I've created a room (4 walls, ceiling and floor) with a couple objects inside and am able to move the camera around it as if walking. I've textured all surfaces with various images and everything was working as expected.
For context, the room is 14 units wide and 16 units deep (centered at origin), 3 units high (1 above origin and 2 below). There are 2 objects in the middle of the room, a cube and an inverted pyramid on top of it.
Then I went to add a light source to shade the cube and pyramid. I had read through and followed a couple of NeHe's ports, so I took what I had working in the lesson on lighting and applied it to my new code.
gl.glEnable(GL10.GL_LIGHTING);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, new float[] { 0.1f, 0.1f, 0.1f, 1f }, 0);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, new float[] { 1f, 1f, 1f, 1f }, 0);
gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, new float[] { -4f, 0.9f, 6f, 1f }, 0);
gl.glEnable(GL10.GL_LIGHT0);
The result is that the cube and pyramid are not shaded. They look the same on sides opposing the light as they do on the sides facing it. When the camera is pointed directly away from the light source the room looks as it did before I added the lighting code. As I rotate the camera to face the light source the entire room (including objects) becomes darker until completely black when the camera is directly facing the source.
What is going on here? I read many articles on lighting and how it works, but I have seen nothing to indicate why this wouldn't light up all sides of the room, with the cube and pyramid shaded based on the light position. Is there some expected behavior of the light because it is "inside" the room? Am I just missing something easy because I'm new?
Every object in your 3D world has a normal, where it helps OpenGL to determine how much light an object need to reflect. You've probably forgot to specify the normals for your surfaces. Without specifying them, OpenGL will light all objects in your world in the same way.
In order to get a surface's normal in 3D you need at least three vertices, which means it at least is a triangle.
Sample stuff:
In order to calculate a surface's normal you need two vectors. Since you have three vertices in 3D space that means that these sample points could contain a triangle:
// Top triangle, three points in 3D space.
vertices = new float[] {
-1.0f, 1.0f, -1.0f,
1.0f, 1.0f, -1.0f,
0.0f, 1.0f, -1.0f,
}
Given these three points, you can now define two vectors by the following:
// Simple vector class, created by you.
Vector3f vector1 = new Vector3f();
Vector3f vector2 = new Vector3f();
vector1.x = vertices[0] - vertices[3];
vector1.y = vertices[1] - vertices[4];
vector1.z = vertices[2] - vertices[5];
vector2.x = vertices[3] - vertices[6];
vector2.y = vertices[4] - vertices[7];
vector2.z = vertices[5] - vertices[8];
Now when you have two vectors, you can finally get the surface's normal by using the Cross Product. Shortly, the cross product is an operation which results in a new vector containing an angle that is perpendicular to the input vectors. This is the normal that we need.
To get the cross product in your code you have to write your own method that calculates it. In theory you calculate the cross product given this formula:
A X B = (A.y * B.z - A.z * B.y, A.z * B.x - A.x * B.z, A.x * B.y - A.y * B.x)
In code (by using the vectors above):
public Vector3f crossProduct(Vector3f vector1, Vector3f vector2) {
Vector3f normalVector = new Vector3f();
// Cross product. The normalVector contains the normal for the
// surface, which is perpendicular both to vector1 and vector2.
normalVector.x = vector1.y * vector2.z - vector1.z * vector2.y;
normalVector.y = vector1.z * vector2.x - vector1.x * vector2.z;
normalVector.z = vector1.x * vector2.y - vector1.y * vector2.x;
return normalVector;
}
Before any further comments; you can specify your normals in an array and just put them into OpenGL when needed, but your understanding of this topic will be much better if you dig into it and your code will be much more flexible.
So now we have a normal which you can loop through, assign the vector values to your normal array (like NeHe's ports, but dynamically) and set up OpenGL to use GL_NORMAL_ARRAY in order to get OpenGL to reflect the light on the object correctly:
gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);
// I'm assuming you know how to put it into a FloatBuffer.
gl.glNormalPointer(GL10.GL_FLOAT, 0, mNormalsBuffer);
// Draw your surface...
Another last comment; if you're using other vertices values (like 5.0f, 10.0f or bigger) you might wanna normalize the vector that returns from the crossProduct() method in order to gain some performance. Otherwise OpenGL must calculate the new vector to get the unit vector and that might be a performance issue.
Also, your new float[] {-4f, 0.9f, 6f, 1f} for GL_POSITION is not quite correct. When the fourth value is set to 1.0f it means that the light position is 0, 0, 0, no matter what the first three values are. In order to specify a vector for your light position, change the fourth value to 0.0f.
You need to reload the light position each frame, otherwise the light source will move with the camera which is probably not what you want. Also the shading you are describing is totally consistent with vertex interpolated lighting. If you want something better you will have to do it per-pixel (which means implementing your own shader), or else subdivide your geometry.