Learn ComputeShader 15 Grass

1.Using Blender to create a single grass clump

首先blender与unity的坐标轴不同,z轴向上,不是y轴

通过小键盘的数字键可以快速切换视图,选中物体以后按下小键盘的点可以将物体聚焦于屏幕中心

首先我们创建一个平面,宽度为0.2m,然后切换到正交前视图,复制两个平面。shift+D可以复制面

接着将上下两个面旋转45°至中间面的中心。先按下R然后按下Y可以绕y轴旋转,然后按G键可以移动面

然后切换到正交顶视图(数字键7)

 

将两个复制的面分别向左向右旋转10.5°左右

最后加上材质和贴图以后的效果就是下面这样

然后就可以保存退出Blender了,后面我们在unity中批量产出这个grass

2. Using instancing to cover a surface with grass

首先就是定义草的类,包含了草的位置,摆动角度

 struct GrassClump{public Vector3 position;public float lean;public float noise;public GrassClump( Vector3 pos){position.x = pos.x;position.y = pos.y;position.z = pos.z;lean = 0;noise = Random.Range(0.5f, 1);if (Random.value < 0.5f) noise = -noise;}}

接着还有全局的草的密度,大小,最大摆动角度

 [Range(0,1)]public float density = 0.8f;[Range(0.1f,3)]public float scale = 0.2f;[Range(10, 45)]public float maxLean = 25;

然后就是初始化草丛的位置信息,生成一个 ComputeBuffer 来存储这些位置数据,并通过计算着色器来模拟草丛的摆动效果。

获取附加的 MeshFilter 组件中的网格边界,bounds.extents 返回边界框的一半大小(每个轴的范围的一半)

MeshFilter mf = GetComponent<MeshFilter>();
Bounds bounds = mf.sharedMesh.bounds;
Vector3 clumps = bounds.extents;

使用对象的缩放值(transform.localScale)和一个密度因子来调整草丛的分布范围,主要是 x 和 z 轴 。并且计算草丛的总数量

Vector3 vec = transform.localScale / 0.1f * density;clumps.x *= vec.x;clumps.z *= vec.z;int total = (int)clumps.x * (int)clumps.z;

获取计算着色器中的内核 LeanGrass,并计算每个线程组的大小。groupSize 是用于处理草丛的线程组数,而 count 则是实际生成的草丛总数 

kernelLeanGrass = shader.FindKernel("LeanGrass");
shader.GetKernelThreadGroupSizes(kernelLeanGrass, out threadGroupSize, out _, out _);
groupSize = Mathf.CeilToInt((float)total / (float)threadGroupSize);
int count = groupSize * (int)threadGroupSize;

随机生成 count 个草丛的 pos 位置。这个位置基于网格的边界和中心生成,使用 TransformPoint 将局部坐标转换为全局坐标 (世界坐标)

clumpsArray = new GrassClump[count];for (int i = 0; i < count; i++)
{Vector3 pos = new Vector3(Random.value * bounds.extents.x * 2 - bounds.extents.x + bounds.center.x,0,Random.value * bounds.extents.z * 2 - bounds.extents.z + bounds.center.z);pos = transform.TransformPoint(pos);clumpsArray[i] = new GrassClump(pos);
}

创建了一个 ComputeBuffer 来存储所有的草丛位置信息,并将 clumpsArray 赋值到缓冲区中。 

clumpsBuffer = new ComputeBuffer(count, SIZE_GRASS_CLUMP);
clumpsBuffer.SetData(clumpsArray);

将缓冲区 clumpsBuffer 绑定到计算着色器的 clumpsBuffer 参数,并将草丛最大倾斜角度 maxLean 传递给着色器。 

shader.SetBuffer(kernelLeanGrass, "clumpsBuffer", clumpsBuffer);
shader.SetFloat("maxLean", maxLean * Mathf.PI / 180);
timeID = Shader.PropertyToID("time");

 通过 argsArray 设置绘制调用的参数(索引数量和实例数量),并使用 ComputeBuffer 类型为 IndirectArguments 创建一个缓冲区,用于 DrawMeshInstancedIndirect 函数的调用

  • argsArray[0] = mesh.GetIndexCount(0);

    • 这行代码获取的是 mesh 的索引数量,也就是用来渲染的几何体有多少个顶点索引。每个网格都有其顶点、法线、UV 等信息,而索引决定了如何连接这些顶点来形成三角形。
    • IndirectArguments 绘制时,第一个参数就是表示绘制网格时使用的顶点索引数量。
  • argsArray[1] = (uint)count;

    • count 是实例化对象的数量。通过 Graphics.DrawMeshInstancedIndirect 方法可以在一次绘制调用中实例化多个对象。
    • 这里第二个参数表示要绘制的实例化网格的数量
argsArray[0] = mesh.GetIndexCount(0);
argsArray[1] = (uint)count;
argsBuffer = new ComputeBuffer(1, 5 * sizeof(uint), ComputeBufferType.IndirectArguments);
argsBuffer.SetData(argsArray);

