【Filament】自定義Blinn Phong光照模型

1 前言

光照元素主要有環(huán)境光(ambient)怜奖、漫反射光(diffuse)浅役、鏡面反射光(specular),基礎(chǔ)的光照模型主要有蘭伯特(Lambert)光照模型变勇、馮氏(Phong)光照模型和改進(jìn)的馮氏(Blinn Phong)光照模型肥隆。

Lambert 光照模型只包含漫反射光的計(jì)算既荚,Phong 光照模型和 Blinn Phong 光照模型都包含環(huán)境光、漫反射光栋艳、鏡面反射光的計(jì)算恰聘,兩者的區(qū)別在與鏡面反射光的計(jì)算,Phong 光照模型根據(jù)反向量和觀察向量計(jì)算鏡面反射光,Blinn Phong 光照模型根據(jù)半向量和法向量計(jì)算鏡面反射光晴叨。

Blinn Phong 光照模型中環(huán)境光凿宾、漫反射光、鏡面反射光的計(jì)算如下兼蕊。

vec3 ambientColor() { // 環(huán)境光
    vec3 ambient = materialParams.ambientColor * materialParams.albedo;
    return ambient;
}

vec3 diffuseColor(vec3 normal, vec3 lightDir) { // 漫反射光
    float factor = max(dot(normal, lightDir), 0.0);
    vec3 diffuse = factor * materialParams.lightColor * materialParams.albedo;
    return diffuse;
}

vec3 specularColor(vec3 normal, vec3 lightDir, vec3 viewDire) { // 鏡面反射光
    vec3 halfVec = normalize(lightDir + viewDire); // 半向量
    float factor = pow(max(dot(normal, halfVec), 0.0), materialParams.gloss);
    vec3 specular = factor * materialParams.lightColor * materialParams.specularStrength;
    return specular;
}

最終的渲染顏色由以上三種光的顏色疊加而成初厚,如下。

material.baseColor = vec4(ambient + diffuse + specular, 1);

2 自定義光照

本文項(xiàng)目結(jié)構(gòu)如下孙技,完整代碼資源 → Filament自定義Blinn Phong光照模型产禾。

2.1 基礎(chǔ)類(lèi)

為方便讀者將注意力聚焦在 Filament 的輸入上,輕松配置復(fù)雜的環(huán)境依賴(lài)邏輯绪杏,筆者仿照 OpenGL ES 的寫(xiě)法下愈,抽出了 FLSurfaceView纽绍、BaseModel蕾久、Mesh、MaterialUtils 和 MeshUtils 類(lèi)拌夏。FLSurfaceView 與 GLSurfaceView 的功能類(lèi)似僧著,承載了渲染環(huán)境配置;BaseModel 用于管理模型的網(wǎng)格和材質(zhì)障簿;Mesh 用于管理模型的頂點(diǎn)屬性盹愚;MaterialUtils 和 MeshUtils 中分別提供了一些材質(zhì)和網(wǎng)格相關(guān)的工具。

build.gradle

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

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

FLSurfaceView.java

package com.zhyan8.customlight.filament.base;

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
 * 功能可以類(lèi)比OpenGL ES中的GLSurfaceView
 * 用于創(chuàng)建Filament的渲染環(huán)境
 */
public class FLSurfaceView extends SurfaceView {
    public static int RENDERMODE_WHEN_DIRTY = 0; // 用戶(hù)請(qǐng)求渲染才渲染一幀
    public static int RENDERMODE_CONTINUOUSLY = 1; // 持續(xù)渲染
    protected int mRenderMode = RENDERMODE_CONTINUOUSLY; // 渲染模式
    protected Choreographer mChoreographer; // 消息控制
    protected DisplayHelper mDisplayHelper; // 管理Display(可以監(jiān)聽(tīng)分辨率或刷新率的變化)
    protected UiHelper mUiHelper; // 管理SurfaceView西篓、TextureView愈腾、SurfaceHolder
    protected Engine mEngine; // 引擎(跟蹤用戶(hù)創(chuàng)建的資源, 管理渲染線(xiàn)程和硬件渲染器)
    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() { // 銷(xiāo)毀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 interface RenderCallback {
        void onCall();
    }
}

BaseModel.java

package com.zhyan8.loadmodel.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.loadmodel.filament.utils.MaterialUtils;
import com.zhyan8.loadmodel.filament.base.Mesh.Part;
 
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
/**
 * 模型基類(lèi)
 * 管理模型的網(wǎng)格、材質(zhì)吮成、渲染id
 */
