Unity Shader 之 Geometry Shader 简单实现物体线框呈现的效果
目录
Unity Shader 之 Geometry Shader 简单实现物体线框呈现的效果
一、简单介绍
Shader Language的发展方向是设计出在便携性方面可以和C++、Java等相比的高级语言,“赋予程序员灵活而方便的编程方式”,并“尽可能的控制渲染过程”同时“利用图形硬件的并行性,提高算法效率”。
几何着色器在渲染管线中的位置是在顶点着色器和片段着色器之间,准确的说是和顶点着色器相邻。所以在功能方面,几何体着色器的操作和顶点着色器有相似性。一些原来位于顶点着色器中的计算,理论上完全可以转移到几何体着色器中进行。但实际操作中,并不建议那么做。因为几何体着色器并行调用硬件困难,并行程度低,效率和顶点着色器有很大的差距。
-
顶点着色器是逐顶点操作,可以进行坐标变换等计算。
-
片段着色器是逐片段/像素操作,进行最终输出颜色的计算。
-
几何着色器同理,是逐图元的操作。它的输入是图元,输出也是图元。
图元是渲染对象在顶点着色器之后,光栅化之前的一种状态。简单的来说,就是包含点【点Point而不是顶点Vertex】或者线段或者三角形的集合。
顶点着色器:
将顶点变换到世界空间,同时传递顶点法线和填充纹理坐标。
几何着色器:
1.定义一个垂直向上的向量up。
2.计算观察向量look。
3.将look向量的Y轴设为0并规格化,使得look向量成为平行于XZ平面指向摄像机的单位向量。
4.计算up向量和look向量的叉乘,得到一个垂直于二者的新向量right。
5.计算Size的一半halfS。
6.将输入的点,以该点为中心点,分别向right向量正负轴向,up向量正负轴向移动halfS的距离,四个顶点的四个顶点的坐标。
7.将顶点依次转换到投影空间,分配UV并Append到输出流上,最后输出。
片段着色器:
根据UV对贴图进行采样,返回颜色。
几何着色器在启用后,它将获得顶点着色器以组成一个基础图元为一组的顶点输入,通过对输入的顶点进行处理,几何着色器将决定输出的图元类型和个数。当输出的图元减少或者不输出时,实际上起到了裁剪图形的作用,当输出的图元类型改变或者输出更多图元时起到了产生和改变图元的作用。
要启用几何着色器,我们需要在之前的顶点和片元着色器基础上,在 Unity shader 脚本上添加 #pragma geometry geom_名称,即可使用几何着色器了。
Unity官方文档关于Geometry Shader的内容较少。不过也是因为Unity的开发者大多数面向的是移动平台开发,所以Geometry Shader作为DirectX 10的特性并没有被开发者广泛使用。
不过,骁龙835处理器的Areno 540开始就支持OpenGL ES3.2了,而这个版本的一个新特性就是支持Geometry Shader!!!所以随着硬件的发展,在移动端以及非旗舰性能的PC端大量使用GS应该也不是一个久远的事情了。
二、关键参数
1、设定着色器编译目标级别为5.0。不过根据Unity的文档,4.0的着色器编译目标级别就已经支持Geometry Shader了
#pragma target 5.0
2、设定几何体着色器函数名称为GS_Main【可自定义】
#pragma geometry GS_Main
3、设置顶点着色器向几何体着色器输出的最大顶点数量为4
[maxvertexcount(4)]
4、几何体着色器输入的图元是点,数量为1。输出的图元是三角形流
void
GS_Main(point GS_INPUT p[1], inout TriangleStream<FS_INPUT> triStream)
5、Append()是几何着色器内置的向输出流附加顶点的函数,因为允许的最大输出顶点数量就是4,所以append四次,输出四个顶点。
triStream.Append(pIn);
三、效果预览
四、实现步骤
1、打开Unity,新建一个工程
2、编写shader,并且对应的构建 Material
3、在场景中添加一个 Capsual ,赋值上材质
4、适当调整 shader 参数
5、效果如上
五、关键代码
Shader "Unlit/GS_Wireframe"
{
Properties
{
_WireColor("WireColor", Color) = (1, 0, 0, 1)
_FillColor("FillColor", Color) = (1, 1, 1, 1)
_WireWidth("WireWidth", Range(0, 0.005)) = 1
}
SubShader
{
Tags { "RenderType" = "Transparent" "Queue" = "Transparent" }
LOD 100
Pass
{
Blend SrcAlpha OneMinusSrcAlpha
Cull Off
CGPROGRAM
#pragma vertex vert
#pragma geometry geom
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex: POSITION;
float2 uv: TEXCOORD0;
};
struct v2g
{
float3 uv: TEXCOORD0;
float4 vertex: SV_POSITION;
};
struct g2f
{
float3 uv: TEXCOORD0;
float4 vertex: SV_POSITION;
float3 dist: TEXCOORD1;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float4 _FillColor, _WireColor;
float _WireWidth, _Clip, _Lerp, _WireLerpWidth;
v2g vert(appdata v)
{
v2g o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);
//o.uv.z = _Clip + v.vertex.y;
o.uv.z = v.vertex.y;
return o;
}
[maxvertexcount(3)]
void geom(triangle v2g IN[3], inout TriangleStream < g2f > triStream)
{
float2 p0 = IN[0].vertex.xy / IN[0].vertex.w;
float2 p1 = IN[1].vertex.xy / IN[1].vertex.w;
float2 p2 = IN[2].vertex.xy / IN[2].vertex.w;
float2 v0 = p2 - p1;
float2 v1 = p2 - p0;
float2 v2 = p1 - p0;
//triangles area
float area = abs(v1.x * v2.y - v1.y * v2.x);
// //到三条边的最短距离
g2f OUT;
OUT.vertex = IN[0].vertex;
OUT.uv = IN[0].uv;
OUT.dist = float3(area / length(v0), 0, 0);
triStream.Append(OUT);
OUT.vertex = IN[1].vertex;
OUT.uv = IN[1].uv;
OUT.dist = float3(0, area / length(v1), 0);
triStream.Append(OUT);
OUT.vertex = IN[2].vertex;
OUT.uv = IN[2].uv;
OUT.dist = float3(0, 0, area / length(v2));
triStream.Append(OUT);
}
fixed4 frag(g2f i) : SV_Target
{
fixed4 col_Wire;
float d = min(i.dist.x, min(i.dist.y, i.dist.z));
col_Wire = d < _WireWidth ? _WireColor : _FillColor;
return col_Wire;
}
ENDCG
}
}
}
六、参考博客
1、https://www.cnblogs.com/Esfog/p/UnityGeometryShader_WireFrame.html
2、https://blog.csdn.net/u012722551/article/details/104281175
3、Unity Shader:用几何着色器实现复联3灭霸的终极大招灰飞烟灭
4、Unity Shader之几何着色器(Geometry Shader)实现面片飞散的爆炸效果