Unity Shader:用几何着色器实现复联3灭霸的终极大招灰飞烟灭

2023-11-07

这里写图片描述
(图1:正常渲染)
这里写图片描述
(图2:几何着色器粒子化特效进行中)
这里写图片描述
(图3:几何着色器粒子化特效进行中)

1,用几何着色器进行图元转换

在OpenGL渲染管线中,几何着色器Geometry Shader有一个独一无二的功能,既是图元转换。可简单理解为对基本图元点,线,三角形等等之间的转换。基本图元是由顶点组成的,所以几何着色器可以在函数内拿到一个基本图元的所有组成顶点,例如输入图元如果是三角形,它可以拿到三个顶点。

几何着色器相关语法很简单,以本文中Shader为例:

声明着色器:

#pragma geometry geom

设置输出顶点数量:

[maxvertexcount(120)]

声明输入与输出struct:

            struct v2g	//vertex to geometry
            {
                float4 vertex : SV_POSITION;
                fixed4 color:COLOR;
                float3 normal:NORMAL;
            };

            struct g2f	//geometry to fragment
            {
                float4 vertex : SV_POSITION;
                fixed4 color:COLOR;
            };

配置几何着色器的输入参数与输出值:“PointStream”决定了几何着色器的输出数据结构,“triangle v2g”必须与顶点着色器输出(既是几何着色器的输入)的图元类型一致,数组长度也要一致,例如当它为三角形图元时,结构数组长度必须为[3],既是每次同时被输入一个三角形的三个顶点。

           void geom(inout PointStream<g2f> OutputStream,triangle v2g input[3])
           {
				...
           }

在本案例中,一个三角形分解为3个点,需要一个方法将输出的点收集起来:

OutputStream.Append(o);

其他图元之间的转换在语法上大同小异。

2,用几何着色器对图元进行细分

除了细分着色器(见另一篇文章),几何着色器也可以用来进行细分工作。
例如本例中将模型网格进行了粒子化,为了加强效果,增加粒子数量,对每个三角形图元都进行了细分。几何着色器对图元进行细分后输出的顶点数是有上限的,根据输出stream的结构体(例如本例中的g2f)的大小,Shader会对输出顶点数做出限制,本例中,每个三角形最多可以转换为120个粒子顶点,“[maxvertexcount(120)]”。

细分的算法

首先需要三个向量,一个位置向量V0作为起点,以及从起点至另两个顶点的方向向量V1,V2:

			  V1 = (input[1].vertex - input[0].vertex).xyz;
			  V2 = (input[2].vertex - input[0].vertex).xyz;
			  V0 = input[0].vertex.xyz;

