Android OpenGL添加纹理

转载请注明出处:【huachao1001的简书:http://www.jianshu.com/users/0a7e42698e4b/latest_articles】

上一篇文章【Android OpenGL添加光照和材料属性 】我们已经学了如何为3D模型添加光照和材料属性,使得模型看起来更有立体感。今天我们学习如何为3D模型贴上纹理,使得模型看起来更真实!目前我在网上没有找到带有纹理图片的STL模型文件,如果随便贴一张图片上去的话并不好看,看起来不会很真实。好在手头上现在有2个带有纹理的STL格式文件,虽然这两个模型看起来有点残缺,但是不影响我们学习如何贴纹理。先看看效果~.

图片1如下:

原图1

对应的模型:

绑定纹理

图片2如下:


原图2

对应的模型:

绑定纹理

注意,本文的讲解是建立在《Android OpenGL显示任意3D模型文件 》之上,请务必先看这篇文章再往下读(当然了,如果你已经有一定的Android OpenGL基础,可以不用看)。好啦,看完效果后,我们开始学习吧!

1 相关基础

1.1 贴纹理原理简单概述

其实贴纹理的原理非常简单,就是给每个三角形贴上图片即可。那么如何给三角形贴图片呢?我们知道,既然是给三角形贴图片,那肯定就需要一张图,然后在这张图片上指定三角形的三个顶点对应这张图的位置。这样就可以准确的为这个三角形贴好图片了。

值得注意的是,三角形的三个顶点在图片上的位置取值范围为[0,1]。即以相对图片的宽高比例来计算的。

在加载纹理图片时,OpenGL为每张图片分配好ID,将图片缓存起来。在贴图时,通过ID来查找图片。

1.2 相关API

跟绘制三角形类似,如果需要开启贴纹理功能需要如下代码:

gl.glEnable(GL10.GL_TEXTURE_2D);

对应的关闭为:

gl.glDisable(GL10.GL_TEXTURE_2D);

前面1.1节中,我们提到:在加载纹理图片时,OpenGL为每张图片分配好ID,将图片缓存起来。在贴图时,通过ID来查找图片。因此,在我们开始贴图之前,需要为当前模型绑定好纹理图片的ID:

//根据ID绑定对应的纹理
gl.glBindTexture(GL10.GL_TEXTURE_2D, model.getTextureIds()[0]);

上面代码中,我们看到,在Model实体类中,通过getTextureIds函数来获取ID数组,并取出数组的第一个数据。而Model类是我们自己自定义的实体类,显然不可能在我们的自定义的实体类中“无中生有”出一个ID数组。那么这个ID数组从哪里来?

注意,纹理的ID是保存在一个int[]数组中,数组的第一个元素即为ID。至于为什么用数组来保存,我暂时还没弄清。可能是扩展性更好吧~

关于纹理对应的ID,后面详细说。我们继续往下走,在拿到纹理ID的情况下,如何绘制。首先你需要启用纹理坐标数组:

gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);

然后在绘制三角形之前,将纹理坐标数据设定好:

gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, model.getTextureBuffer());

此时即可绘制纹理。当然了,我们现在只是大致讲讲,更完整更详细的内容第二节谈~。

1.3 pxy文件格式

由于我采用的模型的纹理坐标数据是pxy格式。pxy格式里面保存的是浮点数的集合,即每4个字节为一个数据。每2个浮点数表示一个坐标点,每三个坐标点对应一个三角形在图片中的纹理区域。

1.4 多个模型数据

为什么要提多个模型数据呢?我们知道,三维模型中,自然是包含各个角度的映像图片。一张图片往往很难包含整个模型的纹理信息。因此,我们将一个3D模型分割成多个3D模型,每个模型对应一张纹理图片。理论上说,3张图片即可包含整个模型纹理信息了,即,将一个模型分割成3个3D模型。当然了,分割的越多,纹理图片的构造就越简单(你也可以以一张360°全景纹理图片,但是构造这样的图片成本比较高)。

可能你会说,我们该如何分割模型?每个模型的坐标位置信息我们无法给它分割,因为坐标位置数据是一个数组,是按照三角形顶点的顺序指定的。

请注意一点,我们只负责显示模型,我们不管如何分割,分割这块丢个模型的设计者!因为设计者可以通过相关的3D设计软件轻松的分割。我们只需关注,如何同时显示多个3D模型,并为每个3D模型贴好对应的纹理即可。

2 代码编写

2.1 解析pxy文件

前面我们大致介绍了pxy文件格式,我们知道,pxy保存的就是当前stl文件中三角形顶点在纹理图片上对应的坐标。每个顶点占2个浮点数(对应x、y)。那么我们的解析就非常简单了,在STLReader类中,添加如下函数:

