OPENGLPG第九版学习 -颜色、像素和片元 PART1
文章目录
- 4.1 基本颜色理论
- 4.2 缓存及其用途
- 颜色缓存
- 深度缓存 / z缓存 / z-buffer
- 模板缓存
- 4.2.1 缓存的清除
- 4.2.2 缓存的掩码
- 4.3 颜色与OpenGL
- 4.3.1 颜色的表达与OpenGL
- 4.3.2 平滑数据插值
- 4.4 片元的测试与操作
- 4.4.1 剪切测试
- 4.4.2 多重采样的片元操作
- 4.4.3 模板测试
- 模板查询
- 4.4.4 模板示例:绘制三角形黑色边框
4.1 基本颜色理论
- 计算机图形学的目的就是计算一幅图像中的颜色值。
- 对于OpenGL来说,图像通常是显示在计算机屏幕的窗口中的,它本身是由规则矩形的像素数组构成的,而每个像素都可以显示自己的颜色。
- OpenGL一般支持RGBA和sRGB颜色空间。
- 通常来说,每个分量的强度都是使用一定数量的位来保存的(称为像素深度,bitdepth),而每个分量的像素深度的总和(alpha分量除外)就决定颜色缓存的深度,因此也就决定可以显示的颜色的总数量。
例如,颜色缓存的一个通常格式是,每个红色、绿色和蓝色分量都占用8位。因此我们得到了一个像素深度为 24位的颜色缓存,这个颜色空间总共可以显示2的24次方种独立的颜色。
4.2 缓存及其用途
- 几乎所有图形程序共有的重要目标都是:在屏幕上绘制图像(或者绘制到离屏的一处缓存中)。
- 帧缓存(通常也就是屏幕)是由矩形的像素数组组成的(分辨率),每个像素都可以在图像对应的点上显示一小块方形的颜色值。
- 经过光栅化阶段,也就是执行片元着色器之后得到的数据还不是真正的像案——只是候选的片元。每个片元都包含与像素位置对应的坐标数据(通常是OpenGL窗口),以及颜色和深度的存储值。
- OpenGL窗口左下角的像素通常也就是(0,0)位置的像素。
- 假设屏幕为1920像素 x 1080像素,颜色深度为rgb888,即每个像素将存储至少3个字节的数据。
- 每个像素中的颜色数据大小也不同。不过,任何特定类型的颜色缓存记录到屏幕上每个像素的数据总量总是相同的。
- 颜色缓存只是记录像素信息的多个缓存中的一个。实际上,一个像素可能会关联多个颜色缓存。
- 一个显示系统的帧缓存(ftamebuffer)中包含了所有这些缓存类型,我们也可以在自己的应用程序中使用多个帧缓存。
- OpenGL系统中通常包含以下几种类型的缓存(一般都是集成到帧缓存中):
- 一个或者多个可用的颜色缓存(color buffer)
- 深度缓存(depth buffer)
- 模板缓存(stencil buffer)
除了主颜色缓存之外,通常不需要直接观察其他缓存的内容,而是用来执行例如隐藏表面的去除、模板草走、动态纹理生成等。
- 当启动应用程序之后,我们使用的是默认的帧缓存(default framebuffer),它是与应用程序窗口所关联的帧缓存。默认帧缓存总是会包含一个颜色缓存。
- 不同的OpenGL实现可以决定自己可用的缓存以及缓存中的每个像素所包含的位数。
- 不同的缓存有不同的特性,包括数据存储形式(数据类型)和精度。
- 多种视效方式,或者窗口类型,也可能需要更多不同的缓存。
颜色缓存
- 颜色缓存是我们通常进行绘制的缓存对象。
- 它包含RGB或者SRGB形式的颜色数据也可能包含帧缓存中每个像素的alpha值。
- 帧缓存中可能会含有多个颜色缓存,其中,默认帧缓存中的“主”颜色缓存需要特别对待,因为它是与屏幕上的窗口相关联的,所以绘制到“主”颜色缓存的图像都会直接显示到屏幕上,其他颜色缓存和屏幕无关。
- 颜色缓存中的像素,可能是采用每个像素存储单一颜色值的形式,也可能从逻辑上被划分为多个子像素(用于多重采样(multisampling)的反走样技术)。
- 双重缓冲的实现需要将主颜色缓存划分为两个部分:直接在窗口中显示的前置缓存(fontbu0er),以及用来渲染新图像的后备缓存,当执行交换缓存的操作时(例如glfwSwapBuffers),前置、后备缓存将进行交换。
- 只有默认帧缓存中的主颜色缓存可以使用双重缓冲的特性。
- 自定义帧缓存只有一个主颜色缓存(Color Attachment 0),但是可以定义两自定义帧缓存来模拟双重缓冲。
- 立体显示就是每个颜色缓存(即使已经是双重缓冲的形式)都会再划分出左颜色缓存和右颜色缓存,以展现立体图像。
深度缓存 / z缓存 / z-buffer
- 深度缓存为每个像素保存一个深度值,它可以用来判断三维空间中物体的可见性。
- 这里深度是物体与观察者眼睛的距离,我们通过x和y值来描述屏幕的水平和竖直方向信息,所以这里使用z来描述垂直于屏幕的距离值。
- 深度缓存值较大的像素会被深度缓存值较小的像素所覆盖。这种特性是非常有用的。
- 不过深度缓存的特性也可以通过“深度测试”的方式来改变。
模板缓存
- 可以用来限制屏幕特定区域的绘制。
例如,模板缓存的一个经典用途就是模拟汽车的后视镜视角:首先将镜子本身的形状渲染到模板缓存中,然后绘制整个场景。此时模板缓存可以阻止所有在镜子形状之外的绘制操作。
4.2.1 缓存的清除
- 每帧都需要(至少)清除一次缓存。
// 如果设置buffer参数为GL COLOR,那么关联的一个颜色缓存将被清除。
// drawbuffer指定需要清除的颜色缓存,因为可以同时绘制多个颜色缓存,但是一般至少有一个,所以可以设置为0.
// value是一个指向四个浮点数组成的数组的指针,它表示清除颜色缓存之后的初始颜色值,浮点数值依次表示红、绿、蓝和 alpha。
// 该函数也可以清楚深度缓存,buffer需要设置为GL_DEPTH,drawbuffer必须设置为0,因为只有一个深度缓存,value指向一个浮点数,即清除深度缓存之后的初始深度值。
void glClearBufferfv(GLenum buffer,GLint drawbuffer,const GLfloat * value);//清理函数还有一些替代的版本,可以用来清除模板缓存(其中保存的是整数数据)。
void glClearBufferiv( GLenum buffer,GLint drawbuffer,const GLint * value);void glClearBufferuiv( GLenum buffer,GLint drawbuffer,const GLuint * value);// 可以同时清理深度和模板缓存,这里buffer必须设置为GL_DEPTH_STENCIL ,drawbuffer必须为0
void glClearBufferfi( GLenum buffer,GLint drawbuffer,GLfloat depth,GLint stencil);
- 还有另一个方法来清理缓存
// 例如清理颜色可以将glClear和对应的clear函数搭配
// mask:GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT, and GL_STENCIL_BUFFER_BIT
void glClear(GLbitfield mask);void glClearColor(GLfloat red,GLfloat green,GLfloat blue,GLfloat alpha);
void glClearDepth(GLdouble depth);
void glClearStencil( GLint s);
4.2.2 缓存的掩码
- 在写入数据之前,可以对数据执行一次掩码操作。
- 下面函数可设置用于控制写入不同缓存的掩码。
- 所有 GLboolean 掩码的默认值均为 GL_TRUE,而所有 GLuint掩码的默认值都是1。
void glColorMask(GLboolean red,GLboolean green,GLboolean blue,GLboolean alpha);// 需要渲染到多个颜色缓存,该函数可以对特定的缓存对象设置颜色掩码。
void glColorMaski(GLuint buf,GLboolean red,GLboolean green,GLboolean blue,GLboolean alpha);// 如果为GL_TRUE,那么深度缓存可以写入,否则,无法写入
void glDepthMask(GLboolean flag);//mask参数用于与模板值进行按位“与”操作。
//如果对应位操作的结果为1,那么像素的模板值可以写入;
//如果为0则无法写入。
void glStencilMask(GLuint mask);//可以为多边形的正面和背面设置不同的模板掩码值。
void glStencilMaskSeparate( GLenum face,GLuint mask);
glStencilMask中mask和glStencilFunc中的mask参数没有关系。
- glStencilMask中mask是用于对模板位平面的写入的控制。
- glStencilFunc中的mask是用来控制模板测试时哪些位需要参与比较,mask 是一个 8 位的无符号整数(范围是 0x00 到 0xFF;默认0xFF,表示所有位都参与比较)。
4.3 颜色与OpenGL
- 片元着色器可以不借助任何外部数据直接生成片元的颜色值。
- 每个输人的顶点都会提供一个附加的颜色数据,可以在其他着色阶段修改后再传人片元着色器,并且用它来判断颜色值。
- 数据的补充,不只是特定的颜色值,都可以在片元着色器中通过计算来生成颜色值。
- 外部数据,例如数字图像等,也可以在片元着色器中引用,用于查找颜色值(或者其他数据值)。这些数据保存在纹理贴图当中,并且需要用到纹理映射(texture mapping)技术
4.3.1 颜色的表达与OpenGL
- 默认情况下,OpenGL内部会使用浮点数来表示一个颜色分量,并且负责维护它的精度,直到数据保存到帧缓存中为止。
- 即除非另有设置,否则片元着色器的输入总是浮点数类型,为片元颜色设置的数值也是如此,并且这些值总是要限制在[0.0,1.01]范围内——称为归一化数值(normalized value)。
- 可以选择让OpenGL自动将非浮点数类型转换为归一化的浮点数,即glVertexAttribPointer或 glVertexAttribN*系列函数中的GLboolean normalized参数。
- 此时 OpenGL把输入的数据类型转换到对应的归一化数值范围(范围与输入的数据类型相关,例如有符号或者无符号数据类型)。
- 这样的颜色写人到帧缓存之后,会被映射到帧缓存所支持的数值区间(颜色空间)内。
例如,如果帧缓存的每个红色、绿色和蓝色分量都有8
位,那么最后颜色分量的区间范围为[0,255]。
4.3.2 平滑数据插值
- 与其他的顶点数据类似,颜色数据也必须保存到顶点缓存对象(VBO)当中。
- 当数据从顶点着色器传递到片元着色器的时候,OpenGL会沿着被渲染的图元的每个面对数据进行平滑的插值,即Gouraud着色。
- Gouraud着色的简单片元着色器
const char* fragmentShaderSource = "#version 450 core\n"
"in vec4 vs_fs_color;\n"
"layout (location = 0) out vec4 color;\n"
"void main()\n"
"{\n"
" color = vs_fs_color;\n"
"}\n\0";
要注意的是,输入到片元着色器的颜色(vs_fs_color)并不是直接来自于之前的着色阶段(即顶点着色器),而是来自光栅化的结果。
4.4 片元的测试与操作
- 当我们在屏幕上绘制几何体的时候,OpenGL会按照下面的顺序来处理管线:
- 首先执行当前绑定的顶点着色器,然后依次是细分和几何着色器(如果它们存在于当前流水线中)。
- 然后将最终几何体装配为图元并送人光栅化阶段,这里将计算出窗口中哪些像素受到了几何体的影响。
- 当OpenGL确定当前需要生成一个独立的片元时,它将执行片元着色器的内容。
- 然后再经过几个处理阶段,判断片元是否可以作为像素绘制到帧缓存中,以及控制绘制的方式。
- 例如,如果片元超出了帧缓存的矩形区域,或者它与当前帧缓存中同位置的像素相比,距离视点更远,那么正在处理的过程都会停止,片元也不会被绘制。
5.片元的颜色会与当前帧缓存中的像素颜色进行混合。
- 而本节内容介绍的就是片元进入到帧缓存之前所需要经过的完整测试过程,以及片元写入时可以执行的一些操作。
这些测试和操作大部分都可以通过glEnable和glDisable来分别启用和禁止。
- 如果一个片元在某个测试过程中丢弃那么之后所有的测试或者操作都不会再执行,这些测试和操作的发生顺序如下:
- 剪切测试(scissor test)
- 多重采样的片元操作
- 模板测试(stencil test)
- 深度测试(depth test)
- 融混(blending)
- 逻辑操作
4.4.1 剪切测试
- 我们将程序窗口中的一个矩形区域称作一个剪切盒(scissor box),并且将所有的绘制操作都限制在这个区域内。
- 使用
glEnable(G_SCISSOR_TEST);
开启测试,或使用glDisable来禁止测试
。 - 如果片元位于矩形区域内,那么它将通过剪切测试。
- 使用glScissor设置剪切盒。
// 设置剪切矩形(或者剪切盒)的位置与大小。
// 参数定义了矩形的左下角(x,y)以及宽度(width)和高度(height)。
// 默认条件下,剪切矩形与窗口的大小是相等的,并且剪切测试是关闭的。
void glScissor( GLint x,。GLint y,GLsizei width,GLsizei height);
- 如果已经开启测试,那么所有的渲染(包括窗口的清除)都被限制在剪切盒区域内,而与之不同,视口并不会限制屏幕的清楚操作。
- glViewport(0, 0, 800, 600);正常情况下:
- glViewport(0, 0, 800, 600); glEnable(GL_SCISSOR_TEST);
glScissor(0,0,400,300);
- 获取是否开启了剪切测试和查询剪切盒:
// cap设置为GL_SCISSOR_TEST
GLboolean glIsEnabled( GLenum cap);
GLboolean glIsEnabledi( GLenum cap,GLuint index);// pname参数为GL_SCISSOR_BOX
void glGetIntegerv( GLenum pname,GLint * data);
- 参数cap的更多参考
- OpenGL实际上有多个剪切矩形。默认情况下所有的渲染测试都是在第一个矩形上完成的(如果开启)。
- 如果要访问其他剪切盒,需要使用几何着色器,详见后文。
4.4.2 多重采样的片元操作
- 多重采样(Multisampling) 是一种抗锯齿(Anti-Aliasing)技术,用于减少渲染图形时边缘的锯齿状走样(Aliasing)。
- 采样点分布:每个像素包含多个子采样点(例如4x、8x),这些点的位置由硬件或驱动决定。
- 光栅化阶段:
- 几何图元(如三角形)被覆盖时,计算每个子采样点是否在图元内。
- 若至少一个子采样点被覆盖,该像素的片段会进入后续处理(如着色器)。
- 最终的像素颜色由所有子采样点的覆盖率混合决定,平滑边缘。
- 仅对几何边缘的像素进行多次采样,其他区域保持单次采样,性能高效。
- 片段着色器仅运行一次,结果被多个子采样点共享(除非使用 sample 关键字修饰变量)。
- 每个子采样点独立进行深度和模板测试,确保边缘精度。
- 默认情况下,多重采样在计算片元的覆盖率时不会考虑alpha的影响。
- 如果假设多重采样本身已经开启(使用
glEnable(GL_MULTISAMPLE);
),并且帧缓存已经关联了一个多重采样的缓存数据,那么可用的特定模式如下:
均可以使用glEnable
// 使用片元的 alpha值来计算最后的采样覆盖率,并且这一过程与具体的硬件实现无关。
GL_SAMPLE_ALPHA_TO_COVERAGE// 将片元的 alpha值设置为最大的 alpha值,然后使用这个值来进行覆盖率的计算。
// 如果GL_SAMPLE_ALPHA_TO_COVERAGE也被启动,那么系统将使用替代前的片元的alpha值。
GL_SAMPLE_ALPHA_TO_ONE// 将使用glSampleCoverage()中设置的数值。
GL_SAMPLE_COVERAGE// 设置多重采样覆盖率的参数,以正确解算alpha值。
// invert是一个布尔变量,用于设置这个临时覆盖值是否需要先进行位反转,
//然后再与片元覆盖率进行合并(“与”操作)。
void glSampleCoverage( GLfloat value,GLboolean invert);
- 多重采样的特定模式主要是为了解决:对于使用透明纹理(如树叶、栅栏、UI元素等)的物体,传统多重采样(MSAA)可能无法有效消除锯齿,因为这些物体的边缘通常通过Alpha测试(如discard)硬性裁剪,导致锯齿边缘;且多重采样仅基于几何覆盖计算抗锯齿,无法直接利用Alpha通道的渐变信息。
- 而且开启特定模式,对于每个片段(Fragment),根据其Alpha值自动生成一个覆盖掩码(Coverage Mask)。
- 覆盖掩码的权重由Alpha值决定:Alpha值越高(越不透明),覆盖的采样点越多;Alpha值越低(越透明),覆盖的采样点越少。
- 最后边缘的Alpha值会控制覆盖的采样点数量,实现平滑过渡。
- 注意,如果启用了混合,则需要保证渲染顺序正确,否则混合结果可能异常,一般是先不透明从后往前,然后是透明的从深度高的到深度低的。
- 还可以设置精确的位掩码来计算和表达覆盖率,而不是让opengl自己去生成这个掩码:这个掩码与缓存中的每个采样值都有一位(bit)进行对应,它将与片元的采样覆盖值再次进行“与”操作
// 设置一个32位的采样掩码mask。
// 掩码本身的索引位置通过index 来设置,而新的掩码值通过 mask来设置。
// 当采样结果准备写入到帧缓存时,只有当前采样掩码值中对应位的数据才会被更新,
//而其他的数据将会被丢弃。
void glSampleMaski( GLuint maskNumber,GLbitfield mask);// 也可以在片元着色器中通过写入下方内置变量来设置采样掩码,
// 是一个32位数据的数组。
gl_SampleMask
如果当前帧缓存包含了多于32个采样数,那么采样掩码的长度可能是多个32位大小的WORD字段组成,其中第一个WORD表示前32,位的数据,第二个 WORD表示之后32位的数据,以此类推。
4.4.3 模板测试
glEnable(GL_STENCIL_TEST)
和glDisable(GL_STENCIL_TEST)
启用和禁用模板测试,默认禁止模板测试。- 只有在建立窗口的过程中,有预先请求模板缓存的前提下,才能使用模板测试,否则没有模板缓存,模板测试永远通过。
- 模板测试过程中会取像素在模板缓存中的值,然后与一个参考值进行比较,然后根据比较 / 测试的结果不同,还可以对模板缓存中的数据进行更改。
- 可以使用各种特定的比较函数、参考值,然后使用
glStencilFunc
和glStencilOp
来完成修改操作。
//参考值将与板缓存中已有的值进行比较,
//但是在此之前需要与mask参数进行按位“与”操作,
//丢弃结果为0的位平面。
// func:GL_NEVER, GL_LESS, GL_LEQUAL, GL_GREATER, GL_GEQUAL, GL_EQUAL, GL_NOTEQUAL, and GL_ALWAYS(默认)
// 例如GL_LESS,应该是ref比模板中的值更小时,片元模板测试通过。
// mask主要用于判断当前片元是否要参与比较,默认所有位为1。
// ref 范围在0到2的n次方-1,n为模板缓存位面数。
void glStencilFunc(GLenum func,GLint ref,GLuint mask);void glStencilFuncSeparate(GLenum face,GLenum func,GLint ref,GLuint mask);
- 如果支持GL_ARB_shader_stencil_export,可以通过在片元着色器向内置变量gl_FragStencilRefARB写入逐片元的值,将会在glStencilFunc和glStencilFuncSeparate中当作ref的值使用。
// 设置当片元通过或者没有通过模板测试的时候,要如何处理模板缓存中的数据。
// sfail,dpfail,dppass都可以设置为GL_KEEP(全部参数的默认), GL_ZERO,
//GL_REPLACE, GL_INCR, GL_INCR_WRAP, GL_DECR,
//GL_DECR_WRAP, and GL_INVERT。
// 加1和减1函数的结果值总是落在0到最大无符号整数值,即0到2的n次方-1,n为模板缓存位面数。
//如果片元没有通过模板测试,将执行sfai1函数;
//如果通过了模板测试,但是没有通过深度测试,那么执行dpfail 函数.
//如果通过深度测试或者没有开启深度测试,则执行dppass函数。
void glStencilOp(GLenum sfail,GLenum dpfail,GLenum dppass);void glStencilOpSeparate(GLenum face,GLenum sfail,GLenum dpfail,GLenum dppass);
- GL_KEEP:保持当前值。
- GL_ZERO:替换为0值。
- GL_REPLACE:替换为参考值ref。
- GL_INCR:增加1(使用饱和运算)
- GL_INCR_WRAP:增加1(不使用饱和运算)
- GL_DECR:减少1(使用饱和运算)
- GL_DECR_WRAP:减少1(不使用饱和运算)
- GL_INVERT:按位反转
模板查询
- 通过
glGetIntegerv
来查询参数
void glGetIntegerv( GLenum pname,GLint * data);
查询参数 | 意义 |
---|---|
GL_STENCIL_FUNC | 模板函数 |
GL_STENCIL_REF | 模板参考值 |
GL_STENCIL_VALUE_MASK | 模板掩码 |
GL_STENCIL_FAIL | 模板测试失败的处理函数 |
GL_STENCIL_PASS_DEPTH_FAIL | 模板测试通过但是深度测试失败的处理函数 |
GL_STENCIL_PASS_DEPTH_PASS | 模板测试和深度测试均通过的处理函数 |
glIsEnable(GL_STENCIL_TEST)
判断模板测试是否已经开启。
4.4.4 模板示例:绘制三角形黑色边框
#include<glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>// Vertex Shader source code
const char* vertexShaderSource = "#version 450 core\n"
"layout (location = 0) in vec4 position;\n"
"layout (location = 1) in vec4 color;\n"
"out vec4 vs_fs_color;\n"
"void main()\n"
"{\n"
" gl_Position = position;\n"
" vs_fs_color = color;\n"
"}\0";
//Fragment Shader source code
const char* fragmentShaderSource = "#version 450 core\n"
"in vec4 vs_fs_color;\n"
"layout (location = 0) out vec4 color;\n"
"void main()\n"
"{\n"
" color = vs_fs_color;\n"
"}\n\0";const GLfloat vertices[] =
{-0.5f, -0.5f, 0.0f,1.0f,0.5f, -0.5f, 0.0f,1.0f,0.0f, 0.5f, 0.0f,1.0f,};//const GLfloat verticesOutline[] =
//{
// -0.55f, -0.55f, -0.01f,1.0f,
// 0.55f, -0.55f, -0.01f,1.0f,
// 0.0f, 0.55f, -0.01f , 1.0f,
//
//};const GLfloat verticesOutline[] =
{-0.55f, -0.55f, 0.0f,1.0f,0.55f, -0.55f, 0.0f,1.0f,0.0f, 0.55f, 0.0f , 1.0f,};const GLfloat colors[] =
{0.0f, 0.0f, 0.0f,1.0f,0.0f, 0.0f, 0.0f,1.0f ,0.0f, 0.0f, 0.0f,1.0f ,};const GLubyte colorsNeedNormal[] =
{255, 255, 255,255,255, 255, 0,255 ,255, 0, 255,255 ,};// 编译链接着色器的辅助函数
unsigned int shaderProgram(const char* vertexSrc, const char* fragmentSrc) {// 创建并编译顶点着色器unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);glShaderSource(vertexShader, 1, &vertexSrc, NULL);glCompileShader(vertexShader);int result;glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &result);if (result == GL_FALSE){int length;glGetShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &length);char* message = (char*)alloca(length * sizeof(char));glGetShaderInfoLog(vertexShader, length, &length, message);std::cout << message << std::endl;glDeleteShader(vertexShader);return -1;}// 创建并编译片段着色器unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);glShaderSource(fragmentShader, 1, &fragmentSrc, NULL);glCompileShader(fragmentShader);glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &result);if (result == GL_FALSE){int length;glGetShaderiv(fragmentShader, GL_INFO_LOG_LENGTH, &length);char* message = (char*)alloca(length * sizeof(char));glGetShaderInfoLog(fragmentShader, length, &length, message);std::cout << message << std::endl;glDeleteShader(fragmentShader);return -1;}// 创建着色器程序并链接unsigned int program = glCreateProgram();glAttachShader(program, vertexShader);glAttachShader(program, fragmentShader);glLinkProgram(program);// 删除临时着色器对象glDeleteShader(vertexShader);glDeleteShader(fragmentShader);return program;
}int main() {// 初始化GLFWif (!glfwInit()) {std::cerr << "Failed to initialize GLFW" << std::endl;return -1;}// 创建窗口GLFWwindow* window = glfwCreateWindow(800, 600, "Stencil Border with Depth", NULL, NULL);if (!window) {std::cerr << "Failed to create GLFW window" << std::endl;glfwTerminate();return -1;}glfwMakeContextCurrent(window);gladLoadGL();// 启用深度测试//glEnable(GL_DEPTH_TEST);// 创建着色器程序unsigned int redShader = shaderProgram(vertexShaderSource, fragmentShaderSource);//unsigned int whiteShader = shaderProgram(vertexShaderSource, fragmentShaderWhiteSource);// 配置顶点数据unsigned int VAOs[2], VBOs[2];glGenVertexArrays(2, VAOs);glGenBuffers(2, VBOs);// 设置原始三角形VAOglBindVertexArray(VAOs[0]);glBindBuffer(GL_ARRAY_BUFFER, VBOs[0]);glBufferData(GL_ARRAY_BUFFER, sizeof(vertices) + sizeof(colorsNeedNormal), NULL, GL_STATIC_DRAW);glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);glBufferSubData(GL_ARRAY_BUFFER, sizeof(vertices), sizeof(colorsNeedNormal), colorsNeedNormal);glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, NULL);glVertexAttribPointer(1, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, (const GLvoid*)sizeof(vertices));glEnableVertexAttribArray(0);glEnableVertexAttribArray(1);// 设置边框三角形VAOglBindVertexArray(VAOs[1]);glBindBuffer(GL_ARRAY_BUFFER, VBOs[1]);glBufferData(GL_ARRAY_BUFFER, sizeof(verticesOutline) + sizeof(colors), NULL, GL_STATIC_DRAW);glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(verticesOutline), verticesOutline);glBufferSubData(GL_ARRAY_BUFFER, sizeof(verticesOutline), sizeof(colors), colors);glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, NULL);glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (const GLvoid*)sizeof(vertices));glEnableVertexAttribArray(0);glEnableVertexAttribArray(1);// 主渲染循环while (!glfwWindowShouldClose(window)) {// 清空缓冲//glClearColor(1.0f, 0.1f, 0.1f, 1.0f); // 深灰色背景//glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);static const float black[] = { 1.0f,0.0f,0.0f,0.0f };glClearBufferfv(GL_COLOR, 0, black);glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);// 启用模板测试glEnable(GL_STENCIL_TEST);glEnable(GL_DEPTH_TEST);// --- 第1步:绘制红色三角形 ---glStencilFunc(GL_ALWAYS, 1, 0xFF); // 总是通过模板测试,将三角形对应像素模板值写入1glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE); // 通过时替换为1glUseProgram(redShader);glBindVertexArray(VAOs[0]);glDrawArrays(GL_TRIANGLES, 0, 3);// --- 第2步:绘制白色边框 ---glStencilFunc(GL_NOTEQUAL, 1, 0xFF); // 仅模板值≠1的区域绘制glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // 不修改模板缓冲glBindVertexArray(VAOs[1]);// 关键设置:启用深度测试但禁用深度写入//glDepthMask(GL_FALSE);glDrawArrays(GL_TRIANGLES, 0, 3);//glDepthMask(GL_TRUE);// 禁用模板测试glDisable(GL_STENCIL_TEST);// 交换缓冲区和处理事件glfwSwapBuffers(window);glfwPollEvents();}// 清理资源glDeleteVertexArrays(2, VAOs);glDeleteBuffers(2, VBOs);glDeleteProgram(redShader);glfwTerminate();return 0;
}
相关文章:

