自由学习记录(82)

public static class BitonicSorter
{
    private static void BitonicMerge(int[] arr, int low, int cnt, bool ascending)
    {
        if (cnt <= 1) return;
        int k = cnt / 2;
        for (int i = low; i < low + k; i++)
        {
            bool shouldSwap = (arr[i] > arr[i + k]) == ascending;
            if (shouldSwap)
            {
                int tmp = arr[i];
                arr[i] = arr[i + k];
                arr[i + k] = tmp;
            }
        }
        BitonicMerge(arr, low, k, ascending);
        BitonicMerge(arr, low + k, k, ascending);
    }

    private static void BitonicSortRec(int[] arr, int low, int cnt, bool ascending)
    {
        if (cnt <= 1) return;
        int k = cnt / 2;
        BitonicSortRec(arr, low, k, true);   // 左半升序
        BitonicSortRec(arr, low + k, k, false); // 右半降序
        BitonicMerge(arr, low, cnt, ascending);
    }

    public static int[] BitonicSort(int[] input)
    {
        int n = input.Length;
        if (n == 0) return Array.Empty<int>();

        // 计算补齐后的长度
        int m = 1;
        while (m < n) m <<= 1;

        // 拷贝并补充最大值
        int[] arr = new int[m];
        Array.Copy(input, arr, n);
        for (int i = n; i < m; i++)
            arr[i] = int.MaxValue;

        // 执行排序
        BitonicSortRec(arr, 0, m, true);

        // 裁掉补充部分
        int[] result = new int[n];
        Array.Copy(arr, result, n);
        return result;
    }
}
// class Program
// {
//     static void Main()
//     {
//         int[] data = { 10, 3, 7, 2, 5, 8 };
//         int[] sorted = BitonicSorter.BitonicSort(data);
//         Console.WriteLine(string.Join(", ", sorted));
//     }
// }

懂了,,看jimao,不想看就不看了,,这个东西要gpt都可以慢慢修正出来,,自己了解之后也是和之前那些算法一样会忘了,真正用 的时候还是要ai最高效

里面各种调用逻辑能看懂,但是之后如果不能用上会很不爽

逼出潜力,66

xxxxx

每个实例都跑同一段动作”的关键,不在 Update 的调度,而在 GenerateSkinnedAnimationForGPUBuffer() 这块的“离线烘焙 + 打包”。它把 _Animator 的骨骼形变结果,在时间轴上逐帧烘焙成顶点位置序列,塞进一个 ComputeBuffer,然后材质/顶点着色器按每只 boid 的 frame 去索引同一份序列。核心就是:一次烘焙 → 全体实例共享;逐实例只发“当前帧号/插值因子”

下面把“多出来的事”拆开讲清:

选定动画与时间采样

  • TargetBoidToGPUSkin 上拿 SkinnedMeshRendererAnimator

  • 计算 NbFrames = 2^p(取接近 clip 时长 * 帧率 的二次幂),perFrameTime = clip.length / NbFrames

  • 这保证了帧数是 2 的幂,便于后面用 bitonic/纹理对齐或环绕(也是 GPU 友好)。

逐帧烘焙骨骼网格 → 顶点形变结果

  • 循环 i = 0..NbFrames-1:

    • Animator.Play(..., normalizedTime = sampleTime/clip.length); Animator.Update(0f);

    • SkinnedMeshRenderer.BakeMesh(bakedMesh);

    • 取出 bakedMesh.vertices[j],写到 vertexAnimationData[(j * NbFrames) + i]

BakeMesh 把“骨骼蒙皮后的结果网格”烤成纯顶点阵列(无骨骼计算),你把所有关键帧的顶点位置都存下来,形成一个二维表

  • 维度1:顶点索引 j(固定拓扑)

  • 维度2:帧索引 i(动画时间)

  • 存储布局:以顶点为主序(每个顶点连着 NbFrames 帧),所以索引是 (j * NbFrames + i)这点很重要,决定了着色器如何寻址。

所有实例共用一份动画曲线(同一只“鸟”的动作),但每只 boid 的 frame/t 不同,于是“同谱不同步”。

  • 用 Animator 把 SkinnedMesh 的每一帧烘焙成“纯顶点位置”,打包/上传vertexAnimation buffer;并把 每个顶点的所有帧按连续布局存放(j * NbFrames + i)。

  • 渲染时不再做骨骼蒙皮:实例化网格 + 顶点着色器按帧索引查表还原形变,可选插值。

  • 每个实例如何“用到”这段 AnimationClip:靠 boidBuffer 里的 frame/t 控制“从表里拿哪两帧、插多少”;所以它们都使用同一段动作,只是播放头不同。

