【Filament】立方体贴图(6张图)
- 创业
- 2025-07-21 18:59:42

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、SurfaceHolder protected Engine mEngine; // 引擎(跟踪用户创建的资源, 管理渲染线程和硬件渲染器) protected Renderer mRenderer; // 渲染器(用于操作系统窗口, 生成绘制命令, 管理帧延时) protected Scene mScene; // 场景(管理渲染对象、灯光) protected View mView; // 存储渲染数据(View是Renderer操作的对象) protected Camera mCamera; // 相机(视角管理) protected Point mDesiredSize; // 渲染分辨率 protected float[] mSkyboxColor; // 背景颜色 protected SwapChain mSwapChain; // 操作系统的本地可渲染表面(native renderable surface, 通常是一个window或view) protected FrameCallback mFrameCallback = new FrameCallback(); // 帧回调 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() { // 设置SurfaceView mUiHelper = new UiHelper(UiHelper.ContextErrorPolicy.DONT_CHECK); mUiHelper.setRenderCallback(new SurfaceCallback()); if (mDesiredSize != null) { mUiHelper.setDesiredSize(mDesiredSize.x, mDesiredSize.y); } mUiHelper.attachTo(this); } private void setupFilament() { // 设置Filament参数 mEngine = Engine.create(); // mEngine = (new Engine.Builder()).featureLevel(Engine.FeatureLevel.FEATURE_LEVEL_0).build(); mRenderer = mEngine.createRenderer(); mScene = mEngine.createScene(); mView = mEngine.createView(); mCamera = mEngine.createCamera(mEngine.getEntityManager().create()); } private void setupView() { // 设置View参数 float[] color = mSkyboxColor != null ? mSkyboxColor : new float[] {0, 0, 0, 1}; Skybox skybox = (new Skybox.Builder()).color(color).build(mEngine); mScene.setSkybox(skybox); if (mEngine.getActiveFeatureLevel() == Engine.FeatureLevel.FEATURE_LEVEL_0) { mView.setPostProcessingEnabled(false); // FEATURE_LEVEL_0不支持post-processing } mView.setCamera(mCamera); mView.setScene(mScene); } /* * 帧回调 */ private class FrameCallback implements Choreographer.FrameCallback { @Override public void doFrame(long frameTimeNanos) { // 渲染每帧数据 if (mRenderMode == RENDERMODE_CONTINUOUSLY) { mChoreographer.postFrameCallback(this); // 请求下一帧 } 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 { @Override public void onNativeWindowChanged(Surface surface) { // Native窗口改变时回调 if (mSwapChain != null) { mEngine.destroySwapChain(mSwapChain); } long flags = mUiHelper.getSwapChainFlags(); if (mEngine.getActiveFeatureLevel() == Engine.FeatureLevel.FEATURE_LEVEL_0) { if (SwapChain.isSRGBSwapChainSupported(mEngine)) { flags = flags | SwapChain.CONFIG_SRGB_COLORSPACE; } } mSwapChain = mEngine.createSwapChain(surface, flags); mDisplayHelper.attach(mRenderer, getDisplay()); } @Override public void onDetachedFromSurface() { // 解绑Surface时回调 mDisplayHelper.detach(); if (mSwapChain != null) { mEngine.destroySwapChain(mSwapChain); mEngine.flushAndWait(); mSwapChain = null; } } @Override public void onResized(int width, int height) { // Surface尺寸变化时回调 mView.setViewport(new Viewport(0, 0, width, height)); FilamentHelper.synchronizePendingFrames(mEngine); FLSurfaceView.this.onResized(width, height); } } /* * 每一帧渲染前的回调 * 一般用于处理模型变换、相机变换等 */ 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; // 渲染id protected int mTransformComponent; // 模型变换组件的id protected Box mBox; // 渲染区域 protected FLSurfaceView.RenderCallback mRenderCallback; // 每一帧渲染前的回调(一般用于处理模型变换、相机变换等) public BaseModel(Context context, Engine engine) { mContext = context; mEngine = engine; mTransformManager = mEngine.getTransformManager(); } public int getRenderable() { // 获取渲染id return 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) { // 获取渲染id int 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数组转换为ByteBuffer ByteBuffer byteBuffer = ByteBuffer.allocate(values.length * Float.BYTES); byteBuffer.order(ByteOrder.nativeOrder()); for (int i = 0; i < values.length; i++) { byteBuffer.putFloat(values[i]); } byteBuffer.flip(); return byteBuffer; } private ByteBuffer getByteBuffer(short[] values) { // short数组转换为ByteBuffer ByteBuffer byteBuffer = ByteBuffer.allocate(values.length * Short.BYTES); byteBuffer.order(ByteOrder.nativeOrder()); for (int i = 0; i < values.length; i++) { byteBuffer.putShort(values[i]); } byteBuffer.flip(); return byteBuffer; } private ByteBuffer getByteBuffer(VertexPosCol[] values) { // VertexPosCol数组转换为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数组转换为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; } /* * 子网格 */ 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转换为ByteBuffer buffer.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转换为ByteBuffer buffer.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 pile( 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) { // 获取Texture Bitmap bitmap = loadBitmapFromAsset(context, texturePath); if (bitmap != null) { return generateTexture(engine, bitmap); } return null; } public static Texture getTexture(Context context, Engine engine, int resourceId) { // 获取Texture Bitmap bitmap = loadBitmapFromDrawable(context, resourceId); if (bitmap != null) { return generateTexture(engine, bitmap); } return null; } private static Texture generateTexture(Engine engine, Bitmap bitmap) { // 生成Texture Texture texture = new Texture.Builder() .width(bitmap.getWidth()) .height(bitmap.getHeight()) .sampler(Texture.Sampler.SAMPLER_2D) .format(Texture.InternalFormat.SRGB8_A8) .levels(0xff) .build(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中加载bitmap InputStream 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中加载bitmap BitmapFactory.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; @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.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(); } @Override public void onDestroy() { mMyModel.destroy(); super.onDestroy(); } @Override protected void setupScene() { // 设置Scene参数 mMyModel = new Cube(getContext(), mEngine); mScene.addEntity(mMyModel.getRenderable()); addRenderCallback(mMyModel.getRenderCallback()); } @Override protected 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), // 0 new VertexPosUV(-r, r, r, 1f, 1f), // 1 new VertexPosUV(-r, -r, r, 1f, 0f), // 2 new VertexPosUV(r, -r, r, 0f, 0f), // 3 // 后面 new VertexPosUV(r, r, -r, 0f, 1f), // 4 new VertexPosUV(-r, r, -r, 1f, 1f), // 5 new VertexPosUV(-r, -r, -r, 1f, 0f), // 6 new VertexPosUV(r, -r, -r, 0f, 0f), // 7 // 上面 new VertexPosUV(r, r, r, 0f, 1f), // 8 new VertexPosUV(r, r, -r, 1f, 1f), // 9 new VertexPosUV(-r, r, -r, 1f, 0f), // 10 new VertexPosUV(-r, r, r, 0f, 0f), // 11 // 下面 new VertexPosUV(r, -r, r, 0f, 1f), // 12 new VertexPosUV(r, -r, -r, 1f, 1f), // 13 new VertexPosUV(-r, -r, -r, 1f, 0f), // 14 new VertexPosUV(-r, -r, r, 0f, 0f), // 15 // 右面 new VertexPosUV(r, r, r, 0f, 1f), // 16 new VertexPosUV(r, r, -r, 1f, 1f), // 17 new VertexPosUV(r, -r, -r, 1f, 0f), // 18 new VertexPosUV(r, -r, r, 0f, 0f), // 19 // 左面 new VertexPosUV(-r, r, r, 0f, 1f), // 20 new VertexPosUV(-r, r, -r, 1f, 1f), // 21 new VertexPosUV(-r, -r, -r, 1f, 0f), // 22 new 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张图)由讯客互联创业栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“【Filament】立方体贴图(6张图)”