OPENGLPG第九版学习 -颜色、像素和片元 PART1
文章目录 4.1 基本颜色理论4.2 缓存及其用途颜色缓存深度缓存 / z缓存 / z-buffer模板缓存 4.2.1 缓存的清除4.2.2 缓存的掩码 4.3 颜色与OpenGL4.3.1 颜色的表达与OpenGL4.3.2 平滑数据插值 4.4 片元的测试与操作4.4.1 剪切测试4.4.2 多重采样的片元操作4.4.3 模板测试模板查询…...

【js逆向】某精灵网
地址:aHR0cHM6Ly93d3cuamluZ2xpbmdzaHVqdS5jb20vYXJ0aWNsZXM f12查看数据包,下面这个不是,你得到的是你的用户信息,需要点击第2页才会显示数据接口 查看载荷 查看预览数据,发现是加密的 查看启动器,看到 Pr…...

自然语言处理:高斯混合模型
介绍 大家好,博主又来给大家分享知识了,今天给大家分享的内容是自然语言处理中的高斯混合模型。 在自然语言处理这个充满挑战与机遇的领域,我们常常面临海量且复杂的文本数据。如何从这些数据中挖掘出有价值的信息,对文本进行有…...

RISC-V汇编学习(三)—— RV指令集
有了前两节对于RISC-V汇编、寄存器、汇编语法等的认识,本节开始介绍RISC-V指令集和伪指令。 前面说了RISC-V的模块化特点,是以RV32I为作为ISA的核心模块,其他都是要基于此为基础,可以这样认为:RISC-V ISA 基本整数指…...

