OpenGL蓝宝书第九章学习笔记:片段着色器和帧缓存

前言
本篇在讲什么

OpenGL蓝宝书第九章学习笔记之片段着色器和帧缓存
本篇适合什么

适合初学OpenGL的小白
本篇需要什么

对C++语法有简单认知
对OpenGL有简单认知
最好是有OpenGL超级宝典蓝宝书
依赖Visual Studio编辑器

本篇的特色

具有全流程的图文教学
重实践,轻理论,快速上手
提供全流程的源码内容


★提高阅读体验★

♠ 一级标题

♥ 二级标题

♣ 三级标题

♦ 四级标题


目录

  • ♠ 本章的学习要点
  • ♠ 片段着色器
    • ♥ 插值与存储限定符
      • ♣ 禁用差值
      • ♣ 无透视校正的插值
  • ♠ 单片段测试
    • ♥ 剪裁测试
    • ♥ 模板测试
    • ♥ 深度测试
      • ♣ 控制深度缓冲更新
      • ♣ 深度夹紧
  • ♠ 颜色输出
    • ♥ 混合
      • ♣ 混合函数
    • ♥ 逻辑运算
    • ♥ 颜色遮罩
  • ♠ 离屏渲染
    • ♥ 多个帧缓冲附件
    • ♥ 分层渲染
    • ♥ 帧缓存的完整性
      • ♣ 附件完整性
      • ♣ 全帧缓存完整性
      • ♣ 检查帧缓冲
    • ♥ 立体渲染
  • ♠ 反混叠
    • ♥ 过滤法反混叠
    • ♥ 多样本反混叠
    • ♥ 多样本纹理
      • ♣ 样本覆盖率
    • ♥ 采样率着色
    • ♥ 重心采样
  • ♠ 高级帧缓冲格式
    • ♥ 无附件渲染
    • ♥ 浮点帧缓冲
      • ♣ 使用浮点格式
    • ♥ 整数帧缓冲
    • ♥ SRGB颜色空间
  • ♠ 点精灵
    • ♥ 点纹理化
    • ♥ 点参数
    • ♥ 有形点
    • ♥ 旋转点
  • ♠ 获取图像
    • ♥ 从帧缓存中读取
    • ♥ 在帧缓冲之间复制数据
    • ♥ 读取纹理数据
  • ♠ 推送
  • ♠ 结语


♠ 本章的学习要点

  • 如何将数据传递到片段着色器,如何控制数据发送给片段着色器的方式以及片段着色器后如何处理数据
  • 如何创建自己的帧缓存并控制它们存储的数据格式
  • 如何从单个片段着色器生成多个输出
  • 如何获取帧缓冲数据并将数据输入纹理、缓冲以及应用程序的内存

♠ 片段着色器

片段着色器确定每个片段的颜色,然后组合发送给帧缓冲
默认情况下,片段着色器的所有输入块都平滑地在经过光栅化的基元上进行插值,该插值的端点由前端最后一个阶段提供(顶点着色器或曲面细分阶段)
你可以在很大程度上控制插值方式甚至决定是否插值


♥ 插值与存储限定符

GLSL支持的一些存储限定符,有一些存储限定符可用于控高级渲染时可使用的插值


♣ 禁用差值

每当从前端传递一个整数到后端时,就必须禁用插值,这一设置是自动完成的,因为 OpenGL王法平滑地插值整数

要为未插的片段着色器创建平场输入,可以使用’flat’存储限定符进行声明

flat in vec4 foo;
flat in int bar;
flat in mat3 baz;

可以将插值限定符应用于输入块
可以对输入块的各元素应用不同的限定符

flat in INPUT BLOCK
{
    vec4 foo;
    int  bar;
    smooth mat3 baz;
}

上述代码foo已禁用插值,因为它从父输入块继承了flat限定
bar是一个整数,因此自动禁用插值
baz虽然父块是禁用限定符flat,但是因为因为限定符smooth存在,所以依旧是平滑插值

当渲染基元为线条或三角形时,则使用基元的第一个或最后一个顶点,可以通过调用以下函数决定

void glProvokingVertex(GLenum provokeMode);

♣ 无透视校正的插值

