OpenGL ES3.1使用计算着色器(Compute Shader)

1.基本介绍

OpenGL ES从3.1版本开始支持计算着色器
        工作模型有全局工作组和本地工作组,全局工作组包含由三维的本地工作组组成,本地工作组也由三个维度组成。本地工作组三个维度大小分别为:local_size_x,local_size_y,local_size_z。
一个什么都不做的着色器最基本代码:

#version 310 es
layout(local_size_x=32,local_size_y=32) in;//设置本地工作组大小,local_size_z未设置默认为1
void main(void){//执行代码
}

2.创建、编译和链接计算着色器

创建计算着色器和顶点着色器、片元着色器过程差不多。

shader=glCreateShader(GL_COMPUTE_SHADER);//使用GL_COMPUTE
glShaderSource(shader,1,sourece,NULL);
glCompileShader(shader);
program=glCreateProgram();
glAttachShader(program,shader);
glLinkProgram(program);

3.执行计算着色器

当然要先使用glUseProgram(program)
3.1使用glDispatchCompute(GLuint num_groups_x,GLuint num_groups_y,GLuint num_groups_z)设置全局工作组大小并且执行计算着色器。
3.2使用glDispatchComputeIndirect(GLintptr indirect)使用存储在缓冲区对象参数来设置全局工作组大小并且执行计算着色器。
通过绑定GL_DISPATCH_INDIRECT_BUFFER缓冲对象来设置全局参数,缓冲对象由三个无符号整数(GLuint)来设置。indirect参数是当前绑定到 GL_DISPATCH_INDIRECT_BUFFER 目标的缓冲区的字节偏移量。

4.获取着色器属性

glGetProgramiv (GLuint program, GLenum pname, GLint *params)
pname:可为GL_MAX_COMPUTE_WORK_GROUP_SIZE

5.每个计算单元的位置

const uvec3 gl_WorkGroupSize;//本地工作组大小
in uvec3 gl_NumWorkGroups;//全局工作组大小
in uvec3 gl_LocalInvocationID;//表示当前执行单元在本地工作组中的位置
in uvec3 gl_WorkGroupID;//表示当前本地工作组在全局工作组中的位置
in uvec3 gl_GlobalInvocationID;//等于gl_WorkGroupID*gl_WorkGroupSize+gl_LocalInvocationID,所以它是当前执行单元的三维索引
in uint gl_LocalInvocationIndex;//它是gl_LocalInvocationID的一种扁平化方式,等于gl_LocalInvocationID.z*gl_WorkGroupSize.x*gl_WorkGroupSize.y+gl_LocalInvocationID.y*gl_WorkGroupSize.x+gl_LocalInvocationID.x

6.同步

同步类型有两种运行屏障(execution barrier)和内存屏障(memory barrier)

6.1运行屏障(execution barrier)

计算着色器程序中使用barrier()触发,如果计算着色器的一个请求遇到了barrier(),那么它就会停止运行,并等待同一本地工作组的所有请求到达为止,这一点在条件语句中使用尤为需要注意避免死锁。

6.2内存屏障(memory barrier)

最基本的版本就是memoryBarrier(),它可以保证着色器的请求内存的写入操作一定提交到内存端,而不是通过缓冲区(cache)或者调度队列之类的方式。它还可以给着色器编译器做出指示,让它不要对内存操作重排序,以免因此跨越屏障函数。
        memoryBarrierAtomicCounter()会等待原子计数器更新,然后继续执行。
        memoryBarrierBuffer()和memoryBarrierImage()会等待缓存和图像变量的写入操作完成,然后继续执行。
        memoryBarrierShared()会等待带有shared限定符的变量更新,然后继续执行。

7.使用计算着色器写一个百万粒子发射器

7.1着色器代码

顶点着色器