OpenCV连续数字识别—可运行验证
前言 文章开始,瞎说一点其他的东西,真的是很离谱,找了至少两三个小时,就一个简单的需求: 1、利用OpenCV 在Windows进行抓图 2、利用OpenCV 进行连续数字的检测。 3、使用C,Qt 3、将检测的结果显示出来 …...

Python中与字符串操作相关的30个常用函数及其示例
以下是Python中与字符串操作相关的30个常用函数及其示例: 1. str.capitalize() 将字符串的第一个字符大写,其余字符小写。 s "hello world" print(s.capitalize()) # 输出: Hello world2. str.lower() 将字符串中的所有字符转换为小写。…...

007-Property在C++中的实现与应用
Property在C中的实现与应用 以下是在C中实现属性(Property)的完整实现方案,结合模板技术和运算符重载实现类型安全的属性访问,支持独立模块化封装: #include <iostream> #include <functional>template<typename HostType, t…...

【实战篇】【DeepSeek 全攻略:从入门到进阶,再到高级应用】
凌晨三点,某程序员在Stack Overflow上发出灵魂拷问:“为什么我的DeepSeek会把财务报表生成成修仙小说?” 这个魔性的AI工具,今天我们就来场从开机键到改造人类文明的硬核教学。(文末含高危操作集锦,未成年人请在师父陪同下观看) 一、萌新村任务:把你的电脑变成炼丹炉 …...

