OPENGLPG第九版学习 - 纹理与帧缓存 part2

文章目录

  • [6.10 纹理视图](#6.10 纹理视图)
  • [6.11 滤波方式(放大缩小)](#6.11 滤波方式(放大缩小))
  • [6.12 高级纹理查询函数](#6.12 高级纹理查询函数)
    • [6.12.1 显式的细节层次控制](#6.12.1 显式的细节层次控制)
    • [6.12.2 显式的梯度设置](#6.12.2 显式的梯度设置)
    • [6.12.3 带有偏移参数的纹理获取函数](#6.12.3 带有偏移参数的纹理获取函数)
    • [6.12.4 投影纹理](#6.12.4 投影纹理)
    • [6.12.5 在着色器中执行纹理查询](#6.12.5 在着色器中执行纹理查询)
    • [6.12.6 纹素收集](#6.12.6 纹素收集)
    • [6.12.7 组合功能的特殊函数](#6.12.7 组合功能的特殊函数)
  • [6.13 无绑定纹理与重绑定](#6.13 无绑定纹理与重绑定)
  • [6.14 稀疏纹理sparse texture](#6.14 稀疏纹理sparse texture)
    • [6.14.1 稀疏纹理的数据提交(传递数据)](#6.14.1 稀疏纹理的数据提交(传递数据))
    • [6.14.2 稀疏纹理的页面](#6.14.2 稀疏纹理的页面)
  • [6.15 点精灵](#6.15 点精灵)
  • [6.16 帧缓存对象](#6.16 帧缓存对象)
  • [6.17 渲染到纹理贴图RTT](#6.17 渲染到纹理贴图RTT)
    • [6.17.1 抛弃渲染数据](#6.17.1 抛弃渲染数据)
    • [6.17.2 渲染缓存renderbuffer](#6.17.2 渲染缓存renderbuffer)
    • [6.17.3 创建渲染缓存的存储空间](#6.17.3 创建渲染缓存的存储空间)
    • [6.17.4 帧缓存附件](#6.17.4 帧缓存附件)
    • [6.17.5 帧缓存的完整性](#6.17.5 帧缓存的完整性)
    • [6.17.6 帧缓存的无效化 / 声明可立即释放](#6.17.6 帧缓存的无效化 / 声明可立即释放)
    • [6.17.7 多重渲染缓存的同步写入](#6.17.7 多重渲染缓存的同步写入)
    • [6.17.8 选择颜色缓存来进行读写操作](#6.17.8 选择颜色缓存来进行读写操作)
    • [6.17.9 双源融混](#6.17.9 双源融混)

part 1

6.10 纹理视图

  • 一个纹理可以与另一个纹理或者多个纹理之间共享图像数据(共享存储数据)的方法,每个纹理可以有自己的格式和维度设置,以及多种不同的解释方式。

可以创建多个纹理对象,它们引用相同的底层数据,但可以有不同的格式、类型、维度、范围等。这在需要以不同方式解释相同图像数据时非常有用

  • 我们可以创建一个"父"纹理的纹理视图,它会对第一个纹理的底层存储空间做引用计数加1的操作,也就是将视图引用到存储空间上。
  • 创建好一个纹理视图之后,它可以在任何有关纹理的场合使用,例如加载图像并存储或者作为帧缓存的附件。
  • 我们也可以为纹理视图创建新的视图(以及进一步再创建视图),而这里的每个视图都引用了原始的数据空间并进行计数。
  • 我们也可以直接删除原始的父纹理。只要还有一个视图在引用数据本身,数据就不会被删除。
  • 创建纹理视图:
cpp 复制代码
//针对纹理 origtexture创建一个新的纹理视图,原始纹理的名称必须有效,并且已经初始化数据空间。
//texture会被关联到origTexture的存储空间,并且成为一个目标为target的永久纹理。
//texture的内部格式通过internalFormat来设置,它必须和origTexture的内部格式是兼容的(兼容见下表)。
//minLevel和numLevels分别设置了texture的第一个mipmap层以及 mipmap 的层次数量。
//minLayer和numLayers设置了texture的第一层切片和总切片数,如果它是纹理数组的话。
void glTextureView(	GLuint texture,
 	GLenum target,
 	GLuint origtexture,
 	GLenum internalformat,
 	GLuint minlevel,
 	GLuint numlevels,
 	GLuint minlayer,
 	GLuint numlayers);
  • 当我们创建已有纹理的视图的时候,新纹理的目标必须和已有纹理的目标是兼容的:
  • 除了目标的兼容性之外,新的视图的内部格式与原始父纹理也必须是相同的格式类别(例如纹素的位):
  • 根据上文给出的格式和目标兼容性要求,我们就可以同时用多种方式来解析纹理中的数据。

使用新的格式创建一个纹理视图

cpp 复制代码
//创建两个纹理名称:一个是父纹理;另一个就用作纹理视图
GLuint tex[2];
glGentextures(2,&tex);

//绑定第一个纹理并初始化数据
glBindTexture(GL_TEXTURE_2D,tex[0]);
glTexStorage2D(GL_TEXTURE_2D,10,GL_RGB8,1024,1024);

//现在创建纹理的视图,这一次使用GL_RGB8UI格式,即直接获取理的原始数据
glTextureView(tex[1],
			  GL_TEXTURE_2D,
			  tex[0],
			  GL_RGB8UI,//新的格式
			  0,10,//所有的 mipmap 层次
			  0,1,//只有一个切片);

使用新的目标创建一个纹理视图

cpp 复制代码
GLuint tex[2];
glGentextures(2,&tex);

glCreateTextures(GL_TEXTURE_2D_ARRAY,1,&tex[0]);
glCreateTextures(GL_TEXTURE_2D,1,&tex[1]);

//这一次我们会创建一个二维数组纹理,它的每一层都是256x256大小的纹素数据,共有100层
glTextureStorage3D(tex[0],8,GL_RGAB32F,256,256,100);
glTextureView(tex[1],//新的纹理视图
			  GL_TEXTURE_2D,//新的纹理目标
			  tex[0],
			  GL_RGAB32F,
			  0,8,
			  50,1//只用到其中一层切片,是100层中的第50层);

6.11 滤波方式(放大缩小)

  • 介绍多种不同的纹理元素群组合并的方法,这样可以减少图像的瑕疵,提升渲染图像的质量。
  • 纹理贴图可以是线性的、方形的、长方形的,甚至是三维的形式,但是当它被映射到多边形或者物体表面,再变换到屏幕坐标系之后,纹理上的独立纹素几乎不可能直接和屏幕上的最终画面的像素直接对应起来
  • 屏幕上的一个像素可能对应了纹理的一个纹素的一小部分(放大),或者纹理中大量的纹素集合(小):
  • 无论是哪一种情况,我们都无法精确知道应该使用哪些纹素值,以及如何对它们求平均或者插值。
  • 因此,OpenGL允许用户从多种不同的滤波选项 中进行选择,可以单独为放大和缩小设置设定不同的滤波方式
    不同的滤波选项在速度和画面质量之间做出了不同的权衡。
  • 如果纹理贴图需要同时在x和y方向被拉伸(或者收缩),那么肯定需要调用放大(或者缩小)设置。
  • 如果纹理贴图在一个方向上需要拉伸,在另一个方向上需要收缩,那么OpenGL会自动在放大和缩小选项之间做一个选择,大多能得到最好的结果:
    • 如果纹理在水平和垂直方向的扩张数值是不同的,例如当观察方向跟模型表面不是相互垂直的的情况下,会出现纹理信息的丢失,表现为图像看上去比较模糊,这被称作各向异性滤波 (anisolropic filtering)。使用参考。
    • 不过最好能够通过纹理坐标的调整,避免出现这样的选择和畸变。

6.11.1 线性滤波

  • 线性滤波技术的含义是:使用坐标值从一组离散的采样信号中选择相邻的采样点 ,然后将信号曲线拟合成线性近似的形式
    • OpcnGL会将用户传递的纹理坐标视为浮点数值,然后找到两个离它最近的采样点。坐标到这两个采样点的距离也就是两个采样点参与计算的权重,从而得到加权平均后的最终结果。
    • 因为线性重采样是可分解的,opengl可以先在一个维度上应用这个过程,然后在第二个维度上应用,从而重构二维图像,甚至重构三维图像。
  • 线性滤波不仅可以用在采样点到一维、二维和三维纹理的平滑变换过程中,它还可以用于纹理相邻 mipmap层之间的采样过程中的纹素混合操作。
  • 关于采样走样:

设置

  • 滤波选项都是通过OpenGL采样器对象 (可设置纹理默认的,也可以设置自定义采样器对象)中的纹理滤波(texturefilter)模式来控制的。
    • GL_TEXTURE_MIN_FILTER和GL_TEXTURE_MAG_FILTER(控制纹理放大的参数) 负责控制OpenGL纹理滤波的方式。
GL_TEXTURE_MAG_FILTER
  • 控制纹理放大的参数,也就是说,此时渲染需要的细节分辨率已经超过了最高分辨率的mipmap层(默认是第0层),mipmap计算的层级结果是一个小于等于0的数值。
  • 由于放大的情形下只有最高层级的mipmap会被使用,因此GL_TEXTURE_MAG_FILTER只有两个可以选择的参数:
    • GL_NEAREST:禁止滤波并直接返回与采样位置距离最近的纹素
    • GL_LINEAR:开启滤波。默认
GL_TEXTURE_MIN_FILTER
  • 纹理缩小的设置中 mipmap层次会起到作用,负责控制mipmap层次大于0的时候,纹素构建的方式。
  • 有六个可以选择的参数:GL_{A}_MIPMAP_{B}
    • 默认为GL_LINEAR_MIPMAP_LINEAR。
    • A和B可以是NEAREST或者LINEAR
    • A负责控制每个mipmap层次的纹素构成方式 ,与GL_TEXTURE_MAG_FILTER中的设置是按照同样的方式工作的。
      • 滤波方式为NEAREST,那么棋盘格花纹会变得十分明显;如果是LINEAR,那么效果会差一些,纹理显得有些模糊。
    • B负责控制采样点在两个 mipmap层次之间的融合方式 。如果设置为NEAREST,那么计算只用到最近的mipmap层。如果设置为LINEAR,那么两个最近的mipmap 层数据之间会进行线性的插值计算。
      • 滤波方式为NEAREST,那么纹理mipmap的边界 会变得清晰可见;如果是LINEAR,滤波后的边界会被掩盖掉。

6.11.2 使用和生成mipmap(多级渐远纹理)

  • 当贴了纹理的物体远离视点运动时,屏幕像素与纹理纹素之间的比率会变得非常低,因此纹理的采样频率也会变得非常低,这样会产生渲染图像上的瑕疵,因为有纹理数据的下采样(undersampling)的缘故。
  • 纹理采样的结果可能会在某个过渡点上发生突然的变化。
  • 即如果我们不使用mipmap的话,纹理被映射到较小的运动物体上之后可能会产生闪烁的问题。
  • 为了降低这个效果的影响,可以对纹理贴图进行提前滤波并且将滤波后的图像存储为连续的低分辨率的版本(原始图像为全分辨率,随这层级增加分辨率降低)。这就叫做mipmap。
  • OpenGL在使用mipmap的时候会自动判断 当前应当使用纹理贴图的哪个分辨率层级(纹理贴图的细节层次(level of detail) ),这是基于被映射的物体的尺寸(像素为单位)来决定的
  • mipmap需要一些额外的计算和纹理存储的区域。
  • 如果要使用mipmap,用户需要给出纹理从最大尺寸到1x1之间的所有尺寸,按照2的幂进行划分
  • 如果你不希望mipmap总是会递减到1x1纹理,也可以设置GL_TEXTURE_MAX_LEVEL,的值,决定程序所支持的最大层次。
  • 如果纹理最高分辨率层次的数据不是正方形的形状,我们需要继续构建新的层次,让另一个维度的数据继续见效,直到整个层数据的大小为1x1为止:

如果用户的最高分辨率贴图是64x16,那么我们还需要提供32x8、16x4、8x2、4x1、2x1和1x1尺寸的贴图。

  • 由于OpenGL可以不需要任何特殊的方法来计算低分辨率的贴图,因此这些不同尺寸的纹理之间可以是完全不相关的。但是实际上,不相关的纹理会导致mipmap层次之间的过渡变得十分明显:
  • 我们将纹理设置给一个巨大的沿着距离方向延伸的平面。这个平面距离观察者越远,它在屏幕空间所占的区域越狭窄,纹理被压缩的程度越高。OpenGL会自动选择连续的从高层次到低层次的 mipmap数据。

手动生成mipmap

  • glTexStorage2D分配纹理空间,然后调用glTexSubImage2D对纹理贴图的每个分辨率都要执行一次,但是level、width、height 和image 参数的设置都不相同
cpp 复制代码
glTexStorage2D(image->target,
                           image->mipLevels,
                           image->internalFormat,
                           image->mip[0].width,
                           image->mip[0].height);
for (level = 0; level < image->mipLevels; ++level)
{
     glTexSubImage2D(GL_TEXTURE_2D,
                           level,
                           0, 0,
                           image->mip[level].width, image->mip[level].height,
                           image->format, image->type,
                           image->mip[level].data);
}

opengl生成mipmap

  • OpenGL的具体实现会提供对应的机制来下采样高分辨率的图像,产生低分辨率的mipmap图,可以快速生成mipmap连续图像
  • 创建连续的每层纹理的滤波方法是由OpenGL的具体硬件平台自己完成的
  • target must be one of GL_TEXTURE_1D, GL_TEXTURE_2D, GL_TEXTURE_3D, GL_TEXTURE_1D_ARRAY, GL_TEXTURE_2D_ARRAY, GL_TEXTURE_CUBE_MAP, or GL_TEXTURE_CUBE_MAP_ARRAY。
cpp 复制代码
void glGenerateMipmap(GLenum target);
 
void glGenerateTextureMipmap(GLuint texture);
  • mipmap的层次 构建是通过GL_TEXTURE_BASE_LEVELGL_TEXTURE_MAX_LEVEL,来控制的。

6.11.3 计算mipmap层次

  • 某个像素点对应的纹理mipmap层级的计算,是取决于当前纹理图像和贴纹理的多边形的尺寸(以像素为单位)之间的缩放比例的,将这个缩放比例称作p
    • 因为纹理可能多维度,p是所有的维度中最大的缩放比例值。
    • λ是mipmap层次。小于0用纹理放大过滤器,大于0会调用纹理缩小滤波器。
    • 这个自动计算其实在内部基于纹理坐标在屏幕空间中的变化率(隐含导数,会被硬件自动计算:dFdx(uv)和dFdy(uv),仅片段着色器有效;可通过textureGrad设置)来实现。
    • 在正方形纹理、多重采样纹理,或纹理缓冲中不能带参数bias和lod,这是因为在这些纹理类型中不容许有 mipmap。

举例来说,如果纹理图像是64x64纹素大小,多边形的尺寸为32x32像素,p=2.0,由此得到λ=1.0;如果纹理图像变成64x32纹素,多边形尺寸8x16像素,p=8.0(x缩放为8.0,y缩放为2.0,取最大值),得到λ=3.0。


  • lod_bias是采样器的细节层次偏移值,是一个常数,通过glSamplerParameter或glTextureParameterf设置 ,pname参数为GL_TEXTURE_LOD_BIAS ,作用是调节λ。
    • 默认是0.0。
    • 限制范围:pname为GL_TEXTURE_MIN_LOD, GL_TEXTURE_MAX_LOD,分别默认-1000.0和1000.0。

6.11.4 mipmap细节层次的控制

  • mipmap的层次 构建是通过GL_TEXTURE_BASE_LEVELGL_TEXTURE_MAX_LEVEL ,来控制的。
    • GL_TEXTURE_BASE_LEVEL设置了采样所用的最低层次的 mipmap 纹理(也就是最高的分辨率),无视λ影响。
    • GL_TEXTURE_MAX_LEVEL设置了采样的最高mipmap层次(也就是最低的分辨率)。
  • GL_TEXTURE_BASE_LEVEL的一个潜在用法是纹理的流式加载(类似osg中lod和pagedlod):默认情况下,整个纹理对象的存储是通过glTextureStorage2D()来分配的,但是开始并不会加载数据。当用户程序运行并且新的对象进入视野的时候,它们的纹理数据将会按照低分辨率mipmap到高分辨率mipmap的顺序被加载进来(加载是按照从低到高层级加载,但是可以在使用glTexSubImage2D设置纹理的时候,低层级是高分辨率的还是低分辨率的纹理)。
  • 为了确保用户总能看到一些有意义的画面,设置GL_TEXTURE_BASE_LEVEL的值为当前已经加载过的最大分辨率的层次,即底层级的是高分辨率的纹理。

6.12 高级纹理查询函数

  • 除了简单的纹理采样函数,例如texture和texelFetch之外,着色语言还支持其他几种纹理获取的函数。
  • 在所有着色阶段纹理查找函数都可用,但是,只在片元着色阶段隐含地计算细节层次,所以OpenGL可以自动进行mipmap过滤。其他着色阶段使用细节层次为0或者直接使用没有 mipmap化的纹理。
  • 高级纹理查找函数可以进行合并,从而在一次函数调用中执行多个特殊函数。详见6.12.7。

6.12.1 显式的细节层次控制

  • 通常来说,使用mipmap,ogl会自行计算细节层次并得到mipmap层级的结果,再将采样结果返回给用户(参考6.11.3)。
  • 可以通过纹理获取函数的参数bias来显式设置细节层次,取代ogl的计算过程。
  • 或者使用textureLod
    • 这些函数并不支持mipmap,此外textureLod也不支持samplerBuffer 和samplerRect这两种采样器类型。
cpp 复制代码
gvec4 textureLod(	gsampler1D sampler,
 	float P,
 	float lod);
 
gvec4 textureLod(	gsampler2D sampler,
 	vec2 P,
 	float lod);
 
gvec4 textureLod(	gsampler3D sampler,
 	vec3 P,
 	float lod);
 
gvec4 textureLod(	gsamplerCube sampler,
 	vec3 P,
 	float lod);
 
float textureLod(	sampler1DShadow sampler,
 	vec3 P,
 	float lod);
 
float textureLod(	sampler2DShadow sampler,
 	vec3 P,
 	float lod);
 
gvec4 textureLod(	gsampler1DArray sampler,
 	vec2 P,
 	float lod);
 
gvec4 textureLod(	gsampler2DArray sampler,
 	vec3 P,
 	float lod);
 
float textureLod(	sampler1DArrayShadow sampler,
 	vec3 P,
 	float lod);
 
gvec4 textureLod(	gsamplerCubeArray sampler,
 	vec4 P,
 	float lod);

6.12.2 显式的梯度设置

  • 另一种方式来实现mipmap的细节层次计算。
  • 可以使用梯度纹理函数textureGrad
    • 6.11.3中ρ会通过dPdx和dPdy的形式传入。
    • 一般在可以获得纹理坐标的导数的解析公式,或者纹理坐标不可导的时候使用。
cpp 复制代码
//此外P在x和y方向的偏导数分别由dPdx 和 dPdy 给定。
gvec4 textureGrad(	gsampler1D sampler,
 	float P,
 	float dPdx,
 	float dPdy);

6.12.3 带有偏移参数的纹理获取函数

  • 有的程序需要获取某个区域中的多个纹素数据,或者需要在采样的时候稍微对纹理坐标进行偏移,可以使用textureOffset
cpp 复制代码
//从采样器tex中读取一个纹素,纹理坐标为P。
//可以用offset参数对纹理坐标进行偏移,然后再进行读取的工作。
gvec4 textureOffset(gsampler2D sampler,
 	vec2 P,
 	ivec2 offset,
 	[float bias]);
  • offset是一个整数值,必须是一个常量表达式,并且必须在内置GLSL常量gl_MinProgramTexelOffset和gl_MaxProgramTexelOffset范围之间。

6.12.4 投影纹理

  • 投影纹理(projective texture)也就是使用一个透视变换矩阵对纹理坐标进行变换。
  • 变换的输人 参数是一组齐次坐标,输出结果是变换后的向量,但是最后一个分量不一定是1。
  • 它会把结果的纹理坐标投影到纹理的坐标空间当中
  • 对于某些场合,例如将花纹投影到平面表面 上(或者手电在墙上照出一个光)以及阴影图映射(详见7.4),很有用。
  • textureProj
cpp 复制代码
//执行带有投影信息的纹理查找。
gvec4 textureProj(	gsampler2D sampler,
 	vec3 P,
 	[float bias]);

6.12.5 在着色器中执行纹理查询

  • 并不会真的从纹理中读取数据,而是会返回纹理数据处理的相关信息。
  • textureQueryLod返回 mipmap的处理信息
cpp 复制代码
//返回 mipmap的处理信息,
//其中x分量是当前访问的mipmap层级,
//y分量是当前计算得到的细节层(相对于纹理 base层次的差),LOD相对于基纹理的比例因子
vec2 textureQueryLod(gsampler2D sampler,
 	vec2 P);

textureLod(, , textureQueryLod(...).x);
  • 每个都有对应的textureQueryLevels会返回当前的 mipmap 层次数,即给定的采样器包含的mipmap层次数目。
cpp 复制代码
int textureQueryLevels(	gsampler2D sampler);
  • 获取正在采样的纹理的尺寸 可用textureSize,会返回纹理的某个层次的尺寸。
cpp 复制代码
//返回值的分量会按照纹理宽度、高度、深度的顺序进行填充。
//对于数组形式的纹理来说,返回值的最后一个分量表示数组中的切片数量。
ivec2 textureSize(gsampler2D sampler,int lod);
  • 还可以在着色器中获取一个多重采样纹理中,每个纹素的采样点数
cpp 复制代码
//返回tex中每个纹素对应的采样点数量。
int textureSamples(	gsampler2DMS sampler);
 
int textureSamples(	gsampler2DMSArray sampler);

6.12.6 纹素收集

  • 使用textureGather可以在着色器中直接读取四个采样值,以便从二维纹理(或者立方体映射、长方形纹理,以及对应的纹理数组)中创建双线性滤波的纹素结果。
  • 这里会读取单通道的纹理数据,可选的参数comp设置了要读取的分量通道的索引 。,0、1、2、3分别表示x、y、z、w分量,默认返回x分量
cpp 复制代码
gvec4 textureGather(gsampler2D sampler,
 	vec2 P,
 	[int comp]);
 

6.12.7 组合功能的特殊函数

  • 例如们需要的是投影纹理,然后又需要显式设置它的细节层次或者梯度,那么可以使用合并的函数textureProjLod或者textureProjGrad
  • 更多:详见ogl编程指南第九版附录C

6.13 无绑定纹理与重绑定

  • 到目前为止,当我们需要在着色器中使用纹理的时候,都需要将它绑定到纹理单元然后把纹理单元和着色器中的采样器关联起来,再使用内置函数读取纹理数据。
  • ogl支持的全局纹理单元和一个着色器中可以使用的最大纹理数量都是有限的。
  • 所以如果程序使用了大量纹理,可能会设计反复绑定和重新绑定纹理的操作。
  • 无绑定纹理(bindless texture):
    • 必须通过GL_ARB_bindless_texture 扩展来实现。
    • 可在着色器开头使用#extension GL_ARB_bindless_texture : require
    • 不需要将纹理和采样器关联起来,而是直接将纹理对象本身表达成一个64位的数字(GLuint64 ),这些64位的句柄的值是 OpenGL提供的
    • 可以把这个64位数值设置给一个uniform块或者顶点属性 ,甚至是从纹理 中获取。当我们在着色器中得到了这个64位的纹理句柄之后,就可以直接创建一个采样器
      • 这个采样器也可以通过一对32位的数值来创建。当采样器被创建之后,它就可以和其他类型的采样器一起在着色器中工作,从纹理中读取纹素。
    • 可以直接把采样器放置到uniform块当中,在应用程序中,它也被定义为一个64位的整数值。

6.13.1 纹理句柄

  • 获取一个纹理对象的句柄:
cpp 复制代码
GLuint64 glGetTextureHandleARB(GLuint texture​);

// 返回一个对应于纹理texture的64位句柄,同时使用采样器对象sampler的参数进行替代。
GLuint64 glGetTextureSamplerHandleARB(GLuint texture​, GLuint sampler​);
  • 一旦用户获取了纹理的句柄之后,纹理的参数(以及采样器参数)就会被烘焙到句柄中。之后再改变了纹理或者采样器的参数,句柄也依然是指向这个纹理或者采样器之前的参数,而不会发生变化。
  • 但是改变了纹理的内容,那么之后再进行采样的时候也会直接读取新的内容,而不是以前的。

6.13.2 纹理驻留

  • 在实际开始在着色器中使用纹理句柄之前,我们需要确定纹理本身是常驻的(resident)。
  • 纹理绑定到环境的时候,ogl会维护一个着色器可用的全部纹理的列表。
  • 在运行着色器代码之前,OpenGL会确认已经绑定的纹理中全部的数据都已经正确驻留在内存中,可以随时读取。
  • 无绑定纹理需要程序通知ogl是否可以在着色器中访问某个句柄,或者不可以访问它。
  • 告诉 OpenGL 可访问的纹理句柄:
cpp 复制代码
//向当前的驻留纹理列表中添加或者删除一个纹理句柄。
//纹理句柄是通过之前的glGetTextureHandleARB或glGetTextureSamplerHandleARB返回的。
//在着色器中访问一个没有驻留的纹理可能带来无法预知的问题,包括应用程序崩溃。
// 
void glMakeTextureHandleResidentARB(GLuint64 handle​);
void glMakeTextureHandleNonResidentARB(GLuint64 handle​);
  • 了判断纹理句柄是否已经驻留的函数:
cpp 复制代码
GIboolean IsTextureHandleResidentARB(GLuint64 handle);
  • 句柄本身不需要特地执行删除操作。经过glMakeTextureHandleResidentARB或glMakeTextureHandleNonResidentARB处理的句柄会持续有效,直到纹理被删除为止。
  • 当纹理被删除之后,它所产生的任何句柄都会无效化,并且不能再次被使用。

6.13.3 采样无绑定纹理

  • 都比直接调用 glBindTextureUnit()要快得多。

将句柄从CPU传递到着色器程序的指定uniform位置

cpp 复制代码
void glUniformHandleui64vARB(GLint location, GLsizei count, const GLuint64 *value);
  • 使用方法:
cpp 复制代码
// 程序:
GLuint textureArray[3]; //纹理数组

GLuint64 handles[3]; //纹理句柄
for (int i = 0; i < 3; i++)
{
	glBindTexture(GL_TEXTURE_2D, textureArray[i]);
	handles[i] = glGetTextureHandleARB(textureArray[i]);
	glMakeTextureHandleResidentARB(handles[i]);
}
                       
glUniformHandleui64vARB(glGetUniformLocation(programObject[0], "textArray"), 3,handles);


// 着色器1:
#version 430 core
#extension GL_ARB_bindless_texture : require

uniform sampler2D textArray[3];
void main()
{
    //纹理采样
    FragColor = texture(textArray[0], vTexPosition1);//以数组成员方式采样
}


// 着色器2:
#version 430 core
#extension GL_ARB_bindless_texture : require

uniform uint64_t textureHandle[3];
void main()
{
    //纹理采样
    sampler2D mySampler = sampler2D(textureHandle[0]);
    FragColor = texture(mySampler , vTexPosition1);//以数组成员方式采样
}

将采样器变量放置到uniform块中直接使用

  • 这种情况下,块中的采样器需要采取等价于GLuint64的主机端内存排列方式。
  • 可以映射一块缓存,然后将 GLuint64形式的句柄(由glGetTextureHandleARB()产生)写人到缓存中。
cpp 复制代码
// 程序:
GLuint64 handles[3]; //纹理句柄

// 查找Uniform的uniform缓存索引
GLuint uboIndex = glGetUniformBlockIndex(shaderProgram,"UBO_data");

//获取uniform块的大小:
//GLint uboSize;
//glGetActiveUniformBlockiv(shaderProgram,uboIndex,GL_UNIFORM_BLOCK_DATA_SIZE,&uboSize);


glGenBuffers(1,&ubo);
glBindBuffer(GL_UNIFORM_BUFFER,ubo);
glBufferData(GL_UNIFORM_BUFFER,3*sizeof(GLuint64),handles,GL_STATIC_DRAW);
//或者glBufferSubData(GL_UNIFORM_BUFFER, uboIndex , 3*sizeof(GLuint64), &handles);

glBindBufferBase(GL_UNIFORM_BUFFER, uboIndex, ubo);// 关联到指定索引的uniform块!

// 着色器:
#version 430 core
#extension GL_ARB_bindless_texture : require 
layout(binding = 0) uniform UBO_data//代表了uboIndex 为0
{
    sampler2D textArray[3];
};

6.14 稀疏纹理sparse texture

  • 介绍创建和使用超大纹理(无法全部进入内存)的方法,只有当前被激活使用的部分会装载到内存中。
  • 需要支持GL_ARB_sparse_texture扩展。

允许你使用一个比GPU物理内存大得多的纹理(e.g. 16384x16384 32bpp),它使用一种虚拟页的方法将大的纹理分成多个页, 这些页有的驻留在物理内存,有的没有,只有当需要的时候才加载它们。比方我们根据相机的视角变化,只加载那些在视野范围内可见的pages。

  • 分配巨大的稀疏纹理:
    • 开启属性之后,将为纹理分配虚拟空间,不会真的分配对应的物理空间。
cpp 复制代码
GLuint page_texture;

glGenTextures(1, &page_texture);
glBindTexture(GL_TEXTURE_2D, page_texture);

//开启它的稀疏属性
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SPARSE_ARB, GL_TRUE);
glTexStorage2D(GL_TEXTURE_2D, 15, GL_RGBA32F, 16384, 16384);

// commit a level 0 page.
glTexPageCommitmentARB(GL_TEXTURE_2D, 0, 0, 0, 0, 64, 64, 0, GL_TRUE);
// 4KB increase here.

6.14.1 稀疏纹理的数据提交(传递数据)

  • 从物理上给稀疏纹理传递数据:
cpp 复制代码
//level设置的是指定页面对应的纹理层次,它必须是0到纹理mipmap总层数减1之间的数值。
//commit:GL_TRUE=提交内存, GL_FALSE=释放
// 操作的数组层/深度层数量
void TexPageCommitmentARB(enum target,
                                  int level,
                                  int xoffset,
                                  int yoffset,
                                  int zoffset,
                                  sizei width,
                                  sizei height,
                                  sizei depth,
                                  boolean commit);//
//支持多线程异步提交(无绑定冲突)
//旧硬件兼容
 void TexturePageCommitmentEXT(uint texture,
                                      int level,
                                      int xoffset,
                                      int yoffset,
                                      int zoffset,
                                      sizei width,
                                      sizei height,
                                      sizei depth,
                                      boolean commit);

6.14.2 稀疏纹理的页面

  • 判断某个特定格式对应的页面大小:
    • pname传入GL_VIRTUAL_PAGE_SIZE_X,GL_VIRTUAL_PAGE_SIZE_Y,GL_VIRTUAL_PAGE_SIZE_Z
cpp 复制代码
void glGetInternalformativ(	GLenum target,
 	GLenum internalformat,
 	GLenum pname,
 	GLsizei bufSize,
 	GLint *params);
 
void glGetInternalformati64v(	GLenum target,
 	GLenum internalformat,
 	GLenum pname,
 	GLsizei bufSize,
 	GLint64 *params);
  • 某个纹理格式可能提供了多种不同的页面大小,判断一个给定的内部格式有多少种支持的页面大小(可用的虚拟页尺寸配置数量):
    • pname传入GL_NUM_VIRTUAL_PAGE_SIZES
    • 大多数格式为1。
    • 如果查询的结果返回0,那么这种格式中不支持稀疏纹理。
  • 选择纹理使用的尺寸和排列方式:
    • pname传入GL_VIRTUAL_PAGE_SIZE_INDEX_ARB,默认为0。
    • 如果页面大小的效率上有什么差异,OpenGL实现通常会把最佳的排列方式放在第一位,因此非特殊不用改。
cpp 复制代码
void glTextureParameteri(	GLuint texture,
 	GLenum pname,
 	GLint param);

6.15 点精灵

  • 介绍使用gl_PointCoord 来渲染点精灵的方法。
  • 本质上就是使用片元着色器来渲染OpenGL的点,使用点内的片元坐标来完成计算的过程。
  • 点内的片元坐标:gl_PointCoord ,专门用于点精灵(Point Sprites)的渲染。
    • 当使用GL_POINTS图元进行绘制时,每个点会被渲染为一个正方形(或圆形)的区域(即点精灵),[0.0, 1.0] 。在这个区域内,每个片元(像素)都有一个相对于该点精灵的纹理坐标,而gl_PointCoord就提供了这个坐标。
    • 如果例如如果gl_PointSize设置较大,那么一个点可能涉及多个片元,那么这些片元应该都在点精灵范围内。
    • 它的两个常见用法是作为纹理坐标使用(这也是点精灵这个名词的经典起源),或者用来解析计算颜色和覆盖度。

6.15.1 纹理点精灵

  • 在片元着色器中使用gl_PointCoord从纹理中直接查找纹素,就可以生成一个简单的点精灵。
  • 我们假设在一个以原点为中心,长度单位为2的立方体中随机渲染和放置400个点。
  • 每个点精灵都会将纹理渲染为一个正方形,然后对一个纹理进行采样。
  • 顶点着色器:
    • 使用gl_PointSize控制点精灵的大小,缩放比率取决于它们到近平面的距离,越远越小。
cpp 复制代码
uniform mat4 model_matrix;
uniform mat4 projection_matrix;
layout(location = 0)in vec4 position;
void main(void)
{
	vec4 pos = projection_matrix*(model_matrix* position);
	gl_PointSize = (1.0 - pos.z/pos.w)*64.0;
	gl_Position = pos;
}
  • 片元着色器:
cpp 复制代码
uniform sampler2D sprite_texture;
out vec4 color;
void main(void)
{
	color = texture(sprite_texture,gl_PointCoord);
}

解析颜色和形状(圆)

  • 片元:
cpp 复制代码
out vec4 color;
void main(void)
{
	const vec4 color1=vec4(0.6,0.0,0.0,1.0);
	const vec4 color2=vec4(0.9,0.7,1.0,0.0);
	
	 // 计算从点精灵中心(0.5,0.5)到当前片元的向量
	vec2 coord = gl_PointCoord - vec2(0.5);
	//方法一:圆外丢弃
	//float f=dot(coord ,coord );
	//if(f > 0.25)
		//discard;
	//方法二: 计算距离,超过0.5(即圆外)则丢弃
	if (length(coord) > 0.5) 
        discard;
	color = mix(color1,color2,smoothstep(0.1,0.25,f));
}

6.15.2 控制点的显示

  • 控制点在用户程序中的显示:
    • pname必须是GL_POINT_FADE_THRESHOLD_SIZE或者GL_POINT_SPRITE_COORD_ORIGIN。
  • GL_POINT_SPRITE_COORD_ORIGIN
    • 表示glPointCoord的原点。
    • 设置了gl_PointCoord.y在片元着色器中增加的方向(从上到下还是从下到上),默认是从上到下y方向增加,与片元的窗口坐标相反。
    • 设置为GL_LOWER_LEFT,gl_PointCoord.y和gl_FragCoord.y(表示了片元的实际窗口坐标)就是相同的。
    • param必须是GL_LOWER_LEFT or GL_UPPER_LEFT.。默认GL_UPPER_LEFT。
  • GL_POINT_FADE_THRESHOLD_SIZE
    • 点消隐的阈值,控制点以及点精灵的反走样方式(对每个片元做一次光照的采样计算)。
    • 如果点的大小比这个阈值更低的话(例如点经过光栅化之后的尺寸小于1.0),OpenGL就有权不再对这个点执行真正的反走样,而是直接将点的颜色与背景进行融混(对采样点的所有片元统一进行光照计算,然后通过点的消隐因数来衰减alpha分量)。
    • param必须是一个大于等于0的浮点数或者是一个包含了数据值的变量地址。默认1.0。
cpp 复制代码
void glPointParameterf(	GLenum pname,
 	GLfloat param);
 
void glPointParameteri(	GLenum pname,
 	GLint param);
 
void glPointParameterfv(	GLenum pname,
 	const GLfloat * params);
 
void glPointParameteriv(	GLenum pname,
 	const GLint * params);

6.16 帧缓存对象

  • 可以创建自己的帧缓存,并且将他们绑定到渲染缓存(renderbuffer)上,将数据拷贝的消耗最小化,同时对性能进行优化。

  • 帧缓存对象常用于离屏渲染技术、纹理贴图的更新,以及缓存乒乓技术(GPGPU中用到的一种数据传输方法)等。

  • 窗口系统所提供的帧缓存 是唯一可以被图形服务器的显示系统所识别的帧缓存,即在屏幕上看到的只能是这个帧缓存

  • 窗口系统的缓存和自己创建的帧缓存之间还有另外一个区别

    • 窗口系统所管理的帧缓存有自己的缓存对象 / 附件(颜色、深度和模板),它们诞生于窗口创建之时。
    • 自己创建的帧缓存需要创建额外的渲染缓存并与帧缓存对象相关联,例如颜色缓存 / 附件。
    • 窗口系统管理的缓存是无法与应用程序创建的帧缓存关联的,反之亦然。
  • 分配一个程序创建的帧缓存对象

cpp 复制代码
// 直接创建并初始化Framebuffer对象
//n为负数,将产生GL_INVALID_VALUE 错误
void glCreateFramebuffers(GLsizei n,GLuint *framebuffers);


// 仅生成ID,使用glBindFramebuffer后才会创建framebuffer资源
void glGenFramebuffers(	GLsizei n,GLuint *ids);
  • 绑定 / 激活
    • 任何后继的OpenGL渲染操作都会被导向到这个帧缓存对象中
    • 如果没有绑定任何的帧缓存对象,那么渲染操作会被导向到默认帧缓存里,也就是操作系统自身所提供的帧缓存。
    • 如果target为GL_DRAW_FRAMEBUFFER ,那么framebuffer设置的是绘制时的目标帧缓存。
    • 如果target为GL_READ_FRAMEBUFFER,那么framebuffer就是读取操作的数据源。
    • 如果target为GL_FRAMEBUFFER,那么framebuffer所设置的帧缓存是既可读也可写的。
    • 如果framebuffer设置为0的话,表示绑定目标到默认的窗口系统帧缓存。
    • 如果既不是0,也不是一个可用的帧缓存对象,则产生GL_INVALID_OPERATION。
cpp 复制代码
// 设置一个可读或者可写的帧缓存.
void glBindFramebuffer(	GLenum target,GLuint framebuffer);
  • 释放缓存
    • 如果某个缓存对象当前已经被绑定,那么删除它意味着帧缓存的目标被立即重置为0(也就是窗口系统的帧缓存),同时缓存对象本身被释放。
    • n为负数,会产生GL_INVALID_VALUE错误;如果传人的名称是未分配的,或者传入0,那么函数不会产生错误,而是忽略。
cpp 复制代码
void glDeleteFramebuffers(	GLsizei n,
 	GLuint *framebuffers);
  • 判断是否为程序纷分配的帧缓存对象
    • 如果framebuffer为0(窗口系统的默认帧缓存),或者值是未分配的,或者已经被glDeleteFramebuffers()删除,返回GL_FALSE。
cpp 复制代码
GLboolean glIsFramebuffer(GLuint framebuffer);
  • 设置帧缓存对象的参数
    • 帧缓冲还处于"未配置内容(attachment)"的状态,只有在还没有附加具体的内容时,设置的参数才会被用作默认值或配置。
    • pname必须是一下枚举:
      • GL_FRAMEBUFFER_DEFAULT_WIDTH:param 指定没有附件的 framebuffer 对象的假定宽度;如果 framebuffer 有 attachments,则使用这些 attachments 的宽度。
      • GL_FRAMEBUFFER_DEFAULT_HEIGHT:同上。
      • GL_FRAMEBUFFER_DEFAULT_LAYERS:param 指定没有附件的 framebuffer 对象的假定层数。如果帧缓冲区有附件,则使用这些附件的层数。
      • GL_FRAMEBUFFER_DEFAULT_SAMPLES:param 指定 framebuffer 对象中无附件的采样数。如果 framebuffer 有 attachments,则使用这些 attachments 的采样数。
      • GL_FRAMEBUFFER_DEFAULT_FIXED_SAMPLE_LOCATIONS:param 指定帧缓冲区是否应为虚拟映像中的所有纹素假定相同的样本位置和相同的样本数。1为true。
cpp 复制代码
void glFramebufferParameteri(GLenum target,
 	GLenum pname,
 	GLint param);
 
void glNamedFramebufferParameteri(GLuint framebuffer,
 	GLenum pname,
 	GLint param);
  • 附件
    • 创建帧缓存对象之后,还需要指定一处空间用作绘制操作或者读取操作 ,这处空间称作帧缓存附件(framebuffer attachment)

6.17 渲染到纹理贴图RTT

  • 解释使用帧缓存对象直接渲染场景到一张纹理贴图的过程。
  • 在渲染完成后,可以从帧缓存对象中解除对纹理贴图的关联,然后继续执行下一步的渲染。
  • 就算纹理已经被绑定成帧缓存附件并且正在进行写入操作,我们依然可以自由地读取纹理的内容。
  • 这种情况被称作帧缓存渲染的循环,因此两个同时进行的操作都可能产生不确定的结果。也就是说,从被绑定的纹理中采样读取的结果,以及写入到纹理某层的数据结果,都是不确定的,有可能完全不正确。
  • 将纹理贴图的一个层次关联到帧缓存附件中:
    • attachment must be GL_COLOR_ATTACHMENTi, GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT or GL_DEPTH_STENCIL_ATTACHMENT(纹理内部格式必须是GL_DEPTH_STENCIL)。
      • i may range from zero to the value of GL_MAX_COLOR_ATTACHMENTS minus one。
    • level:指定纹理的mipmap级别。
    • texture :如果为0,就表示任何绑定到attachment的纹理都会被释放掉,并且attachment上不会再有别的绑定物。如果为非0,必须是一个已经存在的纹理对象,且textarget必须与关联的纹理对象的类型相匹配。
    • textarget :对于立方体映射形式的纹理,texturetarget必须是立方体映射的某个面。否则生成GL_INVALID_OPERATION错误。
      • 如果是GL_RECTANGLE 或 GL_TEXTURE_2D_MULTISAMPLE那么level必须为0。
cpp 复制代码
void glFramebufferTexture(	GLenum target,
 	GLenum attachment,
 	GLuint texture,
 	GLint level);
 
void glFramebufferTexture1D(	GLenum target,
 	GLenum attachment,
 	GLenum textarget,
 	GLuint texture,
 	GLint level);
 
void glFramebufferTexture2D(	GLenum target,
 	GLenum attachment,
 	GLenum textarget,
 	GLuint texture,
 	GLint level);
 
void glFramebufferTexture3D(	GLenum target,
 	GLenum attachment,
 	GLenum textarget,
 	GLuint texture,
 	GLint level,
 	GLint layer);

void glNamedFramebufferTexture(	GLuint framebuffer,
 	GLenum attachment,
 	GLuint texture,
 	GLint level);
  • 一个数组纹理的某一层关联到帧缓存中:
cpp 复制代码
void glFramebufferTextureLayer(	GLenum target,
 	GLenum attachment,
 	GLuint texture,
 	GLint level,
 	GLint layer);
 
void glNamedFramebufferTextureLayer(	GLuint framebuffer,
 	GLenum attachment,
 	GLuint texture,
 	GLint level,
 	GLint layer);

创建纹理并且将它的某一层关联到缓存对象

cpp 复制代码
GLsizei TexWidth,TexHeight;
GLuint framebuffer,texture;

void init()
{
	glCreateTextures(GL_TEXTURE_2D,1,&texture);
	glTexturestorage2D(texture,1,GL_RGBA8,TexWidth,TexHeight);

	glCreateFramebuffers(1,&framebuffer);
	glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
	glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,texture,0);
	
}

void display()
{
	// 渲染到渲染缓存当中
	glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);
	glViewport(0,0,TexWidth,TexHeight);
	glClearColor(1.0,0.0,1.0,1.0);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	...
	// 生成纹理的mipmap
	glGenerateTextureMipmap(texture);

	// 解绑纹理(它可以贴到别的物体上了),然后绑定到窗口系统的缓存
	glBindFramebuffer(GL_FRAMEBUFFER, 0);
	glViewport(0,0,windowWidth,windowHeight);
	glClearColor(0.0,0.0,1.0,1.0);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	// 使用刚刚的纹理...
}

6.17.1 抛弃渲染数据

  • 我们总是需要在渲染新的一帧之前**清除(glClear*)**帧缓存数据。

但是,如果知道该怎么处理目前的帧缓存并且进行后继处理呢?这种时候再清除一次数据也许就是浪费了。

  • 想用新的渲染数据完全替换 / 抛弃 当前帧缓存的(全部 / 部分)内容
    • 通知OpenGL给定的帧缓存附件的内容将会被抛弃。
    • 对于非默认的帧缓存而言,attachments只能包含GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT GL_DEPTH_STENCIL_ATTACHMENT, or GL_COLOR_ATTACHMENTi。
    • 抛弃帧缓存中的内容,根据具体的OpenGL的不同,这个操作比直接清除可能要高效一点。
cpp 复制代码
void glInvalidateFramebuffer(GLenum target,
 	GLsizei numAttachments,
 	const GLenum * attachments);
 
void glInvalidateNamedFramebufferData(	GLuint framebuffer,
 	GLsizei numAttachments,
 	const GLenum *attachments);


void glInvalidateSubFramebuffer(	GLenum target,
 	GLsizei numAttachments,
 	const GLenum * attachments,
 	GLint x,
 	GLint y,
 	GLint width,
 	GLint height);
 
void glInvalidateNamedFramebufferSubData(	GLuint framebuffer,
 	GLsizei numAttachments,
 	const GLenum *attachments,
 	GLint x,
 	GLint y,
 	GLsizei width,
 	GLsizei height);
  • 不想抛弃帧缓存对象附件的数据,或是打算直接抛弃纹理内容
cpp 复制代码
void glInvalidateTexImage(	GLuint texture,
 	GLint level);


void glInvalidateTexSubImage(	GLuint texture,
 	GLint level,
 	GLint xoffset,
 	GLint yoffset,
 	GLint zoffset,
 	GLsizei width,
 	GLsizei height,
 	GLsizei depth);

6.17.2 渲染缓存renderbuffer

  • 是OpenGL所管理的一处高效的内存区域,它可以存储格式化的图像数据。
  • 专为深度、模板等非颜色数据优化的GPU内存块。
  • 无法直接采样(与纹理不同),但支持 高性能写入(适合深度测试/模板操作)。
  • 渲染缓存中的数据只有关联到一个帧缓存对象之后才有意义,并且需要保证**图像缓存的格式(分配存储空间时进行)**必须与OpenGL要求的渲染格式相符。
  • 创建新的渲染缓存
cpp 复制代码
void glCreateRenderbuffers(	GLsizei n,
 	GLuint *renderbuffers);
  • 释放渲染缓存所关联的内容
    • 如果某个渲染缓存当前已经被绑定,然后又调用glDeleteRenderbuffers,那么当前的帧缓存附件点会被重新绑定到0,再将这处染缓存释放。
    • 不会产生任何错误。如果名称是不可用的或者是0,它们将被直接忽略。
cpp 复制代码
void glDeleteRenderbuffers(	GLsizei n,
 	GLuint *renderbuffers);
  • 检查是否是一个合法的渲染缓存
    • 如果当前帧缓存为0(窗口系统的默认帧缓存),或者给定的值是未分配的,或者已经被 glDeleteRenderbuffers所删除,那么返回GL_FALSE。
cpp 复制代码
GLboolean glIsRenderbuffer(	GLuint renderbuffer);
  • 绑定和隐式创建渲染对象
    • target必须是GL_RENDERBUFFER。
    • renderbuffer为0,则表示移除当前的绑定。
cpp 复制代码
void glBindRenderbuffer(GLenum target,
 	GLuint renderbuffer);

6.17.3 创建渲染缓存的存储空间

  • 将渲染缓存关联到帧缓存并渲染到其中之前,需要分配存储空间并且设置图像格式。
    • 对于一个可以绘制颜色信息的缓存来说的internalformat枚举量参考。
    • 如果渲染缓存是作为深度缓存使用的,那么它必须可以写人深度信息,internalformat必须是GL_DEPTH_COMPONENT、GL_DEPTH_COMPONENT16、GL_DEPTH_COMPONENT24、GL_DEPTH_COMPONENT32或GL_DEPTH_COMPONENT32F。
    • 如果渲染缓存要作为模板缓存使用,那么internalformat必须是GL_STENCIL_INDEX、GL_STENCIL_INDEX1、GL_STENCIL_INDEX4、GL_STENCIL_INDEX8或GL_STENCIL_INDEX16。
    • 允许渲染缓存绑定到深度缓存或者模板缓存,甚至是合并的深度模板附件点,那么internalformat必须是GL_DEPTH_STENCIL。
    • 如果width或height超过GL_MAX_RENDERBUFFER_SIZE,或者samples超过GL_MAX_SAMPLES,将产生GL_INVALID_VALUE 。
    • 如果 internalformat是有符号或者无符号的整数类型(改I或UI),且samples非零,而且硬件实现无法支持多重采样的整数缓存的话,那么系统将产生一个GL_INVALID_OPERATION 错误。
    • 如果渲染缓存的大小和格式合起来超出了可分配的内存范围,系统将产生一个GL_OUT_OF_MEMORY 。
cpp 复制代码
// width和height用来设置渲染缓存的像素大小。
// samples可以设置逐像素多重采样的样本个数。
// glRenderbufferStorage相当于samples为0。
void glRenderbufferStorage(GLenum target,
 	GLenum internalformat,
 	GLsizei width,
 	GLsizei height);
 
void glNamedRenderbufferStorage(GLuint renderbuffer,
 	GLenum internalformat,
 	GLsizei width,
 	GLsizei height);

void glRenderbufferStorageMultisample(	GLenum target,
 	GLsizei samples,
 	GLenum internalformat,
 	GLsizei width,
 	GLsizei height);
 
void glNamedRenderbufferStorageMultisample(	GLuint renderbuffer,
 	GLsizei samples,
 	GLenum internalformat,
 	GLsizei width,
 	GLsizei height);
  • 创建一个256x256的RGBA颜色渲染缓存:
    • 当我们创建了渲染缓存的存储空间之后,就需要将它真正关联到帧缓存对象上了(通过帧缓存附件),然后渲染到这处缓存中。
cpp 复制代码
glCreateRenderbuffers(1,&color);
glNamedRenderbufferStorage(color,GL_RGBA,256,256);

6.17.4 帧缓存附件

  • 当我们开始渲染时,可以将渲染的结果保存到以下几个地方:
    • 创建图像到颜色渲染缓存,甚至是多个颜色缓存,前提是你使用了多重渲染目标(multiple render targets 6.17.7)
    • 将遮挡信息保存到深度缓存
    • 将逐像素的渲染掩码保存到模板缓存
  • 这些缓存类型每个都表示了一种帧缓存的附件,将对应的图像缓存(无论是刚渲染完成的,还是准备读取的),直接附加到帧缓存之上(通过将渲染缓存关联到对应帧缓存的对应附件上)。
  • 可用的缓存附件点:
附件名称 描述
GL_COLOR_ATTACHMENTi 第i个颜色缓存。i的范围从0(默认颜色缓存)到GL_MAX_COLOR_ATTACHMENTS
GL_DEPTH_ATTACHMENT 深度缓存
GL_STENCIL_ATTACHMENT 模板缓存
GL_DEPTH_STENCIL_ATTACHMENT ,用于保存压缩后的深度-模板缓存(此时需要渲染缓存的像素格式被设置为GL_DEPTH_STENCIL)
  • 可以将附件关联到两种不同类型的渲染表面上:
    • 渲染缓存
    • 纹理图像的某个层级
  • 渲染缓存关联到帧缓存对象
    • 将渲染缓存 renderbufer 关联到当前绑定的帧缓存对象 famebufer 的附件 attachment上。
    • renderbuffertarget必须是GL_RENDERBUFFER,renderbuffer必须是0(表示将附件所关联的渲染缓存移除)或者是glCreateRenderbuffers生成的名称,否则产生GL_INVALID_OPERATION。
cpp 复制代码
void glFramebufferRenderbuffer(GLenum target,
 	GLenum attachment,
 	GLenum renderbuffertarget,
 	GLuint renderbuffer);
 
void glNamedFramebufferRenderbuffer(GLuint framebuffer,
 	GLenum attachment,
 	GLenum renderbuffertarget,
 	GLuint renderbuffer);

关联染缓存并用做渲染

  • 注意:需要在渲染之前为每个帧缓存重设视口 ,尤其是应用程序的帧缓存大小与窗口系统提供的帧缓存大小不一致的时候。
  • 高效复制帧缓冲区数据的核心函数:
    • mask: GL_COLOR_BUFFER_BIT、GL_DEPTH_BUFFER_BIT、GL_STENCIL_BUFFER_BIT
cpp 复制代码
// 源矩形区域(左下角→右上角)
// mask复制内容掩码(颜色/深度/模板)
// filter缩放过滤方式(GL_NEAREST/GL_LINEAR)
void glBlitFramebuffer(	GLint srcX0,
 	GLint srcY0,
 	GLint srcX1,
 	GLint srcY1,
 	GLint dstX0,
 	GLint dstY0,
 	GLint dstX1,
 	GLint dstY1,
 	GLbitfield mask,
 	GLenum filter);
  • 交换缓冲区:
    • 只有当前绑定的GL_DRAW_FRAMEBUFFER是默认帧缓冲的后缓冲,就需要使用来立即显示内容。
    • 或者当需要将渲染结果显示在窗口上时,需要调用。
    • 最好作为渲染循环的最后一步,并且在glfwPollEvents之前调用。
cpp 复制代码
glfwSwapBuffers(window);
  • 帧率控制:锁定60FPS(每秒 60 帧)
cpp 复制代码
double lastTime = glfwGetTime();
while (...) {
    double current = glfwGetTime();
    if (current - lastTime < 1.0/60.0) continue;
    
    // ...渲染代码...
    glfwSwapBuffers(window);
    lastTime = current;
}
  • 示例:
cpp 复制代码
enum {Color,Depth,NumRenderbuffers};
GLunit framebuffer,renderbuffer[NumRenderbuffers];

void init()
{
	glCreateRenderbuffers(NumRenderbuffers,&renderbuffer);
	glNamedRenderbufferStorage(renderbuffer[Color],GL_RGBA,256,256);
	glNamedRenderbufferStorage(renderbuffer[Depth],GL_DEPTH_COMPONENT24,256,256);

	glGenFramebuffers(l,&framebuffer);
	glBindFramebuffer(GL_DRAW_FRAMEBUFFER,framebuffer);
	glNamedFramebufferRenderbuffer(framebuffer,GL_COLOR_ATTACHMENT0,GL_RENDERBUFFER,renderbuffer[Color]);
	glNamedFramebufferRenderbuffer(framebuffer,GL_DEPTH_ATTACHMENT,GL_RENDERBUFFER,renderbuffer[Depth]);
	glEnable(GL_DEPTH_TEST);
}

void display()
{
	//准备渲染到渲染缓存当中
	glBindFramebuffer(GL_DRAW_FRAMEBUFFER,framebuffer);
	//渲染之前为每个帧缓存重设视口
	glViewport(0,0,256,256);
	//渲染到渲染缓存
	glClearColor(1.0,0.0,0.0,1.0);
	g1Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	...
	//设置从渲染缓存读取,然后绘制到窗口系统的帧缓存中
	glBindFramebuffer(GL_READ_FRAMEBUFFER,framebuffer);
	glBindFramebuffer(GL_DRAW_FRAMEBUFFER,0);

	glViewport(0,0,winWidth,winHeight);
	glClearColor(0.0,0.0,0.0,1.0);
	g1Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	//执行从GL_READ_FRAMEBUFFER拷贝到GL_DRAW_FRAMEBUFFER缓冲区的操作
	glBlitFramebuffer(0, 0, 255, 255, 0, 0, 255, 255, GL_COLOR_BUFFER_BIT, GL_NEAREST);
	//交换缓冲区,使新渲染的内容立即可见
	 glfwSwapBuffers(window);
}

6.17.5 帧缓存的完整性

  • 帧缓存的当前状态进行一次检查:
    • target must be GL_DRAW_FRAMEBUFFER, GL_READ_FRAMEBUFFER or GL_FRAMEBUFFER. GL_FRAMEBUFFER is equivalent to GL_DRAW_FRAMEBUFFER.
    • 如果产生错误,返回值为0.
    • 帧缓存配置时产生的不同类型的错误:
帧缓存完整性状态枚举量 描述
GL_FRAMEBUFFER_COMPLETE 帧缓存和它的附件完全符合渲染或者数据读取的需
GL_FRAMEBUFFER_UNDEFINED 绑定的帧缓存可能是默认的帧缓存(read or draw),但是而默认的帧缓存不存在的
GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT 绑定的帧缓存没有设置必需的附件信息
GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 帧缓存没有关联任何的图像(例如纹理层或者渲染缓存)
GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER 每个绘制缓存(例如void glDrawBuffers(GLsizei n,const GLenum *bufs)所指定的GL_DRAW_BUFFERi)都必须有一个附件,如果该附件查询类型为GL_NONE则返回该错误
GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER 每个用gReadBuffer()设置的缓存都必须有一个附件
GL_FRAMEBUFFER_UNSUPPORTED 关联到帧缓存对象的图像数据与 OpenGL设备实现的需求不兼容
GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE 帧缓存各个附件所关联的所有图像的采样值数量互相不匹配
  • glDrawBuffers:void glDrawBuffers(GLsizei n,const GLenum *bufs);
    • 指导片段着色器的输出流向正确的目的地。
    • bufs是当前帧缓存GL_FRAMEBUFFER中的附件枚举。
    • 然后在片段着色器中通过例如layout(location = 0) out vec4 out0;来指定片段着色器的输出应该写入到哪个颜色附件(即哪个渲染缓存)。
  • glGetFramebufferAttachmentParameteriv:
    • void glGetFramebufferAttachmentParameteriv( GLenum target,
      GLenum attachment,
      GLenum pname,
      GLint *params);
    • 查询缓冲区附件属性。
    • 如果pname为GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE,则可以查询帧缓冲附件类型的关键枚举值。
    • 附件类型:GL_NONE、GL_FRAMEBUFFER_DEFAULT默认帧缓冲的附件、GL_TEXTURE附件是纹理对象、GL_RENDERBUFFER附件是渲染缓存对象、GL_TEXTURE_2D_MULTISAMPLE多重采样纹理、GL_TEXTURE_3D。
cpp 复制代码
GLenum glCheckFramebufferStatus(	GLenum target);
 
GLenum glCheckNamedFramebufferStatus(	GLuint framebuffer,
 	GLenum target);

6.17.6 帧缓存的无效化 / 声明可立即释放

  • 将帧缓存的一块区域或者整体声明为不再使用的,可以立即释放:
    • attachments(自定义):GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT GL_DEPTH_STENCIL_ATTACHMENT, or GL_COLOR_ATTACHMENTi,
    • attachments(默认):GL_FRONT_LEFT, GL_FRONT_RIGHT, GL_BACK_LEFT, GL_BACK_RIGHT, GL_AUXi, GL_ACCUM, GL_COLOR, GL_DEPTH, or GL_STENCIL.
    • target:GL_FRAMEBUFFER, GL_READ_FRAMEBUFFER or GL_DRAW_FRAMEBUFFER。
    • 标识符不对产生GL_INVALID_ENUM 。
    • 某个附件的索引大于等于颜色附件的最大索引值产生GL_INVALID_OPERATION 。
    • numAttachments、width或者height为负数,产生GL_INVALID_VALUE。
cpp 复制代码
//设置绑定的帧缓存对象的一部分或者整体将不再保留。
//左下角的坐标(x,y)和width与height指定的区域对附件而言都将不再使用。
void glInvalidateFramebuffer(	GLenum target,
 	GLsizei numAttachments,
 	const GLenum * attachments);
 
void glInvalidateNamedFramebufferData(	GLuint framebuffer,
 	GLsizei numAttachments,
 	const GLenum *attachments);


void glInvalidateSubFramebuffer(	GLenum target,
 	GLsizei numAttachments,
 	const GLenum * attachments,
 	GLint x,
 	GLint y,
 	GLint width,
 	GLint height);
 
void glInvalidateNamedFramebufferSubData(	GLuint framebuffer,
 	GLsizei numAttachments,
 	const GLenum *attachments,
 	GLint x,
 	GLint y,
 	GLsizei width,
 	GLsizei height);

6.17.7 多重渲染缓存的同步写入

  • 帧缓存对象的多渲染缓存(或者多纹理)特性,也就是在一个片元着色器中同时写人到多个缓存的能力,通常也叫做MRT(多重渲染目标)渲染
  • 须设置一个帧缓存对象并附加多组颜色值(可能还有深度和模板值)附件,再对片元着色器进行一定的修改:
    • 要使用layout(location = ?)来设置out变量与帧缓存附件之间的对应关系
    • 如果当前绑定的帧缓存的附件与当前绑定的片元着色器之间没有完全匹配,那么那些未对应的数据会直接被忽略。
cpp 复制代码
layout(location = 0) out vec4 out0;
layout(location = 1) out vec4 out1;
  • 也可以在着色器链接前使用函数来完成关联工作
    • 如果在着色器中设置了片元着色器的关联关系,这两个函数所设置的位置值不会产生作用。
    • 如果program不是一个着色器程序,或者index大于1,或colorNumber大于等于最大的颜色附件索引值,都将生成GL_INVALID_VALUE错误。
cpp 复制代码
void glBindFragDataLocation(GLuint program,
 	GLuint colorNumber,
 	const char * name);

//index 可以是零或一,以指定将颜色用作第一种或第二种颜色 输入到混合方程中。
void glBindFragDataLocationIndexed(GLuint program,
 	GLuint colorNumber,
 	GLuint index,
 	const char *name);
  • 着色器链接之后,获取片元着色器的变量输出位置和索引
    • 如果name不是一个可用的程序变量,或者program已经链接但是没有设置片元着色器,或者program没有完成链接的话,返回值均为-1。
    • program链接失败还会返回一个GL_INVALID_OPERATION错误。
cpp 复制代码
GLint glGetFragDataLocation(GLuint program,
 	const char * name);

GLint glGetFragDataIndex(GLuint program,
 	const char * name);

6.17.8 选择颜色缓存来进行读写操作

  • 绘制或者读取操作可以与以下几种颜色缓存的内容关联:
    • 默认帧缓存中的前、后、左前、左后、右前、右后缓存;
    • 用户自定义帧缓存对象中的前缓存,或者任意渲染缓存附件。
  • 使用glDrawBuffers 或者glDrawBuffer 来选择要写入的缓存。
    • 设置可以进行写人或者清除操作的颜色缓存,同时将禁止之前一次glDrawBuffers或者 glDrawBuffer所设置的缓存。
    • glDrawBuffer
      • 默认帧缓存buf:GL_NONE , GL_FRONT_LEFT , GL_FRONT_RIGHT , GL_BACK_LEFT , GL_BACK_RIGHT , GL_FRONT , GL_BACK , GL_LEFT , GL_RIGHT 和 GL_FRONT_AND_BACK ,GL_COLOR_ATTACHMENTi(i不大于GL_MAX_COLOR_ATTACHMENTS)。
      • 自定义帧缓存buf:GL_COLOR_ATTACHMENTi、GL_NONE 。
      • buf不符合则产生GL_INVALIDE_ENUM错误。
      • 名称中忽略了LEFT和RIGHT的枚举量,可以同时用于立体缓存中的左缓存和右缓存;名称中不包含FRONT或者BACK的枚举量也可以同时用于前缓存和后缓存。
      • 默认条件下,buf被设置为GL_BACK,以用于双重缓冲的情形。
    • glDrawBuffers
      • 默认帧缓存buf:GL_NONE , GL_FRONT_LEFT , GL_FRONT_RIGHT , GL_BACK_LEFT , GL_BACK_RIGHT、GL_COLOR_ATTACHMENTi。
      • 自定义帧缓存buf:GL_COLOR_ATTACHMENTi、GL_NONE 。
      • buf不符合则产生GL_INVALIDE_ENUM错误。
    • 支持最大绘制缓冲区数可以使用glGet与参数GL_MAX_DRAW_BUFFERS 查询。
    • 双重缓冲技术的时候,通常希望在后缓存中进行绘制(完成绘制之后再交换缓存,glfwSwapBuffers(window))。
    • 使用GL_FRONT_AND_BACK ,可以将双重缓冲的窗口作为一个单缓冲的窗口来使用,同时写人到前缓存和后缓存中。
cpp 复制代码
void glDrawBuffer(	GLenum buf);
 
void glNamedFramebufferDrawBuffer(	GLuint framebuffer,
 	GLenum buf);

void glDrawBuffers(	GLsizei n,
 	const GLenum *bufs);
 
void glNamedFramebufferDrawBuffers(	GLuint framebuffer,
 	GLsizei n,
 	const GLenum *bufs);
  • 使用glReadBuffer 来选择读取用的缓存,并作为glReadPixels 函数的数据源。
    • 设置可以用作像素读取的缓存,会禁止上一次使用 glReadBuffer所设置的缓存。
    • mode:GL_FRONT_LEFT, GL_FRONT_RIGHT, GL_BACK_LEFT, GL_BACK_RIGHT, GL_FRONT, GL_BACK, GL_LEFT, GL_RIGHT, GL_COLOR_ATTACHMENTi.
cpp 复制代码
void glReadBuffer(	GLenum mode);
 
void glNamedFramebufferReadBuffer(	GLuint framebuffer,
 	GLenum mode);
  • 如果帧缓存对象有多个附件的话,可以自由控制 作为附件的渲染缓存的各种功能以及判断是否启用了功能:
cpp 复制代码
// 开启或者关闭缓存 index的某项功能。
void glEnablei(	GLenum cap,
 	GLuint index);
 
void glDisablei(	GLenum cap,
 	GLuint index);

//判断缓存 index是否已经开启了某项功能
GLboolean glIsEnabledi(	GLenum cap,
 	GLuint index);

6.17.9 双源融混

  • 允许片段着色器输出两个颜色值(称为"src0"和"src1"),并在混合方程中同时使用这两个源与目标颜色进行混合。
  • 需要在片段着色器声明双输出,并且绑定到同一个位置location,但使用不同索引index:
cpp 复制代码
#version 330 core // OpenGL 3.3+ 核心模式
layout(location = 0, index = 0) out vec4 first_outColor;  // 源0(主颜色)
layout(location = 0, index = 1) out vec4 second_outColor;  // 源1(辅助颜色,如独立Alpha)
void main() {
    first_outColor= vec4(1.0, 0.0, 0.0, 0.5); // 示例:红色半透明
    second_outColor= vec4(0.0, 0.0, 1.0, 1.0); // 示例:蓝色(用于混合因子)
}
  • 当调用glBlendFunc、glBlendFunci、glBlendFuncSeparate或者glBlendFuncSeparatei时,GL_SRC_COLOR、GL_SRC_ALPHA、GL_ONE_MINUS_SRC_ALPHA还有GL_ONE_MINUS_SRC_COLOR都会使用first_outColor中的值作为混合方程式的输人;GL_SRC1_COLOR、GL_SRC1_ALPHA、GL_ONE_MINUS_SRC1_ALPHA还有GL_ONE_MINUS_SRC1_COLOR则会使用second_outColor中的值。
    • 例如 设置融混公式中的源和目标参数为GL_ONE和GL_SRC1_COLOR,此时公式为:
      RGBdst = RGBsrc0+RGBsrc1*RGBdst
      • 可以用在希望使用有色的镜面高光来渲染一个半透明的物体,物体颜色写入second_outColor,强光颜色写入first_outColor。

双源融混与多个片元着色器输出

  • OpenGL规范要求MAX_DUAL_SOURCE_DRAW_BUFFERS至少为1,但大多数实现只支持1个渲染目标。
  • 如果MAX_DUAL_SOURCE_DRAW_BUFFERS正好是1的话,就说明双源融混和多个绘制缓存的方法互相之间只能选择一者,无法放在一起使用。
相关推荐
西岸行者6 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意6 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码6 天前
嵌入式学习路线
学习
毛小茛6 天前
计算机系统概论——校验码
学习
babe小鑫6 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms6 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下6 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。6 天前
2026.2.25监控学习
学习
im_AMBER6 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J6 天前
从“Hello World“ 开始 C++
c语言·c++·学习