【Filament】立方体贴图(6张图)

1 前言

        本文通过一个立方体贴图的例子,讲解三维纹理贴图(子网格贴图)的应用,案例中使用 6 张不同的图片给立方体贴图,图片如下。

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

  • Filament环境搭建
  • 绘制三角形
  • 绘制矩形
  • 绘制圆形
  • 绘制立方体
  • 纹理贴图

2 立方体贴图

        本文项目结构如下,完整代码资源 → Filament立方体贴图(6张图)。

2.1 基础类

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

        build.gradle

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

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

        FLSurfaceView.java

package com.zhyan8.multitexture.filament;import android.content.Context;
import android.graphics.Point;
import android.view.Choreographer;
import android.view.Surface;
import android.view.SurfaceView;import com.google.android.filament.Camera;
import com.google.android.filament.Engine;
import com.google.android.filament.EntityManager;
import com.google.android.filament.Filament;
import com.google.android.filament.Renderer;
import com.google.android.filament.Scene;
import com.google.android.filament.Skybox;
import com.google.android.filament.SwapChain;
import com.google.android.filament.View;
import com.google.android.filament.Viewport;
import com.google.android.filament.android.DisplayHelper;
import com.google.android.filament.android.FilamentHelper;
import com.google.android.filament.android.UiHelper;import java.util.ArrayList;/** Filament中待渲染的SurfaceView* 功能可以类比OpenGL ES中的GLSurfaceView* 用于创建Filament的渲染环境*/
public class FLSurfaceView extends SurfaceView {public static int RENDERMODE_WHEN_DIRTY = 0; // 用户请求渲染才渲染一帧public static int RENDERMODE_CONTINUOUSLY = 1; // 持续渲染protected int mRenderMode = RENDERMODE_CONTINUOUSLY; // 渲染模式protected Choreographer mChoreographer; // 消息控制protected DisplayHelper mDisplayHelper; // 管理Display(可以监听分辨率或刷新率的变化)protected UiHelper mUiHelper; // 管理SurfaceView、TextureView、SurfaceHolderprotected Engine mEngine; // 引擎(跟踪用户创建的资源, 管理渲染线程和硬件渲染器)protected Renderer mRenderer; // 渲染器(用于操作系统窗口, 生成绘制命令, 管理帧延时)protected Scene mScene; // 场景(管理渲染对象、灯光)protected View mView; // 存储渲染数据(View是Renderer操作的对象)protected Camera mCamera; // 相机(视角管理)protected Point mDesiredSize; // 渲染分辨率protected float[] mSkyboxColor; // 背景颜色protected SwapChain mSwapChain; // 操作系统的本地可渲染表面(native renderable surface, 通常是一个window或view)protected FrameCallback mFrameCallback = new FrameCallback(); // 帧回调protected ArrayList<RenderCallback> mRenderCallbacks; // 每一帧渲染前的回调(一般用于处理模型变换、相机变换等)static {Filament.init();}public FLSurfaceView(Context context) {super(context);mChoreographer = Choreographer.getInstance();mDisplayHelper = new DisplayHelper(context);mRenderCallbacks = new ArrayList<>();}public void init() { // 初始化setupSurfaceView();setupFilament();setupView();setupScene();}public void setRenderMode(int renderMode) { // 设置渲染模式mRenderMode = renderMode;}public void addRenderCallback(RenderCallback renderCallback) { // 添加渲染回调if (renderCallback != null) {mRenderCallbacks.add(renderCallback);}}public void requestRender() { // 请求渲染mChoreographer.postFrameCallback(mFrameCallback);}public void onResume() { // 恢复mChoreographer.postFrameCallback(mFrameCallback);}public void onPause() { // 暂停mChoreographer.removeFrameCallback(mFrameCallback);}public void onDestroy() { // 销毁Filament环境mChoreographer.removeFrameCallback(mFrameCallback);mRenderCallbacks.clear();mUiHelper.detach();mEngine.destroyRenderer(mRenderer);mEngine.destroyView(mView);mEngine.destroyScene(mScene);mEngine.destroyCameraComponent(mCamera.getEntity());EntityManager entityManager = EntityManager.get();entityManager.destroy(mCamera.getEntity());mEngine.destroy();}protected void setupScene() { // 设置Scene参数}protected void onResized(int width, int height) { // Surface尺寸变化时回调double zoom = 1;double aspect = (double) width / (double) height;mCamera.setProjection(Camera.Projection.ORTHO,-aspect * zoom, aspect * zoom, -zoom, zoom, 0, 1000);}private void setupSurfaceView() { // 设置SurfaceViewmUiHelper = 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 {@Overridepublic void doFrame(long frameTimeNanos) { // 渲染每帧数据if (mRenderMode == RENDERMODE_CONTINUOUSLY) {mChoreographer.postFrameCallback(this); // 请求下一帧}mRenderCallbacks.forEach(callback -> callback.onCall());if (mUiHelper.isReadyToRender()) {if (mRenderer.beginFrame(mSwapChain, frameTimeNanos)) {mRenderer.render(mView);mRenderer.endFrame();}}}}/** Surface回调*/private class SurfaceCallback implements UiHelper.RendererCallback {@Overridepublic 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());}@Overridepublic void onDetachedFromSurface() { // 解绑Surface时回调mDisplayHelper.detach();if (mSwapChain != null) {mEngine.destroySwapChain(mSwapChain);mEngine.flushAndWait();mSwapChain = null;}}@Overridepublic void onResized(int width, int height) { // Surface尺寸变化时回调mView.setViewport(new Viewport(0, 0, width, height));FilamentHelper.synchronizePendingFrames(mEngine);FLSurfaceView.this.onResized(width, height);}}/** 每一帧渲染前的回调* 一般用于处理模型变换、相机变换等*/public static interface RenderCallback {void onCall();}
}

        BaseModel.java

