欢迎光临散文网 会员登陆 & 注册

Unity在Game视口中绘制模型网格(GL、Shader)

2023-04-01 18:58 作者:叁拾柒渡伍  | 我要投稿

前言:

        本篇主要讲解如何在Game窗口显示模型网格——类似Scene视口Wireframe的效果。在运行与非运行状态下通过GL接口进行绘制。另一种使用Shader几何着色器绘制的方式。

        文章内容主旨在于分享一些Unity 3D的小技巧,具体的实用性还请各位看官根据实际情形自行判断咯。

        好了话不多说,发车咯!

正文:

        来看一下最终的效果。

对比图1
非运行模式下

      

  因为那个黑色的网格线在桌子模型上实在不起眼,我给修改成了红色的,修改颜色的时候有一个需要注意的地方,材质球选择的Shader需要调整,默认的Shader是不行的,只能绘制出黑色的线。

        网格线绘制的原理很简单,我们都知道直线是由两个点连接形成的,三角面是由三条首尾相连的线段组成的,而N多个三角面按照一定顺序排列,就会形式模型的网格。所以,我们只要拿到模型所有的顶点与这些顶点相连的顺序。理论上就已经可以绘制策划来了。

        说到这,我们就不得不稍微提一下模型的Mesh中有哪些东西。(已经了解的看官可以跳过灰色文段)

        参考文章“https://blog.csdn.net/renwen1579/article/details/125056491”

      从官方手册中可以知道,在Mesh中存储着三维模型的数据:

        vertices(顶点数据数组Vector3[])、  triangles(三角形顶点索引数组,int[])、 normals(法线向量数组,Vector3[])、 uv(纹理坐标数组,Vector2[])。我们需要的是vertices和triangles,有了这两个数据,我们就可以将网格线绘制出来,至于normals和uv则是确定三角面的朝向确定正面和反面用的,uv是纹理采样时用的这个地方暂时用不到。

        知道mesh中包含的内容,我们拿到了vertices顶点数据和triangles三角面数据,就可以进行绘制了。

        过程简述:使用GL根据坐标点两两一对进行连线,就可以绘制出想要的结果。

        GL绘制需要一个材质球,这个可以提前准备好也可以代码生成一个,但是这个材质球采用的Shader建议更换成Unlit下的,因为默认的“Standard”无法改变颜色,始终是黑色,暂时没搞清楚原因,有知道的看官可以留言告诉一下我。

        下面直接看代码,我会对代码做出详细的注释。

        对于GL部分代码参考自:GL参考

        如果截图看不清下方有文字。

代码截图


//目标模型

    public GameObject TargetModel;

    //材质球

    public Material mat;

    //缓存顶点

    Vector3[] vertices;

    //缓存三角面的顶点索引

    int[] triangles;

    private void Start()

    {

        /*

         * 实际情况下很多模型都是有许多subMesh组成的

         * 这个地方只考虑有一个mesh的情况,多个mesh情况类似可以复用实现

         */

        //获取所有顶点

        vertices= TargetModel.GetComponent<MeshFilter>().mesh.vertices;

        //获取三角面索引

        triangles=TargetModel.GetComponent<MeshFilter>().mesh.GetTriangles(0);

    }

    //这个函数必须挂在相机上才起作用

    //如果不确定是否挂在相机上,请采用OnRenderObject.

    //具体功能请可以自行查阅,不做过多解释

    private void OnPostRender()

    {

        //压栈 保存摄像机变换矩阵

        GL.PushMatrix();

        //设置材质通道

        mat.SetPass(0);

        //设置绘制样式,GL.LINES就是线,两两一对

        GL.Begin(GL.LINES);

        //这只线的颜色

        GL.Color(Color.red);

        //提供一个矩阵,作用是将模型顶点的自身坐标变成世界坐标

        //矩阵相乘,过程会有些复杂建议自行查阅资料

        GL.MultMatrix(TargetModel.transform.localToWorldMatrix);

        //每次循环塞入一个三角面,线段需要两两一组

        for (int i = 0; i < triangles.Length - 2; i += 3)

        {

            GL.Vertex(vertices[triangles[i]]); GL.Vertex(vertices[triangles[i + 1]]);

            GL.Vertex(vertices[triangles[i + 1]]); GL.Vertex(vertices[triangles[i + 2]]);

            GL.Vertex(vertices[triangles[i + 2]]); GL.Vertex(vertices[triangles[i]]);

        }

        GL.End();

        //出栈 恢复摄像机投影矩阵

        GL.PopMatrix();

    }

        