public class BaseModel {
    private static String TAG = "BaseModel";
    protected Context mContext; // 上下文
    protected Engine mEngine; // Filament引擎
    protected TransformManager mTransformManager; // 模型變換管理器
    protected Mesh mMesh; // 模型網(wǎng)格
    protected Material[] mMaterials; // 模型材質(zhì)
    protected MaterialInstance[] mMaterialInstances; // 模型材質(zhì)實(shí)例
    protected Map<String, MaterialInstance> mMaterialMap = new HashMap<>(); // 材質(zhì)名->材質(zhì)
    protected Texture[] mTextures; // 紋理
    protected int mRenderable; // 渲染id
    protected int mTransformComponent; // 模型變換組件的id
    protected FLSurfaceView.RenderCallback mRenderCallback; // 每一幀渲染前的回調(diào)(一般用于處理模型變換橱乱、相機(jī)變換等)
 
    public BaseModel(Context context, Engine engine) {
        mContext = context;
        mEngine = engine;
        mTransformManager = mEngine.getTransformManager();
    }
 
    public int getRenderable() { // 獲取渲染id
        return mRenderable;
    }
 
    public FLSurfaceView.RenderCallback getRenderCallback() { // 獲取渲染回調(diào)
        return mRenderCallback;
    }
 
    public void destroy() { // 銷(xiāo)毀模型
        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) { // 加載材質(zhì)
        Material material = MaterialUtils.loadMaterial(mContext, mEngine, materialPath);
        if (material != null) {
            return new Material[] {material};
        }
        return null;
    }
 
    protected Material[] loadMaterials(String[] materialPaths) { // 加載材質(zhì)
        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) { // 獲取材質(zhì)實(shí)例
        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) { // 獲取材質(zhì)實(shí)例
        MaterialInstance[] materialInstances = new MaterialInstance[count];
        for (int i = 0; i < count; i++) {
            materialInstances[i] = material.createInstance();
        }
        return materialInstances;
    }
 
    private MaterialInstance getMaterialInstance(List<String> materialNames, int materialID) { // 獲取材質(zhì)
        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.customlight.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;

/**
 * 網(wǎng)格
 * 用于管理模型的頂點(diǎn)屬性和頂點(diǎn)索引
 */
public class Mesh {
    private Engine mEngine; // Filament引擎
    private VertexBuffer mVertexBuffer; // 頂點(diǎn)屬性緩存
    private IndexBuffer mIndexBuffer; // 頂點(diǎn)索引緩存
    private List<Part> mParts; // 子網(wǎng)格信息
    private Box mBox; // 渲染區(qū)域
    private List<String> mMaterialNames; // 材質(zhì)名

    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) { // 設(shè)置頂點(diǎn)屬性
        mVertexBuffer = getVertexBuffer(vertices);
    }

    public void setVertices(VertexPosCol[] vertices) { // 設(shè)置頂點(diǎn)屬性
        mVertexBuffer = getVertexBuffer(vertices);
    }

    public void setVertices(VertexPosUV[] vertices) { // 設(shè)置頂點(diǎn)屬性
        mVertexBuffer = getVertexBuffer(vertices);
    }

    public void setIndices(short[] indices) { // 設(shè)置頂點(diǎn)索引
        mIndexBuffer = getIndexBuffer(indices);
    }

    public void setParts(List<Part> parts, int count) { // 設(shè)置頂點(diǎn)索引
        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) { // 渲染區(qū)域
        if (box == null) {
            mBox = new Box(0, 0, 0, 1, 1, 1);
        } else {
            mBox = box;
        }
    }

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

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

    public List<Part> getParts() { // 獲取頂點(diǎn)索引緩存
        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) { // 獲取頂點(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;
    }

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

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

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

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

    /**
     * 子網(wǎng)格信息
     */
    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;
        }
    }

    /**
     * 頂點(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;
        }
    }
}

MaterialUtils.java

package com.zhyan8.customlight.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;

/**
 * 材質(zhì)工具類(lèi)
 */
public class MaterialUtils {
    private static String TAG = "MaterialUtils";