private void parseTexture(Model model, byte[] textureBytes) {
    int facetCount = model.getFacetCount();
    // 三角面个数有三个顶点,一个顶点对应纹理二维坐标
    float[] textures = new float[facetCount * 3 * 2];
    int textureOffset = 0;
    for (int i = 0; i < facetCount * 3; i++) {
        //第i个顶点对应的纹理坐标
        //tx和ty的取值范围为[0,1],表示的坐标位置是在纹理图片上的对应比例
        float tx = Util.byte4ToFloat(textureBytes, textureOffset);
        float ty = Util.byte4ToFloat(textureBytes, textureOffset + 4);

        textures[i * 2] = tx;
        //我们的pxy文件原点是在左下角,因此需要用1减去y坐标值
        textures[i * 2 + 1] = 1 - ty;

        textureOffset += 8;
    }
    model.setTextures(textures);
}

同时,我们需要在Model类中添加纹理相关数据属性,并且添加对应的settergetter函数。代码我就不贴出来了,后面我会上传源码。

现在我们编写好了解析pxy纹理坐标数据,接下来就是把解析stl文件和pxy文件整合在一起的函数,在STLReader中添加:

public Model parseStlWithTexture(InputStream stlInput, InputStream textureInput) throws IOException {
    Model model = parseBinStl(stlInput);
    int facetCount = model.getFacetCount();
    // 三角面片有3个顶点,一个顶点有2个坐标轴数据,每个坐标轴数据是float类型(4字节)
    byte[] textureBytes = new byte[facetCount * 3 * 2 * 4];
    textureInput.read(textureBytes);// 将所有纹理坐标读出来
    parseTexture(model, textureBytes);
    return model;
}

此时,我们的STLReader类就可以通过parseStlWithTexture函数完美的将stl和pxy数据封装到Model对象中了。

2.2 加载纹理图片

前面我们提到了,OpenGL为每张纹理图片生成一个ID。接下来我们看看如何将一个纹理图片加载到OpenGL通道中,并且分配一个ID。首先,我们需要读取纹理图片,生成Bitmap对象。然后调用glGenTextures函数,生成ID,并将此ID保存到Model对象中。此时,我们已经拿到了ID,但是这个ID并没有绑定Bitmap对象。在将IDBitmap绑定之前,需要调用glBindTexture函数,将生成的ID绑定到纹理通道,并且通过glTexParameterf设定当前绑定纹理的相关属性。 最后通过GLUtils.texImage2D函数将Bitmap对象与当前纹理通道绑定,而当前纹理通道已经绑定好了ID,从而达到了ID与纹理的间接绑定。以后使用纹理时,就可以直接通过ID来访问,无需直接访问Bitmap对象了。说了这么多,有点抽象,直接通过代码来的实在:

private void loadTexture(GL10 gl, Model model, boolean isAssets) {
    Log.d("GLRenderer", "绑定纹理:" + model.getPictureName());
    Bitmap bitmap = null;
    try {
        // 打开图片资源
        if (isAssets) {//如果是从assets中读取
            bitmap = BitmapFactory.decodeStream(context.getAssets().open(model.getPictureName()));
        } else {//否则就是从SD卡里面读取
            bitmap = BitmapFactory.decodeFile(model.getPictureName());
        }
        // 生成一个纹理对象,并将其ID保存到成员变量 texture 中
        int[] textures = new int[1];
        gl.glGenTextures(1, textures, 0);
        model.setTextureIds(textures);

        // 将生成的空纹理绑定到当前2D纹理通道
        gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);

        // 设置2D纹理通道当前绑定的纹理的属性
        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);

        // 将bitmap应用到2D纹理通道当前绑定的纹理中
        GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
        gl.glBindTexture(GL10.GL_TEXTURE_2D, 0);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {

        if (bitmap != null)
            bitmap.recycle();

    }
}

2.3 读取多个模型数据

我们前面说过,将一个模型分割成多个模型,因此,我们需要读取多个模型数据,并保存起来。我们在此之前已经学会了读取一个模型数据,那么读取多个模型数据通过for循环即可。为了读取上的方便,我们为将每个模型数据按序命名。另外,我们知道,目前为止,我们的一个模型对应三种格式文件:

  • pxy:三角形对应的纹理坐标
  • stl:三角网数据
  • jpg:纹理图片

为了读取上的方便,我们将同一个模型的这三个文件设为相同的名称,如:1.pxy1.stl1.jpg。各个模型之间按序命名,格式如下图:

命名格式

此时,我们就可以很轻松的读取啦~。在GLRenderer中:

private List<Model> models = new ArrayList<>();

public GLRenderer(Context context) {
    this.context = context;
    try {
        STLReader reader = new STLReader();
        for (int i = 1; i <= 6; i++) {
            Model model = reader.parserStlWithTextureInAssets(context, "chuwang/" + i);

            models.add(model);
        }

    } catch (IOException e) {
        e.printStackTrace();
    }
}

此时我们就完成了将所有的模型数据保存在List<Model>类型的models对象中。

