1 前言

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

​ 读者如果对 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.compile(
Material.CompilerPriorityQueue.HIGH,
Material.UserVariantFilterBit.ALL,
new Handler(Looper.getMainLooper()),
() -> Log.i(TAG, "Material " + material.getName() + " compiled."));
engine.flush();
return material;
}
return null;
} private static Buffer readUncompressedAsset(Context context, String assetPath) { // 加载资源
ByteBuffer dist = null;
try {
AssetFileDescriptor fd = context.getAssets().openFd(assetPath);
try(FileInputStream fis = fd.createInputStream()) {
dist = ByteBuffer.allocate((int) fd.getLength());
try (ReadableByteChannel src = Channels.newChannel(fis)) {
src.read(dist);
}
}
} catch (IOException e) {
e.printStackTrace();
}
if (dist != null) {
return dist.rewind();
}
return null;
}
}

​ 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
Bitmap bitmap = null;
try (InputStream inputStream = context.getAssets().open(assetPath)) {
bitmap = BitmapFactory.decodeStream(inputStream);
} 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张图)的更多相关文章

  1. SQL知识点脑图(一张图总结SQL)

    sql语言的分类DDL:create drop alter DML:insert delete update DCL:rollback grant revoke commit 概要,主外键,视图,索引 ...

  2. OpenGL+OpenCV实现立方体贴图

    我屮艸芔茻,转眼就7月份了. 今天试了一下立方体贴图,比较简单,大概说下和平面贴图的区别. 1. 平面贴图需要的是纹理坐标vec2:立方体贴图需要的是一个方向向量vec3,长度没有关系,重要的是方向, ...

  3. OpenGL 核心技术之立方体贴图

    笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家.特邀编辑,畅销书作者,国家专利发明人;已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D ...

  4. Unity Shaders and Effects Cookbook (4-1)(4-2)静态立方体贴图的创建与使用

    開始学习第4章 - 着色器的反射 看完了1.2节,来记录一下.反射主要是利用了 Cubemap 立方体贴图. 认识Cubemap 立方体贴图.就如同名字所说.在一个立方体上有6张图.就这样觉得吧. 假 ...

  5. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十八章:立方体贴图

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十八章:立方体贴图 代码工程地址: https://github.c ...

  6. threejs立方体贴图产生边缘锯齿问题

    threejs立方体贴图产生边缘锯齿问题 立方体贴图边缘锯齿 解决后 经过试验测试发现, textureGrass.wrapS和 textureGrass.wrapT属性导致的. 解决方法1: 删掉t ...

  7. WebGL 利用FBO完成立方体贴图。

    这篇主要记录WebGL的一些基本要点,顺便也学习下如何使用FBO与环境贴图.先看下效果图(需要支持WebGL,Chrome,火狐,IE11). 主要实现过程如下,先用FBO输出当前环境在立方体纹理中, ...

  8. 立方体贴图(Cubemap)

    http://blog.csdn.net/asdjy123/article/details/51190643 点击打开链接 好东西保存方便查看 立方体贴图(Cubemap) 原文 Cubemaps 作 ...

  9. (转)OpenGL学习——立方体贴图

    转自:https://learnopengl-cn.readthedocs.io/zh/latest/04%20Advanced%20OpenGL/06%20Cubemaps/ 我们之前一直使用的是2 ...

  10. 一张图告诉你,只会HTML还不够!

    会了HTML和HTML5语法,你就真的会了HTML吗,来看这张图!是这本<超实用的HTML代码段>入门实例书的导览!熊孩子们,赶紧学习去吧! 如果一半以上的你都不会,必须看这本书,阿里一线 ...

随机推荐

  1. java浅拷贝BeanUtils.copyProperties引发的RPC异常 | 京东物流技术团队

    背景 近期参与了一个攻坚项目,前期因为其他流程原因,测试时间已经耽搁了好几天了,本以为已经解决了卡点,后续流程应该顺顺利利的,没想到 人在地铁上,bug从咚咚来~ 没有任何修改的服务接口,抛出异常: ...

  2. echarts去除坐标轴上的x和y轴

    通过 show:false控制手否显示 <!DOCTYPE html> <html lang="en"> <head> <meta cha ...

  3. 探索 GO 项目依赖包管理与Go Module常规操作

    探索 GO 项目依赖包管理与Go Module常规操作 目录 探索 GO 项目依赖包管理与Go Module常规操作 一.Go 构建模式的演变 1.1 GOPATH (初版) 1.1.1 go get ...

  4. 不同版本的Unity要求的NDK版本和两者对应关系表(Unity NDK Version Match)

    IL2CPP需要NDK Unity使用IL2CPP模式出安卓包时,需要用到NDK,如果没有安装则无法导出Android Studio工程或直接生成APK,本篇记录一下我下载NDK不同版本的填坑过程. ...

  5. 手撕Vue-数据驱动界面改变中

    经过上一篇的介绍,已经实现了观察者模式的基本内容,接下来要完成的就是将上一篇的发布订阅模式运用到 Nue 中,实现数据驱动界面改变. 在监听数据变化的章节当中,根据指定的区域和数据去编译渲染界面 这个 ...

  6. TienChin 活动管理-活动导出

    ActivityController /** * 导出活动列表 */ @PreAuthorize("hasPermission('tienchin:activity:export')&quo ...

  7. 【四】多智能体强化学习(MARL)近年研究概览 {Learning cooperation(协作学习)、Agents modeling agents(智能体建模)}

    相关文章: [一]最新多智能体强化学习方法[总结] [二]最新多智能体强化学习文章如何查阅{顶会:AAAI. ICML } [三]多智能体强化学习(MARL)近年研究概览 {Analysis of e ...

  8. 技嘉水雕II 360水冷散热器评测:稳压340W i9-14900K

    一.前言:极简卡扣连锁风扇设计 再多风扇也只需2根线 如今这个年代,DIY主机几乎都会配大量的RGB风扇,然而"光污染"虽然带来了视觉感官享受,在理线方面却非常繁琐. 就拿360水 ...

  9. DELPHI IDE 代码智能提示

  10. 从零开始的react入门教程(四),了解常用的条件渲染、列表渲染与独一无二的key

    壹 ❀ 引 在从零开始的react入门教程(三),了解react事件与使用注意项一文中,我们了解了react中事件命名规则,绑定事件时对于this的处理,以及事件中可使用的e对象.那么这篇文章中我们来 ...