Learn ComputeShader 12 Setting up a buffer-based particle effect

news2024/11/15 15:57:40

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 particles
        Particle[] particleArray = new Particle[particleCount];

        for (int i = 0; i < particleCount; i++)
        {
            // Initialize particle
            Vector3 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 properties
            particleArray[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 buffer
        particleBuffer = new ComputeBuffer(particleCount, SIZE_PARTICLE);

        particleBuffer.SetData(particleArray);

        // find the id of the kernel
        kernelID = 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 shader
        shader.SetBuffer(kernelID, "particleBuffer", particleBuffer);
        material.SetBuffer("particleBuffer", particleBuffer);

        material.SetInt("_PointSize", pointSize);
    }

然后是OnRenderObject函数,每当 Unity 需要渲染一个对象时,这个函数就会被调用,确保在渲染期间可以执行自定义的渲染操作,下面图片是对DrawProceduralNow函数的解释

    void OnRenderObject()//相机的每个渲染过程自动调用
    {
        material.SetPass(0);//使用第一个Pass
        Graphics.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;

				// Color
                o.color = fixed4(1,0,0,1)

				// Position
				o.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;

				// Color
				float life = particleBuffer[instance_id].life;
				float lerpVal = life * 0.25;

				// 计算颜色值
				o.color = fixed4(
					1 - lerpVal + 0.1,  // Red component
					lerpVal + 0.1,      // Green component
					1,                 // Blue component
					lerpVal             // Alpha component
				);

				// Position
				o.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 0649

public class ParticleFun : MonoBehaviour
{

    private Vector2 cursorPos;

    // struct
    struct 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 initialization
    void Start()
    {
        Init();
    }

    void Init()
    {
        // initialize the particles
        Particle[] particleArray = new Particle[particleCount];

        for (int i = 0; i < particleCount; i++)
        {
            // Initialize particle
            Vector3 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 properties
            particleArray[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 buffer
        particleBuffer = new ComputeBuffer(particleCount, SIZE_PARTICLE);

        particleBuffer.SetData(particleArray);

        // find the id of the kernel
        kernelID = 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 shader
        shader.SetBuffer(kernelID, "particleBuffer", particleBuffer);
        material.SetBuffer("particleBuffer", particleBuffer);

        material.SetInt("_PointSize", pointSize);
    }

    void OnRenderObject()//相机的每个渲染过程自动调用
    {
        material.SetPass(0);//使用第一个Pass
        Graphics.DrawProceduralNow(MeshTopology.Points, 1, particleCount);//程序化绘制顶点
    }

    void OnDestroy()
    {
        if (particleBuffer != null)
            particleBuffer.Release();
    }

    // Update is called once per frame
    void Update()
    {

        float[] mousePosition2D = { cursorPos.x, cursorPos.y };

        // Send datas to the compute shader
        shader.SetFloat("deltaTime", Time.deltaTime);
        shader.SetFloats("mousePosition", mousePosition2D);

        // Update the Particles
        shader.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 200
			Blend SrcAlpha one

			CGPROGRAM
			// Physically based Standard lighting model, and enable shadows on all light types
			#pragma vertex vert
			#pragma fragment frag

			uniform float _PointSize;

			#include "UnityCG.cginc"

			// Use shader model 3.0 target, to get nicer looking lighting
			#pragma target 5.0
		
			struct 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;

				// Color
				float life = particleBuffer[instance_id].life;
				float lerpVal = life * 0.25;

				// 计算颜色值
				o.color = fixed4(
					1 - lerpVal + 0.1,  // Red component
					lerpVal + 0.1,      // Green component
					1,                 // Blue component
					lerpVal             // Alpha component
				);

				// Position
				o.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 particle
	particleBuffer[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);
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2120504.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【HarmonyOS】骨架屏(数据请求回来之前使用组件生成骨架屏,数据回来以后【骨架屏】消失)不会让页面白屏显示空

#骨架屏作用用途 骨架屏用途就是防止用户焦虑&#xff08;为了迷惑用户&#xff09; #效果图 #思路&#xff1a; #步骤&#xff1a; 1.首先是封装一个骨架 &#xff08;所使用的思路就是利用 linearGradient translate animation onAppear 实现骨架的闪光效果&#xff09…

【计算机网络】电路交换、报文交换和分组交换——三种交换方式性能分析

【计算机网络】电路交换、报文交换和分组交换——三种交换方式性能分析 目录 【计算机网络】电路交换、报文交换和分组交换——三种交换方式性能分析电路交换性能分析报文交换性能分析分组交换性能分析对比 电路交换性能分析 4Kb 需要8毫秒 数据传送需要11ms 报文交换性能分…

数组与贪心算法——452、435、646、406、169(1简4中)

452. 用最少数量的箭引爆气球&#xff08;中等&#xff09; 有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points &#xff0c;其中points[i] [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。 一支弓箭可…

Python自带日志库实现springboot彩色效果

整体目标 涉及的库均为Python3自带库实现 loggingsysenum 终端显示彩色基本原理参考&#x1f449;Terminal里的颜色的那些事 Python打印日志可以直接借用logging自带的库实现&#xff0c;但是默认的打印实在太丑了&#xff0c;长下面这样 这只是一条日志看着还好比较清爽&…

VUE+Tailwind网页开发

从nodejs官网下载安装包并安装&#xff1a;https://nodejs.org/zh-cn 参考vue官网步骤配置项目&#xff1a;https://cn.vuejs.org/guide/quick-start.html $ npm create vuelatest $ cd <your-project-name> $ npm install 参考&#xff0c;安装vue-router:安装 | Vue…

【CanMV K230】矩形检测

【CanMV K230】矩形检测 什么是 矩形检测矩形检测应用领域1.目标检测2.自动驾驶3.医学图像处理4.智能零售5.图像识别6.计算机视觉 K230应用相关函数官方例程 本篇内容&#xff1a; 什么是 矩形检测矩形检测应用领域K230应用&#xff08;包含相应函数及例程&#xff09; B站视频…

【有啥问啥】HashHop在LTM-2-mini中的应用:解锁长期记忆模型的新纪元

HashHop在LTM-2-mini中的应用&#xff1a;解锁长期记忆模型的新纪元 引言 随着AI技术的飞速发展&#xff0c;模型在处理复杂任务和数据时所需的上下文窗口大小也在不断扩展。深度学习模型在处理超长上下文时&#xff0c;往往面临着计算资源消耗高、上下文丢失等问题。近期&am…

通信工程学习:什么是IFMP(Ipsilon流管理协议)

IFMP&#xff1a;Ipsilon流管理协议 IFMP&#xff08;Ipsilon Flow Management Protocol&#xff09;&#xff0c;即Ipsilon流量管理协议&#xff0c;是一种用于网络流量管理的协议。它主要用于IP交换机、IP交换网关或IP主机中&#xff0c;通过控制数据传送&#xff0c;将现有网…

【H2O2|全栈】更多关于HTML(1)HTML进阶(一)

目录 HTML进阶知识 前言 准备工作 标签的扩展&#xff08;一&#xff09; 本文中的标签在什么位置使用&#xff1f; title标签 meta标签 name viewport referrer http-equiv charset content link标签 实际案例 可视部分 代码分析 其他标签 base标签 styl…

《论企业集成平台的技术与应用》写作框架,软考高级系统架构设计师

论文真题 企业集成平台是一个支持复杂信息环境下信息系统开发、集成和协同运行的软件支撑环境。它基于各种企业经营业务的信息特征,在异构分布环境(操作系统、网络、数据库)下为应用提供一致的信息访问和交互手段,对其上运行的应用进行管理,为应用提供服务,并支持企业信…

数论技巧——使用线性筛法去求1~n之间欧拉函数的和

本节是数论中的重要内容&#xff0c;也是算法竞赛中的常考点&#xff0c;初学者理解起来可能有些困难&#xff0c;需要多多体会 给定一个正整数 n&#xff0c;求 1∼n 中每个数的欧拉函数之和。 欧拉函数的定义&#xff1a;1~n中与n互质的数的个数被称为欧拉函数,记作φ(n) 欧…

操作系统 ---- 进程的概念、组成、特征

学习路线&#xff1a; 一、进程的概念及组成 我们通过一个例子来说明进程的概念以及程序和进程的区别。 我们在Windows操作系统中打开任务管理器&#xff0c;在任务管理器当中能看到此时系统当中运行的进程有哪些&#xff0c;如下图所示&#xff1a; 此时&#…

【前端】vue+html+js 实现table表格展示,以及分页按钮添加

一. 问题描述 数据条数太多显示到页面上时可能会渲染较慢&#xff0c;因此需要截取数据进行展示。 二. 代码写法 思路&#xff1a;按照上述图示思路&#xff0c;需要有两个数据列表&#xff0c;一个存储的是所有的列表数据&#xff0c;一个存储的是展示的数据列表&#xff0c…

蒙特卡罗——三门问题python代码实现

三门问题 b站李永乐老师讲解三门问题 python蒙特卡罗模拟 #模拟三门问题 import random as rd #n:模拟次数,m:中奖次数 n100000 m0 for i in range(n):#车位于的门号carrd.randint(0,2)#人随机选择一个门doorrd.randint(0,2)#主持人展示空门empties{0,1,2}-{car,door}emptyrd…

jmeter基准测试详解

配置基准测试策略&#xff1a;单线程连续发送请求5分钟 脚本&#xff1a;基准测试.jmx 提取码: 0000 登录接口换成自己需要的登录接口即可 一、基准测试脚本配置 线程组下添加图表插件&#xff1a;TPS、响应时间、服务器资源 linux服务器在serveragent目录下启动serveragen…

Golang | Leetcode Golang题解之第395题至少有K个重复字符的最长子串

题目&#xff1a; 题解&#xff1a; func longestSubstring(s string, k int) (ans int) {for t : 1; t < 26; t {cnt : [26]int{}total : 0lessK : 0l : 0for r, ch : range s {ch - aif cnt[ch] 0 {totallessK}cnt[ch]if cnt[ch] k {lessK--}for total > t {ch : s[…

智能翻译新时代:深入解析AI驱动的翻译软件优势

现在语言已经不再是我们学习交流的难点了&#xff0c;因为我们的身边涌现了一批类似百度在线翻译这样的翻译工具为我们与不了的语言直接搭建其一个桥梁。这次我们就来一起探讨有什么好用的翻译工具吧。 1.福昕在线翻译 链接直达&#xff1a;https://fanyi.pdf365.cn/doc 对…

[Redis] Redis中的String类型

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…

comfyui中的几种inpainting工作流对比

inpainting方法集合_sdxl inpaint教程-CSDN博客文章浏览阅读150次。1.32G,通过它可以将所有的sdxl模型转成sdxl_inpaint模型,源于fooocus_inpaint_head,将9个通道压缩为4个通道的小型卷积网络,标准模型unet有4个通道,重绘模型有9个通道,inpaint_model_head和inpaint mode…

Jupyter Notebook 修改默认路径

Jupyter Notebook 修改默认路径 1、默认路径 安装anaconda后&#xff0c;jupyter notebook默认路径下很多文件&#xff0c;很乱&#xff0c;所以为了创建一个干净的文件夹专门存放我的python项目&#xff0c;修改jupyter notebook的文件路径 这是我现在打开jupyter notebook…