OPENGL|ES第五天 Entering the Third Dimensioin

Here’s our game plan for the chapter:

• First we’ll learn about OpenGL’s perspective division and how to use the w component to create the illusion of 3D on a 2D screen.

• Once we understand the w component, we’ll learn how to set up a perspective projection so that we can see the table in 3D.


6.1 The Art of 3D

People have been fooling people into perceiving a flat two-dimensional painting as a complete third-dimensional scene, one of the tricks they use is called linear 

projection, and it works by joining together parallel lines at an imaginary vanishing point to create the illusion of perspective.


6.2 Transforming a Coordinate from the Shader to the Screen

We are now familiar with normalized device coordinates, and let’s take a look at the following flow chart to review how a coordinate gets transformed from the

original gl_Position written by the vertex shader to the final coordinate onscreen:


There are two transformation steps and three different coordinate spaces.

1.Clip Space

When the vertex shader writes a value out to gl_Position, OpenGL expects this to be in clip space. The logic behind clip space is very simple: for any position, the x, y, and

z components all need to be between -w and w for that position. For example, if a position’s w is 1, then the x, y, and z components all need to be between -1 and 1.

Anything outside this range will not be visible on the screen. The reason why it depends on the position’s w will be apparent once we learn about perspective division.

2.Perspective Division

Before a vertex position becomes a normalized device coordinate, OpenGL actually performs an extra step known as perspective division.every visible coordinate will lie in

the range of [-1, 1] for the x, y, and z components, regardless of the size or shape of the rendering area.

To create the illusion of 3D on the screen, OpenGL will take each gl_Position and divide the x, y, and z components by the w component. When the w component is used

to represent distance, this causes objects that are further away to be moved closer to the center of the rendering area, which then acts like a vanishing point. This is how

OpenGL fools us into seeing a scene in 3D, using the same trick that artists have been using for centuries.

In the following image, we can see an example of this effect in action, as a coordinate with the same x, y, and z will be brought ever closer to the center as the w value

increases:

In OpenGL, the 3D effect is linear and done along straight lines. In real life, things are more complicated (imagine a fish-eye lens), but this sort of linear projection is a

reasonable approximation.

3. Homogeneous Coordinates

Because of the perspective division, coordinates in clip space are often referred to as homogeneous coordinates,1 introduced by August Ferdinand Möbius in 1827. The

reason they are called homogeneous is because several coordinates in clip space can map to the same point. For example, take the following points:

(1, 1, 1, 1), (2, 2, 2, 2), (3, 3, 3, 3), (4, 4, 4, 4), (5, 5, 5, 5)

After perspective division, all of these points will map to (1, 1, 1) in normalized device coordinates.

4.The Advantages of Dividing by W

There are additional advantages to adding w as a fourth component. We can decouple the perspective effect from the actual z coordinate, so we can switch between an

orthographic and a perspective projection. There’s also a benefit to preserving the z component as a depth buffer, which we’ll cover in Removing Hidden Surfaces with the

Depth Buffer, on page 245.

5. Viewport Transformation

Before we can see the final result, OpenGL needs to map the x and y components of the normalized device coordinates to an area on the screen that the operating system

has set aside for display, called the viewport; these mapped coordinates are known as window coordinates. We don’t really need to be too concerned about these

coordinates beyond telling OpenGL how to do the mapping. We’re currently doing this in our code with a call to glViewport() in onSurfaceChanged().When OpenGL does

this mapping, it will map the range (-1, -1, -1) to (1, 1, 1) to the window that has been set aside for display. Normalized device coordinates outside of this range will be

clipped. As we learned in Chapter 5, Adjusting to the Screen's Aspect Ratio, on page 77, this range is always the same, regardless of the width or height of the viewport.


6.3 Adding the W Component to Create Perspective

Since we’ll now be specifying the x, y, z, and w components of a position, let’s begin by updating POSITION_COMPONENT_COUNT as follows:

//AirHockey3D/src/com/airhockey/android/AirHockeyRenderer.java
private static final int POSITION_COMPONENT_COUNT = 4;

The next step is to update all of our vertices:

