Shader- Compute Shaders(转)

首先简单介绍GPGPU programming

和CPU Random Memory Accesses(随机内存获取)不同,GPU是用平行架构处理 大量的并行数据,例如vertex和fragment就是分开计算的。使用GPU并利用这种特性来进行非图形计算被称为GPGPU编程(General Purpose GPU Programming)。大量并行无序数据的少分支逻辑(少if)适合GPGPU,例如粒子间互不影响的粒子系统。GPGPU平台或接口有DirectCompute,OpenCL,CUDA等。

Shader- Compute Shaders(转)_第1张图片

从此图可以看出 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();[例一:基本贴图计算]

Shader- Compute Shaders(转)_第2张图片

将一张贴图所有像素点赋予红色,很简单。

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使用]

Shader- Compute Shaders(转)_第3张图片

这个例子并不是讲实现粒子系统,而只是演示简单的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;

privateListpool=newList();

intcount=16;

privateComputeBufferbuffer;

voidStart()

{

for(inti=0;i

GameObjectobj=Instantiate(prefab)asGameObject;

pool.Add(obj);

}

CreateBuffer();

}

voidCreateBuffer()

{

buffer=newComputeBuffer(count,40);

PBuffer[]values=newPBuffer[count];

for(inti=0;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

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().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(转))