小提示 / 易踩点

  • Animator.cullingMode = AlwaysAnimate;(避免未被摄像机看到时不更新采样)。

  • BakeMesh 性能:Editor 下还好,发布时建议缓存数组(bakedMesh.vertices 会分配;可 bakedMesh.GetVertices(tmpList) 复用),烘完一次即可一直用。

  • 法线/切线:当前只烘了位置。若需要正确光照,同法线也要烘(再来一个 buffer),否则用静态法线会破相。

  • 顶点拓扑必须一致:渲染用的 BoidMesh 顶点顺序要和 BakeMesh 时的 sharedMesh 一致。

  • 坐标系:BakeMesh 默认给局部空间顶点;你的实例世界变换应由每只 boid 的 position/direction 施加到 p 上(在 VS 里做)。

  • 内存量:vertexCount * NbFrames * 16B。例如 5k 顶点 * 256 帧 ≈ 20 MB(只位置)。加法线*3 再翻倍。选择合适的 NbFrames。

为什么要这么做

因为这样:

  • GPU 渲染时完全不用跑骨骼蒙皮(没有矩阵乘法、权重混合)

  • 所有实例共用同一份动画数据,只靠 frame 指针去查

  • 每个实例的播放速度/起始时间可以不同

代价就是 显存占用大,而且动画是离散帧(需要插值才平滑)。

核心新增的是一整套“力场/引导点(Affector)”系统,把外部点线数据或鼠标射线变成一堆影响器,丢给 compute 去“推拉”群体;

这份脚本相比上个版本多做了什么

  1. 定义了影响器结构 & 缓冲区

  • GPUBoidAffector { Vector3 position; float force; float distance; }

  • 新建 AffectorBuffer,把所有影响器打包进 ComputeBuffer,在 Update() 里传进 kernel:

_ComputeFlock.SetInt("NbAffectors", NbAffectors);
_ComputeFlock.SetFloat("AffectorForce", AffectorForce);
_ComputeFlock.SetFloat("AffectorDistance", AffectorDistance);
_ComputeFlock.SetBuffer(kernel, "affectorBuffer", AffectorBuffer);
这允许 compute shader 在每只 boid 更新时遍历所有影响器,根据 force/distance 叠加一个引力/斥力(你在 compute 里通常会做:距离权重、衰减、夹紧最大加速度等)。

两种来源生成影响器

  • 路径/文本里读入的点(DrawingAffectors)
    PointsFromData.GeneratePointsFrom(...)TextAsset → 得到点集 → GenerateDrawingAffectors() 把这些点转成 GPUBoidAffector,写入 AffectorBuffer;可选创建小球可视化。

  • 鼠标射线批量撒点(GenerateAffectors)
    Camera.main.ScreenPointToRay(Input.mousePosition) 出一条射线,按 NbAffectorsPerRay 均分步进,把一路上的点都生成为影响器。左/右键(你注释掉了)可以即时添加吸/斥两种力。

把影响器数量和参数传进内核
NbAffectors、全局 AffectorForce/AffectorDistance(或每个affector自己的 force/distance)让 GPU 端能灵活控制作用强度与半径。

简单说:这版在“烘一份公共动画 + 每实例独立播放头”的基础上,又加了一层“外部场”去偏转群体运动。

影响器系统

  • TextAsset 读点 → 经过 Scale / Offset / 旋转 / 反转Y → 生成 GPUBoidAffector 列表(可视化用小球)。

实例渲染用的 BoidMesh 必须与 BoidSMR.sharedMesh 顶点顺序完全一致,否则从 vertexAnimation 取出来的位置会错位。

boidData.direction = rot.eulerAngles; 后续若在 compute/VS 里当“方向向量”用,会出错。建议保存 单位前向四元数(再在 GPU 侧转矩阵/旋转向量)。

GenerateDrawingAffectors/GenerateAffectors 每次都 CreatePrimitive,长时间调试会堆很多球。
建议:用 Gizmos 画点,或复用对象池/在 Editor 下 HideFlags.DontSave 并集中销毁。

AffectorBuffer 的生命周期/重建

  • 你在 Start() 先建了一次(NbAffectorsPerRay, 20),随后 GenerateDrawingAffectorsRelease+重建
    可以延迟到有数据时再建;频繁重建建议做容量管理(只增不减),用 SetData(data, 0, 0, count) 局部更新。