#version 310 es
precision mediump image2D;//OpenGL ES要求显示定义image2D的精度
layout (binding = 1,rgba32f) restrict uniform image2D position_buffer;
uniform mat4 mvp;
out float intensity;//粒子年龄值
void main() {ivec2 size=imageSize(position_buffer);vec4 pos=imageLoad(position_buffer,ivec2(gl_VertexID%size.y,gl_VertexID/size.y));//将一维坐标转换为图像二维坐标gl_Position=mvp*vec4(pos.xyz,1.0f);intensity=pos.w;
}

片元着色器

#version 310 es
precision mediump float;
out vec4 color;
//这个值来自顶点着色器中读取的粒子年龄值
in float intensity;
void main(void){//根据粒子的年龄,在热红色到冷蓝色之间的混合结果color=mix(vec4(0.0f,0.2f,1.0f,1.0f),vec4(0.2f,0.05f,0.0f,1.0f),intensity);
}

计算着色器

#version 310 es
precision mediump image2D;//OpenGL ES要求显示定义image2D的精度
//uniform块中包含引力器的位置和质量
layout (std140,binding=0) uniform attractor_block{vec4 attractor[64];//xyz=position,w=mass
};
//每块中粒子的数量为128
layout (local_size_x=128) in;
//使用两个缓冲来包含粒子的位置和速度信息
layout (binding = 0,rgba32f) restrict uniform image2D velocity_buffer;
layout (binding = 1,rgba32f) restrict uniform image2D position_buffer;
//时间间隔
uniform float dt;
void main(void){//从缓存中读取当前的位置和速度ivec2 size=imageSize(velocity_buffer);ivec2 p=ivec2(int(gl_GlobalInvocationID.x)/size.y,int(gl_GlobalInvocationID.x)%size.y);vec4 vel=imageLoad(velocity_buffer,p);vec4 pos=imageLoad(position_buffer,p);int i;//使用当前速度x时间来更新位置pos.xyz += vel.xyz * dt;pos.w -= 0.0001 * dt;//对于每个引力器for (i = 0; i < 4; i++){//计算受力并更新速度vec3 dist = (attractor[i].xyz - pos.xyz);vel.xyz += dt * dt * attractor[i].w * normalize(dist) / (dot(dist, dist) + 10.0);}//如果粒子已经过期,那么重置它if (pos.w <= 0.0){pos.xyz = vec3(0.0f);vel.xyz *= 0.01;pos.w += 1.0f;}//将新的位置和速度信息重新保存在缓存中imageStore(position_buffer,p,pos);imageStore(velocity_buffer,p,vel);
}

7.2粒子系统初始化

 srand(clock());glEnable(GL_BLEND);glBlendFunc(GL_ONE, GL_ONE);//激活计算着色器,绑定位置和速度缓存compute_prog = GLTools::createComputeProgramAtAsset(aAssetManager,"compute.glsl");//渲染设置render_prog = GLTools::createProgramAtAsset(aAssetManager, "vertex.glsl","fragment.glsl");glUseProgram(render_prog);/*//我在windows上模拟时需要创建、绑定vao才能正常运行,在android上没有vao也行,现在还不知道为什么glGenVertexArrays(1,&vao);glBindVertexArray(vao);*/glGenTextures(2, tbos);glBindTexture(GL_TEXTURE_2D,position_tbo);glm::vec4 *pos = new glm::vec4[PARTICLE_COUNT];for (int i = 0; i < PARTICLE_COUNT; ++i) {pos[i] = glm::vec4(random_vector(-1.0f, 1.0f), randomFloat());}glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA32F,PARTICLE_GROUP_COUNT,PARTICLE_GROUP_SIZE,0,GL_RGBA,GL_FLOAT,pos);GLTools::getGLError();glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);delete[] pos;glBindTexture(GL_TEXTURE_2D,velocity_tbo);glm::vec4 *velocities = new glm::vec4[PARTICLE_COUNT];for (int i = 0; i < PARTICLE_COUNT; ++i) {velocities[i] = glm::vec4(random_vector(-10.0f, 10.0f), 0.0f);}glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA32F,PARTICLE_GROUP_COUNT,PARTICLE_GROUP_SIZE,0,GL_RGBA,GL_FLOAT,velocities);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);glGenBuffers(1, &attractor_buffer);glBindBuffer(GL_UNIFORM_BUFFER, attractor_buffer);glBufferData(GL_UNIFORM_BUFFER, 32 * sizeof(glm::vec4), NULL, GL_STATIC_DRAW);

