Geomerty Shader 是 SM4.0 Directx 9.0c 中引用的新技术, 它的位置位于 Vertex Shader 和 Fragment Shader 之间, 可以对一组(多个)顶点进行编程
Geometry Shader 可以自定义输出方式
PointStream
LineStream
TriangleStream
分别是 点,线,面
这样如果想做线框模式渲染,就非常的简单
上个线框模式图
请不要无脑复制转载本文, 由 dreamfairy 原创, 转载请注明出处 本文地址 http://www.dreamfairy.cn/blog/2016/06/05/unity3d-geomerty-shader/
一个和 Unlit Shader 一样效果的 GS Shader 如下
Shader "Aoi/GSShader" { Properties { _SpriteTex("Base (RGB)", 2D) = "white" {} _Size("Size", Range(0, 3)) = 0.5 _Tess("Tess", float) = 1 } SubShader { Pass { Tags{ "RenderType" = "Opaque" } LOD 200 CGPROGRAM #pragma target 5.0 #pragma vertex VS_Main #pragma fragment FS_Main #pragma geometry GS_Main #include "UnityCG.cginc" // ************************************************************** // Data structures * // ************************************************************** struct GS_INPUT { float4 pos : POSITION; float3 normal : NORMAL; float2 tex0 : TEXCOORD0; }; struct FS_INPUT { float4 pos : POSITION; float2 tex0 : TEXCOORD0; }; // ************************************************************** // Vars * // ************************************************************** float _Size; float4x4 _VP; sampler2D _SpriteTex; float4 _SpriteTex_ST; // ************************************************************** // Shader Programs * // ************************************************************** // Vertex Shader ------------------------------------------------ GS_INPUT VS_Main(appdata_base v) { GS_INPUT output = (GS_INPUT)0; output.pos = v.vertex; output.normal = v.normal; output.tex0 = v.texcoord; return output; } // Geometry Shader ----------------------------------------------------- [maxvertexcount(4)] void GS_Main(triangle GS_INPUT p[3], inout TriangleStream<FS_INPUT> triStream) { for (int i = 0; i < 3; i++) { FS_INPUT pIn; pIn.pos = mul(UNITY_MATRIX_MVP, p[i].pos); pIn.tex0 = p[i].tex0; triStream.Append(pIn); } } // Fragment Shader ----------------------------------------------- float4 FS_Main(FS_INPUT input) : COLOR { return tex2D(_SpriteTex, input.tex0); } ENDCG } } }
现在来介绍下GS Shader开启的方法
首先要定义2个预处理指令
#pragma target 4.0 #pragma geometry GS_Main
因为 gs shader 是在 sm 4.0 下才支持的, 如果不定义 target,可能会导致 shader.supported api 没效果, 导致部分低端设备的 fallback shader无法调用
之后就是 gs shader 的主体 gs_main 的代码
[maxvertexcount(4)] //当处理的顶点数 >= (数量) 才进行渲染
void GS_Main(triangle GS_INPUT p[3], inout TriangleStream<FS_INPUT> triStream) //参数0 triangle GS_INPUT p[3] 表示输入数据类型转换成什么格式进行处理 //参数1 inout TriangleStream<FS_INPUT> triStream 输入输出流, 将处理的好的数据 append 到流中,完成处理 i.e FS_INPUT pIn; //pIn.pos = mul(UNITY_MATRIX_MVP, float4(localPos, 1.0)); pIn.pos = mul(UNITY_MATRIX_MVP, p[i].pos); pIn.tex0 = p[i].tex0; triStream.Append(pIn);
在这个 Shader 基础上,我们可以直接通过修改 inout 的输出类型,来输出不同渲染模式, 比如将
inout TriangleStream 修改为 inout PointStream
那么就可以看到 以顶点为单位, 中间没有像素插值的 皮卡丘
GS Shader 应用的场景
1.基于模型形状,以顶点为单位的 Billboard
如图,模型本身不是 Billboard, 但是构成模型的每个 Quad 却是 Billboard
只有VS 和 GS 部分的代码和 上面的 GS Shader 有区别
// Vertex Shader ------------------------------------------------ GS_INPUT VS_Main(appdata_base v) { GS_INPUT output = (GS_INPUT)0; //output.pos = v.vertex; output.pos = mul(_Object2World, v.vertex); output.normal = v.normal; output.tex0 = v.texcoord; return output; }
// Geometry Shader ----------------------------------------------------- [maxvertexcount(4)] void GS_Main(point GS_INPUT p[1], inout TriangleStream<FS_INPUT> triStream) { float3 up = float3(0, 1, 0); float3 look = _WorldSpaceCameraPos - p[0].pos; look.y = 0; look = normalize(look); float3 right = (cross(up, look)); float halfS = 0.5f * 0.1; float4 v[4]; v[0] = float4(p[0].pos + halfS * right - halfS * up, 1.0f); v[1] = float4(p[0].pos + halfS * right + halfS * up, 1.0f); v[2] = float4(p[0].pos - halfS * right - halfS * up, 1.0f); v[3] = float4(p[0].pos - halfS * right + halfS * up, 1.0f); float4x4 vp = mul(UNITY_MATRIX_MVP, _World2Object); FS_INPUT pIn; pIn.pos = mul(vp, v[0]); pIn.tex0 = p[0].tex0; triStream.Append(pIn); pIn.pos = mul(vp, v[1]); pIn.tex0 = p[0].tex0; triStream.Append(pIn); pIn.pos = mul(vp, v[2]); pIn.tex0 = p[0].tex0; triStream.Append(pIn); pIn.pos = mul(vp, v[3]); pIn.tex0 = p[0].tex0; triStream.Append(pIn); }
分析这个Shader 的做法。 为了制作 Quad Billboard, 输入输出类型 肯定是 TriangleStream, 因为一个Quad 是由2个三角形组成, 也因此 [maxvertexcount(4)] 数值为4, 4个顶点构成2个三角形,处理完4个顶点就可以输出了。
因为是将每个顶点,扩展为4个顶点做Quad 所以 输入类型为 point GS_INPUT p[1] 一次处理一个顶点
float4 v[4]; v[0] = float4(p[0].pos + halfS * right - halfS * up, 1.0f); v[1] = float4(p[0].pos + halfS * right + halfS * up, 1.0f); v[2] = float4(p[0].pos - halfS * right - halfS * up, 1.0f); v[3] = float4(p[0].pos - halfS * right + halfS * up, 1.0f);
已当前顶点位置抽象为中心点,在它周围创建4个新的顶点,构成Quad.
你可能会问我,如果你想把 point GS_INPUT p[1] 改成 point GS_INPUT p[4] 行不行,已自身已有的4个顶点,来组成一个Quad. 我只能说,不行
因为 GS_INPUT p[Number] 的数量是跟 输入格式绑定的 point 格式 只能是1, line 格式 只能是2, triangle 格式只能是 3
至于用自身4个顶点来做quad 行不行呢, 当然是勉强可以,只是要换种方式做, 就是只保留一个顶点,忽略剩下的三个顶点,然后自己重建它们
添加一个结构体,用来创建Quad
static const float4 Quad_POSITION[4] = { float4(0, 0, 0, 0), float4(0, 0, -1, 0), float4(1, 0, 0, 0), float4(1, 0, -1, 0), };
ps:因为我的皮卡丘模型是颠倒的,所以需要 z 轴 * -1. 如果你是正常模型,把 -1 改为 1 即可
在Shader上添加
float4 _SpriteTex_ST_TexelSize;
在构建的Quad 上,也需要重建 uv, 因此需要 uv 的递进值
之后是 GS Shader
// Geometry Shader ----------------------------------------------------- [maxvertexcount(4)] void GS_Main(triangle GS_INPUT p[3], inout TriangleStream<FS_INPUT> triStream) { float4 vertex = float4(p[0].pos.x, p[0].pos.y, p[0].pos.z, 1); float2 uv = p[0].tex0; for (int i = 3; i >= 0; i--) { FS_INPUT pIn; pIn.pos = mul(UNITY_MATRIX_MVP, vertex + Quad_POSITION[i].xyzw * 0.01); pIn.tex0.xy = uv + Quad_POSITION[i].xy * _SpriteTex_ST_TexelSize.xy; triStream.Append(pIn); } }
2. 为细分曲面做一些前置计算 (这需要 SM 5.0 dx11)
3. 使用一个Pass 绘制 Cubemap
不过GS 的性能好低,到DX11 还是很低, 目前仅限做粒子和一些变形渲染上用咯
by the way 由于 unity3d 的限制 ,没有修改 PointSize, 因此使用 PointStream 输出模型后,模型像素点大小没法缩放 ╮(╯▽╰)╭
我在使用几何着色器的时候,有部分顶点看起来像是没有经过几何着色器处理orz
如果可以希望能获得您的联系方式交流一下qw
我的qq见电子邮件qw
請問要如何抓取特定的vertex位置
例如想要在皮卡丘模型的嘴部或耳朵去沿著法線位移 製造出爆炸效果
如何獲得這些特定的位置呢
謝謝