您的位置:首页 > 编程语言

OpenGL 编程指南(第八版)学习笔记——4 颜色、像素和帧缓冲

2018-03-04 09:47 393 查看
OpenGL 编程指南学习资料以及我整理的代码下载地址https://pan.baidu.com/s/1bqrcspD
文中提到的代码为下载文件中的“OpenGL编程指南 VS2015代码.zip”文件

4 颜色、像素和帧缓冲

光栅化与插值

光栅化
光栅化是渲染管线中执行片元着色器之前的步骤。光栅化的作用是计算图形所占的像素。例如绘制一个三角形,当顶点着色器输出三个点的数据后,此时只有三个点的信息,光栅化会计算这个三角形所占的像素面积,之后会对三角形中每个像素调用一次片元着色器。
 
插值
用以下数据绘制图形Struct  Vertex{    GLubyte  color[4];    GLfloat  position[2];};Vertex  Verts[3] = { { {255,0,0,255 },{ 0.8, 0.9 } },                    {{ 0,255,0,255 },{ -0.9, -0.9 } },
                    {{ 0,0,255,255 },{ 0.9, -0.9 } } };
绘制结果:



之所以会有颜色渐变是因为OpenGL在绘制时对颜色进行了线性插值,插值函数和原理如下:
需要绘制以下三角形时,使用I = f(x,y) = k1*x + k2*y + b进行插值,求出k1, k2, b即可得到插值函数。
 


以下以红色分量R为例求k1, k2, b:
R1 = k1*x1 + k2*y1 + b
R2 = k1*x2 + k2*y2 + b
R3 = k1*x3 + k2*y3 + b
使用这三个等式即可求出k1, k2, b的值。
 

多重采样

使用多重采样,需要创建窗口时开启多重采样缓存和双绘制缓存,GLUT开启如下:
         glutInitDisplayMode(GLUT_RGBA|GLUT_DOUBLE|GLUT_MULTISAMPLE);
开启多重采样缓存后,绘制时多重采样默认是开启的,也可以通过glDisable/glEnable(GL_MULTISAMPLE)关闭和开启多重采样。关闭和开启时的绘制效果如下:



 

片元测试

剪切测试
限制绘制在一定的矩形区域内,但不会改变视口的大小。
这是一个很有用的功能,例如可以在一个矩形区域内设置一些滚动内容,滚动超出矩形区域的内容将会被裁剪,我们可以将剪切区域设为滚动内容显示的区域。
 
深度测试
深度测试,深度测试在模板测试之后执行,因为深度测试更好理解,所以先讲深度测试。
绘制图形没有深度测试的情况下,先绘制的图形会被后绘制的图形所覆盖,所以绘制先后会决定那个图形显示在前面,哪个图形会被遮挡。有深度测试的情况下,系统会有一个深度缓存,这个缓存会存储帧缓冲中每个像素的深度,当有一个像素需要写入时,会先将帧缓存中的像素的深度值与新像素的深度值对比,如果新像素深度值更小,则将新像素写入帧缓存,否则丢弃新像素。有了深度测试以后可以通过设置深度决定哪个图形绘制在前面,就可以不必考虑绘制的先后顺序来决定哪个图形在前面。
OpenGL中像素的深度值由顶点的z轴决定,z必须时规范化坐标,即大小必须在[-1.0, 1.0]之间,超出范围的像素都会被抛弃。OpenGL的深度值限制在[0.0, 1.0]之间, z=-1.0时深度值为0.0,z=1.0时深度值为1.0。
要开启深度测试需要创建窗口时开启深度缓存(GLUT开启方法glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH )),绘制时并用glEnable(GL_DEPTH_TEST)开启深度测试。
深度测试绘制(代码对应工程:4.5.5 DepthTest),使用glDrawArrays(GL_TRIANGLES, 0, 6)一次绘制两个三角形,绘制使用的数据和结果如下:structVertex{   GLubytecolor[4];   GLfloatposition[3];};VertexVerts[6] = { { { 255,0,0,255 },    { 0.0f,  0.0f, 0.0f } },  {{ 0,255,0,255 },    { -0.9f, -0.9f, 0.0f} },  {{ 0,0,255,255 },    {  0.9f, -0.9f, 0.0f } } ,   {{ 255,255,255,255 },{  0.0f,  0.4f, 0.1f } } ,  {{ 255,255,255,255 },{ -0.4f, -0.4f, 0.1f } } ,
  {{ 255,255,255,255 },{  0.4f, -0.4f, 0.1f} } };