效果


        到此我们通过GL绘制网格线就已经完成。

        这里说一下这种方式的限制,通过这种方式绘制的线段其实是通过相机渲染到屏幕上的,并不会真实地在模型上显示,但是深度的遮挡关系是正确的。

遮挡


           使用“OnRenderObject”运行时,如果报错“Matrix stack full depth reached”可以尝试加入 GL.LoadPixelMatrix();

        添加[ExecuteAlways]可以在非运行状态下显示效果


另外一种方式,那就是使用利用Shade的几何着色器。参考“https://www.freesion.com/article/6019262209/”,稍微修改并追加了一个Pass渲染纹理。

这种方式不建议对Shader不太熟悉的看官使用,因为当前这个Shader只是为了展示效果,存在一些问题,例如不支持光照等等。当然展示效果是肯定没有问题的

第一部分

第二部分

       

Shader "Unlit/line"

{

    Properties

    {

        _MainTex ("Texture", 2D) = "white" {}

        _LineColor("Color",COLOR) = (1,1,1,1)

    }

    SubShader

    {

        //第一部分  顺序不能搞错了,否则会被覆盖的

         Pass

        {

            CGPROGRAM

            #pragma vertex vert

            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata

            {

                float4 vertex : POSITION;

                float2 uv : TEXCOORD0;

            };

            struct v2f

            {

                float2 uv : TEXCOORD0;

                float4 vertex : SV_POSITION;

            };

            sampler2D _MainTex;

            float4 _MainTex_ST;


            v2f vert (appdata v)

            {

                v2f o;

                o.vertex = UnityObjectToClipPos(v.vertex);

                o.uv = TRANSFORM_TEX(v.uv, _MainTex);

                return o;

            }

            fixed4 frag (v2f i) : SV_Target

            {

                // sample the texture

                fixed4 col = tex2D(_MainTex, i.uv);

                return col;

            }

            ENDCG

        }

        //第二部分  顺序不能搞错了,否则会被覆盖的

        Pass

        {

            Tags { "RenderType" = "Opaque" }

            CGPROGRAM

            #pragma vertex vert

            #pragma fragment frag

            //几何着色器

            #pragma geometry geo

            #include "UnityCG.cginc"

           

            struct v2g

            {

                float4  pos       : POSITION;

            };

            struct g2f

            {

                float4  pos       : POSITION;

            };

            fixed4 _LineColor;

            v2g vert(appdata_base v)

            {

                v2g o;

                o.pos = UnityObjectToClipPos(v.vertex);

                return o;

            }

            [maxvertexcount(3)]

            void geo(triangle v2g vg[3], inout LineStream<g2f> ls)

            {

                for (int i = 0; i < 3; i++)

                {

                    g2f g2f_1;

                    g2f_1.pos = vg[i].pos;

                    ls.Append(g2f_1);

                }

                ls.RestartStrip();

            }

            fixed4 frag(g2f input) : COLOR

            {

                return _LineColor;

            }

            ENDCG

        }

    }

}

 这种方式绘制的网格线是真正显示在模型上的。这里线的颜色用的默认白色,建议修改其他颜色以免和纯白色的模型区分不明显。

效果

好的本期的内容就到这个这里。

如果本文对您有一点点帮助,那俺这一番功夫就没白费

如果喜欢请点赞支持哦

Unity在Game视口中绘制模型网格(GL、Shader)的评论 (共 条)

分享到微博请遵守国家法律