Unity3D Geomerty Shader

Geomerty Shader 是 SM4.0 Directx 9.0c 中引用的新技术, 它的位置位于 Vertex Shader 和 Fragment Shader 之间, 可以对一组(多个)顶点进行编程

Geometry Shader 可以自定义输出方式
PointStream
LineStream
TriangleStream
分别是 点,线,面
这样如果想做线框模式渲染,就非常的简单

上个线框模式图
LineStream


请不要无脑复制转载本文, 由 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
那么就可以看到 以顶点为单位, 中间没有像素插值的 皮卡丘

PointStream

GS Shader 应用的场景

1.基于模型形状,以顶点为单位的 Billboard
TriangleStream_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);
		}
	}

TriangleStream1

2. 为细分曲面做一些前置计算 (这需要 SM 5.0 dx11)
3. 使用一个Pass 绘制 Cubemap

不过GS 的性能好低,到DX11 还是很低, 目前仅限做粒子和一些变形渲染上用咯
by the way 由于 unity3d 的限制 ,没有修改 PointSize, 因此使用 PointStream 输出模型后,模型像素点大小没法缩放 ╮(╯▽╰)╭

4 comments

  1. 我在使用几何着色器的时候,有部分顶点看起来像是没有经过几何着色器处理orz
    如果可以希望能获得您的联系方式交流一下qw
    我的qq见电子邮件qw

  2. 請問要如何抓取特定的vertex位置
    例如想要在皮卡丘模型的嘴部或耳朵去沿著法線位移 製造出爆炸效果
    如何獲得這些特定的位置呢
    謝謝

KenBoNely进行回复 取消回复

电子邮件地址不会被公开。 必填项已用*标注