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

前言:
本篇主要讲解如何在Game窗口显示模型网格——类似Scene视口Wireframe的效果。在运行与非运行状态下通过GL接口进行绘制。另一种使用Shader几何着色器绘制的方式。
文章内容主旨在于分享一些Unity 3D的小技巧,具体的实用性还请各位看官根据实际情形自行判断咯。
好了话不多说,发车咯!
正文:
来看一下最终的效果。



因为那个黑色的网格线在桌子模型上实在不起眼,我给修改成了红色的,修改颜色的时候有一个需要注意的地方,材质球选择的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
}
}
}
这种方式绘制的网格线是真正显示在模型上的。这里线的颜色用的默认白色,建议修改其他颜色以免和纯白色的模型区分不明显。

好的本期的内容就到这个这里。
如果本文对您有一点点帮助,那俺这一番功夫就没白费
如果喜欢请点赞支持哦