【OpenGL】蓝宝书第九章——高级缓冲区:超越基础水平

目录

获得数据

映射缓冲区

复制缓冲区

控制像素着色器表现,映射片段输出

新一代硬件的新格式

浮点——最终的真正精确

多重采样

整数

sRGB

纹理压缩


获得数据

介绍所有新的数据格式和使用方法,并介绍一些用来访问那些将要进行性能优化的缓冲区的重要方法。

映射缓冲区

在C++中,用glBufferData来进行开辟缓冲区空间和填充内容,如果想修改缓冲区内容,可用glMapBuffer和glMapBufferRange。glMapBufferRange是提供一个指向内存的指针,可用这个指针直接读取或更新某个缓冲区的数据,尽量避免读取和写入同时进行的情况。(注意:修改的是已经在GPU内存中的数据,但不会阻止GPU工作)

void *glMapBufferRange( GLenum target,
GLintptr offset,
GLsizeiptr length,
GLbitfield access);

target
指定glMapBufferRange缓冲区对象绑定到的目标,该对象必须是下表中的缓冲区绑定目标之一:

缓冲区绑定目标    用途
GL_ARRAY_BUFFER    Vertex attributes
GL_ATOMIC_COUNTER_BUFFER    Atomic counter storage
GL_COPY_READ_BUFFER    Buffer copy source
GL_COPY_WRITE_BUFFER    Buffer copy destination
GL_DISPATCH_INDIRECT_BUFFER    Indirect compute dispatch commands
GL_DRAW_INDIRECT_BUFFER    Indirect command arguments
GL_ELEMENT_ARRAY_BUFFER    Vertex array indices
GL_PIXEL_PACK_BUFFER    Pixel read target
GL_PIXEL_UNPACK_BUFFER    Texture data source
GL_SHADER_STORAGE_BUFFER    Read-write storage for shaders
GL_TEXTURE_BUFFER    Texture data buffer
GL_TRANSFORM_FEEDBACK_BUFFER    Transform feedback buffer
GL_UNIFORM_BUFFER    Uniform block storage

offset
指定要映射范围的缓冲区内的起始偏移量。

length
指定要映射的范围的长度。

access
指定访问标志的组合,以指示对该范围的所需访问。

access参数可选项表如下。

访问标记用    途
GL_MAP_READ_BIT返回可能用于读取缓冲区的指针
GL_MAP_WRITE_BIT返回可能用于修改缓冲区的指针
GL_MAP_INVALIDATE_RANGE_BIT表示OpenGL可以丢弃映射范围内以前的内容。范围内的数据将成为未定义的,直到应用程序进行更新
GL_MAP_INVALIDATE_BUFFER_BIT表示OpenGL可以丢弃整个缓冲区中以前的内容。缓冲区内的数据将成为未定义的,直到应用程序进行更新
GL_MAP_FLUSH_EXPLICIT_BIT以GL_MAP_WRITE_BIT方式使用这个位需要一个应用程序明确地通过调用glFlushMappedBufferRange更新的范围进行清理。如果这个位没有被指定,那么整个缓冲区将在调用glUnmapBuffer时被清理
GL_MAP_UNSYNCHRONIZED_BIT告诉OpenGL在进行映射之前不要试图对任何挂起的GPU写入这个缓冲区的操作进行同步

在完成映射缓冲区的更新之后,调用glUnmapBuffer来告诉OpenGL已经完成了这项工作.

GLint accessFlags = GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT;
GLint offset = 32 * 100;
GLint length = 32 * 48;
GLvoid *bufferData = glMapBufferRange(GL_TEXTURE_BUFFER, offset, length, accessFlags);
//在这里更新缓冲区
... ...
glUnmapBuffer(GL_TEXTURE_BUFFER);

在上方中我们使用了GL_MAP_FLUSH_EXPLICIT_BIT,可以用如下函数进行单独清理部分数据,其中target,offset,length要和glMapBufferRange时保持一致。

GLvoid glFlushMappedBufferRange(GLenum target, intprt offset, sizeiptr length);

glMapBuffer可代替glMapBufferRange来映射,它是用来指定整个缓冲区范围的,glMapBufferRange可指定部分范围。

GLvoid *bufferData = glMapBuffer(GL_TEXTURE_BUFFER, accessFlags);

之后的内容中会大量使用这些函数进行加载和更新GPU数据。

复制缓冲区

当数据被发送到GPU之后,可在缓冲区之间共享这些数据,或从一个缓冲区将结果复制到另一个缓冲区。

glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, readStart, writeStart, size);