因为空间是立体的,所以三角形对于视线来说很少是直接垂直的,这意味屏幕空间内的步长不是线性相关,OpenGL通过使用透视校正插值来纠正这个问题

如果要在屏幕空间中执行插值而不考虑基元方向,则可使用noperspective存储限定符


♠ 单片段测试

片段着色器运行后,会对片段进行了许多其他测试,以确定是否以及如何将其写入帧缓存。包括剪裁测试模板测试以及深度测试


♥ 剪裁测试

剪裁测试默认是禁用的,OpenGL支持很多剪裁矩形。如果要设置这些矩形则可调用glscissorIndexed()glScissorIndexedv(),其原型为

void glScissorIndexed(GLuint indexGLint left,GLint bottom,GLsizei width,GLsizei height)

void glScissorIndexedv(GLuint index,const GLint * v)

可以通过调用以下函数设置每个剪裁工具的矩形,无论OpenGL实现支持多大的数量

void glScissor(GLint x, GLint y,GLsizei width,GLsizei height);

全域启动裁剪测试

glEnable(GL_SCISSOR_TEST)

禁用剪裁测试

glDisable(GL_SCISSOR_TEST)

单个视口矩形启用剪裁测试

glEnablei(GL_SCISSOR_TEST,index);

单个视口矩形禁用剪裁测试

glDisablei(GL SCISSOR_TEST,index);

♥ 模板测试

简单理解,用镂空的纸板在墙上喷漆,只有镂空的位置会形成图案

启用模板测试

glEnable(GL_STENCIL_TEST)

glstencilFuncSeparate()该命令用于控制模板测试通过或不通过的条件,其原型如下

void glStencilFuncSeparate(GLenum faceGLenum func,GLint ref,GLuint mask);

可为face传递GL_FRONT、GL_BACK或GL_FRONTAND_BACK,放大将受到影响的几何体
func的值可以是下表中的任意值,这些值指定了几何体通过模板测试的条件

功能 通过条件
GL_BQUAL 参考值等于缓冲值
GL_GEQUAL 参考值大于或等于缓冲值
GL_GREATER 参考值大于缓冲值
GL_NOTEQUAL 参考值不等于缓冲值
GL_EQUAL 参考值等于缓冲值
GL_GEQUAL 参考值大于或等于缓冲值
GL_GREATER 参考值大于缓冲值
GL_NOTEQUAL 参考值不等于缓冲值

ref值是用于计算结果通过或失败的引用,mask参数可以控制将哪些引用位与缓存进行比较

glstencilopSeparate()告诉OpenGL在模板测试通过或失败后应如何处理,其原型如下

void glstencilOpSeparate(GLenum face,GLenum sfail,GLenum dpfail,GLenum dppass)

参数face指定哪些面会受到影响。接下来的3个参数控制执行模板测试后发生的事情,可以是下表中的任意值

功能 结果
GL_KEEP 不修改模板缓冲
GL_ZERO 将模板缓冲值设置为0
GL_REPLACE 将模板值替换成参考值
GL_INCR 带饱和度的增量模板
GL_DECR 带饱和度的减量模板
GL_INVERT 按位倒转模板值
GL_INCR_WRAP 无饱和度的增量模板
GL_DECR_WRAP 无饱和度的减量模板

sfail是模板测试失败后行的操作
dpfail参数指定深度缓冲测试失败后执行的操作
dppass指定深度冲测试通过后执行的操作


♥ 深度测试

启用深度测试

glEnable(GL_DEPTH_TEST)

禁用深度测试

glDisable(GL_DEPTH_TEST)

要设置深度比较运算符(或深度函数),则调用glDepthFunc(),其原型为

void glDepthFunc(GLenum func);

参数func是一种可用的深度比较运算符,func的选择可见下表

功能 含义
GL_ALWAYS 深度测试始终通过一所有片段均视为已通过深度测试
GL_NEVER 深度测试从未通过一一所有片段均视为未通过深度测试
GL_LESS 如果新片段的深度值小于旧片段的深度值,则通过深度测试
GL_LEQUAL 如果新片段的深度值小于或等于旧片段的深度值,则通过深度测试
GL_EQUAL 如果新片段的深度值等于旧片段的深度值,则通过深度测试
GL_NOTEQUAL 如果新片段的深度值不等于旧片段的深度值,则通过深度测试
GL_GREATER 如果新片段的深度值大于旧片段的深度值,则通过深度测试
GL_GEQUAL 如果新片段的深度值大于或等于旧片段的深度值,则通过深度测试

