当前位置: 首页 > news >正文

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有自己的粒子系统&#xff0c;但是这次我们要尝试创建一个我们自己的粒子系统&#xff0c;而且使用计算着色器有下面这些好处。总而言之&#xff0c;计算着色器适合处理大规模的数据集。例如&#xff0c;能够高效地处理数万个甚至数百万个粒子的计算。这对于粒子系统这样的…...

【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的奇迹

大家好&#xff0c;我今天带来了一个让人瞠目结舌的实验&#xff1a;在一小时内快速生成了100个API&#xff01; 其实如果手速高&#xff0c;可以更多。要知道&#xff0c;这得益于之前介绍过的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&#xff08;锁&#xff09;的引入 三、互斥量 1、初始化互斥量&#xff08;mutex&#xff09; 2、互斥量加锁 3、互斥量解锁 4、 销毁互斥量 四、互斥量的使用 1、使用静态互斥量 2、…...

进程之间的通信方式

前言 每个进程的用户地址空间都是独立的&#xff0c;一般而言是不能互相访问的&#xff0c;但内核空间是每个进程都共享的&#xff0c;所以进程之间要通信必须通过内核。 Linux提供了以下进程通信方式&#xff1a; 一、管道 所谓的管道&#xff0c;就是内核里面的一串缓存。…...

动手学深度学习(pytorch)学习记录26-卷积神经网路(LeNet)[学习记录]

目录 LeNet模型训练 LeNet 总体来看&#xff0c;LeNet&#xff08;LeNet-5&#xff09;由两个部分组成&#xff1a; 卷积编码器&#xff1a;由两个卷积层组成; 全连接层密集块&#xff1a;由三个全连接层组成。 每个卷积块中的基本单元是一个卷积层、一个sigmoid激活函数和平均…...

log4j 和 java.lang.OutOfMemoryError PermGen space

还是OneCoder在项目中沙箱的问题&#xff0c;用classloader隔离做的沙箱&#xff0c;反复运行用户的任务&#xff0c;出现永生区内存溢出&#xff1a; java.lang.OutOfMemoryError: PermGen space 这个问题在tomcat重复热部署的时候其实比较常见。其道理也和我们沙箱的道理基本…...

2024.9.9营养小题【2】

营养&#xff1a; 1、什么数是丑数&#xff1f; 2、数学数学&#xff0c;丑数的数学意义&#xff0c;哎&#xff0c;数学思维我是忘干净了。 3、可以把while循环换成for循环。由此又想到了一点&#xff0c;三个循环结构各有使用场景。 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&#xff1a; 你需要一个 Steam Web API Key&#xff0c;可以在 Steam API Key 页面 获取。https://steamcommunity.com/dev/apikey 2.使用 OpenID 登录&#xff1a; 实现 Steam OpenID 登录&#xff0c;以便用户通过 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全国巡回演唱会重磅来袭,首站广州正式官宣!

汹涌人潮将城市填满&#xff0c;斑驳心绪漂浮在时间之隙&#xff0c;当生活的喜悲逐渐演化成歌&#xff0c;天空将自己负载的缄默倾泻&#xff0c;那些或酸涩、或热烈的点滴滑落心海&#xff0c;那层悬挂在「我」与世界分野的无形壁垒&#xff0c;渐也被曙光渗透消融。 提炼生…...

【C++】list(下)

个人主页~ list&#xff08;上&#xff09;~ list 四、模拟实现1、list.h&#xff08;1&#xff09;关于整个list的搭建①节点②迭代器③接口 &#xff08;2&#xff09;自定义类型实例化 2、test.cpp&#xff08;1&#xff09;test1&#xff08;2&#xff09;test2 五、额外小…...

千云物流 -低代码平台MySQL备份数据

windows备份 全量备份 创建备份目录 需要在安装数据库的服务器上创建备份目录,所有如果要做备份至少需要两倍的硬盘空间, mkdir D:\mysql_backup\full_backup准备备份脚本 创建一个windows批处理文件&#xff08;例如 full_backup.bat&#xff09;&#xff0c;用来执行全量…...

MySQL:进阶巩固-视图

目录 一、视图的概述二、视图的基本使用2.1 创建视图2.2 查询视图2.3 修改视图2.4 删除视图 一、视图的概述 视图是一种虚拟存在的表&#xff0c;视图中的数据并不在数据库中实际的存在&#xff0c;行列数据来自于视图中查询的表&#xff0c;并且是在使用视图时动态生成的。 通…...

分布式事务Seata原理及其项目使用

0.Seata官方文档 1.Seata概念及原理 Seata是什么 Seata 是一款开源的分布式事务解决方案&#xff0c;致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式&#xff0c;为用户打造一站式的分布式解决方案。 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.参数校验自定义注解 /*** 参数校验自定义注解* 属性定义&#…...

【WiFi帧结构】

文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成&#xff1a;MAC头部frame bodyFCS&#xff0c;其中MAC是固定格式的&#xff0c;frame body是可变长度。 MAC头部有frame control&#xff0c;duration&#xff0c;address1&#xff0c;address2&#xff0c;addre…...

基于ASP.NET+ SQL Server实现(Web)医院信息管理系统

医院信息管理系统 1. 课程设计内容 在 visual studio 2017 平台上&#xff0c;开发一个“医院信息管理系统”Web 程序。 2. 课程设计目的 综合运用 c#.net 知识&#xff0c;在 vs 2017 平台上&#xff0c;进行 ASP.NET 应用程序和简易网站的开发&#xff1b;初步熟悉开发一…...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八

现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet&#xff0c;点击确认后如下提示 最终上报fail 解决方法 内核升级导致&#xff0c;需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...

【磁盘】每天掌握一个Linux命令 - iostat

目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat&#xff08;I/O Statistics&#xff09;是Linux系统下用于监视系统输入输出设备和CPU使…...

postgresql|数据库|只读用户的创建和删除(备忘)

CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...

苍穹外卖--缓存菜品

1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得&#xff0c;如果用户端访问量比较大&#xff0c;数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据&#xff0c;减少数据库查询操作。 缓存逻辑分析&#xff1a; ①每个分类下的菜品保持一份缓存数据…...

Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级

在互联网的快速发展中&#xff0c;高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司&#xff0c;近期做出了一个重大技术决策&#xff1a;弃用长期使用的 Nginx&#xff0c;转而采用其内部开发…...

Rapidio门铃消息FIFO溢出机制

关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系&#xff0c;以下是深入解析&#xff1a; 门铃FIFO溢出的本质 在RapidIO系统中&#xff0c;门铃消息FIFO是硬件控制器内部的缓冲区&#xff0c;用于临时存储接收到的门铃消息&#xff08;Doorbell Message&#xff09;。…...

Reasoning over Uncertain Text by Generative Large Language Models

https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829 1. 概述 文本中的不确定性在许多语境中传达,从日常对话到特定领域的文档(例如医学文档)(Heritage 2013;Landmark、Gulbrandsen 和 Svenevei…...

SpringAI实战:ChatModel智能对话全解

一、引言&#xff1a;Spring AI 与 Chat Model 的核心价值 &#x1f680; 在 Java 生态中集成大模型能力&#xff0c;Spring AI 提供了高效的解决方案 &#x1f916;。其中 Chat Model 作为核心交互组件&#xff0c;通过标准化接口简化了与大语言模型&#xff08;LLM&#xff0…...