OpenGL超级宝典(第7版)笔记22 原子计数器 清单5.31-5.34

文章目录

  • OpenGL超级宝典(第7版)笔记22 原子计数器 清单5.31-5.34
  • 前言
  • 1 原子计数器初介绍
    • 1.1 原子计数器的声明
    • 1.2 原子计数器准备缓冲
    • 1.3 使用原子计数器计数
  • 2 再进一步,改变明暗
  • 3 同步访问原子计数器
    • 遗留的问题:是否屏障只保证操作依次完成,但是不保证内存操作的先后顺序?
  • 4 总结

前言

上一篇我们介绍了着色器存储区块的相关内容(包括了和uniform块的对比),还介绍了原子内存操作和屏障的实用问题。这一篇我们将介绍原子计数器,我们将通过它来进行计数,并通过它计算图形的面积。

1 原子计数器初介绍

原子计数器是一种特殊的变量,表示的是多个着色器之间共享的存储(即大家都可以对该变量进行修改),而且我们可以调用特定的函数进行原子内存操作。变量本身的功能是用于计数。

1.1 原子计数器的声明

layout (binding=2,offset=8) uniform atomic_uint mycounter;

这里声明了原子计数器mycounter,并绑定在2上,其中起始位置是8。

1.2 原子计数器准备缓冲

首先为原子计数器变量准备一些内存,以便其使用:

GLuint Atomic_counter_buffer;glCreateBuffers(1, &Atomic_counter_buffer);glNamedBufferStorage(Atomic_counter_buffer, 16*sizeof(GLuint), NULL, GL_MAP_WRITE_BIT | GL_DYNAMIC_STORAGE_BIT );glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER,2,Atomic_counter_buffer);

我们要将缓冲中清零:

 GLuint atomic_counter_data=0;glClearNamedBufferSubData(Atomic_counter_buffer,GL_R32UI,2*sizeof(GLuint),sizeof(GLuint),GL_RED_INTEGER,GL_UNSIGNED_INT,&atomic_counter_data);

当然你可以才用书上的其他方法也可以把它清零(使用glNamedBufferSubData或是使用glMapBufferRange的指针来进行清零也可以)

1.3 使用原子计数器计数

我们现在在着色器中已经声明了原子计数器,并且在我们的程序中为该原子计数器安排了内存并做了初始化,现在我们要在着色器内部用原子计数器进行计数。OpenGL提供了三种用于计数的着色器函数:

uint atomicCounterIncrement(atomic_uint a);//让计数器加一
uint atomicCounterDecrement(atomic_uint a);//让计数器减一
uint atomicCounter(atomic_uint a);//返回计数器的值

我们现在改写一下片段着色器:

#version 450 core
layout (binding=2,offset=8) uniform atomic_uint mycounter;
in vec4 fscolor;
out vec4 color;
void main(void){atomicCounterIncrement(mycounter);color = fscolor;
}

我们的想法是在每次运行片段着色器的时候都对计数器进行加一,这样我们在运算完一帧画面后计数器的数字就是所绘制的像素数,如果我们绘制的图像充满了整个窗口,那么计数器的结果就是窗口的像素总数。

当然我们现在还没有把计数的数据从内存中读取出来。
我们要在每次绘制完一帧之后(或之前,因为那样的话读取的是上一帧的计数器数据)对计数器中的数据进行读取:

GLuint* cont = (GLuint*)glMapBuffer(GL_ATOMIC_COUNTER_BUFFER, GL_READ_ONLY);
if (cont != NULL) {std::cout << " number of pixel is " << cont[2] << std::endl;cont[2] = 0;//当然别忘了在读取之后把计数器清零
}else {GLenum error = glGetError();std::cout << "error=" << error;std::cout << "  null" << std::endl;Sleep(200);
}
glUnmapBuffer(GL_ATOMIC_COUNTER_BUFFER);

当然别忘了在读取之后把计数器清零(要不然每绘制一帧就会把数值加进去,这样累积起来我们看不到每一帧的像素数)

ok,现在我们运行程序,并且移动我们的摄像机位置和角度,你会看到计数器的数值会随着窗口中的像素数量跟着改变,当你不动的时候,数值就不动,当我们的图像占满整个窗口的时候数值就会定格在我们窗口的像素总数上(其实并不一定,因为有时候有多层的三角形叠加在一起,这导致本身屏幕上的一个像素运行了多次FS,这会使得最终的输出超过你的窗口像素数)。

这里我在不断晃动视角,得到的计数器数值也跟着不断变化。
注:右侧是绘制的画面,左侧为输出的计数器数值。

