zoukankan      html  css  js  c++  java
  • 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》

  • 相关阅读:
    怎么查看京东店铺的品牌ID
    PPT编辑的时候很卡,放映的时候不卡,咋回事?
    codevs 1702素数判定2
    codevs 2530大质数
    codevs 1488GangGang的烦恼
    codevs 2851 菜菜买气球
    hdu 5653 Bomber Man wants to bomb an Array
    poj 3661 Running
    poj 1651 Multiplication Puzzle
    hdu 2476 String Painter
  • 原文地址:https://www.cnblogs.com/nafio/p/9137266.html
Copyright © 2011-2022 走看看