看到比较有意思的效果,就想偷师过来,哈哈哈哈哈
这玩意着实复杂,我拿到这份代码里面使用的都是NVIDIA的Blast方法,并且都是已经打成dll文件,看不到内部的实现,还好这个插件NVIDIA是开源的(就是有一些接口名有一些变动,但是最主要的是…………他的源码也太长了吧!)
写作思路:
1、原理总述
2、创建墙体
3、利用NVIDIA的API将墙体内部进行碎裂分块
4、给碎裂后的墙块添加关节(Joint)
5、墙体受击后的关节(Joint)处理
6、涉及到的相关文章及源码
效果图如下
1、原理总述
将创建好的墙体信息传入NVIDIA的API,API可以将墙体内部进行破碎(将完整的墙体切割成若干不规则的碎块),在这些碎块之间添加Joint组件,将剩余的物理模拟全权交付给Joint组件
2、创建墙体
由于在调用NVIDIA的API的时候,NVIDIA内部对于mesh是有一套自己的结构,因此需要在Unity层将顶点数据传给API进行一层转化,这样才能实现API的调用。
部分代码如下:
var nvMesh = new NvMesh(
mesh.vertices,
mesh.normals,
mesh.uv,
// 长方体的顶点数为24 大概率是 每个面4个, 4*6=24
mesh.vertexCount,
// 网格顶点的索引 (会存在重复索引,例如这里就有12个索引重复的顶点)好奇怪
// 初步猜测 应该是 一个面 有个两个三角片面组成 因此每个面:2*3=6个顶点,整个长方体有2*3*6=36个顶点
// 不同面的顶点是分属不同索引的,因此每个面的两个三角片面有2个重复顶点,整个长方体有2*6=12个重复顶点
mesh.GetIndices(0),
// 网格的顶点数 这里是36 2*3*6=36
(int) mesh.GetIndexCount(0)
);
3、利用NVIDIA的API将墙体内部进行碎裂分块
在Unity中使用的是dll文件进行方法调用的,因此看不到墙体分块的实现方法,这部分会在第二篇中展开
在Unity层面进行分块的调用如下
private void Voronoi(NvFractureTool fractureTool, NvMesh mesh)
{
var sites = new NvVoronoiSitesGenerator(mesh);
// 下面两个方法最终定位到NvBlastExtAuthoringFractureToolImpl.cpp中
// 在网格内均匀生成站点
// 这里传入的数量在其内部中是有限制的 最多为450块,多了也不会生效
sites.uniformlyGenerateSitesInMesh(totalChunks);
// 用voronoi方法对chunkId(在这里就是0)的块体进行碎裂
fractureTool.voronoiFracturing(0, sites);
}
上述 NvVoronoiSitesGenerator、uniformlyGenerateSitesInMesh、voronoiFracturing 这三个方法对应如下
public NvVoronoiSitesGenerator(NvMesh mesh)
{
//NvBlastExtAuthoringFractureTool.h 中定义了虚方法
//NvBlastExtAuthoringFractureToolImpl.h 中 FractureToolImpl 继承并定义了方法,在NvBlastExtAuthoringFractureToolImpl.cpp中实现了方法
//NvBlastExtAuthoring.cpp中NvBlastExtAuthoringCreateVoronoiSitesGenerator创建对象指针
/*
* 泰森多边形又叫冯洛诺伊图(Voronoi diagram),
* 得名于Georgy Voronoi,是一组由连接两邻点线段的垂直平分线组成的连续多边形组成。
* 一个泰森多边形内的任一点到构成该多边形的控制点的距离小于到其他多边形控制点的距离
*/
Initialize(_VoronoiSitesGenerator_Create(mesh.ptr));
NvLogger.Log("NvVoronoiSitesGenerator:" + this.ptr.ToString());
}
// 在网格内均匀生成站点
public void uniformlyGenerateSitesInMesh(int count)
{
//NvBlastExtAuthoringFractureTool.h 中定义了虚方法
//NvBlastExtAuthoringFractureToolImpl.h 中 FractureToolImpl 继承并定义了方法,在NvBlastExtAuthoringFractureToolImpl.cpp中实现了方法
_NvVoronoiSitesGenerator_uniformlyGenerateSitesInMesh(this.ptr, count);
}
public bool voronoiFracturing(int chunkId, NvVoronoiSitesGenerator vsg)
{
// 用voronoi方法对chunkId的块体进行骨折。
// 这个方法在最新的SDK里面已经没有了,但是有别的同名函数
// NvBlastExtAuthoringFractureToolImpl.h 中 FractureToolImpl 继承并定义了方法,在NvBlastExtAuthoringFractureToolImpl.cpp中实现了方法
return _FractureTool_voronoiFracturing(this.ptr, chunkId, vsg.ptr);
}
上面代码中注明了详细调用位置,如果等不及的可以在下面下载NVIDIA相关API进行查看
4、给碎裂后的墙块添加关节(Joint)
①遍历每一个碎裂后的小块chunk,找到其周围相邻的小块,添加上Unity自带的Joint组件,代码如下
/// <summary>
/// 给当前块到周围连接块添加<FixedJoint>组件并连接、设置铰链力
/// </summary>
/// <param name="chunk">块对象</param>
/// <param name="jointBreakForce">破坏关节的力大小</param>
/// <param name="touchRadius">接触的半径</param>
public static void ConnectTouchingChunks(GameObject chunk, float jointBreakForce, float touchRadius = .01f)
{
var rb = chunk.GetComponent<Rigidbody>();
var mesh = chunk.GetComponent<MeshFilter>().mesh;
// 获取重叠块 实际上就是相邻块
var overlaps = mesh.vertices
.Select(v => chunk.transform.TransformPoint(v))
.SelectMany(v => Physics.OverlapSphere(v, touchRadius))
.Where(o => o.GetComponent<Rigidbody>())
.ToSet();
foreach (var overlap in overlaps)
{
if (overlap.gameObject != chunk.gameObject)
{
var joint = overlap.gameObject.AddComponent<FixedJoint>();
//将此铰链的一端连在自身
joint.connectedBody = rb;
joint.breakForce = jointBreakForce;
}
}
}
②给每个关节创建一个叫做< ChunkNode >的组件,用于管理关节点受击时,每个chunk自身内部的一些数据处理,初始化如下
public void Setup()
{
rb = GetComponent<Rigidbody>();
Freeze();
JointToChunk.Clear();
ChunkToJoint.Clear();
// Joint组件在Fracture类中的FractureUtils.ConnectTouchingChunks添加
foreach (var joint in GetComponents<Joint>())
{
// 给当前chunk用Fixed Joint连接周围chunk的意思
var chunk = joint.connectedBody.GetOrAddComponent<ChunkNode>();
JointToChunk[joint] = chunk;
ChunkToJoint[chunk] = joint;
}
foreach (var chunkNode in ChunkToJoint.Keys)
{
Neighbours.Add(chunkNode);
if (chunkNode.Contains(this) == false)
{
chunkNode.Neighbours.Add(this);
}
}
NeighboursArray = Neighbours.ToArray();
}
5、墙体受击后的关节(Joint)处理
每个墙体(没有被破碎的墙体的对象,其子节点是各个被碎裂的chunk块,结构详见下图)都有一个用于管理所有碎块Chunk的类< ChunkManager >,其作用是:
①初始化每个节点的 < ChunkNode > 组件
②chunk受击时对每个chunk相关Jonit组件的移除
结构图
// 启动初始化挂载ChunkNode
public void Setup(Rigidbody[] bodies)
{
nodes = new ChunkNode[bodies.Length];
for (int i = 0; i < bodies.Length; i++)
{
var node = bodies[i].GetOrAddComponent<ChunkNode>();
node.Setup();
nodes[i] = node;
}
}
// 处理受击时的joint组件
private void FixedUpdate()
{
var runSearch = false;
foreach (var brokenNodes in nodes.Where(n => n.HasBrokenLinks))
{
brokenNodes.CleanBrokenLinks();
runSearch = true;
}
if(runSearch)
SearchGraph(nodes);
}
/// <summary>
/// 遍历所有非静止块chunk
/// </summary>
/// <param name="objects"></param>
public void SearchGraph(ChunkNode[] objects)
{
// 存储的所有非静止chunk的ChunkNode
var anchors = objects.Where(o => o.IsStatic).ToList();
// 这个存储的是所有Chunk的ChunkNode
ISet<ChunkNode> search = new HashSet<ChunkNode>(objects);
var index = 0;
foreach (var o in anchors)
{
if (search.Contains(o))
{
var subVisited = new HashSet<ChunkNode>();
Traverse(o, search, subVisited);
var color = colors[index++ % colors.Length];
foreach (var sub in subVisited)
{
sub.Color = color;
}
search = search.Where(s => subVisited.Contains(s) == false).ToSet();
}
}
foreach (var sub in search)
{
sub.Unfreeze();
sub.Color = Color.black;
}
}
/// <summary>
/// 从一个点通过扩散的方式找寻周围及周围扩散出去的所有还附着的Chunk
/// </summary>
/// <param name="o"></param>
/// <param name="search"></param>
/// <param name="visited"></param>
private void Traverse(ChunkNode o, ISet<ChunkNode> search, ISet<ChunkNode> visited)
{
if (search.Contains(o) && visited.Contains(o) == false)
{
visited.Add(o);
for (var i = 0; i < o.NeighboursArray.Length; i++)
{
var neighbour = o.NeighboursArray[i];
Traverse(neighbour, search, visited);
}
}
}
}
6、涉及到的相关文章及源码
NVIDIA Blast的总体概述
NVIDIA Blast的源码地址
Joint相关Unity API
Physx的API
保持梦想的纯粹,不要参杂过多的情绪进去,这会让你更接近成功