7.3绘制

//执行计算着色器GLTools::getGLError();glUseProgram(compute_prog);glBindBuffer(GL_UNIFORM_BUFFER, attractor_buffer);for (int i = 0; i < MAX_ATTRACTORS; i++){attractor_masses[i] = 0.5f + randomFloat()* 0.5f;}glBindBufferBase(GL_UNIFORM_BUFFER, 0, attractor_buffer);//更新引力器位置和质量的缓存float time = clock()/float(CLOCKS_PER_SEC);glm::vec4* attractors = (glm::vec4*)glMapBufferRange(GL_UNIFORM_BUFFER, 0, 32 * sizeof(glm::vec4), GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);for (int i = 0; i < 32; ++i) {attractors[i] = glm::vec4(sinf(time*float(i + 4)*7.5f*20.0f)*50.0f,cosf(time*float(i + 7)*3.9f*20.0f)*50.0f,sinf(time*float(i + 3)*5.3f*20.0f)*cosf(time*float(i + 5)*9.1f)*100.0f,attractor_masses[i]);}glUnmapBuffer(GL_UNIFORM_BUFFER);//设置时间间隔glUniform1f(glGetUniformLocation(compute_prog, "dt"), 0.2f);glBindImageTexture(0, velocity_tbo, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA32F);glBindImageTexture(1, position_tbo, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA32F);glDispatchCompute(PARTICLE_COUNT, 1, 1);//确保计算着色器的写入操作已经完成glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);//glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);glUseProgram(render_prog);glDrawArrays(GL_POINTS, 0, PARTICLE_COUNT);

窗口大小改变时执行

 aspect_ratio = (float)w / (float)h;//设置模型视图和投影矩阵// 实时改变 MVP 矩阵glm::mat4 projection = glm::perspective(glm::radians(45.0f), aspect_ratio, 0.1f, 1000.0f);glm::mat4 mvp = projection * glm::lookAt(glm::vec3(0, 0, 100.0f), glm::vec3(0, 0, 0), glm::vec3(0, 1.0f, 0));GLuint t = glGetUniformLocation(render_prog, "mvp");glUniformMatrix4fv(t, 1, GL_FALSE, glm::value_ptr(mvp));

杂项

使用的glm库是一个头文件库,只需要下载后复制到项目或者添加到环境变量就可以直接使用。
一些全局变量和静态函数:

 GLuint compute_prog, render_prog;// TBOsunion{struct{GLuint position_tbo;GLuint velocity_tbo;};GLuint tbos[2];};// 吸引器 UBOGLuint  attractor_buffer;GLuint vao;// Mass 吸引子的质量float attractor_masses[MAX_ATTRACTORS];float aspect_ratio = 1.0f;static float randomFloat(){float res;unsigned int tmp;static unsigned int seed = 0xFFFF0C59;seed *= 16807;tmp = seed ^ (seed >> 4) ^ (seed << 15);*((unsigned int *)&res) = (tmp >> 9) | 0x3F800000;return (res - 1.0f);}static glm::vec3 random_vector(float minmag = 0.0f, float maxmag = 1.0f){glm::vec3 randomvec(randomFloat() * 2.0f - 1.0f, randomFloat() * 2.0f - 1.0f, randomFloat() * 2.0f - 1.0f);randomvec = normalize(randomvec);randomvec *= (randomFloat() * (maxmag - minmag) + minmag);return randomvec;}

我自己封装的android端OpenGL ES创建着色器的辅助函数:

