![opengl大作业](https://i-blog.csdnimg.cn/blog_migrate/958e3be849b4897e8c4dddd6ffe24d9a.png)
opengl大作业
在前面的文章中,我们设法显示了三角形。 那么如何显示正方形呢? 一个正方形由2个三角形组成。
下图显示了这一点:
![]() |
三角形的正方形 |
这里有一件有趣的事情要注意。 正方形是ABDC,而不是通常的ABCD 。 这是为什么? 因为OpenGL如何将三角形链接在一起。
您在这里看到的是一个三角带。 三角形带是一系列连接的三角形,在我们的例子中是2个三角形。
OpenGL使用顶点按以下顺序绘制以下三角形带(即正方形):
三角形1: V1-> V2-> V3
三角形2: V3-> V2-> V4
![]() |
三角带 |
它使用顶点按顺序绘制第一个三角形,然后从上一个三角形获取最后一个顶点,并使用该三角形的最后一面作为新三角形的基础。
这也有好处:我们从内存中消除了冗余数据。
抓住上一篇文章中的项目,并创建一个名为Square的新类。
如果将Square类与Triangle类进行比较,您会发现只有一个区别:
package net.obviam.opengl;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import javax.microedition.khronos.opengles.GL10;
public class Square {
private FloatBuffer vertexBuffer; // buffer holding the vertices
private float vertices[] = {
-1.0f, -1.0f, 0.0f, // V1 - bottom left
-1.0f, 1.0f, 0.0f, // V2 - top left
1.0f, -1.0f, 0.0f, // V3 - bottom right
1.0f, 1.0f, 0.0f // V4 - top right
};
public Square() {
// a float has 4 bytes so we allocate for each coordinate 4 bytes
ByteBuffer vertexByteBuffer = ByteBuffer.allocateDirect(vertices.length * 4);
vertexByteBuffer.order(ByteOrder.nativeOrder());
// allocates the memory from the byte buffer
vertexBuffer = vertexByteBuffer.asFloatBuffer();
// fill the vertexBuffer with the vertices
vertexBuffer.put(vertices);
// set the cursor position to the beginning of the buffer
vertexBuffer.position(0);
}
/** The draw method for the square with the GL context */
public void draw(GL10 gl) {
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
// set the colour for the square
gl.glColor4f(0.0f, 1.0f, 0.0f, 0.5f);
// Point to our vertex buffer
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
// Draw the vertices as triangle strip
gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, vertices.length / 3);
//Disable the client state before leaving
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
}
}
区别在于突出显示的行(13-18)。 没错,我们在顶点数组中又添加了一个顶点。
现在更改GlRenderer,以便使用Square代替Triangle 。
package net.obviam.opengl;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.opengl.GLU;
import android.opengl.GLSurfaceView.Renderer;
public class GlRenderer implements Renderer {
private Square square; // the square
/** Constructor to set the handed over context */
public GlRenderer() {
this.square = new Square();
}
@Override
public void onDrawFrame(GL10 gl) {
// clear Screen and Depth Buffer
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
// Reset the Modelview Matrix
gl.glLoadIdentity();
// Drawing
gl.glTranslatef(0.0f, 0.0f, -5.0f); // move 5 units INTO the screen
// is the same as moving the camera 5 units away
square.draw(gl); // Draw the triangle
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
if(height == 0) { //Prevent A Divide By Zero By
height = 1; //Making Height Equal One
}
gl.glViewport(0, 0, width, height); //Reset The Current Viewport
gl.glMatrixMode(GL10.GL_PROJECTION); //Select The Projection Matrix
gl.glLoadIdentity(); //Reset The Projection Matrix
//Calculate The Aspect Ratio Of The Window
GLU.gluPerspective(gl, 45.0f, (float)width / (float)height, 0.1f, 100.0f);
gl.glMatrixMode(GL10.GL_MODELVIEW); //Select The Modelview Matrix
gl.glLoadIdentity(); //Reset The Modelview Matrix
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
}
}
运行此命令将产生以下结果:
|
三角带形成一个正方形 |
对此进行检查, Square类中的draw()方法现在应该有意义。
public void draw(GL10 gl) {
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
// set the colour for the square
gl.glColor4f(0.0f, 1.0f, 0.0f, 0.5f);
// Point to our vertex buffer
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
// Draw the vertices as triangle strip
gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, vertices.length / 3);
//Disable the client state before leaving
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
}
首先,我们使OpenGL使用顶点数组进行渲染。 我们的顶点数组包含正方形的顶点。
gl.glVertexPointer (第5行)告诉opengl渲染器从何处获取顶点以及顶点的类型。
第一个参数告诉一个顶点使用多少个坐标。 我们使用3(x,y,z)。 第二个参数表明值是float类型。
第三个参数是数组顶点之间的偏移量。 这就是所谓的冲突。 我们有一个紧密排列的数组,所以它是0 。
最后,最后一个参数告诉顶点在哪里。 当然是我们的缓冲区vertexBuffer 。
第11行中的gl.glDrawArrays告诉OpenGL绘制图元。 什么样的原始语? 第一个参数中指定的一个: GL10.GL_TRIANGLE_STRIP 。 它从先前设置的顶点缓冲区中提取顶点,并且遵循前面描述的三角形条的规则。
第二个参数指定数组中顶点的起始索引。
第3个参数告诉OpenGL,要渲染的多边形要使用多少个顶点。 因为在上一条语句( gl.glVertexPointer )中我们指定了3个坐标定义一个顶点,所以我们将提供顶点数组的长度除以3。在数组中有9个元素定义了3个顶点。
glDisableClientState(GL10.GL_VERTEX_ARRAY)禁用包含顶点的数组的渲染状态。
将glEnableClientState和glDisableClientState视为程序中的begin…end语句。 我们基本上在OpenGL渲染器中输入子例程。 输入例程后,我们将设置变量(顶点缓冲区,颜色等),并执行其他子例程(绘制顶点)。 完成后,我们退出子例程。 我们在渲染器中隔离工作。
确保在此阶段运行应用程序,并了解发生了什么。
创建纹理
现在有趣的部分。 让我们加载图像并创建纹理。 纹理就是图像。
要了解如何在Android应用中加载图片,请参阅本文。
我们要使用Square类,因为我们想将纹理应用于正方形。
我们需要加载图像,告诉opengl渲染器我们要使用它作为纹理,最后我们将告诉渲染器在原始图元(正方形)上确切显示它的位置。
就像将箔纸放在窗户或墙壁上一样。 我为您提供了包含窗口大小图像的贴膜,并告诉您用它覆盖窗口,因此贴膜的左上角将位于窗口的左上角。 就是这样,让我们开始工作。
OpenGL使用顶点计算出放置内容的位置。 因此,我们需要为图像创建一个数组。 但是这次,这将是2D,因为位图就像一张纸,是一个平面。
添加纹理的坐标数组。
private FloatBuffer textureBuffer; // buffer holding the texture coordinates
private 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)
};
我们需要以类似于vertexBuffer的方式创建textureBuffer 。 这发生在构造函数中,我们将只重用byteBuffer 。 检查新的构造函数:
public Square() {
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(vertices.length * 4);
byteBuffer.order(ByteOrder.nativeOrder());
vertexBuffer = byteBuffer.asFloatBuffer();
vertexBuffer.put(vertices);
vertexBuffer.position(0);
byteBuffer = ByteBuffer.allocateDirect(texture.length * 4);
byteBuffer.order(ByteOrder.nativeOrder());
textureBuffer = byteBuffer.asFloatBuffer();
textureBuffer.put(texture);
textureBuffer.position(0);
}
我们将向Square类添加一个重要的方法。 loadGLTexture方法。 启动时将从渲染器中调用它。 它发生在onSurfaceCreated方法中。 这将从磁盘加载图像并将其绑定到OpenGL存储库中的纹理。 基本上,它将为处理后的图像分配一个内部ID,并且OpenGL API将使用它在其他纹理中对其进行标识。
/** The texture pointer */
private int[] textures = new int[1];
public void loadGLTexture(GL10 gl, Context context) {
// loading texture
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(),
R.drawable.android);
// generate one texture pointer
gl.glGenTextures(1, textures, 0);
// ...and bind it to our array
gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
// create nearest filtered texture
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
// Use Android GLUtils to specify a two-dimensional texture image from our bitmap
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
// Clean up
bitmap.recycle();
}
我们需要一个纹理指针数组。 OpenGL将在此处存储我们将在应用程序中使用的纹理的名称。 因为我们只有一个图像,所以我们将创建一个大小为1的数组。
第06行加载了以前复制到/ res / drawable-mdpi目录中的android位图,因此ID已生成。
有关此位图的说明。 鼓励正方形。 它对扩展很有帮助。 因此,请确保纹理的位图是正方形(6×6、12×12、128×128等)。 如果不是正方形,请确保宽度和高度为2的幂(2、4、8、16、32,…)。 您可以使用128×512的位图,它完全可用并且已优化。
第10行生成纹理的名称。 在我们的情况下,生成一个名称并将其存储在textures数组中。 即使显示名称,它实际上也会生成一个int值。 有点令人困惑,但事实就是如此。
第12行将纹理与新生成的名称(texture [0])绑定。 这意味着,在此子例程中使用纹理的任何东西都将使用绑定的纹理。 它实际上激活了纹理。 绑定纹理是活动纹理。 如果我们有多个纹理和多个正方形可以使用它们,那么在使用它们之前,必须为每个正方形绑定(激活)适当的纹理。第15和16行设置了一些用于纹理的滤镜。 我们刚刚告诉OpenGL需要缩小或扩展纹理以覆盖正方形时使用什么类型的滤镜。 我们选择了一些有关如何缩放图像的基本算法。 现在不必为此担心。
在第19行中,我们使用Android的实用程序为位图指定2D纹理图像。 它基于位图以内部格式在内部创建图像(纹理)。
在第22行中,我们释放了内存。 您不应忘记这一点,因为设备上的内存非常有限并且图像很大。
现在让我们看看如何修改draw()方法。
public void draw(GL10 gl) {
// bind the previously generated texture
gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
// Point to our buffers
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
// Set the face rotation
gl.glFrontFace(GL10.GL_CW);
// Point to our vertex buffer
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, textureBuffer);
// Draw the vertices as triangle strip
gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, vertices.length / 3);
//Disable the client state before leaving
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
}
}
这不是上一篇文章的巨大修改。 这些增加已记录在案,并执行以下操作:
第03行使用存储在textures [0]中的名称(整数ID)绑定(激活)纹理。
第7行在当前OpenGL上下文中启用纹理映射。
第14行为OpenGL上下文提供了纹理坐标。用纹理绘制图元之后,我们将关闭纹理映射以及图元渲染。
重要– UV贴图
如果仔细查看,纹理映射坐标数组中的顶点顺序将不遵循正方形顶点坐标数组中的顺序。
此处对纹理映射坐标有很好的解释: http : //iphonedevelopment.blogspot.com/2009/05/opengl-es-from-ground-up-part-6_25.html 。
我将尽力快速解释它。 检查下图。
|
正方形和纹理坐标排序 |
正方形由2个三角形组成,并且顶点按以下顺序排列。
1 –左下
2 –右下
3 –左上方
4 –右上方
注意逆时针路径。
纹理坐标的顺序为:1-> 3-> 2-> 4
如果您从另一个角开始绘制形状,请记住此映射并旋转它。 要阅读UV映射,请查看Wikipedia条目或在网上搜索。
对于最后一部分,要使其工作,我们需要向渲染器提供上下文,以便在启动时加载纹理。
onSurfaceCreated方法将如下所示。
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// Load the texture for the square
square.loadGLTexture(gl, this.context);
gl.glEnable(GL10.GL_TEXTURE_2D); //Enable Texture Mapping ( NEW )
gl.glShadeModel(GL10.GL_SMOOTH); //Enable Smooth Shading
gl.glClearColor(0.0f, 0.0f, 0.0f, 0.5f); //Black Background
gl.glClearDepthf(1.0f); //Depth Buffer Setup
gl.glEnable(GL10.GL_DEPTH_TEST); //Enables Depth Testing
gl.glDepthFunc(GL10.GL_LEQUAL); //The Type Of Depth Testing To Do
//Really Nice Perspective Calculations
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);
}
第03行加载纹理。 其余各行仅使用一些值配置渲染器。 您现在不必担心它们。
您将需要为Square对象提供应用程序上下文,因为对象本身会加载纹理,并且需要知道位图的路径。
只需在Run活动的onCreate方法( glSurfaceView.setRenderer(new GlRenderer(this)); )中为渲染器提供上下文即可。
确保渲染器具有通过构造函数声明和设置的上下文。
GlRendered类的节选。
private Square square; // the square
private Context context;
/** Constructor to set the handed over context */
public GlRenderer(Context context) {
this.context = context;
// initialise the square
this.square = new Square();
}
如果运行代码,您应该会看到一个正方形,上面放着一个漂亮的机器人。
|
带有Android纹理的正方形 |
在此处下载源代码和项目(obviam.opengl.p03.tgz)。
参考: 纹理贴图–来自“反对谷物”博客的JCG合作伙伴Tamas Jano的OpenGL Android(使用OpenGL和Squares显示图像) 。
- Android游戏开发教程简介
- Android游戏开发–游戏创意
- Android游戏开发–创建项目
- Android游戏开发–基本游戏架构
- Android游戏开发–基本游戏循环
- Android游戏开发–使用Android显示图像
- Android游戏开发–在屏幕上移动图像
- Android游戏开发–游戏循环
- Android游戏开发–测量FPS
- Android游戏开发–雪碧动画
- Android游戏开发–粒子爆炸
- Android游戏开发–设计游戏实体–策略模式
- Android游戏开发–使用位图字体
- Android游戏开发–从Canvas切换到OpenGL ES
- Android游戏开发–使用OpenGL ES显示图形元素(原语)
- Android游戏开发–设计游戏实体–状态模式
- Android游戏文章系列
翻译自: https://www.javacodegeeks.com/2011/10/android-game-development-opengl-texture.html
opengl大作业