美文网首页Filament
【Filament】绘制矩形

【Filament】绘制矩形

作者: LittleFatSheep | 来源:发表于2024-02-22 00:41 被阅读0次

    1 前言

    Filament环境搭建中介绍了 Filament 的 Windows 和 Android 环境搭建,绘制三角形中介绍了绘制纯色和彩色三角形,本文将使用 Filament 绘制纯色和彩色矩形。

    2 绘制矩形

    本文项目结构如下,完整代码资源 → Filament绘制矩形

    2.1 自定义基类

    为方便读者将注意力聚焦在 Filament 的输入上,轻松配置复杂的环境依赖逻辑,笔者仿照 OpenGL ES 的写法,抽出了 FLSurfaceView 和 BaseModel 类。FLSurfaceView 与 GLSurfaceView 的功能类似,承载了渲染环境配置;BaseModel 中提供了一些 VertexBuffer、IndexBuffer、Material、Renderable 相关的工具类,方便子类直接使用这些工具类。

    build.gradle

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

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

    FLSurfaceView.java

    package com.zhyan8.square.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;
    
    /*
     * 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(); // 帧回调
    
        static {
            Filament.init();
        }
    
        public FLSurfaceView(Context context) {
            super(context);
            mChoreographer = Choreographer.getInstance();
            mDisplayHelper = new DisplayHelper(context);
        }
    
        public void init() { // 初始化
            setupSurfaceView();
            setupFilament();
            setupView();
            setupScene();
        }
    
        public void setRenderMode(int renderMode) { // 设置渲染模式
            mRenderMode = renderMode;
        }
    
        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);
            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); // 请求下一帧
                }
                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);
            }
        }
    }
    

    BaseModel.java

    package com.zhyan8.square.filament;
    
    import android.content.res.AssetFileDescriptor;
    import android.content.res.AssetManager;
    import android.os.Handler;
    import android.os.Looper;
    import android.util.Log;
    
    import com.google.android.filament.Box;
    import com.google.android.filament.Engine;
    import com.google.android.filament.EntityManager;
    import com.google.android.filament.IndexBuffer;
    import com.google.android.filament.Material;
    import com.google.android.filament.RenderableManager;
    import com.google.android.filament.RenderableManager.PrimitiveType;
    import com.google.android.filament.VertexBuffer;
    import com.google.android.filament.VertexBuffer.AttributeType;
    import com.google.android.filament.VertexBuffer.VertexAttribute;
    
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.nio.Buffer;
    import java.nio.ByteBuffer;
    import java.nio.ByteOrder;
    import java.nio.channels.Channels;
    import java.nio.channels.ReadableByteChannel;
    
    /*
     * 模型基类
     * 管理模型的材质、顶点属性、顶点索引、渲染id
     */
    public class BaseModel {
        private static String TAG = "BaseModel";
        protected AssetManager mAssetManager; // 资源管理器
        protected Engine mEngine; // Filament引擎
        protected Material mMaterial; // 模型材质
        protected VertexBuffer mVertexBuffer; // 顶点属性缓存
        protected IndexBuffer mIndexBuffer; // 顶点索引缓存
        protected int mRenderable; // 渲染id
        protected Box mBox; // 渲染区域
    
        public BaseModel(AssetManager assetManager, Engine engine) {
            mAssetManager = assetManager;
            mEngine = engine;
        }
    
        public Material getMaterial() { // 获取材质
            return mMaterial;
        }
    
        public VertexBuffer getVertexBuffer() { // 获取顶点属性缓存
            return mVertexBuffer;
        }
    
        public IndexBuffer getIndexBuffer() { // 获取顶点索引缓存
            return mIndexBuffer;
        }
    
        public int getRenderable() { // 获取渲染id
            return mRenderable;
        }
    
        public void destroy() { // 销毁模型
            mEngine.destroyEntity(mRenderable);
            mEngine.destroyVertexBuffer(mVertexBuffer);
            mEngine.destroyIndexBuffer(mIndexBuffer);
            mEngine.destroyMaterial(mMaterial);
            EntityManager entityManager = EntityManager.get();
            entityManager.destroy(mRenderable);
        }
    
        protected Material loadMaterial(String materialPath) { // 加载材质
            Buffer buffer = readUncompressedAsset(mAssetManager, materialPath);
            if (buffer != null) {
                Material material = (new Material.Builder()).payload(buffer, buffer.remaining()).build(mEngine);
                material.compile(
                        Material.CompilerPriorityQueue.HIGH,
                        Material.UserVariantFilterBit.ALL,
                        new Handler(Looper.getMainLooper()),
                        () -> Log.i(TAG, "Material " + material.getName() + " compiled."));
                mEngine.flush();
                return material;
            }
            return null;
        }
    
        protected 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;
        }
    
        protected VertexBuffer getVertexBuffer(Vertex[] values) { // 获取顶点属性缓存
            ByteBuffer vertexData = getByteBuffer(values);
            int vertexCount = values.length;
            int vertexSize = Vertex.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;
        }
    
        protected 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;
        }
    
        protected int getRenderable(PrimitiveType primitiveType, int vertexCount) { // 获取渲染id
            int renderable = EntityManager.get().create();
            new RenderableManager.Builder(1)
                    .boundingBox(mBox)
                    .geometry(0, primitiveType, mVertexBuffer, mIndexBuffer, 0, vertexCount)
                    .material(0, mMaterial.getDefaultInstance())
                    .build(mEngine, renderable);
            return renderable;
        }
    
        private Buffer readUncompressedAsset(AssetManager assetManager, String assetPath) { // 加载资源
            ByteBuffer dist = null;
            try {
                AssetFileDescriptor fd = assetManager.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;
        }
    
        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(Vertex[] values) { // Vertex数组转换为ByteBuffer
            ByteBuffer byteBuffer = ByteBuffer.allocate(values.length * Vertex.BYTES);
            byteBuffer.order(ByteOrder.nativeOrder());
            for (int i = 0; i < values.length; i++) {
                values[i].put(byteBuffer);
            }
            byteBuffer.flip();
            return byteBuffer;
        }
    
        /*
         * 顶点数据
         * 包含顶点位置和颜色
         */
        public static class Vertex {
            public static int BYTES = 16;
            public float x;
            public float y;
            public float z;
            public int color;
            public Vertex() {}
            public Vertex(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) { // Vertex转换为ByteBuffer
                buffer.putFloat(x);
                buffer.putFloat(y);
                buffer.putFloat(z);
                buffer.putInt(color);
                return buffer;
            }
        }
    }
    

    2.2 绘制纯色矩形

    MainActivity.java

    package com.zhyan8.square;
    
    import androidx.appcompat.app.AppCompatActivity;
    
    import android.os.Bundle;
    
    import com.zhyan8.square.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.square;
    
    import android.content.Context;
    
    import com.google.android.filament.Camera;
    import com.zhyan8.square.filament.BaseModel;
    import com.zhyan8.square.filament.FLSurfaceView;
    
    public class MyFLSurfaceView extends FLSurfaceView {
        private BaseModel mMyModel;
        public MyFLSurfaceView(Context context) {
            super(context);
        }
    
        public void init() {
            mSkyboxColor = new float[] {0.965f, 0.941f, 0.887f, 1};
            super.init();
        }
    
        @Override
        public void onDestroy() {
            mMyModel.destroy();
            super.onDestroy();
        }
    
        @Override
        protected void setupScene() { // 设置Scene参数
            mMyModel = new Square1(getContext().getAssets(), mEngine);
            mScene.addEntity(mMyModel.getRenderable());
        }
    
        @Override
        protected void onResized(int width, int height) {
            double zoom = 1.5;
            double aspect = (double) width / (double) height;
            mCamera.setProjection(Camera.Projection.ORTHO,
                    -aspect * zoom, aspect * zoom, -zoom, zoom, 0, 10);
        }
    }
    

    Square1.java

    package com.zhyan8.square;
    
    import android.content.res.AssetManager;
    
    import com.google.android.filament.Box;
    import com.google.android.filament.Engine;
    import com.google.android.filament.RenderableManager.PrimitiveType;
    import com.zhyan8.square.filament.BaseModel;
    
    public class Square1 extends BaseModel {
        private String materialPath = "materials/square1.filamat";
        private float[] mVertices = new float[] {
                -0.5f, -0.5f, 0.0f, // 左下
                0.5f, -0.5f, 0.0f, // 右下
                0.5f, 0.5f, 0.0f, // 右上
                -0.5f, 0.5f, 0.0f // 左上
        };
        private short[] mIndex = new short[] {0, 1, 2, 0, 2, 3};
    
        public Square1(AssetManager assetManager, Engine engine) {
            super(assetManager, engine);
            init();
        }
    
        private void init() {
            mBox = new Box(0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.01f);
            mMaterial = loadMaterial(materialPath);
            mVertexBuffer = getVertexBuffer(mVertices);
            mIndexBuffer = getIndexBuffer(mIndex);
            mRenderable = getRenderable(PrimitiveType.TRIANGLES, mIndex.length);
        }
    }
    

    square1.mat

    material {
        name : square,
    
        // 禁用所有lighting
        shadingModel : unlit,
        featureLevel : 0
    }
    
    fragment {
        void material(inout MaterialInputs material) {
            prepareMaterial(material); // 在方法返回前必须回调该函数
            material.baseColor = vec4(1, 0, 0, 1);
        }
    }
    

    说明:mat 文件需要使用通过 matc.exe 工具转换为 filamat 文件。

    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
    

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

    运行效果如下。

    2.3 绘制彩色矩形

    MyFLSurfaceView.java

    package com.zhyan8.square;
    
    import android.content.Context;
    
    import com.google.android.filament.Camera;
    import com.zhyan8.square.filament.BaseModel;
    import com.zhyan8.square.filament.FLSurfaceView;
    
    public class MyFLSurfaceView extends FLSurfaceView {
        private BaseModel mMyModel;
        public MyFLSurfaceView(Context context) {
            super(context);
        }
    
        public void init() {
            mSkyboxColor = new float[] {0.965f, 0.941f, 0.887f, 1};
            super.init();
        }
    
        @Override
        public void onDestroy() {
            mMyModel.destroy();
            super.onDestroy();
        }
    
        @Override
        protected void setupScene() { // 设置Scene参数
            mMyModel = new Square2(getContext().getAssets(), mEngine);
            mScene.addEntity(mMyModel.getRenderable());
        }
    
        @Override
        protected void onResized(int width, int height) {
            double zoom = 1.5;
            double aspect = (double) width / (double) height;
            mCamera.setProjection(Camera.Projection.ORTHO,
                    -aspect * zoom, aspect * zoom, -zoom, zoom, 0, 10);
        }
    }
    

    Square2.java

    package com.zhyan8.square;
    
    import android.content.res.AssetManager;
    
    import com.google.android.filament.Box;
    import com.google.android.filament.Engine;
    import com.google.android.filament.RenderableManager.PrimitiveType;
    import com.zhyan8.square.filament.BaseModel;
    
    public class Square2 extends BaseModel {
        private String materialPath = "materials/square2.filamat";
        private Vertex[] mVertices = new Vertex[] {
                new Vertex(-0.5f, -0.5f, 0f, 0xffff0000), // 左下
                new Vertex(0.5f, -0.5f, 0f, 0xff00ff00), // 右下
                new Vertex(0.5f, 0.5f, 0f, 0xff0000ff), // 右上
                new Vertex(-0.5f, 0.5f, 0f, 0x000f0f0f), // 左上
        };
    
        private short[] mIndex = new short[] {0, 1, 2, 0, 2, 3};
    
        public Square2(AssetManager assetManager, Engine engine) {
            super(assetManager, engine);
            init();
        }
    
        private void init() {
            mBox = new Box(0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.01f);
            mMaterial = loadMaterial(materialPath);
            mVertexBuffer = getVertexBuffer(mVertices);
            mIndexBuffer = getIndexBuffer(mIndex);
            mRenderable = getRenderable(PrimitiveType.TRIANGLES, mIndex.length);
        }
    }
    

    square2.mat

    material {
        name : square,
    
        // 顶点着色器入参MaterialVertexInputs中需要的顶点属性
        requires : [
            color
        ],
    
        // 禁用所有lighting
        shadingModel : unlit,
        featureLevel : 0
    }
    
    fragment {
        void material(inout MaterialInputs material) {
            prepareMaterial(material); // 在方法返回前必须回调该函数
            material.baseColor = getColor();
        }
    }
    

    运行效果如下。

    声明:本文转自【Filament】绘制矩形

    相关文章

      网友评论

        本文标题:【Filament】绘制矩形

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