最近做一个项目,需要加载带纹理的3d模型obj文件并显示出来,仅限于安卓平台,因此需要一个轻量级的3d内核。
找了很多内核,要不就是太久没更新不能用了,要不就是功能太复杂,后来找到了JPCT-AE,网上的文档相当少,但经过自己研究了两天,终于成功完成了obj模型的加载和显示,有需要的同学可以参考借鉴。
1、JPCT-AE简介
官网:http://www.jpct.net/jpct-ae/index.html
支持加载3DS, OBJ, MD2, ASC,序列化文件,也支持mtl和纹理,基于egl实现。
注意:纹理只支持图片的宽高为2的n次方,如1024,512,如果纹理不是这些尺寸,那么在加载时会出错。此问题也有解决办法,解决办法详见后面的说明。
jpct-ae显示3d的流程大致如下:
mWorld = new World(); //创建3d场景
mWorld.setAmbientLight(20, 20, 20); //添加环境光源
mSun = new Light(mWorld); //添加点光源
mSun.setIntensity(250, 250, 250); //设置点光源强度
//此处略,利用loader加载3d模型
// ......
//加载3d模型完毕
Camera cam = mWorld.getCamera(); //获取相机
cam.moveCamera(Camera.CAMERA_MOVEOUT, 130); //调整相机位置
cam.lookAt(mObj.getTransformedCenter()); //调整相机视角
场景绘制在一个GLSurfaceView上, 构建完毕后,在onDrawFrame方法中对场景进行渲染:
mWorld.renderScene(mFb);
mWorld.draw(mFb);
在官方提供的demo中有很完整的示例代码,可以参考demo来完成代码的编写。
2、利用JPCT-AE加载obj模型
JPCT-AE提供一个静态的Loader类用于加载不同数据格式的3d模型,在官方文档中可以查到这个类的用法及api,加载.obj格式使用下面的api:
public static Object3D[] loadOBJ(java.io.InputStream objStream, java.io.InputStream mtlStream, float scale)此api会加载模型中的所有多边形的几何信息,支持多边形最多4个顶点(超过会有警告并且显示异常)。对于模型的texture文件,此api会加载成空文件,并不会实际读取,因此需要在执行此函数前预先将相关的texture文件进行加载,或者在此函数执行后对相关的texture文件进行替换,否则texture将不能显示。
同时,JPCT-AE提供一个类TextureManager用于管理纹理,此类是一个单例,用于存储和取出textures,常用的有addTexture, replaceTexture, containsTexture, unloadTexture,用法很简单,详细说明可以查官方api文档。
整理以上思路,得知正确的显示出带纹理的obj模型,需要做以下几步:
1)解析obj文件,得到mtl文件;
2)解析mtl文件,得到texture文件;
3)使用TextureManager类,加载所有的texture文件;
4)创建场景并显示。
2.2 解析mtl文件,得到texture文件
打开mtl查看texture信息,其中以"newmtl"关键字开关,描述的就是纹理信息,一个mtl文件中可以描述多个纹理,如:
newmtl _01_-_Default1noCulli__01_-_Default1noCulli
Ns 154.901961
Ka 0.000000 0.000000 0.000000
Kd 0.640000 0.640000 0.640000
Ks 0.165000 0.165000 0.165000
Ni 1.000000
d 1.000000
illum 2
map_Kd 01_-_Default1noCulling.JPG
newmtl FrontColorNoCullingID_male-02-1noCulling.JP
Ns 154.901961
Ka 0.000000 0.000000 0.000000
Kd 0.800000 0.800000 0.800000
Ks 0.165000 0.165000 0.165000
Ni 1.000000
d 1.000000
illum 2
map_Kd male-02-1noCulling.JPG
map_Kd 表示材质的固有色,map_Ka表示材质的阴影色。
得到所有的纹理。参考代码:
2.3 加载所有texture,参考代码如下:
2.4 创建场景并显示
在onSurfaceCreated中创建场景,在onDrawFrame中进行绘制:
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
Log.i(TAG, "onSurfaceCreated enter!!");
createWorld();
}
public void onDrawFrame(GL10 gl) {
//Log.i(TAG, "onDrawFrame enter!!");
// Draw the main screen
mFb.clear(mBgColor);
if(mWorld != null) {
mWorld.renderScene(mFb);
mWorld.draw(mFb);
mFb.display();
}
}
3、解决texture只支持宽高为2的n次幂的图片的问题。
解决办法是改变bitmap的大小,不过我这个解决办法很牵强,因为改变bitmap后会有一定程度的失真,另外也不是百分百适用,只适用于texture为正方形的情况。当然一般像3dmax这样的建模软件生成的texture肯定都是符合要求的。但是由于openge本身并没有对texture有宽高上的要求,所以jpct-ae有这样的限制感觉很不爽。
参考代码如下:
//jpct-ae only supports images size of which is 2^x
public Bitmap scaleBitmap(Bitmap bitmap){
int w = bitmap.getWidth();
int h = bitmap.getHeight();
int destW = 1024;
int destH = h*destW/w;
Bitmap newbm = Bitmap.createScaledBitmap(bitmap, destW, destH, true);
return newbm;
}
总之,暂时利用jpct-ae快速解决了3d模型加载的问题,但还是觉得jpct-ae稍微有点重,而且存在一些限制不太方便,有空了还是自己利用egl写一个比较好。