【Filament】Cube map (6 pictures)

1 Introduction

        This article uses an example of cube mapping to explain the application of three-dimensional texture mapping (sub-grid mapping). In the case, 6 different pictures are used to map the cube, as shown below.

        If readers are not familiar with Filament, please review the following content.

2 Cubemap

        The project structure of this article is as follows, complete code resources →  Filament cube map (6 pictures) .

2.1 Basic classes

        In order to facilitate readers to focus on the input of Filament and easily configure complex environment-dependent logic, the author followed the writing method of OpenGL ES and extracted the FLSurfaceView, BaseModel, Mesh, MaterialUtils and TextureUtils classes. FLSurfaceView has similar functions to GLSurfaceView, carrying the rendering environment configuration; BaseModel is used to manage the mesh and materials of the model; Mesh is used to manage the vertex attributes of the model; MaterialUtils and TextureUtils provide some materials and texture-related tools respectively.

        build.gradle

...
android {
    ...
    aaptOptions { // 在应用程序打包过程中不压缩的文件
        noCompress 'filamat', 'ktx'
    }
}
 
dependencies {
    implementation fileTree(dir: '../libs', include: ['*.aar'])
    ...
}

        Note: In the libs directory under the project root directory, you need to put the following aar files, which are derived from the aar compiled and generated in the Filament environment setup .

        FLSurfaceView.java

package com.zhyan8.multitexture.filament;

import android.content.Context;
import android.graphics.Point;
import android.view.Choreographer;
import android.view.Surface;
import android.view.SurfaceView;

import com.google.android.filament.Camera;
import com.google.android.filament.Engine;
import com.google.android.filament.EntityManager;
import com.google.android.filament.Filament;
import com.google.android.filament.Renderer;
import com.google.android.filament.Scene;
import com.google.android.filament.Skybox;
import com.google.android.filament.SwapChain;
import com.google.android.filament.View;
import com.google.android.filament.Viewport;
import com.google.android.filament.android.DisplayHelper;
import com.google.android.filament.android.FilamentHelper;
import com.google.android.filament.android.UiHelper;

import java.util.ArrayList;

/*
 * Filament中待渲染的SurfaceView
 * 功能可以类比OpenGL ES中的GLSurfaceView
 * 用于创建Filament的渲染环境
 */
public class FLSurfaceView extends SurfaceView {
    public static int RENDERMODE_WHEN_DIRTY = 0; // 用户请求渲染才渲染一帧
    public static int RENDERMODE_CONTINUOUSLY = 1; // 持续渲染
    protected int mRenderMode = RENDERMODE_CONTINUOUSLY; // 渲染模式
    protected Choreographer mChoreographer; // 消息控制
    protected DisplayHelper mDisplayHelper; // 管理Display(可以监听分辨率或刷新率的变化)
    protected UiHelper mUiHelper; // 管理SurfaceView、TextureView、SurfaceHolder
    protected Engine mEngine; // 引擎(跟踪用户创建的资源, 管理渲染线程和硬件渲染器)
    protected Renderer mRenderer; // 渲染器(用于操作系统窗口, 生成绘制命令, 管理帧延时)
    protected Scene mScene; // 场景(管理渲染对象、灯光)
    protected View mView; // 存储渲染数据(View是Renderer操作的对象)
    protected Camera mCamera; // 相机(视角管理)
    protected Point mDesiredSize; // 渲染分辨率
    protected float[] mSkyboxColor; // 背景颜色
    protected SwapChain mSwapChain; // 操作系统的本地可渲染表面(native renderable surface, 通常是一个window或view)
    protected FrameCallback mFrameCallback = new FrameCallback(); // 帧回调
    protected ArrayList<RenderCallback> mRenderCallbacks; // 每一帧渲染前的回调(一般用于处理模型变换、相机变换等)

    static {
        Filament.init();
    }

    public FLSurfaceView(Context context) {
        super(context);
        mChoreographer = Choreographer.getInstance();
        mDisplayHelper = new DisplayHelper(context);
        mRenderCallbacks = new ArrayList<>();
    }

    public void init() { // 初始化
        setupSurfaceView();
        setupFilament();
        setupView();
        setupScene();
    }

    public void setRenderMode(int renderMode) { // 设置渲染模式
        mRenderMode = renderMode;
    }

    public void addRenderCallback(RenderCallback renderCallback) { // 添加渲染回调
        if (renderCallback != null) {
            mRenderCallbacks.add(renderCallback);
        }
    }