package com.zhyan8.multitexture.filament;import android.content.Context;import com.google.android.filament.Box;
import com.google.android.filament.Engine;
import com.google.android.filament.EntityManager;
import com.google.android.filament.Material;
import com.google.android.filament.MaterialInstance;
import com.google.android.filament.RenderableManager;
import com.google.android.filament.RenderableManager.PrimitiveType;
import com.google.android.filament.Texture;
import com.google.android.filament.TransformManager;import com.zhyan8.multitexture.filament.Mesh.SubMesh;
import com.zhyan8.multitexture.filament.utils.MaterialUtils;
import com.zhyan8.multitexture.filament.utils.TextureUtils;/** 模型基类* 管理模型的网格、材质、渲染id*/
public class BaseModel {private static String TAG = "BaseModel";protected Context mContext; // 上下文protected Engine mEngine; // Filament引擎protected TransformManager mTransformManager; // 模型变换管理器protected Mesh mMesh; // 模型网格protected SubMesh[] mSubMeshes; // 子网格protected Material[] mMaterials; // 模型材质protected MaterialInstance[] mMaterialInstances; // 模型材质实例protected Texture[] mTextures; // 纹理protected int mRenderable; // 渲染idprotected int mTransformComponent; // 模型变换组件的idprotected Box mBox; // 渲染区域protected FLSurfaceView.RenderCallback mRenderCallback; // 每一帧渲染前的回调(一般用于处理模型变换、相机变换等)public BaseModel(Context context, Engine engine) {mContext = context;mEngine = engine;mTransformManager = mEngine.getTransformManager();}public int getRenderable() { // 获取渲染idreturn mRenderable;}public FLSurfaceView.RenderCallback getRenderCallback() { // 获取渲染回调return mRenderCallback;}public void destroy() { // 销毁模型mEngine.destroyEntity(mRenderable);if (mMesh != null) {mMesh.destroy();}if (mTextures != null) {for (int i = 0; i < mTextures.length; i++) {mEngine.destroyTexture(mTextures[i]);}}if (mMaterialInstances != null) {for (int i = 0; i < mMaterialInstances.length; i++) {mEngine.destroyMaterialInstance(mMaterialInstances[i]);}}if (mMaterials != null) {for (int i = 0; i < mMaterials.length; i++) {mEngine.destroyMaterial(mMaterials[i]);}}EntityManager entityManager = EntityManager.get();entityManager.destroy(mRenderable);}protected int getRenderable(PrimitiveType primitiveType, int vertexCount) { // 获取渲染idint renderable = EntityManager.get().create();if (mSubMeshes == null) {mSubMeshes = new SubMesh[] {new SubMesh(0, 0, vertexCount - 1, vertexCount)};}RenderableManager.Builder builder = new RenderableManager.Builder(mSubMeshes.length).boundingBox(mBox);for (int i = 0; i < mSubMeshes.length; i++) {builder.geometry(i, primitiveType, mMesh.getVertexBuffer(), mMesh.getIndexBuffer(),mSubMeshes[i].offset, mSubMeshes[i].minIndex, mSubMeshes[i].maxIndex, mSubMeshes[i].indexCount).material(i, mMaterialInstances[i]);}builder.build(mEngine, renderable);return renderable;}protected Material[] loadMaterials(String materialPath) { // 加载材质Material material = MaterialUtils.loadMaterial(mContext, mEngine, materialPath);if (material != null) {return new Material[] {material};}return null;}protected Material[] loadMaterials(String[] materialPaths) { // 加载材质Material[] materials = new Material[materialPaths.length];for (int i = 0; i < materials.length; i++) {materials[i] = MaterialUtils.loadMaterial(mContext, mEngine, materialPaths[i]);}return materials;}protected MaterialInstance[] getMaterialInstance(Material[] materials) { // 获取材质实例MaterialInstance[] materialInstances = new MaterialInstance[materials.length];for (int i = 0; i < materials.length; i++) {materialInstances[i] = materials[i].createInstance();}return materialInstances;}protected MaterialInstance[] getMaterialInstance(Material material, int count) { // 获取材质实例MaterialInstance[] materialInstances = new MaterialInstance[count];for (int i = 0; i < count; i++) {materialInstances[i] = material.createInstance();}return materialInstances;}protected Texture[] loadTextures(String texturePath) { // 加载纹理Texture texture = TextureUtils.getTexture(mContext, mEngine, texturePath);if (texture != null) {return new Texture[] {texture};}return null;}protected Texture[] loadTextures(String[] texturePaths) { // 加载纹理Texture[] textures = new Texture[texturePaths.length];for (int i = 0; i < textures.length; i++) {textures[i] = TextureUtils.getTexture(mContext, mEngine, texturePaths[i]);}return textures;}
}

        Mesh.java