2.4 开始绘制

前面所做的铺垫已经完成,接下来就是最后的绘制了!相比上一篇的代码,我们只需修改onSurfaceCreatedonDrawFrame函数。其实大部分代码都是相同的,只是我们通过for循环的方式,将所有的模型绘制出来而已。

但是有个区别需要注意,就是我们需要获取所有模型在xyz坐标中的最大值最小值,以及所有模型加在一起后的中心点位置。前面我们只有一个模型,很快就算好了。多个模型我们也是很简单,只需根据每个模型的在xyz坐标中的最大值最小值计算即可,详情请看我的附件源码。

我们看看onSurfaceCreated函数,onSurfaceCreated函数需要负责计算所有模型的中心点、所有模型在xyz坐标中的最大值最小值,以及加载所有模型对应的纹理图片:

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    gl.glEnable(GL10.GL_DEPTH_TEST); // 启用深度缓存
    gl.glClearColor(0f, 0f, 0f, 0f);// 设置深度缓存值
    gl.glDepthFunc(GL10.GL_LEQUAL); // 设置深度缓存比较函数
    gl.glShadeModel(GL10.GL_SMOOTH);// 设置阴影模式GL_SMOOTH


    //初始化相关数据
    initConfigData(gl);

}

private void initConfigData(GL10 gl) {
    float r = Util.getR(models);
    mScalef = 0.5f / r;
    mCenterPoint = Util.getCenter(models);

    //为每个模型绑定纹理
    for (Model model : models) {
        loadTexture(gl, model, true);
    }

}

再看看onDrawFrame函数,onDrawFrame函数需要通过for循环的方式,绘制出每个模型:

@Override
public void onDrawFrame(GL10 gl) {
    // 清除屏幕和深度缓存
    gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

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


    //眼睛对着原点看
    GLU.gluLookAt(gl, eye.x, eye.y, eye.z, center.x,
            center.y, center.z, up.x, up.y, up.z);

    //为了能有立体感觉,通过改变mDegree值,让模型不断旋转
    gl.glRotatef(mDegree, 0, 1, 0);

    //将模型放缩到View刚好装下
    gl.glScalef(mScalef, mScalef, mScalef);
    //把模型移动到原点
    gl.glTranslatef(-mCenterPoint.x, -mCenterPoint.y,
            -mCenterPoint.z);


    //===================begin==============================//
    for (Model model : models) {
        //开启贴纹理功能
        gl.glEnable(GL10.GL_TEXTURE_2D);
        //根据ID绑定对应的纹理
        gl.glBindTexture(GL10.GL_TEXTURE_2D, model.getTextureIds()[0]);
        //启用相关功能
        gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);
        gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
        gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);

        //开始绘制
        gl.glNormalPointer(GL10.GL_FLOAT, 0, model.getVnormBuffer());
        gl.glVertexPointer(3, GL10.GL_FLOAT, 0, model.getVertBuffer());
        gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, model.getTextureBuffer());

        gl.glDrawArrays(GL10.GL_TRIANGLES, 0, model.getFacetCount() * 3);

        //关闭当前模型贴纹理,即将纹理id设置为0
        gl.glBindTexture(GL10.GL_TEXTURE_2D, 0);

        //关闭对应的功能
        gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
        gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
        gl.glDisableClientState(GL10.GL_NORMAL_ARRAY);
        gl.glDisable(GL10.GL_TEXTURE_2D);
    }


    //=====================end============================//

}

从代码上也可以看出,相比前几篇文章,代码的修改并不大。细心的童鞋会发现,我这里并没有开启光照、材料属性。主要是我们已经贴好纹理了,并且默认上模型会有光照效果。

最后看看效果吧,其实效果已经在最开始已经看过了,我们再看看

绑定纹理

最后看看源码吧,由于AndroidStudio项目过大,我只上传app/src/main里面内容:http://download.csdn.net/download/huachao1001/9599565

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容

  • 1 相关基础 1.1 贴纹理原理简单概述 其实贴纹理的原理非常简单,就是给每个三角形贴上图片即可。那么如何给三角形...
    CHSmile阅读 1,521评论 0 0
  • 版本记录 前言 OpenGL 图形库项目中一直也没用过,最近也想学着使用这个图形库,感觉还是很有意思,也就自然想着...
    刀客传奇阅读 8,764评论 0 8
  • 纹理(Textures) 我们已经了解到,我们可以为每个顶点使用颜色来增加图形的细节,从而创建出有趣的图像。但是通...
    IceMJ阅读 5,612评论 2 13
  • http://blog.csdn.net/wangdingqiaoit/article/details/51457...
    jerryhigh阅读 5,283评论 0 8
  • 现在是时候着手启用Assimp,并开始创建实际的加载和转换代码了。本教程的目标是创建另一个类,这个类可以表达模型的...
    IceMJ阅读 2,745评论 0 3