目前Unity中有两种并行运算的方式
1)C# Job System,在CPU上以多线程的方式并行运算,通常用来处理逻辑相关内容
2)Compute Shader,从DirectX11、OpenGL4.3之后开始支持的技术,使用GPU来进行并行运算,而且可直接将运算结果传入VS、PS,通常用来处理图形相关内容
参考:
https://catlikecoding.com/unity/tutorials/basics/compute-shaders/
先实现一个用CPU单线程的方式模拟群集运算的场合,然后再将其移植到Compute Shader中
在Unity中创建一个StandardSurfaceShader:NewSurfaceShader.shader,简单的将3d坐标值转换成rgb,颜色值对应坐标值,分布在-1,1之间
Shader "Custom/NewSurfaceShader"
{
Properties
{
_Glossiness ("Smoothness", Range(0,1)) = 0.5
_Metallic ("Metallic", Range(0,1)) = 0.0
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Standard fullforwardshadows
#pragma target 3.0
struct Input
{
float3 worldPos;
};
half _Glossiness;
half _Metallic;
UNITY_INSTANCING_BUFFER_START(Props)
UNITY_INSTANCING_BUFFER_END(Props)
void surf (Input IN, inout SurfaceOutputStandard o)
{
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Albedo.rgb = IN.worldPos.xyz * 0.5f + 0.5f;
o.Alpha = 1;
}
ENDCG
}
FallBack "Diffuse"
}
创建材质New Material,并设置使用Custem/NewSurfaceShader
新建场景,新建一个Cube物体,并使用New Material,再将这个物体拖入Project/Assets,创建Cube.prefab
新建CShape脚本NewBehaviourScript,用来实现集群运算,并用Cube显示出来(有点类似手动实现粒子系统)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public delegate Vector3 GraphFuncion(float u, float v, float t);
public class NewBehaviourScript : MonoBehaviour
{
public Transform pointTransform;
[Range(10, 200)]
public int resolution = 200;
Transform[] points;
public enum GraphFunctionName
{
Sine, Sine2D, MultiSine, MultiSine2D, Ripple, Cylinder, Sphere, Torus
}
public GraphFunctionName functionName;
static GraphFuncion[] functions = { SineFunction, Sine2DFunction, MultiSineFunction, MultiSine2DFunction, Ripple, Cylinder, Sphere, Torus };
private void Awake()
{
float step = 2f / resolution;
Vector3 scale = Vector3.one * step;
points = new Transform[resolution * resolution];
for (int i = 0; i < points.Length; i++)
{
Transform point = Instantiate(pointTransform);
point.localScale = scale;
point.SetParent(transform);
points[i] = point;
}
}
private void Update()
{
float t = Time.time;
GraphFuncion f = functions[(int)functionName];
float step = 2f / resolution;
for (int i = 0, z = 0; z < resolution; z++)
{
float v = (z + 0.5f) * step - 1f;
for (int x = 0; x < resolution; x++, i++)
{
float u = (x + 0.5f) * step - 1f;
points[i].localPosition = f(u, v, t);
}
}
}
static Vector3 SineFunction(float x, float z, float t)
{
Vector3 p;
p.x = x;
p.y = Mathf.Sin(Mathf.PI * (x + t));
p.z = z;
return p;
}
static Vector3 Sine2DFunction(float x, float z, float t)
{
Vector3 p;
p.x = x;
p.y = Mathf.Sin(Mathf.PI * (x + t));
p.y += Mathf.Sin(Mathf.PI * (z + t));
p.y *= 0.5f;
p.z = z;
return p;
}
static Vector3 MultiSineFunction(float x, float z, float t)
{
Vector3 p;
p.x = x;
p.y = Mathf.Sin(Mathf.PI * (x + t));
p.y += Mathf.Sin(Mathf.PI * (x + t * 2f) * 2f) / 2f;
p.y *= 2f / 3f;
p.z = z;
return p;
}
static Vector3 MultiSine2DFunction(float x, float z, float t)
{
Vector3 p;
p.x = x;
p.y = 4 * Mathf.Sin(Mathf.PI * (x + z + t * 0.5f));
p.y += Mathf.Sin(Mathf.PI * (x + t));
p.y += Mathf.Sin(Mathf.PI * (z + 2f * t) * 2f) * 0.5f;
p.y *= 1f / 5.5f;
p.z = z;
return p;
}
static Vector3 Ripple(float x, float z, float t)
{
Vector3 p;
float d = Mathf.Sqrt(x * x + z * z);
p.x = x;
p.y = Mathf.Sin(Mathf.PI * (d * 4f - t));
p.y /= 1f + 10f * d;
p.z = z;
return p;
}
static Vector3 Cylinder(float u, float v, float t)
{
Vector3 p;
float r = 0.8f + Mathf.Sin(Mathf.PI * (6f * u + 2f * v + t)) * 0.2f;
p.x = r * Mathf.Sin(Mathf.PI * u);
p.y = v;
p.z = r * Mathf.Cos(Mathf.PI * u);
return p;
}
static Vector3 Sphere(float u, float v, float t)
{
Vector3 p;
float r = 0.8f + Mathf.Sin(Mathf.PI * (6f * u + t)) * 0.1f;
r += Mathf.Sin(Mathf.PI * (4f * v + t)) * 0.1f;
float s = r * Mathf.Cos(Mathf.PI * v * 0.5f);
p.x = s * Mathf.Sin(Mathf.PI * u);
p.y = r * Mathf.Sin(Mathf.PI * v * 0.5f);
p.z = s * Mathf.Cos(Mathf.PI * u);
return p;
}
static Vector3 Torus(float u, float v, float t)
{
float r1 = 0.7f + 0.1f * Mathf.Sin(Mathf.PI * (6f * u + 0.5f * t));
float r2 = 0.15f + 0.05f * Mathf.Sin(Mathf.PI * (8f * u + 4f * v + 2f * t));
float s = r1 + r2 * Mathf.Cos(Mathf.PI * v);
Vector3 p;
p.x = s * Mathf.Sin(Mathf.PI * u);
p.y = r2 * Mathf.Sin(Mathf.PI * v);
p.z = s * Mathf.Cos(Mathf.PI * u);
return p;
}
}
新建场景,新建Empty Object,将NewBehaviourScript挂载到这个物体上,并设置Point Transform参数为之前创建的Cube.prefab
运行,就能看到一个集群物体演示动画
这里如果,Resolution设的很大,就会发现动画很卡,通过Profile,可知Player Loop中脚本NewBehaviourScript.Update占用了大量时间,这就需要考虑使用ComputeShader来优化了。
创建一个ComputeShader:NewComputeShader.compute
// Each #kernel tells which function to compile; you can have many kernels
#pragma kernel CSMain
#define PI 3.14159265358979323846
// Create a RenderTexture with enableRandomWrite flag and set it
// with cs.SetTexture
RWTexture2D<float4> Result;
RWStructuredBuffer<float3> _Positions;
float _Step, _Time;
uint _Resolution;
float2 GetUV(uint3 id)
{
return (id.xy + 0.5) * _Step - 1.0;
}
void SetPosition(uint3 id, float3 position)
{
if (id.x < _Resolution && id.y < _Resolution)
{
_Positions[id.x + id.y * _Resolution] = position;
}
}
float3 Wave(float u, float v, float t)
{
float3 p;
p.x = u;
p.y = sin(PI * (u + v + t));
p.z = v;
return p;
}
[numthreads(8,8,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
// TODO: insert actual code here!
float2 uv = GetUV(id);
SetPosition(id, Wave(uv.x, uv.y, _Time));
}
这里numthreads:8、8、1,表示最大并行线程数为64个(单组)
创建一个StandardSurfaceShader:NewSurfaceShader 1.shader(注意,这里加了个1,防止和前面的shader文件重名),用来处理之前ComputeShader传出的数组RWStructuredBuffer _Positions
Shader "Custom/NewSurfaceShader 1"
{
Properties
{
_Smoothness("Smoothness", Range(0, 1)) = 0.5
}
SubShader
{
CGPROGRAM
// Physically based Standard lighting model, and enable shadows on all light types
#pragma surface ConfigureSurface Standard fullforwardshadows addshadow
#pragma instancing_options procedural:ConfigureProcedural
#pragma editor_sync_compilation
struct Input
{
float3 worldPos;
};
half _Smoothness;
#if defined(UNITY_PROCEDURAL_INSTANCING_ENABLED)
StructuredBuffer<float3> _Positions;
#endif
float2 _Scale;
void ConfigureProcedural()
{
#if defined(UNITY_PROCEDURAL_INSTANCING_ENABLED)
float3 position = _Positions[unity_InstanceID];
unity_ObjectToWorld = 0.0f;
unity_ObjectToWorld._m03_m13_m23_m33 = float4(position, 1.0f);
unity_ObjectToWorld._m00_m11_m22 = _Scale.x;
unity_WorldToObject = 0.0f;
unity_WorldToObject._m03_m13_m23_m33 = float4(-position, 1.0f);
unity_WorldToObject._m00_m11_m22 = _Scale.y;
#endif
}
void ConfigureSurface (Input input, inout SurfaceOutputStandard surface)
{
surface.Albedo = saturate(input.worldPos * 0.5 + 0.5);
surface.Smoothness = _Smoothness;
}
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 4.5
ENDCG
}
FallBack "Diffuse"
}
新建一个材质:New Material 1.mat,设置使用Custom/NewSurfaceShader 1,并开启Enable GPU Instance
再新建一个CShape脚本:NewBehaviourScript1.cs,实现ComputeShader进行群集运算
注意这里的ComputeBuffer positionsBuffer的大小是resolution * resolution,所以computeShader.Dispatch需要分多组分发,resolution / 8 然后向上取整
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NewBehaviourScript1 : MonoBehaviour
{
// public Transform pointTransform;
[SerializeField, Range(10, 200)]
public int resolution = 200;
// Transform[] points;
ComputeBuffer positionsBuffer;
[SerializeField]
ComputeShader computeShader = default;
static readonly int
positionsId = Shader.PropertyToID("_Positions"),
resolutionId = Shader.PropertyToID("_Resolution"),
scaleId = Shader.PropertyToID("_Scale"),
stepId = Shader.PropertyToID("_Step"),
timeId = Shader.PropertyToID("_Time");
[SerializeField]
Material material = default;
[SerializeField]
Mesh mesh = default;
private void OnEnable()
{
positionsBuffer = new ComputeBuffer(resolution * resolution, 3 * 4);
}
private void OnDisable()
{
positionsBuffer.Release();
positionsBuffer = null;
}
void UpdateFunctionOnGPU()
{
float step = 2f / resolution;
computeShader.SetInt(resolutionId, resolution);
computeShader.SetFloat(stepId, step);
computeShader.SetFloat(timeId, Time.time);
computeShader.SetBuffer(0, positionsId, positionsBuffer);
int groups = Mathf.CeilToInt(resolution / 8f);
computeShader.Dispatch(0, groups, groups, 1);
material.SetBuffer(positionsId, positionsBuffer);
material.SetVector(scaleId, new Vector4(step, 1f / step));
var bounds = new Bounds(Vector3.zero, Vector3.one * (2f + 2f / resolution));
Graphics.DrawMeshInstancedProcedural(mesh, 0, material, bounds, positionsBuffer.count);
}
private void Update()
{
UpdateFunctionOnGPU();
}
}
新建场景,新建Empty Object,挂载NewBehaviourScript1.cs,设置参数
再运行,就能看到由GPU运算的群集演示动画了