package com.zhyan8.multitexture.filament;import com.google.android.filament.Engine;
import com.google.android.filament.IndexBuffer;
import com.google.android.filament.VertexBuffer;
import com.google.android.filament.VertexBuffer.AttributeType;
import com.google.android.filament.VertexBuffer.VertexAttribute;import java.nio.ByteBuffer;
import java.nio.ByteOrder;/** 网格* 用于管理模型的顶点属性和顶点索引*/
public class Mesh {private Engine mEngine; // Filament引擎private VertexBuffer mVertexBuffer; // 顶点属性缓存private IndexBuffer mIndexBuffer; // 顶点索引缓存public Mesh(Engine engine) {mEngine = engine;}public Mesh(Engine engine, float[] vertices, short[] indices) {mEngine = engine;setVertices(vertices);setIndices(indices);}public Mesh(Engine engine, VertexPosCol[] vertices, short[] indices) {mEngine = engine;setVertices(vertices);setIndices(indices);}public Mesh(Engine engine, VertexPosUV[] vertices, short[] indices) {mEngine = engine;setVertices(vertices);setIndices(indices);}public void setVertices(float[] vertices) { // 设置顶点属性mVertexBuffer = getVertexBuffer(vertices);}public void setVertices(VertexPosCol[] vertices) { // 设置顶点属性mVertexBuffer = getVertexBuffer(vertices);}public void setVertices(VertexPosUV[] vertices) { // 设置顶点属性mVertexBuffer = getVertexBuffer(vertices);}public void setIndices(short[] indices) { // 设置顶点索引mIndexBuffer = getIndexBuffer(indices);}public VertexBuffer getVertexBuffer() { // 获取顶点属性缓存return mVertexBuffer;}public IndexBuffer getIndexBuffer() { // 获取顶点索引缓存return mIndexBuffer;}public void destroy() {mEngine.destroyVertexBuffer(mVertexBuffer);mEngine.destroyIndexBuffer(mIndexBuffer);}private VertexBuffer getVertexBuffer(float[] values) { // 获取顶点属性缓存ByteBuffer vertexData = getByteBuffer(values);int vertexCount = values.length / 3;int vertexSize = Float.BYTES * 3;VertexBuffer vertexBuffer = new VertexBuffer.Builder().bufferCount(1).vertexCount(vertexCount).attribute(VertexBuffer.VertexAttribute.POSITION, 0, VertexBuffer.AttributeType.FLOAT3, 0, vertexSize).build(mEngine);vertexBuffer.setBufferAt(mEngine, 0, vertexData);return vertexBuffer;}private VertexBuffer getVertexBuffer(VertexPosCol[] values) { // 获取顶点属性缓存ByteBuffer vertexData = getByteBuffer(values);int vertexCount = values.length;int vertexSize = VertexPosCol.BYTES;VertexBuffer vertexBuffer = new VertexBuffer.Builder().bufferCount(1).vertexCount(vertexCount).attribute(VertexAttribute.POSITION, 0, AttributeType.FLOAT3, 0, vertexSize).attribute(VertexAttribute.COLOR,    0, AttributeType.UBYTE4, 3 * Float.BYTES, vertexSize).normalized(VertexBuffer.VertexAttribute.COLOR).build(mEngine);vertexBuffer.setBufferAt(mEngine, 0, vertexData);return vertexBuffer;}private VertexBuffer getVertexBuffer(VertexPosUV[] values) { // 获取顶点属性缓存ByteBuffer vertexData = getByteBuffer(values);int vertexCount = values.length;int vertexSize = VertexPosUV.BYTES;VertexBuffer vertexBuffer = new VertexBuffer.Builder().bufferCount(1).vertexCount(vertexCount).attribute(VertexBuffer.VertexAttribute.POSITION, 0, VertexBuffer.AttributeType.FLOAT3, 0, vertexSize).attribute(VertexBuffer.VertexAttribute.UV0,    0, VertexBuffer.AttributeType.FLOAT2, 3 * Float.BYTES, vertexSize).build(mEngine);vertexBuffer.setBufferAt(mEngine, 0, vertexData);return vertexBuffer;}private IndexBuffer getIndexBuffer(short[] values) { // 获取顶点索引缓存ByteBuffer indexData = getByteBuffer(values);int indexCount = values.length;IndexBuffer indexBuffer = new IndexBuffer.Builder().indexCount(indexCount).bufferType(IndexBuffer.Builder.IndexType.USHORT).build(mEngine);indexBuffer.setBuffer(mEngine, indexData);return indexBuffer;}private ByteBuffer getByteBuffer(float[] values) { // float数组转换为ByteBufferByteBuffer 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数组转换为ByteBufferByteBuffer 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数组转换为ByteBufferByteBuffer 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数组转换为ByteBufferByteBuffer byteBuffer = ByteBuffer.allocate(values.length * VertexPosUV.BYTES);byteBuffer.order(ByteOrder.nativeOrder());for (int i = 0; i < values.length; i++) {values[i].put(byteBuffer);}byteBuffer.flip();return byteBuffer;}/** 子网格*/public static class SubMesh {public int offset;public int minIndex;public int maxIndex;public int indexCount;public SubMesh() {}public SubMesh(int offset, int minIndex, int maxIndex, int indexCount) {this.offset = offset;this.minIndex = minIndex;this.maxIndex = maxIndex;this.indexCount = indexCount;}}/** 顶点数据(位置+颜色)* 包含顶点位置和颜色*/public static class VertexPosCol {public static int BYTES = 16;public float x;public float y;public float z;public int color;public VertexPosCol() {}public VertexPosCol(float x, float y, float z, int color) {this.x = x;this.y = y;this.z = z;this.color = color;}public ByteBuffer put(ByteBuffer buffer) { // VertexPosCol转换为ByteBufferbuffer.putFloat(x);buffer.putFloat(y);buffer.putFloat(z);buffer.putInt(color);return buffer;}}/** 顶点数据(位置+纹理坐标)* 包含顶点位置和纹理坐标*/public static class VertexPosUV {public static int BYTES = 20;public float x;public float y;public float z;public float u;public float v;public VertexPosUV() {}public VertexPosUV(float x, float y, float z, float u, float v) {this.x = x;this.y = y;this.z = z;this.u = u;this.v = v;}public ByteBuffer put(ByteBuffer buffer) { // VertexPosUV转换为ByteBufferbuffer.putFloat(x);buffer.putFloat(y);buffer.putFloat(z);buffer.putFloat(u);buffer.putFloat(v);return buffer;}}
}

        MaterialUtils.java

