|
Updated to be Android 2.0.1 compatible.
You are new to this series? Please start with the
first part.
The sixth part of this series will show you how you create the correct perspective
because 3D is nothing without the correct perspective.
Before we start we should discuss the two possible “views” OpenGL offers: orthographic
and perspective.
Orthographic (non vanish point projection)
The orthographic view is a view that makes it impossible to see if a object
is right in front of us or far away. Why? Because it doesn’t shrink in the
distance. So if you will draw an object with the a specific size near the
viewport and another object with the same size far behind the first one
but a bit to one side (to be sure that the first object doesn’t stand in
the view), you won’t be able to say which element is the first one. Because
both will have the same size independent of their distance. They simply
don’t shrink with the distance. Perspective (vanish point projection)
The perspective view is the view we know from our eyes. An example: A tall
guy in front of you is, of course, tall. If this guy is 100 meter away,
he is not as tall as your thumb. He seems to shrink in the distance but
we know of course, he stays tall. That effect is called perspective. For
our example with the two objects, the second one will be much smaller so
we can say in an instance, that this is far away and the other one is right
in front of us.
Because my example might confuse you, I recommend again a blog post of
iPhone development: OpenGL ES From the Ground Up, Part 3: Viewports in Perspective
which uses railroad tracks as an example.
The first view we want to create is the one using the orthographic. The
view setup is something we usually do just once or, in case we change the
rotation, we do it every time the surface is created. Thats the reason why
we should change a bit. So some code we have in our onDrawFrame() method
will be moved to the onSurfaceCreated() method. There it should only be
executed if we start the application or change the rotation.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// preparation
// define the color we want to be displayed as the "clipping wall"
gl.glClearColor(0f, 0f, 0f, 1.0f);
// enable the differentiation of which side may be visible
gl.glEnable(GL10.GL_CULL_FACE);
// which is the front? the one which is drawn counter clockwise
gl.glFrontFace(GL10.GL_CCW);
// which one should NOT be drawn
gl.glCullFace(GL10.GL_BACK);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
initTriangle();
}
// code snipped
@Override
public void onDrawFrame(GL10 gl) {
gl.glLoadIdentity();
// clear the color buffer and the depth buffer to show the ClearColor
// we called above...
gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
// set rotation
gl.glRotatef(_xAngle, 1f, 0f, 0f);
gl.glRotatef(_yAngle, 0f, 1f, 0f);
//gl.glColor4f(0.5f, 0f, 0f, 0.5f);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, _vertexBuffer);
gl.glColorPointer(4, GL10.GL_FLOAT, 0, _colorBuffer);
gl.glDrawElements(GL10.GL_TRIANGLES, _nrOfVertices, GL10.GL_UNSIGNED_SHORT, _indexBuffer);
}
|
You see that we didn’t move the glClear() and the glLoadIdentity() method
from onDrawFrame() to onSurfaceCreated(). The reason is easy: they should
be called on every frame.
Because we need the screen size to calculate the ratio of our screen
we introduce two object variables called _width and _height. An we need
to set them in the method onSurfaceChanged() which will be called on every
rotation change.
1
2
3
4
5
6
7
8
9
|
private float _width = 320f;
private float _height = 480f;
@Override
public void onSurfaceChanged(GL10 gl, int w, int h) {
_width = w;
_height = h;
gl.glViewport(0, 0, w, h);
}
|
Now we have everything we need to start with the viewport. We need to
change the onSurfaceCreated() method.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
gl.glMatrixMode(GL10.GL_PROJECTION);
float ratio = _width / _height;
// orthographic:
gl.glOrthof(-1, 1, -1 / ratio, 1 / ratio, 0.01f, 100.0f);
gl.glViewport(0, 0, (int) _width, (int) _height);
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glEnable(GL10.GL_DEPTH_TEST);
// define the color we want to be displayed as the "clipping wall"
gl.glClearColor(0f, 0f, 0f, 1.0f);
// enable the differentiation of which side may be visible
gl.glEnable(GL10.GL_CULL_FACE);
// which is the front? the one which is drawn counter clockwise
gl.glFrontFace(GL10.GL_CCW);
// which one should NOT be drawn
gl.glCullFace(GL10.GL_BACK);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
initTriangle();
}
|
Wow this is a lot of new code! Don’t worry we will go step by step.
On line 3 we see the glMatrixMode() with GL10.GL_PROJECTION as parameter
and on line 8 we see the method again but with the GL10.GL_MODELVIEW as
parameter. The reasons are in the lines between line 3 and line 8. In the
lines 4 till 7 we set up our viewport, so we set up our projection. On line
9 till 17 we set up our model environment. In this context both calls with
different parameter should be understandable. Tip: As always simply try
to get rid of some code lines and see the result. Thats the best way to
understand what line of code is responsible for what.
On line 4 we calculate our screen ratio we need for the next line. On this
line (line 6) we set our viewport to make a orthographic view. The parameter
are for the border in the order of left, right, bottom, top, zNear, zFar.
On line 7 we set the viewport. We know this method because we already used
it in onSurfaceChanged().
On line 8 we switch the MatrixMode to GL10.GL_MODELVIEW which set OpenGL
to accept some calls which change the way models should be drawn.
On line 9 we call glEnable() with the parameter GL10.GL_DEPTH_TEST. That
enabled that OpenGL ES check which z-order the objects have. If we don’t
enable that, we will see the last drawn object always in front of all other
objects. That means even if this object should be hidden by another much
nearer and bigger object, we will see the first one in front of it.
The other lines of code we already know from the previous parts of this
series.
The perspective view is nearly the same but instead of calling glOrthof()
we will call glFrustumf().
The parameters for glFrustumf() will be a bit different from the parameters
we used for glOrthof(). The reason therefor is, that we didn’t shrink the
objects but we the defined frustum will be funnel-formed. Look at these
pictures to see the difference between glOrthof() and glFrustumf():
Orthographic:

