Unity Shader学习:CPU/GPU boid

本文档探讨了使用Unity Shader在CPU和GPU上实现Boid行为的学习过程。CPU实现中包含了射线碰撞检测,而GPU实现则是通过compute shader完成,不包含碰撞检测,需要构建旋转矩阵。文章提供了相关的GitHub资源和YouTube视频教程链接作为参考。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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
			}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值