clickhouse属于国产吗
《ClickHouse:探索其背景与国内的应用实例》 当我们谈论数据库技术时,ClickHouse是一个绕不开的话题。很多人可能会好奇,ClickHouse是否属于国产软件呢?答案是,虽然ClickHouse最初并非在中国开发,但这款列…...

ESP32 UART select解析json数据,上位机控制LED灯实验
前言: 本实验的目的主要是通过上位机通过UART来控制ESP32端的LED的点亮以及熄灭,整个项目逻辑比较简单,整体架构如下: 上位机(PC)主要是跑在PC端的一个软件,主要作用包含: 1)串口相关配置&…...

K8S 集群搭建——cri-dockerd版
目录 一、工作准备 1.配置主机名 2.配置hosts解析 3.配置免密登录(只需要在master上操作) 4.时间同步(每台节点都要做,必做,否则可能会因为时间不同步导致集群初始化失败) 5.关闭系统防火墙 6.配置…...

基于Python的电商销售数据分析与可视化系统实
一、系统架构设计 1.1系统流程图 #mermaid-svg-Pdo9oZWrVHNuOoTT {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-Pdo9oZWrVHNuOoTT .error-icon{fill:#552222;}#mermaid-svg-Pdo9oZWrVHNuOoTT .error-text{fill:#5…...

