【Filament】紋理貼圖

1 前言

本文主要介紹使用 Filament 實(shí)現(xiàn)紋理貼圖抖单,讀者如果對(duì) Filament 不太熟悉匾南,請(qǐng)回顧以下內(nèi)容潭袱。

Filament 紋理坐標(biāo)的 x栋烤、y 軸正方向分別朝右和朝上虎敦,其 y 軸正方向朝向與 OpenGL ES 和 libGDX 相反(詳見【OpenGL ES】紋理貼圖酿愧、【libGDX】Mesh紋理貼圖)沥潭,如下。

2 紋理貼圖

本文項(xiàng)目結(jié)構(gòu)如下嬉挡,完整代碼資源 → Filament紋理貼圖钝鸽。

2.1 自定義基類

為方便讀者將注意力聚焦在 Filament 的輸入上,輕松配置復(fù)雜的環(huán)境依賴邏輯庞钢,筆者仿照 OpenGL ES 的寫法拔恰,抽出了 FLSurfaceView 和 BaseModel 類。FLSurfaceView 與 GLSurfaceView 的功能類似基括,承載了渲染環(huán)境配置颜懊;BaseModel 中提供了一些 VertexBuffer、IndexBuffer阱穗、Material饭冬、Renderable 相關(guān)的工具類,方便子類直接使用這些工具類揪阶。

build.gradle

...
android {
    ...
    aaptOptions { // 在應(yīng)用程序打包過程中不壓縮的文件
        noCompress 'filamat', 'ktx'
    }
}
 
dependencies {
    implementation fileTree(dir: '../libs', include: ['*.aar'])
    ...
}

說明:在項(xiàng)目根目錄下的 libs 目錄中昌抠,需要放入以下 aar 文件,它們?cè)醋?a target="_blank">Filament環(huán)境搭建中編譯生成的 aar鲁僚。

FLSurfaceView.java

package com.zhyan8.texture.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
 * 用于創(chuàng)建Filament的渲染環(huán)境
 */