//AirHockey3D/src/com/airhockey/android/AirHockeyRenderer.java
float[] tableVerticesWithTriangles = {
// Order of coordinates: X, Y, Z, W, R, G, B
// Triangle Fan
0f, 0f, 0f, 1.5f, 1f, 1f, 1f,
-0.5f, -0.8f, 0f, 1f, 0.7f, 0.7f, 0.7f,
0.5f, -0.8f, 0f, 1f, 0.7f, 0.7f, 0.7f,
0.5f, 0.8f, 0f, 2f, 0.7f, 0.7f, 0.7f,
-0.5f, 0.8f, 0f, 2f, 0.7f, 0.7f, 0.7f,
-0.5f, -0.8f, 0f, 1f, 0.7f, 0.7f, 0.7f,
// Line 1
-0.5f, 0f, 0f, 1.5f, 1f, 0f, 0f,
0.5f, 0f, 0f, 1.5f, 1f, 0f, 0f,
// Mallets
0f, -0.4f, 0f, 1.25f, 0f, 0f, 1f,
0f, 0.4f, 0f, 1.75f, 1f, 0f, 0f
};

We added a z and a w component to our vertex data. We’ve updated all of the vertices so that the ones near the bottom of the screen have a w of 1 and the ones near the

top of the screen have a w of 2; we also updated the line and the mallets to have a fractional w that’s in between. This should have the effect of making the top part of the

table appear smaller than the bottom, as if we were looking into the distance. We set all of our z components to zero, since we don’t need to actually have anything in z to

get the perspective effect.

OpenGL will automatically do the perspective divide for us using the w values that we’ve specified, and our current orthographic projection will just copy these w values

over.

what if we wanted to make things more dynamic, like changing the angle of the table or zooming in and out? Instead of hard-coding the w values, we’ll use matrices to

generate the values for us. Go ahead and revert the changes that we’ve made; in the next section, we’ll learn how to use a perspective projection matrix to generate the w

values automatically.

6.4 Moving to a Perspective Projection

Have a look again.

6.5 Defining a Perspective Projection

To recreate the magic of 3D, our perspective projection matrix needs to work together with the perspective divide. The projection matrix can’t do the perspective divide by

itself, and the perspective divide needs something to work with.

An object should move toward the center of the screen and decrease in size as it gets further away from us, so the most important task for our projectionmatrix is to create

the proper values for w so that when OpenGL does the perspective divide, far objects will appear smaller than near objects. One of the ways that we can do that is by

using the z component as the distance from the focal point and then mapping this distance to w. The greater the distance, the greater the w and the smaller the resulting

object.

1.Adjusting for the Aspect Ratio and Field of Vision

Let’s take a look at a more general-purpose projection matrix, which will allow us to adjust for the field of vision as well as for the screen’s aspect ratio:


6.6 Creating a Projection Matrix in Our Code

Android’s Matrix class contains two methods for this, frustumM() and perspectiveM(). Unfortunately, frustumM() has a bug that affects some types of projections,3 and

perspectiveM() was only introduced in Android Ice Cream Sandwich and is not available on earlier versions of Android. We could simply target Ice Cream Sandwich and

above, but then we’d be leaving out a large part of the market that still runs earlier versions of Android.

Instead, we can create our own method to implement the matrix as defined in the previous section. Open up the project we created back at the beginning of this chapter

and add a new class called MatrixHelper to the package com.airhockey.android.util. We’ll implement a method very similar to the perspectiveM() in Android’s Matrix class.

1.Creating Our Own perspectiveM

Add the following method signature to the beginning of MatrixHelper:

//AirHockey3D/src/com/airhockey/android/util/MatrixHelper.java
public static void perspectiveM(float[] m, float yFovInDegrees, float aspect,
float n, float f) {
2.Calculating the Focal Length

The first thing we’ll do is calculate the focal length, which will be based on the field of vision across the y-axis. Add the following code just

after the method signature:

//AirHockey3D/src/com/airhockey/android/util/MatrixHelper.java
final float angleInRadians = (float) (yFovInDegrees * Math.PI / 180.0);
final float a = (float) (1.0 / Math.tan(angleInRadians / 2.0));
We use Java’s Math class to calculate the tangent, and since it wants the angle in radians, we convert the field of vision from degrees to radians. We then calculate the

focal length as described in the previous section.

We use Java’s Math class to calculate the tangent, and since it wants the angle in radians, we convert the field of vision from degrees to radians. We then calculate the

focal length as described in the previous section. 

Writing Out the Matrix

We can now write out the matrix values. Add the following code to complete the method:

//AirHockey3D/src/com/airhockey/android/util/MatrixHelper.java
m[0] = a / aspect;
m[1] = 0f;
m[2] = 0f;
m[3] = 0f;
m[4] = 0f;
m[5] = a;
m[6] = 0f;
m[7] = 0f;
m[8] = 0f;
m[9] = 0f;
m[10] = -((f + n) / (f - n));
m[11] = -1f;
m[12] = 0f;
m[13] = 0f;
m[14] = -((2f * f * n) / (f - n));
m[15] = 0f;}