♣ 控制深度缓冲更新

glDepthMask()函数空值深度缓存的写入,GL_TRUE则启用,GL_FALSE则禁用

glDepthMask(GL_FALSE);

♣ 深度夹紧

OpenGL可以选择关闭对近平面和远平面的裁剪,将生成的深度值限制在0~1

启用深度夹紧

glEnable(GL_DEPTH_CLAMP)

禁用深度夹紧

glDisable(GL_DEPTH_CLAMP)

♠ 颜色输出

颜色输出阶段是片段被写入帧缓存前所经历的最后一个openGL管线阶段,它确定了颜色数据离开片段着色器后最终显示给用户之前所经历的操作


♥ 混合

就是颜色叠加,几乎所有的动画或者游戏引擎都支持颜色叠加效果

启用混合

glEnable(GL_BLEND);

禁用混合

glDisable(GL BLEND);

♣ 混合函数

可以调用gIBlendFunc()glBlendFuncSeparate()函数,其函数原型如下

glBlendFuncSeparate(GLenum srcRGB, GLenum dstRGB,GLenum srcAlpha, GLenum dstaAlpha) ;
glBlendFunc(GLenum src, GLenum dst);

最后一个值常量混色可通过调用 glBlendColor()设置


♥ 逻辑运算

在传递像素颜色前可以先对其应用一次逐辑运算

启用逻辑运算

glEnable(GL_COLOR_LOGIC_OP);

禁用逻辑运算

glDisable(GL_COLOR_LOGIC_OP);

逻运算使用输入像素和现有帧缓冲的值计算最终值,可通过调用glLogicop()来选择计算最终值的操作


♥ 颜色遮罩

写入片段前可以对其进行的最后修改之一是遮罩
为了应用颜色遮罩或禁用颜色遮,可以使用gColorMask()gcolorMaski()函数
函数g1ColorMask()允许你屏蔽当前已启用的所有演染缓存
函数glcolorMaski()许你将罩设置成特定颜色缓存


♠ 离屏渲染

OpenGL包含用户可自行设置帧缓存并用于直接绘制到纹理的功能

OpenGL中的大多数对象,可使用适当的创建函数glCreateFramebuffers()创建一个或多个缓存对象

  • 生成帧缓存对象的名称
void glGenFramebuffers(GLsizei n,GLuint * framebuffers);
  • 要将缓存绑定到语境
void glBindFramebuffer(GLenum target,GLuint framebuffer)
  • 附加纹理到帧缓存
void glNamedFramebufferTexture (GLuint framebuffer, GLenum attachment,GLuint texture,GLint level);
void glFramebufferTexture(GLenum target,GLenum attachment,GLuint texture,GLint level);

♥ 多个帧缓冲附件

用户定义帧缓存的另一个极其有用的功能是,它们支持多个附件,也就是说,可将多个纹理附加到单个帧缓存并同时使用单个片段着色器渲染到其中

纹理附加到FBO,我们需要调用glFramebufferTexture()gINamedFramebufferTexture()函数

要从单个片段着色器渲染到多个附件,则必须在着色器中声明多个输出并将其关联到附着点。为此,我们使用布局限定符,如下所示

layout (location = 0) out vec4 color0;
layout (location = 1) out vec4 colorl;
layout (location = 2) out vec4 color2;

绘制使用函数glDrawBuffers()(复数)函数


♥ 分层渲染

分层排列的可索引至着色器的2D纹理,也可将纹理附加到缓存对象,这种帧缓存就叫作分层缓存


♥ 帧缓存的完整性

完整性分为两类:附件完整性全帧缓存完整性


♣ 附件完整性

FBO的每个附件必须满足某些标准才能视为完整

  • 附件对象无任何相关图像
  • 附加图像的宽度或高度为零
  • 将非彩色可渲染格式附加到彩色附件
  • 将非深度可渲染格式附加到深度附件
  • 将非模板可渲染格式附加到模板附件

