正文  图形图像 > OpenGL/3D >

adnroid 3D 之纹理篇

之前的例子都是纯色的3D模型。今天我们就开始给这些模型贴上华丽丽的纹理,让他们看起来更加接近我们印象中的真实物体。功能启动为使用纹理,我们需要打开OpenGL的一些开关以启动我们需要的一些功能:gl.glEnabl......

之前的例子都是纯色的3D模型。今天我们就开始给这些模型贴上华丽丽的纹理,让他们看起来更加接近我们印象中的真实物体。

功能启动

为使用纹理,我们需要打开OpenGL的一些开关以启动我们需要的一些功能:

gl.glEnable(GL10.GL_TEXTURE_2D);

这个调用是必不可缺的;如果你没有打开此功能,那么你就无法将图像映射到多边形上。它可以在需要时打开和关闭,通常在初始化时打开。

创建纹理

生成纹理

OpenGL 中的纹理通过一个唯一号引用,通过函数 glBindTexture() 实现。你 可以自己指定这个唯一号,或者通过调用 glGenTextures () 函数生成一个唯一 号。

int[] tmp_tex = new int[1];//尽管只有一个纹理,但使用一个元素的数组

//glGenTextures(申请个数,存放数组,偏移值)

gl.glGenTextures(1, tmp_tex, 0); //向系统申请可用的,用于标示纹理的ID

int texture = tmp_tex[0];

纹理绑定

>在为纹理生成名称后,在为纹理提供图像数据之前,我们必须 绑定纹理。绑定使得指定纹理处于活动状态。一次只能激活一个纹理。活动的或“被绑定”的纹理是绘制多边形时使用的纹理,也是新纹理数据将加载其上纹理,所以在提供图像数据前必须绑定纹理。

gl.glBindTexture(GL10.GL_TEXTURE_2D, texture);

>绑定纹理数据,传入指定图片

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

纹理限制

> 用于纹理的图像宽和高必须为乘方,比如 2, 4, 8, 16, 32, 64, 128, 256, 512, 或 1024。例如图像可能为 64×128 或 512×512。

>

纹理坐标

>当纹理映射启动后绘图时,你必须为OpenGL ES提供其他数据,即顶点数组中各顶点的 纹理坐标。纹理坐标定义了图像的哪一部分将被映射到多边形。它的工作方式有点奇怪。与我们顶点坐标方向不一致,假设你有一个正方形或长方形的纹理,其左下角为二维平面的原点,高和宽的单位为一。像这样:

texture_coords

>这就是我们的“纹理坐标系统”,不使用x 和 y 来代表二维空间,我们使用 s 和 t 作为纹理坐标轴,但原理上是一样的。

除了 s 和 t 轴外,被映射的纹理在多边形同样有两个轴,它们称为 u 和 v轴。这是源于许多3D图像程序中的UV 映射 的术语。>

>

uvst

>好,我们明白了纹理坐标系统,我们现在讨论怎样使用这些纹理坐标。当我们指定顶点数组中的顶点时,我们需要在另一个数组中提供纹理坐标,它称为纹理坐标数组。 每个顶点,我们使用float 来指定顶点在上图所示坐标系统的位置。让我们看看一个可能是最为简单的例子,将整个图像映射到一个由三角形条组成的正方形上。首先,我们创建一个由四个顶点组成的顶点数组:

trianglestrip

>现在将两个框图叠在一起,所使用的坐标数组的值变得很明显:

overlay

>将其转化为坐标数组:

float texCoords[] = new float[]{0.0f, 1.0f,

1.0f, 1.0f,

0.0f, 0.0f,

1.0f, 0.0f };

纹理坐标对应纹理在物体上的图片位置和方向。

我们还需要传递纹理坐标给系统

gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, texBuff); gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); //开启纹理坐标数组

我们今天实例的效果:

代码:

public class CubeRenderer implements Renderer {

Bitmap bmp;

float box[] = new float[] {

// FRONT

-0.5f, -0.5f, 0.5f,

0.5f, -0.5f, 0.5f,

-0.5f, 0.5f, 0.5f,

0.5f, 0.5f, 0.5f,

// BACK

-0.5f, -0.5f, -0.5f,

-0.5f, 0.5f, -0.5f,

0.5f, -0.5f, -0.5f,

0.5f, 0.5f, -0.5f,

// LEFT

-0.5f, -0.5f, 0.5f,

-0.5f, 0.5f, 0.5f,

-0.5f, -0.5f, -0.5f,

-0.5f, 0.5f, -0.5f,

// RIGHT

0.5f, -0.5f, -0.5f,

0.5f, 0.5f, -0.5f,

0.5f, -0.5f, 0.5f,

0.5f, 0.5f, 0.5f,

// TOP

-0.5f, 0.5f, 0.5f,

0.5f, 0.5f, 0.5f,

-0.5f, 0.5f, -0.5f,

0.5f, 0.5f, -0.5f,

// BOTTOM

-0.5f, -0.5f, 0.5f,

-0.5f, -0.5f, -0.5f,

0.5f, -0.5f, 0.5f,

0.5f, -0.5f, -0.5f,

};

float lightAmbient[] = new float[] { 0.5f, 0.5f, 0.6f, 1.0f }; //环境光

float lightDiffuse[] = new float[] { 0.6f, 0.6f, 0.6f, 1.0f };//漫反射光

float[] lightPos = new float[] {0,0,3,1}; //光源位置

/**

* 因为进行光照处理,你必须告知系统你定义的模型各个面的方向,以便系统计算光影情况,方向的描述是通过向量点来描述的

*/

float norms[] = new float[] { //法向量数组,用于描述个顶点的方向,以此说明各个面的方向

// FRONT

0f, 0f, 1f, //方向为(0,0,0)至(0,0,1)即Z轴正方向

0f, 0f, 1f,

0f, 0f, 1f,

0f, 0f, 1f,

// BACK

0f, 0f, -1f,

0f, 0f, -1f,

0f, 0f, -1f,

0f, 0f, -1f,

// LEFT

-1f, 0f, 0f,

-1f, 0f, 0f,

-1f, 0f, 0f,

-1f, 0f, 0f,

// RIGHT

1f, 0f, 0f,

1f, 0f, 0f,

1f, 0f, 0f,

1f, 0f, 0f,

// TOP

0f, 1f, 0f,

0f, 1f, 0f,

0f, 1f, 0f,

0f, 1f, 0f,

// BOTTOM

0f, -1f, 0f,

0f, -1f, 0f,

0f, -1f, 0f,

0f, -1f, 0f

};

float texCoords[] = new float[] { //纹理坐标对应数组

// FRONT

0.0f, 0.0f,

1.0f, 0.0f,

0.0f, 1.0f,

1.0f, 1.0f,

// BACK

1.0f, 0.0f,

1.0f, 1.0f,

0.0f, 0.0f,

0.0f, 1.0f,

// LEFT

1.0f, 0.0f,

1.0f, 1.0f,

0.0f, 0.0f,

0.0f, 1.0f,

// RIGHT

1.0f, 0.0f,

1.0f, 1.0f,

0.0f, 0.0f,

0.0f, 1.0f,

// TOP

0.0f, 0.0f,

1.0f, 0.0f,

0.0f, 1.0f,

1.0f, 1.0f,

// BOTTOM

1.0f, 0.0f,

1.0f, 1.0f,

0.0f, 0.0f,

0.0f, 1.0f

};

FloatBuffer cubeBuff;

FloatBuffer normBuff;

FloatBuffer texBuff;

float xrot = 0.0f;

float yrot = 0.0f;

/**

* 将float数组转换存储在字节缓冲数组

* @param arr

* @return

*/

public FloatBuffer makeFloatBuffer(float[] arr) {

ByteBuffer bb = ByteBuffer.allocateDirect(arr.length * 4);//分配缓冲空间,一个float占4个字节

bb.order(ByteOrder.nativeOrder()); //设置字节顺序, 其中ByteOrder.nativeOrder()是获取本机字节顺序

FloatBuffer fb = bb.asFloatBuffer(); //转换为float型

fb.put(arr); //添加数据

fb.position(0); //设置数组的起始位置

return fb;

}

public CubeRenderer(Context c) {

// TODO Auto-generated constructor stub

cubeBuff = makeFloatBuffer(box);//转换float数组

normBuff = makeFloatBuffer(norms);

texBuff = makeFloatBuffer(texCoords);

bmp = BitmapFactory.decodeResource(c.getResources(), R.drawable.face);

}

protected void init(GL10 gl) {

gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);//设置清屏时背景的颜色,R,G,B,A

gl.glEnable(GL10.GL_LIGHTING); //启用光照

gl.glEnable(GL10.GL_LIGHT0); //开启光源0

//设置光照参数,也可以使用默认的,不设置

gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_AMBIENT, lightAmbient, 0);

gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_DIFFUSE, lightDiffuse, 0);