然后看一下我们的计算着色器

很简短,就是设置了个倾斜角度,方便后续在表面shader中进行旋转

[numthreads(THREADGROUPSIZE,1,1)]
void LeanGrass (uint3 id : SV_DispatchThreadID)
{GrassClump clump = clumpsBuffer[id.x];clump.lean = sin(time + clump.noise) * maxLean * clump.noise;clumpsBuffer[id.x] = clump;
}

接着继续编写表面着色器

首先是设置每个草丛的位置以及旋转平移矩阵

        void setup(){#ifdef UNITY_PROCEDURAL_INSTANCING_ENABLEDGrassClump clump = clumpsBuffer[unity_InstanceID];_Position = clump.position;_Matrix = create_matrix(clump.position, clump.lean);#endif}

然后是创建矩阵函数,这是一个绕z轴旋转的矩阵

  float4x4 create_matrix(float3 pos, float theta){float c = cos(theta);float s = sin(theta);return float4x4(c,-s, 0, pos.x,s, c, 0, pos.y,0, 0, 1, pos.z,0, 0, 0, 1);}

最后就是顶点函数的设置

首先乘上缩放系数,然后计算经过旋转和平移的顶点位置,接着计算只经过平移的位置,最后根据uv的y值来插值坐标,也就是高度越高,弯曲幅度越大

 void vert(inout appdata_full v, out Input data){UNITY_INITIALIZE_OUTPUT(Input, data);#ifdef UNITY_PROCEDURAL_INSTANCING_ENABLEDv.vertex.xyz *= _Scale;float4 rotatedVertex = mul(_Matrix, v.vertex);v.vertex.xyz += _Position;v.vertex = lerp(v.vertex, rotatedVertex, v.texcoord.y);#endif}

最终效果:

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

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

相关文章

SpringBoot中使用EasyExcel并行导出多个excel文件并压缩zip后下载

❃博主首页 &#xff1a; 「码到三十五」 &#xff0c;同名公众号 :「码到三十五」&#xff0c;wx号 : 「liwu0213」 ☠博主专栏 &#xff1a; <mysql高手> <elasticsearch高手> <源码解读> <java核心> <面试攻关> ♝博主的话 &#xff1a…

SysML图例-农业无人机

DDD领域驱动设计批评文集>> 《软件方法》强化自测题集>> 《软件方法》各章合集>>

dll修复工具4DDiG DLL Fixer,解决电脑dll丢失问题

4DDiG DLL Fixer是一款专业的DLL修复工具&#xff0c;旨在解决Windows系统中各种DLL相关问题。该工具能够快速全面地扫描计算机&#xff0c;检测并修复导致程序功能异常的DLL错误。它支持一键式操作&#xff0c;自动扫描、识别和替换缺失或损坏的DLL文件&#xff0c;从而帮助用…

推荐3款AIai论文大纲一键生成文献,精选整理!

在当前的学术写作环境中&#xff0c;AI论文大纲生成工具已经成为许多学者和学生的重要助手。这些工具不仅能够快速生成高质量的论文大纲&#xff0c;还能提供内容填充、文献引用和查重修改等全方位的服务。以下是三款值得推荐的AI论文大纲一键生成文献工具&#xff1a;千笔-AIP…

爬虫--翻页tips

免责声明&#xff1a;本文仅做分享&#xff01; 伪线程 from DrissionPage import ChromiumPage import timepage ChromiumPage() page.get("https://you.ctrip.com/sight/taian746.html") # 初始化 第0页 index_page 0# 翻页点击函数 sleep def page_turn():page…

C/C++语言基础--从C到C++的不同(下),15个部分说明C与C++的不同

本专栏目的 更新C/C的基础语法&#xff0c;包括C的一些新特性 前言 1-10在上篇C/C语言基础–从C到C的不同(上&#xff09;&#xff1b;当然C和C的不同还有很多&#xff0c;本人暂时只总结这些&#xff0c;其他的慢慢更新&#xff1b;上一篇C/C语言基础–从C到C的不同(上&…

node.js 中的进程和线程工作原理

本文所有的代码均基于 node.js 14 LTS 版本分析 概念 进程是对正在运行中的程序的一个抽象&#xff0c;是系统进行资源分配和调度的基本单位&#xff0c;操作系统的其他所有内容都是围绕着进程展开的 线程是操作系统能够进行运算调度的最小单位&#xff0c;其是进程中的一个执…

康养小站:长者舒缓疼痛的港湾

【导语】在老龄化日益加剧的当下&#xff0c;如何关爱和照顾好长者&#xff0c;成为社会关注的焦点。近日&#xff0c;笔者走进深圳宝安区一家专注于长者康养的社区小站&#xff0c;探访它如何帮助长者缓解疼痛&#xff0c;提高生活质量。 随着我国人口老龄化问题日益显著&…

算法:30.串联所有单词的子串

题目 链接&#xff1a;leetcode链接 思路分析&#xff08;滑动窗口&#xff09; 这道题目类似寻找异位词的题目&#xff0c;我认为是寻找异位词的升级版 传送门:寻找异位词 为什么说像呢&#xff1f; 注意&#xff1a;这道题目中words数组里面的字符串长度都是相同的&…

[JAVA]介绍怎样在Java中通过字节字符流实现文件读取与写入

一&#xff0c;初识File类及其常用方法 File类是java.io包下代表与平台无关的文件和目录&#xff0c;程序中操作文件和目录&#xff0c;都可以通过File类来完成。 通过这个File对象&#xff0c;可以进行一系列与文件相关的操作&#xff0c;比如判断文件是否存在&#xff0c;获…

Java毕业设计 基于SpringBoot和Vue药店管理系统

Java毕业设计 基于SpringBoot和Vue药店管理系统 这篇博文将介绍一个基于SpringBoot框架和Vue开发的药店管理系统&#xff0c;适合用于Java毕业设计。 功能介绍 首页 图片轮播 登录 注册 药品信息 药品详情 评论 收藏 购买 添加到购物车 用药指南 公告资讯 购物车 …

在深圳停车场我居然能看到很漂亮的瓦房

石岩街道在宝安确实是小透明哈&#xff0c;从市区搬到石岩快4年了&#xff0c;确实这里的建筑特别像老家的感觉&#xff0c;马路很狭窄。如果是开车的话&#xff0c;我是不会进入罗租大道来着&#xff0c;人车太复杂。由于上屋社区适合儿童的室内场所太少了&#xff0c;石岩这块…

python之模块和包的导入与使用,pip的使用(13)

文章目录 1、模块1.1 模块的分类1.1.1 内置模块1.1.2 第三方模块&#xff08;比较重要&#xff09;1.1.3 自定义模块 1.2 模块的导入1.2.1 单个模块的导入1.2.2 同时导入多个模块1.2.3 模块导入规范1.2.4 给导入的模块取别名1.2.5 同时导入模块和名字1.2.6 给导入的名字取别名扩…

【Python机器学习】序列到序列建模——使用序列到序列网络构建一个聊天机器人

为了寻聊天机器人&#xff0c;下面使用康奈尔电影对话语料库训练一个序列到序列的网络来“适当的”湖大问题或语句。以下聊天机器人示例采用的是Keras blog中的序列到序列的示例。 为训练准备语料库 首先&#xff0c;需要加载语料库并从中生成训练集&#xff0c;训练数据将决…

【刷题】Day5--数字在升序数组中出现的次数

Hi! 今日份刷题~ 数字在升序数组中出现的次数_牛客题霸_牛客网 我感觉题目简单&#xff0c;我的解答也很简单&#xff0c;二分法遗忘&#xff0c;有时间复习一下尝试新的解法。 /*** 代码中的类名、方法名、参数名已经指定&#xff0c;请勿修改&#xff0c;直接返回方法规定的…

electron-updater实现electron全量版本更新

在 Electron 应用中使用 electron-updater 来实现自动更新功能时&#xff0c;通常你会在一个专门的模块或文件中管理更新逻辑。如果你想要使用 ES6 的 import 语法来引入 electron-updater&#xff0c;你需要确保你的项目已经配置好了支持 ES6 模块的构建工具&#xff08;如 We…

系统指标优化 Stream流 API使用及源码分析

java 8 Stream流 api 使用 &#xff08;1&#xff09;集合筛选&#xff0c;为什么此处要使用 Collectors.toMap 加 Function.identity() 的方式&#xff0c;而不使用简单的filter过滤呢&#xff1f; Stream流 API 代码: collect(Collectors.toMap(GoldTagResponse::getValue, …

C语言深入理解指针(一)

目录 内存和地址内存编址的理解 指针变量和地址取地址操作符&#xff08;&&#xff09;指针变量和解引用操作符&#xff08;*&#xff09;指针变量如何拆解指针类型解引用操作符 指针变量的大小 指针变量类型的意义指针的解引用指针-整数 const修饰指针const修饰变量const修…

多态的相关知识

一.多态的概念 1.多态&#xff1a;多态是⼀个继承关系的下的类对象&#xff0c;去调⽤同⼀函数&#xff0c;产⽣了不同的⾏为。 2.多态分为编译时多态(静态多态)和运⾏时多态(动态多态)。 1>编译时多态(静态多态)主要就是函数重载和函数模板&#xff0c;他们传不同类型的…

Datawhale------Tiny-universe学习笔记——Qwen

1. Qwen整体介绍 对于一个完全没接触过大模型的小白来说&#xff0c;猛一听这个名字首先会一懵&#xff1a;Qwen是啥。这里首先解答一下这个问题。下面是官网给出介绍&#xff1a;Qwen是阿里巴巴集团Qwen团队研发的大语言模型和大型多模态模型系列。其实随着大模型领域的发展&a…