精华内容
下载资源
问答
  • 为什么80%的码农都做不了架构师?...安卓opengl ES教程之二——创建多边形 后一篇教程: 安卓opengl ES教程之四——添加颜色 转载于:https://my.oschina.net/tnjin/blog/551403

    在上一篇教程中我们主要讲的是关于建立一个多边形。这一篇教程全都是关于变换的——如何移动一个多边形到任意位置。我会接着上一篇教程而继续讲下去,所以你可以继续使用上一篇的源码或者它的副本。

    在这一篇里你可能会嫌我叨叨一大堆数学知识,但是我认为这些对于了解opengl ES渲染网格过程中对所有的顶点乘以一个矩阵是重要的。你做的所有变换实际上都是通过不同的方式去修改顶点的矩阵。你可以把矩阵看作一张纸,在开始绘制之前,你没有移动过笔,所以你会始终绘制在纸的中央位置。但是通过变换,你可以移动这张纸和中心点。旋转操作就好像把这张纸绕着中心点旋转。缩放操作有点难以理解,姑且理解为你绘制网格的单位大小改变了。通常我们所说的变换是基于网格而不是基于世界坐标,这是很重要的一点。

    坐标系

    opengl使用所谓的“右手坐标系”。一个坐标系,如果你沿坐标轴,从正方向向着原点望去,逆时针的旋转被认为是正向旋转,那么这个坐标系就被称为右手坐标系。当你初始化了你的视图,并且没有做任何变换的时候,坐标轴处于这样的状态:X轴从左向右,Y轴从下向上,Z轴从屏幕里向外。

    26105806_ioxK.png

    平移

    public abstract void glTranslatef(float x, float y, float z)

    对矩阵的平移表现为矩阵的网格被移动。平移是沿着坐标轴,没有任何旋转操作,坐标轴处于默认的状态。平移对一个多边形的所有顶点在同一坐标轴方向上偏移同样的距离,它是对当前值的简单加减操作。下图展示了一个二维的平移操作。

    26105806_JTPh.png

    起点是{x:-2,y:1},我们想要移动到{x:1,y:3},所以我们增加{x:3,y:2},即一个简单的加法操作:

    {x:-2, y:1} + {x:3, y:2} = {x:-2 + 3, y:1 + 2} = {x:1, y:3}

    同样,在三维中,我们如果定位在{x:1, y:1, z:0},我们想要向屏幕里移动3个单位,于是我们加上{x:0, y:0, z:-3}得到{x:1, y:1, z:-3}。

    在上篇教程中,为了看到绘制的四边形,我们把它向屏幕里移动了4个单位,我们的做法是在当前位置上加上{x:0, y:0, z:-4} ,这是我们曾经用过的代码:

    // 向屏幕里移动4个单位
    gl.glTranslatef(0, 0, -4);

    如果你沿着x,y,z轴做一系列的平移操作,顺序并不是很重要,但是当我们做旋转操作的时候,顺序就非常重要了。

    记住坐标轴的放置方式有点困难,好在我们有个便于记忆的方法。向下图一样伸出你的左手。每个手指的方向代表着坐标轴的正方向。在我一开始接触3D编程的时候,我曾经在我的这几个手指上写上X,Y,Z。

    26105807_ACdi.jpg

    旋转

    public abstract void glRotatef(float angle, float x, float y, float z)

    旋转顾名思义,你对一个矩阵做旋转操作,会表现为网格被旋转了。在旋转之前如果没有任何平移操作的话,那么就是绕着原点旋转。x,y,z三个值定义了旋转操作的中心点,angle代表旋转的角度值。

    如果你记住下面三点,你将会很容易的进行旋转操作:

    • 旋转的单位是角度值

      好多框架和数学方法使用弧度制,但是opengl使用角度制

    • 如果进行一系列旋转操作,顺序很重要

    如果你要还原一个旋转操作,只需要把角度值或者三个坐标值取负就可以,例如glRotatef(angle, x, y, z) 可以被 glRotatef(angle, -x, -y, -z)或者glRotatef(-angle, x, y, z)复位。

    但是如果你像下面这样做了一系列的旋转操作:

    gl.glRotatef(90f, 1.0f, 0.0f, 0.0f);
    gl.glRotatef(90f, 0.0f, 1.0f, 0.0f);
    gl.glRotatef(90f, 0.0f, 0.0f, 1.0f);

    26105807_NQWW.png

    这时候想要复位到原始位置,你不能向下面这样仅仅对坐标值(原文中是的“对角度”,个人认为作者这里有笔误——译者注)取负:

    gl.glRotatef(90f, -1.0f, 0.0f, 0.0f);
    gl.glRotatef(90f, 0.0f, -1.0f, 0.0f);
    gl.glRotatef(90f, 0.0f, 0.0f, -1.0f);

    你还需要逆序执行,如下:

    gl.glRotatef(90f, 0.0f, 0.0f, -1.0f);
    gl.glRotatef(90f, 0.0f, -1.0f, 0.0f);
    gl.glRotatef(90f, -1.0f, 0.0f, 0.0f);

    一系列旋转操作的顺序是非常重要的。

    • 如果你沿着坐标轴从正方向向原点看去,此坐标轴的旋转正方向是逆时针的

    如果你握着一支铅笔,笔头和你的大拇指方向相同,如下图,将铅笔放在x轴上,让笔头指向坐标轴的正方向,你的其余四指握起来的方向就是沿着这个坐标轴旋转的正方向。

    26105808_TLvv.jpg

    平移和旋转

    由于平移和旋转操作都是基于网格自己的坐标系统,所以记住平移和旋转操作的顺序是很重要的事情。

    如果你先对网格进行了平移操作,然后又进行旋转,那么平移是基于当前的网格坐标状态,而旋转则是基于新的坐标。

    26105808_Xas8.png

    如果你先进行了旋转操作,然后进行平移,那么平移操作将会在旋转之后的坐标基础上进行。

    26105808_e5w7.png

    缩放

    public abstract void glScalef(float x, float y, float z)

    缩放,见文之一,不解释。它可沿任意坐标轴方向上独立进行。缩放和所有的顶点都乘以一个缩放因子的效果是一样的。在下图中,我们用gl.glScalef(2f, 2f, 2f)进行缩放,这也就相当于所有的顶点坐标都乘以2.

    26105808_Lo0J.png

    平移和缩放

    缩放和平移的相互顺序也很重要。如果你在缩放之前进行了平移,那么平移的结果不受影响。如下面的例子,我们先平移了两个单位,然后缩放0.5个单位。

    gl.glTranslatef(2, 0, 0);
    gl.glScalef(0.5f, 0.5f, 0.5f);

    26105809_AYF6.png

    但是如果你先进行了缩放,然后平移,你会得到一个完全不同的结果。因为你先缩放了网格的坐标系统,然后才平移,所以你平移操作不会和之前那样移动同样的尺度。所以如果你先缩放了0.5个单位,然后平移两个单位,结果将会表现为像平移了一个单位一样。

    gl.glScalef(0.5f, 0.5f, 0.5f);
    gl.glTranslatef(2, 0, 0);

    26105809_z52K.png

    初始化,push,pop矩阵

    当你进行平移,旋转,缩放操作的时候,这些操作并不是在同一坐标条件下进行的,每次变换都会基于前一次变换,所以你可能需要复位位置。

    glLoadIdentity

    public abstract void glLoadIdentity()

    glLoadIdentity这个方法是替换当前矩阵为初始矩阵。这和通过glLoadMatrix来加载下面这个矩阵是一样的效果:

    1 0 0 0
    0 1 0 0
    0 0 1 0
    0 0 0 1

    然而有些场景下,你可能不想要初始化整个模型矩阵,你可能需要回到上一次变换之前的状态。

    glPushMatrix

    public abstract void glPushMatrix()

    glPushMatrix是备份当前的矩阵,放入栈中。这也就是说你在执行glPushMatrix之后,进行的任何变换都是基于这个副本的。

    glPopMatrix

    public abstract void glPopMatrix()

    这个方法是把你之前通过glPushMatrix放到栈里的矩阵拿回来。

    一个好的实践就是在每一帧的开始调用glLoadIdentity,而在之后使用glPushMatrix和glPopMatrix。

    组装

    现在让我们基于这个新知识点做个例子。先做三个四边形A,B,C。先对他们进行缩放,使得B比A小一半,C比B小一半。然后让A在屏幕中心绕逆时针方向旋转。B逆时针绕着A旋转,C顺时针绕着B旋转,同时逆时针高速自转。

    public void onDrawFrame(GL10 gl) {
    	// 清空屏幕和深度缓存
    	gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
    	// 替换当前矩阵为初始矩阵
    	gl.glLoadIdentity();
    	// 像屏幕里移动10个单位
    	gl.glTranslatef(0, 0, -10);
    
    	// 四边形 A
    	// 保留当前矩阵
    	gl.glPushMatrix();
    	// 逆时针旋转A
    	gl.glRotatef(angle, 0, 0, 1);
    	// 绘制A
    	square.draw(gl);
    	// 回复到变换前的状态
    	gl.glPopMatrix();
    
    	// 四边形 B
    	// 保留当前矩阵
    	gl.glPushMatrix();
    	//旋转B,然后平移,使它绕着A运动
    	gl.glRotatef(-angle, 0, 0, 1);
    	// 平移 B.
    	gl.glTranslatef(2, 0, 0);
    	// 缩小到A的50%
    	gl.glScalef(.5f, .5f, .5f);
    	// 绘制B
    	square.draw(gl);
    
    	// 四边形 C
    	//保留当前矩阵
    	gl.glPushMatrix();
    	// 使其绕着B旋转
    	gl.glRotatef(-angle, 0, 0, 1);
    	gl.glTranslatef(2, 0, 0);
    	// 缩小到B的50%
    	gl.glScalef(.5f, .5f, .5f);
    	// 自转
    	gl.glRotatef(angle*10, 0, 0, 1);
    	// 绘制C
    	square.draw(gl);
    
    	// 回复到C之前的状态
    	gl.glPopMatrix();
    	//回复到B之前的状态
    	gl.glPopMatrix();
    
    	// 角度自增
    	angle++;
    }

    最后不要忘了增加角度这个变量,谢谢提醒。

    public class OpenGLRenderer implements Renderer {
    	private Square square;
    	private float angle; // 不要忘了添加这个变量
            ...

    引用

    这篇教程引用如下文献:

    Android Developers

    OpenGL ES 1.1 Reference Pages

    你可以下载教程的源码:Tutorial_Part_III

    你也可以检出代码:code.google.com

    前一篇教程:安卓opengl ES教程之二——创建多边形

    后一篇教程:安卓opengl ES教程之四——添加颜色


    转载于:https://my.oschina.net/tnjin/blog/551403

    展开全文
  • 实际上我们是在告诉opengl去使用纹理中“不存在”的部分,所以我们需要告诉opengl如何去处理这个“不存在”的部分。 GL_REPEAT意味着opengl将会按照类似1.0的那个设置来展示并重复使用纹理。 GL_CLAMP_TO_EDGE...

    在上一篇教程中,我们主要讨论了网格,以及如何在网格中添加颜色。实际上向网格添加颜色的更普遍的做法是使用纹理。向网格添加纹理是与添加颜色有些不同的,接下来,我将展示这些操作,并解释一些基本知识。

    载入Bitmap

    第一步,载入用于生成纹理的Bitmap。你可以通过下载,生成或者从resources中加载一张图片。我这里是用的最简单的方式——从resources中直接加载一张图片。

    Bitmap bitmap = BitmapFactory.decodeResource(contect.getResources(),R.drawable.icon);

    关于纹理需要注意的一点是,一些硬件会要求纹理的高宽必须为2的N次幂(1,2,4,8,16,32,64。。。。),如果你在这些设备上使用一个比如30px*30px大小的纹理,那么你只能得到一个白色方块(除非你更改了默认颜色)。

    生成纹理

    在我们加载Bitmap完成之后,我们就需要告诉opengl去生成一个纹理了。

    首先,我们要先让opengl生成一些纹理的id,我们将会用它持有我们后续产生的纹理。在这个例子中,我们只有一个纹理。

    // 创建一个数组,容量为我们需要的纹理数量,
    int[] textures = new int[1];
    // 让opengl去产生纹理id
    gl.glGenTextures(1, textures, 0);

    使用相同的参数,你可以删除纹理:

    // 删除纹理
    gl.glDeleteTextures(1, textures, 0)

    在纹理生成之后,就像其他的元素一样,我们只需要告诉opengl要处理什么即可。我们使用glBindTexture命令来绑定纹理:

    gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);

    这样,我们后续调用的关于纹理的所有的命令都将会应用给这个id的纹理。

    glTexParameter

    我们需要给纹理设置一些参数,第一个参数就是需要告诉opengl是否要缩放纹理以匹配绘制的场景。如果纹理偏小,那么通过缩放的函数可以达到放大的目的:

    //如果纹理偏小,则放大
    gl.glTexParameterf(GL10.GL_TEXTURE_2D,
                       GL10.GL_TEXTURE_MAG_FILTER,
                       GL10.GL_LINEAR);

    反之,下面是如何通过缩放函数去缩小纹理的操作:

    // scale linearly when image smalled than texture
    gl.glTexParameterf(GL10.GL_TEXTURE_2D,
                       GL10.GL_TEXTURE_MIN_FILTER,
                       GL10.GL_LINEAR);

    你需要传递一个参数给这些函数——这里我只展示了两个,其余的你可以自己尝试。

    如果你想要一个清晰而干净的渲染结果,那么你应该使用这个参数:GL10.GL_NEAREST.

    如果你想要一个模糊的渲染结果,那么你应该使用这个参数:GL10.GL_LINEAR.

    UV Mapping

    我们还需要告诉opengl如何把这张图片映射到网格上,需要两步,首先我们要建立UV坐标。UV映射是把二维图片的像素映射到三维顶点的过程。UV坐标左上角为(0,0),右下角为(1,1),如下左图。而下右图所示的是我们如何建立平面。

    23224438_IWGl.png

    为了正确的映射纹理,我们需要把纹理的左下角部分texture(0,1)映射到我们平面的左下角的顶点vertice(0),把纹理的右下角(1,1)映射到我们平面顶点的右面(1).......后面的你知道怎么回事了。

    23224438_6yJa.jpg

    我们把映射放进一个float数组中:

    float textureCoordinates[] = {0.0f, 1.0f,
                                  1.0f, 1.0f,
                                  0.0f, 0.0f,
                                  1.0f, 0.0f };

    如果我们使用0.5代替上面数组中的1.0:

    float textureCoordinates[] = {0.0f, 0.5f,
                                  0.5f, 0.5f,
                                  0.0f, 0.0f,
                                  0.5f, 0.0f };

    平面上将会只有被映射纹理的左上角,如下图:

    23224439_PdiC.jpg

    回到glParameterf,如果我们使用另一种方式,同时把上面的1.0替换成2.0,如下:

    float textureCoordinates[] = {0.0f, 2.0f,
                                  2.0f, 2.0f,
                                  0.0f, 0.0f,
                                  2.0f, 0.0f };

    实际上我们是在告诉opengl去使用纹理中“不存在”的部分,所以我们需要告诉opengl如何去处理这个“不存在”的部分。

    GL_REPEAT意味着opengl将会按照类似1.0的那个设置来展示并重复使用纹理。

    GL_CLAMP_TO_EDGE意味着opengl将会绘制纹理一次,然后只重复纹理的最后一条像素线。

    由于我们是在处理2D的纹理,所以我们需要告诉opengl如何去做。

    下面的四个图示是GL_REPEAT和GL_CLAMP_EDGE组合结果示意图:

    215143_FpGM_1267266.png

    这是我们如何去使用glParameterf方法:

    gl.glTexParameterf(GL10.GL_TEXTURE_2D,
                       GL10.GL_TEXTURE_WRAP_S,
                       GL10.GL_REPEAT);
    gl.glTexParameterf(GL10.GL_TEXTURE_2D,
                       GL10.GL_TEXTURE_WRAP_T,
                       GL10.GL_REPEAT);

    最后我们要做的是把Bitmap帮到到我们前面产生的纹理id上。

    GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);

    使用纹理

    为了能够使用纹理,我们要做的仅仅是像其他元素一样,用UV坐标构造一个ByteBuffer。

    FloatBuffer byteBuf = ByteBuffer.allocateDirect(texture.length * 4);
    byteBuf.order(ByteOrder.nativeOrder());
    textureBuffer = byteBuf.asFloatBuffer();
    textureBuffer.put(textureCoordinates);
    textureBuffer.position(0);

    渲染

    // 告诉opengl开启纹理模式
    gl.glEnable(GL10.GL_TEXTURE_2D);
    // 告诉opengl,纹理已经被分配到内存中
    gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
    // 告诉opengl开启使用UV坐标映射
    gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
    // 告诉openglUV坐标的buffer
    gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, textureBuffer);
    
    // 这里省略网格渲染
    
    // 禁用UV映射
    gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
    //禁用纹理模式
    gl.glDisable(GL10.GL_TEXTURE_2D);

    组装

    我使用的是上一篇教程的修改后的代码。最大的不同应该是我重命名了一些变量和方法名,增加了更多的注释,并且现在所有的代码基于Apache协议授权。为了让代码更易懂,我删除了原来的Plane类,改为使用SimplePlane。

    更新mesh类

    首先我们需要更新mesh类(se.jayway.opengl.tutorial.mesh.Mesh)。我们需要增加加载和渲染纹理的方法。我们需要能够设置和保存UV坐标。

    // Our UV texture buffer.
    private FloatBuffer mTextureBuffer;
    
    /**
     * Set the texture coordinates.
     *
     * @param textureCoords
     */
    protected void setTextureCoordinates(float[] textureCoords) {
    	// float is 4 bytes, therefore we multiply the number if
            // vertices with 4.
    	ByteBuffer byteBuf = ByteBuffer.allocateDirect(
                                               textureCoords.length * 4);
    	byteBuf.order(ByteOrder.nativeOrder());
    	mTextureBuffer = byteBuf.asFloatBuffer();
    	mTextureBuffer.put(textureCoords);
    	mTextureBuffer.position(0);
    }

    我们还需要设置Bitmap和生成纹理的方法:

    // Our texture id.
    private int mTextureId = -1;
    
    // The bitmap we want to load as a texture.
    private Bitmap mBitmap;
    
    /**
     * Set the bitmap to load into a texture.
     *
     * @param bitmap
     */
    public void loadBitmap(Bitmap bitmap) {
    	this.mBitmap = bitmap;
    	mShouldLoadTexture = true;
    }
    
    /**
     * Loads the texture.
     *
     * @param gl
     */
    private void loadGLTexture(GL10 gl) {
    	// Generate one texture pointer...
    	int[] textures = new int[1];
    	gl.glGenTextures(1, textures, 0);
    	mTextureId = textures[0];
    
    	// ...and bind it to our array
    	gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureId);
    
    	// Create Nearest Filtered Texture
    	gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,
    			GL10.GL_LINEAR);
    	gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER,
    			GL10.GL_LINEAR);
    
    	// Different possible texture parameters, e.g. GL10.GL_CLAMP_TO_EDGE
    	gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
    			GL10.GL_CLAMP_TO_EDGE);
    	gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
    			GL10.GL_REPEAT);
    
    	// Use the Android GLUtils to specify a two-dimensional texture image
    	// from our bitmap
    	GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, mBitmap, 0);
    }

    最终,我们加入对纹理加载的调用以及告知opengl用这个纹理去渲染。因篇幅有限,我精简了一部分代码,你可以从附件里找到完整版。

    // Indicates if we need to load the texture.
    private boolean mShouldLoadTexture = false;
    
    /**
     * Render the mesh.
     *
     * @param gl
     *            the OpenGL context to render to.
     */
    public void draw(GL10 gl) {
    	...
    
    	// Smooth color
    	if (mColorBuffer != null) {
    		// Enable the color array buffer to be used during rendering.
    		gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
    		gl.glColorPointer(4, GL10.GL_FLOAT, 0, mColorBuffer);
    	}
    
    	if (mShouldLoadTexture) {
    		loadGLTexture(gl);
    		mShouldLoadTexture = false;
    	}
    	if (mTextureId != -1 && mTextureBuffer != null) {
    		gl.glEnable(GL10.GL_TEXTURE_2D);
    		// Enable the texture state
    		gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
    
    		// Point to our buffers
    		gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, mTextureBuffer);
    		gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureId);
    	}
    
    	gl.glTranslatef(x, y, z);
    
    	...
    
    	// Point out the where the color buffer is.
    	gl.glDrawElements(GL10.GL_TRIANGLES, mNumOfIndices,
    			GL10.GL_UNSIGNED_SHORT, mIndicesBuffer);
    
    	...
    
    	if (mTextureId != -1 && mTextureBuffer != null) {
    		gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
    	}
    
    	...
    
    }

    创建SimplePlane类

    我们还需要创建SimplePlane.java 。这段代码很简单,而且如果你看过我之前的教程,那么你能明白是什么意思。新增的变量是textureCoordinates。

    package se.jayway.opengl.tutorial.mesh;
    
    /**
     * SimplePlane is a setup class for Mesh that creates a plane mesh.
     *
     * @author Per-Erik Bergman (per-erik.bergman@jayway.com)
     *
     */
    public class SimplePlane extends Mesh {
    	/**
    	 * Create a plane with a default with and height of 1 unit.
    	 */
    	public SimplePlane() {
    		this(1, 1);
    	}
    
    	/**
    	 * Create a plane.
    	 *
    	 * @param width
    	 *            the width of the plane.
    	 * @param height
    	 *            the height of the plane.
    	 */
    	public SimplePlane(float width, float height) {
    		// Mapping coordinates for the vertices
    		float textureCoordinates[] = { 0.0f, 2.0f, //
    				2.0f, 2.0f, //
    				0.0f, 0.0f, //
    				2.0f, 0.0f, //
    		};
    
    		short[] indices = new short[] { 0, 1, 2, 1, 3, 2 };
    
                    float[] vertices = new float[] { -0.5f, -0.5f, 0.0f,
                                                      0.5f, -0.5f, 0.0f,
                                                     -0.5f,  0.5f, 0.0f,
                                                      0.5f, 0.5f, 0.0f };
    
    		setIndices(indices);
    		setVertices(vertices);
    		setTextureCoordinates(textureCoordinates);
    	}
    }

    引用

    Android Developer

    OpenGL ES 1.1 Reference Pages

    你可以下载这篇教程的源码:Tutorial_Part_VI

    你也可以使用版本工具检出:code.google.com

    上一篇教程:安卓opengl ES教程之五——mesh

    转载于:https://my.oschina.net/tnjin/blog/549311

    展开全文
  • 为什么80%的码农都做不了架构师?>>>   ...上一篇教程是关于变换的。...安卓openGL ES教程之五——关于网格的更多事 转载于:https://my.oschina.net/tnjin/blog/552548

    上一篇教程是关于变换的。这篇教程比较短,我将会告诉你如何给网格添加颜色。我会继续使用教程二的源码。

    添加颜色

    没有颜色的3D模型看起来很无趣,那么,接下来,让我们给它添加点颜色吧。一般来说,颜色不用过多解释。OpenGL使用RGBA(red,green,blue,alpha)颜色模式。前三个见字知意,不多说。第四个值代表透明度,即颜色有多纯净。如果你想阅读关于颜色的更多信息,可以看这里:RGB颜色模式——维基百科,免费的百科全书

    你也许对使用十六进制(#FF00FF)或者十进制(255,0,255)定义颜色很熟悉,在OpenGL中,我们使用0~1来定义颜色,其中0代表十进制定义颜色的0(#00),1代表十进制定义颜色的255(#FF)。

    最简单给网格上色的方式称之为顶点着色,我将讲解两个不同的方法来实现它。纯色着色是指每个顶点用纯色填充,渐变着色是指混合每个顶点的颜色。

    纯色填充

    纯色填充很简单,只需要告诉OpenGL ES在渲染时使用什么颜色即可。有一点需要记住的是,一旦你设置了OpenGL使用的颜色,那么,直到你再次改变颜色,OpenGL才会改变颜色。这也就意味着,如果你有两个不同的四边形,当你在渲染第二个四边形第一帧的时候去告诉OpenGL 改变颜色,这两个四边形的颜色将会不一样,但是在下一帧,两个的颜色就变得一样了。

    28224227_FzMR.png

    告诉OpenGL ES去使用什么颜色,使用下面的方法:

    public abstract void glColor4f(float red, float green, float blue, float alpha)

    默认的值是red=1,green=1,blue=1,alpha=1。这个颜色是白色,这也是为什么所有的四边形最开始的时候是白色的原因。

    创建一个新的class。命名为FlatColoredSquare,和square几乎一样。在FlatColoredSquare的draw方法中,增加如下代码:

    gl.glColor4f(0.5f, 0.5f, 1.0f, 1.0f); // 0x8080FFFF

    我通常会增加一个像上面那样的注释,因为我习惯读这样的格式。这让我review我的代码时会变得比较容易。

    现在这个方法应该看起来像这样:

    public void draw(GL10 gl) {
            gl.glColor4f(0.5f, 0.5f, 1.0f, 1.0f);
            ...

    然后改变renderer类,使用FlatColoredSquare代替Square:

    public class OpenGLRenderer implements Renderer {
    	private FlatColoredSquare flatSquare; // 改动
    
    	public OpenGLRenderer() {
    		// 初始化这个四边形
    		flatSquare = new FlatColoredSquare(); // 改动
    	}
    
            public void onDrawFrame(GL10 gl) {
                    ...
    		flatSquare.draw(gl); // 别忘了改变这个位置
                    ...
    	}

    记住,在你设置颜色之后,所有被渲染的对象都会使用同一个颜色,在两帧之间不会被重置。

    如果你编译运行,就会看到一个被纯色填充的蓝色四边形。

    为了给接下来的渐变着色的四边形腾开位置,我们把这个四边形平移一下。

    public void onDrawFrame(GL10 gl) {
    	gl.glLoadIdentity();
    	// 向屏幕里移动7个单位,向上移动1.5个单位
    	gl.glTranslatef(0, 1.5f, -7);
            // 绘制我们的纯色四边形
    	flatSquare.draw(gl);
    }

    注意:对于纯色填充,你无需告诉OpenGL ES去开启或者关闭什么状态,因为openGL ES默认使用纯色作为着色方式。

    渐变填充

    渐变填充,只要你给每个顶点赋予不同颜色。openGL ES会插值计算两个顶点间的颜色,你将会得到一个渐变的颜色效果。和纯色填充一样,渐变填充也是会一直使用你设定的颜色,直到你告诉openGL ES去做点改变。

    28224227_xG5I.png

    创建一个新的class,命名为SmoothColoredSquare,和Square类,FlatColoredSquare类几乎一样,像下面这样修改这个类:

    定义每个顶点的颜色:

    public class SmoothColoredSquare {
            ...
            // 映射给每个顶点的颜色
            float[] colors = {
                    1f, 0f, 0f, 1f, // vertex 0 red
                    0f, 1f, 0f, 1f, // vertex 1 green
                    0f, 0f, 1f, 1f, // vertex 2 blue
                    1f, 0f, 1f, 1f, // vertex 3 magenta
            };
            ...

    颜色的定义顺序是很重要的,因为他们是按顺序映射到顶点上的,第一个颜色(1f, 0f, 0f, 1f ) 会被映射到左上角,第二个颜色 ( -1.0f, 1.0f, 0.0f ) 会被映射给左下角,其余的你可以自己推断了。提示,看上图。

    就像我们处理顶点和顺序索引一样,把他们放到buffer中:

    public SmoothColoredSquare() {
    	...
    
    	// float为3字节, colors (RGBA) * 4 bytes
    	ByteBuffer cbb = ByteBuffer.allocateDirect(colors.length * 4);
    	cbb.order(ByteOrder.nativeOrder());
    	colorBuffer = cbb.asFloatBuffer();
    	colorBuffer.put(colors);
    	colorBuffer.position(0);
    	}

    别忘了添加colorBuffer作为这个类的属性变量。

            //我们的颜色缓冲
    	private FloatBuffer colorBuffer;

    同样,我们需要开始颜色缓冲,并告诉openGL ES颜色缓冲的位置。

    public void draw(GL10 gl) {
            ...
    	gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
    
    	// 开启渲染时的颜色缓冲
    	gl.glEnableClientState(GL10.GL_COLOR_ARRAY); // NEW LINE ADDED.
    	// 指出颜色缓冲的位置
    	gl.glColorPointer(4, GL10.GL_FLOAT, 0, colorBuffer); // NEW LINE ADDED.
    
    	gl.glDrawElements(GL10.GL_TRIANGLES, indices.length,
    				GL10.GL_UNSIGNED_SHORT, indexBuffer);
    	...
            // 禁用颜色缓冲
    	gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
            ...
    	}

    别忘了禁用颜色缓冲,如果你不禁用,那么两个四边形都会被渲染成渐变色。

    让我们使用这个新的类,把它添加到renderer中:

    public class OpenGLRenderer implements Renderer {
    	private FlatColoredSquare flatSquare;
    	private SmoothColoredSquare smoothSquare; // 新添加的代码
    
    	public OpenGLRenderer() {
    		// Initialize our squares.
    		flatSquare = new FlatColoredSquare();
    		smoothSquare = new SmoothColoredSquare(); // 新添加的代码
    	}

    我们把这个四边形下移一点,这样他们俩就不会重叠了。

    public void onDrawFrame(GL10 gl) {
    	...
            // 向下平移
    	gl.glTranslatef(0, -3f, 0);
    	// 绘制我们的渐变四边形
    	smoothSquare.draw(gl);
    }

    现在如果你编译运行,就会看到两个四边形,一个是蓝色填充,一个是渐变色填充。

    引用

    这篇教程引用如下文献:

    Android Developers

    OpenGL ES 1.1 Reference Pages

    你可以下载教程的源码:Tutorial_Part_IV

    你也可以检出代码:code.google.com

    上一篇教程:【翻译】安卓opengl ES教程之三——变换

    下一篇教程:安卓openGL ES教程之五——关于网格的更多事

    转载于:https://my.oschina.net/tnjin/blog/552548

    展开全文
  • 为什么80%的码农都做不了架构师?...安卓opengl ES教程之一——初始化view 后一篇教程: 安卓opengl ES教程之三——变换 转载于:https://my.oschina.net/tnjin/blog/550738

    在上一篇教程中我们讨论了关于初始化一个GLSurfaceView。在我们开始新的内容之前,请确保你已经读了它。

    创建多边形

    在这篇教程中,我们将要渲染我们的第一个多边形。

    3D模型是由许多可以被单独操作的小元素(顶点,边,面和多边形)组成的;

    顶点

    一个顶点vertex(复数vertices)是3D模型的最小单元。顶点是由两条或者多条边相交形成的点。在3D模型中,顶点可以被所有相连的边,面,多边形共享。一个顶点也可以代表相机或者光源的位置。你可以在下图中看到一个标记为黄色的顶点。

    24224615_yqdI.png

    我们通过定义一个float数组,并将其放进一个bytebuffer中以提高性能的方式来定义一组顶点。如下图,我们标记的顶点对应到下面的代码中:

    24224616_houP.png

    private float vertices[] = {
          -1.0f,  1.0f, 0.0f,  // 0, 左上
          -1.0f, -1.0f, 0.0f,  // 1, 左下
           1.0f, -1.0f, 0.0f,  // 2, 右下
           1.0f,  1.0f, 0.0f,  // 3, 右上
    };
    
    // 一个float占用4字节,所以我们申请大小时要把顶点数组的长度乘以4.
    ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
    vbb.order(ByteOrder.nativeOrder());
    FloatBuffer vertexBuffer = vbb.asFloatBuffer();
    vertexBuffer.put(vertices);
    vertexBuffer.position(0);

    当你通知opengl ES去渲染的时候,它会调用一些管道方法来实现。大多数管道方法是不开启的,所以你必须记得去开启你所需要的那些。你也需要去告诉这些方法去处理什么。所以,在这个例子中,我们需要告诉opengl ES我们已经创建好了顶点缓冲区,并告诉它缓冲区的位置。

    // 为渲染过程中开启顶点数组的读写
    gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
    // 指定渲染过程中顶点数据的位置和格式
    gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);

    当你处理完这些buffer的时候,别忘了禁用顶点数组。

    // 禁用顶点数组
    gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);


    边是两个顶点之间的连线。他们是面和多边形的边框。在3D模型中,一条边可以被两个面或者多边形共有。对一个边进行变换,会影响所有的相接的点,面,多边形。在opengl ES中,你不必定义边,而应该通过给定一组顶点来定义一个面,从而建立三条边。如果你想要修改一条边,你应该修改组成这条边的两个顶点。如下图,你可以看到一条被标记为黄色的边。

    24224616_KQUt.png


    面是一个三角形。面是由三个角的顶点和他们的边围成的区域。对一个面的变换,影响所有相接的顶点,边和多边形。

    24224617_ax16.png

    顺序很重要

    绘制平面的方向是很重要的,因为这个方向决定了哪一面是“前面”,哪一面是“背面”。之所以这点很重要,是因为出于性能考虑,我们不需要去同时绘制两个面,我们需要背面裁剪。所以使用同样的解析是个不错的办法。这样就可能使得通过glFrontFace定义的“前面”的方向可以被改变。

     gl.glFrontFace(GL10.GL_CCW);

    当然也可以改变哪个面被绘制或者不被绘制。

     gl.glCullFace(GL10.GL_BACK);

    多边形

    24224618_9Lkk.png

    是时候解析平面了,记住我们使用默认的方向——逆时针。看下图和代码解释了如何去建立一个四边形。

    24224620_iKyJ.png

    private short[] indices = { 0, 1, 2, 0, 2, 3 };

    为了提升一点性能,我们把这个数组放到一个byteBuffer中:

    // short是两个字节,所以应该是数组长度乘以2
    ByteBuffer ibb = ByteBuffer.allocateDirect(indices.length * 2);
    ibb.order(ByteOrder.nativeOrder());
    ShortBuffer indexBuffer = ibb.asShortBuffer();
    indexBuffer.put(indices);
    indexBuffer.position(0);

    渲染

    现在我们可以在屏幕上绘制些东西了。这有两个函数可以用来绘制,我们需要决定一下使用哪一个。

    这两个函数分别是:

    public abstract void glDrawArrays(int mode, int first, int count)

    glDrawArrays会按照我们在顶点缓冲里指定的顺序绘制顶点们。

    public abstract void glDrawElements(int mode, int count, int type,
                                        Buffer indices)

    glDrawElements需要多一点的信息才能绘制。他需要知道顶点的绘制顺序,还需要知道顶点的顺序缓冲indicesbuffer。

    由于我们已经定义了indicesBuffer,所以我想你应该知道我们要使用哪个方法了。

    这两个方法的相同之处在于他们都需要知道要绘制什么,即最原始的东西(图元)。由于绘制这些顶点的方式也有好几种,而出于我们对程序的调试需要,最好也了解一下。下面我将会介绍几种。

    图元类型

    GL_POINTS

    在屏幕上绘制独立的点

    24224621_j2jc.jpg

    GL_LINE_STRIP

        一系列连起来的线段

    24224622_RSvK.jpg

    GL_LINE_LOOP

    和上面的类似,但是多了一条连起起点和终点的线(封闭)

    24224624_y5Li.jpg

    GL_LINES

    每两个点连接成线段,互相独立。

    24224627_d5mi.jpg

    GL_TRIANGLES

    每三个点组成一个三角形。

    24224628_LxVo.jpg

    GL_TRIANGLE_STRIP

    按照顶点先v0,v1,v2,然后v2,v1,v3(注意顺序),接着v2,v3,v4。。。,绘制一系列的三角形。这个顺序是保证所有的三角形都是按照同一个方向绘制出来的,这样这些三角形就能正确的组成一个平面的一部分。

    24224629_4NEK.jpg

    GL_TRIANGLE_FAN

    和上面类似,除了绘制顺序变成v0,v1,v2,然后是v0,v2,v3,接下来是v0,v3,v4。。。。

    24224630_MPKs.jpg

    我认为GL_TRIANGLES是最容易的,所以接下来我们就使用它了。

    组装代码

    现在让我们把四边形的代码放到一个类中。

    package se.jayway.opengl.tutorial;
    
    import java.nio.ByteBuffer;
    import java.nio.ByteOrder;
    import java.nio.FloatBuffer;
    import java.nio.ShortBuffer;
    
    import javax.microedition.khronos.opengles.GL10;
    
    public class Square {
    	// 顶点.
    	private float vertices[] = {
    		      -1.0f,  1.0f, 0.0f,  // 0, Top Left
    		      -1.0f, -1.0f, 0.0f,  // 1, Bottom Left
    		       1.0f, -1.0f, 0.0f,  // 2, Bottom Right
    		       1.0f,  1.0f, 0.0f,  // 3, Top Right
    		};
    
    	// 顶点连接的顺序
    	private short[] indices = { 0, 1, 2, 0, 2, 3 };
    
    	// 顶点缓冲.
    	private FloatBuffer vertexBuffer;
    
    	// 顺序缓冲.
    	private ShortBuffer indexBuffer;
    
    	public Square() {
    		// 一个float是4字节,所以我们申请缓冲的大小为数组长度乘以4
    		ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
    		vbb.order(ByteOrder.nativeOrder());
    		vertexBuffer = vbb.asFloatBuffer();
    		vertexBuffer.put(vertices);
    		vertexBuffer.position(0);
    
    		// 一个short是两个字节,申请缓冲大小为数组长度乘以2
    		ByteBuffer ibb = ByteBuffer.allocateDirect(indices.length * 2);
    		ibb.order(ByteOrder.nativeOrder());
    		indexBuffer = ibb.asShortBuffer();
    		indexBuffer.put(indices);
    		indexBuffer.position(0);
    	}
    
    	/**
    	 * 绘制我们的四边形到屏幕上
    	 * @param gl
    	 */
    	public void draw(GL10 gl) {
    		// 逆时针
    		gl.glFrontFace(GL10.GL_CCW);
    		// 开启面裁剪.
    		gl.glEnable(GL10.GL_CULL_FACE);
    		// 指定要被裁剪的面——背面
    		gl.glCullFace(GL10.GL_BACK);
    
    		// 开启顶点数组状态,以便我们读写顶点信息
    		gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
    		// 指定顶点坐标数据的位置和格式
    		
    		gl.glVertexPointer(3, GL10.GL_FLOAT, 0,
                                     vertexBuffer);
    
    		gl.glDrawElements(GL10.GL_TRIANGLES, indices.length,
    				  GL10.GL_UNSIGNED_SHORT, indexBuffer);
    
    		// 禁用顶点数组
    		gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
    		// 禁用裁剪
    		gl.glDisable(GL10.GL_CULL_FACE);
    	}
    
    }

    我们需要在Renderer中初始化我们的square类。

    // 初始化square.
    Square square = new Square();

    然后在绘制方法中调用square的绘制方法:

    public void onDrawFrame(GL10 gl) {
    		// 清除平面和深度缓存
    		gl.glClear(GL10.GL_COLOR_BUFFER_BIT |
                               GL10.GL_DEPTH_BUFFER_BIT);
    
    		// 绘制四边形
    		square.draw(gl); // ( NEW )
    }

    如果你现在运行这个程序,会发现平面还是一片漆黑。为什么?因为opengl ES渲染时默认从当前位置渲染,默认是(0,0,0),和视口(这里为眼睛或者镜头最好-译者注)位置一致。而且opengl ES不会渲染离视口很近的东西。解决方法是在绘制之前,先把绘图位置向屏幕里面移动一点。

    // 向屏幕里移动4个单位
    gl.glTranslatef(0, 0, -4);

    在下一篇教程中,我会介绍不同的平移方式。

    再次运行程序,你会看到这个四边形,但是很快就向远处移动直到消失。opengl ES不会重置两帧之间的绘制点,所以你必须自己来做这事。

    // 替换当前的矩阵为初始矩阵
    gl.glLoadIdentity();

    现在你运行这个应用可以看到四边形在固定的位置上了。

    引用

    这篇教程引用如下文献:

    Android Developers

    OpenGL ES 1.1 Reference Pages

    你可以下载教程的源码:Tutorial_Part_II

    你也可以检出代码:code.google.com

    前一篇教程:安卓opengl ES教程之一——初始化view

    后一篇教程:安卓opengl ES教程之三——变换

    转载于:https://my.oschina.net/tnjin/blog/550738

    展开全文
  • 为什么80%的码农都做不了架构师?>>>   ...我将会写一个系列的关于安卓opengl ES的教程。...安卓opengl ES教程之二——创建多边形 转载于:https://my.oschina.net/tnjin/blog/550187
  • 我有一个预感,看了我前几篇教程的人,可能会问我:这是一系列3D教程,为什么讲的都是2D的事呢?那么在接下来这篇教程中,我们来创建一些3D的渲染网格。这也是后面的教程所需要的准备。 在当初我开始学习openGL的...
  • 我已经在OpenGL ES 2.0 for android上开始了一个新的更新系列教程。请查看:OpenGL ES 2.0 我将在Android手机上编写一些关于使用OpenGL ES教程OpenGL ES的理论在不同的设备上是相同的,因此将它们转换到另一个...
  • 原文转自:http://www.cnblogs.com/sunnychuh/archive/2011/07/20/2112110.html官方网站:http://www.khronos.org/opengles博客链接:http://www.cnblogs.com/dwinter/安卓巴士:...
  • 本文转载自安卓巴士。
  • OpenGLES 绘制三角形

    2020-03-23 00:52:53
    需要用到OpenGLES ,所以在安卓开发的指导文档里学习。但是在学习第一个绘制三角形时,一切流程似乎都是对的,但是就是不能够正常绘制三角形,前后卡了快三个钟,一开始对照官方文档每行代码一一排查都没有找到,...
  • 绘制一个三角形 正如我们学习Java、C++等编程语言时...接下来我们就按照上述所说的渲染过程,讲解一下如何通过OpenGL ES的API在Android手机上显示出一个三角形。 在Demo中我们创建一个 TriangleActivity&...
  • 首先下载eclipse和cdt。我的版本依次是:Version: Indigo Service Release 2和Version: 1.0.0.201202111925,再下载windows的ndk,我使用的是android-ndk-r9d...复杂,今天给一个最爽的编译教程。 前面的cdt插件怎么...
  • 大多数教程都是画三角锥、立方体,而我的需求是以画一个坐标系为基础,类似图一 首先,要确定想画顶点 //定义XYZ坐标 float xyzVertices[] = new float[]{ -0.6f, 0f, 0f,//x轴起点 0.6f, 0f, 0f,//X轴的...
  • 导语Android 音视频开发这块目前的确没有比较系统的教程或者书籍,网上的博客文章也都是比较零散的。只能通过一点点的学习和积累把这块的知识串联积累起来。音视频的开发,往往是比较难的,而这个比较难的技术就是...
  • 内含《 OpenGL ES应用开发实践指南 Android卷》《Android C++高级编程:使用NDK_Onur Cinar, 于红》《Android OpenGL ES 简明开发教程》《Android_OpenGL_ES_book》《OpenGL ES 2.0游戏与图形编程:适用于iOS和...
  • 在最新的Android 4.0.3 R2模拟器中,已经加入了GPU支持,可以支持OpenGL ES 2.0标准,让开发者可以借助模拟器来测试自己的OpenGL游戏。在去年新增了摄像头支持之后,现在的新版模拟器也加入了包括多点触摸输入设备的...
  • 在最新的Android 4.0.3 R2模拟器中,已经加入了GPU支持,可以支持OpenGL ES 2.0标准,让开发者可以借助模拟器来测试自己的OpenGL游戏。在去年新增了摄像头支持之后,现在的新版模拟器也加入了包括多点触摸输入设备的...
  •  + Optimized graphics: 包含一个自定义的2D图形库和基于OpenGL ES 1.0 标准的3D实现。  + SQLite: 数据库  + Media support: 通用的音频,视频和对各种图片格式的支持(MPEG4, H.264, MP3, AAC, AMR, JPG, PNG, ...
  • 如今说到什么智能手机最火,那很多人肯定会说android系统的手机。如果你还没有Android手机,又想买一部试试,可是...在最新的Android 4.0.3 R2模拟器中,已经加入了GPU支持,可以支持OpenGL ES 2.0标准,让开发者可...
  • xp装安卓

    2012-10-10 20:31:44
    安卓模拟器Android SDK4.0.3 R2安装完整图文教程 在最新的Android 4.0.3 R2模拟器中,已经加入了GPU支持,可以支持OpenGL ES 2.0标准,让开发者可以借助模拟器来测试自己的OpenGL游戏。在去年新增了摄像头支持...

空空如也

空空如也

1 2
收藏数 37
精华内容 14
关键字:

安卓opengles教程