Skip to main content
ARShow
ARShow
 首页 » 资源教程 » Unity3D教程

Unity中Mesh分解与边缘高亮加上深度检测

2016年09月04日 17:01:4624770

一个比较简单的需求,不过遇到些坑,记录下。

  房间有多个模型,每个模型可能多个SubMesh,点击后,需要能具体到是那个SubMesh,并且在这个SubMesh上显示边缘高光,以及能个性这单个SubMesh对应的Material。如一个桌子的Mesh,其实有二个材质,分别对应二个SubMesh,一个桌面和一个桌脚,点击桌面后,只有这个桌面高光,而不是整个桌子,并且能单独更换这个桌面的Material.

  我们知道Unity中,Mesh和Ogre一样,也是可以有多个SubMesh,每个SubMesh有自己的Material,但是不同Ogre每个Submesh可以有不同的顶点数据,Unity中Mesh所有SubMesh共享相同顶点数据,分别使用不同的顶点索引。我原来做过一个项目,用Ogre里的Renderable与MovableObject组合形成这种格式,里面的所有模型都是用的这种格式显示,而不是Ogre本身的Entiy,当时就发现这种更容易理解,好用。

  下面这个脚本文件是这个功能的具体实现,包含分解Mesh,检查具体是那个SubMesh碰撞等功能。