package com.zhyan8.multitexture.filament.utils;import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;import com.google.android.filament.Engine;
import com.google.android.filament.Material;import java.io.FileInputStream;
import java.io.IOException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;/** 材质工具类*/
public class MaterialUtils {private static String TAG = "MaterialUtils";public static Material loadMaterial(Context context, Engine engine, String materialPath) { // 加载材质Buffer buffer = readUncompressedAsset(context, materialPath);if (buffer != null) {Material material = (new Material.Builder()).payload(buffer, buffer.remaining()).build(engine);material.compile(Material.CompilerPriorityQueue.HIGH,Material.UserVariantFilterBit.ALL,new Handler(Looper.getMainLooper()),() -> Log.i(TAG, "Material " + material.getName() + " compiled."));engine.flush();return material;}return null;}private static Buffer readUncompressedAsset(Context context, String assetPath) { // 加载资源ReadableByteChannel src = null;FileInputStream fis = null;ByteBuffer dist = null;try {AssetFileDescriptor fd = context.getAssets().openFd(assetPath);fis = fd.createInputStream();dist = ByteBuffer.allocate((int) fd.getLength());src = Channels.newChannel(fis);src.read(dist);} catch (IOException e) {e.printStackTrace();} finally {if (src != null) {try {src.close();} catch (IOException e) {e.printStackTrace();}}if (fis != null) {try {fis.close();} catch (IOException e) {e.printStackTrace();}}}if (dist != null) {return dist.rewind();}return null;}
}