学习笔记:Python网络编程初探之基本概念(一)
一、网络目的 让你设备上的数据和其他设备上进行共享,使用网络能够把多方链接在一起,然后可以进行数据传递。 网络编程就是,让在不同的电脑上的软件能够进行数据传递,即进程之间的通信。 二、IP地址的作用 用来标记唯一一台电脑…...

高效处理 List<T> 集合:更新、查找与优化技巧
引言 在日常开发中,List<T> 是我们最常用的数据结构之一。无论是批量更新数据、查找特定项还是进行复杂的集合操作,掌握 List<T> 的高级用法可以显著提高代码的效率和可读性。本文将详细介绍如何使用 List<T> 进行批量更新、查找匹配项以及优化性能的方法…...

HTML5(Web前端开发笔记第一期)
p.s.这是萌新自己自学总结的笔记,如果想学习得更透彻的话还是请去看大佬的讲解 目录 三件套标签标题标签段落标签文本格式化标签图像标签超链接标签锚点链接默认链接地址 音频标签视频标签 HTML基本骨架综合案例->个人简介列表表格表单input标签单选框radio上传…...

Windows控制台函数:标准输入输出流交互函数GetStdHandle()
目录 什么是 GetStdHandle? 它长什么样? 怎么用它? 它跟 std::cout 有什么不一样? GetStdHandle 是一个 Windows API 函数,用于获取标准输入、标准输出或标准错误设备的句柄。它定义在 Windows 的核心头文件 <…...

