Android artoolkitx渲染3D模型

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);
    }
}

腦殼痛

GitHub

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章