        TextureUtils.java

package com.zhyan8.multitexture.filament.utils;import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.util.Log;import com.google.android.filament.Engine;
import com.google.android.filament.Texture;
import com.google.android.filament.android.TextureHelper;import java.io.IOException;
import java.io.InputStream;/** 纹理工具类*/
public class TextureUtils {private static String TAG = "TextureUtils";public static Texture getTexture(Context context, Engine engine, String texturePath) { // 获取TextureBitmap bitmap = loadBitmapFromAsset(context, texturePath);if (bitmap != null) {return generateTexture(engine, bitmap);}return null;}public static Texture getTexture(Context context, Engine engine, int resourceId) { // 获取TextureBitmap bitmap = loadBitmapFromDrawable(context, resourceId);if (bitmap != null) {return generateTexture(engine, bitmap);}return null;}private static Texture generateTexture(Engine engine, Bitmap bitmap) { // 生成TextureTexture texture = new Texture.Builder().width(bitmap.getWidth()).height(bitmap.getHeight()).sampler(Texture.Sampler.SAMPLER_2D).format(Texture.InternalFormat.SRGB8_A8).levels(0xff).build(engine);TextureHelper.setBitmap(engine, texture, 0, bitmap, new Handler(), () ->Log.i(TAG, "getTexture, Bitmap is released."));texture.generateMipmaps(engine);return texture;}private static Bitmap loadBitmapFromAsset(Context context, String assetPath) { // 从asset中加载bitmapInputStream inputStream = null;Bitmap bitmap = null;try {inputStream = context.getAssets().open(assetPath);bitmap = BitmapFactory.decodeStream(inputStream);} catch (IOException e) {e.printStackTrace();} finally {if (inputStream != null) {try {inputStream.close();} catch (IOException e) {e.printStackTrace();}}}return bitmap;}private static Bitmap loadBitmapFromDrawable(Context context, int resourceId) { // 从drawable中加载bitmapBitmapFactory.Options options = new BitmapFactory.Options();options.inPremultiplied = true;Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resourceId, options);return bitmap;}
}

2.2 业务类

        MainActivity.java

package com.zhyan8.multitexture;import android.os.Bundle;import androidx.appcompat.app.AppCompatActivity;import com.zhyan8.multitexture.filament.FLSurfaceView;public class MainActivity extends AppCompatActivity {private FLSurfaceView mFLSurfaceView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mFLSurfaceView = new MyFLSurfaceView(this);setContentView(mFLSurfaceView);mFLSurfaceView.init();mFLSurfaceView.setRenderMode(FLSurfaceView.RENDERMODE_CONTINUOUSLY);}@Overridepublic void onResume() {super.onResume();mFLSurfaceView.onResume();}@Overridepublic void onPause() {super.onPause();mFLSurfaceView.onPause();}@Overridepublic void onDestroy() {super.onDestroy();mFLSurfaceView.onDestroy();}
}