Perspective:

Back to the code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
Log.i(LOG_TAG, "onSurfaceCreated()");
gl.glMatrixMode(GL10.GL_PROJECTION);
float size = .01f * (float) Math.tan(Math.toRadians(45.0) / 2);
float ratio = _width / _height;
// perspective:
gl.glFrustumf(-size, size, -size / ratio, size / ratio, 0.01f, 100.0f);
// orthographic:
//gl.glOrthof(-1, 1, -1 / ratio, 1 / ratio, 0.01f, 100.0f);
gl.glViewport(0, 0, (int) _width, (int) _height);
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glEnable(GL10.GL_DEPTH_TEST);
// define the color we want to be displayed as the "clipping wall"
gl.glClearColor(0f, 0f, 0f, 1.0f);
// enable the differentiation of which side may be visible
gl.glEnable(GL10.GL_CULL_FACE);
// which is the front? the one which is drawn counter clockwise
gl.glFrontFace(GL10.GL_CCW);
// which one should NOT be drawn
gl.glCullFace(GL10.GL_BACK);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
initTriangle();
}
|
Information: The calculation of our variable size (on line 5)
is something
we should take on faith, we will see why it works when we talk about matrices.
On line 8 we now have glFrustumf() instead of glOrthof(). Thats all we
have to change between orthographic view and perspective view.
But hey, with just one object we won’t see any changes. Ok lets change
the method onDrawFrame().
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
@Override
public void onDrawFrame(GL10 gl) {
// clear the color buffer and the depth buffer
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, _vertexBuffer);
gl.glColorPointer(4, GL10.GL_FLOAT, 0, _colorBuffer);
for (int i = 1; i <= 10; i++) {
gl.glLoadIdentity();
gl.glTranslatef(0.0f, -1f, -1.0f + -1.5f * i);
// set rotation
gl.glRotatef(_xAngle, 1f, 0f, 0f);
gl.glRotatef(_yAngle, 0f, 1f, 0f);
gl.glDrawElements(GL10.GL_TRIANGLES, _nrOfVertices, GL10.GL_UNSIGNED_SHORT, _indexBuffer);
}
}
|
Ok, what have changed?
On line 3 we have now modified parameter to be sure that the depth buffer
will be cleared to.
On line 9 we started a loop to create 10 objects.
On line 10 we see the missing glLoadIdentity(). It is now located here to
reset the matrix. Thats have to be done because we will use glRotatef()
and glTranslatef() to modify our objects. But to be sure that we only modify
the object we are currently looping over, we call glLoadIdentity(). So we
“reset” every glTranslatef() and glRotatef() call we made for the previous
object.
On line 11 we see the new glTranslatef() method which will move our object
to another location. In our case we don’t change the location on the x axis,
but we change -1.0f on the y axis which means it will be near the bottom
of our screen. The last calculation you see is simply to modify the position
on the z axis, which means the depth, of your object. The first object will
be on -2.5f, the second on -4.0f and so on. So we should see 10 objects
lined up to the middle of our screen.
If you compile that using the glFrustumf() call, you should see that:

If you switch the lines glFrustumf() and glOrthof() you should see that:

Hey wait, why do we not see more than one object? Because in orthographic
we don’t have any kind of perspective. So every single object has the same
size and doesn’t shrink so it won’t follow any vanish point and without
that, each object is exactly behind the first one.
Full source:
Vortex Part VI
Source:
droidnova
|
How to use custom de...
lets say i change this app to days to...
How to display a JPG...
Is that posible to zoom in and drag p...
Making a custom Andr...
Cool - I will use it carefully i promise
How to display a JPG...
problem in image loading - i have exe...
Introducing Calculon...
Thank You for your contribution - Hi,...