    public static Material loadMaterial(Context context, Engine engine, String materialPath) { // 加載材質(zhì)
        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.customlight.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.customlight.filament.base.Mesh;
import com.zhyan8.customlight.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;

/**
 * 網(wǎng)格工具類(lèi)
 */
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) { // 讀取模型頂點(diǎn)數(shù)據(jù)
        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) { // 讀取子網(wǎng)格屬性
        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) { // 讀取魔法數(shù)字, 用于判斷是否是有效的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) { // 讀取材質(zhì)
        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) { // 創(chuàng)建頂點(diǎn)索引緩沖
        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) { // 創(chuàng)建頂點(diǎn)屬性緩沖
        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坐標(biāo)的精度類(lèi)型
        if (uvNormalized(header)) {
            return AttributeType.SHORT2;
        }
        return AttributeType.HALF2;
    }

    private static boolean uvNormalized(Header header) { // uv坐標(biāo)是否已正則化
        return (header.flags & HEADER_FLAG_SNORM16_UV) != 0L;
    }

    private static int readIntLE(InputStream input) { // 獲取輸入流中Little Endian格式的整數(shù)
        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格式的無(wú)符號(hào)整數(shù)
        return (int) (readIntLE(input) & 0xFFFFFFFFL);
    }

    private static float readFloat32LE(InputStream input) { // 獲取輸入流中Little Endian格式的浮點(diǎn)數(shù)
        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();
    }
}

/**
 * 網(wǎng)格文件頭
 */
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;
}

2.2 業(yè)務(wù)類(lèi)

MainActivity.java

package com.zhyan8.customlight;

import android.os.Bundle;

import androidx.appcompat.app.AppCompatActivity;

import com.zhyan8.customlight.filament.base.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.customlight;

import android.content.Context;

import com.google.android.filament.Camera;
import com.zhyan8.customlight.filament.base.BaseModel;
import com.zhyan8.customlight.filament.base.FLSurfaceView;

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

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

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

    @Override
    protected void setupScene() { // 設(shè)置Scene參數(shù)
        mMyModel = new MyModel(getContext(), mEngine);
        mScene.addEntity(mMyModel.getRenderable());
        addRenderCallback(mMyModel.getRenderCallback());
    }

    @Override
    protected void onResized(int width, int height) {
        double aspect = (double) width / (double) height;
        mCamera.setProjection(45.0, aspect, 0.1, 1000.0, Camera.Fov.VERTICAL);
        float[] eye = new float[] {1.5f, 1f, 7.5f}; // cube
        //float[] eye = new float[] {1.5f, 1f, 500f}; // spider_bot
        //float[] eye = new float[] {1.5f, 1f, 10f}; // shader_ball
        float[] center = new float[] {0, 0, 0};
        float[] up = new float[] {0, 1, 0};
        mCamera.lookAt(eye[0], eye[1], eye[2],center[0], center[1], center[2], up[0], up[1], up[2]);
    }
}

MyModel.java

package com.zhyan8.customlight;

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

import com.google.android.filament.Engine;
import com.google.android.filament.MaterialInstance;
import com.google.android.filament.RenderableManager.PrimitiveType;
import com.zhyan8.customlight.filament.base.BaseModel;
import com.zhyan8.customlight.filament.utils.MeshUtils;

public class MyModel extends BaseModel {
    private String mMaterialPath = "materials/custom_light.filamat";
    private String mMeshPath = "models/cube.filamesh";
    //private String mMeshPath = "models/spider_bot.filamesh";
    //private String mMeshPath = "models/shader_ball.filamesh";

    private float[] mModelMatrix; // 模型變換矩陣
    private float[] mRotateAxis; // 旋轉(zhuǎn)軸
    private float mRotateAgree = 0; // 旋轉(zhuǎn)角度

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

    private void init() {
        mMaterials = loadMaterials(mMaterialPath);
        mMaterialInstances = getMaterialInstance(mMaterials);
        setupMaterial(mMaterialInstances[0]);
        mMaterialMap.put("DefaultMaterial", mMaterialInstances[0]);
        mMesh = MeshUtils.loadMesh(mContext, mEngine, mMeshPath);
        mRenderable = getRenderable(PrimitiveType.TRIANGLES);
        mTransformComponent = mTransformManager.getInstance(mRenderable);
        mRenderCallback = () -> renderCallback();
        mModelMatrix = new float[16];
        mRotateAxis = new float[] { 0.5f, 1f, 1f };
    }

