artoolkitx的demo只是簡單的渲染了一個方塊,想要複雜的3D模型,最好能有動畫的,最初想用assimp的,可是太複雜了,我自己還搞不懂,就找其他的,發現了個純java的3D引擎rajawali,嗯還是有點複雜要仔細研究,最後找到個簡單的android-3D-model-viewer,然後下載下來東改改,西改改勉強可以用了,先看效果
用的是android-3D-model-viewer(下邊我就叫它modelview了)裏的cowboy.dae,改的太多了,說說注意事項和問題吧
1.artoolkitx把東西都集成在了ARActivity內,只提供了個supplyRenderer函數來添加ARRenderer,很不自由,而且攝像頭啓動的很慢,還要深入研究,把ARActivity拆開,能自由的添加東西,優化速度
2.artoolkitx的矩陣已經把攝像頭的位置矩陣和模型的位置矩陣已經合併了,然後我對矩陣又很陌生,modelview裏有個攝像頭光源,我就沒法設置了,然後出來的模型就比較暗
3.模型大小問題,modelview會自適應模型大小,通過計算生成相應的尺寸的矩陣,其實還是矩陣問題,artoolkitx就一個矩陣,不知道怎麼和modelview的矩陣相互合併,頭疼,所以每換一個模型就手動一點一點設置一個合適的尺寸,我在我自定義的ModelRenderer的onDrawFrame內添加了設置模型大小的代碼
float scale = 20;
objData.setScale(new float[]{scale,scale,scale});
4.modelview有些參數設置了全局靜態的,不小心漏了就會導致模型加載不出來,坑死了
好了貼一下我改的代碼吧
MainApplication.java
public class MainApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
AssetHelper assetHelper = new AssetHelper(getAssets());
assetHelper.cacheAssetFolder(this, "Data");
assetHelper.cacheAssetFolder(this, "cparam_cache");
}
}
MainActivity.java
public class MainActivity extends ARActivity {
static {
System.setProperty("java.protocol.handler.pkgs", "org.andresoviedo.util.android");
URL.setURLStreamHandlerFactory(new AndroidURLStreamHandlerFactory());
}
private SceneLoader scene;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ContentUtils.provideAssets(this);
scene = new SceneLoader(this, Uri.parse("assets://" + getPackageName() + "/models/cowboy.dae"));
scene.init();
}
/**
* Provide our own ARSquareTrackingRenderer.
*/
@Override
protected ARRenderer supplyRenderer() {
return new ModelRenderer(this,scene);
}
/**
* Use the FrameLayout in this Activity's UI.
*/
@Override
protected FrameLayout supplyFrameLayout() {
return (FrameLayout) this.findViewById(R.id.mainFrameLayout);
}
}
ModelRenderer.java
public class ModelRenderer extends ARRenderer {
private final static String TAG = ModelRenderer.class.getName();
/**
* Add 0.5f to the alpha component to the global shader so we can see through the skin
*/
private static final float[] BLENDING_FORCED_MASK_COLOR = {1.0f, 1.0f, 1.0f, 0.5f};
// width of the screen
private int width;
// height of the screen
private int height;
/**
* Drawer factory to get right renderer/shader based on object attributes
*/
private DrawerFactory drawer;
// The loaded textures
private Map<Object, Integer> textures = new HashMap<>();
// 3D matrices to project our 3D world
private final float[] lightPosInWorldSpace = new float[4];
private final float[] cameraPosInWorldSpace = new float[3];
/**
* Whether the info of the model has been written to console log
*/
private Map<Object3DData, Boolean> infoLogged = new HashMap<>();
/**
* Did the application explode?
*/
private boolean fatalException = false;
private SceneLoader scene;
private static final Trackable trackables[] = new Trackable[]{
new Trackable("hiro", 80.0f),
new Trackable("kanji", 80.0f)
};
private int trackableUIDs[] = new int[trackables.length];
/**
* Markers can be configured here.
*/
@Override
public boolean configureARScene() {
int i = 0;
for (Trackable trackable : trackables) {
trackableUIDs[i] = ARController.getInstance().addTrackable("single;Data/" + trackable.getName() + ".patt;" + trackable.getWidth());
if (trackableUIDs[i] < 0) return false;
i++;
}
return true;
}
/**
* Construct a new renderer for the specified surface view
* the 3D window
*/
public ModelRenderer(Context context, SceneLoader scene) {
this.scene = scene;
// This component will draw the actual models using OpenGL
try {
drawer = new DrawerFactory(context);
} catch (IllegalAccessException | IOException e) {
e.printStackTrace();
}
}
private float[] backgroundColor = new float[]{0f, 0f, 0f, 1.0f};
@Override
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
super.onSurfaceCreated(unused,config);
// Set the background frame color
GLES20.glClearColor(backgroundColor[0], backgroundColor[1], backgroundColor[2], backgroundColor[3]);
// Use culling to remove back faces.
// Don't remove back faces so we can see them
// GLES20.glEnable(GLES20.GL_CULL_FACE);
}
@Override
public void onSurfaceChanged(GL10 unused, int width, int height) {
super.onSurfaceChanged(unused,width,height);
this.width = width;
this.height = height;
}
@Override
public void draw() {
super.draw();
if(fatalException){
return;
}
GLES20.glViewport(0, 0, width, height);
GLES20.glScissor(0, 0, width, height);
// Draw background color
// Enable depth testing for hidden-surface elimination.
GLES20.glEnable(GLES20.GL_DEPTH_TEST);
// Enable not drawing out of view port
GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
if (scene == null) {
// scene not ready
return;
}
for (int trackableUID : trackableUIDs) {
// If the trackable is visible, apply its transformation, and render a cube
float[] modelViewMatrix = new float[16];
if (ARController.getInstance().queryTrackableVisibilityAndTransformation(trackableUID, modelViewMatrix)) {
float[] projectionMatrix = ARController.getInstance().getProjectionMatrix(10.0f, 10000.0f);
onDrawFrame(projectionMatrix,modelViewMatrix);
}
}
}
private void onDrawFrame(float[] projectionMatrix,float[] modelViewMatrix){
try {
float[] colorMask = null;
if (scene.isBlendingEnabled()) {
// Enable blending for combining colors when there is transparency
GLES20.glEnable(GLES20.GL_BLEND);
GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);
if (scene.isBlendingForced()){
colorMask = BLENDING_FORCED_MASK_COLOR;
}
} else {
GLES20.glDisable(GLES20.GL_BLEND);
}
// animate scene
scene.onDrawFrame();
this.onDrawFrame(modelViewMatrix, projectionMatrix,colorMask, cameraPosInWorldSpace);
}catch (Exception ex){
Log.e("ModelRenderer", "Fatal exception: "+ex.getMessage(), ex);
fatalException = true;
}
}
private void onDrawFrame(float[] viewMatrix, float[] projectionMatrix,float[] colorMask,float[] cameraPosInWorldSpace) {
if (scene.getObjects().isEmpty()){
return;
}
// draw all available objects
List<Object3DData> objects = scene.getObjects();
for (int i=0; i<objects.size(); i++) {
Object3DData objData = null;
try {
objData = objects.get(i);
if (!objData.isVisible()) continue;
Object3D drawerObject = drawer.getDrawer(objData, scene.isDrawTextures(), scene.isDrawLighting(),
scene.isDoAnimation(), scene.isDrawColors());
if (drawerObject == null){
continue;
}
float scale = 20;
objData.setScale(new float[]{scale,scale,scale});
if (!infoLogged.containsKey(objData)) {
Log.v("ModelRenderer","Drawing model: "+objData.getId());
infoLogged.put(objData, true);
}
// load model texture
Integer textureId = textures.get(objData.getTextureData());
if (textureId == null && objData.getTextureData() != null) {
Log.i("ModelRenderer","Loading texture '"+objData.getTextureFile()+"'...");
ByteArrayInputStream textureIs = new ByteArrayInputStream(objData.getTextureData());
textureId = GLUtil.loadTexture(textureIs);
textureIs.close();
textures.put(objData.getTextureData(), textureId);
Log.i("GLUtil", "Loaded texture ok. id: "+textureId);
}
if (textureId == null){
textureId = -1;
}
drawerObject.draw(objData, projectionMatrix, viewMatrix,
textureId, lightPosInWorldSpace, colorMask, cameraPosInWorldSpace);
} catch (Exception ex) {
Log.e("ModelRenderer","There was a problem rendering the object '"+objData.getId()+"':"+ex.getMessage(),ex);
}
}
}
}
SceneLoader.java
public class SceneLoader implements LoaderTask.Callback {
private Activity activity;
private Uri paramUri;
/**
* List of data objects containing info for building the opengl objects
*/
private List<Object3DData> objects = new ArrayList<>();
/**
* Animate model (dae only) or not
*/
private boolean doAnimation = true;
/**
* Animator
*/
private Animator animator = new Animator();
/**
* time when model loading has started (for stats)
*/
private long startTime;
public SceneLoader(Activity activity,Uri paramUri) {
this.activity = activity;
this.paramUri = paramUri;
}
public void init() {
if (paramUri == null){
return;
}
startTime = SystemClock.uptimeMillis();
Uri uri = paramUri;
Log.i("Object3DBuilder", "Loading model " + uri + ". async and parallel..");
if (uri.toString().toLowerCase().endsWith(".obj")) {
new WavefrontLoaderTask(activity, uri, this).execute();
} else if (uri.toString().toLowerCase().endsWith(".stl")) {
Log.i("Object3DBuilder", "Loading STL object from: "+uri);
new STLLoaderTask(activity, uri, this).execute();
} else if (uri.toString().toLowerCase().endsWith(".dae")) {
Log.i("Object3DBuilder", "Loading Collada object from: "+uri);
new ColladaLoaderTask(activity, uri, this).execute();
}
}
/**
* Hook for animating the objects before the rendering
*/
public void onDrawFrame() {
if (objects.isEmpty()) return;
if (doAnimation) {
for (int i=0; i<objects.size(); i++) {
Object3DData obj = objects.get(i);
animator.update(obj, false);
}
}
}
synchronized void addObject(Object3DData obj) {
List<Object3DData> newList = new ArrayList<Object3DData>(objects);
newList.add(obj);
this.objects = newList;
}
public synchronized List<Object3DData> getObjects() {
return objects;
}
public boolean isDoAnimation() {
return doAnimation;
}
/**
* Whether to draw using textures
*/
private boolean drawTextures = true;
public boolean isDrawTextures() {
return drawTextures;
}
@Override
public void onStart(){
ContentUtils.setThreadActivity(activity);
if(listener != null){
listener.onStart();
}
}
@Override
public void onLoadComplete(List<Object3DData> datas) {
// TODO: move texture load to LoaderTask
for (Object3DData data : datas) {
if (data.getTextureData() == null && data.getTextureFile() != null) {
Log.i("LoaderTask","Loading texture... "+data.getTextureFile());
try (InputStream stream = ContentUtils.getInputStream(data.getTextureFile())){
if (stream != null) {
data.setTextureData(IOUtils.read(stream));
}
} catch (IOException ex) {
data.addError("Problem loading texture " + data.getTextureFile());
}
}
}
// TODO: move error alert to LoaderTask
List<String> allErrors = new ArrayList<>();
for (Object3DData data : datas) {
addObject(data);
allErrors.addAll(data.getErrors());
}
if (!allErrors.isEmpty()){
Log.e("SceneLoader", allErrors.toString());
// makeToastText(allErrors.toString(), Toast.LENGTH_LONG);
}
final String elapsed = (SystemClock.uptimeMillis() - startTime) / 1000 + " secs";
Log.i("LoaderTask","Build complete (" + elapsed + ")");
ContentUtils.setThreadActivity(null);
if(listener != null){
listener.onLoadComplete(this);
}
}
@Override
public void onLoadError(Exception ex) {
Log.e("SceneLoader", ex.getMessage(), ex);
ContentUtils.setThreadActivity(null);
if(listener != null){
listener.onLoadError(ex);
}
}
/**
* Enable or disable blending (transparency)
*/
private boolean isBlendingEnabled = true;
public boolean isBlendingEnabled() {
return isBlendingEnabled;
}
/**
* Force transparency
*/
private boolean isBlendingForced = false;
public boolean isBlendingForced() {
return isBlendingForced;
}
/**
* Whether to draw using colors or use default white color
*/
private boolean drawColors = true;
public boolean isDrawColors() {
return drawColors;
}
/**
* Light toggle feature: whether to draw using lights
*/
private boolean drawLighting = true;
public boolean isDrawLighting() {
return drawLighting;
}
private OnSceneLoaderListener listener;
public void setOnSceneLoaderListener(OnSceneLoaderListener listener) {
this.listener = listener;
}
public interface OnSceneLoaderListener {
void onStart();
void onLoadError(Exception ex);
void onLoadComplete(SceneLoader scene);
}
}
modelview的SceneLoader改的比較多,導致LoaderTask內的dialog報錯,所以就把關於dialog的代碼都註釋了
LoaderTask.java
public abstract class LoaderTask extends AsyncTask<Void, Integer, List<Object3DData>> {
/**
* URL to the 3D model
*/
protected final Uri uri;
/**
* Callback to notify of events
*/
private final Callback callback;
/**
* The dialog that will show the progress of the loading
*/
// private final ProgressDialog dialog;
/**
* Build a new progress dialog for loading the data model asynchronously
* @param uri the URL pointing to the 3d model
*
*/
public LoaderTask(Activity parent, Uri uri, Callback callback) {
this.uri = uri;
// this.dialog = ProgressDialog.show(this.parent, "Please wait ...", "Loading model data...", true);
// this.dialog.setTitle(modelId);
// this.dialog = new ProgressDialog(parent);
this.callback = callback; }
@Override
protected void onPreExecute() {
super.onPreExecute();
// this.dialog.setMessage("Loading...");
// this.dialog.setCancelable(false);
// this.dialog.show();
}
@Override
protected List<Object3DData> doInBackground(Void... params) {
try {
callback.onStart();
List<Object3DData> data = build();
build(data);
callback.onLoadComplete(data);
return data;
} catch (Exception ex) {
callback.onLoadError(ex);
return null;
}
}
protected abstract List<Object3DData> build() throws Exception;
protected abstract void build(List<Object3DData> data) throws Exception;
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
// switch (values[0]) {
// case 0:
// this.dialog.setMessage("Analyzing model...");
// break;
// case 1:
// this.dialog.setMessage("Allocating memory...");
// break;
// case 2:
// this.dialog.setMessage("Loading data...");
// break;
// case 3:
// this.dialog.setMessage("Scaling object...");
// break;
// case 4:
// this.dialog.setMessage("Building 3D model...");
// break;
// case 5:
// // Toast.makeText(parent, modelId + " Build!", Toast.LENGTH_LONG).show();
// break;
// }
}
@Override
protected void onPostExecute(List<Object3DData> data) {
super.onPostExecute(data);
// if (dialog.isShowing()) {
// dialog.dismiss();
// }
}
public interface Callback {
void onStart();
void onLoadError(Exception ex);
void onLoadComplete(List<Object3DData> data);
}
}
腦殼痛