美文网首页Unity Shader分享
Shader- Compute Shaders(转)

Shader- Compute Shaders(转)

作者: 树上的cat_ee3c | 来源:发表于2017-09-28 23:08 被阅读53次

    首先简单介绍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演示

    #pragma kernel FillWithRed

    RWTexture2D< float4 > res;

    [numthreads(1,1,1)]

    void FillWithRed (uint3 id : SV_DispatchThreadID)

    {

    res[id.xy] = float4(1,0,0,1);

    }

    这一段代码只是输出红色至res贴图.

    一 前缀

    在核的前缀用三个纬度,定义了一个线程组内线程的数量,如果只用2个纬度那么,最后一个参数为1即可

    [numthreads(thread_group_size_x,thread_group_size_y,1)]

    GroupID:线程组id

    ThreadIID:组内线程idDispatchThreadID:线程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并且支持随机读写(RenderTextureenableRandomWrite=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脚本

    usingUnityEngine;

    usingSystem.Collections;

    publicclassSetTexColor_1:MonoBehaviour{

    publicMaterialmat;

    publicComputeShadershader;

    voidStart()

    {

    RunShader();

    }

    voidRunShader()

    {

    ////////////////////////////////////////

    //RenderTexture

    ////////////////////////////////////////

    //1新建RenderTexture

    RenderTexturetex=newRenderTexture(256,256,24);

    //2开启随机写入

    tex.enableRandomWrite=true;

    //3创建RenderTexture

    tex.Create();

    //4赋予材质

    mat.mainTexture=tex;

    ////////////////////////////////////////

    //ComputeShader

    ////////////////////////////////////////

    //1找到computeshader中所要使用的KernelID

    intk=shader.FindKernel("CSMain");

    //2设置贴图参数1=kid参数2=shader中对应的buffer名参数3=对应的texture,如果要写入贴图,贴图必须是RenderTexture并enableRandomWrite

    shader.SetTexture(k,"Result",tex);

    //3运行shader参数1=kid参数2=线程组在x维度的数量参数3=线程组在y维度的数量参数4=线程组在z维度的数量

    shader.Dispatch(k,256/8,256/8,1);

    }

    }

    Compute Shader

    //1定义kernel的名称

    #pragmakernelCSMain

    //2定义buffer

    RWTexture2DResult;

    //3kernel函数

    //组内三维线程数

    [numthreads(8,8,1)]

    voidCSMain(uint3id:SV_DispatchThreadID)

    {

    //给buffer赋值

    //纯红色

    //Result[id.xy]=float4(1,0,0,1);

    //基于uv的x给颜色

    floatv=id.x/256.0f;

    Result[id.xy]=float4(v,0,0,1);

    }

    [例二:Buffer使用]

    这个例子并不是讲实现粒子系统,而只是演示简单的Buffer使用和传递。

    步骤:

    shader:

    1 定义struct结构体

    2 声明struct变量

    3 函数里计算

    c#:

    1 定义对应的struct结构体

    2声明struct数组3创建bufferComputeBufferbuffer=newComputeBuffer(count,40);

    参数1是 数组长度(等于2个三维的乘积),参数2是结构体的字节长度如float=4

    4 初始化结构体并赋予buffer

    buffer.SetData(values);

    参数是struct数组

    5 Dispatch

    还是FindKernel->SetBuffer->Dispatch

    Compute Shader

    #pragmakernelCSMain

    structPBuffer

    {

    floatlife;

    float3pos;

    float3scale;

    float3eulerAngle;

    };

    RWStructuredBufferbuffer;

    floatdeltaTime;

    [numthreads(2,2,1)]

    voidCSMain(uint3id:SV_DispatchThreadID)

    {

    intindex=id.x+id.y*2*2;

    buffer[index].life-=deltaTime;

    buffer[index].pos=buffer[index].pos+float3(0,deltaTime,0);

    buffer[index].scale=buffer[index].scale;

    buffer[index].eulerAngle=buffer[index].eulerAngle+float3(0,20*deltaTime,0);

    }

    CS脚本

    usingUnityEngine;

    usingSystem.Collections;

    usingSystem.Collections.Generic;

    //Buffer数据结构

    structPBuffer

    {

    //size40

    publicfloatlife;//4

    publicVector3pos;//4x3

    publicVector3scale;//4x3

    publicVector3eulerAngle;//4x3

    };

    publicclassParticles_2:MonoBehaviour{

    publicComputeShadershader;

    publicGameObjectprefab;

    privateList<GameObject>pool=newList<GameObject>();

    intcount=16;

    privateComputeBufferbuffer;

    voidStart()

    {

    for(inti=0;i<count;i++){

    GameObjectobj=Instantiate(prefab)asGameObject;

    pool.Add(obj);

    }

    CreateBuffer();

    }

    voidCreateBuffer()

    {

    buffer=newComputeBuffer(count,40);

    PBuffer[]values=newPBuffer[count];

    for(inti=0;i<count;i++){

    PBufferm=newPBuffer();

    InitStruct(refm);

    values[i]=m;

    }

    buffer.SetData(values);

    }

    voidInitStruct(refPBufferm)

    {

    m.life=Random.Range(1f,3f);

    m.pos=Random.insideUnitSphere*5f;

    m.scale=Vector3.one*Random.Range(0.3f,1f);

    m.eulerAngle=newVector3(0,Random.Range(0f,180f),0);

    }

    voidUpdate()

    {

    //运行Shader

    Dispatch();

    //根据Shader返回的buffer数据更新物体信息

    PBuffer[]values=newPBuffer[count];

    buffer.GetData(values);

    boolreborn=false;

    for(inti=0;i<count;i++){

    if(values[i].life<0){

    InitStruct(refvalues[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<MeshRenderer>().material.SetColor("_TintColor",newColor(1,1,1,values[i].life));

    }

    }

    if(reborn)

    buffer.SetData(values);

    }

    voidDispatch()

    {

    shader.SetFloat("deltaTime",Time.deltaTime);

    intkid=shader.FindKernel("CSMain");

    shader.SetBuffer(kid,"buffer",buffer);

    shader.Dispatch(kid,2,2,1);

    }

    voidReleaseBuffer()

    {

    buffer.Release();

    }

    privatevoidOnDisable()

    {

    ReleaseBuffer();

    }

    }

    相关文章

      网友评论

        本文标题:Shader- Compute Shaders(转)

        本文链接:https://www.haomeiwen.com/subject/lmcbextx.html