#ifndef OPENGL_GLTOOLS_H
#define OPENGL_GLTOOLS_H
#ifdef ENABLE_GLES31#include <GLES3/gl31.h>
#elif defined(ENABLE_GLES32)#include <GLES3/gl32.h>
#else#include <GLES3/gl3.h>
#endif
#include <android/log.h>
#include <android/asset_manager.h>
#include <stdlib.h>
#define GLTOOLS_TAG "gltools"
class GLTools {public:static void getGLError() {switch (glGetError()){case GL_NO_ERROR:return ;case GL_INVALID_ENUM:LOGE("glError","GL Invalid Enum\n");break;case GL_INVALID_VALUE:LOGE("glError", "GL Invalid Value\n");break;case GL_INVALID_OPERATION:LOGE("glError", "GL Invalid Operation\n");break;case GL_OUT_OF_MEMORY:LOGE("glError", "GL Out Of Memory\n");break;case GL_INVALID_FRAMEBUFFER_OPERATION:LOGE("glError", "GL Invalid FrameBuffer Operation\n");break;default:LOGE("glError", "err\n");break;}}static char* readAsset(AAssetManager* pManager,const char* filename){if(pManager== nullptr||filename== nullptr){LOGE(GLTOOLS_TAG,"%s空指针异常!",filename);}AAsset* pAsset =AAssetManager_open(pManager,filename,AASSET_MODE_UNKNOWN);if(pAsset== nullptr){LOGE(GLTOOLS_TAG,"打开文件:%s失败!",filename);return nullptr;}size_t size = AAsset_getLength(pAsset);char* pData= nullptr;if( size > 0 ){pData = new char[size+1];int iRet = AAsset_read( pAsset, pData, size);if( iRet <= 0 ){AAsset_close(pAsset);delete [] pData;LOGE(GLTOOLS_TAG,"文件:%s读取失败!",filename);return nullptr;}pData[iRet]=0;}else{LOGE(GLTOOLS_TAG,"文件:%s为空!",filename);return nullptr;}AAsset_close(pAsset);return pData;}static char*readFile(const char*filename){if(filename == nullptr){LOGE(GLTOOLS_TAG,"文件名错误!");return nullptr;}FILE* fp=fopen(filename,"r");if(fp== nullptr){LOGE(GLTOOLS_TAG,"文件:%s打开失败!",filename);return nullptr;}fseek(fp,0,SEEK_END);long size=ftell(fp);fseek(fp,0,SEEK_SET);char* data=new char[size+1];data[size]=0;fread(data,size,1,fp);fclose(fp);return data;}static GLuint createShader(GLenum shaderType,const char* shaderCode){GLuint shader=glCreateShader(shaderType);if(shader==0){LOGE(GLTOOLS_TAG,"创建着色器失败!\n%s",shaderCode);return 0;}glShaderSource(shader,1,&shaderCode, nullptr);glCompileShader(shader);//编译结果GLint status;//获取编译结果,并且将结果赋值给statusglGetShaderiv(shader,GL_COMPILE_STATUS,&status);if(status!=GL_TRUE){char buf[256]={};//顶点着色器编译失败glGetShaderInfoLog(shader,255, nullptr,buf);LOGE(GLTOOLS_TAG,"编译着色器失败->%s!\n%s",buf,shaderCode);glDeleteShader(shader);return 0;}return shader;}static GLuint createProgram(const char* vertexCode,const char* fragmentCode){GLuint vertexShader=createShader(GL_VERTEX_SHADER,vertexCode);if(vertexShader==0){LOGE(GLTOOLS_TAG,"顶点着色器创建失败!");return 0;}GLuint fragmentShader=createShader(GL_FRAGMENT_SHADER,fragmentCode);if(fragmentShader==0){LOGE(GLTOOLS_TAG,"片元着色器创建失败!");return 0;}GLuint program=glCreateProgram();if(program==0){LOGE(GLTOOLS_TAG,"着色器程序创建失败!");glDeleteShader(vertexShader);glDeleteShader(fragmentShader);return 0;}glAttachShader(program,vertexShader);glAttachShader(program,fragmentShader);glLinkProgram(program);//存储链接状态GLint status;//获取链接状态信息glGetProgramiv(program,GL_LINK_STATUS,&status);if(status!=GL_TRUE){GLsizei num;//保存日志GLchar *log;glGetProgramiv(program,GL_INFO_LOG_LENGTH,&num);log = new char[num+1];//从日志缓存中取出关于program length个长度的日志,并保存在log中glGetProgramInfoLog(program,num,&num,log);/** 在这里查看日志*/LOGE(GLTOOLS_TAG,"链接程序失败!->%s\n顶点着色器:\n%s\n片元着色器:\n%s",log,vertexCode,fragmentCode);delete[] log;glDeleteShader(vertexShader);glDeleteShader(fragmentShader);glDeleteProgram(program);return 0;}glDetachShader(program, vertexShader);glDeleteShader(vertexShader);glDetachShader(program, fragmentShader);glDeleteShader(fragmentShader);return program;}static GLuint createProgramAtAsset(AAssetManager *pManager, const char*vertexFileName, const char*fragmentFileName){char*vCode=readAsset(pManager,vertexFileName);char*fCode=readAsset(pManager,fragmentFileName);if(vCode&&fCode){GLuint program=createProgram(vCode,fCode);delete[] vCode;delete[] fCode;return program;}else{return 0;}}static GLuint createProgramAtFile(const char*vertexFileName, const char*fragmentFileName){char*vCode=readFile(vertexFileName);char*fCode=readFile(fragmentFileName);if(vCode&&fCode){GLuint program=createProgram(vCode,fCode);delete[] vCode;delete[] fCode;return program;}else{return 0;}}
#if defined(ENABLE_GLES31)||defined(ENABLE_GLES32)static GLuint createComputeProgram(const char*calculationCode) {GLuint shader = createShader(GL_COMPUTE_SHADER, calculationCode);GLint status;if (shader == 0) {LOGE(GLTOOLS_TAG, "计算着色器创建失败!\n");return 0;}GLuint program = glCreateProgram();if (program == 0) {LOGE(GLTOOLS_TAG, "计算着色器程序创建失败!\n");glDeleteShader(shader);return 0;}glAttachShader(program, shader);glLinkProgram(program);//存储链接状态//获取链接状态信息glGetProgramiv(program, GL_LINK_STATUS, &status);if (status != GL_TRUE) {GLsizei num;//保存日志GLchar *log;glGetProgramiv(program, GL_INFO_LOG_LENGTH, &num);log = new char[num + 1];//从日志缓存中取出关于program length个长度的日志,并保存在log中glGetProgramInfoLog(program, num, &num, log);/** 在这里查看日志*/LOGE(GLTOOLS_TAG, "链接程序失败!->%s\n计算着色器:\n%s\n", log, shader);delete[] log;glDeleteShader(shader);glDeleteProgram(program);return 0;}glDetachShader(program, shader);glDeleteShader(shader);return program;}static GLuint createComputeProgramAtFile(const char*calculationPath) {char*fCode = readFile(calculationPath);if (fCode) {GLuint program = createComputeProgram(fCode);delete[] fCode;return program;}else {return 0;}}static GLuint createComputeProgramAtAsset(AAssetManager *pManager,const char*calculationPath) {char*fCode = readAsset(pManager,calculationPath);if (fCode) {GLuint program = createComputeProgram(fCode);delete[] fCode;return program;}else {return 0;}}
#endif
};#endif //OPENGL_GLTOOLS_H

结语

你可以自己调整计算着色代码和初始化代码来实现一个你自己的粒子发射器

OpenGL ES3.1使用计算着色器(Compute Shader)相关推荐

