在這一課中,你将學會如何加載3D世界,并在3D世界中漫遊。
現在這些日子您所需要的是一個大一點的、更複雜些的、動态3D世界,它帶有空間的六自由度和花哨的效果如鏡像、入口、扭曲等等,當然還要有更快的幀顯示速度。這一課就要解釋一個基本的3D世界"結構",以及如何在這個世界裡遊走。
資料結構
當您想要使用一系列的數字來完美的表達3D環境時,随着環境複雜度的上升,這個工作的難度也會随之上升。出于這個原因,我們必須将資料歸類,使其具有更多的可操作性風格。在程式清單頭部出現了sector(區段)的定義。每個3D世界基本上可以看作是sector(區段)的集合。一個sector(區段)可以是一個房間、一個立方體、或者任意一個閉合的區間。
這裡我們在渲染類Render裡面定義了三個内部類,内部類的代碼如下所示:
Java代碼
- private static class Vertex {
- public float x, y, z;
- public float u, v;
- }
- private static class Triangle {
- public Vertex[] vertex = new Vertex[3];
- public Triangle() {
- for (int i = 0; i < 3; i++)
- vertex[i] = new Vertex();
- }
- }
- private static class Sector {
- public int numtriangles;
- public Triangle[] triangles;
- public Sector(int inTri) {
- //确定了三邊形的數量
- numtriangles = inTri;
- triangles = new Triangle[inTri];
- //對三邊形指派
- for (int i = 0; i < inTri; i++)
- triangles[i] = new Triangle();
- }
- }
/**
* 定義頂點
* 頂點包含了OpenGL真正感興趣的資料。
* 我們用3D空間中的坐标值(x,y,z)以及它們的紋理坐标(u,v)來定義三角形的每個頂點。
*/
private static class Vertex {
public float x, y, z;
public float u, v;
}
/**
* 定義三邊形
* 三角形本質上是由一些(兩個以上)頂點組成的多邊形,
* 頂點同時也是我們的最基本的分類機關。
*/
private static class Triangle {
public Vertex[] vertex = new Vertex[3];
public Triangle() {
for (int i = 0; i < 3; i++)
vertex[i] = new Vertex();
}
}
/**
* 區段定義
* 一個sector(區段)包含了一系列的多邊形,
* 是以下一個目标就是triangle(我們将隻用三角形,這樣寫代碼更容易些)。
*/
private static class Sector {
public int numtriangles;
public Triangle[] triangles;
public Sector(int inTri) {
//确定了三邊形的數量
numtriangles = inTri;
triangles = new Triangle[inTri];
//對三邊形指派
for (int i = 0; i < inTri; i++)
triangles[i] = new Triangle();
}
}
下面是渲染類的全部的代碼:
Java代碼
- package demos.nehe.lesson10;
- import demos.common.ResourceRetriever;
- import demos.common.TextureReader;
- import javax.media.opengl.GL;
- import javax.media.opengl.GLAutoDrawable;
- import javax.media.opengl.GLEventListener;
- import javax.media.opengl.glu.GLU;
- import java.io.BufferedReader;
- import java.io.IOException;
- import java.io.InputStreamReader;
- import java.util.StringTokenizer;
- import javax.media.opengl.GL2;
- import javax.media.opengl.glu.gl2.GLUgl2;
- class Renderer implements GLEventListener {
- //常量
- private final float PI_180 = (float) (Math.PI / 180.0);
- //啟用混合的開關
- private boolean blendingEnabled; // Blending ON/OFF
- //
- private float heading;
- private float xpos;
- private float zpos;
- //設定是否可以在上下左右移動或旋轉視角
- private boolean stepForward;
- private boolean stepBackward;
- private boolean turnRight;
- private boolean turnLeft;
- //Y軸方向上的旋轉量
- private float yrot; // Y Rotation
- private float walkbias = 0;
- private float walkbiasangle = 0;
- private float lookupdown = 0.0f;
- private boolean lookUp;
- private boolean lookDown;
- //選擇過濾的種類
- private int filter;
- //使用的紋理貼圖
- private int[] textures = new int[3];
- //我們的模型存儲
- private Sector sector1;
- private GLU glu = new GLUgl2();
- public void toggleBlending() {
- blendingEnabled = !blendingEnabled;
- }
- public void switchFilter() {
- filter = (filter + 1) % 3;
- }
- public void stepForward(boolean step) {
- stepForward = step;
- }
- public void stepBackward(boolean step) {
- stepBackward = step;
- }
- public void turnRight(boolean turn) {
- turnRight = turn;
- }
- public void turnLeft(boolean turn) {
- turnLeft = turn;
- }
- public void lookUp(boolean look) {
- lookUp = look;
- }
- public void lookDown(boolean look) {
- lookDown = look;
- }
- private void setupWorld() throws IOException {
- BufferedReader in = null;
- try {
- in = new BufferedReader(new InputStreamReader(ResourceRetriever.getResourceAsStream("demos/data/models/world.txt")));
- String line = null;
- while ((line = in.readLine()) != null) {
- //如果一行為空
- if (line.trim().length() == 0 || line.trim().startsWith("//"))
- continue;
- //讀取第一行 第一行裡以字元串“NUMPOLLIES”開頭,記錄了這個區段裡的三邊形的數量
- if (line.startsWith("NUMPOLLIES")) {
- int numTriangles;
- //将讀取的數量的字元串轉化為整形
- numTriangles = Integer.parseInt(line.substring(line.indexOf("NUMPOLLIES") + "NUMPOLLIES".length() + 1));
- //建構區段将頂點和面的資訊存儲在區段裡并儲存在記憶體中
- sector1 = new Sector(numTriangles);
- break;
- }
- }
- //将頂點資訊和面的資訊儲存在區段中
- for (int i = 0; i < sector1.numtriangles; i++) {
- for (int vert = 0; vert < 3; vert++) {
- //去除空格行
- while ((line = in.readLine()) != null) {
- if (line.trim().length() == 0 || line.trim().startsWith("//"))
- continue;
- break;
- }
- //如果不為空格行
- if (line != null) {
- StringTokenizer st = new StringTokenizer(line, " ");
- //由檔案txt中存儲的格式,将每行中的資料轉換為浮點型,儲存到三邊形的頂點數組中
- sector1.triangles[i].vertex[vert].x = Float.valueOf(st.nextToken()).floatValue();
- sector1.triangles[i].vertex[vert].y = Float.valueOf(st.nextToken()).floatValue();
- sector1.triangles[i].vertex[vert].z = Float.valueOf(st.nextToken()).floatValue();
- sector1.triangles[i].vertex[vert].u = Float.valueOf(st.nextToken()).floatValue();
- sector1.triangles[i].vertex[vert].v = Float.valueOf(st.nextToken()).floatValue();
- }
- }
- }
- } finally {
- if (in != null)
- in.close();
- }
- }
- private void loadGLTextures(GL2 gl, GLU glu) throws IOException {
- TextureReader.Texture texture = TextureReader.readTexture("demos/data/images/mud.png");
- //Create Nearest Filtered Texture
- //啟用Nearest 過濾
- gl.glGenTextures(3, textures, 0);
- gl.glBindTexture(GL2.GL_TEXTURE_2D, textures[0]);
- gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAG_FILTER, GL.GL_NEAREST);
- gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MIN_FILTER, GL.GL_NEAREST);
- gl.glTexImage2D(GL2.GL_TEXTURE_2D,
- 0,
- 3,
- texture.getWidth(),
- texture.getHeight(),
- 0,
- GL.GL_RGB,
- GL.GL_UNSIGNED_BYTE,
- texture.getPixels());
- //Create Linear Filtered Texture
- //啟用線性過濾的紋理
- gl.glBindTexture(GL2.GL_TEXTURE_2D, textures[1]);
- gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR);
- gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR);
- gl.glTexImage2D(GL2.GL_TEXTURE_2D,
- 0,
- 3,
- texture.getWidth(),
- texture.getHeight(),
- 0,
- GL.GL_RGB,
- GL.GL_UNSIGNED_BYTE,
- texture.getPixels());
- //啟用 mipmapped濾波方式
- gl.glBindTexture(GL2.GL_TEXTURE_2D, textures[2]);
- gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR);
- gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR_MIPMAP_NEAREST);
- glu.gluBuild2DMipmaps(GL2.GL_TEXTURE_2D,
- 3,
- texture.getWidth(),
- texture.getHeight(),
- GL.GL_RGB,
- GL.GL_UNSIGNED_BYTE,
- texture.getPixels());
- }
- public void init(GLAutoDrawable drawable) {
- GL2 gl = drawable.getGL().getGL2();
- try {
- //導入紋理圖檔并banding
- loadGLTextures(gl, glu);
- //導入模型頂點和三邊形
- setupWorld();
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- //啟用紋理映射
- gl.glEnable(GL2.GL_TEXTURE_2D);
- //設定混合
- gl.glBlendFunc(GL2.GL_SRC_ALPHA, GL.GL_ONE);
- //啟用高斯模糊
- gl.glShadeModel(GL2.GL_SMOOTH);
- //設定背景顔色
- gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
- //啟用深度緩存
- gl.glClearDepth(1.0);
- //啟用深度測試
- gl.glEnable(GL.GL_DEPTH_TEST);
- gl.glDepthFunc(GL.GL_LEQUAL); //The Type Of Depth Test To Do
- //啟用
- gl.glHint(GL2.GL_PERSPECTIVE_CORRECTION_HINT, GL.GL_NICEST); // Really Nice Perspective Calculations
- }
- private void update() {
- if (stepForward) {
- xpos -= (float) Math.sin(heading * PI_180) * 0.05f;
- zpos -= (float) Math.cos(heading * PI_180) * 0.05f;
- if (walkbiasangle >= 359.0f) {
- walkbiasangle = 0.0f;
- } else {
- walkbiasangle += 10;
- }
- walkbias = (float) Math.sin(walkbiasangle * PI_180) / 20.0f;
- }
- if (stepBackward) {
- xpos += (float) Math.sin(heading * PI_180) * 0.05f;
- zpos += (float) Math.cos(heading * PI_180) * 0.05f;
- if (walkbiasangle <= 1.0f) {
- walkbiasangle = 359.0f;
- } else {
- walkbiasangle -= 10;
- }
- walkbias = (float) Math.sin(walkbiasangle * PI_180) / 20.0f;
- }
- if (turnRight) {
- heading -= 1.0f;
- yrot = heading;
- }
- if (turnLeft) {
- heading += 1.0f;
- yrot = heading;
- }
- if (lookUp) {
- lookupdown -= 1.0f;
- }
- if (lookDown) {
- lookupdown += 1.0f;
- }
- }
- public void display(GLAutoDrawable drawable) {
- //更新
- update();
- GL2 gl = drawable.getGL().getGL2();
- //啟用顔色緩存和深度緩存
- gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT); //Clear The Screen And The Depth Buffer
- //重置觀察模型
- gl.glLoadIdentity(); //Reset The View
- //如果混合啟用就開啟混合
- if (!blendingEnabled) {
- gl.glDisable(GL.GL_BLEND);
- gl.glEnable(GL.GL_DEPTH_TEST);
- } else {
- gl.glEnable(GL.GL_BLEND);
- gl.glDisable(GL.GL_DEPTH_TEST);
- }
- // 頂點的臨時 X, Y, Z, U 和 V 的數值
- float x = 0,y = 0,z = 0,u = 0,v = 0;
- //平移的三維分量
- 用于遊戲者沿X軸平移時的大小
- float xtrans = -xpos;
- // 用于遊戲者沿Z軸平移時的大小
- float ztrans = -zpos;
- // 用于頭部的上下擺動
- float ytrans = -walkbias - 0.25f;
- // 位于遊戲者方向的360度角
- float sceneroty = 360.0f - yrot;
- // 上下旋轉
- gl.glRotatef(lookupdown, 1.0f, 0, 0);
- // 根據遊戲者正面所對方向所作的旋轉
- gl.glRotatef(sceneroty, 0, 1.0f, 0);
- // 以遊戲者為中心的平移場景
- gl.glTranslatef(xtrans, ytrans, ztrans);
- // 根據 filter 選擇的紋理
- gl.glBindTexture(GL2.GL_TEXTURE_2D, textures[filter]);
- // 處理各個三邊形
- for (int i = 0; i < sector1.numtriangles; i++) {
- //繪制三邊形
- gl.glBegin(GL2.GL_TRIANGLES);
- // 指向前面的法線
- gl.glNormal3f(0.0f, 0.0f, 1.0f);
- //第一個頂點的各分量
- x = sector1.triangles[i].vertex[0].x;
- y = sector1.triangles[i].vertex[0].y;
- z = sector1.triangles[i].vertex[0].z;
- u = sector1.triangles[i].vertex[0].u;
- v = sector1.triangles[i].vertex[0].v;
- gl.glTexCoord2f(u, v);
- gl.glVertex3f(x, y, z);
- //第二個頂點的各分量
- x = sector1.triangles[i].vertex[1].x;
- y = sector1.triangles[i].vertex[1].y;
- z = sector1.triangles[i].vertex[1].z;
- u = sector1.triangles[i].vertex[1].u;
- v = sector1.triangles[i].vertex[1].v;
- gl.glTexCoord2f(u, v);
- gl.glVertex3f(x, y, z);
- //第三個頂點的各分量
- x = sector1.triangles[i].vertex[2].x;