指定读缓冲区复制到写缓冲区,从readStart读,并从wirteState写入,size是复制字节数,其中第一和第二个参数是缓冲区绑定点参数,这样就能从一个捆绑到GL_COPY_READ_BUFFER绑定点作为复制源的缓冲区复制到另一个捆绑到GL_COPY_WRITE_BUFFER绑定点的目标缓冲区了。

用法:为一个应用程序创建第二个OpenGL环境下的线程用于进行数据加载。在这里,glCopyBufferSubData用来在主环境中更新几何图形数据非常方法,而不需要主线程中断渲染。

控制像素着色器表现,映射片段输出

片段着色器的输出可以传给内建变量gl_FragColor或gl_FlagData,但这是旧的做法,新做法是自定义输出,如下。

out vec4 oStraightColor;
out vec4 oGreyscale;
out vec4 oLumAdjColor;

在连接着色器之前使用glBindFragDataLocation将输出映射到哪里。

glBindFragDataLocation(processProg, 0, "oStraightColor");
glBindFragDataLocation(processProg, 1, "oGreyscale");
glBindFragDataLocation(processProg, 2, "oLumAdjColor");
glLinkProgram(processProg);

注意:不能将一个输出分配给多个索引。

完整案例:https://blog.csdn.net/qq_39574690/article/details/115800203

glBindFragDataLocation将片段着色器输出变量名和索引捆绑,它对应着glDrawBuffers指定的输出映射列表。

在第十章会有更加高级的使用混合操作,在OpenGL3.3中,某些混合方程式需要一个片段着色器输出两个不同的颜色(即一个正常输出,一个用于混合?)

glBindFragDataLocationIndexed(program, colorNumber, index, outputName);

其中index为0时,将这个输出颜色作为第一个输入颜色使用(默认glBindFragDataLocation就是index为0的情况);为1时,作为第二个输入颜色用于混合(都输出了给谁混合?)

新一代硬件的新格式

浮点——最终的真正精确

意思就是诞生了HDR(High Dynamic Range),能够将颜色和阴影等参数使用1.18*10的-38次方到3.4*10的38次方之间的所有值,而不再是256个值了。

使用浮点格式

可以在RBO创建存储或分配纹理时使用新的格式GL_RGBA16F和GL_RGBA32F。

glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA16F, nWidth, nHeight);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA32F, nWidth, nHeight);

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, texWidth, texHeight, 0, GL_RGBA, GL_FLOAT, texels);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, texWidth, texHeight, 0, GL_RGBA, GL_FLOAT, texels);

浮点渲染缓冲区格式和纹理格式:

渲染缓冲区纹    理
GL_RGBA32FGL_RGBA32F
GL_RGBA16FGL_RGBA16F
GL_R11_G11 B10FGL_R11_G11 B10F
GL_RG32FGL_RG32F
GL_RG16FGL_RG16F
GL_R32FGL_R32F
GL_R16FGL_R16F
 GL_RGB32F
 GL_RGB16F

HDR

现代游戏应用都使用浮点渲染来生成所有我们希望看到的漂亮视觉效果,例如:光源泛光(light bloom)、镜头光晕(lens flare)、光线反射(light reflection)、光线折射(light refraction)、朦胧光线(crepuscular ray)以及尘埃和云雾等非均匀介质效果,通常它们都需要使用精确的浮点值才能实现。浮点缓冲区的HDR渲染能够使得明亮的区域真正地明亮,使阴影部分保持非常阴暗,同时又使得我们仍然能够看清这两种区域中的细节。

工业光学魔术公司简称工业光魔,开发了OpenEXR,它是一种帮助存储所有高保真图像处理所需的图像数据。可将OpenEXR图像看做是一个由照相机在不同的曝光等级下拍摄的多幅图像的组合。低曝光能拍摄到高亮区域的细节,而高曝光能拍摄到阴暗区域的细节。

若想在单个图像中所有细节都保留,需要使用浮点数据,浮点数据能保留真正的颜色进行输出,而不是将其截取到0.0到1.0之间然后再分成仅有的256个可能值,这样看起来会真实很多。

使用OpenEXR

创建一个RGBAInputFile对象,将想要打开的文件名传递给它的构造函数即可。(这部分原文代码中没有出现,可认为file就是RGBAInputFile对象)相关代码如下。

//OpenEXR图像的缓存2D rgba数组
Array2D<Rgba> pixels;
//获取图像文件的窗口数据
Box2i dw = file.dataWindow();
//根据窗口数据计算出图像宽度和高度
texWidth = dw.max.x - dw.min.x + 1;
texHeight = dw.max.y - dw.min.y + 1;
pixels.resizeErase(texHeight, texWidth);

//设置EXR图像数据开始填充位置配置
file.setFrameBuffer(&pixels[0][0] - dw.min.x - dw.min.y * texWidth, 1, texWidth);
file.readPixels(dw.min.y, dw.max.y); //开始从EXR图像读取数据到pixels

