这篇文章主要针对SpatialMap_Dense_BallGame场景讲一下DenseSpatialMapBuilderFrameFilter 和 DenseSpatialMapDepthRenderer 这两个脚本。
一、场景分析
EasyAR4.0的所有功能都模块化了,SpatialMap_Dense_BallGame这个场景中
绿色框的物体上面挂载的脚本就是实现稠密空间的脚本了。其他三个物体是属于基础脚本。
Sample物体上的脚本是写了UI的控制,和小球的生成,这个代码很简单,所以就不说了。
二、DenseSpatialMapDepthRenderer脚本
这个脚本比较简单,主要功能就是记录相机的视口坐标系中的深度信息,然后转换成一张texture,把这种图片传递到场景中自动生成的环境Mesh的材质中。
相机是用的是session.Assembly.Camera也就是Main Camera,
if (!RenderDepthCamera || !MapMeshMaterial)
{
return;
}
if (depthTexture && (depthTexture.width != Screen.width || depthTexture.height != Screen.height))
{
Destroy(depthTexture);
}
if (!depthTexture)
{
depthTexture = new RenderTexture(Screen.width, Screen.height, 24);
MapMeshMaterial.SetTexture("_DepthTexture", depthTexture);
}
RenderDepthCamera.targetTexture = depthTexture;
RenderDepthCamera.RenderWithShader(Shader, "Tag");
RenderDepthCamera.targetTexture = null;
代码主要就是通过一个特殊shader 渲染相机,生成一个可以说是深度图的图片,然后传递给材质。
v2f vert(appdata_base v)
{
v2f o;
o.depthPos.xyz = UnityObjectToViewPos(v.vertex);
o.depthPos.w = 1.0;
o.pos = mul(UNITY_MATRIX_P, o.depthPos);
return o;
}
float4 frag(v2f i) : SV_Target {
float depth = length(i.depthPos.xyz);
depth = depth * 0.000001;
return EncodeFloatRGBA(depth);
}
这是shader主要部分,这里使用的是length当作深度数据,因为一个画面中xy相同的情况下,length的大小也代表了深度。
然后把这个数据*0.000001,因为EncodeFloatRGBA范围0-1.。
if (length(i.viewPos.xyz) * 0.000001 - depth >= 0.005 * 0.000001)
{
discard;
}
这是环境Mesh的Shader,会剔除深度远的mesh。这么做可能是因为本身环境Mesh是透明的,如果不剔除,可能会有很多层渲染出来。
三、DenseSpatialMapBuilderFrameFilter脚本
讲这个脚本,先讲几个其他类。
DenseSpatialMap 这个类用来对环境进行精确的三维稠密重建,就是这个类其实是主要的计算控制类
DenseSpatialMapBlockController 这个类是管理每一个生成的Mesh的,用做对相应Mesh的更新
BlockInfo 这是一个结构体,主要保存了一个Mesh的更新记录 其中x,y,z组成了类似id ,version是记录更新的次数
首先看类中的Awake函数:我这里删除了一些不重要的代码
protected virtual void Awake()
{
if (!EasyARController.Initialized) //是否初始化
{
return;
}
if (!DenseSpatialMap.isAvailable()) //是否支持稠密重建
{
throw new UIPopupException(typeof(DenseSpatialMap) + " not available");
}
Builder = DenseSpatialMap.create(); //创建 DenseSpatialMap 对象
}
然后稠密空间计算的控制可以有下面几个函数:
Builder.start(); //开始重建或从暂停中恢复,继续重建
Builder.stop(); //暂停重建过程。调用start来继续重建过程。
Builder.Dispose(); //这个api中没解释,应该是释放资源,但是不关闭重建
Builder.close(); //关闭重建过程。close之后不应继续使用。
public InputFrameSink InputFrameSink()
{
if (Builder != null)
{
return Builder.inputFrameSink();
}
return null;
}
这个函数主要是实现 FrameFilter.IInputFrameSink这个接口的,这个接口应该是在获取到数据后,会回调。因此这里在数据更新后,需要更新Builder。
protected virtual void Update()
{
if (dirtyBlocks.Count <= 0) //dirtyBlocks是一个保存需要更新的mesh的集合
{
if (Builder.updateSceneMesh(false)) //这里会让重新计算mesh
{
using (var mesh = Builder.getMesh()) //获取计算后的BlockInfo集合
{
foreach (var blockInfo in mesh.getBlocksInfoIncremental()) //遍历
//所有的blockInfo
{
DenseSpatialMapBlockController oldBlock;
blocksDict.TryGetValue(new Vector3(blockInfo.x, blockInfo.y, blockInfo.z), out oldBlock); //查看所有生成的环境mesh中有没有这个id
if (oldBlock == null) //没有的话就生成Mesh
{
var go = new GameObject("MeshBlock");
go.AddComponent();
go.AddComponent();
var renderer = go.AddComponent();
renderer.material = mapMaterial;
renderer.enabled = RenderMesh;
var block = go.AddComponent();
block.UpdateData(blockInfo, mesh);
go.transform.parent = mapRoot.transform;
blocksDict.Add(new Vector3(blockInfo.x, blockInfo.y, blockInfo.z), block);
dirtyBlocks.Add(block);
if (MapCreate != null)
{ //这里是Action的回调,用户可以监听地图生成
MapCreate(block);
}
}
else if (oldBlock.Info.version != blockInfo.version)
{
oldBlock.UpdateData(blockInfo, mesh); //更新mesh的数据
if (!dirtyBlocks.Contains(oldBlock))
{
dirtyBlocks.Add(oldBlock); //将这个mesh放到需要更新的
//集合
}
}
}
}
}
}
else //这里每次刷新数据的数量是可以设置的,默认是5个
{
var count = Math.Min(dirtyBlocks.Count, BlockUpdateLimitation);
var blocks = dirtyBlocks.GetRange(0, count);
foreach (var block in blocks) //每次最多刷新5个mesh
{
block.UpdateMesh(); //更新mesh ,更新数据不代表mesh更新了
}
dirtyBlocks.RemoveRange(0, count); //移出需要更新的列表
if (MapUpdate != null)
{
MapUpdate(blocks); //这里是Action的回调,用户可以监听地图更新
}
}
}
稠密空间构建的基本脚本就是这样了。
有问题可以加入qq群: