【OpenGL】蓝宝书第十章——片段操作:管线的终点

目录

片段着色器之后的逐片段操作流程

裁剪——将几何图形剪切到希望的大小

多重采样

样本覆盖

样本遮罩

综合运用

模板操作

深度测试

深度截取

进行混合

混合方程式

综合运用

抖动

逻辑操作

遮罩输出

颜色

深度

模板

用途


片段着色器之后的逐片段操作流程

对一个虚拟片段进行多重采样操作、模板测试、深度缓冲区测试、混合、抖动和逻辑操作等操作。

逐像素操作:输入片段->裁剪测试->多重采样操作->模板测试->深度缓冲测试->混合->逻辑操作->抖动->到帧缓冲区。

裁剪——将几何图形剪切到希望的大小

决定是否将片段放在一个可渲染区域中裁剪出来的区域中。裁剪操作是在窗口坐标中执行的,即片段有一个(0,0)到(width,height)范围的窗口坐标,其中width和height是窗口维度。

定义剪切面来决定对几何图形裁剪:

glEnable(GL_SCISSOR_TEST);
void glScissor(GLint left, GLint bottom, sizei width, sizei height);

多重采样

多重采样阶段可以为任何给定像素生成多重子样本,这在一个像素恰好落在线或多边形边缘时可能会尤其有用。一个缓冲区的样本数是在进行分配时确定的。

对于窗口表面来说,我们必须在选择一个像素格式或配置时指定采样数。

对于帧缓冲区来说,我们可以在创建绑定到帧缓冲区的纹理和渲染缓冲区时选择样本数。请注意,所有帧缓冲区绑定的样本数都必须是相同的。

可通过glGetMultisamplefx获取像素中每个亚像素样本的位置。下面将学习到2个方法控制亚像素:修改覆盖值和对样本进行掩码操作。

样本覆盖

这里“覆盖”是指一个亚像素“覆盖”了多大区域。我们可将一个片段的alpha值直接转换成一个覆盖值,来确定帧缓冲区的多少个样本将被这个片段更新。

glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE);

一个片段的覆盖值用于确定将写入多少个子样本,例如:alpha为0.4,则是40%的覆盖值,以8x8MSAA缓冲区来说,即有8*40%=3个样本被写入。

为防止这些亚像素样本在启用混合时也被进行混合,可调用如下方法避免无意义混合,因为“alpha覆盖"操作已经是一种混合方式了,就没必要再进行混合。

glEnable(GL_SAMPLE_ALPHA_TO_ONE);

这函数是将亚像素样本的alpha值强制设置为1。

使用"alpha覆盖"方法,alpha遮罩的边缘将是抗锯齿的,将产生更加自然和平滑的结果,例如:绘制灌木丛、树木、或刷子上部分alpha透明的浓密刷毛产生更好的平滑过渡。[?]

在应用"alpha覆盖"后,手动设置样本覆盖的方法:

glEnable(GL_SAMPLE_COVERAGE);
glSampleCoverage(clampf value, Boolean invert)

value 覆盖值[0,1]范围。

【*疑惑点】如果结果得到的遮罩应该进行反转,那么invert参数将被标记到OpenGL。例如,如果绘制两颗重叠的树,其中一颗覆盖60%,而另一颗覆盖40%,那么我们将希望对其中一个覆盖值进行反转以确保两次绘制调用不会使用同一个遮罩。

glSampleCoverage(0.5, GL_FALSE);
//绘制第一组几何图形
... ..

glSampleCoverage(0.5, GL_TRUE);
//绘制第二组几何图形
... ...

样本遮罩

多重采样操作的第二个可配置选项(同是也是最后一个)是样本遮罩,允许我们使用glSampleMaski函数将特定样本屏蔽掉。注意的是"alpha遮罩"样本覆盖操作已经被执行了。

glSampleMaski(GLuint maskNumber, GLbitfield mask);

mask参数是一个32位的像素样本遮罩,其中0位映射到样本0,1位映射到样本1,以此类推。maskNumber为0时,mask从0开始到31位;为1时,mask从32位开始到63位,以此类推。

可查询GL_MAX_SAMPLE_MASK_WORDS来检查能支持多少个遮罩。目前只支持一个,因目前为止还没有支持每个像素多于32个样本的。

另一种在片段着色器代码中支持的修改样本遮罩方法:写入gl_SampleMask[]输出数组。

综合运用

解决透明物体的渲染问题,即2个0.5alpha的透明物体,一个在前一个在后,如果开启深度检测会将后面的物体遮挡,如果不开启,那么则可能后面的物体渲染在前面。解决这种问题需要使用顺序无关透明度(Order Independent Transparency)简称 OIT。Unity会对透明物体从后往前排序好,再一个个后到前顺序地渲染,但这样会带来非常大的开销和耗时,而且也并不是100%保证正确地渲染的。

OIT是使用样本遮罩将每次渲染传递存储到多重采样缓冲区的独立样本中,在场景进行渲染之后,遮罩解析操作会为每个像素将所有样本以正确的顺序组合在一起。

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

模板操作

glEnable(GL_STENCIL_TEST)开启模板测试 大多数支持8位模板值(但可能会支持更少)

glStencilFuncSeparate 函数

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

face参数:GL_FRONT、GL_BACK 或 GL_FRONT_AND_BACK 决定我们模板测试影响的面(正面还是反面 或都影响)

func参数:模板测试条件 

ref参数:模板参数值(整数)

mask参数:遮罩值,用于控制模板参数值的哪些位与缓冲区进行比较。

 func条件大全:

函数通过条件
GL_NEVER总是不通过条件
GL_ALWAYS总是通过测试
GL_LESSref值小于缓冲区值
GL_LEQUAL小于等于
GL_EQUAL等于
GL_GEQUAL大于等于
GL_GREATER大于
GL_NOTEQUAL不等于

 glStencilOpSeparate 函数

void glStencilOpSeparate(GLenum face, GLenum sfail, GLenum dpfail, GLenum dpass);

face参数:同上

sfail参数:模板测试失败时的操作

dpfail参数:深度测试失败时的操纵

dpass参数:深度测试通过时的操作

模板操作大全:

操作结果
GL_KEEP不要修改模板缓冲区
GL_ZERO模板缓冲区值设置为0
GL_REPLACE用ref替换缓冲区的模板值
GL_INCR增加模板的饱和度值
GL_DECR降低模板的饱和度值
GL_INVERT对模板支进行按位取反
GL_INCR_WRAP增加模板的非饱和度值
GL_DECR_WRAP降低模板的非饱和度值

glClearStencil 设置模板清除值 调用电邮模板缓冲区位的清除操作 从而将模板缓冲区清除为0。

示例程序:暂时没有 ,但大致代码如下:

//清空模板缓冲区为0
glClearStencil(0);
glClear(GL_STENCIL_BUFFER_BIT);

... ...

//设置模板参数:正面进行 模板参考值为1 总是通过测试 遮罩
glStencilFuncSeparate(GL_FRONT, GL_ALWAYS, 1, 0xff);
//模板操作:正面进行测试 不通过模板测试则保持  不通过深度测试则为设置为0   通过深度测试则替换为1
glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_ZERO, GL_REPLACE);

//渲染边框装饰 (此时边框装饰区域的模板值为1,其他地方为0)
... ...

//设置模板参数:双面进行 条件小于1时通过测试 遮罩
glStencilFuncSeparate(GL_FRONT_AND_BACK, GL_LESS, 1, 0xff);
//模板操作:正面进行  不通过模板测试保持   不通过深度测试则保持  通过深度测试则保持
glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_KEEP);

//渲染其他东西(只有模板值小于1的部分 即边框装饰以外的部分才能被渲染上其他物体) 保证了边框装饰不会被其他物品遮挡。
... ...

深度测试

在模板操作之后,深度测试启动的话 将对一个片段的深度进行测试,如果深度写入被启用,那么通过深度测试的片段深度值会被写入深度缓冲区,如果深度测试失败则片段将被销毁,不会传递到片段操作的其他阶段。

深度截取

与深度测试相关的功能称为“深度截取” depth clamping.

在默认情况下,深度截取是关闭的,可通过调用glEnable(GL_DEPTH_CLAMP)开启。

如果开启了深度截取,那么输入像素的深度将在深度测试执行之前被截取到近端和远端剪切面。在防止几何图形被裁剪到裁剪区域时,深度截取可能会非常有用。阴影区域渲染就是一个应用实例。在对阴影区域进行渲染时,我们希望尽可能地多沿着z轴方向保留几何图形。要做到这一点可以启用深度截取,用它来避免比远端剪切面还要远和比近端剪切面还要近的数据被裁剪掉。

进行混合