    public void requestRender() { // 请求渲染
        mChoreographer.postFrameCallback(mFrameCallback);
    }

    public void onResume() { // 恢复
        mChoreographer.postFrameCallback(mFrameCallback);
    }

    public void onPause() { // 暂停
        mChoreographer.removeFrameCallback(mFrameCallback);
    }

    public void onDestroy() { // 销毁Filament环境
        mChoreographer.removeFrameCallback(mFrameCallback);
        mRenderCallbacks.clear();
        mUiHelper.detach();
        mEngine.destroyRenderer(mRenderer);
        mEngine.destroyView(mView);
        mEngine.destroyScene(mScene);
        mEngine.destroyCameraComponent(mCamera.getEntity());
        EntityManager entityManager = EntityManager.get();
        entityManager.destroy(mCamera.getEntity());
        mEngine.destroy();
    }

    protected void setupScene() { // 设置Scene参数
    }

    protected void onResized(int width, int height) { // Surface尺寸变化时回调
        double zoom = 1;
        double aspect = (double) width / (double) height;
        mCamera.setProjection(Camera.Projection.ORTHO,
                -aspect * zoom, aspect * zoom, -zoom, zoom, 0, 1000);
    }

    private void setupSurfaceView() { // 设置SurfaceView
        mUiHelper = new UiHelper(UiHelper.ContextErrorPolicy.DONT_CHECK);
        mUiHelper.setRenderCallback(new SurfaceCallback());
        if (mDesiredSize != null) {
            mUiHelper.setDesiredSize(mDesiredSize.x, mDesiredSize.y);
        }
        mUiHelper.attachTo(this);
    }

    private void setupFilament() { // 设置Filament参数
        mEngine = Engine.create();
        // mEngine = (new Engine.Builder()).featureLevel(Engine.FeatureLevel.FEATURE_LEVEL_0).build();
        mRenderer = mEngine.createRenderer();
        mScene = mEngine.createScene();
        mView = mEngine.createView();
        mCamera = mEngine.createCamera(mEngine.getEntityManager().create());
    }

    private void setupView() { // 设置View参数
        float[] color = mSkyboxColor != null ? mSkyboxColor : new float[] {0, 0, 0, 1};
        Skybox skybox = (new Skybox.Builder()).color(color).build(mEngine);
        mScene.setSkybox(skybox);
        if (mEngine.getActiveFeatureLevel() == Engine.FeatureLevel.FEATURE_LEVEL_0) {
            mView.setPostProcessingEnabled(false); // FEATURE_LEVEL_0不支持post-processing
        }
        mView.setCamera(mCamera);
        mView.setScene(mScene);
    }

    /*
     * 帧回调
     */
    private class FrameCallback implements Choreographer.FrameCallback {
        @Override
        public void doFrame(long frameTimeNanos) { // 渲染每帧数据
            if (mRenderMode == RENDERMODE_CONTINUOUSLY) {
                mChoreographer.postFrameCallback(this); // 请求下一帧
            }
            mRenderCallbacks.forEach(callback -> callback.onCall());
            if (mUiHelper.isReadyToRender()) {
                if (mRenderer.beginFrame(mSwapChain, frameTimeNanos)) {
                    mRenderer.render(mView);
                    mRenderer.endFrame();
                }
            }
        }
    }

    /*
     * Surface回调
     */
    private class SurfaceCallback implements UiHelper.RendererCallback {
        @Override
        public void onNativeWindowChanged(Surface surface) { // Native窗口改变时回调
            if (mSwapChain != null) {
                mEngine.destroySwapChain(mSwapChain);
            }
            long flags = mUiHelper.getSwapChainFlags();
            if (mEngine.getActiveFeatureLevel() == Engine.FeatureLevel.FEATURE_LEVEL_0) {
                if (SwapChain.isSRGBSwapChainSupported(mEngine)) {
                    flags = flags | SwapChain.CONFIG_SRGB_COLORSPACE;
                }
            }
            mSwapChain = mEngine.createSwapChain(surface, flags);
            mDisplayHelper.attach(mRenderer, getDisplay());
        }

        @Override
        public void onDetachedFromSurface() { // 解绑Surface时回调
            mDisplayHelper.detach();
            if (mSwapChain != null) {
                mEngine.destroySwapChain(mSwapChain);
                mEngine.flushAndWait();
                mSwapChain = null;
            }
        }

        @Override
        public void onResized(int width, int height) { // Surface尺寸变化时回调
            mView.setViewport(new Viewport(0, 0, width, height));
            FilamentHelper.synchronizePendingFrames(mEngine);
            FLSurfaceView.this.onResized(width, height);
        }
    }

