Learn ComputeShader 12 Setting up a buffer-based particle effect
unity有自己的粒子系统,但是这次我们要尝试创建一个我们自己的粒子系统,而且使用计算着色器有下面这些好处。总而言之,计算着色器适合处理大规模的数据集。例如,能够高效地处理数万个甚至数百万个粒子的计算。这对于粒子系统这样的效果特别重要,因为粒子数量通常很大。

首先创建一个粒子结构体,然后给上要用到的属性,以及一些相关的变量
struct Particle{public Vector3 position;public Vector3 velocity;public float life;}const int SIZE_PARTICLE = 7 * sizeof(float);public int particleCount = 1000000;
然后通过init方法初始化粒子数据,分别随机位置和生命周期,然后重置速度为0.很明显位置会随机分布在-0.5-0.5之间。然后填充粒子数据到computebuffer中,分别传递buffer到computeshader和shader中,这里很关键的一部分就是我们在computershader中修改粒子数据,可以在shader中的buffer访问到修改后的数据

void Init(){// initialize the particlesParticle[] particleArray = new Particle[particleCount];for (int i = 0; i < particleCount; i++){// Initialize particleVector3 v = new Vector3();v.x = Random.value * 2 - 1.0f;v.y = Random.value * 2 - 1.0f;v.z = Random.value * 2 - 1.0f;v.Normalize();v *= Random.value * 0.5f;// Assign particle propertiesparticleArray[i].position.x = v.x;particleArray[i].position.y = v.y;particleArray[i].position.z = v.z + 3;//远离摄像机particleArray[i].velocity.x = 0;particleArray[i].velocity.y = 0;particleArray[i].velocity.z = 0;particleArray[i].life = Random.value * 5.0f + 1.0f;//1-6}// create compute bufferparticleBuffer = new ComputeBuffer(particleCount, SIZE_PARTICLE);particleBuffer.SetData(particleArray);// find the id of the kernelkernelID = shader.FindKernel("CSParticle");uint threadsX;shader.GetKernelThreadGroupSizes(kernelID, out threadsX, out _, out _);groupSizeX = Mathf.CeilToInt((float)particleCount / (float)threadsX);// bind the compute buffer to the shader and the compute shadershader.SetBuffer(kernelID, "particleBuffer", particleBuffer);material.SetBuffer("particleBuffer", particleBuffer);material.SetInt("_PointSize", pointSize);}
然后是OnRenderObject函数,每当 Unity 需要渲染一个对象时,这个函数就会被调用,确保在渲染期间可以执行自定义的渲染操作,下面图片是对DrawProceduralNow函数的解释
void OnRenderObject()//相机的每个渲染过程自动调用{material.SetPass(0);//使用第一个PassGraphics.DrawProceduralNow(MeshTopology.Points, 1, particleCount);//程序化绘制顶点}
然后看一下我们的顶点着色器,首先是两个参数,第二实例ID就是逐渐增加的,从0增加到particleCount,第一个参数是每个实例的顶点索引,因为这次粒子都是点,所以永远是0,如果是三角形就会是0,1,2.

v2f vert(uint vertex_id : SV_VertexID, uint instance_id : SV_InstanceID){v2f o = (v2f)0;// Coloro.color = fixed4(1,0,0,1)// Positiono.position = UnityObjectToClipPos(float4(particleBuffer[instance_id].position,1));o.size = 1;return o;}
好了,现在运行会得到一个在屏幕中央半径为0.5左右的红色小球。就像下面这样,这是因为我们并没有对粒子进行任何处理,只是设置了位置和颜色。

接下来就是让这些粒子动起来,我们让粒子跟着鼠标的位置移动,首先找到鼠标的位置。然后设置粒子的速度并且更改粒子的位置。然后如果生命变成0,就调用函数重新生成粒子。(重新生成函数代码与获取鼠标位置代码在后面完整代码里)
下面这个链接是将鼠标位置转换为世界空间坐标
gamedevbeginner.com/
how-to-convert-the-mouse-position-to-world-space-in-unity-2d-3d/
[numthreads(256, 1, 1)]
void CSParticle(uint3 id : SV_DispatchThreadID)
{Particle particle = particleBuffer[id.x];// 减少粒子的生命值particle.life -= deltaTime;// 计算粒子位置与鼠标位置的差值float3 delta = float3(mousePosition.xy, 3) - particle.position;// 计算粒子运动的方向float3 dir = normalize(delta);// 更新粒子的速度particle.velocity += dir;// 根据速度更新粒子的位置particle.position += particle.velocity * deltaTime;// 将更新后的粒子数据存储回缓冲区particleBuffer[id.x] = particle;// 如果粒子的生命值小于 0,则重新生成粒子if (particle.life < 0){respawn(id.x);}
}
现在因为只有红色,有点单调,所以我们要丰富一下颜色。
随着粒子的生命周期减少,这是每个通道的颜色变化

v2f vert(uint vertex_id : SV_VertexID, uint instance_id : SV_InstanceID){v2f o = (v2f)0;// Colorfloat life = particleBuffer[instance_id].life;float lerpVal = life * 0.25;// 计算颜色值o.color = fixed4(1 - lerpVal + 0.1, // Red componentlerpVal + 0.1, // Green component1, // Blue componentlerpVal // Alpha component);// Positiono.position = UnityObjectToClipPos(float4(particleBuffer[instance_id].position,1));o.size = _PointSize;return o;}
最后就来看一下最终效果把

完整代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;#pragma warning disable 0649public class ParticleFun : MonoBehaviour
{private Vector2 cursorPos;// structstruct Particle{public Vector3 position;public Vector3 velocity;public float life;}const int SIZE_PARTICLE = 7 * sizeof(float);public int particleCount = 1000000;public Material material;public ComputeShader shader;[Range(1, 10)]public int pointSize = 2;int kernelID;ComputeBuffer particleBuffer;int groupSizeX; // Use this for initializationvoid Start(){Init();}void Init(){// initialize the particlesParticle[] particleArray = new Particle[particleCount];for (int i = 0; i < particleCount; i++){// Initialize particleVector3 v = new Vector3();v.x = Random.value * 2 - 1.0f;v.y = Random.value * 2 - 1.0f;v.z = Random.value * 2 - 1.0f;v.Normalize();v *= Random.value * 0.5f;// Assign particle propertiesparticleArray[i].position.x = v.x;particleArray[i].position.y = v.y;particleArray[i].position.z = v.z + 3;//远离摄像机particleArray[i].velocity.x = 0;particleArray[i].velocity.y = 0;particleArray[i].velocity.z = 0;particleArray[i].life = Random.value * 5.0f + 1.0f;//1-6}// create compute bufferparticleBuffer = new ComputeBuffer(particleCount, SIZE_PARTICLE);particleBuffer.SetData(particleArray);// find the id of the kernelkernelID = shader.FindKernel("CSParticle");uint threadsX;shader.GetKernelThreadGroupSizes(kernelID, out threadsX, out _, out _);groupSizeX = Mathf.CeilToInt((float)particleCount / (float)threadsX);// bind the compute buffer to the shader and the compute shadershader.SetBuffer(kernelID, "particleBuffer", particleBuffer);material.SetBuffer("particleBuffer", particleBuffer);material.SetInt("_PointSize", pointSize);}void OnRenderObject()//相机的每个渲染过程自动调用{material.SetPass(0);//使用第一个PassGraphics.DrawProceduralNow(MeshTopology.Points, 1, particleCount);//程序化绘制顶点}void OnDestroy(){if (particleBuffer != null)particleBuffer.Release();}// Update is called once per framevoid Update(){float[] mousePosition2D = { cursorPos.x, cursorPos.y };// Send datas to the compute shadershader.SetFloat("deltaTime", Time.deltaTime);shader.SetFloats("mousePosition", mousePosition2D);// Update the Particlesshader.Dispatch(kernelID, groupSizeX, 1, 1);}void OnGUI(){Vector3 p = new Vector3();Camera c = Camera.main;Event e = Event.current;Vector2 mousePos = new Vector2();// Get the mouse position from Event.// Note that the y position from Event is inverted.mousePos.x = e.mousePosition.x;mousePos.y = c.pixelHeight - e.mousePosition.y;p = c.ScreenToWorldPoint(new Vector3(mousePos.x, mousePos.y, c.nearClipPlane + 14));// z = 3.cursorPos.x = p.x;cursorPos.y = p.y;}
}
Shader "Custom/Particle" {Properties { _PointSize("Point size", Float) = 5.0 } SubShader {Pass {Tags{ "RenderType" = "Opaque" }LOD 200Blend SrcAlpha oneCGPROGRAM// Physically based Standard lighting model, and enable shadows on all light types#pragma vertex vert#pragma fragment fraguniform float _PointSize;#include "UnityCG.cginc"// Use shader model 3.0 target, to get nicer looking lighting#pragma target 5.0struct v2f{float4 position : SV_POSITION;float4 color : COLOR;float life : LIFE;float size: PSIZE;};// 定义粒子结构体struct Particle{float3 position; // 粒子位置float3 velocity; // 粒子速度float life; // 粒子的生命值};// 声明结构化缓冲区StructuredBuffer<Particle> particleBuffer;v2f vert(uint vertex_id : SV_VertexID, uint instance_id : SV_InstanceID){v2f o = (v2f)0;// Colorfloat life = particleBuffer[instance_id].life;float lerpVal = life * 0.25;// 计算颜色值o.color = fixed4(1 - lerpVal + 0.1, // Red componentlerpVal + 0.1, // Green component1, // Blue componentlerpVal // Alpha component);// Positiono.position = UnityObjectToClipPos(float4(particleBuffer[instance_id].position,1));o.size = _PointSize;return o;}float4 frag(v2f i) : COLOR{return i.color;}ENDCG}}FallBack Off
}
#pragma kernel CSParticle// Variables set from the CPU
float deltaTime;
float2 mousePosition;uint rng_state;// 定义粒子结构体
struct Particle
{float3 position; // 粒子位置float3 velocity; // 粒子速度float life; // 粒子的生命值
};// 声明结构化缓冲区
RWStructuredBuffer<Particle> particleBuffer;uint rand_xorshift()//随机数范围0-4,294,967,295
{// Xorshift 算法,来自 George Marsaglia 的论文rng_state ^= (rng_state << 13); // 将状态左移13位,并与原状态进行异或rng_state ^= (rng_state >> 17); // 将状态右移17位,并与原状态进行异或rng_state ^= (rng_state << 5); // 将状态左移5位,并与原状态进行异或return rng_state; // 返回新的状态,作为随机数
}void respawn(uint id)
{rng_state = id;float tmp = (1.0 / 4294967296.0);float f0 = float(rand_xorshift()) * tmp - 0.5;float f1 = float(rand_xorshift()) * tmp - 0.5;float f2 = float(rand_xorshift()) * tmp - 0.5;float3 normalF3 = normalize(float3(f0, f1, f2)) * 0.8f;normalF3 *= float(rand_xorshift()) * tmp;particleBuffer[id].position = float3(normalF3.x + mousePosition.x, normalF3.y + mousePosition.y, normalF3.z + 3.0);// reset the life of this particleparticleBuffer[id].life = 4;particleBuffer[id].velocity = float3(0,0,0);
}[numthreads(256, 1, 1)]
void CSParticle(uint3 id : SV_DispatchThreadID)
{Particle particle = particleBuffer[id.x];// 减少粒子的生命值particle.life -= deltaTime;// 计算粒子位置与鼠标位置的差值float3 delta = float3(mousePosition.xy, 3) - particle.position;// 计算粒子运动的方向float3 dir = normalize(delta);// 更新粒子的速度particle.velocity += dir;// 根据速度更新粒子的位置particle.position += particle.velocity * deltaTime;// 将更新后的粒子数据存储回缓冲区particleBuffer[id.x] = particle;// 如果粒子的生命值小于 0,则重新生成粒子if (particle.life < 0){respawn(id.x);}
}
相关文章:
Learn ComputeShader 12 Setting up a buffer-based particle effect
unity有自己的粒子系统,但是这次我们要尝试创建一个我们自己的粒子系统,而且使用计算着色器有下面这些好处。总而言之,计算着色器适合处理大规模的数据集。例如,能够高效地处理数万个甚至数百万个粒子的计算。这对于粒子系统这样的…...
【STL中容器汇总】map、list、vector等详解
容器学习分享 1、STL简介1.1、STL六大组件 2、vector容器2.1、vector 基本操作2.2、vector容器示例2.3、vector容器存放自定义数据类型示例2.3、vector嵌套vector示例 3、list 容器3.1使用示例3.2、list容器基本函数 4、map容器4.1、map函数原型4.2、map函数示例 1、STL简介 ST…...
Semantic Kernel + Natasha:一小时快速生成100个API的奇迹
大家好,我今天带来了一个让人瞠目结舌的实验:在一小时内快速生成了100个API! 其实如果手速高,可以更多。要知道,这得益于之前介绍过的Natasha —— 一个可以动态编译并加载代码的神奇工具。 动态编程神器! 探秘.Net…...
rancher upgrade 【rancher 升级】
文章目录 1. 背景2. 下载3. 安装4. 检查5. 测试5.1 创建项目5.2 创建应用5.3 删除集群5.4 注册集群 1. 背景 rancher v2.8.2 升级 v2.9.1 2. 下载 下载charts helm repo add rancher-latest https://releases.rancher.com/server-charts/latest helm repo update helm fetc…...
【Linux】多线程:线程互斥、互斥锁、线程安全
目录 一、多线程访问公共资源时所产生的问题 二、互斥相关背景概念 互斥量mutex(锁)的引入 三、互斥量 1、初始化互斥量(mutex) 2、互斥量加锁 3、互斥量解锁 4、 销毁互斥量 四、互斥量的使用 1、使用静态互斥量 2、…...
进程之间的通信方式
前言 每个进程的用户地址空间都是独立的,一般而言是不能互相访问的,但内核空间是每个进程都共享的,所以进程之间要通信必须通过内核。 Linux提供了以下进程通信方式: 一、管道 所谓的管道,就是内核里面的一串缓存。…...
动手学深度学习(pytorch)学习记录26-卷积神经网路(LeNet)[学习记录]
目录 LeNet模型训练 LeNet 总体来看,LeNet(LeNet-5)由两个部分组成: 卷积编码器:由两个卷积层组成; 全连接层密集块:由三个全连接层组成。 每个卷积块中的基本单元是一个卷积层、一个sigmoid激活函数和平均…...
log4j 和 java.lang.OutOfMemoryError PermGen space
还是OneCoder在项目中沙箱的问题,用classloader隔离做的沙箱,反复运行用户的任务,出现永生区内存溢出: java.lang.OutOfMemoryError: PermGen space 这个问题在tomcat重复热部署的时候其实比较常见。其道理也和我们沙箱的道理基本…...
2024.9.9营养小题【2】
营养: 1、什么数是丑数? 2、数学数学,丑数的数学意义,哎,数学思维我是忘干净了。 3、可以把while循环换成for循环。由此又想到了一点,三个循环结构各有使用场景。 for(;n%factors[i]0;n/factors[i]){}...
uniapp的barcode组件去掉自动放大功能
autoZoom“false” <barcode id1 class"barcode" autoZoom"false" autostart"false" ref"barcode" background"rgb(0,0,0)" frameColor"#1C86EE"scanbarColor"#1C86EE" :filters"fil" ma…...
H5接入Steam 获取用户数据案例
官方文档地址 1.注册 Steam API Key: 你需要一个 Steam Web API Key,可以在 Steam API Key 页面 获取。https://steamcommunity.com/dev/apikey 2.使用 OpenID 登录: 实现 Steam OpenID 登录,以便用户通过 Steam 账户登录你的…...
《A Few Useful Things to Know about Machine Learning》论文导读
版权声明 本文原创作者:谷哥的小弟作者博客地址:http://blog.csdn.net/lfdfhl机器学习作为人工智能领域的重要分支,近年来得到了广泛的关注和应用。Pedro Domingos的经典论文《A Few Useful Things to Know about Machine Learning》为我们提供了对机器学习深入且全面的理解…...
隔壁老樊2024全国巡回演唱会重磅来袭,首站广州正式官宣!
汹涌人潮将城市填满,斑驳心绪漂浮在时间之隙,当生活的喜悲逐渐演化成歌,天空将自己负载的缄默倾泻,那些或酸涩、或热烈的点滴滑落心海,那层悬挂在「我」与世界分野的无形壁垒,渐也被曙光渗透消融。 提炼生…...
【C++】list(下)
个人主页~ list(上)~ list 四、模拟实现1、list.h(1)关于整个list的搭建①节点②迭代器③接口 (2)自定义类型实例化 2、test.cpp(1)test1(2)test2 五、额外小…...
千云物流 -低代码平台MySQL备份数据
windows备份 全量备份 创建备份目录 需要在安装数据库的服务器上创建备份目录,所有如果要做备份至少需要两倍的硬盘空间, mkdir D:\mysql_backup\full_backup准备备份脚本 创建一个windows批处理文件(例如 full_backup.bat),用来执行全量…...
MySQL:进阶巩固-视图
目录 一、视图的概述二、视图的基本使用2.1 创建视图2.2 查询视图2.3 修改视图2.4 删除视图 一、视图的概述 视图是一种虚拟存在的表,视图中的数据并不在数据库中实际的存在,行列数据来自于视图中查询的表,并且是在使用视图时动态生成的。 通…...
分布式事务Seata原理及其项目使用
0.Seata官方文档 1.Seata概念及原理 Seata是什么 Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。 Seata主要由三个重要组…...
JS_分支结构
if结构 这里的if结构几乎和JAVA中的一样,需要注意的是 if()中的非空字符串会被认为是trueif()中的非零数字会被认为是trueif()中的非空对象会被认为是true <script> if(false){// 非空字符串 if判断为true console.log(true) }else{ console.log(false) } if(){// 长度…...
决策树(Decison Tree)—有监督学习方法、概率模型、生成模型、非线性模型、非参数化模型、批量学习
定义 ID3算法 输入:训练数据集(T= { ( x 1 , y 1 ) , ( x 2 , y 2 ) , ⋯ , ( x N , y N ) } \left\{(x_1,y_1),(x_2,y_2),\cdots,(x_N,y_N)\right\} {(x1,y1),(x2,y2),⋯,(xN,yN)}),特征集A阀值 ε \varepsilon ε 输出:决策树T (1)若D中所有实例属于同一…...
java 自定义注解校验实体类属性
直接上代码 1.是否启用参数校验注解 Target({ElementType.TYPE}) Retention(RetentionPolicy.RUNTIME) Documented public interface EnableArgumentsCheck {/*** 是否启用*/boolean enable() default true;} 2.参数校验自定义注解 /*** 参数校验自定义注解* 属性定义&#…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...
MFC内存泄露
1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...
Nuxt.js 中的路由配置详解
Nuxt.js 通过其内置的路由系统简化了应用的路由配置,使得开发者可以轻松地管理页面导航和 URL 结构。路由配置主要涉及页面组件的组织、动态路由的设置以及路由元信息的配置。 自动路由生成 Nuxt.js 会根据 pages 目录下的文件结构自动生成路由配置。每个文件都会对…...
CMake 从 GitHub 下载第三方库并使用
有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...
关于 WASM:1. WASM 基础原理
一、WASM 简介 1.1 WebAssembly 是什么? WebAssembly(WASM) 是一种能在现代浏览器中高效运行的二进制指令格式,它不是传统的编程语言,而是一种 低级字节码格式,可由高级语言(如 C、C、Rust&am…...
c#开发AI模型对话
AI模型 前面已经介绍了一般AI模型本地部署,直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型,但是目前国内可能使用不多,至少实践例子很少看见。开发训练模型就不介绍了&am…...
C++ Visual Studio 2017厂商给的源码没有.sln文件 易兆微芯片下载工具加开机动画下载。
1.先用Visual Studio 2017打开Yichip YC31xx loader.vcxproj,再用Visual Studio 2022打开。再保侟就有.sln文件了。 易兆微芯片下载工具加开机动画下载 ExtraDownloadFile1Info.\logo.bin|0|0|10D2000|0 MFC应用兼容CMD 在BOOL CYichipYC31xxloaderDlg::OnIni…...
Golang——9、反射和文件操作
反射和文件操作 1、反射1.1、reflect.TypeOf()获取任意值的类型对象1.2、reflect.ValueOf()1.3、结构体反射 2、文件操作2.1、os.Open()打开文件2.2、方式一:使用Read()读取文件2.3、方式二:bufio读取文件2.4、方式三:os.ReadFile读取2.5、写…...
MySQL的pymysql操作
本章是MySQL的最后一章,MySQL到此完结,下一站Hadoop!!! 这章很简单,完整代码在最后,详细讲解之前python课程里面也有,感兴趣的可以往前找一下 一、查询操作 我们需要打开pycharm …...
车载诊断架构 --- ZEVonUDS(J1979-3)简介第一篇
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 做到欲望极简,了解自己的真实欲望,不受外在潮流的影响,不盲从,不跟风。把自己的精力全部用在自己。一是去掉多余,凡事找规律,基础是诚信;二是…...
