找回密码
 立即注册
查看: 227|回复: 0

ComputeShader(二)

[复制链接]
发表于 2022-9-23 12:00 | 显示全部楼层 |阅读模式
总结上一篇笔记所做的工作:

  • 建立一个positionBuffer缓冲区;
  • 写好ComputeShader的内核函数和其他相关函数;
  • 在GPUGraph脚本中调用procedural draw.
目前所有Point的position都已经由GPU计算好了,但是进入play Mode后会发现所有屏幕中只有一个被渲染的立方体,因为所有的立方体都重叠在一起了。此时由于几乎没有数据需要从CPU发送到GPU,所以性能相比于之前有很大的提升。
接下来复制之前的PointSurface重命名为PointSurfaceGPU,改写如下:
Shader "Graph/Point Surface GPU"
{
    Properties
    {
        _Smoothness("Smoothness", Range(0, 1)) = 0.5
    }
    SubShader
    {
        CGPROGRAM
        //下面的ConfigureProcedural函数只会在常规绘制中调用,为了在阴影中也调用,我们需要增加一个
        //自己定义的阴影通道 addshadow
        #pragma surface ConfigureSurface Standard fullforwardshadows addshadow
        //类似于GPU instancing,需要由pragma instancing_options指令指示,
        //这表示表面着色器需要为每一个顶点调用一个函数,因此增加一个void ConfigureProcedural函数
        //assumeuniformscaling用于命令Unity假设所有实例具有统一的缩放
        #pragma instancing_options assumeuniformscaling procedural:ConfigureProcedural  
        //shader在editor模式编译是异步的,(为了方便修改),在编译完成前会用一个青蓝色的代替shader
        //暂时代替,通常情况下没什么问题,但是代替shader不会使用程序化着色,当我们一次性渲染一百万
        //个点的时候,可能会造成unity和操作系统崩溃。我们可以关闭异步shader功能,但是这仅仅是目前这
        //一个shader的问题,我们可以添加 #pragma editor_sync_compilation 指令强制Unity暂缓一下,
        //然后立刻编译shader,防止使用替代Shader
        #pragma editor_sync_compilation
        //target 4.5 代表需要支持OpenGL ES 3.1
        #pragma target 4.5

        struct Input
        {
            float3 worldPos;
        };

        float _Smoothness;

        //只在shader编译procedural drawing的时候定义
        #if defined(UNITY_PROCEDURAL_INSTANCING_ENABLED)
            StructuredBuffer<float3> _Positions; //不需要写入所以不需要RWstructBuffer
        #endif

        //间距,控制模型空间到世界空间矩阵的缩放
        float _Step;

        void ConfigureProcedural()
        {
            #if defined(UNITY_PROCEDURAL_INSTANCING_ENABLED)
                float3 position = _Positions[unity_InstanceID];           //模型空间到世界空间变换矩阵
                //因为我们在程序绘图,所以unity_ObjectToWorld是一个单位矩阵; _Step   0  0  position.x
                //我们先把它置零,然后把第三列坐标赋值为(position, 1.0)       0   _Step  0  position.y
                //然后把缩放参数设置为_Step                                   0    0  _Step position.z
                unity_ObjectToWorld = 0.0;                                  //0    0     0     1
                unity_ObjectToWorld._m03_m13_m23_m33 = float4(position, 1.0);
                unity_ObjectToWorld._m00_m11_m22 = _Step;
            #endif
        }

        void ConfigureSurface(Input input, inout SurfaceOutputStandard surface)
        {
            surface.Albedo = saturate(input.worldPos.xyz * 0.5 + 0.5);
            surface.Smoothness = _Smoothness;
        }
        ENDCG
    }
    FallBack "Diffuse"
}
这段代码主要的作用除了着色,还有获取ComputeShader中的_Positions参数(由脚本传入)
修改脚本如下:
using UnityEngine;