  1. OpenGL之计算着色器(Compute Shader)注解

    一.前言 关于计算着色器,我也是刚试验成功,所以接下来我也讲不出什么长篇大论,概念什么的百度一下到处都是,我这边只讲讲百度没有的填坑经历吧. 二.计算着色器的语法解释 先附上一个计算着色器的代码段: ...

  2. Directx 计算着色器(compute shader)

    原文 :http://www.cnblogs.com/Ninputer/archive/2009/12/11/1622190.html 博者注:计算着色器调试(http://msdn.microsof ...

  3. 计算着色器(Compute Shaders)

    原文 : https://catlikecoding.com/unity/tutorials/basics/compute-shaders/ 1 将工作转移到GPU(Moving Work to th ...

  4. Learn OpenGL(四)——片段着色器(Fragment Shader)

    片段着色器(Fragment Shader) 片段着色器是第二个也是最终我们打算创建的用于渲染三角形的着色器. 片段着色器的全部, 都是用来计算你的像素的最后颜色输出. 为了让事情比较简单, 我们的片 ...

  5. OpenGL深入探索——《OpenGL编程指南(原书第8版)》——计算着色器

    转载自 <OpenGL编程指南(原书第8版)>--计算着色器 概述 由于图形处理器每秒能够进行数以亿计次的计算,它已成为一种性能十分惊人的器件.过去,这种处理器主要被设计用于承担实时图形渲 ...