这里我把视角完全对着地面(即摄像机前方只有一层三角形)得到的数据正好是我窗口的像素数量(600 * 600)。
注:右侧是绘制的画面,左侧为输出的计数器数值。

这里我把视角仍对着地面,但是漏出了正方体的一角,这里蓝色和肉色的区域由于底下还有一层三角形,所以传回的计数器数值已经超过了屏幕中像素的总数(>600 * 600)
注:右侧是绘制的画面,左侧为输出的计数器数值。

2 再进一步,改变明暗

仅仅用原子计数器对像素数进行计数有些无聊,我们根据书上的内容试着用计数的结果来改变一下图案的亮度

对于每一帧来说我们都要绘制两遍,第一遍是通过FS来对像素数进行计数,第二遍通过第一遍得到的数据,在第二遍的着色器中通过数据对FS的颜色输出做一些修改(调整一下像素点的亮度)
具体操作如下:

所以我们要在第一次绘制完之后,把原先绑定在GL_ATOMIC_COUNTER_BUFFER中的缓存对象绑定在一个uniform块上,比如以下变量:

layout (std140,binding=0) uniform area_block{layout (offset=8) uint counter_value;
};

然后再用uniform块中的数据来进行运算。

由于我们要绘制两次,所以我们准备了两个管线程序:
第一个:

//VS1
#version 450 core
layout (location=0) uniform mat4 cam;
layout (location=4) in vec3 vsposition;
void main(void){gl_Position = cam * vec4(vsposition,1.0);
}
//FS1
#version 450 core
layout (binding=2,offset=8) uniform atomic_uint mycounter;
void main(void){            atomicCounterIncrement(mycounter);
}

第二个:

//VS2
#version 450 core
layout (location=0) uniform mat4 cam;
layout (location=4) in vec3 vsposition;
layout (location=5) in vec3 vscolor;
out vec4 fscolor;
void main(void){fscolor = vec4(vscolor,1.0);gl_Position = cam * vec4(vsposition,1.0);
}
//FS2
#version 450 core
layout (std140,binding=0) uniform area_block{layout (offset=8) uint counter_value;
};
in vec4 fscolor;
out vec4 color;
void main(void){float brightness=clamp(float(counter_value)/360000.0,0.0,1.0);color = fscolor*brightness;
}

我们在绘制的时候也要绘制两次:

static const GLfloat color[] = { 0.5f,0.8f,0.5f,1.0f };static const GLfloat red[] = { 0.7f,0.55f,0.5f,1.0f };glClearBufferfv(GL_COLOR, 0, red);//第一次绘制glUseProgram(rendering_program2);glUniformMatrix4fv(0,1, GL_TRUE,matrix_ok.m_mat4f);glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, Atomic_counter_buffer);glColorMask(GL_FALSE,GL_FALSE,GL_FALSE,GL_FALSE);glDrawArrays(GL_TRIANGLES, 0, 42);glColorMask(GL_TRUE,GL_TRUE,GL_TRUE,GL_TRUE);//第二次绘制glUseProgram(rendering_program);glUniformMatrix4fv(0,1, GL_TRUE,matrix_ok.m_mat4f);//one more time?? glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, 0);glBindBufferBase(GL_UNIFORM_BUFFER, 0,Atomic_counter_buffer);glDrawArrays(GL_TRIANGLES, 0, 42);//读取原子计数器的数据并清零glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, Atomic_counter_buffer);GLuint* cont = (GLuint*)glMapBuffer(GL_ATOMIC_COUNTER_BUFFER, GL_READ_ONLY);if (cont != NULL) {std::cout << " number of pixel is " << cont[2] << std::endl;cont[2] = 0;}else {GLenum error = glGetError();std::cout << "error=" << error;std::cout << "  null" << std::endl;Sleep(200);}glUnmapBuffer(GL_ATOMIC_COUNTER_BUFFER);//后面修改摄像机位置的代码略,可见本篇附带的源码

这里要注意一些细节,首先是我们要用uniform块来读取原子计数器的数据,因为我们可以直接通过glBindBufferBase把原先绑定在GL_ATOMIC_COUNTER_BUFFER的数据改绑定到GL_UNIFORM_BUFFER上去,这样直接就可以读取原子计数器的数据。
其中还要注意一下我们在原子计数器中有偏移offset=8,在uniform中要使用std140布局,并手动把counter_value的偏移进行修改才能正确读到offset=8处的数据。

其次在第一次绘制的时候要关闭向帧缓存的写入(用glColorMask)因为我们第一次绘制的目的仅仅是进行计数。