开启深度测试后,因为彩色三角形深度值更小,所以彩色三角形绘制在白色三角形前面。
深度值偏移:当两个图形的深度值相同时,为了明确哪个三角形绘制在前面还是后,可以使用函数glPolygonMode()对其中一个三角形的深度值进行偏移,可增加或减少深度值。
 
模板测试
深度测试是像素比较深度值,模板测试则是像素比较模板值。在渲染管线中需要先进行模板测试,再进行深度测试,模板测试失败像素会被丢弃不会进行深度测试等。使用模板测试也需要模板缓存,需要在创建窗口时分配,GLUT如下:glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_STENCIL ),绘制时也需要使用glEnable(GL_STENCIL_TEST)开启模板测试。模板缓存会为每个像素存储一个int类型的模板值。模板值并不像深度值是顶点数据的z轴决定,模板值并不是由顶点数据决定而是由glStencilFunc()/glStencilOp()函数决定。具体使用见以下例子(对应代码4.5.4StencilTest:),该例子绘制了一个镂空的三角形:
绘制数据:
struct Vertex
{
         GLubytecolor[4];
         GLfloatposition[2];
};
//大三角形
Vertex Verts[3] = { { { 255,0,0,255 },{0.0f, 0.9f } },
                                     {{ 0,255,0,255 },{ -0.9f, -0.9f } },
                                     {{ 0,0,255,255 },{ 0.9f, -0.9f } } };
float VertsNone[3][2] = { { 0.0f, 0.4f  },//大三角形镂空区域
                   { -0.4f, -0.4f } ,
                   { 0.4f, -0.4f  }};
1.      在程序初始化init函数中
         glClearColor(0.2,0.1, 0.3, 1.0); //设置背景颜色
   glClearStencil(0x0);//设置模板初识值
2.      绘制函数中
//绘制背景颜色,并将模板值清除设为 0x0
glClear(GL_COLOR_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
   glEnable(GL_STENCIL_TEST);
 
         //绘制镂空区域
         glBindVertexArray(vertNone);
         //设置模板测试总是通过
   glStencilFunc(GL_ALWAYS, 0x1, 0x1);
         //设置模板值总是替换为0x1
   glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
         //禁止绘制颜色,从而不会绘制出图形,只会更新模板值
   glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
         glDrawArrays(GL_TRIANGLES,0, 3);
   glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
 
         //绘制彩色三角形
         glBindVertexArray(vert);
         //设置模板值不等于 0x1时才通过模板测试,
//这样镂空三角形区域内像素不会通过模板测试
   glStencilFunc(GL_NOTEQUAL, 0x1, 0x1);
         //设置模板值不更新
   glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
         glDrawArrays(GL_TRIANGLES,0, 3);
3.      绘制时先绘制一次镂空区域,将镂空区域的模板值设置为0x1,然后在绘制大的彩色三角形,绘制时只有模板值不等于0x1才通过测试,这样镂空区域就不会被绘制。
4.      绘制结果:


 
融合
融合从字面上理解就是将像素融合在一起,融合的对象是新像素和帧缓存中的像素。当帧缓存中绘制一个新像素时,我们可以把新像素与帧缓存中已有的像素按一定规律进行混合,这就是融合。融合中一个很重要的应用就是透明显示。绘制一个透明物体时,绘制后的新像素既要保留之前帧缓存中的信息又要有透明物体的信息。假如物体透明度为s(s在0到1之间),我们只需要把透明物体像素*s再加上缓存中像素*(1-s)就可以使得物体看上去透明。颜色有是个分量RGBA,我们可以把A分量当成透明度进行绘制,如一下例子(对应代码4.5.6BlendTest):
1.      数据
struct Vertex
{
   GLubyte color[4];
   GLfloat position[3];
};
Vertex Verts[6] = { { { 255,0,0,255 },{0.0f, 0.9f, 0.0f } },
{ { 0,255,0,255 },{ -0.9f, -0.9f, 0.0f } },
{ { 0,0,255,255 },{ 0.9f, -0.9f, 0.0f } } ,
{ { 255,255,255,105 },{ 0.5f, 0.4f, -0.1f }} ,
{ { 255,255,255,105 },{ 0.1f, -0.4f, -0.1f} } ,
{ { 255,255,255,105 },{ 0.9f, -0.4f, -0.1f} } };
 
2.      绘制
   glEnable(GL_BLEND);//开启融合
    glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);//设置融合函数
   glDrawArrays(GL_TRIANGLES, 0, 6);
3.      结果:


 
逻辑操作
逻辑操作每次渲染都有,是渲染的最后一个步骤。默认情况下逻辑操作是GL_COPY,即将新像素替换帧缓存中的像素。我们可以将逻辑操作设为GL_INVERT(反转),绘制是新像素的颜色并不会有任何作用,只是会将原有帧缓存中像素的颜色反转,具体见例子4.5.11 LogicOpTest。


 

