Android Studio OpenGL ES绘制三棱锥/四面体的多纹理贴图 每个面使用一张图片渲染

本文参考了王刚的《疯狂Android讲义(第3版)》P554-P559

要求:利用OpenGL ES绘制一个三棱锥,并对每个面进行纹理贴图,每个面使用不同的图片进行渲染。

环境:Android Studio 3.6.1,gradle版本为5.6.4,OpenGL使用1x版本,新建工程后需在res/drawable目录下放4张图片img1、img2、img3、img4(建议图片长宽均是2的n次方,如果不符合则系统会自动调节,我使用的图片长宽是256×256)。

运行结果:

三棱锥多纹理贴图
三棱锥多纹理贴图运行结果

 主要步骤:

1.在MainActivity.java文件自定义内部公共类MyRenderer实现接口Renderer(也可新建Java文件来实现)。

2.定义三棱锥的顶点坐标、纹理坐标、几个需要用到的Buffer及其他数据等。

// 定义三棱椎的4个顶点
private float[] taperVertices = new float[]{
         0.0f,  0.5f,  0.0f, //0
        -0.5f, -0.5f, -0.2f, //1
         0.5f, -0.5f, -0.2f, //2
         0.0f, -0.2f,  0.2f  //3
};
// 定义纹理贴图的坐标数据
private float[] taperTextures = {
         0.5000f, 0.0000f, 0.0000f, 1.0000f, 1.0000f, 1.0000f,
         0.0000f, 1.0000f, 1.0000f, 1.0000f, 0.5000f, 0.0000f,
         0.0000f, 1.0000f, 1.0000f, 1.0000f, 0.5000f, 0.0000f,
         1.0000f, 1.0000f, 0.0000f, 1.0000f, 0.5000f, 0.0000f,
};
private Context context;
private FloatBuffer taperVerticesBuffer;
private ByteBuffer[] taperFacts_indices;
private FloatBuffer taperTexturesBuffer;
// 定义本程序所使用的纹理
private int[] textures = new int[4];
// 旋转角度
float rotate = 0;

定义纹理坐标数据要注意:Android使用的OpenGL ES的纹理坐标系跟官方的OpenGL纹理坐标系统不一样。官方的OpenGL ES纹理坐标为左下角是(0, 0)右上角是(1, 1);而Android的纹理坐标左上角为(0, 0)右下角为(1, 1)。

3.实现MyRenderer(Context main)方法

public MyRenderer(Context main) {
    this.context = main;
    // 将三棱椎的顶点位置数据数组包装成FloatBuffer
    taperVerticesBuffer = floatBufferUtil(taperVertices);
    // 将三棱椎的4个面的数组indices[1:4]包装成ByteBuffer并加入一个数组中
    ByteBuffer indices1 = ByteBuffer.wrap(new byte[]{
                    0, 1, 2,
                    0, 0, 0,
                    0, 0, 0,
                    0, 0, 0
    });
    ByteBuffer indices2 = ByteBuffer.wrap(new byte[]{
                    0, 0, 0,
                    0, 1, 3,
                    0, 0, 0,
                    0, 0, 0
    });
    ByteBuffer indices3 = ByteBuffer.wrap(new byte[]{
                    0, 0, 0,
                    0, 0, 0,
                    1, 2, 3,
                    0, 0, 0
    });
    ByteBuffer indices4 = ByteBuffer.wrap(new byte[]{
                    0, 0, 0,
                    0, 0, 0,
                    0, 0, 0,
                    0, 2, 3
    });
    taperFacts_indices = new ByteBuffer[]{indices1, indices2, indices3, indices4};
    // 将立方体的纹理贴图的坐标数据包装成FloatBuffer
    taperTexturesBuffer = floatBufferUtil(taperTextures);
}

4.纹理映射功能默认是关闭的,所以需要在onSurfaceCreated内启用纹理映射功能。

// 启用2D纹理贴图
gl.glEnable(GL10.GL_TEXTURE_2D);
// 装载纹理
loadTexture(gl);

loadTexture()是自定义的一个装载纹理的方法,实现方法如下:

