多种方法如:手动指定、遍历静态物体、碰撞检测、搜索等等。php
主要是为裁剪作准备。html
var mesh = meshFilter.sharedMesh; // 相交物体的 Mesh List<Vector3> oriVertices = new List<Vector3>(); mesh.GetVertices(oriVertices); List<Vector3> oriNormals = new List<Vector3>(); mesh.GetNormals(oriNormals); List<Vector3> vertices = new List<Vector3>(); List<Vector3> normals = new List<Vector3>(); int vertexCount = mesh.vertexCount; for (int i = 0; i < vertexCount; i++) { // meshFilter.transform.TransformPoint 从模型坐标转换到世界坐标 // 投影框的transform,transform.InverseTransformPoint,从世界坐标转换到投影框的模型坐标 positions.Add(transform.InverseTransformPoint(meshFilter.transform.TransformPoint(oriVertices[i]))); normals.Add(transform.InverseTransformDirection(meshFilter.transform.TransformDirection(oriNormals[i]))); }
须要剔除投影框外的三角网格,保留投影框内的三角网格,裁剪部分投影框内三角网格,背部剔除(可选)。less
for(遍历三角网格) { 1. 背部剔除(可选),判断三角网格的法线朝向,背部则continue 2. 投影框内的顶点 3. 若是投影框内的顶点=3,则continue 4. 获取全部在三角网格内与投影框的边(8个角所组成的包围边)的交点 5. 获取三角网格的边与投影框面的交点(即裁剪) // 我一开始的作法是将全部遍历所得的顶点保存,并使用三角剖分组成新的Mesh,大部分状况下没问题,可是对于复杂的模型(好比中空平面)会出错,中空的地方会被补上。 // 因此应该在原有三角网格的基础下衍生新的多个三角网格 //TODO 验证 6. 对当前循环内的所得顶点三角剖分,保存全部新的三角网格 }
三角网格的三个顶点xyz坐标是否都在投影框内,通常使用单位立方体做为投影框,即xyz范围[-0.5,0.5]ide
在投影框的模型坐标系下,投影框其实是一个轴对称包围盒。spa
将三维的三角网格分别投影到xz,xy,yz轴平面上(eg:投影到xz轴平面上,直接将三角网格三个顶点的y值置为零便可)。获得的二维平面可能结果:.net
ABCD是投影框的投影,三角形是三角网格的投影,也多是一条直线,并不影响计算结果。code
判断ABCD是否在三角形内(重心法),若是是的话,则将对应点返回到三维空间内。orm
// eg: xz投影,求y // (corner - triangle.A)*Normal = 0 float y = ( Vector3.Dot(triangle.Normal, triangle.A) - triangle.Normal.x * corner.x - triangle.Normal.z * corner.y ) / triangle.Normal.y;
而后判断点是否在投影框内,是则表示这个点是三角网格内与投影框的边的交点。htm
逐边裁剪(Sutherland Hodgman)。blog
for(遍历投影框6个面) { if(可遍历三角网格的边=0) break; for(遍历三角网格的边) { if(边位于外侧即不可见一侧) 直接剔除这条边; // 交点的某个坐标固定,经过这个值对开始、结束坐标插值,便可获取交点坐标 if(开始位于内侧,结束位于外侧 或 结束位于内侧,开始位于外侧) 获取交点,判断交点是否在投影框内,是则保存。 // 都位于内侧的状况,须要保存的结束点在上面的流程2已经保存。 } }
新Mesh的uv取自顶点的xz坐标或者xy坐标(贴花的投射方向),或者根据Normal来动态改变,切线也须要从新计算。
三者原理基本相同。Deferred Decal在延迟着色中使用。Screen Space Decal能够在向前渲染路径中使用。Volume decals一样在延迟着色中使用,做为无缝(seamless )贴花,投影到复杂几何上没有拉伸现象,在其余贴花方案经过法线动态修改贴图采样坐标也能够实现相似功能。
// 渲染顺序在贴花处物体渲染后面 // 关闭阴影投射 Tags { "Queue" = "Transparent+1" "ForceNoShadowCasting"="True" } // 贴花半透明时须要指定混合方式 Blend SrcAlpha OneMinusSrcAlpha
橙色为投影处
// 经过深度重构世界坐标(有两种方法,后处理、延迟光照中常常使用) vert { o.viewRay = UnityObjectToViewPos(v.vertex)*float3(-1,-1,1); o.screenUV = ComputeScreenPos(o.vertex); } frag { float3 ray = i.viewRay*(_ProjectionParams.z/i.viewRay.z); float2 suv = i.screenUV.xy / i.screenUV.w; // 经过screen uv做为采样坐标获取深度(即上图中投影框后物体坐标的深度) float depth = Linear01Depth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, suv)); float4 vpos=fixed4(ray*depth,1); float4 wpos=mul(unity_CameraToWorld,vpos); }
// 投影框通常为单位正方体,只要判断转换后的坐标xyz是否小于边长的一半。 float3 opos=mul(unity_WorldToObject,wpos).xyz; clip(0.5-abs(opos)); // 单位正方体边长为1
法线主要用于光照计算、解决非平面贴花的拉伸现象。向前渲染从_CameraDepthNormalsTexture取的深度、法线精度不够基本上无法使用,解决方案能够是单独渲一张精度足够的深度法线贴图,或者使用坐标偏导从新构建法线(面法线 face normal)。
// 使用坐标偏导构建法线 // ddx(wpos) 至关于三角网格的切线 float3 normal = normalize(cross(ddy(wpos), ddx(wpos)));
使用偏导数计算的切线结果,三角网格比较明显。向前渲染路径在没有顶点法线的状况下目前不知道如何平滑面法线。
纹理法线须要切线空间转换矩阵。
struct v2f { half3 oriSpace[3]:TEXCOORD5; }; vert { // 在顶点做色阶段根据投射方向,指定xyz轴 o.oriSpace[0] = mul((half3x3)unity_ObjectToWorld, half3(1, 0, 0)); o.oriSpace[1] = mul((half3x3)unity_ObjectToWorld, half3(0, 1, 0)); o.oriSpace[2] = mul((half3x3)unity_ObjectToWorld, half3(0, 0, 1)); } frag { float2 decalUV = opos.xz + 0.5; fixed3 normal = UnpackNormal(tex2D(_NormalMap, decalUV)); half3x3 norMat = half3x3(i.oriSpace[0], i.oriSpace[2], i.oriSpace[1]); normal = mul(normal, norMat); }
根据投射方向,使用模型坐标采样纹理(通常是按Y轴从上往下投射,全部使用模型坐标的xz份量)。角度变化明显(法线变化大),根据实际状况舍弃或者使用模型坐标其余份量采样纹理。