Vue3 中 Computed 用法
Computed 又被称作计算属性,用于动态的根据某个值或某些值的变化,来产生对应的变化,computed 具有缓存性,当无关值变化时,不会引起 computed 声明值的变化。 产生一个新的变量并挂载到 vue 实例上去。 vue3 中 的 com…...

常见的三种锁
一、互斥锁 互斥锁 Mutex 保证在任意时刻只有一个线程能够进入被保护的临界区。当一个线程获取到互斥锁后,其他线程若要进入临界区就会被阻塞,直到该线程释放锁。 互斥锁是一种阻塞锁,当线程无法获取到锁时,会进入阻塞状态。 应…...

离线文本转语音库pyttsx3(目前接触到的声音效果最好的,基本上拿来就能用)
在现代应用程序中,文本转语音(Text-to-Speech, TTS)技术越来越受到重视。无论是为视力障碍人士提供帮助,还是为教育和娱乐应用增添趣味,TTS 都能发挥重要作用。今天,我们将介绍一个简单易用的 Python 库——…...

LeetCode Hot100刷题——反转链表(迭代+递归)
206.反转链表 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。 示例 1: 输入:head [1,2,3,4,5] 输出:[5,4,3,2,1]示例 2: 输入:head [1,2] 输出:[2,1]示例 3&#…...