public class GPUGraph : MonoBehaviour
{
    [SerializeField]
    ComputeShader computeShader;  //定义所调用的compute shader

    [SerializeField]
    Material material;            //需要的材质,使用之前的point surface

    [SerializeField]
    Mesh mesh;                    //mesh网格,使用自带的cube网格

    static readonly int           //把字符串添加到指示字段,方便修改和debug
        positionsID = Shader.PropertyToID("_Positions"),
        resolutionID = Shader.PropertyToID("_Resolution"),
        stepID = Shader.PropertyToID("_Step"),
        timeID = Shader.PropertyToID("_Time");

    [SerializeField, Range(10, 1000)] //分辨率
    int resolution = 10;

    [SerializeField]
    FunctionLibrary.FunctionName function = default;

    [SerializeField, Min(0f)]
    float functionDuration = 1f, transitionDuration = 1f;

    public enum TransitionMode { Cycle, Random }
    [SerializeField]
    TransitionMode transitionMode = TransitionMode.Cycle;

    //Transform[] points;

    float duration;

    bool transitioning;

    FunctionLibrary.FunctionName transitionFunction;

    ComputeBuffer positionsBuffer;

    void OnEnable()
    {
        //创建一块GPU区域用来存放positions,每个位置数据有3个float变量,所以是3 * 4 byte
        positionsBuffer = new ComputeBuffer(resolution * resolution, 3 * 4);
    }
    private void OnDisable()
    {
        positionsBuffer.Release(); //释放存储区域

        //可以激活Unity的内存垃圾回收机制(没有这步也会回收,不过是任意的,为了避免内存阻塞,要尽快回收)
        positionsBuffer = null;
    }

    private void Update()
    {
        duration += Time.deltaTime;
        if (transitioning)
        {
            if(duration >= transitionDuration)
            {
                duration -= transitionDuration;
                transitioning = false;
            }
        }
        else if (duration >= functionDuration)
        {
            duration -= functionDuration;
            transitioning = true;
            transitionFunction = function;
            PickNextFunction();
        }
        UpdateFunctionOnGPU(); //需要运行内核,所以放在update里
    }

    void PickNextFunction()
    {
        function = transitionMode == TransitionMode.Cycle ?
        FunctionLibrary.GetNextFunctionName(function) :
        FunctionLibrary.GetRandomFunctionNameOtherThan(function);
    }

    void UpdateFunctionOnGPU()
    {
        float step = 2f / resolution; //间隔,为了使得所有cube保持在[-1,1]之间
        computeShader.SetInt(resolutionID, resolution);  //设置compute shader的参数
        computeShader.SetFloat(stepID, step);
        computeShader.SetFloat(timeID, Time.time);

        computeShader.SetBuffer(0, positionsID, positionsBuffer); //设置缓冲区,不会传输数据,
                                                                //但是会和Compute内核建立联系
                                                                //第一个参数是内核的序号,单个内核序号为0
                                                                //必要时可以用FindKernel确定序号
        int groups = Mathf.CeilToInt(resolution / 8f);
        computeShader.Dispatch(0, groups, groups, 1); //运行内核,需要4个int参数,第一个索引其余三个时组数
                                                      //1表示计算第一组8*8个位置,由于固定8*8组大小,
                                                      //(8*8是因为cumputeShader中设置的是8*8)
                                                      //需要的组数可以 分辨率/8f 并向上取整
        material.SetBuffer(positionsID, positionsBuffer);
        material.SetFloat(stepID, step);
        var bounds = new Bounds(Vector3.zero, Vector3.one * (2f + 2f / resolution));

        //调用procedural draw,参数依次是网格,子网格,材质,边界,draw个数
        Graphics.DrawMeshInstancedProcedural(mesh, 0, material, bounds, positionsBuffer.count);
    }
}
最终可以看到同时渲染100万个点,我的电脑大概是37帧左右。



同时渲染100万个点

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-24 10:31 , Processed in 0.089899 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表