如果没出错的话我们的结果如下:


你可以看到当我们调整俯仰角度的时候,对应的计数器数值也随之改变,同时我们得到的明暗程度也随之改变。

3 同步访问原子计数器

我们在着色器中调用对应的函数,就会对计数器进行加一或减一操作。但是注意的是,虽然说理论上是在调用函数后计数器中的值会相应改变,但是由于我们读取的内容是存在内存中的,而当着色器运行过程中原子计数器的数值很有可能是被放在了显存中,并没有把内存中的数值做相应的修改,所以可能存在同步访问时数据出错的情况。如果需要同步访问计数器的数值,那么我们需要用glMemoryBarrier()或是GLSL中的memoryBarrierAtomicCounter()来建立屏障,用来保证屏障之前的内存操作都已完成。

遗留的问题:是否屏障只保证操作依次完成,但是不保证内存操作的先后顺序?

(比如说同时对m加一,是否无法保证哪次加一先运行,哪次加一后运行?)

4 总结

本篇简单的了解了一下原子计数器的功能,并且按照书上的示例使用了一下原子计数器,其中需要注意原子计数器的数据初始化问题、每一帧结束后要对计数器清零、计数器对应的数据是否有偏移(offset)、可以使用uniform块来读取计数器中的数据、设置屏障来达到同步访问的目的…
我们下一篇将介绍纹理,我们要进行纹理贴图(我们的三角形就不再是简单的色块了,终于可以看到图案了)
(注:本篇的代码会在稍后上传到资源中,在最终的资源上传之前文中的代码可能会有所修改)
那我们下一篇见~~

关于书籍的问题
如果你手中没有该书,我还是建议你购买一本,毕竟书本毕竟更加严谨专业,我这里难免遗漏一些细节,主要是提供实例,并做一个消化,将很混乱的流程为大家理清,但这笔记一定是通俗的,是对新手友好的(当然有时候你需要在某些方面自己努努力,比如后面出现的基本线性代数的内容,还有C语言或是c++的基础知识,虽然我可能也不太懂O(∩_∩)O,慢慢来吧)。

别被吓住
刚开始的时候很容易被OpenGL的巨长的函数和超级复杂的流程吓到,其实并没有那么可怕,只要对这样或那样的流程熟悉之后,一切都变得相当简单(当然如果你能提出一个更好的流程那就更好了,当我们把很多基础的工作做完,我们会不断的提出新问题新点子,用新的技术来实现它,最终完成OpenGL的学习)

虽然我也不知道后面将是怎样的道路,但至少努力学习是没错的。

我看过的相关内容
以下并不是全看完了,大部分看了15%就看不下去了,实在是没看懂。(本人没什么计算机编程基础,算是野生程序员吧,很多内容都不能标准表述,望见谅)
如果你对opengl的工作有了一定的了解,我一开始也是从这里开始的,但是仍然有很多的不懂的,最后至今为止,我杂糅了很多的网站内容包括LearnOpenGL、极客学院、哔哩哔哩的闫令琪计算机图形学(闫令琪又开202课程了,可以围观一下)、哔哩哔哩的傅老师的OpenGL课程、OpenGL编程指南"也称为红宝书"、OpenGL超级宝典"也称为蓝宝书"、当然还有很多的csdn文章O(∩_∩)O这就不介绍了,等用到是时候我在放链接吧O(∩_∩)O

这里面闫令琪的图形学比较易懂也很基础推荐可以作为开始(如果你是学OpenGL需要马上用,应该可以跳过,但是其中的内容很是很重要,这会让马上要涉及变换透视的章节更加易懂,推荐大家看看),之后是蓝宝书或是极客学院翻译的教程比较推荐,这两个还是比较适合你我这样的新手的。
这里不推荐看的是红宝书,这本书我看了有点类似于字典那样的工具书,不太适合新手上手学,而且讲的也并不是很通俗易懂(可能是我的书版本比较老吧…)

加油
当然如果你对我有信心,我也会持续更新(虽然前路漫漫),跟大家一同进步(虽然很可能没人看(╥╯^╰╥),无所谓了,当然如有错误还请大家指正∠(°ゝ°),哪里不懂我会尽力解决,哪里说的不好也可以指出我会及时修改~)

我们下篇见~~

