Unity Shader学习:CPU/GPU boid
参考:https://github.com/chenjd/Unity-Boids-Behavior-on-GPGPU
https://www.youtube.com/watch?v=bqtqltqcQhw
CPU:加入了射线碰撞
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CPUBoid : MonoBehaviour {
public Transform[] centers;
public Transform[] boids;
public float speed = 1f;
public float nearbyDis = 1f;
private float tempDis;
private Vector3 separation;
private Vector3 alignment;
private Vector3 cohesion;
private Vector3 tempCohesion;
private Vector3 direction;
private int nearbyCount;
private Vector3 toCenter;
private Quaternion tempRotation;
private RaycastHit hit;
private Ray ray;
private Vector3 tempDir;
private bool isHit=false;
private Transform center;
void Start () {
getColliderDirections();
center = centers[0];
}
void LateUpdate () {
for (int i = 0; i < boids.Length; i++)
{
separation = Vector3.zero;
alignment = Vector3.zero;
cohesion = Vector3.zero;
tempCohesion = Vector3.zero;
nearbyCount = 0;
toCenter = Vector3.Normalize(center.position - boids[i].position);
for (int j = 0; j < boids.Length; j++)
{
if (i!=j)
{
tempDis = Vector3.Distance(boids[i].position, boids[j].position);
if (tempDis<nearbyDis&&Vector3.Dot(Vector3.Normalize( boids[j].position- boids[i].position), boids[i].forward)>-0.5f)
{
separation += boids[i].position - boids[j].position;
alignment += boids[j].forward;
tempCohesion+= boids[j].position;
nearbyCount++;
}
}
}
if (nearbyCount>0)
{
alignment *= 1 / nearbyCount;
tempCohesion *= 1 / nearbyCount;
}
cohesion += tempCohesion;
direction = alignment + separation + Vector3.Normalize(cohesion - boids[i].position);
Vector3 t = toCenter;
Vector3 tt= Vector3.Lerp(boids[i].forward, direction, Time.deltaTime);
t = Vector3.Lerp(t, tt, 0.95f);
ray = new Ray(boids[i].position, t);
if (Physics.SphereCast(ray, 1f, 1f))
{
isHit = true;
for (int k = 0; k < directions.Length; k++)
{
tempDir = boids[i].TransformDirection(directions[k]);
ray = new Ray(boids[i].position, tempDir);
if (!Physics.SphereCast(ray, 1f, 3f))
{
tempDir = boids[i].TransformDirection(directions[k]);
break;
}
}
}
if (isHit)
{
isHit = false;
t = Vector3.Lerp(t,tempDir,1f);
}
boids[i].rotation = Quaternion.LookRotation(t,boids[i].up);
boids[i].position += speed * t * Time.deltaTime;
}
}
private int numViewDirections = 300;
private Vector3[] directions;
void getColliderDirections()
{
directions = new Vector3[numViewDirections];
float goldenRatio = (1 + Mathf.Sqrt(5)) / 2;
float angleIncrement = Mathf.PI * 2 * goldenRatio;
for (int i = 0; i < numViewDirections; i++)
{
float t = (float)i / numViewDirections;
float inclination = Mathf.Acos(1 - 2 * t);
float azimuth = angleIncrement * i;
float x = Mathf.Sin(inclination) * Mathf.Cos(azimuth);
float y = Mathf.Sin(inclination) * Mathf.Sin(azimuth);
float z = Mathf.Cos(inclination);
directions[i] = new Vector3(x, y, z);
}
}
}
GPU:没有碰撞,基于compute shader,需要构建旋转矩阵
c#部分:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public struct BoidData
{
public Vector3 pos;
public Vector3 rot;
}
public class ZzcGPUBoid : MonoBehaviour {
public ComputeShader computeShader;
public int boidsCount;
public float nearbyDis;
public float flockSpeed;
public Material boidMat;
public Mesh boidMesh;
public Transform target;
private int kernelIndex;
private BoidData[] boidData;
private ComputeBuffer Buffer;
private int stride;
private ComputeBuffer positionBuffer;
private ComputeBuffer argsBuffer;
private uint[] args = new uint[5] { 0, 0, 0, 0, 0 };
private int subMeshIndex = 0;
private int cachedInstanceCount = -1;
private int cachedSubMeshIndex = -1;
private Bounds bounds = new Bounds(Vector3.zero, Vector3.one * 100f);
// Use this for initialization
void Start () {
int vector3Stride= sizeof(float) * 3;
stride = vector3Stride * 2;
Buffer = new ComputeBuffer(boidsCount, stride);
kernelIndex = computeShader.FindKernel("CSMain");
boidData = new BoidData[boidsCount];
for (int i = 0; i < boidData.Length; i++)
{
boidData[i] = new BoidData();
boidData[i].pos = Vector3.right * i;
boidData[i].rot = Vector3.Normalize( new Vector3(Random.Range(0f,1f), Random.Range(0f, 1f), Random.Range(0f, 1f)));
}
computeShader.SetFloat("nearbyDis", nearbyDis);
computeShader.SetFloat("speed", flockSpeed);
computeShader.SetInt("boidsCount", boidsCount);
Buffer.SetData(boidData);
computeShader.SetBuffer(kernelIndex, "boidBuffer", Buffer);
boidMat.SetBuffer("positionBuffer", Buffer);
argsBuffer = new ComputeBuffer(1, args.Length * sizeof(uint), ComputeBufferType.IndirectArguments);
UpdateBuffers();
}
void Update () {
if (cachedInstanceCount != boidsCount || cachedSubMeshIndex != subMeshIndex)
UpdateBuffers();
computeShader.SetFloat("deltaTime", Time.deltaTime);
computeShader.SetVector("flockPos", target.position);
computeShader.Dispatch(kernelIndex, boidsCount, 1, 1);
Graphics.DrawMeshInstancedIndirect(boidMesh, subMeshIndex, boidMat, bounds, argsBuffer);
}
void UpdateBuffers()
{
if (boidMesh!=null)
{
subMeshIndex = Mathf.Clamp(subMeshIndex, 0, boidMesh.subMeshCount - 1);
}
if (boidMesh!=null)
{
args[0] = (uint)boidMesh.GetIndexCount(subMeshIndex);
args[1] = (uint)boidsCount;
args[2] = (uint)boidMesh.GetIndexStart(subMeshIndex);
args[3] = (uint)boidMesh.GetBaseVertex(subMeshIndex);
}
else
{
args[0] = 0;
args[1] = 0;
args[2] = 0;
args[3] = 0;
}
argsBuffer.SetData(args);
cachedInstanceCount = boidsCount;
cachedSubMeshIndex = subMeshIndex;
}
private void OnDisable()
{
if (positionBuffer!=null)
{
positionBuffer.Release();
}
positionBuffer = null;
if (argsBuffer!=null)
{
argsBuffer.Release();
}
argsBuffer = null;
Buffer.Release();
}
}
compute shader部分:
#pragma kernel CSMain
struct Boid {
float3 pos;
float3 rot;
};
RWStructuredBuffer<Boid> boidBuffer;
float deltaTime;
float speed;
float nearbyDis;
int boidsCount;
float3 flockPos;
[numthreads(128,1,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
Boid boid = boidBuffer[id.x];
float3 pos = boid.pos;
float3 rot = boid.rot;
float3 separation = float3(0, 0, 0);
float3 alignment = float3(0, 0, 0);
float3 cohesion = flockPos;
float3 tempCohesion = float3(0, 0, 0);
float tempSpeed = 0;
int nearbyCount = 0;
[loop]
for (int i = 0; i < int(boidsCount); i++)
{
if (i!=int(id.x))
{
Boid tempBoid = boidBuffer[i];
if (length(boid.pos-tempBoid.pos)<nearbyDis)
{
separation += boid.pos - tempBoid.pos;
alignment += tempBoid.rot;
tempCohesion += tempBoid.pos;
nearbyCount++;
}
}
}
if (nearbyCount>0)
{
alignment *= 1 / nearbyCount;
tempCohesion *= 1 / nearbyCount;
}
cohesion += tempCohesion;
float3 direction = alignment + separation + normalize(cohesion - boid.pos);
boid.rot = lerp(boid.rot, normalize(direction), deltaTime*4);
boid.rot = normalize(boid.rot);
boid.pos += boid.rot*speed*deltaTime;
boidBuffer[id.x] = boid;
}
着色shader:
Shader "Instanced/InstancedShader" {
Properties{
_MainTex("Albedo (RGB)", 2D) = "white" {}
}
SubShader{
Pass {
Tags {"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma target 4.5
#include "UnityCG.cginc"
#include "UnityLightingCommon.cginc"
#include "AutoLight.cginc"
sampler2D _MainTex;
struct Boid {
float3 pos;
float3 rot;
};
#if SHADER_TARGET >= 45
StructuredBuffer<Boid> positionBuffer;
#endif
struct v2f
{
float4 pos : SV_POSITION;
float2 uv_MainTex : TEXCOORD0;
float3 normal:NORMAL;
};
float3x3 rotateMatrix(float angle, float3 axis) {
float u0 = axis.x;
float u1 = axis.y;
float u2 = axis.z;
float3x3 r = float3x3(cos(angle) + u0 * u0*(1 - cos(angle)),
u0*u1*(1 - cos(angle) - u2 * sin(angle)),
u1*sin(angle) + u0 * u2*(1 - cos(angle)),
u2*sin(angle) + u0 * u1*(1 - cos(angle)),
cos(angle) + u1 * u1*(1 - cos(angle)),
-u0 * sin(angle) + u1 * u2*(1 - cos(angle)),
-u1 * sin(angle) + u0 * u2*(1 - cos(angle)),
u0*sin(angle) + u1 * u2*(1 - cos(angle)),
cos(angle) + u2 * u2*(1 - cos(angle))
);
return r;
}
float3x3 calculationRotateMatrix(float3 vectorBefore, float3 vectorAfter) {
float3 rotationAxis = cross(vectorBefore, vectorAfter);
float rotationAngle=acos(dot(vectorBefore,vectorAfter)) ;
return rotateMatrix(rotationAngle,rotationAxis);
}
v2f vert(appdata_full v, uint instanceID : SV_InstanceID)
{
#if SHADER_TARGET >= 45
float3 data = positionBuffer[instanceID].pos.xyz;
#else
float3 data = 0;
#endif
float3x3 rotMatrix = calculationRotateMatrix(float3(0, 0, 1), positionBuffer[instanceID].rot.xyz);
v.vertex.xyz = mul(rotMatrix,v.vertex.xyz);
float3 worldPosition = data.xyz+ v.vertex.xyz;
v2f o;
o.pos = mul(UNITY_MATRIX_VP, float4(worldPosition, 1.0f));
o.uv_MainTex = v.texcoord;
o.normal = v.normal;
return o;
}
fixed4 frag(v2f i) : SV_Target
{
return saturate(dot( i.normal , _WorldSpaceLightPos0.xyz ))*0.5+0.5 ;
}
ENDCG
}
}
}