JJJ:linux sysfs相关
文章目录 1.sysfs(属性)文件的创建、读、写1.1 创建流程1.2 open流程1.3 read流程 2.补充2.1 sysfs下常见目录介绍2.2 属性相关2.2.1 简介2.2.2 attribute文件的创建 2.3 sysfs目录如何创建的 1.sysfs(属性)文件的创建、读、写 1…...

Leetcode 刷题记录 06 —— 矩阵
本系列为笔者的 Leetcode 刷题记录,顺序为 Hot 100 题官方顺序,根据标签命名,记录笔者总结的做题思路,附部分代码解释和疑问解答。 目录 01 矩阵置零 方法一:标记数组 方法二:两个标记变量 02 螺旋矩阵…...

什么样的物联网框架适合开展共享自助KTV唱歌项目?
现在物联网的广泛应用,也让更多用户们看到了它的实力,也使得共享经济遍地开花。其中共享自助唱歌设备也备受欢迎,那么适合开展共享自助KTV唱歌项目的物联网框架都应具备哪些特点呢? 智能化与自动化管理 物联网技术在共享KTV中的应…...

【Academy】HTTP Host 标头攻击 ------ HTTP Host header attacks
HTTP Host 标头攻击 ------ HTTP Host header attacks 1. 什么是 HTTP Host 标头?2. 什么是 HTTP Host 标头攻击?3. HTTP Host 标头漏洞是如何产生的?4. 如何测试 HTTP Host 标头漏洞4.1 提供任意 Host 标头4.2 检查有缺陷的验证4.3 发送不明…...