public class FLSurfaceView extends SurfaceView {
    public static int RENDERMODE_WHEN_DIRTY = 0; // 用戶請(qǐng)求渲染才渲染一幀
    public static int RENDERMODE_CONTINUOUSLY = 1; // 持續(xù)渲染
    protected int mRenderMode = RENDERMODE_CONTINUOUSLY; // 渲染模式
    protected Choreographer mChoreographer; // 消息控制
    protected DisplayHelper mDisplayHelper; // 管理Display(可以監(jiān)聽分辨率或刷新率的變化)
    protected UiHelper mUiHelper; // 管理SurfaceView炊苫、TextureView、SurfaceHolder
    protected Engine mEngine; // 引擎(跟蹤用戶創(chuàng)建的資源, 管理渲染線程和硬件渲染器)
    protected Renderer mRenderer; // 渲染器(用于操作系統(tǒng)窗口, 生成繪制命令, 管理幀延時(shí))
    protected Scene mScene; // 場(chǎng)景(管理渲染對(duì)象冰沙、燈光)
    protected View mView; // 存儲(chǔ)渲染數(shù)據(jù)(View是Renderer操作的對(duì)象)
    protected Camera mCamera; // 相機(jī)(視角管理)
    protected Point mDesiredSize; // 渲染分辨率
    protected float[] mSkyboxColor; // 背景顏色
    protected SwapChain mSwapChain; // 操作系統(tǒng)的本地可渲染表面(native renderable surface, 通常是一個(gè)window或view)
    protected FrameCallback mFrameCallback = new FrameCallback(); // 幀回調(diào)
    protected ArrayList<RenderCallback> mRenderCallbacks; // 每一幀渲染前的回調(diào)(一般用于處理模型變換侨艾、相機(jī)變換等)

    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) { // 設(shè)置渲染模式
        mRenderMode = renderMode;
    }

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

    public void requestRender() { // 請(qǐng)求渲染
        mChoreographer.postFrameCallback(mFrameCallback);
    }

    public void onResume() { // 恢復(fù)
        mChoreographer.postFrameCallback(mFrameCallback);
    }

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

    public void onDestroy() { // 銷毀Filament環(huán)境
        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() { // 設(shè)置Scene參數(shù)
    }

    protected void onResized(int width, int height) { // Surface尺寸變化時(shí)回調(diào)
        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() { // 設(shè)置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() { // 設(shè)置Filament參數(shù)
        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() { // 設(shè)置View參數(shù)
        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);
    }

    /*
     * 幀回調(diào)
     */
    private class FrameCallback implements Choreographer.FrameCallback {
        @Override
        public void doFrame(long frameTimeNanos) { // 渲染每幀數(shù)據(jù)
            if (mRenderMode == RENDERMODE_CONTINUOUSLY) {
                mChoreographer.postFrameCallback(this); // 請(qǐng)求下一幀
            }
            mRenderCallbacks.forEach(callback -> callback.onCall());
            if (mUiHelper.isReadyToRender()) {
                if (mRenderer.beginFrame(mSwapChain, frameTimeNanos)) {
                    mRenderer.render(mView);
                    mRenderer.endFrame();
                }
            }
        }
    }

    /*
     * Surface回調(diào)
     */
    private class SurfaceCallback implements UiHelper.RendererCallback {
        @Override
        public void onNativeWindowChanged(Surface surface) { // Native窗口改變時(shí)回調(diào)
            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時(shí)回調(diào)
            mDisplayHelper.detach();
            if (mSwapChain != null) {
                mEngine.destroySwapChain(mSwapChain);
                mEngine.flushAndWait();
                mSwapChain = null;
            }
        }

        @Override
        public void onResized(int width, int height) { // Surface尺寸變化時(shí)回調(diào)
            mView.setViewport(new Viewport(0, 0, width, height));
            FilamentHelper.synchronizePendingFrames(mEngine);
            FLSurfaceView.this.onResized(width, height);
        }
    }

    /*
     * 每一幀渲染前的回調(diào)
     * 一般用于處理模型變換、相機(jī)變換等
     */
    public static interface RenderCallback {
        void onCall();
    }
}

BaseModel.java

package com.zhyan8.texture.filament;

import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
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.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.google.android.filament.VertexBuffer;
import com.google.android.filament.VertexBuffer.AttributeType;
import com.google.android.filament.VertexBuffer.VertexAttribute;
import com.google.android.filament.android.TextureHelper;

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

/*
 * 模型基類
 * 管理模型的材質(zhì)拓挥、頂點(diǎn)屬性唠梨、頂點(diǎn)索引、渲染id
 */
public class BaseModel {
    private static String TAG = "BaseModel";
    protected Context mContext; // 上下文
    protected Engine mEngine; // Filament引擎
    protected TransformManager mTransformManager; // 模型變換管理器
    protected Material mMaterial; // 模型材質(zhì)
    protected MaterialInstance mMaterialInstance; // 模型材質(zhì)實(shí)例
    protected VertexBuffer mVertexBuffer; // 頂點(diǎn)屬性緩存
    protected IndexBuffer mIndexBuffer; // 頂點(diǎn)索引緩存
    protected int mRenderable; // 渲染id
    protected int mTransformComponent; // 模型變換組件的id
    protected Box mBox; // 渲染區(qū)域
    protected FLSurfaceView.RenderCallback mRenderCallback; // 每一幀渲染前的回調(diào)(一般用于處理模型變換侥啤、相機(jī)變換等)

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

    public Material getMaterial() { // 獲取材質(zhì)
        return mMaterial;
    }

    public VertexBuffer getVertexBuffer() { // 獲取頂點(diǎn)屬性緩存
        return mVertexBuffer;
    }

    public IndexBuffer getIndexBuffer() { // 獲取頂點(diǎn)索引緩存
        return mIndexBuffer;
    }

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

    public FLSurfaceView.RenderCallback getRenderCallback() { // 獲取渲染回調(diào)
        return mRenderCallback;
    }

    public void destroy() { // 銷毀模型
        mEngine.destroyEntity(mRenderable);
        mEngine.destroyVertexBuffer(mVertexBuffer);
        mEngine.destroyIndexBuffer(mIndexBuffer);
        mEngine.destroyMaterialInstance(mMaterialInstance);
        mEngine.destroyMaterial(mMaterial);
        EntityManager entityManager = EntityManager.get();
        entityManager.destroy(mRenderable);
    }

    protected Material loadMaterial(String materialPath) { // 加載材質(zhì)
        Buffer buffer = readUncompressedAsset(mContext, materialPath);
        if (buffer != null) {
            Material material = (new Material.Builder()).payload(buffer, buffer.remaining()).build(mEngine);
            mMaterialInstance = material.createInstance();
            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) { // 獲取頂點(diǎn)屬性緩存
        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(VertexPosCol[] values) { // 獲取頂點(diǎn)屬性緩存
        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;
    }

    protected VertexBuffer getVertexBuffer(VertexPosUV[] values) { // 獲取頂點(diǎn)屬性緩存
        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;
    }

    protected IndexBuffer getIndexBuffer(short[] values) { // 獲取頂點(diǎn)索引緩存
        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, mMaterialInstance)
                .build(mEngine, renderable);
        return renderable;
    }

    protected Texture getTexture(String texturePath) { // 獲取Texture
        Bitmap bitmap = loadBitmapFromAsset(mContext, texturePath);
        if (bitmap != null) {
            return generateTexture(bitmap);
        }
        return null;
    }

    protected Texture getTexture(int resourceId) { // 獲取Texture
        Bitmap bitmap = loadBitmapFromDrawable(resourceId);
        if (bitmap != null) {
            return generateTexture(bitmap);
        }
        return null;
    }

    private Texture generateTexture(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(mEngine);
        TextureHelper.setBitmap(mEngine, texture, 0, bitmap, new Handler(), () ->
            Log.i(TAG, "getTexture, Bitmap is released.")
        );
        texture.generateMipmaps(mEngine);
        return texture;
    }

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

    private 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 Bitmap loadBitmapFromDrawable(int resourceId) { // 從drawable中加載bitmap
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPremultiplied = true;
        Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), resourceId, options);
        return bitmap;
    }

    private ByteBuffer getByteBuffer(float[] values) { // float數(shù)組轉(zhuǎn)換為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數(shù)組轉(zhuǎn)換為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數(shù)組轉(zhuǎn)換為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數(shù)組轉(zhuǎn)換為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;
    }

    /*
     * 頂點(diǎn)數(shù)據(jù)(位置+顏色)
     * 包含頂點(diǎn)位置和顏色
     */
    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轉(zhuǎn)換為ByteBuffer
            buffer.putFloat(x);
            buffer.putFloat(y);
            buffer.putFloat(z);
            buffer.putInt(color);
            return buffer;
        }
    }


    /*
     * 頂點(diǎn)數(shù)據(jù)(位置+紋理坐標(biāo))
     * 包含頂點(diǎn)位置和紋理坐標(biāo)
     */
    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轉(zhuǎn)換為ByteBuffer
            buffer.putFloat(x);
            buffer.putFloat(y);
            buffer.putFloat(z);
            buffer.putFloat(u);
            buffer.putFloat(v);
            return buffer;
        }
    }
}