要确定颜色深度或模板格式是否可渲染,使用下面函数,结果为GL_TRUE或GL_FALSE

glGetInternalformativ(GL_COLOR_RENDERABLE)   //(颜色)
glGetInternalformativ(GL_DEPTH_RENDERABLE)   //(深度)
glGetInternalformativ(GL_STENCIL_RENDERABLE) //(模板)

♣ 全帧缓存完整性

帧缓存对象作为一个整体也必须是完整的,全帧缓存不完整的常见情形如下

  • glDrawBuffers()已将输出映射到一个无附加图像的FBO附件
  • OpenGL驱动器不支持同时使用多种内部格式

♣ 检查帧缓冲

当用户认为已经完成FBO设置时,可以通过调用以下函数检查FBO是否完整

Glenum fbostatus = glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER);
GLenum fbostatus = glCheckNamedFramebufferStatus (framebuffer);

♥ 立体渲染

OpenGL可根据所用显示设备生成成对的图像,这些图像可分别呈现给两眼并提高图像景深感

  • 渲染到左眼图像
glDrawBuffer(GLBACK_LEFT);
  • 渲染到右眼图像
glDrawBuffer(GL_BACK RIGHT);

♠ 反混叠

混叠就是锯齿锯齿。减少或消除混叠的方法叫作反混叠技术


♥ 过滤法反混叠

启用方式

glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_LINE_SMOOTH);

仅适合少数情况,对于立方体,需要把GL_LINE_SMOOTH替换成GL_POLYGON_SMOOH,如下代码所示

glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_POLYGON_SMOOH);

♥ 多样本反混叠

为增加图像的采样率,OpenGL支持在屏幕上存储每个像素的多个样本。这种技术称为多样本反混叠(MSAA)

启用多采样

glEnable(GL_MULTISAMPLE);

禁用多采样

glDisable(GL_MULTISAMPLE);

♥ 多样本纹理

我们可以创建一个多采样纹理并将其附加到帧缓存对象进行渲染

  • 多采样纹理

例如:GL_TEXTURE_2D_MULTISAMPLE或GL_TEXTURE_2D_MULTISAMPLE_ARRAY

  • 分配储存空间

glTextureStorage2DMultisample()或glTextureStorage3DMultisample()


♣ 样本覆盖率

覆盖率指片段“覆盖”的像素占比

OpenGL将片段透明度值直接转换成覆盖率值,调用glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE);

也可以通过调用gl_SampleCoverage()手动设置样本覆盖率

另一种可以生成覆盖信息的方法是在片段着色器中明确设置该信息,使用片段着色器内置变量即gl_SampleMaskIn[]gl_SampleMask[]


♥ 采样率着色

-启用采样率着色

gl_Enable(GL SAMPLE SHADING);

-禁用采样率着色

gl_Disable(GL_SAMPLE_SHADING);

单独对哪部分样本着色,可以调用gl_MinSampleShading()


♥ 重心采样

它仅适用于渲染到多采样帧缓存的情形
要创建带有 centroid 存储限定符的易变变量


♠ 高级帧缓冲格式

适用于帧缓存的更高级的格式


♥ 无附件渲染

可以创建帧缓存但不附加任何纹理

每个帧缓存对象都有一组参数,用于替代无附件时从其附件获得的参数。为了指定这些参数,需要调用gl_FramebufferParameteri()


♥ 浮点帧缓冲

浮点格式的附件


♣ 使用浮点格式

创建缓存时可使用两种新记号,即GL_RGBA16EGL_RGBA32F,如下所示

glTextureStorage2D(texture,1,GL_RGBA16F,width, height);
glTextureStorage2D(texture,1,GL_RGBA32F,width, height);

有许多可用格式如下表所示

格式 内容
GL_RGBA32F 4个32位浮点分量
GL_RGBA16F 4个16位浮点分量
GL_RGB32F 3 个32 位浮点分量
GL_RGB16F 3个16 位浮点分量
GL_RG32F 两个32位浮点分量
GL_RG16F 两个 16 位浮点分量
GL_R32F 一个32位浮点分量
GL_R16F 一个16 位浮点分量
GL_R11F_G11F_B10F 两个 11 位浮点分量和一个 10 位浮点分量