NbFrames = ClosestPowerOfTwo

  • 这是为 GPU 访问对齐/循环友好,但也会“拉伸/压缩”时间采样。必要时可:

    • 保留真实帧数 + 做取模;或

    • 记录 clip.length/realF,让 BoidFrameSpeed 与真实帧率对应。

Camera.main 依赖

  • GenerateAffectors 里直接用 Camera.main,在多相机或剔除 MainCamera 标签的场景下会是 null。记得判空或传入相机引用。

对,就是这个意思。你的脚本里那两个入口——GenerateAffectors(鼠标射线撒点)和 GenerateDrawingAffectors(从文本点集)——会把一串“影响器”(位置+力+半径)塞进 AffectorBuffer,Compute 内核每帧遍历这些影响器,对每只 boid 叠加吸/斥力,所以鼠标点哪儿、力场就往哪儿“拉/推”鸟群。

最短可用做法(把射线交互打开):

// Update() 里:取消注释
if (Input.GetMouseButtonDown(0)) GenerateAffectors(-2, 2); // 左键:吸引
if (Input.GetMouseButtonDown(1)) GenerateAffectors( 2, 2); // 右键:排斥

Compute 里常见力叠加示例(核心逻辑):

// 伪代码,假设已有 boid.pos、boid.vel
for (uint a = 0; a < NbAffectors; ++a) {
    float3 toA = aff[a].pos - boid.pos;
    float d = length(toA);
    if (d < aff[a].distance) {
        float w = 1 - d / aff[a].distance; // 近强远弱
        float3 dir = (d > 1e-4) ? toA / d : 0;
        boid.vel += dir * aff[a].force * w * _DeltaTime;
    }
}

  • 别让影响器太多:这是 O(Boids×Affectors),量大要做采样/网格或八叉树再加速。

  • 确保 CPU/GPU 结构对齐一致AffectorBuffer 建议用 32 字节对齐(HLSL 里用两个 float4 打包),否则读出来会乱。

  • AffectorForce 正负决定吸/斥;AffectorDistance 是影响半径。

“最终版”在你上一版基础上,加了“可选多来源影响器系统 + 更稳的内存对齐 + 更完整的播放/渲染管线整理”

影响器来源更丰富

  • UseAffectors 总开关。

  • UseMeshAffectors:可直接用 MeshAffectors.vertices 生成影响器(点云→力场)。

  • 仍支持从 TextAsset(路径点)生成影响器:GenerateDrawingAffectors(...)

  • 影响器结构升级为 GPUAffectorposition, force, distance, axis, padding(支持按轴/全轴作用、对齐更安全)。

结构/内存与稳健性改动

  • ComputeBuffer stride 不再硬编码

    • BoidBuffer = new ComputeBuffer(BoidsCount, Marshal.SizeOf(typeof(GPUBoid)));

    • AffectorBuffer = new ComputeBuffer(NbAffectors, Marshal.SizeOf(typeof(GPUAffector)));

    • VertexAnimationBuffer = new ComputeBuffer(vertexCount * NbFramesInAnimation, Marshal.SizeOf(typeof(Vector4)));

  • 数据结构更合理

    • GPUBoid 增加 size(每实例缩放),总大小= 2×Vector3(24B) + 6×float(24B) = 48B(合逻辑且紧凑)。

    • GPUAffector 通过 axis + Vector2 padding 对齐到 32B,匹配 HLSL 16 字节对齐习惯,避免之前 20B 跨端错读的隐患。

  • 材质实例化

    • BoidMaterial = new Material(BoidMaterial); 避免改到原材质(全局副作用)。

动画烘焙与回退

  • 无 Clip 的回退路径

    • _AnimationClip == null 时走 CreateOneFrameAnimationData():把静态网格顶点当作 1 帧动画上传,保证管线照样跑起来。

  • 帧数据更稳

    • NbFramesInAnimation 代替旧变量名;使用 Marshal.SizeOf(Vector4) 明确 16B 单元。

    • 仍是顶点主序布局:vertexAnimation[j * NbFrames + i] = pos

渲染/调度整理

  • 数据下发集中化

    • SetComputeData()SetMaterialData() 封装参数下发;#if UNITY_EDITOR 下每帧更新,构建版只在 Start 设置(少改少传,省 CPU)。

  • 渲染时序调整

    • Update()Dispatch + GL.Flush()(确保在本帧绘制前完成计算)。

    • LateUpdate()DrawMeshInstancedIndirect(...)(保证计算→渲染顺序明确)。

  • 大包围盒统一管理

    • InfiniteBounds 替代魔法数字,避免剔除。