    /*
     * 每一帧渲染前的回调
     * 一般用于处理模型变换、相机变换等
     */
    public static interface RenderCallback {
        void onCall();
    }
}

        BaseModel.java

package com.zhyan8.multitexture.filament;

import android.content.Context;

import com.google.android.filament.Box;
import com.google.android.filament.Engine;
import com.google.android.filament.EntityManager;
import com.google.android.filament.Material;
import com.google.android.filament.MaterialInstance;
import com.google.android.filament.RenderableManager;
import com.google.android.filament.RenderableManager.PrimitiveType;
import com.google.android.filament.Texture;
import com.google.android.filament.TransformManager;

import com.zhyan8.multitexture.filament.Mesh.SubMesh;
import com.zhyan8.multitexture.filament.utils.MaterialUtils;
import com.zhyan8.multitexture.filament.utils.TextureUtils;

/*
 * 模型基类
 * 管理模型的网格、材质、渲染id
 */
public class BaseModel {
    private static String TAG = "BaseModel";
    protected Context mContext; // 上下文
    protected Engine mEngine; // Filament引擎
    protected TransformManager mTransformManager; // 模型变换管理器
    protected Mesh mMesh; // 模型网格
    protected SubMesh[] mSubMeshes; // 子网格
    protected Material[] mMaterials; // 模型材质
    protected MaterialInstance[] mMaterialInstances; // 模型材质实例
    protected Texture[] mTextures; // 纹理
    protected int mRenderable; // 渲染id
    protected int mTransformComponent; // 模型变换组件的id
    protected Box mBox; // 渲染区域
    protected FLSurfaceView.RenderCallback mRenderCallback; // 每一帧渲染前的回调(一般用于处理模型变换、相机变换等)

    public BaseModel(Context context, Engine engine) {
        mContext = context;
        mEngine = engine;
        mTransformManager = mEngine.getTransformManager();
    }

    public int getRenderable() { // 获取渲染id
        return mRenderable;
    }

    public FLSurfaceView.RenderCallback getRenderCallback() { // 获取渲染回调
        return mRenderCallback;
    }

    public void destroy() { // 销毁模型
        mEngine.destroyEntity(mRenderable);
        if (mMesh != null) {
            mMesh.destroy();
        }
        if (mTextures != null) {
            for (int i = 0; i < mTextures.length; i++) {
                mEngine.destroyTexture(mTextures[i]);
            }
        }
        if (mMaterialInstances != null) {
            for (int i = 0; i < mMaterialInstances.length; i++) {
                mEngine.destroyMaterialInstance(mMaterialInstances[i]);
            }
        }
        if (mMaterials != null) {
            for (int i = 0; i < mMaterials.length; i++) {
                mEngine.destroyMaterial(mMaterials[i]);
            }
        }
        EntityManager entityManager = EntityManager.get();
        entityManager.destroy(mRenderable);
    }

    protected int getRenderable(PrimitiveType primitiveType, int vertexCount) { // 获取渲染id
        int renderable = EntityManager.get().create();
        if (mSubMeshes == null) {
            mSubMeshes = new SubMesh[] {new SubMesh(0, 0, vertexCount - 1, vertexCount)};
        }
        RenderableManager.Builder builder = new RenderableManager.Builder(mSubMeshes.length).boundingBox(mBox);
        for (int i = 0; i < mSubMeshes.length; i++) {
            builder.geometry(i, primitiveType, mMesh.getVertexBuffer(), mMesh.getIndexBuffer(),
                            mSubMeshes[i].offset, mSubMeshes[i].minIndex, mSubMeshes[i].maxIndex, mSubMeshes[i].indexCount)
                    .material(i, mMaterialInstances[i]);
        }
        builder.build(mEngine, renderable);
        return renderable;
    }

    protected Material[] loadMaterials(String materialPath) { // 加载材质
        Material material = MaterialUtils.loadMaterial(mContext, mEngine, materialPath);
        if (material != null) {
            return new Material[] {material};
        }
        return null;
    }

    protected Material[] loadMaterials(String[] materialPaths) { // 加载材质
        Material[] materials = new Material[materialPaths.length];
        for (int i = 0; i < materials.length; i++) {
            materials[i] = MaterialUtils.loadMaterial(mContext, mEngine, materialPaths[i]);
        }
        return materials;
    }