如果片段即通过了模板测试,也通过了深度测试了,之后会被传递到混合阶段。混合操作允许我们将输入的颜色与已经存在于颜色缓冲区中的颜色,或其他使用众多支持的混合方程式之一的常量进行组合。混合只能在定点和浮点格式中进行,不能讲诸如GL_RGB_16I或GL_RGB32I这样的整数格式进行混合。 若绘制到的缓冲区是定点格式的,那么输入源颜色将在进行任何混合操作之前被截取到0.0-1.0范围内。混合是以每个绘制缓冲区为基础进行控制的,通过调用glEnablei(GL_BLEND, bufferIndex)启用。就像使用glDrawBuffers一样,缓冲区索引可以是GL_DRAW_BUFFER0、GL_DRAW_BUFFER1等。如果默认FBO被绑定,那么混合将在所有启用的缓冲区执行。

混合方程式

混合方程式决定源像素值和缓冲区像素值如何混合

glBlendEquation(GLenum mode);
glBlendEquationSeparate(GLenum modeRGB, GLenum modeAlpha);

混合方程式RGBAlpha
GL_FUNC_ADD

R = Rs * Sr + Rd * Dr

G = Gs * Sg + Gd * Dg

B = Bs * Sb + Bd * Db

A = As * Sa + Ad * Da
GL_FUNC_SUBTRACT

R = Rs * Sr - Rd * Dr

G = Gs * Sg - Gd * Dg

B = Bs * Sb - Bd * Db

A = As * Sa - Ad * Da
GL_FUNC_REVERSE_SUBTRACT

R = Rd * Dr - Rs * Sr

G =  Gd * Dg - Gs * Sg

B = Bd * Db - Bs * Sb

A = Ad * Da - As * Sa
GL_MIN

R = min(Rs, Rd)

G = min(Gs, Gd)

B = min(Bs, Bd)

A=min(As, Ad)
GL_MAX

R = max(Rs, Rd)

G = max(Gs, Gd)

B = max(Bs, Bd)

A = max(As, Ad)

混合函数

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

glBlendFuncSeparate : srcRGB是源颜色RGB,dstRGB 是缓冲区颜色RGB,srcAlpha是源颜色alpha , dstaAlpha是缓冲区颜色alpha

glBlendFunc:直接设置源颜色RGBA 和 缓冲区颜色RGBA 的混合因子。

混合函数RGBAlpha
GL_ZERO(0,0,0)0
GL_ONE(1,1,1)1
GL_SRC_COLOR(Rs0, Gs0, Bs0)As0
GL_ONE_MINUS_SRC_COLOR(1,1,1)-(Rs0, Gs0, Bs0)1-As0
GL_DST_COLOR(Rd, Gd, Bd)Ad
GL_ONE_MINUS_DST_COLOR(1,1,1)-(Rd, Gd, Bd)1-Ad
GL_SRC_ALPHA(As0, As0, As0)As0
GL_ONE_MINUS_SRC_ALPHA(1,1,1) - (As0, As0, As0)1-As0
GL_DST_ALPHA(Ad0, Ad0, Ad0)Ad0
GL_ONE_MINUS_DST_ALPHA(1,1,1) - (Ad0, Ad0, Ad0)1-Ad0
GL_CONSTANT_COLOR(Rc, Gc, Bc)Ac
GL_ONE_MINUS_CONSTANT_COLOR(1,1,1) - (Rc, Gc, Bc)1 - Ac
GL_CONSTANT_ALPHA(Ac, Ac, Ac)Ac
GL_ONE_MINUS_CONSTANT_ALPHA(1,1,1) - (Ac, Ac, Ac)1-Ac
GL_ALPHA_SATURATE(f,f,f) f = min(As0, 1-Ad)1
GL_SRC1_COLOR(Rs1, Gs1, Bs1)As1
GL_ONE_MINUS_SRC1_COLOR(1,1,1) - (Rs1, Gs1, Bs1)1-As1
GL_SRC1_ALPHA(As1, As1, As1)As1
GL_ONE_MINUS_SRC1_ALPHA(1,1,1) - (As1, As1, As1)1 - As1

上面一系列带有 As0 的 0 代表的是 第一个颜色, As1 的 1 代表 第二个颜色,是给着色器通过 glBindFragDataLocationIndexed 进行设置的,为给定的颜色绑定输出到一个以上的最终颜色。 使用两个输出的方法是使用正确的混合因子将这些颜色混合到一起。通过查询GL_MAX_DUAL_SOURCE_DRAW_BUFFERS的值 可看到支持的双输出缓冲区数量。

