【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/ 目录下面。
运行效果如下。

相关文章:
【Filament】立方体贴图(6张图)
1 前言 本文通过一个立方体贴图的例子,讲解三维纹理贴图(子网格贴图)的应用,案例中使用 6 张不同的图片给立方体贴图,图片如下。 读者如果对 Filament 不太熟悉,请回顾以下内容。 Filament环境搭建绘制三角…...
SpringBoot 3.2.0 结合Redisson接入Redis
依赖版本 JDK 17 Spring Boot 3.2.0 Redisson 3.25.0 工程源码:Gitee 集成Redis步骤 导入依赖 <properties><redisson.version>3.25.0</redisson.version> </properties> <dependencies><dependency><groupId>org.pr…...
C++ 比C语言增加的新特性 5 之字符串string
1. c 的string类型 1.1 创建和初始化字符串 string.cpp #include "iostream" #include <string>using namespace std;//创建和初始化字符串 int main() {// 初始化空字符串string emptyString;// 使用字符串字面量初始化字符串string greeting "hello, …...
【第2讲】原理介绍和权限开通
系列文章目录 第1讲:Python环境的下载和安装第2讲:免费开通权限第3讲:1行代码,自动发正文第4讲:1行代码,自动发正文+附件第5讲:自动批量发送第6讲:1行代码,自动下载邮件的附件提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 系列文章目录…...
C++ opencv-3.4.1 提取不规则物体的轮廓
在学习opencv的时候,对一张照片,需要标注照片上物体的不规则轮廓。 如图: 使用opencv进行物体的轮廓处理,关键在于对照片的理解,前期的照片处理的越好最后调用api出来的结果就越接近理想值。 提取照片中物体分如下三步ÿ…...
ServletConfig对象.
是什么 ServletConfig是javax.servlet.包下的一个接口,ServletConfig它是Servlet的一个配置对象; ServletConfig是由tomcat容器创建,通过init方法传入给Servlet; ServletConfig对象如何获取? 在GenericServlet里面定义了&#x…...
jQuery实现框里画面的展开、收起和停止
jQuery实现框里画面的展开、收起和停止 主要用到动画效果中的三个操作: (“id”).slideDown(3000); // 后面的数字表示效果的时长 (“id”).stop(); (“id”).slideUp(3000); 效果图 代码如下: <!DOCTYPE html> <html lang"en…...
less 查看文本时,提示may be a binary file.See it anyway?
解决办法 首先使用echo $LESSCHARSET查看less的编码 看情况设置less的编码格式(我的服务器上使用utf-8查看中文) 还要特别注意一下,Linux中存在的文本文件的编码一定要是utf - 8;(这一步很关键) 例如:要保证windows上传到Linux的…...
H266/VVC帧内预测编码技术概述
预测编码技术 预测编码(Prediction Coding)是指利用已编码的一个或多个样本值,根据某种模型或方法,对当前的样本值进行预测,并对样本真实值和预测值之间的差值进行编码。 视频中的每个像素看成一个信源符号ÿ…...
重组蛋白表达系统的比较-卡梅德生物
一、重组蛋白表达是什么? 重组蛋白表达是通过基因工程手段将目标蛋白基因导入宿主细胞,使其表达出特定的蛋白。该过程包括以下步骤: 1. 构建表达载体:将目标蛋白基因插入表达载体中,通常选择带有启动子、终止子和选择…...
【Java、Python】获取电脑当前网络IP进行位置获取(附源码)
我相信看到这篇博客的时候心里肯定是想解决自己的一个问题的,而这篇博客我就以简单快速的方式解决这些烦恼! 一、获取当前IP 在Java中自带了一些自己的流对象来获取当前的IP地址,不多说我们直接上代码。 //获取当前网络ip地址 ipAddress Ine…...
接口测试学习笔记
文章目录 认识urlhttp协议接口规范Postman实现接口测试设计接口测试用例使用软件发送请求并查看响应结果Postman 自动关联Postman如何提交multipart/form-data请求数据Postman如何提交查询参数Postman 如何批量执行用例单接口测试Postman 断言Postman参数化 接口测试自动化requ…...
一起玩儿物联网人工智能小车(ESP32)——14. 用ESP32的GPIO控制智能小车运动起来(二)
摘要:本文主要讲解如何使用Mixly实现对单一车轮的运动控制。 下面就该用程序控制我们的小车轮子转起来了。打开Mixly软件,然后单击顶部“文件”菜单中的“新建”功能,我们来开启一个新程序的开发工作。 我们的工作同样是先从最简单的开始&am…...
[PyTorch][chapter 8][李宏毅深度学习][DNN 训练技巧]
前言: DNN 是神经网络的里面基础核心模型之一.这里面结合DNN 介绍一下如何解决 深度学习里面过拟合,欠拟合问题 目录: DNN 训练常见问题 过拟合处理 欠拟合处理 keras 项目 一 DNN 训练常见问题 我们在深度学习网络训练的时候经常会遇到下面…...
Nginx快速入门:实现企业安全防护|nginx部署https,ssl证书(七)
0. 引言 之前我们讲到nginx的一大核心作用就是实现企业安全防护,而实现安全防护的原理就是通过部署https证书,以此实现参数加密访问,从而加强企业网站的安全能力。 nginx作为各类服务的统一入口,只需要在入口处部署一个证书&…...
将Go语言开发的Web程序部署到K8S
搭建K8S基础环境 如果已经有K8S环境的同学可以跳过,如果没有,推荐你看看我的《Ubuntu22加Minikue搭建K8S环境》,课程目录如下: Ubuntu22安装Vscode 下载:https://code.visualstudio.com/Download 安装命令&#…...
Python发送数据到Unity实现
Unity设置: 打开Unity项目。创建一个空的GameObject,并附加一个新的脚本TCPReceiver using System.Net; using System.Net.Sockets; using System.Text; using UnityEngine; using System.Threading;public class MyListener : MonoBehaviour {Thread thread;pub…...
Unity 渲染顺序受哪些影响(相机depth、SortingLayer、Render Queue、透明)
目录 相机深度(Camera Depth) Clear Flags 多相机渲染不同部分 SortingLayer 先后顺序 Render Queue Render Queue的作用 Render Queue的分类 GeometryLast(值为2500) 渲染顺序总结 相机深度(Camera Depth&am…...
【论文笔记】Run, Don’t Walk: Chasing Higher FLOPS for Faster Neural Networks
论文地址:Run, Dont Walk: Chasing Higher FLOPS for Faster Neural Networks 代码地址:https://github.com/jierunchen/fasternet 该论文主要提出了PConv,通过优化FLOPS提出了快速推理模型FasterNet。 在设计神经网络结构的时候ÿ…...
python常用函数汇总
python常用函数汇总 对准蓝字按下左键可以跳转哦 类型函数数值相关函数abs() divmod() max() min() pow() round() sum()类型转换函数ascii() bin() hex() oct() bool() bytearray() bytes() chr() complex() float() int() 迭代和循环函数iter() next() e…...
python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...
Linux云原生安全:零信任架构与机密计算
Linux云原生安全:零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言:云原生安全的范式革命 随着云原生技术的普及,安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测,到2025年,零信任架构将成为超…...
PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...
CSS设置元素的宽度根据其内容自动调整
width: fit-content 是 CSS 中的一个属性值,用于设置元素的宽度根据其内容自动调整,确保宽度刚好容纳内容而不会超出。 效果对比 默认情况(width: auto): 块级元素(如 <div>)会占满父容器…...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...
2025年渗透测试面试题总结-腾讯[实习]科恩实验室-安全工程师(题目+回答)
安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 目录 腾讯[实习]科恩实验室-安全工程师 一、网络与协议 1. TCP三次握手 2. SYN扫描原理 3. HTTPS证书机制 二…...
Linux nano命令的基本使用
参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时,显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...
Proxmox Mail Gateway安装指南:从零开始配置高效邮件过滤系统
💝💝💝欢迎莅临我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:「storms…...
安卓基础(Java 和 Gradle 版本)
1. 设置项目的 JDK 版本 方法1:通过 Project Structure File → Project Structure... (或按 CtrlAltShiftS) 左侧选择 SDK Location 在 Gradle Settings 部分,设置 Gradle JDK 方法2:通过 Settings File → Settings... (或 CtrlAltS)…...
用鸿蒙HarmonyOS5实现中国象棋小游戏的过程
下面是一个基于鸿蒙OS (HarmonyOS) 的中国象棋小游戏的实现代码。这个实现使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chinesechess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├──…...
