OPENGLPG第九版学习 -颜色、像素和片元 PART1

文章目录

  • [4.1 基本颜色理论](#4.1 基本颜色理论)
  • [4.2 缓存及其用途](#4.2 缓存及其用途)
    • [4.2.1 缓存的清除](#4.2.1 缓存的清除)
    • [4.2.2 缓存的掩码](#4.2.2 缓存的掩码)
  • [4.3 颜色与OpenGL](#4.3 颜色与OpenGL)
    • [4.3.1 颜色的表达与OpenGL](#4.3.1 颜色的表达与OpenGL)
    • [4.3.2 平滑数据插值](#4.3.2 平滑数据插值)
  • [4.4 片元的测试与操作](#4.4 片元的测试与操作)
    • [4.4.1 剪切测试](#4.4.1 剪切测试)
    • [4.4.2 多重采样的片元操作](#4.4.2 多重采样的片元操作)
    • [4.4.3 模板测试](#4.4.3 模板测试)
    • [4.4.4 模板示例:绘制三角形黑色边框](#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系统中通常包含以下几种类型的缓存(一般都是集成到帧缓存中):
  1. 一个或者多个可用的颜色缓存(color buffer)
  2. 深度缓存(depth buffer)
  3. 模板缓存(stencil buffer)

除了主颜色缓存之外,通常不需要直接观察其他缓存的内容,而是用来执行例如隐藏表面的去除、模板草走、动态纹理生成等。

  • 当启动应用程序之后,我们使用的是默认的帧缓存(default framebuffer),它是与应用程序窗口所关联的帧缓存。默认帧缓存总是会包含一个颜色缓存。
  • 不同的OpenGL实现可以决定自己可用的缓存以及缓存中的每个像素所包含的位数
  • 不同的缓存有不同的特性,包括数据存储形式(数据类型)和精度。
  • 多种视效方式,或者窗口类型,也可能需要更多不同的缓存。

颜色缓存

  • 颜色缓存是我们通常进行绘制的缓存对象。
  • 它包含RGB或者SRGB形式的颜色数据也可能包含帧缓存中每个像素的alpha值。
  • 帧缓存中可能会含有多个颜色缓存,其中,默认帧缓存中的"主"颜色缓存需要特别对待,因为它是与屏幕上的窗口相关联的,所以绘制到"主"颜色缓存的图像都会直接显示到屏幕上,其他颜色缓存和屏幕无关。
  • 颜色缓存中的像素,可能是采用每个像素存储单一颜色值的形式,也可能从逻辑上被划分为多个子像素(用于多重采样(multisampling)的反走样技术)。
  • 双重缓冲的实现需要将主颜色缓存划分为两个部分:直接在窗口中显示的前置缓存(fontbu0er),以及用来渲染新图像的后备缓存,当执行交换缓存的操作时(例如glfwSwapBuffers),前置、后备缓存将进行交换。
  • 只有默认帧缓存中的主颜色缓存可以使用双重缓冲的特性。
  • 自定义帧缓存只有一个主颜色缓存(Color Attachment 0),但是可以定义两自定义帧缓存来模拟双重缓冲。
  • 立体显示就是每个颜色缓存(即使已经是双重缓冲的形式)都会再划分出左颜色缓存和右颜色缓存,以展现立体图像。

深度缓存 / z缓存 / z-buffer

  • 深度缓存为每个像素保存一个深度值,它可以用来判断三维空间中物体的可见性。
  • 这里深度是物体与观察者眼睛的距离,我们通过x和y值来描述屏幕的水平和竖直方向信息,所以这里使用z来描述垂直于屏幕的距离值。
  • 深度缓存值较大的像素会被深度缓存值较小的像素所覆盖。这种特性是非常有用的。
  • 不过深度缓存的特性也可以通过"深度测试"的方式来改变。

模板缓存

  • 可以用来限制屏幕特定区域的绘制。

例如,模板缓存的一个经典用途就是模拟汽车的后视镜视角:首先将镜子本身的形状渲染到模板缓存中,然后绘制整个场景。此时模板缓存可以阻止所有在镜子形状之外的绘制操作。

4.2.1 缓存的清除

  • 每帧都需要(至少)清除一次缓存。
cpp 复制代码
// 如果设置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);
  • 还有另一个方法来清理缓存
cpp 复制代码
// 例如清理颜色可以将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。
cpp 复制代码
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着色的简单片元着色器
cpp 复制代码
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会按照下面的顺序来处理管线:
  1. 首先执行当前绑定的顶点着色器,然后依次是细分和几何着色器(如果它们存在于当前流水线中)。
  2. 然后将最终几何体装配为图元并送人光栅化阶段,这里将计算出窗口中哪些像素受到了几何体的影响。
  3. 当OpenGL确定当前需要生成一个独立的片元时,它将执行片元着色器的内容。
  4. 然后再经过几个处理阶段,判断片元是否可以作为像素绘制到帧缓存中,以及控制绘制的方式。
  • 例如,如果片元超出了帧缓存的矩形区域,或者它与当前帧缓存中同位置的像素相比,距离视点更远,那么正在处理的过程都会停止,片元也不会被绘制。
    5.片元的颜色会与当前帧缓存中的像素颜色进行混合。
  • 而本节内容介绍的就是片元进入到帧缓存之前所需要经过的完整测试过程,以及片元写入时可以执行的一些操作

这些测试和操作大部分都可以通过glEnable和glDisable来分别启用和禁止。

  • 如果一个片元在某个测试过程中丢弃那么之后所有的测试或者操作都不会再执行,这些测试和操作的发生顺序如下:
  1. 剪切测试(scissor test)
  2. 多重采样的片元操作
  3. 模板测试(stencil test)
  4. 深度测试(depth test)
  5. 融混(blending)
  6. 逻辑操作

4.4.1 剪切测试

  • 我们将程序窗口中的一个矩形区域称作一个剪切盒(scissor box),并且将所有的绘制操作都限制在这个区域内。
  • 使用glEnable(G_SCISSOR_TEST);开启测试,或使用glDisable来禁止测试
  • 如果片元位于矩形区域内,那么它将通过剪切测试。
  • 使用glScissor设置剪切盒。
cpp 复制代码
// 设置剪切矩形(或者剪切盒)的位置与大小。
// 参数定义了矩形的左下角(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);
  • 获取是否开启了剪切测试和查询剪切盒:
cpp 复制代码
// 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),这些点的位置由硬件或驱动决定。
  • 光栅化阶段:
  1. 几何图元(如三角形)被覆盖时,计算每个子采样点是否在图元内。
  2. 若至少一个子采样点被覆盖,该像素的片段会进入后续处理(如着色器)。
  3. 最终的像素颜色由所有子采样点的覆盖率混合决定,平滑边缘。
  • 仅对几何边缘的像素进行多次采样,其他区域保持单次采样,性能高效。
  • 片段着色器仅运行一次,结果被多个子采样点共享(除非使用 sample 关键字修饰变量)。
  • 每个子采样点独立进行深度和模板测试,确保边缘精度。
  • 默认情况下,多重采样在计算片元的覆盖率时不会考虑alpha的影响。
  • 如果假设多重采样本身已经开启(使用glEnable(GL_MULTISAMPLE);),并且帧缓存已经关联了一个多重采样的缓存数据,那么可用的特定模式 如下:
    均可以使用glEnable
cpp 复制代码
// 使用片元的 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)进行对应,它将与片元的采样覆盖值再次进行"与"操作
cpp 复制代码
// 设置一个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)启用和禁用模板测试,默认禁止模板测试
  • 只有在建立窗口的过程中,有预先请求模板缓存的前提下,才能使用模板测试,否则没有模板缓存,模板测试永远通过
  • 模板测试过程中会取像素在模板缓存中的值,然后与一个参考值进行比较,然后根据比较 / 测试的结果不同,还可以对模板缓存中的数据进行更改
  • 可以使用各种特定的比较函数、参考值,然后使用glStencilFuncglStencilOp来完成修改操作
cpp 复制代码
//参考值将与板缓存中已有的值进行比较,
//但是在此之前需要与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的值使用。
cpp 复制代码
// 设置当片元通过或者没有通过模板测试的时候,要如何处理模板缓存中的数据。
// 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来查询参数
cpp 复制代码
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 模板示例:绘制三角形黑色边框

cpp 复制代码
#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() {
    // 初始化GLFW
    if (!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);

    // 设置原始三角形VAO
    glBindVertexArray(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);

    // 设置边框三角形VAO
    glBindVertexArray(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); // 总是通过模板测试,将三角形对应像素模板值写入1
        glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE); // 通过时替换为1
        glUseProgram(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;
}
相关推荐
西岸行者11 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意11 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码11 天前
嵌入式学习路线
学习
毛小茛11 天前
计算机系统概论——校验码
学习
babe小鑫11 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms11 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下11 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。11 天前
2026.2.25监控学习
学习
im_AMBER11 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J11 天前
从“Hello World“ 开始 C++
c语言·c++·学习