OpenGL超级宝典(第7版)笔记22 原子计数器 清单5.31-5.34相关推荐

  1. 《OpenGL超级宝典第5版》学习笔记(一)—— 第一个OpenGL程序

    // GLTools库包含了一个用于操作矩阵和向量的3D数学库,并依靠GLEW获得OpenGL3.3中用来产生和渲染一些简单3D对象的函数, // 以及对视觉平截头体.相机类和变换矩阵进行管理的函数的 ...

  2. OpenGL超级宝典第7版环境配置

    1.下载源码 地址:http://www.openglsuperbible.com/ 2.运行Cmake,编译glfw库 打开项目后,编译工程(Debug和Release下都编译). 把E:\open ...

  3. OpenGL超级宝典(第7版)笔记4 渲染管线介绍 清单2.3-2.7

    OpenGL超级宝典(第7版)笔记4 渲染管线介绍 清单2.3-2.7 文章目录 OpenGL超级宝典(第7版)笔记4 渲染管线介绍 清单2.3-2.7 1 OpenGL简介 2 OpenGL渲染管线 ...

  4. OpenGL超级宝典学习笔记——操作矩阵

    为了更强大的功能和灵活性,我们有时需要直接操作矩阵.在OpenGL中4x4的矩阵用包含16个浮点数值的一维数组来表示,而不是用二维的4x4的数组来表示.OpenGL之所以这么做,因为使用一维数组更高效 ...

  5. OpenGL超级宝典(第五版)环境配置

    本文转自:http://blog.csdn.net/sunny_unix/article/details/8056807,感谢作者分享. OpenGL超级宝典(第五版)环境配置 Vs2008+winX ...

  6. OpenGL超级宝典(第五版)环境配置【转】

    OpenGL超级宝典(第五版)环境配置 Vs2008+winXP  后续会整理Ubuntu 12.04LTS下的配置作者:sunny_unix 1.各种库的配置 (1)glew 下载:https:// ...

  7. OpenGL超级宝典(第五版) 环境配置

    特别提醒:有些在word中或者其他中的代码复制到vs中会报错,原因是word中有些隐含的字符,复制到vs中就会报错:重新输一遍就可以解决问题,这里只是提醒下! 可以参阅我前面转载的一篇文章,进行比较然 ...

  8. OpenGL超级宝典(第五版) 环境配置(WinXp+VS2008)

    转自:http://blog.csdn.net/sunny_unix/article/details/8056807 OpenGL超级宝典(第五版)环境配置 1.各种库的配置 (1)glew 下载:h ...

  9. [转]OpenGL超级宝典 5e 环境配置

    OpenGL超级宝典(第五版)环境配置 1.各种库的配置 (1)glew 下载:https://sourceforge.net/projects/glew/files/glew/1.7.0/glew- ...

  10. OpenGL超级宝典 5e 环境配置

    OpenGL超级宝典(第五版)环境配置 1.各种库的配置 (1)glew 下载:https://sourceforge.net/projects/glew/files/glew/1.7.0/glew- ...

最新文章

  1. Spring Boot 注解大全,一键收藏!回城路上复习!
  2. SVG动画.animateTransform
  3. module 'schedule' has no attribute 'every
  4. YARN中的失败分析
  5. php语言冒泡法,PHP实现冒泡排序算法的案例
  6. REG Delete用法
  7. 1.7_heap_sort_堆的向下调整堆排序
  8. undefined reference to `crypto_get_random'
  9. python之lxml.etree解析HTML
  10. plc和c语言和cnc,cnc数控编程和plc编程哪个难学
  11. Ionic3.x/Ionic4.x项目实战视频教程
  12. SHOPEX网店系统测试 旗下50万家网站的安全另人担忧
  13. android开发:NDK开发配置
  14. 【商业】梳理你的商业模式
  15. pcntl php windows_PHP各版本安装pcntl扩展
  16. 一个可以提升180%推广效果的信息流广告投放策略
  17. CentOS 7 网络配置
  18. 股票怎么量化选股?怎么在通达信接口运行公式?
  19. 2022年湖南省基金从业资格(证券投资基金基础知识)练习题及答案
  20. 微信支付(一) - 企业付款到用户零钱

热门文章

  1. Matlab视频系列教程-小木讲matlab-前12讲
  2. 中小企业IT建设的小看法2
  3. 汉字在字库中的偏移地址计算、显示方法
  4. 10款国外知名杀软的免费试用版(三个月、半年或一年)
  5. 白杨SEO:关键词定位与兴趣定位是什么?这两类推广渠道在网络营销上有什么差异?
  6. 操作系统 李治军 操作系统基础(一)
  7. 图像分割的衡量指标详解
  8. macOS 锐捷校园网解决方案
  9. SpringBoot中是如何创建WebServer的?
  10. java 删除文件或文件夹的7种方法(io基础)