2.2 紋理貼圖實(shí)現(xiàn)

MainActivity.java

package com.zhyan8.texture;

import android.os.Bundle;

import androidx.appcompat.app.AppCompatActivity;

import com.zhyan8.texture.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.texture;

import android.content.Context;

import com.google.android.filament.Camera;
import com.zhyan8.texture.filament.BaseModel;
import com.zhyan8.texture.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() { // 設(shè)置Scene參數(shù)
        mMyModel = new Square(getContext(), mEngine);
        mScene.addEntity(mMyModel.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.texture;

import android.content.Context;

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.texture.filament.BaseModel;

public class Square extends BaseModel {
    private String materialPath = "materials/square.filamat";
    private String texturePath = "textures/1.jpg";
    private VertexPosUV[] mVertices = new VertexPosUV[] {
            new VertexPosUV(-1f, -1f, 0f, 0f, 0f), // 左下
            new VertexPosUV(1f, -1f, 0f, 1f, 0f), // 右下
            new VertexPosUV(1f, 1f, 0f, 1f, 1f), // 右上
            new VertexPosUV(-1f, 1f, 0f, 0f, 1f) // 左上
    };
    private short[] mIndex = new short[] {0, 1, 2, 0, 2, 3};

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

    private void init() {
        mBox = new Box(0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.01f);
        mMaterial = loadMaterial(materialPath);
        mMaterialInstance.setParameter("mainTex", getTexture(texturePath), new TextureSampler());
        mVertexBuffer = getVertexBuffer(mVertices);
        mIndexBuffer = getIndexBuffer(mIndex);
        mRenderable = getRenderable(PrimitiveType.TRIANGLES, mIndex.length);
    }
}

square.mat

material {
    name : square,

    shadingModel : unlit, // 禁用所有l(wèi)ighting
    // 自定義變量參數(shù)
    parameters : [
        {
            type : sampler2d,
            name : mainTex
        }
    ],
    // 頂點(diǎn)著色器入?yún)aterialVertexInputs中需要的頂點(diǎn)屬性
    requires : [
        uv0
    ]
}

fragment {
    void material(inout MaterialInputs material) {
        prepareMaterial(material); // 在方法返回前必須回調(diào)該函數(shù)
        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

說明:需要將 matc.exe 文件與 transform.bat 文件放在同一個(gè)目錄下面当叭,matc.exe 源自Filament環(huán)境搭建中編譯生成的 exe 文件。雙擊 transform.bat 文件盖灸,會(huì)自動(dòng)將 /src/main/materials/ 下面的所有 mat 文件全部轉(zhuǎn)換為 filamat 文件蚁鳖,并移到 /src/main/assets/materials/ 目錄下面。

運(yùn)行效果如下赁炎。

聲明:本文轉(zhuǎn)自【Filament】紋理貼圖醉箕。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子讥裤,更是在濱河造成了極大的恐慌放棒,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坞琴,死亡現(xiàn)場(chǎng)離奇詭異哨查,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)剧辐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門寒亥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人荧关,你說我怎么就攤上這事溉奕。” “怎么了忍啤?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵加勤,是天一觀的道長。 經(jīng)常有香客問我同波,道長鳄梅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任未檩,我火速辦了婚禮戴尸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘冤狡。我一直安慰自己孙蒙,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布悲雳。 她就那樣靜靜地躺著挎峦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪合瓢。 梳的紋絲不亂的頭發(fā)上坦胶,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音晴楔,去河邊找鬼迁央。 笑死,一個(gè)胖子當(dāng)著我的面吹牛滥崩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播讹语,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼钙皮,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起短条,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤导匣,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后茸时,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贡定,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年可都,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了缓待。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡渠牲,死狀恐怖旋炒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情签杈,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站砰苍,受9級(jí)特大地震影響铣缠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鹦付,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一尚粘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧睁壁,春花似錦背苦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至钳降,卻和暖如春厚宰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背遂填。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國打工铲觉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人吓坚。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓撵幽,卻偏偏與公主長得像,于是被迫代替她去往敵國和親礁击。 傳聞我的和親對(duì)象是個(gè)殘疾皇子盐杂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容