图元反走样

图元走样
当图形或者线条出现锯齿时,则为走样,如下图:


 
图元反走样
消除上图的锯齿则为反走样,如下图:


 
图元反走样原理
前面提到的多重采样也可以有反走样的效果,但对于多边形效果不是很好,而且多重采样非常消耗性能。与第一张图相比,反走样后线段和图形明显看上去更加平滑,没有锯齿。观察两张图右边线段放大部分,可以发现走样时线条两边的像素很锐利,而启用反走样后线条两边有些像素看上去有些模糊,如果仔细观察这些“模糊”,其实这些模糊的像素是透明的。启用反走样后OpenGL在绘制线条时,如果线条只覆盖了像素的一部分,则这个像素的Alpha值会被设置为像素的覆盖率,绘制的时候开启融合,设置融合函数为透明绘制函数,就可以达到反走样的效果。
 
绘制反走样图形
首先声明以下有些显卡并不支持图元反走样,例如intel的一些集成显卡,我的笔记本是集显,测试时不支持,我台式机是GTX1050TI显卡,测试时完全支持。
因为要将像素绘制成透明,所以绘制时要启用融合,如下例子(对应代码:4.6 Smooth):
1.      数据
struct Vertex
{
   GLubyte color[4];
   GLfloat position[3];
};
Vertex Verts[6] = { { { 255,0,0,255 },{0.6f, 0.9f, 0.0f } },
{ { 0,255,0,255 },{ -0.9f, -0.9f, 0.0f } },
{ { 0,0,255,255 },{ 0.9f, -0.9f, 0.0f } } ,
{ { 255,255,255,255 },{ 0.3f, 0.0f, 0.2f }} ,
{ { 255,255,255,255 },{ -0.4f, -0.8f, 0.2f} } ,
{ { 255,255,255,255 },{ 0.4f, -0.8f, 0.2f }} };
 
2.      绘制
   glEnable(GL_BLEND);//启用融合
   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);//启用透明绘制
   glEnable(GL_LINE_SMOOTH);//启用线段平滑
   glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); // 设置线段平滑程度
   glLineWidth(6);
   glDrawArrays(GL_LINE_LOOP, 0, 3);
 
   glEnable(GL_BLEND);//启用融合
   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);//启用透明绘制
   glEnable(GL_POLYGON_SMOOTH);//启用图形平滑
   glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST); //设置图形平滑程度
   glDrawArrays(GL_TRIANGLES, 3, 3);
3.      结果:
结果不贴图了,本结两张图例,就是这个程序的绘制结果。
 

帧缓存

除了可以在默认的帧缓存上绘制图形外,我也可以创建自己的帧缓存绘制。绘制完自己的帧缓存后,我们可以把该缓存复制到窗口缓存上,也可以用作纹理(图片),也可以保存为截图等等。
 
帧缓存构成
帧缓存首先有一个“帧缓存对象(Frame Buffer)”,然后至少还需要一个颜色缓存与之关联才能在帧缓存上绘制图形。当然帧缓存也可以像窗口缓存一样添加深度缓存和模板缓存,用于深度测试和模板测试,而且帧缓存可以有多个颜色附件。帧缓存=帧缓存对象+深度缓存+模板缓存+颜色缓存0+颜色缓存1+…+颜色缓存i
 
帧缓存多颜色缓存绘制
帧缓存可以同时向多个颜色缓存中绘制数据,且每个帧缓存颜色可以不一样,具体步骤如下:
1.  在片元着色器中设置多个输出,每个输出使用layout(location = 1)定位要输出的颜色缓存。
2.  创建帧缓存,并关联多个颜色
3.  绑定帧缓存进行绘制glBindFramebuffer(GL_DRAW_FRAMEBUFFER,FrameBuf)
4.  默认情况下只会在GL_COLOR_ATTACHMENT0上绘制,所以需要使用glDrawBuffers()函数设置同时开启多个颜色缓存绘制。
5.  绘制完成图形后使用glBindFramebuffer(GL_READ_FRAMEBUFFER,FrameBuf),将帧缓存设为读取缓存。
6.  使用glReadBuffer()设置需要读取哪个颜色缓存。
7.  使用glBlitFramebuffer()拷贝颜色缓存
具体例子代码请查看:工程4.7 FrameBuffer代码
以下是使用帧缓存同时绘制左下和右下两个三角形,上面三角形是直接在窗口上绘制的。


 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息