//将RGBA二维数组转为一维浮点数组
GLfloat* texels = (GLfloat*)malloc(texWidth * texHeight * 3 * sizeof(GLfloat));
GLfloat* pTex = texels;
//将OpenEXR复制到本地缓冲区,准备加载纹理
for(unsigned int v = 0; v < texHeight; v++)
{
   for(unsigned int u = 0; u < texWidth; u++)
   {
      Imf::Rgba texel = pixels[texHeight - v - 1][u]; //RGBA数据 
      pTex[0] = texel.r;   //R分量存储到浮点数组
      pTex[1] = texel.g;   //G分量存储到浮点数组
      pTex[2] = texel.b;   //B分量存储到浮点数组
      pTex += 3;  //指针地址+3
   }
}

//使用已经转化成GLfloat类型的纹理数据texels放入纹理对象中
glBindTexture(GL_TEXTURE_2D, textureName);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB16F, texWidth, texHeight, 0, GL_RGB, GL_FLOAT, texels);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
free(texels); //释放

色调映射

色调映射是将颜色数据从一组颜色映射到另一组颜色,或者从一个颜色空间映射到另一个颜色空间的操作。因为我们不能直接显示浮点数据,所以这些数据必须将色调映射到一个能够被显示的颜色空间。

示例程序:https://blog.csdn.net/qq_39574690/article/details/115840526

向场景添加泛光

泛光简单来说就是过于明亮的光照导致物体被吞没的现象。

示例程序:https://blog.csdn.net/qq_39574690/article/details/115841569

浮点深度缓冲区

默认使用的是定点深度缓冲区,它是24位的,提供了16 777 216种可能的深度值,通常会被缩放到0.0到1.0之间。但当存在几何图形的深度值几乎一样时,定点深度缓冲区就会认定为同一个深度值,它会被取舍到相同的深度值,从而出现一些精度问题,如深度冲突。但浮点深度缓冲区就能解决这种情况,但会带来内存和性能开销,而且即使使用浮点深度缓冲区,几何图形太近的话仍然会造成Z冲突的。为此我们可以将几何图形进行了伸展,超越典型的0.0-1.0范围,充分利用浮点存储方式。

OpenGL提供了渲染的裁剪区(clip volume),落在裁剪区外的几何图形会被裁剪掉,而不会进行光栅化,裁剪区通常视为视椎体区域,离得太远或太近的物体将会被裁剪掉,但处理阴影时我们需要所有物体的深度信息,所以必须关闭裁剪,即glDisable(GL_DEPTH_CLAMP)来关闭深度裁剪,默认情况是关闭的。

多重采样

即一个片段进行了多次采样来避免锯齿效果出现,它会在每个像素位置在稍有不同的位置生成几个片段,这些片段成为亚像素(subpixel)。这些亚像素还必须进行“解析”,然后才能显示。对一个多重采样缓冲区进行解析,就是将所有亚像素一起进行平均,以确定最终的像素颜色。

亚像素的位置并不是有规律分布的。取而代之的是,亚像素位置是在像素区域进行伪随机(pseudorandomly)分布的。这种方式增强了多重采样的抗锯齿效果。例如有:2xMSAA、4xMSAA、8xMSAA分布区域,分别是2个,4个,8个亚像素位置(顾名思义抗锯齿效果越大越好,但内存和性能开销越大)

函数介绍:

glGetIntegerv(GL_SAMPLES, &sampleCount) //获取多重采样个数
glGetMultisamplefx(GL_SAMPLE_POSITION, index, &positions[i*2]); //获取每个亚像素的实际位置

glGenTextures(1, hdrTextures);  //生成RBO缓冲区的纹理
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, hdrTextures[0]); // 纹理对象捆绑多重采样2D纹理
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 8, GL_RGB16F, screenWidth, screenHeight, GL_FALSE);  //分配多重采样纹理纹理对象空间

iTmp = textureSize(origImage); //获取多重采样纹理的大小
tmp = floor(iTmp * vTex); 
vec4 vBaseImage = texelFetch(origImage, ivec2(tmp), sampleNumber); //使用一个整数片段位置进行采样。

hdr_msaa示例程序:https://blog.csdn.net/qq_39574690/article/details/115876632

整数

书中说的一堆话,简单来说就是整数格式支持的比较全面了现在,例如hdr_msaa的texelFetch函数使用的是整数纹理数据

texelFetch(origImage, ivec2(tmp), i)

对绑定到PBO的基于整数格式的缓冲区进行清除,使用glClearBufferiv,对无符号整数缓冲区则可用glClearBufferuiv。(其实是不太理解书上说的是啥米鬼东西) 

