这几天在Android项目中需要加载三维模型,找了多种方法,最后决定使用jpct引擎。话不多说,上代码。
一、代码解析
首先创建一个活动MainActivity,活动布局如下:
包括五个button,一个ImageView作为背景,一个GLSurfaceView显示模型,布局代码如下:
新建RenderView.class用于加载模型和显示处理:(如果有Unity或者其他游戏引擎的开发经验的话,下面的代码非常好理解。)
public class RenderView implements GLSurfaceView.Renderer{
public World myWorld;
private FrameBuffer frameBuffer;
private Object3D selectedObj;
public RenderView(Context context) {
myWorld = new World();
myWorld.setAmbientLight(25, 25, 25);
Light light = new Light(myWorld);
light.setIntensity(250, 250, 250);
light.setPosition(new SimpleVector(0, 0, -15));
Camera cam = myWorld.getCamera();
cam.setFOVLimits(0.1f,2.0f);
cam.setFOV(1.08f);
cam.setYFOV(1.92f);
cam.setClippingPlanes(0f,2000f);
System.out.println(cam.getFOV());
System.out.println(cam.getYFOV());
System.out.println(cam.getPosition());
String[] names=Config.getParameterNames();
for(String i:names){
System.out.println(i);
}
}
public void onSurfaceChanged(GL10 gl, int w, int h) {
if (frameBuffer != null) {
frameBuffer.dispose();
}
frameBuffer = new FrameBuffer(gl, w, h);
}
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
gl.glClearColor(1.0f,1.0f,1.0f,0.3f);
}
public void onDrawFrame(GL10 gl) {
frameBuffer.clear(Color.TRANSPARENT);
myWorld.renderScene(frameBuffer);
myWorld.draw(frameBuffer);
frameBuffer.display();
}
public void addObject(Context context) {
Object3D newObject = null;
try {
createTextures(context);
Object3D[] objectsArray2 = Loader.loadOBJ(context.getResources().getAssets().open("policecar.obj"), context.getResources()
.getAssets().open("policecar.mtl"), 1f);
newObject = Object3D.mergeAll(objectsArray2);
newObject.setTexture("policecar_texture");
newObject.setOrigin(new SimpleVector(0, 0, 300));
newObject.rotateZ(3.1415926f);
newObject.setName("policecar.obj");
} catch (IOException e) {
e.printStackTrace();
}
newObject.strip();
newObject.build();
Message msg=new Message();
msg.what=MainActivity.MSG_LOAD_MODEL_SUC;
msg.obj=newObject;
MainActivity.handler.sendMessage(msg);
selectedObj=newObject;
}
public void applyTranslation(float incX, float incY, float incZ) {
if (this.selectedObj != null) {
SimpleVector objOrigin = this.selectedObj.getOrigin();
SimpleVector currentPoition=this.selectedObj.getTransformedCenter();
System.out.println(currentPoition);
this.selectedObj.translate(incX, incY, incZ);
}
}
private void createTextures(Context context) {
Bitmap bitmap=BitmapFactory.decodeResource(context.getResources(),R.mipmap.policecar);
Texture texture = new Texture(bitmap);
if(!TextureManager.getInstance().containsTexture("policecar_texture")){
TextureManager.getInstance().addTexture("policecar_texture", texture);
}
}
}
myWorld是当前的场景,所有的物体都在当前场景中,FrameBuffer是显示缓存,selectedObj是当前选择的物体。下面我们来详细解释这些代码。
public RenderView(Context context) {
myWorld = new World();
myWorld.setAmbientLight(25, 25, 25);
Light light = new Light(myWorld);
light.setIntensity(250, 250, 250);
light.setPosition(new SimpleVector(0, 0, -15));
Camera cam = myWorld.getCamera();
cam.setFOVLimits(0.1f,2.0f);
cam.setFOV(1.08f);
cam.setYFOV(1.92f);
cam.setClippingPlanes(0f,2000f);
System.out.println(cam.getFOV());
System.out.println(cam.getYFOV());
System.out.println(cam.getPosition());
String[] names=Config.getParameterNames();
for(String i:names){
System.out.println(i);
}
}
RenderView是类的 构造函数,定义了场景中的一些关键要素,例如摄像机,光照等。myWorld.setAmbientLight设置全局光,Light是当前场景中的光线设置,light.setIntensity设置光照强度,light.setPosition设置光源位置,此处光源是点光源。cam是场景中的摄像机,cam.setFOVLimits设置摄像机的视场角范围,此处设置为0.1-2.0。摄像机的X和Y方向的视场角可以分别设置。cam.setClippingPlanes设置相机的视距,此处为0-2000。
public void onSurfaceChanged(GL10 gl, int w, int h) {
if (frameBuffer != null) {
frameBuffer.dispose();
}
frameBuffer = new FrameBuffer(gl, w, h);
}
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
gl.glClearColor(1.0f,1.0f,1.0f,0.3f);
}
public void onDrawFrame(GL10 gl) {
frameBuffer.clear(Color.TRANSPARENT);
myWorld.renderScene(frameBuffer);
myWorld.draw(frameBuffer);
frameBuffer.display();
}
这几个函数是用于定义GLSurfaceView创建时和刷新时的函数,gl.glClearColor定义了SurfaceView刷新颜色。frameBuffer.clear定义了frameBuffer刷新颜色。
public void addObject(Context context) {
Object3D newObject = null;
try {
createTextures(context);
Object3D[] objectsArray2 = Loader.loadOBJ(context.getResources().getAssets().open("policecar.obj"), context.getResources()
.getAssets().open("policecar.mtl"), 1f);
newObject = Object3D.mergeAll(objectsArray2);
newObject.setTexture("policecar_texture");
newObject.setOrigin(new SimpleVector(0, 0, 300));
newObject.rotateZ(3.1415926f);
newObject.setName("policecar.obj");
} catch (IOException e) {
e.printStackTrace();
}
newObject.strip();
newObject.build();
Message msg=new Message();
msg.what=MainActivity.MSG_LOAD_MODEL_SUC;
msg.obj=newObject;
MainActivity.handler.sendMessage(msg);
selectedObj=newObject;
}
addObject用于向场景中添加物体,loadOBJ函数采用的是二进制流的形式加载模型数据。加载OBJ格式的模型需要三个文件,分别是模型.obj,模型.mtl以及模型的贴图文件,Obj文件是模型的面信息,mtl文件定义了模型贴图与面位置信息。
public void applyTranslation(float incX, float incY, float incZ) {
if (this.selectedObj != null) {
SimpleVector objOrigin = this.selectedObj.getOrigin();
SimpleVector currentPoition=this.selectedObj.getTransformedCenter();
System.out.println(currentPoition);
this.selectedObj.translate(incX, incY, incZ);
}
}
此函数用于模型的位移,其中object.translate()函数定义了模型的位移,translate将会将模型位移到当前世界坐标分别加上函数参数之后的坐标。也就是新的坐标=旧坐标+参数。getTransfromedCenter获取当前物体的当前世界坐标。、
下面解析MainActivity中的代码
public class MainActivity extends AppCompatActivity{
public final static int MSG_LOAD_MODEL_SUC=0;
private GLSurfaceView myGLView;
private RenderView myRenderer;
private Button btnLoad;
private Button btnLeft;
private Button btnRight;
private Button btnTop;
private Button btnDown;
private Thread threadLoadModel;
public static Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler=new Handler(){
@Override
public void handleMessage(Message msg){
switch (msg.what){
case MSG_LOAD_MODEL_SUC:
Toast.makeText(MainActivity.this, "模型加载成功", Toast.LENGTH_SHORT).show();
Object3D object3D=(Object3D) msg.obj;
myRenderer.myWorld.addObject(object3D);
break;
}
}
};
btnLoad=findViewById(R.id.btnLoadModel);
btnLeft=findViewById(R.id.btnLeft);
btnRight=findViewById(R.id.btnRight);
btnTop=findViewById(R.id.btnTop);
btnDown=findViewById(R.id.btnDown);
btnLoad.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "开始加载模型", Toast.LENGTH_SHORT).show();
threadLoadModel.start();
}
});
btnLeft.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
myRenderer.applyTranslation(-10,0,0);
}
});
btnRight.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
myRenderer.applyTranslation(10,0,0);
}
});
btnTop.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
myRenderer.applyTranslation(0,-10,0);
}
});
btnDown.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
myRenderer.applyTranslation(0,10,0);
}
});
myGLView = (GLSurfaceView) this.findViewById(R.id.surfaceView);
myGLView.setEGLConfigChooser(8, 8, 8, 8, 16, 0);
myGLView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
myGLView.setZOrderOnTop(true);
myRenderer = new RenderView(this);
myGLView.setRenderer(myRenderer);
threadLoadModel=new Thread(new Runnable() {
@Override
public void run() {
myRenderer.addObject(MainActivity.this);
}
});
}
@Override
protected void onPause() {
super.onPause();
myGLView.onPause();
}
@Override
protected void onResume() {
super.onResume();
myGLView.onResume();
}
}
有关View的代码就不多做解析。具体解释一下模型的加载和GLView的设置。
为了使加载模型的过程中程序不堵塞住,开辟一个子线程进行模型解析。模型解析之后,向主线程发送消息,提醒主线程将模型添加到场景中,即myRenderer.myWorld.addObjct()。同时也为了避免模型过大导致加载过程失败导致的程序崩溃。
myGLView.setEGLConfigChooser(8, 8, 8, 8, 16, 0);
myGLView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
myGLView.setZOrderOnTop(true);
这段代码将GLSurfaceView的刷新设置为透明并将View设置到所有View的最顶层。
二、jpct的世界坐标系
jpct中的正世界坐标系格式如下,以GlSurfaceView中心为原点,垂直于屏幕向内为z轴正方向,平行于屏幕向右为X轴正方向,向下为Y轴正方向,与Android设备的屏幕xy正方向相一致。
jpct下载地址:http://www.jpct.net/
项目下载地址:https://download.csdn.net/download/njnutzhou/10968296