Geometry Shader学习笔记

几何着色器作为一个可选项,位于顶点着色器和片元着色器之间。
几何着色器获得顶点着色器组成一个基础图元为一组的顶点输入,对输入的顶点进行处理,这些顶点组可以来自一个点或者一个三角面。进行的是逐图元的操作。
所以基于GS能轻松实现线框着色等效果。

类似OpenGL中GS的输入和输出


输入类型有:
point
line
triangle
lineadj
triangleadj

输出类型有:
PointStream
LineStream
TriangleStream

类似于顶点片元,需要先定义几何着色器geometry,由于GS是第四代显卡着色架构后才支持的,所以着色器编译目标级别最低4.0

#pragma target 4.0
#pragma geometry geom

函数前设置输出最大顶点数量

[maxvertexcount(21)]

实例:

10年世博会的英国馆让当时还在初一的我印象深刻,会馆由内向外伸出由无数根带种子的“条”,非常美

为了将一个面转化为一个三菱体,我们选择输入triangle,输出TriangleStream,所以须绘制7个三角面21个点。

核心代码如下

#define ADD_VERT(v) \
o.vertex = UnityObjectToClipPos(v); \
tristream.Append(o);
      
#define ADD_TRI(p0, p1, p2) \
ADD_VERT(p0) ADD_VERT(p1) \
ADD_VERT(p2) \
tristream.RestartStrip();
      

			
[maxvertexcount(21)]
void geom(triangle v2g IN[3], inout TriangleStream<g2f> tristream)
{
	g2f o;

	float3 edgeA = IN[1].vertex - IN[0].vertex;
	float3 edgeB = IN[2].vertex - IN[0].vertex;
	float3 normalFace = normalize(cross(edgeA, edgeB));

	float2 centerTex = (IN[0].uv + IN[1].uv + IN[2].uv) / 3;

	o.uv = centerTex;

	float3 v0 = IN[0].vertex;
	float3 v1 = IN[1].vertex;
	float3 v2 = IN[2].vertex;
	float3 v3 = IN[0].vertex + normalFace * _Length;
	float3 v4 = IN[1].vertex + normalFace * _Length;
	float3 v5 = IN[2].vertex + normalFace * _Length;
				
	ADD_TRI(v0,v3,v2);
	ADD_TRI(v3,v5,v2);
	ADD_TRI(v3,v4,v0);
	ADD_TRI(v4,v1,v0);
	ADD_TRI(v5,v2,v1);
	ADD_TRI(v5,v4,v1);
	ADD_TRI(v3,v4,v5);
}

先通过点找到底边向量,通过叉积求出法线方向后找到新的三个点,然后组成面。需注意的是,GS输出的依然是图元,还没有进行光栅化插值,接下来的输出传递到片元着色器中完成。每输出一个点Append到输出流中 ,每输出点足够对应相应的图元后都要RestartStrip()一下再继续构成下一图元。完整代码如下:

Shader "MyShader/GS Test"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
		_Length("Length", Range(0.01, 10)) = 0.02
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" }
		LOD 100

		Pass
		{
		Cull Off
			CGPROGRAM
			#pragma target 4.0
			#pragma vertex vert
			#pragma fragment frag
			#pragma geometry geom
			
			#include "UnityCG.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
				float3 normal : NORMAL;
			};

			struct v2g
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
			};

			struct g2f
			{
				float2 uv : TEXCOORD0;
				float4 vertex : SV_POSITION;
			};

			sampler2D _MainTex;
			float4 _MainTex_ST;
			float _Length;
			
			v2g vert(appdata_base v)
			{
				v2g o;
				o.vertex = v.vertex;
				o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
				return o;
			}

		#define ADD_VERT(v) \
        o.vertex = UnityObjectToClipPos(v); \
        tristream.Append(o);
      
        #define ADD_TRI(p0, p1, p2) \
        ADD_VERT(p0) ADD_VERT(p1) \
        ADD_VERT(p2) \
        tristream.RestartStrip();
      

			
		[maxvertexcount(21)]
		void geom(triangle v2g IN[3], inout TriangleStream<g2f> tristream)
		{
			g2f o;

			float3 edgeA = IN[1].vertex - IN[0].vertex;
			float3 edgeB = IN[2].vertex - IN[0].vertex;
			float3 normalFace = normalize(cross(edgeA, edgeB));

			float2 centerTex = (IN[0].uv + IN[1].uv + IN[2].uv) / 3;

			o.uv = centerTex;

			float3 v0 = IN[0].vertex;
			float3 v1 = IN[1].vertex;
			float3 v2 = IN[2].vertex;
			float3 v3 = IN[0].vertex + normalFace * _Length;
			float3 v4 = IN[1].vertex + normalFace * _Length;
			float3 v5 = IN[2].vertex + normalFace * _Length;
				
			ADD_TRI(v0,v3,v2);
			ADD_TRI(v3,v5,v2);
			ADD_TRI(v3,v4,v0);
			ADD_TRI(v4,v1,v0);
			ADD_TRI(v5,v2,v1);
			ADD_TRI(v5,v4,v1);
			ADD_TRI(v3,v4,v5);
		}


			fixed4 frag (g2f i) : SV_Target
			{
				fixed4 col = tex2D(_MainTex, i.uv);
				return col;
			}
			ENDCG
		}
	}
}