sRGB

sRGB是一种颜色空间,传统上使用的是RGB颜色空间。RGB颜色空间使用从0到1的值,但是对最终结果应用了一种线性伽马渐变,扩展了它能够显示的颜色范围。

sRGB光谱中更深的颜色使用一个接近2.2的伽马值,但是在更亮的区域使用一个达到2.4的伽马值,意味着sRGB格式有一个内建的扩展动态范围。

sRGB最初是为了在图像和照片处理中用来帮助办公室和暗房这样的典型观察环境中更好地映射和显示颜色而创建的。

在OpenGL中,sRGB格式的纹理被采样时会被转换成RGB格式,值小于0.04045会进行sample = texel / 12.92 ,大于0.04045的会进行sample = ((texel + 0.055) / 1.055)的2.4次方。
渲染缓冲区格式支持sRGB存储格式,必须支持GL_SRGB8_ALPHA8格式。可通过调用glEnable(GL_FRAMEBUFFER_SRGB)自动将着色器输出的线性颜色转换为sRGB值,这种方式只适用于包含一个sRGB表面的绑定,可调用glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING)来得知是否表面,若返回的是GL_SRGB则为SRGB表面,否则为GL_LINEAR。

这种对于片段颜色(fc)的变换遵循如下表列出的方程式

片段之变换方程
fc  <= 0.00.0
0.0 < fc < 0.003130812.92 * fc
0.0031308 < fc < 1.0 1.055 * fc 0.41666 - 0.055
fc > 1.0 1.0

纹理压缩

纹理压缩——一种解决大量纹理数据的存储和使用的方式,即将数据进行压缩。

RGTC(红-绿纹理压缩)是将一个纹理图形分解成4*4纹理单元块,使用一系列的代码将独立的通道压缩到这个块中,这种压缩格式只适用于一个或两个通道的有符号和无符号纹理。RGTC所节省的空间是50%。

格式类型
GL_COMPRESSED_REDGeneric
GL_COMPRESSED_RGGeneric
GL_COMPRESSED_RGBGeneric
GL_COMPRESSED_RGBAGeneric
GL_COMPRESSED_SRGBGeneric
GL_COMPRESSED_SRGB_ALPHAGeneric
GL_COMPRESSED_RED_RGTC1RGTC
GL_COMPRESSED_SIGNED_RED_RGTC1RGTC
GL_COMPRESSED_RG_RGTC1RGTC
GL_COMPRESSED_SIGNED_RG_RGTC1RGTC

前六种是通用的,允许OpenGL驱动程序决定使用哪种压缩机制,意味着驱动程序能够使用最适合当前情况的格式,不足的是这种方式是与实现相关的,无法移植。

实现可能还会支持其他压缩格式例如ETC1、S3TC

应首先查询OpenGL没有要求的格式的适用性,然后在尝试使用它们,最好的方式就是查询对相关扩展名的支持,13,14,15章节会介绍。

使用压缩

①可以加载一个未经压缩的纹理时交给加载纹理函数进行压缩纹理,在采样这个被OpenGL压缩的纹理时会解压,之后才会被采样出颜色值。

相关文章:https://blog.csdn.net/qq_39574690/article/details/115281922

简单来说就是:glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB, nWidth, nHeight, 0, eFormat, GL_UNSIGNED_BYTE, pBits); 加载纹理时使用GL_COMPRESSED_RGB 压缩格式进行加载。

②直接加载压缩过的纹理,glCompressedTexImage1D、glCompressedTexImage2D、glCompressedTexImage3D方法 类似glTexture2D使用即可,加载后的压缩纹理可通过如下代码获取,必须保证有足够的大小空间缓存。

GLint imageSize = 0;
glGetTexParameteri(GL_TEXTURE_2D, TEXTURE_COMPRESSED_IMAGE_SIZE, &imageSize);
void *data = malloc(imageSize);
glGetCompressedTexImage(GL_TEXTURE_2D, 0, data);

共享指数

它是一种在使用浮点纹理数据时节省空间的手段,共享指数对整个纹理单元使用同样的指数值,每个值的小数和指数部分都会以整数形式进行存储,然后在对纹理采样时将它们组合到一起。大白话来说就是将RGB值原本是分别存储在不同的8位寄存器的,然后变成了首先将浮点值划分为整数部分和指数部分,将RGB的整数部分存储在一起,指数部分均相同存储起来。例如:(0.536610, 0.355603, 0.335896)变为( 整数536610 355603 335896 指数-6)其GL_RGB9_E5格式 就是将9位用于存储颜色RGB整数部分,5位用于存储RGB通道的通用指数,一共用了14位,比起24位的情况节省了不少空间。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值