[toc]
# 1. 浮动的圆
# 效果拆解
单体圆的绘制:
Bresenham 并发画圆算法 有了上述并发画圆的方法,我们就可以在每帧绘制一个单体圆。
如果我们想绘制更多的圆,只需要分配更多的 ThreadGroup
, 每个线程负责画一个 (如果想也可以画多个)
圆形如何移动?
只需要在时序上更新圆的中心点即可
如何实现不同圆有不同的匀速?
这就需要用到 SSBO
Shader Storage Buffer Object
, 首先需要知道的是,这个 Buffer 的适配性很差,至少移动端基本不同架构的数量相差巨大,可以说不能用 (? 比如有的只允许 30 个,有的却可以允许 10000 个???),但是作为 Demo 可以用来玩玩 直接类似于 CPU 端一样,在 GPU 上分配每个圆的数据 Circle
1 2 3 4 5 6 7 8 9 struct circle { float2 origin; float2 velocity; float radius; }; StructuredBuffer<circle> circlesBuffer;
然后在 kernel 里使用线程下标来找到线程对应圆的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 [numthreads (32 ,1 ,1 )] void Circles (uint3 id : SV_DispatchThreadID) { int2 center = (int2)(circlesBuffer[id.x].origin + circlesBuffer[id.x].velocity * time); int radius = (int )circlesBuffer[id.x].radius; while (center.x > texResolution) center.x -= texResolution; while (center.x < 0 ) center.x += texResolution; while (center.y > texResolution) center.y -= texResolution; while (center.y < 0 ) center.y += texResolution; drawCircle (center,radius); }
# 2. 瞎转圈的星星
# 效果拆解
从圆心随机一个方向 Vec
然后求出某一个随机圆切面上的一个 正交基
- 随机一个 x 方向 (sinDir)
- 叉积 vec 与 sinDir 得到一个 cosDir 向量,其中 cosDir 和 sinDir 必然互相垂直 ( 其实和vec也是互相垂直
)
- 则 sinDir 和 cosDir 就是对应圆切面上的一组正交基,然后按单位圆的 x/y 轴计算坐标即可
1 2 3 4 5 6 7 8 9 10 11 12 13 [numthreads (64 ,1 ,1 )] void OrbitingStars (uint3 id : SV_DispatchThreadID) { float3 sinDir = normalize (random3 (id.x) - 0.5 ); float3 vec = normalize (random3 (id.x + 7.1393 ) - 0.5 ); float3 cosDir = normalize (cross (sinDir,vec)); float scaledTime = time *0.5 +random (id.x) * 712.131234 ; float3 pos = sinDir * sin (scaledTime) + cosDir * cos (scaledTime); Result[id.x] = pos * 2 ; }
如何同步数据和显示
实际上这里 ComputeShader 只是用来计算位置的 (这玩意完全可以用 Job 代替,因为使用 ComputeShader 还需要回读,回读性能消耗也很大的), 所以 Dispatch 之后,直接 resultBuffer.GetData(目标数组)
即可,其中 resultBuffer 的顶层封装是 ComputeBuffer
, 获取到数据后对 Gameobject 的 Transform 进行赋值 (完全 Demo 做法 XD)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 using System;using System.Collections;using System.Collections.Generic;using Unity.Collections.LowLevel.Unsafe;using UnityEngine;public class OrbitingStars : MonoBehaviour { public int starCount = 17 ; public ComputeShader shader; public GameObject prefab; private ComputeBuffer resultBuffer; private int kernelHandle; private uint threadGroupSizeX; private int groupSizeX; private Vector3[] output; private Transform[] stars; void Start () { kernelHandle = shader.FindKernel("OrbitingStars" ); shader.GetKernelThreadGroupSizes(kernelHandle,out threadGroupSizeX,out _,out _); groupSizeX = (int )((starCount + threadGroupSizeX - 1 ) / threadGroupSizeX); resultBuffer = new ComputeBuffer(starCount, UnsafeUtility.SizeOf<Vector3>()); shader.SetBuffer(kernelHandle,"Result" ,resultBuffer); output = new Vector3[starCount]; stars = new Transform[starCount]; for (int i = 0 ; i < starCount; ++i) { stars[i] = Instantiate(prefab, transform).transform; } } void Update () { shader.SetFloat("time" ,Time.time); shader.Dispatch(kernelHandle,groupSizeX,1 ,1 ); resultBuffer.GetData(output); for (int i = 0 ; i < stars.Length; ++i) { stars[i].localPosition = output[i]; } } private void OnDestroy () { resultBuffer.Dispose(); } }
# 3. 简单 Mesh 变形
计算模型最适合的圆心的位置上 (假如 0.0 在最中心也可以)
计算每个顶点到圆心的方向向量,然后乘上半径
使用时间进行 sin/cos 的循环,然后 lerp
Tips, 个人感觉这种其实用隐式表达 ( 如SDF
) 会更好,因为对于顶点而言的数据量是巨大的,直接上传或者回读对于性能的影响也是巨大的
# 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #pragma kernel CSMain struct Vertex { float3 position; float3 normal; }; RWStructuredBuffer<Vertex> vertexBuffer; StructuredBuffer<Vertex> initialBuffer; float delta;float radius;[numthreads (1 ,1 ,1 )] void CSMain (uint3 id : SV_DispatchThreadID) { float3 initialPos = initialBuffer[id.x].position; float3 s = float3 (normalize (initialPos)*radius*0.01 ); float3 pos = lerp (initialPos,s,delta); float3 initialNormal = initialBuffer[id.x].normal; float3 snormal = normalize (initialPos); float3 norm = lerp (initialNormal,snormal,delta); vertexBuffer[id.x].normal = norm; vertexBuffer[id.x].position = pos; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 using UnityEngine;using System.Collections;using System.Numerics;using Vector3 = UnityEngine.Vector3;public class MeshDeform : MonoBehaviour { public ComputeShader shader; [Range(0.5f, 2.0f) ] public float radius; public struct Vertex { public Vector3 position; public Vector3 normal; public Vertex (Vector3 p, Vector3 n ) { position.x = p.x; position.y = p.y; position.z = p.z; normal.x = n.x; normal.y = n.y; normal.z = n.z; } } int kernelHandle; Mesh mesh; private Vertex[] vertexArray; private Vertex[] initialArray; private ComputeBuffer vertexBuffer; private ComputeBuffer initialBuffer; void Start () { if (InitData()) { InitShader(); } } private bool InitData () { kernelHandle = shader.FindKernel("CSMain" ); MeshFilter mf = GetComponent<MeshFilter>(); if (mf == null ) { Debug.Log("No MeshFilter found" ); return false ; } InitVertexArrays(mf.mesh); InitGPUBuffers(); mesh = mf.mesh; return true ; } private void InitShader () { shader.SetFloat("radius" , radius); } private void InitVertexArrays (Mesh mesh ) { vertexArray = new Vertex[mesh.vertices.Length]; initialArray = new Vertex[mesh.vertices.Length]; for (int i = 0 ; i < vertexArray.Length; ++i) { Vertex v1 = new Vertex(mesh.vertices[i], mesh.normals[i]); vertexArray[i] = v1; Vertex v2 = new Vertex(mesh.vertices[i], mesh.normals[i]); initialArray[i] = v2; } } private void InitGPUBuffers () { vertexBuffer = new ComputeBuffer(vertexArray.Length,sizeof (float ) * 6 ); vertexBuffer.SetData(vertexArray); initialBuffer = new ComputeBuffer(initialArray.Length, sizeof (float ) * 6 ); initialBuffer.SetData(initialArray); shader.SetBuffer(kernelHandle,"vertexBuffer" ,vertexBuffer); shader.SetBuffer(kernelHandle,"initialBuffer" ,initialBuffer); } void GetVerticesFromGPU () { vertexBuffer.GetData(vertexArray); Vector3[] vertices = new Vector3[vertexArray.Length]; Vector3[] normals = new Vector3[vertexArray.Length]; for (int i = 0 ; i < vertexArray.Length; ++i) { vertices[i] = vertexArray[i].position; normals[i] = vertexArray[i].normal; } mesh.vertices = vertices; mesh.normals = normals; } void Update () { if (shader) { shader.SetFloat("radius" , radius); float delta = (Mathf.Sin(Time.time) + 1 )/ 2 ; shader.SetFloat("delta" , delta); shader.Dispatch(kernelHandle, vertexArray.Length, 1 , 1 ); GetVerticesFromGPU(); } } void OnDestroy () { } }