This writes out the matrix data to the floating-point array defined in the argument m, which needs to have at least sixteen elements. OpenGL stores matrix data in column-

major order, which means that we write out data one column at a time rather than one row at a time. The first four values refer to the first column, the second four values to

the second column, and so on.

We’ve now finished our perspectiveM(), and we’re ready to use it in our code.Our method is very similar to the one found in the Android source code,4 with a few slight

changes to make it more readable.

6.7 Switching to a Projection Matrix

We’ll now switch to using the perspective projection matrix. Open up AirHockeyRenderer and remove all of the code from onSurfaceChanged(), except for the call to

glViewport(). Add the following code:

//AirHockey3D/src/com/airhockey/android/AirHockeyRenderer.java

MatrixHelper.perspectiveM(projectionMatrix, 45, (float) width
/ (float) height, 1f, 10f);
This will create a perspective projection with a field of vision of 45 degrees. The frustum will begin at a z of -1 and will end at a z of -10.

After adding the import for MatrixHelper, go ahead and run the program. You’ll probably notice that our air hockey table has disappeared! Since we didn’t specify a z

position for our table, it’s located at a z of 0 by default. Since our frustum begins at a z of -1, we won’t be able to see the table unless we move it into the distance.

Instead of hard-coding the z values, let’s use a translation matrix to move the table out before we project it using the projection matrix. By convention, we’ll call this matrix

the model matrix.

1.Moving Objects Around with a Model Matrix

Let’s add the following matrix definition to the top of the class:

//AirHockey3D/src/com/airhockey/android/AirHockeyRenderer.java
private final float[] modelMatrix = new float[16];
We’ll use this matrix to move the air hockey table into the distance. At the end of onSurfaceChanged(), add the following code:

//AirHockey3D/src/com/airhockey/android/AirHockeyRenderer.java
setIdentityM(modelMatrix, 0);
translateM(modelMatrix, 0, 0f, 0f, -2f);
This sets the model matrix to the identity matrix and then translates it by -2 along the z-axis. When we multiply our air hockey table coordinates with this matrix, they will

end up getting moved by 2 units along the negative z-axis.

2.Multiplying Once Versus Multiplying Twice

3.Updating the Code to Use One Matrix

Let’s wrap up the new matrix code and add the following to onSurfaceChanged() after the call to translateM():

//AirHockey3D/src/com/airhockey/android/AirHockeyRenderer.java
final float[] temp = new float[16];
multiplyMM(temp, 0, projectionMatrix, 0, modelMatrix, 0);
System.arraycopy(temp, 0, projectionMatrix, 0, temp.length);
Whenever we multiply two matrices, we need a temporary area to store the result. If we try to write the result directly, the results are undefined! 

We first create a temporary floating-point array to store the temporary result; then we call multiplyMM() to multiply the projection matrix and model matrix together into this

temporary array. Next we call System.arraycopy() to store the result back into projectionMatrix, which now contains the combined effects of the model matrix and the

projection matrix.

Pushing the air hockey table into the distance brought it into our frustum, but the table is still standing upright. After a quick recap, we’ll learn how to rotate the table so that

we see it from an angle rather than upright.

6.8 Adding Rotation

1.The Direction of Rotation

To figure out how an object would rotate around a given axis, we’ll use the right-hand rule.Try this out with the x-, y-, and z-axes. If we rotate around the y-axis, our

table will spin horizontally around its top and bottom ends. If we rotate around the z-axis, the table will spin around in a circle. What we want to do is rotate the table

backward around the x-axis, as this will bring the table more level with our eyes.

2.Rotation Matrices
To do the actual rotation, we’ll use a rotation matrix. Matrix rotation uses the trigonometric functions of sine and cosine to convert the rotation angle into scaling factors.

The following is the matrix definition for a rotation around the x-axis:


Then you have a matrix for a rotation around the y-axis:


Finally, there’s also one for a rotation around the z-axis:


3.Adding the Rotation to Our Code

We’re now ready to add the rotation to our code. Go back to onSurfaceChanged(), and adjust the translation and add a rotation as follows:

//AirHockey3D/src/com/airhockey/android/AirHockeyRenderer.java
translateM(modelMatrix, 0, 0f, 0f, -2.5f);
rotateM(modelMatrix, 0, -60f, 1f, 0f, 0f);

We push the table a little farther, because once we rotate it the bottom end will be closer to us. We then rotate it by -60 degrees around the x-axis, which brings the table at

a nice angle, as if we were standing in front of it. The table should now look like the following:













  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值