♥ 整数帧缓冲

使用正数内部格式创建纹理并附加到帧缓存对象来创建整数帧缓冲

使用包含整数分量的内部格式创建纹理并附加到帧缓存对象(例如,GL RGBA32UI),如下所示

glTextureStorage2D(tex,1,GLRGBA32UI,10241024);

♥ SRGB颜色空间

如果输入电压达到最大电压的-半,则光输出还略低于最大可能光输出的四分之一!为了弥补这一点,在计算机图形中,使用伽马校正(以y项的幂函数命名),通过低幂次提高线性值、缩放结果和补偿结果值。生成的颜色空间叫作 sRGB


♠ 点精灵

术语点精灵(pointsprites)通常指纹理点,指以点的渲染来代替纹理,绘制单个 3D点将2D纹理图像置于屏幕上,常见于粒子系统


♥ 点纹理化

使用点精灵只需要绑定一个2D纹理并使用内置变量gl_PointCoord从片段着色器中读取该纹理,如下述代码所示

#version 450 core
out vec4 vFragColor;

in vec4 vStarColor;

layout (binding =0) uniform sampler2D starImage;

void main(void)
{
    vFragColor = texture(starImage, gl_PointCoord) * vStarColor;
}

♥ 点参数

可以使用函数glPointParameteri()对点精灵(以及一般点)的一些特征进行微调


♥ 有形点

除使用gl_PointCoord对纹理坐标应用纹理外,还可以使用gl_PointCoord导出除纹理坐标外的许多其他信息
例如,可以使用片段着色器中的discard关键词生成非正方形的点,从而舍弃预期点形状外的片段


♥ 旋转点

可以在片段着色器中直接创建旋转矩阵,然后将其与gl_Pointcoord相乘使它旋转


♠ 获取图像

仅仅获取最终的渲染图像,不一定需要向用户展示,例如截图、打印图像等


♥ 从帧缓存中读取

OpenGL提供函数glReadPixels()能从帧缓存中读取像素数据,其原型如下

void glReadPixels(GLint X, GLint y,GLsizei width,GLsizei height, GLenum format, GLenum type,GLvoid * data);


♥ 在帧缓冲之间复制数据

图形API允许应用程序将像素或缓冲数据读取至系统内存,还提供了将这些像素或数据绘制至屏幕的方法

上述方法很低效,可以使用位块传输命令将像素数据从一个点快速移动到另一个点,步骤如下

  • 复制源是通过调用glReadBuffer()指定的读取缓存的读取缓存
  • 复制目标是通过调用glDrawBuffer()指定的当前绘制缓存的绘制缓存
  • 通过调用glReadPixels()将缓存中的数据读入应用程序的内存
  • 使用g1BlitFramebuffer()从一个缓存读入另一个缓存
  • 更简单地将数据直接从缓存复制到纹理中,通过glCopyTexSubImage2D()glcopyTexturesubImage2D()
  • 如果想要将纹理中的数据复制到另一个纹理中,则可以通过调用glCopyImageSubData()来实现

♥ 读取纹理数据

通过调用以下任意函数从纹理中读取图像数据

void glGetTexImage(GLenum targetGLint Ievel,GLenum format, GLenum type,GLvoid *img);
void glGetTextureImage(GLuint texture, Glint level,GLenum format,GLenum type,GLsizei bufsize, void *pixels)

上述函数都将整级纹理读回到内存中,如果只需要一小片纹理,则可调用下面函数

void glGetTextureSubImage (GLuint texture, GLint level,
GLint xoffset,GLint yoffset, GLint zoffset,GLsizei width,GLsizei height, GLsizei depth,GLenum format,GLenum type,GLsizei bufSize, void *pixels);


♠ 推送

  • Github
https://github.com/KingSun5

♠ 结语

若是觉得博主的文章写的不错,不妨关注一下博主,点赞一下博文,另博主能力有限,若文中有出现什么错误的地方,欢迎各位评论指摘。

本文属于原创文章,转载请评论留言,并在转载文章头部著名作者出处

你可能感兴趣的:(OpenGL超级宝典,学习,笔记,着色器)