    protected MaterialInstance[] getMaterialInstance(Material[] materials) { // 获取材质实例
        MaterialInstance[] materialInstances = new MaterialInstance[materials.length];
        for (int i = 0; i < materials.length; i++) {
            materialInstances[i] = materials[i].createInstance();
        }
        return materialInstances;
    }

    protected MaterialInstance[] getMaterialInstance(Material material, int count) { // 获取材质实例
        MaterialInstance[] materialInstances = new MaterialInstance[count];
        for (int i = 0; i < count; i++) {
            materialInstances[i] = material.createInstance();
        }
        return materialInstances;
    }

    protected Texture[] loadTextures(String texturePath) { // 加载纹理
        Texture texture = TextureUtils.getTexture(mContext, mEngine, texturePath);
        if (texture != null) {
            return new Texture[] {texture};
        }
        return null;
    }

    protected Texture[] loadTextures(String[] texturePaths) { // 加载纹理
        Texture[] textures = new Texture[texturePaths.length];
        for (int i = 0; i < textures.length; i++) {
            textures[i] = TextureUtils.getTexture(mContext, mEngine, texturePaths[i]);
        }
        return textures;
    }
}

        Mesh.java

package com.zhyan8.multitexture.filament;

import com.google.android.filament.Engine;
import com.google.android.filament.IndexBuffer;
import com.google.android.filament.VertexBuffer;
import com.google.android.filament.VertexBuffer.AttributeType;
import com.google.android.filament.VertexBuffer.VertexAttribute;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;

/*
 * 网格
 * 用于管理模型的顶点属性和顶点索引
 */
public class Mesh {
    private Engine mEngine; // Filament引擎
    private VertexBuffer mVertexBuffer; // 顶点属性缓存
    private IndexBuffer mIndexBuffer; // 顶点索引缓存

    public Mesh(Engine engine) {
        mEngine = engine;
    }

    public Mesh(Engine engine, float[] vertices, short[] indices) {
        mEngine = engine;
        setVertices(vertices);
        setIndices(indices);
    }

    public Mesh(Engine engine, VertexPosCol[] vertices, short[] indices) {
        mEngine = engine;
        setVertices(vertices);
        setIndices(indices);
    }

    public Mesh(Engine engine, VertexPosUV[] vertices, short[] indices) {
        mEngine = engine;
        setVertices(vertices);
        setIndices(indices);
    }

    public void setVertices(float[] vertices) { // 设置顶点属性
        mVertexBuffer = getVertexBuffer(vertices);
    }

    public void setVertices(VertexPosCol[] vertices) { // 设置顶点属性
        mVertexBuffer = getVertexBuffer(vertices);
    }

    public void setVertices(VertexPosUV[] vertices) { // 设置顶点属性
        mVertexBuffer = getVertexBuffer(vertices);
    }

    public void setIndices(short[] indices) { // 设置顶点索引
        mIndexBuffer = getIndexBuffer(indices);
    }

    public VertexBuffer getVertexBuffer() { // 获取顶点属性缓存
        return mVertexBuffer;
    }

    public IndexBuffer getIndexBuffer() { // 获取顶点索引缓存
        return mIndexBuffer;
    }

    public void destroy() {
        mEngine.destroyVertexBuffer(mVertexBuffer);
        mEngine.destroyIndexBuffer(mIndexBuffer);
    }

    private VertexBuffer getVertexBuffer(float[] values) { // 获取顶点属性缓存
        ByteBuffer vertexData = getByteBuffer(values);
        int vertexCount = values.length / 3;
        int vertexSize = Float.BYTES * 3;
        VertexBuffer vertexBuffer = new VertexBuffer.Builder()
                .bufferCount(1)
                .vertexCount(vertexCount)
                .attribute(VertexBuffer.VertexAttribute.POSITION, 0, VertexBuffer.AttributeType.FLOAT3, 0, vertexSize)
                .build(mEngine);
        vertexBuffer.setBufferAt(mEngine, 0, vertexData);
        return vertexBuffer;
    }

    private VertexBuffer getVertexBuffer(VertexPosCol[] values) { // 获取顶点属性缓存
        ByteBuffer vertexData = getByteBuffer(values);
        int vertexCount = values.length;
        int vertexSize = VertexPosCol.BYTES;
        VertexBuffer vertexBuffer = new VertexBuffer.Builder()
                .bufferCount(1)
                .vertexCount(vertexCount)
                .attribute(VertexAttribute.POSITION, 0, AttributeType.FLOAT3, 0, vertexSize)
                .attribute(VertexAttribute.COLOR,    0, AttributeType.UBYTE4, 3 * Float.BYTES, vertexSize)
                .normalized(VertexBuffer.VertexAttribute.COLOR)
                .build(mEngine);
        vertexBuffer.setBufferAt(mEngine, 0, vertexData);
        return vertexBuffer;
    }