    private void setupMaterial(MaterialInstance materialInstance) {
        materialInstance.setParameter("albedo", 0.7f, 0.1f, 0.2f); // 模型顏色
        materialInstance.setParameter("ambientColor", 0.2f, 0.2f, 0.2f); // 環(huán)境光顏色
        materialInstance.setParameter("lightPos", 100f, 500f, 50f); // 燈光位置
        materialInstance.setParameter("lightColor", 0.9f, 0.9f, 0.9f); // 燈光顏色
        materialInstance.setParameter("specularStrength", 0.1f); // 鏡面反射光強(qiáng)度
        materialInstance.setParameter("gloss", 4f); // 鏡面反射光澤度
    }

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

custom_light.mat

material {
    name : custom_light,

    shadingModel : unlit, // 禁用所有l(wèi)ighting
    // 自定義變量參數(shù)
    parameters : [
        {
            type : float3,
            name : albedo // 模型顏色
        },
        {
            type : float3,
            name : ambientColor // 環(huán)境光顏色
        },
        {
            type : float3,
            name : lightPos // 燈光位置
        },
        {
            type : float3,
            name : lightColor // 燈光顏色
        },
        {
            type : float,
            name : specularStrength // 鏡面反射光強(qiáng)度
        },
        {
            type : float,
            name : gloss  // 鏡面反射光澤度
        }
    ],
    // 頂點(diǎn)著色器入?yún)aterialVertexInputs中需要的頂點(diǎn)屬性
    requires : [
        tangents
    ]
}

fragment {
    vec3 ambientColor() { // 環(huán)境光
        vec3 ambient = materialParams.ambientColor * materialParams.albedo;
        return ambient;
    }

    vec3 diffuseColor(vec3 normal, vec3 lightDir) { // 漫反射光
        float factor = max(dot(normal, lightDir), 0.0);
        vec3 diffuse = factor * materialParams.lightColor * materialParams.albedo;
        return diffuse;
    }

    vec3 specularColor(vec3 normal, vec3 lightDir, vec3 viewDire) { // 鏡面反射光(Blinn Phong 光照模型)
        vec3 halfVec = normalize(lightDir + viewDire); // 半向量
        float factor = pow(max(dot(normal, halfVec), 0.0), materialParams.gloss);
        vec3 specular = factor * materialParams.lightColor * materialParams.specularStrength;
        return specular;
    }

    void material(inout MaterialInputs material) {
        prepareMaterial(material); // 在方法返回前必須回調(diào)該函數(shù)
        vec3 pos = getWorldPosition(); // 頂點(diǎn)的世界坐標(biāo)
        vec3 normal = normalize(getWorldNormalVector()); // 法線(xiàn)向量
        vec3 lightDir = normalize(materialParams.lightPos - pos); // 頂點(diǎn)指向光源的向量
        vec3 viewDire = normalize(getWorldCameraPosition() - pos); // 頂點(diǎn)指向相機(jī)的向量
        vec3 ambient = ambientColor();
        vec3 diffuse = diffuseColor(normal, lightDir);
        vec3 specular = specularColor(normal, lightDir, viewDire);
        material.baseColor = vec4(ambient + diffuse + specular, 1);
    }
}

說(shuō)明:custom_light 材質(zhì)實(shí)現(xiàn)了 Blinn Phong 光照模型。

cube.obj

# 正方體模型
 
# 頂點(diǎn)位置
v 1.0 1.0 -1.0   # V1
v 1.0 -1.0 -1.0  # V2
v 1.0 1.0 1.0    # V3
v 1.0  -1.0 1.0  # V4
v -1.0 1.0 -1.0  # V5
v -1.0 -1.0 -1.0 # V6
v -1.0 1.0 1.0   # V7
v -1.0 -1.0 1.0  # V8
 
# 紋理坐標(biāo)
vt 0.0 0.0  # VT1
vt 1.0 0.0  # VT2
vt 1.0 1.0  # VT3
vt 0.0 1.0  # VT4
 
# 法線(xiàn)
vn 0.0 1.0 0.0   # VN1 (上面法線(xiàn))
vn 0.0 0.0 1.0   # VN2 (背面法線(xiàn))
vn -1.0 0.0 0.0  # VN3 (左面法線(xiàn))
vn 0.0 -1.0 0.0  # VN4 (下面法線(xiàn))
vn 1.0 0.0 0.0   # VN5 (右面法線(xiàn))
vn 0.0 0.0 -1.0  # VN6 (前面法線(xiàn))
 
# 面(v/vt/vn)
f 1/1/1 5/2/1 7/3/1
f 1/1/1 7/3/1 3/4/1
f 4/1/2 3/2/2 7/3/2
f 4/1/2 7/3/2 8/4/2
f 8/1/3 7/2/3 5/3/3
f 8/1/3 5/3/3 6/4/3
f 6/1/4 2/2/4 4/3/4
f 6/1/4 4/3/4 8/4/4
f 2/1/5 1/2/5 3/3/5
f 2/1/5 3/3/5 4/4/5
f 6/1/6 5/2/6 1/3/6
f 6/1/6 1/3/6 2/4/6

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

說(shuō)明:需要將 matc.exe 文件粱甫、filamesh.exe 文件與 transform.bat 文件放在同一個(gè)目錄下面泳叠,matc.exe 和 filamesh.exe 源自Filament環(huán)境搭建中編譯生成的 exe 文件。雙擊 transform.bat 文件茶宵,會(huì)自動(dòng)將 /src/main/raw/materials 下面的所有 mat 文件全部轉(zhuǎn)換為 filamat 文件危纫,并移到 /src/main/assets/materials/ 目錄下面,同時(shí)自動(dòng)將 /src/main/raw/models下面的所有 obj 或 fbx 文件全部轉(zhuǎn)換為 filamesh 文件,并移到 /src/main/assets/models/ 目錄下面叶摄。

加載 cube 模型運(yùn)行效果如下属韧。

加載 spider_bot 模型運(yùn)行效果如下。

加載 shader_ball 模型運(yùn)行效果如下蛤吓。

聲明:本文轉(zhuǎn)自【Filament】自定義Blinn Phong光照模型宵喂。

?著作權(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)離奇詭異,居然都是意外死亡泼疑,警方通過(guò)查閱死者的電腦和手機(jī)德绿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)退渗,“玉大人移稳,你說(shuō)我怎么就攤上這事』嵊停” “怎么了个粱?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)翻翩。 經(jīng)常有香客問(wèn)我都许,道長(zhǎng),這世上最難降的妖魔是什么嫂冻? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任胶征,我火速辦了婚禮,結(jié)果婚禮上絮吵,老公的妹妹穿的比我還像新娘弧烤。我一直安慰自己,他們只是感情好蹬敲,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布暇昂。 她就那樣靜靜地躺著,像睡著了一般伴嗡。 火紅的嫁衣襯著肌膚如雪急波。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,166評(píng)論 1 284
  • 那天瘪校,我揣著相機(jī)與錄音澄暮,去河邊找鬼名段。 笑死,一個(gè)胖子當(dāng)著我的面吹牛泣懊,可吹牛的內(nèi)容都是我干的伸辟。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼馍刮,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼信夫!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起卡啰,我...
    開(kāi)封第一講書(shū)人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤静稻,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后匈辱,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體振湾,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有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
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望懂版。 院中可真熱鬧鹃栽,春花似錦、人聲如沸躯畴。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蓬抄。三九已至丰嘉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間嚷缭,已是汗流浹背饮亏。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工耍贾, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人路幸。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓荐开,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親简肴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子誓焦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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

  • 模擬正式光照環(huán)境生成圖像時(shí)所需了解的相關(guān)概念 模擬真實(shí)光照環(huán)境生成圖像的過(guò)程主要分為如下3個(gè)步驟 光源發(fā)射光線(xiàn) 光...
    全新的飯閱讀 2,306評(píng)論 0 0
  • 1 簡(jiǎn)介 本系列的文章到目前為止已經(jīng)介紹完了OpenGL的基礎(chǔ)知識(shí),你應(yīng)該已經(jīng)了解OpenGL中的大部分特性着帽,也在...
    RichardJieChen閱讀 3,370評(píng)論 0 15
  • 在講Unity光照模型之前杂伟,先介紹圖形學(xué)中的兩個(gè)基礎(chǔ)光照模型原理,將會(huì)更利于我們理解和使用Unity中的光照模型仍翰。...
    Unity云中客閱讀 3,566評(píng)論 1 6
  • 一. 我們?nèi)绾慰匆?jiàn)事物 我們?cè)诿枋鲆粋€(gè)物體外貌時(shí)時(shí)常會(huì)說(shuō) “這物體是什么什么顏色的”赫粥,而在中學(xué)時(shí),我們學(xué)的物理知識(shí)...
    憶中異閱讀 2,442評(píng)論 0 2
  • 本系列所有文章目錄[http://www.reibang.com/p/df4c8f9bc08d] 獲取示例代碼[...
    handyTOOL閱讀 3,035評(píng)論 3 1