接下来,利用V1,V2对三角形进行参数化。
这里写图片描述
(图4:三角形参数化。原图来自http://web.engr.oregonstate.edu/~mjb/cs519/Handouts/geometry_shaders.1pp.pdf)

我对三角形参数化的理解是:从V0出发,以V1为方向行进x个单位,再以V2为方向行进y个单位,可到达三角形内任意一点:
这里写图片描述
(图5:从V0出发按照V1,V2方向行进到达点P0,P1)

Shader代码实现

			  int numLayers =1<<_Level;		//2^_Level
			  float dt = 1.0f / float( numLayers );
              float t = 1.0f;
              for( int it = 0; it < numLayers; it++ )
			  {
				float smax = 1.0f - t;
				int nums = it + 1;
				float ds = smax / float( nums - 1 );
				float s = 0;
				for( int is = 0; is < nums; is++ )
				{
					float3 v = V0 + s*V1 + t*V2;
					//略......
				    s += ds;
   				} 
				t -= dt;
			  }

上面代码删去与细分无关的部分,核心思想既是在双重循环中等距的向V1,V2方向移动,移动过程中找出的所有点既是三角形细分后的点的集合。
这里写图片描述
(图6:一个待细分的quad)

这里写图片描述
(图7:用此算法对quad进行细分后,由于输出顶点数达到了极限120,中间部分为空白)

_Level变量可用来控制细分中t方向的密度:
这里写图片描述
(图8:_Level=4)
这里写图片描述
(图9:_Level=1)

3,用几何着色器构建粒子系统

使用了几何着色器后,它就成为了面向顶点编程的最后阶段,可在此对顶点进行动画编程。例如案例中的Shader在几何着色器中实现了一个位移动画和淡出效果。

粒子位移

在顶点属性准备阶段,C#脚本不断更新一个unityTime全局变量。

Shader.SetGlobalFloat ("unityTime", Time.time);

在Shader内计算动画累计时间:

              float time_SinceBirth=(unityTime-_ShaderStartTime)*0.1f;

计算重心坐标:

			  CG=(input[0].vertex.xyz + input[1].vertex.xyz+ input[2].vertex.xyz)/3.0f;

位移:根据动画时间进行位移加速。此行代码决定了此Shader中粒子的移动效果,如果想模拟真实物理效果可套入一些公式。此行代码没什么特殊思想,只是根据时间有一个越飞越快的效果。

					v = CG + vel*(_Speed*time_SinceBirth+1.0f) + 0.5f*_DispDir.xyz*sin(it*is)*(_Speed*time_SinceBirth)*(_Speed*time_SinceBirth);

淡出效果

根据动画累计时间对alpha值进行递减让粒子逐渐消失:

					o.color=_FinalColor;
					o.color.w=1.0f-smoothstep(0,1.0f,time_SinceBirth);

除了通过alpha进行淡出处理也可以通过语义ponitsize对粒子size进行缩小处理以达到淡出(但目前在unity 5.x版本中使用此语义无法通过编译)。

4,案例源码:

Shader:

Shader "Unlit/ParticleExp_Beta"
{
    Properties
    {
    	//细分相关变量
    	_Level("Level",int)=0
    	_DispDir("Displacement Direction",Vector)=(0,0,0)
    	_uVelScale("VelScale",float)=2
    	//粒子化特效相关变量
		_Speed("Speed",Range(0,1))=1
		_ShaderStartTime("Shader Start Time",float)=0
		_FinalColor("Final Color",color)=(1,1,1,1)
    }

    SubShader
    {
		Tags{"RenderType"="Transparent" "Queue" = "Transparent"}
        LOD 100
 
        Pass
        {
         	Blend SrcAlpha OneMinusSrcAlpha // use alpha blending
        	cull off

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma geometry geom
           
            #include "UnityCG.cginc"
            细分相关变量
            uniform int _Level;
			uniform float3 _DispDir;
			uniform float _uVelScale;
			粒子化特效相关变量
			uniform float _Speed;			//粒子位移速度
			uniform float _ShaderStartTime; //粒子化起始时间
			uniform fixed4 _FinalColor;		//粒子颜色

			float3 V0, V1, V2;
			float3 CG;
			float unityTime;

            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal:NORMAL;
            };
 
            struct v2g
            {
                float4 vertex : SV_POSITION;
                fixed4 color:COLOR;
                float3 normal:NORMAL;
            };

            struct g2f
            {
                float4 vertex : SV_POSITION;
                fixed4 color:COLOR;
            };
           
            v2g vert (appdata v)
            {
                v2g o;
                o.vertex = v.vertex;
                o.normal=UnityObjectToWorldNormal(v.normal);
                return o;
            }

           [maxvertexcount(120)]//v2g input[3]
           void geom(inout PointStream<g2f> OutputStream,triangle v2g input[3])
           {
              float time_SinceBirth=(unityTime-_ShaderStartTime)*0.1f;
 			  g2f o = (g2f)0;
			  V1 = (input[1].vertex - input[0].vertex).xyz;
			  V2 = (input[2].vertex - input[0].vertex).xyz;
			  V0 = input[0].vertex.xyz;
			  CG=(input[0].vertex.xyz + input[1].vertex.xyz+ input[2].vertex.xyz)/3.0f;
			  int numLayers =1<<_Level;		//2^_Level
			  float dt = 1.0f / float( numLayers );
              float t = 1.0f;
              
              for( int it = 0; it < numLayers; it++ )
			  {
				float smax = 1.0f - t;
				int nums = it + 1;
				float ds = smax / float( nums - 1 );
				float s = 0;
				
				for( int is = 0; is < nums; is++ )
				{
					float3 v = V0 + s*V1 + t*V2;
					float3 vel = _uVelScale * ( v - CG );
					v = CG + vel*(_Speed*time_SinceBirth+1.0f) + 0.5f*_DispDir.xyz*sin(it*is)*(_Speed*time_SinceBirth)*(_Speed*time_SinceBirth);
					o.vertex = UnityObjectToClipPos(float4( v, 1.0f ));
					o.color=_FinalColor;
					o.color.w=1.0f-smoothstep(0,1.0f,time_SinceBirth);
					OutputStream.Append(o);
				    s += ds;
   				} 
				t -= dt;
			  }
           }

            fixed4 frag (g2f i) : SV_Target
            {
      			return i.color;
            }
            ENDCG
        }
    }
}