Web基础:HTML快速入门
HTML基础语法 HTML(超文本标记语言) 是用于创建网页内容的 标记语言,通过定义页面的 结构和内容 来告诉浏览器如何呈现网页。 超文本(Hypertext) 是一种通过 链接(Hyperlinks) 将不同文本、图像…...

技术领域,有许多优秀的博客和网站
在技术领域,有许多优秀的博客和网站为开发者、工程师和技术爱好者提供了丰富的学习资源和行业动态。以下是一些常用的技术博客和网站,涵盖了编程、软件开发、数据科学、人工智能、网络安全等多个领域: 1. 综合技术博客 1.1 Medium 网址: ht…...

k8s概念及k8s集群部署(Centos7)
Centos7部署k8s集群 部署之前,先简单说下k8s是个啥: 一、k8s简介: k8s,全称:kubernetes,它可以看作是一个分布式系统支撑平台。k8s的作用: 1、故障自愈: k8s这个玩意可以监控容器…...

《DeepSeek-V3:动态温度调节算法,开启推理新境界!》
在人工智能领域不断探索的征程中,DeepSeek-V3以其卓越的创新技术,尤其是动态温度调节算法,成为了备受瞩目的焦点。这项算法犹如一把神奇的钥匙,巧妙地开启了推理速度与精度动态平衡的大门,为大语言模型的发展开辟了新的…...

Python从入门到精通1:FastAPI
引言 在现代 Web 开发中,API 是前后端分离架构的核心。FastAPI 凭借其高性能、简洁的语法和自动文档生成功能,成为 Python 开发者的首选框架。本文将从零开始,详细讲解 FastAPI 的核心概念、安装配置、路由设计、请求处理以及实际应用案例&a…...

fastapi+angular停车管理系统可跨域
说明: 我计划用fastapiangular做一款停车管理系统,支持跨域 1.设计mysql数据库表, 2.建表,添加测试数据,多表查询, 3.在fastapi写接口查询数据, 4.用postman测试, 5.在angular前端展…...