  6. Unity Shader:细分着色器(Tessellation Shader)在Unity顶点着色器中的写法以及各参数变量解释

    图1:在Unity内将sphere细分后 图2:在Unity内将sphere细分后 Unity官网关于细分着色器的资料比较少,只有在Surface Shader中使用的例子.我看了下Surface S ...

  7. OpenGL Compute Shader计算着色器的实例

    OpenGL Compute Shader计算着色器 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 // #define USE_GL3W #include &l ...

  8. OpenGL Compute Shader Raytracing 计算着色器光线追踪的实例

    OpenGL Compute Shader Raytracing 计算着色器光线追踪 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 // #define USE_ ...

  9. OpenGL Compute Shader Particle System计算着色器粒子系统的实例

    OpenGL Compute Shader Particle System计算着色器粒子系统 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 #include &l ...

最新文章

  1. 486. Predict the Winner | 486. 预测赢家(博弈论)
  2. 高斯低通滤波 matlab_一维和二维高斯函数及其一阶和二阶导数
  3. 3D旋转(CSS3)
  4. stl之vector的应用
  5. web安全day11:进一步学习windows域的gpo
  6. CSS z-index 属性 控制div上下层次
  7. JAVA日期格式化大写YYYY-MM-dd和小写yyyy-MM-DD的坑
  8. 云服务器哪家好?国内云服务器十大品牌排行榜
  9. 计算机预测自己未来的相貌,测测未来另一半的模样 测测你的另一半长相软件...
  10. UV (网站独立访客)
  11. 11个能够帮助Java开发者提升编程能力的Java论坛汇总
  12. AQS中非公平锁的实现原理简介
  13. 波士顿大学计算机工程硕士,波士顿大学计算机工程专业硕士.pdf
  14. PHP自学教程之PHP语法基础
  15. Android高仿微信图片多选功能
  16. 计算机毕业设计——基于HTML电商购物商城项目设计与实现-快购优品 带论文6500字 答辩ppt
  17. 为什么电子邮件发送失败?电子邮件发送失败的原因及解决方法
  18. 丁香园php岗_丁香园面试药学新闻编辑岗位体悟
  19. CXF框架的一些问题
  20. 8月份,留言精选赠书!

热门文章

  1. 累死累活干不过一个写PPT的
  2. 大学机械专业减速器毕业设计、课程设计案例/说明书(论文) CAD图纸 三维图 开题报告 任务书 外文翻译…
  3. 鼠标右键菜单栏程序删除
  4. 非主流茶馆: CIO的八种死法
  5. SecureCRT中文突然乱码,设置UTF-8失效
  6. 小睿睿的等式 (思维dp)
  7. PCI-E x1/x4/x8/x16四种插槽区别是什么
  8. 基于百度智能云的人脸识别系统
  9. 大乱斗ps4好玩吗_《马里奥赛车》夺冠,网友票选“最好玩的马里奥游戏”
  10. (二)DSP28335基础教程——GPIO输入(矩阵按键扫描)