综合运用

oit示例程序的混合模式 即按'b' 键盘 即可切换到这种模式,它可以按1~7的数字进行切换混合模式中的混合操作方式和混合因子 查看不同的效果。

最常见的是 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);来设置的混合因子 即 Srgba * Sa + Drgba * (1-Sa) 

https://blog.csdn.net/qq_39574690/article/details/116107087

抖动

在混合之后,像素数据仍然以一组浮点数的形式表示,但是除非帧缓冲区是一个浮点缓冲区,否则像素数据都必须先进行转换才能存储。例如,大多数窗口可渲染格式只支持每通道8位。这就意味着GPU必须对最终颜色输出进行转换才能存储它。

根据抖动开启或者关闭,这种转换可以以两种方式进行。

① 可简单地映射到可表示的最大正颜色,例如  特定像素的R值为0.3222,而窗口格式为GL_RGB_8, 那么GPU会将它映射到256中的82,或者256中的83。

如果抖动关闭,那么GPU将自动选择83(也就是说抖动就是不确定性地选择不同的映射情况)

glDisable(GL_DITHER) 关闭抖动

② 对结果进行抖动。

默认情况下深度截取是关闭的,但可通过glEnable(GL_DEPTH_CLAMP)开启。什么是抖动? 这是一种用硬件对从一种可表示的颜色到下一步骤的过渡进行混合的方法。

通过在两种相邻颜色中的任何一种都无法在其中真正表现出来的区域中将两种颜色混合在一起,GPU可以将过渡的边缘进行柔和化,而不是突然地从一个颜色层次改变到另一个颜色层次。

抖动是默认开启的,它能更好地处理好不同颜色层之间的混合,而且是由硬件处理的,当你不需要时只需关闭即可。

逻辑操作

如果像素颜色与帧缓冲区的格式和位深度相同,那么还有2个步骤可以对最终结果产生影响。

① 允许我们在进行传递之前对像素颜色应用一个逻辑操作。逻辑操作不会影响浮点缓冲区,启用时会忽略混合的影响。

glEnable(GL_COLOR_LOGIC_OP) 启用逻辑操作

逻辑操作是将输入像素的值和已经存在帧缓冲区的值来计算一个最终值,我们可决定的是这个计算操作是什么。

glLogicOp(GLenum op);

操作结果
GL_ZERO将所有值设为0
GL_AND源&目标
GL_AND_REVERSE源&~目标
GL_COPY
GL_AND_INVERTED~(源&目标)
GL_NOOP目标
GL_XOR源^目标
GL_OR源 | 目标
GL_NOR~ (源 | 目标)
GL_EQUIV~ (源^目标)
GL_INVERT~目标
GL_OR_REVERSE源 | ~目标
GL_COPY_INVERTED~源
GL_OR_INVERTED~源 | 目标
GL_NAND~(源&目标)
GL_ONE(为毛书籍写的是GL_ZERO!)将所有值设为1

逻辑操作分别应用到每个颜色通道。将源和目标进行组合的操作是在颜色值上按位执行的。它并不是普遍应用的,但它仍保留着,因GPU还支持。

遮罩输出

在一个片段着色器进行写操作之前,可进行的最后一个操作即遮罩。到现在为止了解到的,一个片段着色器可写入三种类型的数据:颜色、深度和模板数据。我们可以对这3种分别进行遮罩。

颜色

glColorMask 和 glColorMaski 对颜色写入进行遮罩,或者防止颜色写入发生。

glColorMask(writeR, writeG, writeB); //为默认颜色缓冲区 遮罩RGB    填的是GL_TRUE则允许写入,GL_FALSE则不允许写入
glColorMaski(colorBufferIndex, writeR, writeG, writeB); //为特定的颜色缓冲区 进行遮罩RGB     

深度

glDepthMask(GL_FALSE) 关闭深度写入   ,设置GL_TRUE 则开启深度写入

模板

GLuint mask = 0x0007;
glStencilMask(mask); //对最低3位进行允许写入,其他位不允许写入。
glStencilMaskSeparate(GL_BACK, mask); //第一参数face即对哪个面进行的遮罩,GL_BACK背面

用途

例如 制作阴影图时,可以关闭颜色写入,因为只有深度信息才是我们需要的;制作贴花时,可关闭深度写入,避免贴花深度的影响。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值