        MyFLSurfaceView.java

package com.zhyan8.multitexture;import android.content.Context;import com.google.android.filament.Camera;
import com.zhyan8.multitexture.filament.BaseModel;
import com.zhyan8.multitexture.filament.FLSurfaceView;public class MyFLSurfaceView extends FLSurfaceView {private BaseModel mMyModel;public MyFLSurfaceView(Context context) {super(context);}public void init() {mSkyboxColor = new float[] {0.35f, 0.35f, 0.35f, 1};super.init();}@Overridepublic void onDestroy() {mMyModel.destroy();super.onDestroy();}@Overrideprotected void setupScene() { // 设置Scene参数mMyModel = new Cube(getContext(), mEngine);mScene.addEntity(mMyModel.getRenderable());addRenderCallback(mMyModel.getRenderCallback());}@Overrideprotected void onResized(int width, int height) {double zoom = 0.25;double aspect = (double) width / (double) height;mCamera.setProjection(Camera.Projection.PERSPECTIVE,-aspect * zoom, aspect * zoom, -zoom, zoom, 0.3, 100);float[] eye = new float[] {1, 1, 1.5f};float[] center = new float[] {0, 0, 0};float[] up = new float[] {0, 1, 0};mCamera.lookAt(eye[0], eye[1], eye[2],center[0], center[1], center[2], up[0], up[1], up[2]);}
}

        Cube.java

package com.zhyan8.multitexture;import android.content.Context;
import android.opengl.Matrix;import com.google.android.filament.Box;
import com.google.android.filament.Engine;
import com.google.android.filament.RenderableManager.PrimitiveType;
import com.google.android.filament.TextureSampler;
import com.zhyan8.multitexture.filament.BaseModel;
import com.zhyan8.multitexture.filament.Mesh;
import com.zhyan8.multitexture.filament.Mesh.SubMesh;
import com.zhyan8.multitexture.filament.Mesh.VertexPosUV;public class Cube extends BaseModel {private String materialPaths = "materials/square.filamat";private String[] texturePaths = new String[] {"textures/a1.jpg", "textures/a2.jpg", "textures/a3.jpg","textures/a4.jpg", "textures/a6.jpg", "textures/a5.jpg"};private VertexPosUV[] mVertices; // 顶点坐标private short[] mIndices; // 顶点索引private float[] mModelMatrix; // 模型变换矩阵private float[] mRotateAxis; // 旋转轴private float mRotateAgree = 0; // 旋转角度public Cube(Context context, Engine engine) {super(context, engine);init();}private void init() {mBox = new Box(0.0f, 0.0f, 0.0f, 2.0f, 2.0f, 2.0f);mVertices = getVertices(0.5f);mIndices = getIndices();mMesh = new Mesh(mEngine, mVertices, mIndices);mSubMeshes = getSubMesh();mTextures = loadTextures(texturePaths);mMaterials = loadMaterials(materialPaths);mMaterialInstances = getMaterialInstance(mMaterials[0], mTextures.length);TextureSampler textureSampler = new TextureSampler();for (int i = 0; i < mMaterialInstances.length; i++) {mMaterialInstances[i].setParameter("mainTex", mTextures[i], textureSampler);}mRenderable = getRenderable(PrimitiveType.TRIANGLES, mIndices.length);mTransformComponent = mTransformManager.getInstance(mRenderable);mRenderCallback = () -> renderCallback();mModelMatrix = new float[16];mRotateAxis = new float[] { 0.5f, 1f, 1f };}private void renderCallback() {mRotateAgree = (mRotateAgree + 1) % 360;mRotateAxis[0] = mRotateAgree / 180f - 1;mRotateAxis[1] = (float) Math.sin(mRotateAgree / 180f * Math.PI * 0.7f);mRotateAxis[2] = (float) Math.cos(mRotateAgree / 180f * Math.PI * 0.5f);Matrix.setRotateM(mModelMatrix, 0, mRotateAgree, mRotateAxis[0], mRotateAxis[1], mRotateAxis[2]);mTransformManager.setTransform(mTransformComponent, mModelMatrix);}private VertexPosUV[] getVertices(float r) {VertexPosUV[] vertices = new VertexPosUV[] {// 前面new VertexPosUV(r, r, r, 0f, 1f), // 0new VertexPosUV(-r, r, r, 1f, 1f), // 1new VertexPosUV(-r, -r, r, 1f, 0f), // 2new VertexPosUV(r, -r, r, 0f, 0f), // 3// 后面new VertexPosUV(r, r, -r, 0f, 1f), // 4new VertexPosUV(-r, r, -r, 1f, 1f), // 5new VertexPosUV(-r, -r, -r, 1f, 0f), // 6new VertexPosUV(r, -r, -r, 0f, 0f), // 7// 上面new VertexPosUV(r, r, r, 0f, 1f), // 8new VertexPosUV(r, r, -r, 1f, 1f), // 9new VertexPosUV(-r, r, -r, 1f, 0f), // 10new VertexPosUV(-r, r, r, 0f, 0f), // 11// 下面new VertexPosUV(r, -r, r, 0f, 1f), // 12new VertexPosUV(r, -r, -r, 1f, 1f), // 13new VertexPosUV(-r, -r, -r, 1f, 0f), // 14new VertexPosUV(-r, -r, r, 0f, 0f), // 15// 右面new VertexPosUV(r, r, r, 0f, 1f), // 16new VertexPosUV(r, r, -r, 1f, 1f), // 17new VertexPosUV(r, -r, -r, 1f, 0f), // 18new VertexPosUV(r, -r, r, 0f, 0f), // 19// 左面new VertexPosUV(-r, r, r, 0f, 1f), // 20new VertexPosUV(-r, r, -r, 1f, 1f), // 21new VertexPosUV(-r, -r, -r, 1f, 0f), // 22new VertexPosUV(-r, -r, r, 0f, 0f) // 23};return vertices;}private short[] getIndices() {short[] indices = new short[] {0, 1, 2, 0, 2, 3, // 前面4, 6, 5, 4, 7, 6, // 上面8, 9, 10, 8, 10, 11, // 右面12, 14, 13, 12, 15, 14, // 后面16, 18, 17, 16, 19, 18, // 下面20, 21, 22, 20, 22, 23 // 左面};return indices;}private SubMesh[] getSubMesh() {int subMeshCount = 6;int vertexCount = 6;SubMesh[] subMeshes = new SubMesh[subMeshCount];for (int i = 0; i < subMeshCount; i++) {int offset = i * vertexCount;int minIndex = offset;int maxIndex = minIndex + vertexCount - 1;subMeshes[i] = new SubMesh(offset, minIndex, maxIndex, vertexCount);}return subMeshes;}
}

        square.mat

material {name : square,shadingModel : unlit, // 禁用所有lighting// 自定义变量参数parameters : [{type : sampler2d,name : mainTex}],// 顶点着色器入参MaterialVertexInputs中需要的顶点属性requires : [uv0]
}fragment {void material(inout MaterialInputs material) {prepareMaterial(material); // 在方法返回前必须回调该函数material.baseColor = texture(materialParams_mainTex, getUV0());}
}

        transform.bat

@echo off
setlocal enabledelayedexpansion
set "srcFolder=../src/main/materials"
set "distFolder=../src/main/assets/materials"for %%f in ("%srcFolder%\*.mat") do (set "matfile=%%~nf"matc -p mobile -a opengl -o "!matfile!.filamat" "%%f"move "!matfile!.filamat" "%distFolder%\!matfile!.filamat"
)echo Processing complete.
pause

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

        运行效果如下。
 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/579980.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HTML制作暴雨特效

🎀效果展示 🎀代码展示 <body> <!-- partial:index.partial.html --> <canvas id="canvas-club">

python消费rabbitmq

队列经常用&#xff0c;能保持信息一致性。也能跨语言&#xff0c;java写的生产者&#xff0c;推到队列中&#xff0c;python写的消费者消费。 这里&#xff0c;生成者&#xff0c;我们是java&#xff0c;已经发了一条消息了。 python是使用pika来链接rabbitmq 安装pika pip…

爬虫工作量由小到大的思维转变---<Scrapy异常的存放小探讨>

前言: 异常很正常,调试异常/日志异常/错误异常~ 但在爬虫的时候,写完代码--->运行后根本挡不住一些运行异常;于是,把异常写到了中间件~ 当然,这也没有错; 不过,其实可以直接这么设计一下... 正文: 参照一下中间件处理的异常 def process_exception(self, request, exc…

扩展mybatis-plus,保留逻辑删、逻辑查的前提下,扩展硬删除、硬查询

引入相关依赖 <!-- 提示&#xff1a;1. common-mybatis-plus:2100.8.8 中只有4个类文件&#xff0c;是对硬删除、硬查询的扩展支持&#xff0c;如果你不想引入依赖的话&#xff0c;你可以把这四个文件复制到自己的项目中即可2. common-mybatis-plus:2100.8.8 对应mybatis-p…

青少年CTF-qsnctf-Web-include01include02(多种方法-知识点较多-建议收藏!)

PHP常见伪协议 php://filter是PHP中独有的一种协议&#xff0c;它是一种过滤器&#xff0c;可以作为一个中间流来过滤其他的数据流。通常使用该协议来读取或者写入部分数据&#xff0c;且在读取和写入之前对数据进行一些过滤&#xff0c;例如base64编码处理&#xff0c;rot13处…

Unity 渲染顺序受哪些影响(相机depth、SortingLayer、Render Queue、透明)

目录 相机深度&#xff08;Camera Depth&#xff09; Clear Flags 多相机渲染不同部分 SortingLayer 先后顺序 Render Queue Render Queue的作用 Render Queue的分类 GeometryLast&#xff08;值为2500&#xff09; 渲染顺序总结 相机深度&#xff08;Camera Depth&am…

MongoDB ReplicaSet 部署

文章目录 前言1. 环境准备2. 生成密钥3. 配置参数4. 创建 ReplicaSet5. 副本集维护5.1 新增成员5.2 移除节点5.4 主节点降级5.5 阻止选举5.6 允许副本节点读5.7 延迟观测 6. 连接副本集 后记 前言 本篇文章介绍 MongoDB ReplicaSet 如何搭建&#xff0c;及常用的维护方法。 1…

求简单表达式的值

题目&#xff1a;在键盘输入类似(56-20)/(42)这样的表达式输出结果 此题分为两部分&#xff08;1&#xff09;将表达式转换成后缀表达式&#xff08;2&#xff09;计算后缀表达式的值 需要注意的是本题要定义两个不同的栈 一个数据类型是字符&#xff0c;一个数据类型是doubl…

Spring Boot:Spring Boot 入门、yaml 配置文件给属性赋值、自动装配原理详解

文章目录 Spring Boot - 01一、概述二、第一个 Spring Boot 程序补充知识 三、配置文件1. yaml 配置文件2. 使用 yaml 配置文件给属性赋值3. 松散绑定以及数据校验4. 配置文件的位置以及多环境配置 四、Spring Boot 分析1. pom.xml2. 启动器3. 主程序4. 自动装配原理5. 主启动类…

数栈UI5.0设计实战|B端表单这样设计,不仅美观还提效

表单是B端产品中最常见的组件之一&#xff0c;主要⽤于数据收集、校验和提交。比如登陆流程的账号密码填写&#xff0c;注册流程的邮箱、用户名等信息填写&#xff0c;都是表单应用的常见案例&#xff0c;在数栈产品中也是出现频率⾮常⾼的组件。 尽管表单应用十分普遍&#x…

腾讯云4核8G服务器轻量和CVM标准型S5对比

腾讯云4核8G服务器优惠价格表&#xff0c;云服务器CVM标准型S5实例4核8G配置价格15个月1437.3元&#xff0c;5年6490.44元&#xff0c;轻量应用服务器4核8G12M带宽一年446元、529元15个月&#xff0c;阿腾云atengyun.com分享腾讯云4核8G服务器详细配置、优惠价格及限制条件&…

网络通信协议

WebSocket通信 WebSocket是一种基于TCP的网络通信协议&#xff0c;提供了浏览器和服务器之间的全双工通信&#xff08;full-duplex&#xff09;能力。在WebSocket API中&#xff0c;浏览器和服务器只需要完成一次握手&#xff0c;两者之间就直接可以创建持久性的连接&#xff…

我们可以创建一个包含可变对象的不可变对象吗?

不可变对象是指一旦创建其状态就无法更改的对象。任何修改都会产生一个新对象&#xff0c;例如String、Integer和其他包装类。 请参阅在 Java 中创建不可变类的分步指南http://t.csdnimg.cn/8muWz。我们可以创建一个包含可变对象的不可变对象吗&#xff1f; 是的&…

虚幻学习笔记16—C++和3DUI(二)

一、前言 上一篇虚幻学习笔记15—C和UI&#xff08;一&#xff09;中介绍了通过C代码创建2D的ui&#xff0c;本章主要讲解怎么用C代码创建3D的UI&#xff0c;在虚幻学习笔记3—UI跟随弹窗这章中讲解了怎样用蓝图创建一个3D的UI&#xff0c;并且和其交互。 本系列使用的虚幻5.2.…

【Tensor张量】AI模型的信息流通货币

官方解释https://www.tensorflow.org/guide/tensor?hl=zh-cn 1.Tensor的概念理解 如果大模型是一个会运行的城市工厂,那么Tensor就是 运输车! 如果大模型是计算机,那么Tensor就是硬盘。 负责深度学习数据的运输和存储!把数据送给AI模型进行训练,AI模型推理后的数据也…

操作系统“文艺复兴”,云数智融合涌现“美第奇效应”

操作系统作为信息技术中的基础软件&#xff0c;是现代计算机的“灵魂”。随着大模型AI推动云计算、大数据、人工智能、物联网和5G等新一代数字技术的飞速融合&#xff0c;现代计算机体系结构和软件架构面临着重大创新机遇期。图灵奖获得者John Hennessy和David Patterson早在20…

探索未来的方向

运维工程师的出路到底在哪里&#xff1f; 随着技术的不断发展和进步&#xff0c;运维行业也在不断演变。对于许多运维人员来说&#xff0c;他们可能会感到困惑&#xff0c;不知道自己的出路在哪里。本文将探讨运维人员未来的发展方向和机会。 首先&#xff0c;对于那些对技术有…

接口测试学习笔记

文章目录 认识urlhttp协议接口规范Postman实现接口测试设计接口测试用例使用软件发送请求并查看响应结果Postman 自动关联Postman如何提交multipart/form-data请求数据Postman如何提交查询参数Postman 如何批量执行用例单接口测试Postman 断言Postman参数化 接口测试自动化requ…

python常用函数汇总

python常用函数汇总 对准蓝字按下左键可以跳转哦 类型函数数值相关函数abs() divmod() max() min() pow() round() sum()类型转换函数ascii() bin() hex() oct() bool() bytearray() bytes() chr() complex() float() int() 迭代和循环函数iter() next() e…

家有一宝,快乐翻倍,最新最全面基于AdGuard Home 自建 DNS 防污染、去广告教程、安装部署详解、优化增强设置详解

家有一宝,快乐翻倍,最新最全面基于AdGuard Home 自建 DNS 防污染、去广告教程、安装部署详解、优化增强设置详解。支持各个客户端,全平台覆盖。 AdGuard Home 部署的方式有很多种,一般二进制文件部署和直接编译到 OpenWrt 系统中是大家所常用的。而博主个人倾向于使用 Dock…