    private VertexBuffer getVertexBuffer(VertexPosUV[] values) { // 获取顶点属性缓存
        ByteBuffer vertexData = getByteBuffer(values);
        int vertexCount = values.length;
        int vertexSize = VertexPosUV.BYTES;
        VertexBuffer vertexBuffer = new VertexBuffer.Builder()
                .bufferCount(1)
                .vertexCount(vertexCount)
                .attribute(VertexBuffer.VertexAttribute.POSITION, 0, VertexBuffer.AttributeType.FLOAT3, 0, vertexSize)
                .attribute(VertexBuffer.VertexAttribute.UV0,    0, VertexBuffer.AttributeType.FLOAT2, 3 * Float.BYTES, vertexSize)
                .build(mEngine);
        vertexBuffer.setBufferAt(mEngine, 0, vertexData);
        return vertexBuffer;
    }

    private IndexBuffer getIndexBuffer(short[] values) { // 获取顶点索引缓存
        ByteBuffer indexData = getByteBuffer(values);
        int indexCount = values.length;
        IndexBuffer indexBuffer = new IndexBuffer.Builder()
                .indexCount(indexCount)
                .bufferType(IndexBuffer.Builder.IndexType.USHORT)
                .build(mEngine);
        indexBuffer.setBuffer(mEngine, indexData);
        return indexBuffer;
    }

    private ByteBuffer getByteBuffer(float[] values) { // float数组转换为ByteBuffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(values.length * Float.BYTES);
        byteBuffer.order(ByteOrder.nativeOrder());
        for (int i = 0; i < values.length; i++) {
            byteBuffer.putFloat(values[i]);
        }
        byteBuffer.flip();
        return byteBuffer;
    }

    private ByteBuffer getByteBuffer(short[] values) { // short数组转换为ByteBuffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(values.length * Short.BYTES);
        byteBuffer.order(ByteOrder.nativeOrder());
        for (int i = 0; i < values.length; i++) {
            byteBuffer.putShort(values[i]);
        }
        byteBuffer.flip();
        return byteBuffer;
    }

    private ByteBuffer getByteBuffer(VertexPosCol[] values) { // VertexPosCol数组转换为ByteBuffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(values.length * VertexPosCol.BYTES);
        byteBuffer.order(ByteOrder.nativeOrder());
        for (int i = 0; i < values.length; i++) {
            values[i].put(byteBuffer);
        }
        byteBuffer.flip();
        return byteBuffer;
    }

    private ByteBuffer getByteBuffer(VertexPosUV[] values) { // VertexPosUV数组转换为ByteBuffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(values.length * VertexPosUV.BYTES);
        byteBuffer.order(ByteOrder.nativeOrder());
        for (int i = 0; i < values.length; i++) {
            values[i].put(byteBuffer);
        }
        byteBuffer.flip();
        return byteBuffer;
    }

    /*
     * 子网格
     */
    public static class SubMesh {
        public int offset;
        public int minIndex;
        public int maxIndex;
        public int indexCount;

        public SubMesh() {}

        public SubMesh(int offset, int minIndex, int maxIndex, int indexCount) {
            this.offset = offset;
            this.minIndex = minIndex;
            this.maxIndex = maxIndex;
            this.indexCount = indexCount;
        }
    }

    /*
     * 顶点数据(位置+颜色)
     * 包含顶点位置和颜色
     */
    public static class VertexPosCol {
        public static int BYTES = 16;
        public float x;
        public float y;
        public float z;
        public int color;

        public VertexPosCol() {}

        public VertexPosCol(float x, float y, float z, int color) {
            this.x = x;
            this.y = y;
            this.z = z;
            this.color = color;
        }

        public ByteBuffer put(ByteBuffer buffer) { // VertexPosCol转换为ByteBuffer
            buffer.putFloat(x);
            buffer.putFloat(y);
            buffer.putFloat(z);
            buffer.putInt(color);
            return buffer;
        }
    }

    /*
     * 顶点数据(位置+纹理坐标)
     * 包含顶点位置和纹理坐标
     */
    public static class VertexPosUV {
        public static int BYTES = 20;
        public float x;
        public float y;
        public float z;
        public float u;
        public float v;

        public VertexPosUV() {}

        public VertexPosUV(float x, float y, float z, float u, float v) {
            this.x = x;
            this.y = y;
            this.z = z;
            this.u = u;
            this.v = v;
        }

