HoloLens MRTK模型切割算法解析
脚本解析
例程中主要用到的脚本有位于Assets/MRTK/Core/Utilities/StandardShader路径下的ClippingPlane.cs
、ClippingBox.cs、ClippingSphere.cs、ClippingPrimitive.cs(继承关系),位于Assets/MRTK/Core/StandardAssets/Shaders路径下的MixedRealityStandard.shader
着色器,以及位于Assets/MRTK/Core/StandardAssets/Shaders路径下的MixedRealityShaderUtils.cginc
。它们三个的关系如下:
清晰框图及注释见:https://download.youkuaiyun.com/download/qq_41452267/12986383
算法实现
面切割算法:对于心脏模型的每一个片元,在世界坐标系下,使用平面的法线向量n与平面中心点到待渲染点的方向向量d做点乘。根据计算结果的正负号将模型划分为平面的内侧和外侧两部分。
球切割算法:对于心脏模型的每一个片元,计算其到球心的距离,如果小于半径,则位于内侧,反之,位于外侧。
立方体切割算法:对于心脏模型的每一个片元,将片元坐标变换到立方体的模型坐标系下得到坐标position,用position的每个坐标分量取绝对值再减去立方形大小。最终得到的坐标中最大值大于零的,说明在立方体外,最大值小于零的说明在立方体内。
修改shader实现不同的功能
修改Clipping Plane裁剪的范围
在原工程中,Clipping Plane会将整个空间一分为二,空间的一侧被切割,另一侧不被切割。
需求:希望Clipping Plane根据自身范围进行切割,只有接触到的部分被切割,未接触到的部分不予切割,效果如下图:
实现方法
①在场景中修改变量至合适大小,默认大小为1米x1米。这里设置切片为0.5米
①在ClippingPlane.cs
文件中声明变量,并获取切片信息
③在着色器MixedRealityStandard.shader
中调用变量和函数,函数定义在MixedRealityShaderUtils.cginc
文件中:
MixedRealityStandard.shader
如下:
primitiveDistance = min(primitiveDistance, PointVsPlanePart(i.worldPosition.xyz, _ClipPlane, _ClipPlanePosition, _ClipPlaneUp, _ClipPlaneRight, _ClipPlaneForward) * _ClipPlaneSide);
MixedRealityShaderUtils.cginc
定义函数如下:
//部分截取,返回值为负时显示,为正时不显示
inline float PointVsPlanePart(float3 worldPosition, float4 plane,float3 planeRawPosition,float3 planeUp, float3 planeRight, float3 planeForward)
{
float3 planePosition = plane.xyz * plane.w;
float3 distance = worldPosition - planeRawPosition;//计算面片与平面中心坐标差
float x = dot(distance, planeRight);//计算面片在平面坐标系下的x y值
float z = dot(distance, planeForward);
float y = dot(distance, planeUp);
if (y<0 || z<-0.25 || z>0.25 || x<-0.25 || x>0.25)
return -0.2;//超过面片范围显示显示
else
return 0.2;//不显示
//return dot(worldPosition - planePosition, plane.xyz);//plane.xyz是平面法向量
}
修改模型透明度
需求:原始可切割模型默认为不透明渲染模式,希望调整模型为半透明可切割,且透明度可调整。
①创建材质球,选择着色器为mixedrealitytoolkit/Standard(这里最好自己创一个新的shader,否则很多场景都将无法正常显示),选择Rendering Mode为Transparent
②在MixedRealityStandard.shader
中(或者是自己创建的shader),修改最终的output如下:
// Perform non-alpha clipped primitive clipping on the final output.
#if defined(_CLIPPING_PRIMITIVE) && !defined(_ALPHA_CLIP)
//output *= saturate(primitiveDistance * (1.0f / _BlendedClippingWidth));//saturate函数将返回值限制在0-1之间
if (primitiveDistance > 0) {
output.a = 0.3;
}
else {
output *= 0;
}
#endif
return output;
}
ENDCG
}
output.a即是调整透明度的地方。
多个ClippingPrimitive对同一个模型进行切割
By default only one ClippingPrimitive
can clip a renderer at a time. If your project requires more than one ClippingPrimitive
to influence a renderer the sample code below demonstrates how to achieve this.
Two different ClippingPrimitives clip a render
How to have two different ClippingPrimitives clip a render. For example a ClippingSphere and ClippingBox at the same time:
// Within MRTK/Core/StandardAssets/Shaders/MixedRealityStandard.shader (or another MRTK shader) change:
#pragma multi_compile _ _CLIPPING_PLANE _CLIPPING_SPHERE _CLIPPING_BOX
// to:
#pragma multi_compile _ _CLIPPING_PLANE
#pragma multi_compile _ _CLIPPING_SPHERE
#pragma multi_compile _ _CLIPPING_BOX
Two of the same ClippingPrimitives clip a render
How to have two of the same ClippingPrimitives clip a render. For example two ClippingBoxes at the same time:
// 1) Add the below MonoBehaviour to your project:
[ExecuteInEditMode]
public class SecondClippingBox : ClippingBox
{
/// <inheritdoc />
protected override string Keyword
{
get { return "_CLIPPING_BOX2"; }
}
/// <inheritdoc />
protected override string ClippingSideProperty
{
get { return "_ClipBoxSide2"; }
}
/// <inheritdoc />
protected override void Initialize()
{
base.Initialize();
clipBoxSizeID = Shader.PropertyToID("_ClipBoxSize2");
clipBoxInverseTransformID = Shader.PropertyToID("_ClipBoxInverseTransform2");
}
}
// 2) Within MRTK/Core/StandardAssets/Shaders/MixedRealityStandard.shader (or another MRTK shader) add the following multi_compile pragma:
#pragma multi_compile _ _CLIPPING_BOX2
// 3) In the same shader change:
#if defined(_CLIPPING_PLANE) || defined(_CLIPPING_SPHERE) || defined(_CLIPPING_BOX)
// to:
#if defined(_CLIPPING_PLANE) || defined(_CLIPPING_SPHERE) || defined(_CLIPPING_BOX) || defined(_CLIPPING_BOX2)
// 4) In the same shader add the following shader variables:
#if defined(_CLIPPING_BOX2)
fixed _ClipBoxSide2;
float4 _ClipBoxSize2;
float4x4 _ClipBoxInverseTransform2;
#endif
// 5) In the same shader change:
#if defined(_CLIPPING_BOX)
primitiveDistance = min(primitiveDistance, PointVsBox(i.worldPosition.xyz, _ClipBoxSize.xyz, _ClipBoxInverseTransform) * _ClipBoxSide);
#endif
// to:
#if defined(_CLIPPING_BOX)
primitiveDistance = min(primitiveDistance, PointVsBox(i.worldPosition.xyz, _ClipBoxSize.xyz, _ClipBoxInverseTransform) * _ClipBoxSide);
#endif
#if defined(_CLIPPING_BOX2)
primitiveDistance = min(primitiveDistance, PointVsBox(i.worldPosition.xyz, _ClipBoxSize2.xyz, _ClipBoxInverseTransform2) * _ClipBoxSide2);
#endif
Finally, add a ClippingBox and SecondClippingBox component to your scene and specify the same renderer for both boxes. The renderer should now be clipped by both boxes simultaneously.
详情见:https://microsoft.github.io/MixedRealityToolkit-Unity/Documentation/Rendering/ClippingPrimitive.html