美文网首页Filament
【Filament】壁纸

【Filament】壁纸

作者: LittleFatSheep | 来源:发表于2024-02-24 23:25 被阅读0次

    1 前言

    本文将使用 Filament 制作壁纸。Android 中要实现自定义壁纸,需要继承 WallpaperService 和 WallpaperService.Engine,并在 onCreateEngine 方法中返回自定义 Engine 的实例。

    public class MyWallpaperService extends WallpaperService {
    
        @Override
        public Engine onCreateEngine() {
            return new MyEngine();
        }
    
        class MyEngine extends Engine {
            private FLSurfaceView mFLSurfaceView;
    
            @Override
            public void onCreate(SurfaceHolder holder) {
                super.onCreate(holder);
                mFLSurfaceView = new MyFLSurfaceView(MyWallpaperService.this);
                mFLSurfaceView.setSurfaceHolder(holder);
                mFLSurfaceView.init();
            }
    
            @Override
            public void onVisibilityChanged(boolean visible) {
                super.onVisibilityChanged(visible);
                if(visible) {
                    mFLSurfaceView.onResume();
                } else {
                    mFLSurfaceView.onPause();
                }
            }
    
            @Override
            public void onDestroy() {
                super.onDestroy();
                mFLSurfaceView.onDestroy();
            }
        }
    }
    

    读者如果对 Filament 不太熟悉,请回顾以下内容。

    2 壁纸

    本文项目结构如下,完整代码资源 → 基于Filament实现壁纸

    2.1 基础类

    为方便读者将注意力聚焦在 Filament 的输入上,轻松配置复杂的环境依赖逻辑,笔者仿照 OpenGL ES 的写法,抽出了 FLSurfaceView、BaseModel、Mesh、MaterialUtils、MeshUtils、TextureUtils 类。FLSurfaceView 与 GLSurfaceView 的功能类似,承载了渲染环境配置;BaseModel 用于管理模型的网格和材质;Mesh 用于管理模型的顶点属性;MaterialUtils、MeshUtils 和 TextureUtils 中分别提供了一些材质、网格和纹理等相关的工具。

    build.gradle

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

    说明:在项目根目录下的 libs 目录中,需要放入以下 aar 文件,它们源自Filament环境搭建中编译生成的 aar。

    FLSurfaceView.java

    package com.zhyan8.wallpaper.filament.base;
    
    import android.app.Service;
    import android.content.Context;
    import android.graphics.Point;
    import android.view.Choreographer;
    import android.view.Surface;
    import android.view.SurfaceHolder;
    import android.view.WindowManager;
    
    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 {
        public static int RENDERMODE_WHEN_DIRTY = 0; // 用户请求渲染才渲染一帧
        public static int RENDERMODE_CONTINUOUSLY = 1; // 持续渲染
        protected int mRenderMode = RENDERMODE_CONTINUOUSLY; // 渲染模式
        protected Context mContext; // 上下文环境
        protected SurfaceHolder mSurfaceHolder; // 表面持有者(由外部传入)
        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) {
            mContext = context;
            mChoreographer = Choreographer.getInstance();
            mDisplayHelper = new DisplayHelper(context);
            mRenderCallbacks = new ArrayList<>();
        }
    
        public void init() { // 初始化
            setupSurfaceView();
            setupFilament();
            setupView();
            setupScene();
        }
    
        public void setSurfaceHolder(SurfaceHolder surfaceHolder) {
            mSurfaceHolder = surfaceHolder;
        }
    
        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);
            mUiHelper.attachTo(mSurfaceHolder);
        }
    
        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());
                WindowManager windowManager = (WindowManager) mContext.getSystemService(Service.WINDOW_SERVICE);
                mDisplayHelper.attach(mRenderer, windowManager.getDefaultDisplay());
            }
    
            @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 interface RenderCallback {
            void onCall();
        }
    }
    

    BaseModel.java

    package com.zhyan8.wallpaper.filament.base;
    
    import android.content.Context;
    
    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.wallpaper.filament.base.Mesh.Part;
    import com.zhyan8.wallpaper.filament.utils.MaterialUtils;
    import com.zhyan8.wallpaper.filament.utils.TextureUtils;
    import com.zhyan8.wallpaper.filament.utils.TextureUtils.TextureType;
    
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    /**
     * 模型基类
     * 管理模型的网格、材质、渲染id
     */
    public class BaseModel {
        private static String TAG = "BaseModel";
        protected Context mContext; // 上下文
        protected Engine mEngine; // Filament引擎
        protected TransformManager mTransformManager; // 模型变换管理器
        protected Mesh mMesh; // 模型网格
        protected Material[] mMaterials; // 模型材质
        protected MaterialInstance[] mMaterialInstances; // 模型材质实例
        protected Map<String, MaterialInstance> mMaterialMap = new HashMap<>(); // 材质名->材质
        protected Texture[] mTextures; // 纹理
        protected int mRenderable; // 渲染id
        protected int mTransformComponent; // 模型变换组件的id
        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() { // 销毁模型
            mMaterialMap.clear();
            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) { // 获取渲染id
            int renderable = EntityManager.get().create();
            List<Part> parts = mMesh.getParts();
            List<String> materialNames = mMesh.getMaterialNames();
            RenderableManager.Builder builder = new RenderableManager.Builder(parts.size()).boundingBox(mMesh.getBox());
            for (int i = 0; i < parts.size(); i++) {
                Part part = parts.get(i);
                builder.geometry(i, primitiveType, mMesh.getVertexBuffer(), mMesh.getIndexBuffer(),
                                part.offset, part.minIndex, part.maxIndex, part.indexCount);
                MaterialInstance material = getMaterialInstance(materialNames, part.materialID);
                builder.material(i, material);
            }
            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, TextureType type) { // 加载纹理
            Texture texture = TextureUtils.loadTexture(mContext, mEngine, texturePath, type);
            if (texture != null) {
                return new Texture[] {texture};
            }
            return null;
        }
    
        protected Texture[] loadTextures(String[] texturePaths, TextureType type) { // 加载纹理
            Texture[] textures = new Texture[texturePaths.length];
            for (int i = 0; i < textures.length; i++) {
                textures[i] = TextureUtils.loadTexture(mContext, mEngine, texturePaths[i], type);
            }
            return textures;
        }
    
        private MaterialInstance getMaterialInstance(List<String> materialNames, int materialID) { // 获取材质
            MaterialInstance material = null;
            if (materialNames != null && materialNames.size() > materialID && materialID >= 0) {
                String name = materialNames.get(materialID);
                if (mMaterialMap.containsKey(name)) {
                    material = mMaterialMap.get(name);
                }
            }
            if (material == null && mMaterialMap.containsKey("DefaultMaterial")) {
                material = mMaterialMap.get("DefaultMaterial");
            }
            return material;
        }
    }
    

    Mesh.java

    package com.zhyan8.wallpaper.filament.base;
    
    import com.google.android.filament.Box;
    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;
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * 网格
     * 用于管理模型的顶点属性和顶点索引
     */
    public class Mesh {
        private Engine mEngine; // Filament引擎
        private VertexBuffer mVertexBuffer; // 顶点属性缓存
        private IndexBuffer mIndexBuffer; // 顶点索引缓存
        private List<Part> mParts; // 子网格信息
        private Box mBox; // 渲染区域
        private List<String> mMaterialNames; // 材质名
    
        public Mesh(Engine engine) {
            mEngine = engine;
        }
    
        public Mesh(Engine engine, float[] vertices, short[] indices, List<Part> parts, Box box, List<String> materialNames) {
            mEngine = engine;
            setVertices(vertices);
            setIndices(indices);
            setParts(parts, indices.length);
            setBox(box);
            mMaterialNames = materialNames;
        }
    
        public Mesh(Engine engine, VertexBuffer vrtexBuffer, IndexBuffer indexBuffer, List<Part> parts, Box box, List<String> materialNames) {
            mEngine = engine;
            mVertexBuffer = vrtexBuffer;
            mIndexBuffer = indexBuffer;
            mParts = parts;
            setBox(box);
            mMaterialNames = materialNames;
        }
    
        public Mesh(Engine engine, VertexPosCol[] vertices, short[] indices, List<Part> parts, Box box, List<String> materialNames) {
            mEngine = engine;
            setVertices(vertices);
            setIndices(indices);
            setParts(parts, indices.length);
            setBox(box);
            mMaterialNames = materialNames;
        }
    
        public Mesh(Engine engine, VertexPosUV[] vertices, short[] indices, List<Part> parts, Box box, List<String> materialNames) {
            mEngine = engine;
            setVertices(vertices);
            setIndices(indices);
            setParts(parts, indices.length);
            setBox(box);
            mMaterialNames = materialNames;
        }
    
        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 void setParts(List<Part> parts, int count) { // 设置顶点索引
            if (parts == null || parts.size() == 0) {
                mParts = new ArrayList<>();
                mParts.add(new Part(0, count, 0, count - 1));
            } else {
                mParts = parts;
            }
        }
    
        public void setBox(Box box) { // 渲染区域
            if (box == null) {
                mBox = new Box(0, 0, 0, 1, 1, 1);
            } else {
                mBox = box;
            }
        }
    
        public VertexBuffer getVertexBuffer() { // 获取顶点属性缓存
            return mVertexBuffer;
        }
    
        public IndexBuffer getIndexBuffer() { // 获取顶点索引缓存
            return mIndexBuffer;
        }
    
        public List<Part> getParts() { // 获取顶点索引缓存
            return mParts;
        }
    
        public Box getBox() {
            return mBox;
        }
    
        public List<String> getMaterialNames() {
            return mMaterialNames;
        }
    
        public void destroy() {
            mEngine.destroyVertexBuffer(mVertexBuffer);
            mEngine.destroyIndexBuffer(mIndexBuffer);
            if (mParts != null) {
                mParts.clear();
            }
            if (mMaterialNames != null) {
                mMaterialNames.clear();
            }
        }
    
        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(VertexAttribute.POSITION, 0, 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(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(VertexAttribute.POSITION, 0, AttributeType.FLOAT3, 0, vertexSize)
                    .attribute(VertexAttribute.UV0,    0, 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 Part {
            public int offset = 0;
            public int indexCount = 0;
            public int minIndex = 0;
            public int maxIndex = 0;
            public int materialID = -1;
            public Box aabb = new Box();
    
            public Part() {}
    
            public Part(int offset, int indexCount, int minIndex, int maxIndex) {
                this.offset = offset;
                this.indexCount = indexCount;
                this.minIndex = minIndex;
                this.maxIndex = maxIndex;
            }
    
            public Part(int offset, int indexCount, int minIndex, int maxIndex, int materialID, Box aabb) {
                this.offset = offset;
                this.indexCount = indexCount;
                this.minIndex = minIndex;
                this.maxIndex = maxIndex;
                this.materialID = materialID;
                this.aabb = aabb;
            }
        }
    
        /**
         * 顶点数据(位置+颜色)
         * 包含顶点位置和颜色
         */
        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.wallpaper.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) { // 加载资源
            ByteBuffer dist = null;
            try {
                AssetFileDescriptor fd = context.getAssets().openFd(assetPath);
                try(FileInputStream fis = fd.createInputStream()) {
                    dist = ByteBuffer.allocate((int) fd.getLength());
                    try (ReadableByteChannel src = Channels.newChannel(fis)) {
                        src.read(dist);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (dist != null) {
                return dist.rewind();
            }
            return null;
        }
    }
    

    MeshUtils.java

    package com.zhyan8.wallpaper.filament.utils;
    
    import android.content.Context;
    import android.util.Log;
    
    import com.google.android.filament.Box;
    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 com.zhyan8.wallpaper.filament.base.Mesh;
    import com.zhyan8.wallpaper.filament.base.Mesh.Part;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.nio.ByteBuffer;
    import java.nio.ByteOrder;
    import java.nio.channels.Channels;
    import java.nio.channels.ReadableByteChannel;
    import java.nio.charset.Charset;
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * 网格工具类
     */
    public class MeshUtils {
        private static final String FILAMESH_FILE_IDENTIFIER = "FILAMESH";
        private static final long HEADER_FLAG_SNORM16_UV = 0x2L;
        private static final long MAX_UINT32 = 4294967295L;
    
        public static Mesh loadMesh(Context context, Engine engine, String meshPath) {
            try (InputStream inputStream = context.getAssets().open(meshPath)) {
                Header header = readHeader(inputStream);
                ReadableByteChannel channel = Channels.newChannel(inputStream);
                ByteBuffer vertexBufferData = readSizedData(channel, header.verticesSizeInBytes);
                ByteBuffer indexBufferData = readSizedData(channel, header.indicesSizeInBytes);
                List<Part> parts = readParts(header, inputStream);
                List<String> materialNames = readMaterials(inputStream);
    
                VertexBuffer vertexBuffer = createVertexBuffer(engine, header, vertexBufferData);
                IndexBuffer indexBuffer = createIndexBuffer(engine, header, indexBufferData);
                return new Mesh(engine, vertexBuffer, indexBuffer, parts, header.aabb, materialNames);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        private static Header readHeader(InputStream input) { // 读取文件头信息
            Header header = new Header();
            if (!readMagicNumber(input)) {
                Log.e("Filament", "Invalid filamesh file.");
                return header;
            }
            header.versionNumber = readUIntLE(input);
            header.parts = readUIntLE(input);
            header.aabb = new Box(
                    readFloat32LE(input), readFloat32LE(input), readFloat32LE(input),
                    readFloat32LE(input), readFloat32LE(input), readFloat32LE(input));
            header.flags = readUIntLE(input);
            header.posOffset = readUIntLE(input);
            header.positionStride = readUIntLE(input);
            header.tangentOffset = readUIntLE(input);
            header.tangentStride = readUIntLE(input);
            header.colorOffset = readUIntLE(input);
            header.colorStride = readUIntLE(input);
            header.uv0Offset = readUIntLE(input);
            header.uv0Stride = readUIntLE(input);
            header.uv1Offset = readUIntLE(input);
            header.uv1Stride = readUIntLE(input);
            header.totalVertices = readUIntLE(input);
            header.verticesSizeInBytes = readUIntLE(input);
            header.indices16Bit = readUIntLE(input);
            header.totalIndices = readUIntLE(input);
            header.indicesSizeInBytes = readUIntLE(input);
            header.valid = true;
            return header;
        }
    
        private static ByteBuffer readSizedData(ReadableByteChannel channel, int sizeInBytes) { // 读取模型顶点数据
            ByteBuffer buffer = ByteBuffer.allocateDirect(sizeInBytes);
            buffer.order(ByteOrder.LITTLE_ENDIAN);
            try {
                channel.read(buffer);
            } catch (IOException e) {
                e.printStackTrace();
            }
            buffer.flip();
            return buffer;
        }
    
        private static List<Part> readParts(Header header, InputStream input) { // 读取子网格属性
            List<Part> parts = new ArrayList<>(header.parts);
            for (int i = 0; i < header.parts; i++) {
                Part p = new Part();
                p.offset = readUIntLE(input);
                p.indexCount = readUIntLE(input);
                p.minIndex = readUIntLE(input);
                p.maxIndex = readUIntLE(input);
                p.materialID = readUIntLE(input);
                float minX = readFloat32LE(input);
                float minY = readFloat32LE(input);
                float minZ = readFloat32LE(input);
                float maxX = readFloat32LE(input);
                float maxY = readFloat32LE(input);
                float maxZ = readFloat32LE(input);
                p.aabb = new Box(minX, minY, minZ, maxX, maxY, maxZ);
                parts.add(p);
            }
            return parts;
        }
    
        private static boolean readMagicNumber(InputStream input) { // 读取魔法数字, 用于判断是否是有效的filamesh文件
            byte[] temp = new byte[FILAMESH_FILE_IDENTIFIER.length()];
            int bytesRead = 0;
            try {
                bytesRead = input.read(temp);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            if (bytesRead != FILAMESH_FILE_IDENTIFIER.length()) {
                return false;
            }
            String tempS = new String(temp, Charset.forName("UTF-8"));
            return tempS.equals(FILAMESH_FILE_IDENTIFIER);
        }
    
        private static List<String> readMaterials(InputStream input) { // 读取材质
            int numMaterials = readUIntLE(input);
            List<String> materials = new ArrayList<>(numMaterials);
            for (int i = 0; i < numMaterials; i++) {
                int dataLength = readUIntLE(input);
                byte[] data = new byte[dataLength];
                try {
                    input.read(data);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    input.skip(1);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                materials.add(new String(data, Charset.forName("UTF-8")));
            }
            return materials;
        }
    
        private static IndexBuffer createIndexBuffer(Engine engine, Header header, ByteBuffer data) { // 创建顶点索引缓冲
            IndexBuffer.Builder.IndexType indexType = (header.indices16Bit != 0) ?
                    IndexBuffer.Builder.IndexType.USHORT : IndexBuffer.Builder.IndexType.UINT;
            IndexBuffer buffer = new IndexBuffer.Builder()
                    .bufferType(indexType)
                    .indexCount(header.totalIndices)
                    .build(engine);
            buffer.setBuffer(engine, data);
            return buffer;
        }
    
        private static VertexBuffer createVertexBuffer(Engine engine, Header header, ByteBuffer data) { // 创建顶点属性缓冲
            AttributeType uvType = uvType(header);
            VertexBuffer.Builder vertexBufferBuilder = new VertexBuffer.Builder()
                    .bufferCount(1)
                    .vertexCount(header.totalVertices)
                    .normalized(VertexAttribute.COLOR)
                    .normalized(VertexAttribute.TANGENTS)
                    .attribute(VertexAttribute.POSITION, 0, AttributeType.HALF4, header.posOffset, header.positionStride)
                    .attribute(VertexAttribute.TANGENTS, 0, AttributeType.SHORT4, header.tangentOffset, header.tangentStride)
                    .attribute(VertexAttribute.COLOR, 0, AttributeType.UBYTE4, header.colorOffset, header.colorStride)
                    .attribute(VertexAttribute.UV0, 0, uvType, header.uv0Offset, header.uv0Stride)
                    .normalized(VertexAttribute.UV0, uvNormalized(header));
            if (header.uv1Offset != MAX_UINT32 && header.uv1Stride != MAX_UINT32 && header.uv1Offset > -1 && header.uv1Stride > -1) {
                vertexBufferBuilder
                        .attribute(VertexAttribute.UV1, 0, uvType, header.uv1Offset, header.uv1Stride)
                        .normalized(VertexAttribute.UV1, uvNormalized(header));
            }
            VertexBuffer buffer = vertexBufferBuilder.build(engine);
            buffer.setBufferAt(engine, 0, data);
            return buffer;
        }
    
        private static AttributeType uvType(Header header) { // UV坐标的精度类型
            if (uvNormalized(header)) {
                return AttributeType.SHORT2;
            }
            return AttributeType.HALF2;
        }
    
        private static boolean uvNormalized(Header header) { // uv坐标是否已正则化
            return (header.flags & HEADER_FLAG_SNORM16_UV) != 0L;
        }
    
        private static int readIntLE(InputStream input) { // 获取输入流中Little Endian格式的整数
            try {
                return (input.read() & 0xff) |
                        ((input.read() & 0xff) << 8) |
                        ((input.read() & 0xff) << 16) |
                        ((input.read() & 0xff) << 24);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return 0;
        }
    
        private static int readUIntLE(InputStream input) { // 获取输入流中Little Endian格式的无符号整数
            return (int) (readIntLE(input) & 0xFFFFFFFFL);
        }
    
        private static float readFloat32LE(InputStream input) { // 获取输入流中Little Endian格式的浮点数
            byte[] bytes = new byte[4];
            try {
                input.read(bytes, 0, 4);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getFloat();
        }
    }
    
    /**
     * 网格文件头
     */
    class Header {
        boolean valid = false;
        int versionNumber = 0;
        int parts = 0;
        Box aabb = new Box();
        int flags = 0;
        int posOffset = 0;
        int positionStride = 0;
        int tangentOffset = 0;
        int tangentStride = 0;
        int colorOffset = 0;
        int colorStride = 0;
        int uv0Offset = 0;
        int uv0Stride = 0;
        int uv1Offset = 0;
        int uv1Stride = 0;
        int totalVertices = 0;
        int verticesSizeInBytes = 0;
        int indices16Bit = 0;
        int totalIndices = 0;
        int indicesSizeInBytes = 0;
    }
    

    TextureUtils.java

    package com.zhyan8.wallpaper.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;
    import java.nio.ByteBuffer;
    
    /**
     * 纹理工具类
     */
    public class TextureUtils {
        private static String TAG = "TextureUtils";
        public static final boolean SKIP_BITMAP_COPY = true;
    
        public static Texture loadTexture(Context context, Engine engine, String texturePath, TextureType type) { // 获取Texture
            Bitmap bitmap = loadBitmapFromAsset(context, texturePath);
            if (bitmap != null) {
                return generateTexture(engine, bitmap, type);
            }
            return null;
        }
    
        public static Texture loadTexture(Context context, Engine engine, int resourceId, TextureType type) { // 获取Texture
            Bitmap bitmap = loadBitmapFromDrawable(context, resourceId, type);
            if (bitmap != null) {
                return generateTexture(engine, bitmap, type);
            }
            return null;
        }
    
        private static Texture generateTexture(Engine engine, Bitmap bitmap, TextureType type) { // 生成Texture
            Texture texture = new Texture.Builder()
                    .width(bitmap.getWidth())
                    .height(bitmap.getHeight())
                    .sampler(Texture.Sampler.SAMPLER_2D)
                    .format(internalFormat(type))
                    .levels(0xff)
                    .build(engine);
            if (SKIP_BITMAP_COPY) {
                TextureHelper.setBitmap(engine, texture, 0, bitmap, new Handler(), () ->
                        Log.i(TAG, "getTexture, Bitmap is released.")
                );
            } else {
                ByteBuffer buffer = ByteBuffer.allocateDirect(bitmap.getByteCount());
                bitmap.copyPixelsToBuffer(buffer);
                buffer.flip();
                Texture.PixelBufferDescriptor descriptor = new Texture.PixelBufferDescriptor(
                        buffer,
                        format(bitmap),
                        type(bitmap));
                texture.setImage(engine, 0, descriptor);
            }
            texture.generateMipmaps(engine);
            return texture;
        }
    
        private static Bitmap loadBitmapFromAsset(Context context, String assetPath) { // 从asset中加载bitmap
            Bitmap bitmap = null;
            try (InputStream inputStream = context.getAssets().open(assetPath)) {
                bitmap = BitmapFactory.decodeStream(inputStream);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return bitmap;
        }
    
        private static Bitmap loadBitmapFromDrawable(Context context, int resourceId, TextureType type) { // 从drawable中加载bitmap
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inPremultiplied = (type == TextureType.COLOR);
            Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resourceId, options);
            return bitmap;
        }
    
        private static Texture.InternalFormat internalFormat(TextureType type) { // 获取纹理类型对应的内部格式
            switch (type) {
                case COLOR:
                    return Texture.InternalFormat.SRGB8_A8;
                case NORMAL:
                case DATA:
                    return Texture.InternalFormat.RGBA8;
                default:
                    throw new IllegalArgumentException("Unsupported texture type: " + type);
            }
        }
    
        private static Texture.Format format(Bitmap bitmap) { // 获取bitmap的纹理类型对应的Texture的纹理格式
            switch (bitmap.getConfig().name()) {
                case "ALPHA_8":
                    return Texture.Format.ALPHA;
                case "RGB_565":
                    return Texture.Format.RGB;
                case "ARGB_8888":
                case "RGBA_F16":
                    return Texture.Format.RGBA;
                default:
                    throw new IllegalArgumentException("Unknown bitmap configuration");
            }
        }
    
        private static Texture.Type type(Bitmap bitmap) { // 获取bitmap的纹理类型对应的Texture的纹理类型
            switch (bitmap.getConfig().name()) {
                case "ALPHA_8":
                    return Texture.Type.USHORT;
                case "RGB_565":
                    return Texture.Type.USHORT_565;
                case "ARGB_8888":
                    return Texture.Type.UBYTE;
                case "RGBA_F16":
                    return Texture.Type.HALF;
                default:
                    throw new IllegalArgumentException("Unsupported bitmap configuration");
            }
        }
    
        /**
         * 纹理类型
         */
        public enum TextureType {
            COLOR,
            NORMAL,
            DATA
        }
    }
    

    2.2 业务类

    AndroidManifest.xml

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android">
    
        <uses-permission android:name = "android.permission.SET_WALLPAPER"/>
    
        <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            android:theme="@style/Theme.FilamentDemo">
            <activity
                android:name=".MainActivity"
                android:exported="true">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
    
            <service android:name=".MyWallpaperService"
                android:label="@string/app_name"
                android:permission="android.permission.BIND_WALLPAPER"
                android:exported = "true">
    
                <intent-filter>
                    <action android:name="android.service.wallpaper.WallpaperService" />
                </intent-filter>
    
                <meta-data
                    android:name="android.service.wallpaper"
                    android:resource="@xml/wallpaper" />
            </service>
        </application>
    
    </manifest>
    

    wallpaper.xml

    <?xml version="1.0" encoding="utf-8"?>
    <wallpaper xmlns:android ="http://schemas.android.com/apk/res/android"/>
    

    MainActivity.java

    package com.zhyan8.wallpaper;
    
    import android.app.WallpaperManager;
    import android.content.ComponentName;
    import android.content.Intent;
    import android.os.Bundle;
    import android.widget.Button;
    
    import androidx.appcompat.app.AppCompatActivity;
    
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            Button button = findViewById(R.id.set_btn);
            button.setOnClickListener(view -> {
                startLiveWallpaperPreView(getPackageName(), MyWallpaperService.class.getName());
            });
        }
    
        public void startLiveWallpaperPreView(String packageName, String classFullName) {
            ComponentName componentName = new ComponentName(packageName, classFullName);
            Intent intent;
            if (android.os.Build.VERSION.SDK_INT < 16) {
                intent = new Intent(WallpaperManager.ACTION_LIVE_WALLPAPER_CHOOSER);
            } else {
                intent = new Intent("android.service.wallpaper.CHANGE_LIVE_WALLPAPER");
                intent.putExtra("android.service.wallpaper.extra.LIVE_WALLPAPER_COMPONENT", componentName);
            }
            startActivity(intent);
        }
    }
    

    activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".MainActivity">
    
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/set_btn"
            android:text="设置动态壁纸" />
    
    </LinearLayout>
    

    MyWallpaperService

    package com.zhyan8.wallpaper;
    
    import android.graphics.PixelFormat;
    import android.service.wallpaper.WallpaperService;
    import android.view.SurfaceHolder;
    
    import com.zhyan8.wallpaper.filament.base.FLSurfaceView;
    
    public class MyWallpaperService extends WallpaperService {
    
        @Override
        public Engine onCreateEngine() {
            return new MyEngine();
        }
    
        class MyEngine extends Engine {
            private FLSurfaceView mFLSurfaceView;
    
            @Override
            public void onCreate(SurfaceHolder holder) {
                super.onCreate(holder);
                holder.setSizeFromLayout();
                holder.setFormat(PixelFormat.RGBA_8888);
    
                mFLSurfaceView = new MyFLSurfaceView(MyWallpaperService.this);
                mFLSurfaceView.setSurfaceHolder(holder);
                mFLSurfaceView.setRenderMode(FLSurfaceView.RENDERMODE_CONTINUOUSLY);
                mFLSurfaceView.init();
            }
    
            @Override
            public void onVisibilityChanged(boolean visible) {
                super.onVisibilityChanged(visible);
                if(visible) {
                    mFLSurfaceView.onResume();
                } else {
                    mFLSurfaceView.onPause();
                }
            }
    
            @Override
            public void onDestroy() {
                super.onDestroy();
                mFLSurfaceView.onDestroy();
            }
        }
    }
    

    MyFLSurfaceView.java

    package com.zhyan8.wallpaper;
    
    import android.content.Context;
    
    import com.google.android.filament.Camera;
    import com.zhyan8.wallpaper.filament.base.BaseModel;
    import com.zhyan8.wallpaper.filament.base.FLSurfaceView;
    
    public class MyFLSurfaceView extends FLSurfaceView {
        private BaseModel mSquare;
    
        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() {
            mSquare.destroy();
            super.onDestroy();
        }
    
        @Override
        protected void setupScene() { // 设置Scene参数
            mSquare = new Square(mContext, mEngine);
            mScene.addEntity(mSquare.getRenderable());
        }
    
        @Override
        protected void onResized(int width, int height) {
            mCamera.setProjection(Camera.Projection.ORTHO, -1, 1, -1, 1, 0, 10);
        }
    }
    

    Square.java

    package com.zhyan8.wallpaper;
    
    import android.content.Context;
    
    import com.google.android.filament.Engine;
    import com.google.android.filament.MaterialInstance;
    import com.google.android.filament.RenderableManager.PrimitiveType;
    import com.google.android.filament.TextureSampler;
    import com.zhyan8.wallpaper.filament.base.BaseModel;
    import com.zhyan8.wallpaper.filament.utils.MeshUtils;
    import com.zhyan8.wallpaper.filament.utils.TextureUtils.TextureType;
    
    public class Square extends BaseModel {
        private String materialPath = "materials/square.filamat";
        private String meshPath = "models/square.filamesh";
    
        public Square(Context context, Engine engine) {
            super(context, engine);
            init();
        }
    
        private void init() {
            mMaterials = loadMaterials(materialPath);
            mMaterialInstances = getMaterialInstance(mMaterials);
            setupMaterial(mMaterialInstances[0]);
            mMaterialMap.put("DefaultMaterial", mMaterialInstances[0]);
            mMesh = MeshUtils.loadMesh(mContext, mEngine, meshPath);
            mMesh.getBox().setHalfExtent(1, 1, 0.01f);
            mRenderable = getRenderable(PrimitiveType.TRIANGLES);
        }
    
        private void setupMaterial(MaterialInstance materialInstance) {
            mTextures = loadTextures("textures/a1.jpg", TextureType.COLOR);
            materialInstance.setParameter("mainTex", mTextures[0], new TextureSampler());
        }
    }
    

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

    square.obj

    # 正方体模型
    
    # 顶点位置
    v -1.0 -1.0 0.0  # V1(左下)
    v 1.0 -1.0 0.0   # V2(右下)
    v 1.0 1.0 0.0    # V3(右上)
    v -1.0 1.0 0.0   # V4(左上)
    
    # 纹理坐标
    vt 0.0 0.0  # VT1
    vt 1.0 0.0  # VT2
    vt 1.0 1.0  # VT3
    vt 0.0 1.0  # VT4
    
    # 法线
    vn 0.0 0.0 1.0  # VN1
    
    # 面(v/vt/vn)
    f 1/1/1 2/2/1 3/3/1
    f 1/1/1 3/3/1 4/4/1
    

    transform.bat

    @echo off
    setlocal enabledelayedexpansion
    
    echo transform materials
    set "srcMatDir=../src/main/raw/materials"
    set "distMatDir=../src/main/assets/materials"
    
    for %%f in ("%srcMatDir%\*.mat") do (
        set "matfile=%%~nf"
        matc -p mobile -a opengl -o "!matfile!.filamat" "%%f"
        move "!matfile!.filamat" "%distMatDir%\!matfile!.filamat"
    )
    
    echo transform mesh
    set "srcMeshDir=../src/main/raw/models"
    set "distMeshDir=../src/main/assets/models"
    
    for %%f in ("%srcMeshDir%\*.obj" "%srcMeshDir%\*.fbx") do (
        set "meshfile=%%~nf"
        filamesh "%%f" "!meshfile!.filamesh"
        move "!meshfile!.filamesh" "%distMeshDir%\!meshfile!.filamesh"
    )
    
    echo Processing complete.
    pause
    

    说明:需要将 matc.exe 文件、filamesh.exe 文件与 transform.bat 文件放在同一个目录下面,matc.exe 和 filamesh.exe 源自Filament环境搭建中编译生成的 exe 文件。双击 transform.bat 文件,会自动将 /src/main/raw/materials 下面的所有 mat 文件全部转换为 filamat 文件,并移到 /src/main/assets/materials/ 目录下面,同时自动将 /src/main/raw/models下面的所有 obj 或 fbx 文件全部转换为 filamesh 文件,并移到 /src/main/assets/models/ 目录下面。

    运行效果如下。

    声明:本文转自【Filament】壁纸

    相关文章

      网友评论

        本文标题:【Filament】壁纸

        本文链接:https://www.haomeiwen.com/subject/cwliadtx.html