        public ByteBuffer put(ByteBuffer buffer) { // VertexPosUV转换为ByteBuffer
            buffer.putFloat(x);
            buffer.putFloat(y);
            buffer.putFloat(z);
            buffer.putFloat(u);
            buffer.putFloat(v);
            return buffer;
        }
    }
}

        MaterialUtils.java

package com.zhyan8.multitexture.filament.utils;

import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;

import com.google.android.filament.Engine;
import com.google.android.filament.Material;

import java.io.FileInputStream;
import java.io.IOException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;

/*
 * 材质工具类
 */
public class MaterialUtils {
    private static String TAG = "MaterialUtils";

    public static Material loadMaterial(Context context, Engine engine, String materialPath) { // 加载材质
        Buffer buffer = readUncompressedAsset(context, materialPath);
        if (buffer != null) {
            Material material = (new Material.Builder()).payload(buffer, buffer.remaining()).build(engine);
            material.compile(
                    Material.CompilerPriorityQueue.HIGH,
                    Material.UserVariantFilterBit.ALL,
                    new Handler(Looper.getMainLooper()),
                    () -> Log.i(TAG, "Material " + material.getName() + " compiled."));
            engine.flush();
            return material;
        }
        return null;
    }

    private static Buffer readUncompressedAsset(Context context, String assetPath) { // 加载资源
        ReadableByteChannel src = null;
        FileInputStream fis = null;
        ByteBuffer dist = null;
        try {
            AssetFileDescriptor fd = context.getAssets().openFd(assetPath);
            fis = fd.createInputStream();
            dist = ByteBuffer.allocate((int) fd.getLength());
            src = Channels.newChannel(fis);
            src.read(dist);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (src != null) {
                try {
                    src.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        if (dist != null) {
            return dist.rewind();
        }
        return null;
    }
}

        TextureUtils.java

package com.zhyan8.multitexture.filament.utils;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.util.Log;

import com.google.android.filament.Engine;
import com.google.android.filament.Texture;
import com.google.android.filament.android.TextureHelper;

import java.io.IOException;
import java.io.InputStream;

/*
 * 纹理工具类
 */
public class TextureUtils {
    private static String TAG = "TextureUtils";

    public static Texture getTexture(Context context, Engine engine, String texturePath) { // 获取Texture
        Bitmap bitmap = loadBitmapFromAsset(context, texturePath);
        if (bitmap != null) {
            return generateTexture(engine, bitmap);
        }
        return null;
    }

    public static Texture getTexture(Context context, Engine engine, int resourceId) { // 获取Texture
        Bitmap bitmap = loadBitmapFromDrawable(context, resourceId);
        if (bitmap != null) {
            return generateTexture(engine, bitmap);
        }
        return null;
    }

    private static Texture generateTexture(Engine engine, Bitmap bitmap) { // 生成Texture
        Texture texture = new Texture.Builder()
                .width(bitmap.getWidth())
                .height(bitmap.getHeight())
                .sampler(Texture.Sampler.SAMPLER_2D)
                .format(Texture.InternalFormat.SRGB8_A8)
                .levels(0xff)
                .build(engine);
        TextureHelper.setBitmap(engine, texture, 0, bitmap, new Handler(), () ->
                Log.i(TAG, "getTexture, Bitmap is released.")
        );
        texture.generateMipmaps(engine);
        return texture;
    }

    private static Bitmap loadBitmapFromAsset(Context context, String assetPath) { // 从asset中加载bitmap
        InputStream inputStream = null;
        Bitmap bitmap = null;
        try {
            inputStream = context.getAssets().open(assetPath);
            bitmap = BitmapFactory.decodeStream(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return bitmap;
    }

    private static Bitmap loadBitmapFromDrawable(Context context, int resourceId) { // 从drawable中加载bitmap
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPremultiplied = true;
        Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resourceId, options);
        return bitmap;
    }
}

2.2 Business category

        MainActivity.java

package com.zhyan8.multitexture;

import android.os.Bundle;

import androidx.appcompat.app.AppCompatActivity;

import com.zhyan8.multitexture.filament.FLSurfaceView;

public class MainActivity extends AppCompatActivity {
    private FLSurfaceView mFLSurfaceView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mFLSurfaceView = new MyFLSurfaceView(this);
        setContentView(mFLSurfaceView);
        mFLSurfaceView.init();
        mFLSurfaceView.setRenderMode(FLSurfaceView.RENDERMODE_CONTINUOUSLY);
    }

    @Override
    public void onResume() {
        super.onResume();
        mFLSurfaceView.onResume();
    }

    @Override
    public void onPause() {
        super.onPause();
        mFLSurfaceView.onPause();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mFLSurfaceView.onDestroy();
    }
}

        MyFLSurfaceView.java

package com.zhyan8.multitexture;

import android.content.Context;

import com.google.android.filament.Camera;
import com.zhyan8.multitexture.filament.BaseModel;
import com.zhyan8.multitexture.filament.FLSurfaceView;

public class MyFLSurfaceView extends FLSurfaceView {
    private BaseModel mMyModel;
    public MyFLSurfaceView(Context context) {
        super(context);
    }

    public void init() {
        mSkyboxColor = new float[] {0.35f, 0.35f, 0.35f, 1};
        super.init();
    }

    @Override
    public void onDestroy() {
        mMyModel.destroy();
        super.onDestroy();
    }

    @Override
    protected void setupScene() { // 设置Scene参数
        mMyModel = new Cube(getContext(), mEngine);
        mScene.addEntity(mMyModel.getRenderable());
        addRenderCallback(mMyModel.getRenderCallback());
    }

    @Override
    protected void onResized(int width, int height) {
        double zoom = 0.25;
        double aspect = (double) width / (double) height;
        mCamera.setProjection(Camera.Projection.PERSPECTIVE,
                -aspect * zoom, aspect * zoom, -zoom, zoom, 0.3, 100);
        float[] eye = new float[] {1, 1, 1.5f};
        float[] center = new float[] {0, 0, 0};
        float[] up = new float[] {0, 1, 0};
        mCamera.lookAt(eye[0], eye[1], eye[2],center[0], center[1], center[2], up[0], up[1], up[2]);
    }
}

        Cube.java

package com.zhyan8.multitexture;

import android.content.Context;
import android.opengl.Matrix;

import com.google.android.filament.Box;
import com.google.android.filament.Engine;
import com.google.android.filament.RenderableManager.PrimitiveType;
import com.google.android.filament.TextureSampler;
import com.zhyan8.multitexture.filament.BaseModel;
import com.zhyan8.multitexture.filament.Mesh;
import com.zhyan8.multitexture.filament.Mesh.SubMesh;
import com.zhyan8.multitexture.filament.Mesh.VertexPosUV;

public class Cube extends BaseModel {
    private String materialPaths = "materials/square.filamat";
    private String[] texturePaths = new String[] {
            "textures/a1.jpg", "textures/a2.jpg", "textures/a3.jpg",
            "textures/a4.jpg", "textures/a6.jpg", "textures/a5.jpg"
    };
    private VertexPosUV[] mVertices; // 顶点坐标
    private short[] mIndices; // 顶点索引
    private float[] mModelMatrix; // 模型变换矩阵
    private float[] mRotateAxis; // 旋转轴
    private float mRotateAgree = 0; // 旋转角度

    public Cube(Context context, Engine engine) {
        super(context, engine);
        init();
    }

    private void init() {
        mBox = new Box(0.0f, 0.0f, 0.0f, 2.0f, 2.0f, 2.0f);
        mVertices = getVertices(0.5f);
        mIndices = getIndices();
        mMesh = new Mesh(mEngine, mVertices, mIndices);
        mSubMeshes = getSubMesh();
        mTextures = loadTextures(texturePaths);
        mMaterials = loadMaterials(materialPaths);
        mMaterialInstances = getMaterialInstance(mMaterials[0], mTextures.length);
        TextureSampler textureSampler = new TextureSampler();
        for (int i = 0; i < mMaterialInstances.length; i++) {
            mMaterialInstances[i].setParameter("mainTex", mTextures[i], textureSampler);
        }
        mRenderable = getRenderable(PrimitiveType.TRIANGLES, mIndices.length);
        mTransformComponent = mTransformManager.getInstance(mRenderable);
        mRenderCallback = () -> renderCallback();
        mModelMatrix = new float[16];
        mRotateAxis = new float[] { 0.5f, 1f, 1f };
    }

    private void renderCallback() {
        mRotateAgree = (mRotateAgree + 1) % 360;
        mRotateAxis[0] = mRotateAgree / 180f - 1;
        mRotateAxis[1] = (float) Math.sin(mRotateAgree / 180f * Math.PI * 0.7f);
        mRotateAxis[2] = (float) Math.cos(mRotateAgree / 180f * Math.PI * 0.5f);
        Matrix.setRotateM(mModelMatrix, 0, mRotateAgree, mRotateAxis[0], mRotateAxis[1], mRotateAxis[2]);
        mTransformManager.setTransform(mTransformComponent, mModelMatrix);
    }

    private VertexPosUV[] getVertices(float r) {
        VertexPosUV[] vertices = new VertexPosUV[] {
                // 前面
                new VertexPosUV(r, r, r, 0f, 1f), // 0
                new VertexPosUV(-r, r, r, 1f, 1f), // 1
                new VertexPosUV(-r, -r, r, 1f, 0f), // 2
                new VertexPosUV(r, -r, r, 0f, 0f), // 3
                // 后面
                new VertexPosUV(r, r, -r, 0f, 1f), // 4
                new VertexPosUV(-r, r, -r, 1f, 1f), // 5
                new VertexPosUV(-r, -r, -r, 1f, 0f), // 6
                new VertexPosUV(r, -r, -r, 0f, 0f), // 7
                // 上面
                new VertexPosUV(r, r, r, 0f, 1f), // 8
                new VertexPosUV(r, r, -r, 1f, 1f), // 9
                new VertexPosUV(-r, r, -r, 1f, 0f), // 10
                new VertexPosUV(-r, r, r, 0f, 0f), // 11
                // 下面
                new VertexPosUV(r, -r, r, 0f, 1f), // 12
                new VertexPosUV(r, -r, -r, 1f, 1f), // 13
                new VertexPosUV(-r, -r, -r, 1f, 0f), // 14
                new VertexPosUV(-r, -r, r, 0f, 0f), // 15
                // 右面
                new VertexPosUV(r, r, r, 0f, 1f), // 16
                new VertexPosUV(r, r, -r, 1f, 1f), // 17
                new VertexPosUV(r, -r, -r, 1f, 0f), // 18
                new VertexPosUV(r, -r, r, 0f, 0f), // 19
                // 左面
                new VertexPosUV(-r, r, r, 0f, 1f), // 20
                new VertexPosUV(-r, r, -r, 1f, 1f), // 21
                new VertexPosUV(-r, -r, -r, 1f, 0f), // 22
                new VertexPosUV(-r, -r, r, 0f, 0f) // 23
        };
        return vertices;
    }

    private short[] getIndices() {
        short[] indices = new short[] {
                0, 1, 2, 0, 2, 3, // 前面
                4, 6, 5, 4, 7, 6, // 上面
                8, 9, 10, 8, 10, 11, // 右面
                12, 14, 13, 12, 15, 14, // 后面
                16, 18, 17, 16, 19, 18, // 下面
                20, 21, 22, 20, 22, 23 // 左面
        };
        return indices;
    }

    private SubMesh[] getSubMesh() {
        int subMeshCount = 6;
        int vertexCount = 6;
        SubMesh[] subMeshes = new SubMesh[subMeshCount];
        for (int i = 0; i < subMeshCount; i++) {
            int offset = i * vertexCount;
            int minIndex = offset;
            int maxIndex = minIndex + vertexCount - 1;
            subMeshes[i] = new SubMesh(offset, minIndex, maxIndex, vertexCount);
        }
        return subMeshes;
    }
}

        square.mat

material {
    name : square,

    shadingModel : unlit, // 禁用所有lighting
    // 自定义变量参数
    parameters : [
        {
            type : sampler2d,
            name : mainTex
        }
    ],
    // 顶点着色器入参MaterialVertexInputs中需要的顶点属性
    requires : [
        uv0
    ]
}

fragment {
    void material(inout MaterialInputs material) {
        prepareMaterial(material); // 在方法返回前必须回调该函数
        material.baseColor = texture(materialParams_mainTex, getUV0());
    }
}

        transform.bat

@echo off
setlocal enabledelayedexpansion
set "srcFolder=../src/main/materials"
set "distFolder=../src/main/assets/materials"

for %%f in ("%srcFolder%\*.mat") do (
	set "matfile=%%~nf"
	matc -p mobile -a opengl -o "!matfile!.filamat" "%%f"
    move "!matfile!.filamat" "%distFolder%\!matfile!.filamat"
)

echo Processing complete.
pause

        Note: The matc.exe file and transform.bat file need to be placed in the same directory. matc.exe is derived from the exe file compiled and generated during the Filament environment setup . Double-click the transform.bat file to automatically convert all mat files under /src/main/materials/ into filamat files and move them to the /src/main/assets/materials/ directory.

        The running effect is as follows.
 

Guess you like

Origin blog.csdn.net/m0_37602827/article/details/135188945