gl.glLightfv(GL10.GL_LIGHT0, GL10.GL_POSITION, lightPos, 0);

gl.glNormalPointer(GL10.GL_FLOAT, 0, normBuff);

gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);

//使用纹理步骤:

// 1.开启贴图

gl.glEnable(GL10.GL_TEXTURE_2D);

// 2.生成纹理ID

int[] tmp_tex = new int[1];//尽管只有一个纹理,但使用一个元素的数组

//glGenTextures(申请个数,存放数组,偏移值)

gl.glGenTextures(1, tmp_tex, 0); //向系统申请可用的,用于标示纹理的ID

int texture = tmp_tex[0];

//3.绑定纹理,使得指定纹理处于活动状态。一次只能激活一个纹理

gl.glBindTexture(GL10.GL_TEXTURE_2D, texture);

//4.绑定纹理数据,传入指定图片

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

//5.传递各个顶点对应的纹理坐标

gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, texBuff);

gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); //开启纹理坐标数组

//6.设置纹理参数 (可选)

/*下面的两行参数告诉OpenGL在显示图像时,当它比放大得原始的纹理大

*( GL_TEXTURE_MAG_FILTER )或缩小得比原始得纹理小( GL_TEXTURE_MIN_FILTER )

*时OpenGL采用的滤波方式。通常这两种情况下我都采用 GL_LINEAR 。这使得纹理从很远处

*到离屏幕很近时都平滑显示。使用 GL_LINEAR 需要CPU和显卡做更多的运算。如果您的机器很慢,

*您也许应该采用 GL_NEAREST 。过滤的纹理在放大的时候,看起来马赛克的很。您也可以结合这

*两种滤波方式。在近处时使用 GL_LINEAR ,远处时 GL_NEAREST 。

**/

gl.glTexParameterx(GL10.GL_TEXTURE_2D,GL10.GL_TEXTURE_MAG_FILTER,GL10.GL_LINEAR);

gl.glTexParameterx(GL10.GL_TEXTURE_2D,GL10.GL_TEXTURE_MIN_FILTER,GL10.GL_LINEAR);

gl.glEnable(GL10.GL_DEPTH_TEST); //启用深度缓存

gl.glEnable(GL10.GL_CULL_FACE); //启用背面剪裁

gl.glClearDepthf(1.0f); // 设置深度缓存值

gl.glDepthFunc(GL10.GL_LEQUAL); // 设置深度缓存比较函数,GL_LEQUAL表示新的像素的深度缓存值小于等于当前像素的深度缓存值(通过gl.glClearDepthf(1.0f)设置)时通过深度测试

gl.glShadeModel(GL10.GL_SMOOTH);// 设置阴影模式GL_SMOOTH

}

@Override

public void onSurfaceCreated(GL10 gl, EGLConfig config) {

// TODO Auto-generated method stub

init(gl);

}

@Override

public void onSurfaceChanged(GL10 gl, int w, int h) {

// TODO Auto-generated method stub

gl.glViewport(0, 0, w, h); //设置视窗

gl.glMatrixMode(GL10.GL_PROJECTION); // 设置投影矩阵

gl.glLoadIdentity(); //设置矩阵为单位矩阵,相当于重置矩阵

GLU.gluPerspective(gl, 45.0f, ((float) w) / h, 0.1f, 10f);//设置透视范围

}

@Override

public void onDrawFrame(GL10 gl) {

// TODO Auto-generated method stub

gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);// 清除屏幕和深度缓存

gl.glMatrixMode(GL10.GL_MODELVIEW); //切换至模型观察矩阵

gl.glLoadIdentity();// 重置当前的模型观察矩阵

GLU.gluLookAt(gl, 0, 0, 3, 0, 0, 0, 0, 1, 0);//设置视点和模型中心位置

gl.glVertexPointer(3, GL10.GL_FLOAT, 0, cubeBuff);//设置顶点数据

gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);

gl.glRotatef(xrot, 1, 0, 0); //绕着(0,0,0)与(1,0,0)即x轴旋转

gl.glRotatef(yrot, 0, 1, 0);

gl.glColor4f(1.0f, 0, 0, 1.0f); //设置颜色,红色

gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4); //绘制正方型FRONT面

gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 4, 4);

gl.glColor4f(0, 1.0f, 0, 1.0f);

gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 8, 4);

gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 12, 4);

gl.glColor4f(0, 0, 1.0f, 1.0f);

gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 16, 4);

gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 20, 4);

xrot += 0.5f;

yrot += 0.5f;

}

}