jquave 发表于 2022-9-23 12:00

ComputeShader(二)

总结上一篇笔记所做的工作:

[*]建立一个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_ObjectToWorld是一个单位矩阵; _Step   00position.x
                //我们先把它置零,然后把第三列坐标赋值为(position, 1.0)       0   _Step0position.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
{
   
    ComputeShader computeShader;//定义所调用的compute shader

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

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

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

    //分辨率
    int resolution = 10;

   
    FunctionLibrary.FunctionName function = default;

   
    float functionDuration = 1f, transitionDuration = 1f;

    public enum TransitionMode { Cycle, Random }
   
    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万个点
页: [1]
查看完整版本: ComputeShader(二)