private void loadTexture(GL10 gl) {
    Bitmap[] bitmap = new Bitmap[4];

    try {
        // 加载位图
        bitmap[0] = BitmapFactory.decodeResource(context.getResources(),
              R.drawable.img1);
        bitmap[1] = BitmapFactory.decodeResource(context.getResources(),
              R.drawable.img2);
        bitmap[2] = BitmapFactory.decodeResource(context.getResources(),
              R.drawable.img3);
        bitmap[3] = BitmapFactory.decodeResource(context.getResources(),
              R.drawable.img4);

        // 创建纹理对象,指定生成N个纹理(第一个参数指定生成几个纹理)
        // textures数组将负责存储所有纹理的代号
        gl.glGenTextures(4, textures, 0);
        for (int i = 0; i < textures.length; i++) {
            // 通知OpenGL将texture纹理绑定到GL10.GL_TEXTURE_2D目标中
            gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[i]);
            // 设置纹理被缩小(距离视点很远时被缩小)时的滤波方式
            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);
            // 设置在横向、纵向上都是平铺纹理
            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);
            // 加载位图生成纹理
            GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap[i], 0);
        }
    } finally {
        // 生成纹理之后,回收位图
        for (int i = 0; i < bitmap.length; i++) 
            bitmap[i].recycle();
    }
}

其中几个函数的含义如下:

void glGenTextures(int n,int[] textures,int offset)  //创建纹理对象

  • n:需要生成的纹理对象个数;
  • textures:保存生成所有纹理对象的数组;
  • offset:纹理对象数组的偏移(生成的纹理从数组什么位置开始赋值)。

void glBindTexture (int target, int texture)  //绑定纹理,绑定后的纹理才处于活动状态

  • target:纹理类型,使用 2D 纹理则参数设为GL_TEXTURE_2D;
  • texture:纹理对象的 ID。 

void glTexParameterf(int target,int pname,float param)  //对绑定的纹理进行基本属性设置

  • target:纹理类型,表示我们激活的纹理单元对应了什么样的操作类型,比如GL_TEXTURE_1D、GL_TEXTURE_2D等;
  • pname:属性名称;
  • param:属性值。

void texImage2D (int target, int level, Bitmap bitmap, int border)  //指定纹理

  • target:操作的目标类型,本程序均设为 GL_TEXTURE_2D 即可
  • level:纹理的级别,表示多级分辨率的纹理图象的级数。若只有一种分辨率,level为0。
  • bitmap:使用的图像
  • border:边框,一般设为0

Bitmap decodeResource(Resources res, int id)  //加载位图

  • res:包含图像数据的Resources对象
  • id:图像数据的资源id

 5.绘制图像及纹理

//绘制图形的方法
@Override
public void onDrawFrame(GL10 gl) {
    // 清除屏幕缓存和深度缓存
    gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
    // 启用顶点坐标数据
    gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
    // 启用贴图坐标数组数据
    gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
    // 设置当前矩阵模式为模型视图。
    gl.glMatrixMode(GL10.GL_MODELVIEW);
    gl.glLoadIdentity();
    // 把绘图中心移入屏幕2个单位
    gl.glTranslatef(0f, 0.0f, -2.0f);
    // 旋转图形
    gl.glRotatef(rotate, -0.1f, -0.1f, 0.05f); // 旋转总坐标系
    // 设置顶点的位置数据
    gl.glVertexPointer(3, GL10.GL_FLOAT, 0, taperVerticesBuffer);
    // 设置贴图的坐标数据
    gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, taperTexturesBuffer);  // ②
    //使用for循环给三棱锥每个面都贴上相应的纹理图
    for(int i = 0; i < taperFacts_indices.length; i++) {
        // 绑定纹理,执行纹理贴图
        gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[i]);
        // 按taperFacts_indices[i]指定的面绘制三角形
        gl.glDrawElements(GL10.GL_TRIANGLES, 3*(i+1), GL10.GL_UNSIGNED_BYTE, taperFacts_indices[i]);
    }
    // 绘制结束
    gl.glFinish();
    // 禁用顶点、纹理坐标数组
    gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
    gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
    //角度+1使绘制的图形旋转显示其他面
    rotate += 1;
}

void glTexCoordPointer(int size, int type, int stride, Buffer pointer)  //设置顶点数组为纹理坐标缓存

  • size:纹理顶点坐标的分量个数,本程序使用纹理坐标的2个分量(s, t)映射顶点坐标的3个分量(x, y, z);
  • type:纹理坐标的数据类型,short, int, float, double都可以;
  • stride:位图的宽度,可以理解为相邻的两个纹理之间跨多少个字节,一般为0,因为一般不会在纹理中再添加其他的信息;
  • pointer:存放纹理坐标的数组。

 总结:如果想多面体各个面的纹理不一样,则在画每个面之前要加上void glBindTexture (int target, int texture)函数绑定纹理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值