• Shader第二十八讲 Compute Shaders


    http://blog.sina.com.cn/s/blog_471132920102w97k.html

    首先简单介绍GPGPU programming

    和CPU Random Memory Accesses(随机内存获取)不同,GPU是用平行架构处理 大量的并行数据,例如vertex和fragment就是分开计算的。使用GPU并利用这种特性来进行非图形计算被称为GPGPU编程(General Purpose GPU Programming)。大量并行无序数据的少分支逻辑(少if)适合GPGPU,例如粒子间互不影响的粒子系统。GPGPU平台或接口有DirectCompute,OpenCL,CUDA等。
    从此图可以看出 CPU和GPU之间的数据传输是瓶颈。故当使用GPGPU时,对Texture的逐像素处理不需要传回CPU,因而速度比较快。


     Compute Shader

     Compute Shader下文简称cs


    概念
    Compute Shaders是在GPU运行却又在普通渲染管线之外的程序。用于运行GPGPU program。

    平行算法被拆分成 很多线程组,而线程组包含很多线程。例如一个线程处理一个像素点。而一定要注意这种处理是无序的随机的,并不一定是固定的处理顺序,例如不一定是从左到右挨个处理像素点。
    线程组
    A Thread Group 运行在一个GPU单元 (A single multiprocesser),如果GPU有16个
    multiprocesser,那么程序至少要分成16个 Thread Group使得每个multiprocesser都参与计算。
    组之间不分享内存。
    
    线程
    一个线程组包含n个线程,每32个thread称为一个warp(nvidia:warp=32 ,ati:wavefront=64,因此未来此数字可能会更高)。从效率考虑,一个线程组包含的线程数最好的warp的倍数,256是一个比较合适的数字。
    
    实现步骤 
    (1)在compute shader里 通过对贴图或者buffer进行数据读写
    (2)在cs脚本里设置shader的贴图或者buffer并运行
    规则语法
    1 Compute Shaders的文件后缀为.compute

    2 使用#pragma指出内核。

    一个Compute Shader至少需要一个内核。
    例如
     #pragma kernel FillWithRed 
    也可以接宏定义

    #pragma kernel KernelOne SOME_DEFINE DEFINE_WITH_VALUE=1337 #pragma kernel KernelTwo OTHER_DEFINE

    3 函数语法
    下面通过一个完整简单的ComputeShader演示
    
    
    1. #pragma kernel FillWithRed
    2. RWTexture2D< float4 > res;

    3. [numthreads(1,1,1)]
    4. void FillWithRed (uint3 id : SV_DispatchThreadID)
    5. {
    6.   res[id.xy] = float4(1,0,0,1);
    7. }
    这一段代码只是输出红色至res贴图. 

    一 前缀
    在核的前缀用三个纬度,定义了一个线程组内线程的数量,如果只用2个纬度那么,最后一个参数为1即可
    [numthreads(thread_group_size_x,thread_group_size_y,1)]
    
    
    GroupID:线程组id
    ThreadIID:组内线程id
    DispatchThreadID:线程DispatchId
    (DispatchThreadID =GroupID*组内线程数 + ThreadId)
    这些id都是从0开始
    
     二 资源类型
    cs可以读取两种类型的资源:buffer ,texture
    
    【buffer】
    例如顶点缓冲就是一种buffer,很多时候我们去定义struct数组并作为buffer传入cs
    
    自己定义buffer(必须是固定size):
    struct Data
    {
    float x;
    };
    StructuredBuffer< Data > b;
    RWStructuredBuffer< Data > b;
    
    buffer的添加是append,消耗是consume 
    
    texture:
    只读 Texture2d< float4 > xx;
    读写 RWTexture2d< float4 > xx;
    RWTexture2d< float2 > xx;  //RG_int
    
    在Unity里读写的只能是RenderTexture并且支持随机读写RenderTexture enableRandomWrite=true)
    
    三 其他
    1 每个线程都有一个对应的id: SV_DispatchThreadID
    对贴图进行采样不能用Sample 而是SampleLevel,额外的参数是mipmap level ,0为最高级,1为次级,2...
    
    2 int格子转换至[0,1]uv范围
    例如一张512x512的贴图
    Texture2d tex;
    tex.SampleLevel(samPoint,float2(id.x,id.y)/512)
    
    
    blur需要所有所有像素都sample完,因此需要同步:
    GroupMemoryBarrierWithGroupSync(); 
    
    
    [例一:基本贴图计算]
     将一张贴图所有像素点赋予红色,很简单。
    
    CS脚本
    
    1. using UnityEngine;
    2. using System.Collections;
    3. public class SetTexColor_1 : MonoBehaviour {
    4.     public Material mat;
    5.     public ComputeShader shader;
    6.     void Start()
    7.     {
    8.         RunShader ();
    9.     }
    10.     void RunShader()
    11.     {
    12.         ////////////////////////////////////////
    13.         //    RenderTexture
    14.         ////////////////////////////////////////
    15.         //1 新建RenderTexture
    16.         RenderTexture tex = new RenderTexture (256, 256, 24);
    17.         //2 开启随机写入
    18.         tex.enableRandomWrite = true;
    19.         //3 创建RenderTexture
    20.         tex.Create ();
    21.         //4 赋予材质
    22.         mat.mainTexture = tex;
    23.         ////////////////////////////////////////
    24.         //    Compute Shader
    25.         ////////////////////////////////////////
    26.         //1 找到compute shader中所要使用的KernelID
    27.         int k = shader.FindKernel ("CSMain");
    28.         //2 设置贴图    参数1=kid  参数2=shader中对应的buffer名 参数3=对应的texture, 如果要写入贴图,贴图必须是RenderTexture并enableRandomWrite
    29.         shader.SetTexture (k, "Result", tex);
    30.         //3 运行shader  参数1=kid  参数2=线程组在x维度的数量 参数3=线程组在y维度的数量 参数4=线程组在z维度的数量
    31.         shader.Dispatch (k, 256 / 8, 256 / 8, 1);
    32.     }
    33. }
    Compute Shader
    
    
    
    1. //1 定义kernel的名称
    2. #pragma kernel CSMain
    3. //2 定义buffer
    4. RWTexture2D Result;
    5. //3 kernel函数
    6. //组内三维线程数
    7. [numthreads(8,8,1)]
    8. void CSMain (uint3 id SV_DispatchThreadID)
    9. {
    10.     //给buffer赋值
    11.     //纯红色
    12.     //Result[id.xy] float4(1,0,0,1);
    13.     //基于uv的x给颜色
    14.     float id.x/256.0f;
    15.     Result[id.xy] float4(v,0,0,1);
    16. }

    [例Buffer使用]
     
    
    这个例子并不是讲实现粒子系统,而只是演示简单的Buffer使用和传递。
    
    步骤:
    shader:
    1 定义struct结构体
    2 声明struct变量
    3 函数里计算
    
    c#:
    1 定义对应的struct结构体
    2 声明struct数组
     创建buffer
       ComputeBuffer  buffer = new ComputeBuffer(count,40);  
     参数1是 数组长度(等于2个三维的乘积),参数2是结构体的字节长度如float=4
    4 初始化结构体并赋予buffer
       buffer.SetData (values); 
     参数是 struct数组
    5 Dispatch
    还是 FindKernel-> SetBuffer ->Dispatch
    
    
    Compute Shader
    
    1. #pragma kernel CSMain
    2. struct PBuffer
    3. {
    4.     float life;
    5.     float3 pos;
    6.     float3 scale;
    7.     float3 eulerAngle;
    8. };
    9. RWStructuredBuffer buffer;
    10. float deltaTime;
    11. [numthreads(2,2,1)]
    12. void CSMain (uint3 id SV_DispatchThreadID)
    13. {
    14.      int index id.x id.y 2;
    15.      buffer[index].life -= deltaTime;
    16.     buffer[index].pos buffer[index].pos float3(0,deltaTime,0); 
    17.     buffer[index].scale buffer[index].scale; 
    18.     buffer[index].eulerAngle buffer[index].eulerAngle float3(0,20*deltaTime,0); 
    19. }


    CS脚本

    using UnityEngine;
    using System.Collections;
    using System.Collections.Generic;

    //Buffer数据结构
    struct PBuffer
    {
        //size 40
        public float life;//4
        public Vector3 pos;//4x3
        public Vector3 scale;//4x3
        public Vector3 eulerAngle;//4x3
    };

    public class Particles_2 : MonoBehaviour {
        public ComputeShader shader;
        public GameObject prefab;
        private List<<font face="Consolas"> GameObject > pool = new List<<font face="Consolas"> GameObject >();
        int count = 16;
        private ComputeBuffer buffer;

        void Start()
        {
            for (int i = 0; i  <<font face="Consolas">  count; i++) {
                GameObject obj = Instantiate (prefab) as GameObject;
                pool.Add (obj);
            }
            CreateBuffer ();
        }

        void CreateBuffer()
        {
            buffer = new ComputeBuffer(count,40);
            PBuffer[] values = new PBuffer[count];
            for (int i = 0; i <</span> count; i++) {
                PBuffer m = new PBuffer ();
                InitStruct (ref m);
                values [i] = m;
            }
            buffer.SetData (values);
        }

        void InitStruct(ref PBuffer m )
        {
            m.life = Random.Range(1f,3f);
            m.pos = Random.insideUnitSphere * 5f;
            m.scale = Vector3.one * Random.Range(0.3f,1f);
            m.eulerAngle = new Vector3 (0, Random.Range(0f,180f), 0);
        }

        void Update()
        {
            //运行Shader
            Dispatch ();

            //根据Shader返回的buffer数据更新物体信息
            PBuffer[] values = new PBuffer[count];
            buffer.GetData(values);
            bool reborn = false;
            for (int i = 0; i <</span> count; i++) {
                if (values [i].life <</span> 0) {
                    InitStruct (ref values [i]);
                    reborn = true;
                } else {
                    pool [i].transform.position = values [i].pos;
                    pool [i].transform.localScale = values [i].scale;
                    pool [i].transform.eulerAngles = values [i].eulerAngle;
                    //pool [i].GetComponent<</span>MeshRenderer>().material.SetColor ("_TintColor", new Color(1,1,1,values [i].life));
                }
            }
            if(reborn)
                buffer.SetData(values);
        }

        void Dispatch()
        {
            shader.SetFloat ("deltaTime", Time.deltaTime);
            int kid = shader.FindKernel ("CSMain");
            shader.SetBuffer (kid, "buffer", buffer);
            shader.Dispatch (kid, 2, 2, 1);
        }

        void ReleaseBuffer()
        {
            buffer.Release();
        }
        private void OnDisable()
        {
            ReleaseBuffer();
        }
    }


    参考:

    《Introduction_to_3D_Game_Programming_with_Directx_11》

  • 相关阅读:
    顶级jQuery树插件
    jQuery 表格
    FlexiGrid使用手册
    gif动图快速制作方法(附工具)(转)
    Maven搭建SpringMVC+Hibernate项目详解(转)
    Gradle cookbook(转)
    Gradle入门系列(转)
    Gradle构建多模块项目(转)
    oracle中imp命令具体解释
    DisplayContent、StackBox、TaskStack笔记
  • 原文地址:https://www.cnblogs.com/nafio/p/9137266.html
Copyright © 2020-2023  润新知