效果如下:

实例2:

最近在RIME中看到这样一个效果,不得不说RIME真的是一款非常赞的游戏,BGM更是首首经典。

这个台阶消失的效果必然是用粒子来做的,但是学到GS我们当然要用它来稍微模拟一下这个效果了。

思路:首先是溶解的边缘有一段纯色,这个很容易实现。其次是溶解途中散发出的小粒子,这里我们使用GS用每一个三角面生成一个立方体。所以这就要求模型的面数不要太高,否则=。=  

Shader "My/DoublePass"
{

Properties
{
		_Diffuse("Diffuse", Color) = (1,1,1,1)
		_DissolveColor("Dissolve Color", Color) = (0,0,0,0)
		_MainTex("Base 2D", 2D) = "white"{}
		_ColorFactor("ColorFactor", Range(0,1)) = 0.7
		_DissolveThreshold("DissolveThreshold", Float) = 0  
		_BoxScale("BoxScale",Range(0,1)) = 0.001
		_BoxColor("BoxColor",Color) = (1,1,1,1)
}

SubShader
{
Tags{ "RenderType" = "Opaque" } 
	
Pass{
	Cull Off
	CGPROGRAM
	#pragma vertex vert
	#pragma fragment frag	

	#include "Lighting.cginc"
	uniform fixed4 _Diffuse;
	uniform fixed4 _DissolveColor;
	uniform sampler2D _MainTex;
	uniform float4 _MainTex_ST;
	uniform float _ColorFactor;
	uniform float _DissolveThreshold;  
	
	struct v2f
	{
		float4 pos : SV_POSITION;
		float3 worldNormal : TEXCOORD0;
		float2 uv : TEXCOORD1;
		float4 objPos : TEXCOORD2; 
	};
	
	v2f vert(appdata_base v)
	{
		v2f o;
		o.pos = UnityObjectToClipPos(v.vertex);
		o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
		o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
		o.objPos = v.vertex;  
		return o;
	}
	
	fixed4 frag(v2f i) : SV_Target
	{
		float factor = _DissolveThreshold - i.objPos.y;
		clip(factor); 
		
		fixed3 worldNormal = normalize(i.worldNormal);
		fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
		fixed3 lambert = saturate(dot(worldNormal, worldLightDir));
		fixed3 albedo = lambert * _Diffuse.xyz * _LightColor0.xyz + UNITY_LIGHTMODEL_AMBIENT.xyz;
		fixed3 color = tex2D(_MainTex, i.uv).rgb * albedo;

		if (factor < _ColorFactor)
		{
			return _DissolveColor;
		}
		return fixed4(color, 1);
	}
	ENDCG
}
 
Tags { "RenderType"="Transparent" "Queue"="Transparent" }

Pass {
	  Blend SrcAlpha OneMinusSrcAlpha
      Cull Off

      CGPROGRAM
      #pragma target 4.0
      #pragma vertex vert
      #pragma geometry geo
      #pragma fragment frag
      #pragma multi_compile_fog
      #include "UnityCG.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
				float3 normal : NORMAL;
			};

			struct v2g
			{
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float2 uv : TEXCOORD0;
				float4 objPos: TEXCOORD1;
			};

			struct g2f
			{
				float2 uv : TEXCOORD0;
				float4 vertex : SV_POSITION;
			};

      sampler2D _MainTex;
	  float4 _MainTex_ST;
      float _BoxScale;
	  fixed4 _BoxColor;
	  float _ColorFactor;
	  float _DissolveThreshold; 
	  float _Alpha;

      v2g vert (appdata v) {
        v2g o;
        o.vertex = v.vertex;
        o.uv  = TRANSFORM_TEX(v.uv, _MainTex);
		o.objPos = v.vertex;
        return o;
      }


      #define ADD_VERT(v) \
        o.vertex = UnityObjectToClipPos(v); \
        TriStream.Append(o);
      
      #define ADD_TRI(p0, p1, p2) \
        ADD_VERT(p0) ADD_VERT(p1) \
        ADD_VERT(p2) \
        TriStream.RestartStrip();

      
      [maxvertexcount(36)]
      void geo(triangle v2g v[3], inout TriangleStream<g2f> TriStream) { 

        float4 vertex = (v[0].vertex + v[1].vertex + v[2].vertex) / 3;
        float2 uv = (v[0].uv + v[1].uv + v[2].uv) / 3;

		float3 edgeA = v[1].vertex - v[0].vertex;
		float3 edgeB = v[2].vertex - v[0].vertex;
		float3 normalFace = normalize(cross(edgeA, edgeB));

		

		float factor =  _DissolveThreshold - vertex.y;
		//if(factor < 0) return;
		if(factor < _ColorFactor){

		//这里存粹是为效果而设置的,让它看上去是对的。
		vertex.xyz += normalFace * clamp(-0.5f + vertex.y  + _Time.y * 0.2f,0,5);

        g2f o;
        o.uv = uv;
        float scale = _BoxScale;

        float4 v0 = float4( 1, 1, 1,1)*scale + float4(vertex.xyz,0);
        float4 v1 = float4( 1, 1,-1,1)*scale + float4(vertex.xyz,0);
        float4 v2 = float4( 1,-1, 1,1)*scale + float4(vertex.xyz,0);
        float4 v3 = float4( 1,-1,-1,1)*scale + float4(vertex.xyz,0);
        float4 v4 = float4(-1, 1, 1,1)*scale + float4(vertex.xyz,0);
        float4 v5 = float4(-1, 1,-1,1)*scale + float4(vertex.xyz,0);
        float4 v6 = float4(-1,-1, 1,1)*scale + float4(vertex.xyz,0);
        float4 v7 = float4(-1,-1,-1,1)*scale + float4(vertex.xyz,0);


        ADD_TRI(v0, v2, v3);
        ADD_TRI(v3, v1, v0);
        ADD_TRI(v5, v7, v6);
        ADD_TRI(v6, v4, v5);

        ADD_TRI(v4, v0, v1);
        ADD_TRI(v1, v5, v4);
        ADD_TRI(v7, v3, v2);
        ADD_TRI(v2, v6, v7);

        ADD_TRI(v6, v2, v0);
        ADD_TRI(v0, v4, v6);
        ADD_TRI(v5, v1, v3);
        ADD_TRI(v3, v7, v5);
        }	    
      }

      fixed4 frag (g2f i) : SV_Target {
        float4 col = _BoxColor;
		col.a = _Alpha;
        return col;
      }
      ENDCG
}
    }
 }

在这里选择用两个Pass,第一个绘制溶解边缘部分,注意这里是基于自身坐标的溶解。第二个用GS绘制立方体,类似于之前的三菱体。先找到面的中心点后视为坐标原点创建相应点和面。在生成立方体后移动中心点位置即可实现粒子散开的效果,这里简单起见沿着法线移动。

为了实现从上到下逐渐散开的效果,最好的想法是当到达溶解区域后粒子再向外运动而不是同步扩散,但这个shader反馈是脚本接收不到的,所以我弄了这个纯粹为效果的公式,不同模型还得改不同参数。然后在脚本中控制溶解速度来配合其扩散。

vertex.xyz += normalFace * clamp(-0.5f + vertex.y  + _Time.y * 0.2f,0,5);

最终效果如下:

学习资料:

https://www.khronos.org/opengl/wiki/Geometry_Shader

https://github.com/n0mimono/CustomRendering

 

  • 10
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值