参数/行为上的新增

  • 邻居检查抽样StepBoidCheckNeighbours 传进 compute,用于降低邻居遍历频率(性能旋钮)。

  • 实例尺寸GPUBoid.size(你可在 VS 中按实例缩放顶点)。

要点了:别直接改方向,而是用“力→加速度→速度→位置”的链路,让方向由速度推导,同时对“力/角速度/速度”做上限和衰减,这样既有“力的质感”,又不会一吸就卡死。

  • float3 pos;

  • float3 vel; ← 方向由它决定:dir = normalize(vel)

  • (可选)float3 angVel; 或只用“最大转向角速度”限制

力的组成(示例配方)

F_total =  w_sep*F_separation + w_align*F_alignment + w_coh*F_cohesion
         + w_field*F_affectors - c_damp*vel;

F_affectors:对每个影响器,用弹簧-阻尼(spring-damper)+ 距离衰减

toA = A.pos - pos;  d = length(toA);  dir = toA / (d+1e-6);
float R = A.distance; // 影响半径
float s = saturate(1 - d / R);        // 近强远弱 (0..1)
// 轴向限制(如果你有 axis:0=全轴, 1=X, 2=Y, 3=Z)
dir = apply_axis_mask(dir, A.axis);

// 弹簧力(到中心)+ 阻尼(沿径向)
F_radial = A.force * pow(s, p) * dir;         // p∈[1,3] 形状因子
v_radial = dot(vel, dir) * dir;
F_damp   = -c_radial * v_radial;              // 防穿透/抖动

// 反“吸死”小技巧:加入一点切向力,让轨迹绕行而非直冲
tangential = normalize(cross(dir, float3(0,1,0))); // 或基于法线场
F_tan = beta * pow(s, q) * tangential;        // beta≈(0.2~1), q≈1

F_affector += F_radial + F_damp + F_tan;

想要“靠近时减速”:这是“Arrive”行为的物理版,靠 sF_damp 自动刹车。
想要“只吸不斥”或“只斥不吸”,直接限定 A.force 的正负或把 max(F_radial,0) 之类。

xxxx

看的很歪,也别看了,,说明不是你要处理的问题了

要做的话肯定要自己写一个物理的系统,在compute shader里面可以用的那种计算模式,,要的只是高级的封装而已

一个力作为一个结构体

方向,大小,是否xxxx每个boid受到的力不同,,力的计算放在computebuffer,,

  • RP Asset = 渲染总闸:决定渲染路径、阴影、灯光、Post、体积、Adaptive Performance 等,团队用它做“全局画质/性能预设”。你看到的 Balanced/HighFidelity/Performant 就是典型质量档的资产。Unity Documentation

  • 项目级常规做法:在 Project Settings/Quality 里给每个质量档分配各自的 URP Asset(比如手机用 Performant,PC/主机用 Balanced/HighFidelity),并在 Renderer Feature(如 Decal、SSAO)层面按需开关。Niantic SDK for Unity CommunityUnity Discussions

  • 性能与取舍:官方手册直接给了 URP 的性能建议(减少附加光、深度纹理、内存等),这就是你之后做“雪地留痕”时要对着检查的清单。Unity Documentation

https://www.youtube.com/watch?v=Eua7G7Vct0A

粒子系统

VFX是“视觉特效(Visual Effects)”的统称;在 Unity 里常说的 VFX Graph 是一套“基于节点、在 GPU 上模拟”的特效工具。它和传统 粒子系统(Shuriken) 不是一回事。

二者核心差异

  • 工作流:VFX Graph 用节点图搭效果;粒子系统用 Inspector 的各模块(Emission/Shape/Collision…)。Unity Documentation+1

  • 运行位置:VFX Graph在 GPU 上模拟(需要 Compute/SSBO 能力);粒子系统在 CPU 上模拟、GPU 渲染。Unity Documentation+1

  • 规模与性能:VFX Graph适合超大量粒子与复杂流程;粒子系统更轻巧、易上手、兼容性好。UnityUnity Documentation

  • 管线支持:VFX Graph 现已支持 URP/HDRP,并在新版本加入 URP 的 Lit 输出等特性;粒子系统在各管线都能用(用对应管线的粒子着色器)。Unity User ManualUnity Documentation

  • 平台限制:VFX Graph要求支持 Compute/SSBO;对不支持这些特性的设备/图形 API(如部分移动与 OpenGL ES)受限。Unity Documentation

