文档章节

【翻译】安卓 opengl ES教程之六——纹理

tnjin
 tnjin
发布于 2015/12/22 22:23
字数 2043
阅读 514
收藏 5

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

载入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),如下左图。而下右图所示的是我们如何建立平面。

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

我们把映射放进一个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 };

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

回到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组合结果示意图:

这是我们如何去使用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

© 著作权归作者所有

tnjin

tnjin

粉丝 27
博文 36
码字总数 28535
作品 0
海淀
Android工程师
私信 提问
OpenGL实现物体动画和视频特效

OpenGL实现视频的水印、滤镜?OpenGL实现视频的剪裁、旋转? 2D/3D物体的 旋转,平移,缩放? OpenGL图片滤镜与视频滤镜? 矩阵(Matrix)是一个按照长方阵列排列的复数或实数集合,最早来自于方...

shareus
2018/04/24
0
0
Android 使用 OpenGL ES 绘制图片

关于 OpenGL ES 的介绍,请先看上篇:Android 使用 OpenGL ES 绘制三角形。 1. 纹理介绍 使用 OpenGL ES 绘制简单的几何形状还不够,OpenGL 更多地是用来显示而纹理图像,比如本地图片、相机...

落英坠露
05/02
0
0
Android图形---OpenGL(二)

本文译自:http://developer.android.com/guide/topics/graphics/opengl.html OpenGL 包 一旦使用GLSurfaceView和GLSurfaceView.Renderer类给OpenGL建立了一个View容器,那么就可以开始使用以...

长平狐
2012/10/16
81
0
Android图形---OpenGL(六)

本文译自:http://developer.android.com/guide/topics/graphics/opengl.html#compatibility OpenGL版本和设备兼容性 Android系统从1.0开始就支持OpenGL ES 1.0和1.1规范,从Android2.2(API......

长平狐
2012/10/16
836
0
Android 使用 OpenGL ES 绘制三角形

1. OpenGL ES 简介 OpenGL 是一个跨平台的图形 API,为 3D 图形处理硬件制定了一个标准软件接口。OpenGL ES 是为嵌入式设备设计的 OpenGL 规范,Android 提供了对 OpenGL ES 的支持。 OpenGL...

落英坠露
05/02
0
0

没有更多内容

加载失败,请刷新页面

加载更多

老也有错?35岁程序员是一道坎,横亘在每个技术职场人的心中

随着互联网的高速发展变革,大龄恐惧症越来越多地在技术圈被人讨论。很多程序员在工作5-10年以后,都会开始思考5年、10年甚至更久以后的自己,会是怎样一种生活工作状态,以及是否会被时代抛...

我最喜欢三大框架
34分钟前
2
0
今日头条算法原理详解全集,值得收藏!

今天,算法分发已经是信息平台、搜索引擎、浏览器、社交软件等几乎所有软件的标配,但同时,算法也开始面临质疑、挑战和误解。今日头条的推荐算法,从 2012 年 9月第一版开发运行至今,已经经...

骚年锦时
46分钟前
4
0
零拷贝:用户态视角

在Linux系统越来越多的人听说过所谓的零拷贝技术,但是我经常遇到很多对这个名词没有完全理解的人。因此,我决定写一些文章,深挖这个问题,希望能揭开这个有用的特性。在这篇文章,我们从用...

凌渡
58分钟前
1
0
以太坊中文文档翻译-区块

本文原文链接 点击这里获取Etherscan API 中文文档(完整版) 完整内容排版更好,推荐读者前往阅读。 区块(Blocks) 区块相关的 API,接口的参数说明请参考Etherscan API 约定, 文档中不单独...

Tiny熊
今天
2
0
Linux 内核的一个问题

是virtio 驱动,但是没有启动 virtio-mmio virtio-mmio.0: Failed to enable 64-bit or 32-bit DMA. Trying to continue, but this might not work.....[ 1.047924] md: ... autorun......

MtrS
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部