上篇创建的是LitMesh,这种Mesh要计算Normal和Tangent才能正确显示颜色,不然是黑色的。Tangent是根据Normal和UV计算出来的,所以改变一次UV就得计算一遍Tangent。目前还用不到光照,这篇就创建简单的UnlitMesh来做例子了。
还有Tiny运行时无法动态加载Material,也无法像GameObject那样将Material转成预制体引用起来(至少目前没看到类似的用法),所以只能在场景中建一个球体,挂上要用到的材质,然后在运行过程中创建好DynamicMesh后,把球体的材质赋值给DynamicMesh。
这篇的主要效果就是动态创建一个UnlitMesh,然后给它赋值一个带纹理的材质,再动态改变Mesh的UV,从而显示纹理上的不同部分。代码有点多,还有用Unity的ECS写代码容易出错,可能稍微改点代码就运行不起来了,这个要注意。
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;
using Unity.Tiny.Rendering;
using Unity.Tiny;
namespace TinyMesh {
public struct MaterialHolder : IComponentData
{
}
public struct ChangeMeshUV : IComponentData
{
}
// [DisableAutoCreation]
[UpdateBefore(typeof(ChangeMeshUVSystem))]
public class CreateUnlitMeshSystem : SystemBase {
EntityCommandBuffer ecb;
protected override void OnCreate() {
base.OnCreate();
ecb = World.GetExistingSystem<EndSimulationEntityCommandBufferSystem>().CreateCommandBuffer();
}
protected override void OnUpdate() {
Entities.WithAll<MaterialHolder>().ForEach((Entity e) => {
var mat = EntityManager.GetComponentData<MeshRenderer>(e).material;
var unlitMesh = ecb.CreateEntity(); // ForEach里边只能用EntityCommandBuffer来创建Mesh,用EntityManager会报错
// 添加储存顶点数据的Buffer
var vertex = ecb.AddBuffer<DynamicSimpleVertex>(unlitMesh); // 不需要光照的SimpleVertex
// 添加储存顶点下标的Buffer
var index = ecb.AddBuffer<DynamicIndex>(unlitMesh);
vertex.Capacity = 4; // buffer的总大小,可以不用跟ResizeUninitialized的设置一样,但必须大于等于
vertex.ResizeUninitialized(4); // 把buffer长度设置为4,且不初始化每个数据的值(里边的值是乱的)
index.Capacity = 6;
index.ResizeUninitialized(6);
DynamicMeshData dmd = new DynamicMeshData{
Dirty = true,
IndexCapacity = index.Capacity,
VertexCapacity = vertex.Capacity,
NumIndices = index.Length,
NumVertices = vertex.Length,
UseDynamicGPUBuffer = true
};
MeshBounds mb;
var org = new float3(-0.5f, -0.5f, 0);
var du = new float3(1, 0, 0);
var dv = new float3(0, 1, 0);
var destVertices = vertex.AsNativeArray().Reinterpret<DynamicSimpleVertex, SimpleVertex>();
var destIndices = index.AsNativeArray().Reinterpret<DynamicIndex, ushort>();
MeshHelper.FillPlaneMesh(destVertices, destIndices, org, du, dv, out mb.Bounds);
unsafe {
int n = vertex.Length;
SimpleVertex *vp = (SimpleVertex *)vertex.GetUnsafePtr();
for (int i = 0; i < n; i++) {
vp[i].Color = new float4(1, 0, 0, 1);
}
}
// 添加各种渲染UnlitMesh必须用到的组件
ecb.AddComponent<DynamicMeshData>(unlitMesh, dmd); // 动态网格数据,这种可以在运行时改顶点数据的。还有一种是固定了顶点数据的,无法在运行时改数据!
ecb.AddComponent<MeshRenderer>(unlitMesh, new MeshRenderer{ // 网格渲染组件
mesh = unlitMesh,
material = mat,
startIndex = 0,
indexCount = dmd.NumIndices
});
ecb.AddComponent(unlitMesh, new SimpleMeshRenderer()); // 这个组件用来表明该Mesh是不支持光照的网格
ecb.AddComponent(unlitMesh, mb); // Mesh的包围盒
ecb.AddComponent<Translation>(unlitMesh, new Translation { Value = float3.zero });
ecb.AddComponent<Rotation>(unlitMesh, new Rotation { Value = quaternion.RotateX(179) });
ecb.AddComponent<LocalToWorld>(unlitMesh, new LocalToWorld { Value = float4x4.Translate(float3.zero) });
ecb.AddComponent(unlitMesh, new ChangeMeshUV());
ecb.SetComponent(unlitMesh, dmd);
}).WithoutBurst().Run();
// 在Entities.ForEach外面可以用EntityManager创建Entity
var unlitMaterial = CreateUnlitMaterial();
var _unlitMesh = CreateUnlitMesh(unlitMaterial);
EntityManager.SetComponentData<Translation>(_unlitMesh, new Translation{Value = new float3(-2, 1, 0)});
EntityManager.AddComponentData(_unlitMesh, new DemoSpinner { spin = math.normalize(new quaternion(new float4(1, 0, 0, 1))) });
// 销毁该System,避免重复创建。在OnCreate中无法调用EntityManager创建Entity的
World.GetExistingSystem<SimulationSystemGroup>().RemoveSystemFromUpdateList(this);
World.DestroySystem(this);
}
// 注意:该接口不能在Entities.ForEach中调用
Entity CreateUnlitMesh(Entity unlitMaterial) {
var unlitMesh = EntityManager.CreateEntity();
// 添加储存顶点数据的Buffer
EntityManager.AddBuffer<DynamicSimpleVertex>(unlitMesh); // 不需要光照的SimpleVertex
// 添加储存顶点下标的Buffer
EntityManager.AddBuffer<DynamicIndex>(unlitMesh);
var vertex = EntityManager.GetBuffer<DynamicSimpleVertex>(unlitMesh);
var index = EntityManager.GetBuffer<DynamicIndex>(unlitMesh);
vertex.Capacity = 4; // buffer的总大小,可以不用跟ResizeUninitialized的设置一样,但必须大于等于
vertex.ResizeUninitialized(4); // 把buffer长度设置为4,且不初始化每个数据的值(里边的值是乱的)
index.Capacity = 6;
index.ResizeUninitialized(6);
DynamicMeshData dmd = new DynamicMeshData{
Dirty = true,
IndexCapacity = index.Capacity,
VertexCapacity = vertex.Capacity,
NumIndices = index.Length,
NumVertices = vertex.Length,
UseDynamicGPUBuffer = true
};
MeshBounds mb;
var org = new float3(-0.5f, -0.5f, 0);
var du = new float3(1, 0, 0);
var dv = new float3(0, 1, 0);
var destVertices = vertex.AsNativeArray().Reinterpret<DynamicSimpleVertex, SimpleVertex>();
var destIndices = index.AsNativeArray().Reinterpret<DynamicIndex, ushort>();
// 手动设置顶点数据
destVertices[0] = new SimpleVertex { Position = org, Color = new float4(1, 0, 0, 1), TexCoord0 = new float2(0, 0) };
destVertices[1] = new SimpleVertex { Position = org + du, Color = new float4(0, 1, 0, 1), TexCoord0 = new float2(1, 0) };
destVertices[2] = new SimpleVertex { Position = org + du + dv, Color = new float4(0, 0, 1, 1), TexCoord0 = new float2(1, 1) };
destVertices[3] = new SimpleVertex { Position = org + dv, Color = new float4(0.5f, 0.5f, 0.5f, 1), TexCoord0 = new float2(0, 1) };
destIndices[0] = 0; destIndices[1] = 1; destIndices[2] = 2;
destIndices[3] = 2; destIndices[4] = 3; destIndices[5] = 0;
mb.Bounds = MeshHelper.ComputeBounds(destVertices);
// 调用接口设置顶点数据,颜色默认是白色
// MeshHelper.FillPlaneMesh(destVertices, destIndices, org, du, dv, out mb.Bounds);
// unsafe { // 可以这样重新设置顶点颜色
// int n = vertex.Length;
// SimpleVertex *vp = (SimpleVertex *)vertex.GetUnsafePtr();
// for (int i = 0; i < n; i++)
// vp[i].Color = new float4(1, 0, 0, 1);
// }
// 添加各种渲染UnlitMesh必须用到的组件
EntityManager.AddComponentData<DynamicMeshData>(unlitMesh, dmd); // 动态网格数据,这种可以在运行时改顶点数据的。还有一种是固定了顶点数据的,无法在运行时改数据!
EntityManager.AddComponentData<MeshRenderer>(unlitMesh, new MeshRenderer{ // 网格渲染组件
mesh = unlitMesh,
material = unlitMaterial,
startIndex = 0,
indexCount = dmd.NumIndices
});
EntityManager.AddComponentData(unlitMesh, new SimpleMeshRenderer()); // 这个组件用来表明该Mesh是不支持光照的网格
EntityManager.AddComponentData(unlitMesh, mb); // Mesh的包围盒
EntityManager.AddComponentData<Translation>(unlitMesh, new Translation { Value = float3.zero });
EntityManager.AddComponentData<Rotation>(unlitMesh, new Rotation { Value = quaternion.identity });
EntityManager.AddComponentData<LocalToWorld>(unlitMesh, new LocalToWorld { Value = float4x4.Translate(float3.zero) });
return unlitMesh;
}
// 创建一个不带光照的材质。该接口也不能在Entities.ForEach中调用
Entity CreateUnlitMaterial() {
var unlitMaterial = EntityManager.CreateEntity();
EntityManager.AddComponentData(unlitMaterial, new SimpleMaterial
{
texAlbedoOpacity = Entity.Null,
constAlbedo = new float3(1, 1, 1),
constOpacity = 1.0f,
twoSided = true,
blend = BlendOp.Disabled,
transparent = false,
billboarded = false,
scale = new float2(1, 1),
offset = new float2(0, 0)
});
return unlitMaterial;
}
}
}
动态改变UV的System
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;
using Unity.Tiny.Rendering;
using Unity.Tiny;
namespace TinyMesh {
public class ChangeMeshUVSystem : SystemBase
{
float time = 0;
const int count = 16;
const float size = 1.0f / count;
protected override void OnUpdate()
{
Entities.WithAll<ChangeMeshUV>().ForEach((Entity entity, ref DynamicMeshData dmd, ref DynamicBuffer<DynamicSimpleVertex> dsv) => {
time += World.Time.DeltaTime;
var u = (int)(time % count);
var v = (int)((time / count) % count);
unsafe {
int n = dsv.Length;
SimpleVertex *vp = (SimpleVertex *)dsv.GetUnsafePtr();
vp[0].TexCoord0 = new float2(size * u, size * v);
vp[1].TexCoord0 = new float2(size * u + size, size * v);
vp[2].TexCoord0 = new float2(size * u + size, size * v + size);
vp[3].TexCoord0 = new float2(size * u, size * v + size);
}
dmd.Dirty = true;
}).WithoutBurst().Run();
}
}
}
打包安卓后运行效果