C#动画触发控制脚本:

挂在场景中任意gameObject上。主要功能是收集model上的所有材质球并对其进行逐渐替换以实现逐渐粒子化的效果。另一个思路是同时替换所有材质球,然后根据uv或位置坐标进行更细腻的粒子化渐变。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ParticleExpController : MonoBehaviour {

	public Material particleExp;
	public MeshRenderer[] smRs;
	private Material[] originalMaterial;
	public GameObject model;

	// Use this for initialization
	void Start () {

	}

	IEnumerator EXP(){
		smRs=model.GetComponentsInChildren<MeshRenderer>();
		Material p_exp = new Material (particleExp);
		p_exp.SetFloat ("_ShaderStartTime", Time.time);
		for (int i = 0; i < smRs.Length; i++) {
			Material[] temp=smRs[i].materials;
			for(int j=0;j<smRs[i].materials.Length;j++){
				temp [j] = p_exp;
			}
			smRs [i].materials = temp;
			yield return new WaitForSeconds (0.5f);
		}
	}

	// Update is called once per frame
	void Update () {
		if(Input.GetKeyDown(KeyCode.E)){
			StartCoroutine (EXP ());
		}
		
		Shader.SetGlobalFloat ("unityTime", Time.time);
	}
}


参考:
OPENGL编程指南–Khronos Group
GLSL Geometry Shader–Mike Bailey–
(http://web.engr.oregonstate.edu/~mjb/cs519/Handouts/geometry_shaders.1pp.pdf)
射线和三角形的相交检测–DirectX
(http://www.cnblogs.com/graphics/archive/2010/08/09/1795348.html)

维护日志:
2018-2-24:填词改句
2018-6-10:改标题
2020-8-16:review

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Unity Shader:用几何着色器实现复联3灭霸的终极大招灰飞烟灭 的相关文章

随机推荐

  • 生成静态页面的五种方案 收藏

    方案1 public static bool WriteFile string strText string strContent string strAuthor string path HttpContext Current Serve
  • 【知识图谱】知识图谱数据库将人类的思维路径转化为机器的路径思维

    前段时间被沙特阿拉伯授予公民身份的人形机器人 索菲亚 再一次颠覆了人们对人工智能技术的认知 索菲亚 多次与人类交锋并公开发表言论的过程中 我们感受到了基本的对答如流 有时甚至还可以做到妙语连珠 据了解 索菲亚的大脑存储在云端 通过连接WIF
  • 处理处理kdevtmpfsi挖矿病毒以及他的守护进程kinsing

    服务器CPU资源占用一直处于100 的状态 检查发现是kdevtmpfsi占用导致的 此进程为挖矿程序 处理步骤如下 kdevtmpfsi 进程处理 1 top 查看cpu占用情况 找到占用cpu的进程 最后是 kdevtmpfsi 2 n
  • 将文件间的编译依存关系降至最低——条款31

    假设你对C 程序的某个class实现文件做了些轻微修改 注意 修改的不是class接口 而是实现 而且只改private成分 然后重新建置这个程序 并预计只花数秒就好 毕竟只有一个class被修改 你按下 Build 按钮或键入make 或
  • 西门子200SMART(六)数据块

    数据块中的数据页可以插入 编辑 删除 查询 和之前讨论的程序块 符号快以及状态图标基本具备一样的功能 数据块最主要点作用就是对地址和数据赋值 如下图 这里需要注意点是 这里的赋值和之前我们说过的状态图表中的强制是有区别的 强制顾名思义不管你
  • 【Pandas学习】读、存excel数据

    目录 一 读数据 二 将df存为excel 1 pandas DataFrame to csv 函数语法 2 利用 import os 获取保存路径 3 产生新的数据 添加至上述csv文件中已有数据的后面 4 多sheet 指定存入的she
  • Leecode 每日一题 problem2 (03-23)

    给你两个 非空 的链表 表示两个非负的整数 它们每位数字都是按照 逆序 的方式存储的 并且每个节点只能存储 一位 数字 请你将两个数相加 并以相同形式返回一个表示和的链表 你可以假设除了数字 0 之外 这两个数都不会以 0 开头 来源 力扣
  • 2021-10-24

    Python 简介 Python 是一个高层次的结合了解释性 编译性 互动性和面向对象的脚本语言 Python 的设计具有很强的可读性 相比其他语言经常使用英文关键字 其他语言的一些标点符号 它具有比其他语言更有特色语法结构 Python
  • ERR_PNPM_NO_GLOBAL_BIN_DIR Unable to find the global bin directory

    错误提示 ERROR Unable to find the global bin directory Run pnpm setup to create it automatically or set the global bin dir s
  • (1)minikube玩转k8s集群之虚拟机支持嵌套虚拟化

    配套视频教程 1 Minikube介绍 简单说 创建k8s集群很麻烦 minikube可以让我们快速搭建一个k8s集群用于学习 Minikube 是一种可以让您在本地轻松运行 Kubernetes 的工具 Minikube 在笔记本电脑上的
  • openId和unionId的区别

    网友的解释 微信的用户隐私策略 每个接入微信的应用 公众号 APP 就像一个独立的商场 用户使用这些应用就像逛商场 商场用会员卡识别用户 类似的 我们根据商场名字为每个用户生成了一张专属会员卡 openid 每张会员卡只能在对应的商场才能够
  • Navicat for MySQL安装教程

    Navicat for MySQL是一款强大的 MySQL 数据库管理和开发工具 它为专业开发者提供了一套强大的足够尖端的工具 但对于新用户仍然易于学习 Navicat for MySQL 基于Windows平台 为 MySQL 量身订作
  • R语言logistic回归的细节解读

    本文首发于公众号 医学和生信笔记 完美观看体验请至公众号查看本文 医学和生信笔记 专注R语言在临床医学中的使用 R语言数据分析和可视化 文章目录 二项logistic回归 R语言中的 factor 函数可以把变量变为因子类型 默认是没有等级
  • Redis缓存击穿问题及解决思路

    一 什么是缓存击穿 缓存击穿问题也叫热点Key问题 就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了 无数的请求访问会在瞬间给数据库带来巨大的冲击 逻辑分析 假设线程1在查询缓存之后 本来应该去查询数据库 然后把这个数据重新加载
  • 【Java】对象的序列化与反序列化

    对象序列化的含义 对象序列化 Serialize 指将一个Java对象写入IO流中 对象的反序列化 Deserialize 则是指从IO流中恢复该Java对象 如果想让某个Java对象能够序列化 则必须让它的类实现java io Seria
  • 使用docker需要知道的基础知识

    一 docker概念 1 1 docker仓库 镜像 容器的作用和三者之间的关系是什么 答 Docker 仓库 用来保存镜像 可以理解为代码控制中的代码仓库 Docker 镜像 是用于创建 Docker 容器的模板 Docker 容器 是独
  • 前端工程化详解——理解与实践前端工程化

    前言 前端工程化一直是一个老生常谈的问题 不管是面试还是我们在公司做基建都会经常提到前端工程化 那么为什么经常会说到前端工程化 并没有听过后端工程化 Java工程化或者Python工程化呢 我们理解的前端工程化是不是一直都是Webpack的
  • 回望2001年的雪鸟城:引发全球软件革命的“敏捷宣言”是如何诞生的?

    将人们置于流程之上 专注于开发可以工作的软件 而不是软件的文档 和你的客户一起工作 而不是为一份合同而争吵 在此过程中 要对改变持开放态度 编者按 敏捷 Agile 这个对于开发真来说不在陌生的概念 已经提出了近17年了 其背后的哲学理念也
  • [CSDN竞赛]第五期参赛回顾

    CSDN竞赛 第五期参赛回顾 体验感想 第一次参加 本来有点小期待 那天还起晚了 结果登录不上去 发现大家都有问题 对我来说反而是好事 本来早上没有下午晚上更加精神 下面是提的建议 上次的登录问题 希望官方下次不要再出现 一定要准备充足 为
  • Unity Shader:用几何着色器实现复联3灭霸的终极大招灰飞烟灭

    图1 正常渲染 图2 几何着色器粒子化特效进行中 图3 几何着色器粒子化特效进行中 1 用几何着色器进行图元转换 在OpenGL渲染管线中 几何着色器Geometry Shader有一个独一无二的功能 既是图元转换 可简单理解为对基本图元点