Unity中Mesh分解与边缘高亮加上深度检测 Unity3D教程 第1张
Unity中Mesh分解与边缘高亮加上深度检测 Unity3D教程 第2张
 
      cIndex = - Material lineMat =  Material selectMat = 
     MeshFilter meshFilter =  MeshRenderer meshRender =  MeshCollider meshCollider =  BoxCollider boxCollider =   Transform transform =  LineRenderer lineRender =  HighlightableObject hightLight =   vminDist =  List<> indexLay =  List<>  bPreObject =   preIndex =  Mesh mesh = 
      defaultAdd =  RaycastHit preHit =   bHold =  Vector3 oldLocation = Dictionary<, List<>> matTextures =  Dictionary<, List<>>= Resources.Load<Material>(= checkDefault<MeshFilter>= checkDefault<MeshCollider>= checkDefault<MeshRenderer>= checkDefault<BoxCollider>= checkDefault<Transform>= checkDefault<LineRenderer>= checkDefault<HighlightableObject>= =, 
        .gameObject.layer = = , matTextures, XmlReader.ParseMatXml,  <T>() = .gameObject.GetComponent<T> (t == = .gameObject.AddComponent<T>
     UNITY_EDITOR         (Input.GetMouseButtonDown() && ! UNITY_ANDROID || UNITY_IPHONE         (Input.touchCount >  && Input.GetTouch().phase == TouchPhase.Began  && !EventSystem.current.IsPointerOverGameObject(Input.GetTouch(= bAxis = Physics.Raycast(ray,  hit, ,  << == =
             (GetMinDist(ray,  (preCollider != = 
                bPreObject = hit.collider == (!bPreObject && preCollider != =
                
                     (selectMat !=  haveTexture =
                         (defaultAdd || (defaultAdd || UNITY_EDITOR         (Input.GetMouseButtonUp( UNITY_ANDROID || UNITY_IPHONE         (Input.touchCount >  && Input.GetTouch().phase ===    UNITY_EDITOR         (bHold && Input.GetMouseButton( UNITY_ANDROID || UNITY_IPHONE         (bHold && Input.touchCount >  && Input.GetTouch().phase === newPot = ray.origin + ray.direction * preHit.distance -=  GetMinDist(Ray ray, =  hits = origin = minDist =  result =  ( hit  (hit.collider == meshCollider || hit.collider == sqrLenght = (hit.point - (sqrLenght <===   render = collider.GetComponent<Renderer> filter = collider.GetComponent<MeshFilter> (render !=  && filter != 
            transform.position ====
             minDist = = = = = ====
             (filter.mesh.subMeshCount >  ( meshIndex = ; meshIndex < filter.mesh.subMeshCount; meshIndex++= =  indexs = (indexs.Length /  > = ==== == (currentCollider.Raycast(ray,  hit,  sqrLenght = (Camera.main.transform.position -
                         (Mathf.Abs(sqrLenght - minDist) < (! (! (sqrLenght <=== mesh.bounds.center - mesh.bounds.size /= mesh.bounds.center + mesh.bounds.size /
             (indexLay.Count >  && nIndex == ++nIndex %= (cIndex >=  && render.materials.Length >=
                 indexs ==
                meshRender.material = vertexs =
                meshRender.enabled =    =    cornerDirty =  Vector3 min = Vector3 max = Vector3[] mCorners =  Vector3[== ==   (cmp.x <= (cmp.y <= (cmp.z <=  (cmp.x >= (cmp.y >= (cmp.z >= =  ===     
         ] =].x = min.x; mCorners[].y = max.y; mCorners[].z =].x = max.x; mCorners[].y = max.y; mCorners[].z =].x = max.x; mCorners[].y = min.y; mCorners[].z =] =].x = min.x; mCorners[].y = max.y; mCorners[].z =].x = min.x; mCorners[].y = min.y; mCorners[].z =].x = max.x; mCorners[].y = min.y; mCorners[].z = i = =  Vector3[
        pos[i++] = .Corners[++] = .Corners[++] = .Corners[
        pos[i++] = .Corners[++] = .Corners[++] = .Corners[
        pos[i++] = .Corners[++] = .Corners[++] = .Corners[
        pos[i++] = .Corners[++] = .Corners[++] = .Corners[
        pos[i++] = .Corners[++] = .Corners[++] = .Corners[++] = .Corners[= = = =
Unity中Mesh分解与边缘高亮加上深度检测 Unity3D教程 第3张

  需要注意的点是:

  1 如果几个模型有多个SubMesh分散在各个位置,故需要把所有RaycastHit上碰撞点与眼睛求出最近点。

  2 LineRender中是N点组成N-1条线,而不是N/2,如A-B-C-D,并不是显示AB,CD.而是AB,BC,CD.

  3 模型的SubMesh可能边框重合,这样的话,就会导致可能永远都是选的其中一个。

  4 我们根据SubMesh生成新的Mesh,并不需要在主摄像头中渲染(通过Layer与cullingMask组合),不然和原来模型的SubMesh显示不清。

  5 鼠标按下,是否在UI上面,鼠标弹起,电脑与移动平台要不同的处理。

  6 安卓平台下,用WWW加载资源,必需用yield return,故相应加载完成的处理可以用函数指针传入。

  到这模型就差不多了,然后添加边缘高亮组件highightingSystem,这个的思路也是比较简单的。

  首先在主摄像机渲染场景前,把边缘高亮的模型给一个单独的层,并且修改相应材质为我们需要高亮的颜色,然后复制主摄像头新生成一个摄像头,新摄像头的cullingMask只渲染前面边缘高亮模型的层的那些模型到一张Stencil的RTT中保存,然后把原来的边缘高亮的模型的层和材质换回来。

  然后是主摄像头正常渲染,渲染完后,在OnRenderImage中先把在上面的那张RTT进行简单的Blur模糊,保存为Blur的RTT。最后把上面的Stencil的RTT,Blur的RTT,主摄像头渲染的source,我们并不渲染stencil本身,只渲染stencil模糊后的边缘部分。

  嗯,现在有个麻烦,老大要在看不到的部分不显示高亮,如下这样:

  Unity中Mesh分解与边缘高亮加上深度检测 Unity3D教程 第4张

   第一张图是现在的显示效果,老大要的是第二张,说实话,我最开始以为很简单,好吧,做完后就加了点东东,确实不复杂,但是因为对Unity的相关理解有误,把采过的坑说下。

  说实话,这个需求就是加个深度检测就行了,那么在原来基础上添加如下一些代码。

Unity中Mesh分解与边缘高亮加上深度检测 Unity3D教程 第5张
Unity中Mesh分解与边缘高亮加上深度检测 Unity3D教程 第6张
= refCam.projectionMatrix;        shaderCamera.cullingMask == == = = ==shaderCamera.depthTextureMode =depthBuffer = RenderTexture.GetTemporary(()GetComponent<Camera>().pixelWidth, ()GetComponent<Camera>().pixelHeight, =);
Unity中Mesh分解与边缘高亮加上深度检测 Unity3D教程 第7张

  Shader.

Unity中Mesh分解与边缘高亮加上深度检测 Unity3D教程 第8张
Unity中Mesh分解与边缘高亮加上深度检测 Unity3D教程 第9张
Shader  =  vertex vert             fragment frag
            
            == v.texcoord.xy; 
                o.depth =
                 float4(i.depth,,,
Unity中Mesh分解与边缘高亮加上深度检测 Unity3D教程 第10张

  在这我进行一次尝试,结果不对,在shaderCamera.Render()渲染之前,设定depthTextureMode为Depth,我在Shader开始应用_CameraDepthTexture,发现结果不对,网上查找说是这个RTT一直是主摄像头的,后面使用_LastCameraDepthTexture,结果很奇怪,和后面主摄像头的_CameraDepthTexture比对结果完全对不上,深度值不是0或1,但是渲染出来看,深度值又没看到变化,后来仔细想了下,应该是主摄像头Graphics.Blit后的值,因为这个只是渲染一个正方形,深度显示出来就会这样。

  最后去Unity5Shader里面找_CameraDepthTexture这个RTT是如何渲染的,我们找到这个值COMPUTE_DEPTH_01是放入深度RTT中的,具体意思大家去unityCG.cginc里去找就行了,因为这个值就是根据当前顶点的位置算出来的,所以在这我们放入顶点着色器就行。

  然后就是在第一张Blur模糊图上比较上一张深度RTT的深度值,相应DEPTH_COMP_ON位置为新增加的。

Unity中Mesh分解与边缘高亮加上深度检测 Unity3D教程 第11张
Unity中Mesh分解与边缘高亮加上深度检测 Unity3D教程 第12张
    
      off =
Unity中Mesh分解与边缘高亮加上深度检测 Unity3D教程 第13张
Unity中Mesh分解与边缘高亮加上深度检测 Unity3D教程 第14张
Unity中Mesh分解与边缘高亮加上深度检测 Unity3D教程 第15张
Shader , 2D) = , Range(,)) = , 2D) =  vertex vert             fragment frag             fragmentoption ARB_precision_hint_fastest             multi_compile __ DEPTH_COMP_ON 
         defined(DEPTH_COMP_ON)
            == _MainTex_TexelSize.xy *].x = v.texcoord.x -].y = v.texcoord.y -].x = v.texcoord.x +].y = v.texcoord.y -].x = v.texcoord.x +].y = v.texcoord.y +].x = v.texcoord.x -].y = v.texcoord.y += (_MainTex_TexelSize.y < =  -= tex2D(_MainTex, i.uv[= tex2D(_MainTex, i.uv[= tex2D(_MainTex, i.uv[= tex2D(_MainTex, i.uv[==== (color1.a + color2.a + color3.a + color4.a) * defined(DEPTH_COMP_ON)                 cDepth = oDepth =
                 (abs(oDepth - cDepth) > = fixed4(,,,
Unity中Mesh分解与边缘高亮加上深度检测 Unity3D教程 第16张

  注意:

  1 我们只需要比较第一张模糊图的深度,后面的模糊都是根据这张再重新模糊,因此我们在着色器定义编译符,使之第一次与后面几次根据编译符不同的执行。

  2 在深度比较的Shader中,我们其实已经取不到原顶点pos相应的值了,因为我们并不是渲染原来的模型,而是相当于Ogre中的后处理PassQuad(只渲染一个正方形),因此,在这之前,需要将主摄像根据情况,先把设定主摄像头的depthTextureMode为Depth,这样在OnPreRender之后,主摄像头正常渲染前,先调用UpdateDepthTexture,渲染场景内所有模型的深度到_CameraDepthTexture上,这样在后面的OnRenderImage中,我们才能取到正常的深度值。

  3 在这,二张深度图里默认的精度都只有16位,因此需要定义一个范围。

  有几次试错,都在于没搞清Unity里的执行过程,后来结合Untiy提供的Frame Debugger,才搞定这个简单的修改。

评论列表暂无评论
发表评论