在Unity中使用ComputeShader

目前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运算的群集演示动画了
在这里插入图片描述

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值