不完全一样,但工作流“思路”基本一致。VFX Graph 的节点范式(Spawner→Update→Output)、在场景里用 Visual Effect 组件挂载、参数驱动等,在 URP 和 HDRP 下是同一套;唯独“可用的输出/着色能力、平台支持”因管线不同而有差别Unity Documentation

一致的部分

不同点(URP vs HDRP)

  • 输出/功能覆盖:HDRP 与 URP 都支持 VFX Graph,但可用的输出类型与着色目标略有不同;例如 Decal 输出早期仅在 HDRP 提供,Unity 6(VFX Graph 17.x)开始 URP 也有 URP Lit Decal 输出Unity Documentation+2Unity Documentation+2

  • 着色模型差异:官方对比表明确,URP 与 HDRP 在 VFX Shader Graph 的支持目标不同(HDRP 额外有 Hair/Fabric 等;两者都支持 Lit/Unlit,但细节能力不完全等同)。Unity Documentation

  • 平台/API 支持:两条管线下的 VFX Graph 都要求“支持 Compute 的硬件”,并且 OpenGL ES 不支持;URP 过去某些版本只有 Unlit 粒子,后来逐步补齐。Unity Documentation+1

结论怎么用

  • 心智模型可以通用:你在 HDRP/URP 里搭 VFX Graph 的“使用逻辑”基本一样。

  • 落地前要查该管线当前版本可用的输出与限制(比如是否有 Lit/Decal 输出、2D 相关支持等),再挑对的 Output Context/Shader 目标。Unity Documentation+1

[Unity6][URP17] Renderer Feature 入门指南_哔哩哔哩_bilibili

这个资产里还维护一个 Renderer List(渲染器列表),相机会从这里挑“用哪个 Renderer”。

Scriptable Renderer Feature(可编程渲染器特性)

本质是一个 ScriptableObject,继承 ScriptableRendererFeature,用它把你的 ScriptableRenderPass 注入到 URP 的渲染流程里。

项目统一提质/降级:改 URP Asset(比如切 HighFidelity / Performant;开关深度纹理、不透明纹理、阴影质量)。某类相机/某个绘制流程变了:做或切换 Renderer 资产(Universal Renderer/2D Renderer…)具体效果/后处理/自定义绘制:在该 Renderer 下添加或编写 Renderer Feature,把自定义 Pass 插到合适的阶段

https://www.youtube.com/watch?v=GAh225QNpm0

unity call this every  frame 

nextt enqueue the pass

a shader cannot write to a render target
handle until it has been given a texture

but a render target identifier is ready
to go by definition

https://www.youtube.com/watch?v=MLl4yzaYMBY

Experienced Unity developers will know,
using Built-in you would have added as a
Graphics.Blit using OnRenderlmage

We'll use the version of the function
that uses a material to process
each pixel in the image

RenderingUtils.ReAllocateIfNeeded(ref temp, desc, …) 是管理 RTHandle 的惯用法,避免每帧创建/销毁 RT。

cameraColorTargetHandle 提供了相机颜色 RT 的 RTHandle,直接拿来当源/目的即可;官方“全屏 Blit 示例”就是这么做的。

Pass 需要相机颜色/深度/法线时,记得 ConfigureInput(ScriptableRenderPassInput.Color | Depth | Normal);否则这些纹理可能不会生成。

能用内置就用内置:如果只是做全屏后处理,不想写代码,直接用 Full Screen Pass Renderer Feature,把你的全屏 Shader 塞进去、选注入点即可。

URP blit best practices | Universal RP | 14.0.8

A blit operation is a process of copying a source texture to a destination texture.

This page provides an overview of different ways to perform a blit operation in URP and best practices to follow when writing custom render passes.

Avoid using the CommandBuffer.Blit API in URP projects.

The CommandBuffer.Blit API is the legacy API. It implicitly runs extra operations related to changing states, binding textures, and setting render targets. Those operations happen under the hood in SRP projects and are not transparent to the user.

XR(扩展现实)是一个总括性的术语,包含了增强现实(AR)、虚拟现实(VR)和混合现实(MR)等多种沉浸式技术。VR 是完全沉浸在由计算机生成的虚拟环境中,而AR 是将数字元素叠加到现实世界中,MR 则是将虚拟和现实元素结合在一起,并允许两者进行交互。

新版 URP(14+)开始不推荐 cmd.Blit,而改用 Blitter + RTHandle;你现在用 12.1 可以继续老写法,但未来升级要迁移(官方在 14.x 文档明确说明了原因与替代 API)。

https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal%4014.0/manual/urp-universal-renderer.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值