005 --- 多边形偏移、裁剪、混合、 抗锯齿

image.png

多边形偏移

前言

虽然深度测试能够实现真实的视觉并提高性能,但有时也会带来一些小麻.如果有意将两个几何图形绘制到同一位置时,会带来一些问题.

例如,如果绘制一架大型飞机,然后在飞机在绘制一个较小的的但却与飞机在同一物理空间的图形,这就叫做 贴花.

这种情况下星形图案的深度值将会与绘制原来的飞机的深度缓冲区值相同,或者机会相同,这会导致片段深度测试不可预料的通过或失败,这种情况称为 z-fighting(z冲突).

如何处理z冲突

另外一种情况,绘制实心的几何图形但又要突出它的边时.

image.png
image.png

问题在于,如果我们在实体条带的同一位置绘制线框,就会遇到 z-fighting(z冲突) 的问题.

  • 处理深度值冲突的方法:
  1. 手动调整z值进行一点点偏移,但可能会出现图形悬浮(不推荐)
  2. 利用 多边形偏移 调节片段的深度值,但实际不改变3D空间物理位置(推荐)
  • glPolygonOffset

GLAPI void GLAPIENTRY glPolygonOffset (GLfloat factor, GLfloat units);

  • 应用到片段上的总偏移可以通过下面方程式表示:
image.png
  1. 其中 DZ是深度值相对多边形屏幕区域的变化量,r 是使深度缓冲区值产生变化的最小值,这2个值都是 OpenGL 内部的值,我们不用关心,我们是通过控制 factorunits 达到效果.

  2. 负值将是z值距离我们更近,而正直则会将他们移动的更远.

  3. 一般而言,只要将-1 和 -1 这样简单赋值给 glPolygonOffset基本可以满足需求.

  • 多边形单独偏移
image.png
  • 绘制一个绿色有黑色线框的几何图形

void DrawWireFramedBatch(GLBatch* pBatch)
{
    /*------------画绿色部分----------------*/
    /* GLShaderManager 中的Uniform 值——平面着色器
     参数1:平面着色器
     参数2:运行为几何图形变换指定一个 4 * 4变换矩阵
          --transformPipeline 变换管线(指定了2个矩阵堆栈)
     参数3:颜色值
    */
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vGreen);
    pBatch->Draw();
    
    /*-----------边框部分-------------------*/
    /*
        glEnable(GLenum mode); 用于启用各种功能。功能由参数决定
        参数列表:http://blog.csdn.net/augusdi/article/details/23747081
        注意:glEnable() 不能写在glBegin() 和 glEnd()中间
        GL_POLYGON_OFFSET_LINE  根据函数glPolygonOffset的设置,启用线的深度偏移
        GL_LINE_SMOOTH          执行后,过虑线点的锯齿
        GL_BLEND                启用颜色混合。例如实现半透明效果
        GL_DEPTH_TEST           启用深度测试 根据坐标的远近自动隐藏被遮住的图形(材料
     
     
        glDisable(GLenum mode); 用于关闭指定的功能 功能由参数决定
     
     */
    
    //画黑色边框
    glPolygonOffset(-1.0f, -1.0f);// 偏移深度,在同一位置要绘制填充和边线,会产生z冲突,所以要偏移
    glEnable(GL_POLYGON_OFFSET_LINE);
    
    
     /*-----------画反锯齿,让黑边好看些-------------------*/
    glEnable(GL_LINE_SMOOTH);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    
    
    
    /*-----------绘制线框几何黑色版 三种模式,实心,边框,点,可以作用在正面,背面,或者两面------------------*/
    
    //通过调用glPolygonMode将多边形正面或者背面设为线框模式,实现线框渲染
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    //设置线条宽度
    glLineWidth(2.5f);
    
    /* GLShaderManager 中的Uniform 值——平面着色器
     参数1:平面着色器
     参数2:运行为几何图形变换指定一个 4 * 4变换矩阵
         --transformPipeline.GetModelViewProjectionMatrix() 获取的
          GetMatrix函数就可以获得矩阵堆栈顶部的值
     参数3:颜色值(黑色)
     */
    
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vBlack);
    pBatch->Draw();


/*-----------复原原本的设置------------------*/
    //通过调用glPolygonMode将多边形正面或者背面设为全部填充模式
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    glDisable(GL_POLYGON_OFFSET_LINE);
    glLineWidth(1.0f);
    glDisable(GL_BLEND);
    glDisable(GL_LINE_SMOOTH);
    
    
}
image.png
image.png

裁剪

另一种提高性能的方法是只刷新屏幕上发生变化的部分.我们还可能需要将OpenGL渲染绘制在窗口中一个较小的矩形区域中.OpenGL可以让我们在想要进行渲染的窗口中指定一个裁剪框.

  • 开启裁剪测试

glEnable(GL_SCISSOR_TEST)

  • 在执行渲染窗口中,被称为裁剪框的矩形是使用如下的函数来指定窗口坐标(像素).

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

  • 代码示例

void RenderScene(void)
{
    //设置清屏颜色为蓝色
    glClearColor(0.0f, 0.0f, 1.0f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    
    //1.现在剪成小红色分区
    //(1)设置裁剪区颜色为红色
    glClearColor(1.0f, 0.0f, 0.0f, 0.0f);
    //(2)设置裁剪尺寸
    glScissor(100, 100, 600, 400);
    //(3)开启裁剪测试
    glEnable(GL_SCISSOR_TEST);
    //(4)开启清屏,执行裁剪
    glClear(GL_COLOR_BUFFER_BIT);
    
    // 2.裁剪一个绿色的小矩形
    //(1).设置清屏颜色为绿色
    glClearColor(0.0f, 1.0f, 0.0f, 0.0f);
    //(2).设置裁剪尺寸
    glScissor(200, 200, 400, 200);
    //(3).开始清屏执行裁剪
    glClear(GL_COLOR_BUFFER_BIT);
    
    //关闭裁剪测试
    glDisable(GL_SCISSOR_TEST);
    
    //强制执行缓存区
    glutSwapBuffers();
}

  • 效果如图
image.png

混合

首先了解一个概念:

  • 颜色缓冲区(COLOR_BUFFER)就是帧缓冲区(FRAME_BUFFER),你需要渲染的场景最终每一个像素都要写入该缓冲区,然后由它在渲染到屏幕上显示.

  • 深度缓冲区(DEPTH_BUFFER)与帧缓冲区对应,用于记录上面每个像素的深度值,通过深度缓冲区,我们可以进行深度测试,从而确定像素的遮挡关系,保证渲染正确.

  • 模版缓冲(STENCIL_BUFFER)与深度缓冲大小相同,通过设置模版缓冲每个像素的值,我们可以指定在渲染的时候只渲染某些像素,从而可以达到一些特殊的效果.

OpenGL渲染时会把颜色值放在颜色缓冲区中,每个片段的深度值也是防砸深度缓冲区中的.

  • 深度测试关闭(禁用)时,新的颜色值简单的覆盖颜色缓冲区已经存在的其他值.

  • 当深度测试打开(启用)时,新的颜色片段只有当它们比原来的值更接近邻区的裁剪平面才会替换原来的颜色片段.

  • 在正在情况下,任何绘制操作不熟完全被抛弃,就是完全覆盖原来的颜色值,这取决于深度测试的结果.

  • 如果打开OpenGL的混合功能,那么下层的颜色值就不会被清除.


GLAPI void GLAPIENTRY glEnable (GLenum cap);

在打开混合功能的情况下,新的颜色会与已经存在的颜色值在颜色缓冲区进行组合.这些颜色的组合方式不同会导致很多不同的特殊颜色.

组合颜色

  • 已经存储在颜色缓冲区的颜色值叫做目标颜色.(前女友)

这个颜色值包含了一个单独的红,绿,蓝乘成分以及一个可选的alpha值.

  • 作为当前渲染命令的结果进入颜色缓冲区的颜色值成为源颜色. (现女友)

这个颜色值包含了一个单独的红,绿,蓝成分以及一个可选的alpha值.

  • 注意:

任何情况下只要我们忽略一个alpha值,OpenGL默认设置为1.0;

当混合功能被启用时,源颜色和目标颜色的组合方式有混合方程式控制的.


Cf = (Cs * S) + (Cd * D)

Cf最终计算产生的颜色
Cs是源颜色
Cd是目标颜色
S D分别是源和目标混合因子.

正如我们看到的那样,S和D都是枚举值,而不是可以指定的实际值.

image.png

颜色值实用浮点表示的,因此可以对它们进行加减甚至乘法都是完全合法的.

  • glBlendFunc混合函数

 GLAPI void GLAPIENTRY glBlendFunc (GLenum sfactor, GLenum dfactor);

  • 假设设置 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  1. 这个函数表示OpenGL接受源颜色并将这个颜色(RGB值)alpha相乘
  2. 目标颜色乘以 乘以"1-源颜色的alpha值"
  3. 最后相加
/*
Rs/Gs/Bs/As - 源颜色 RGBA 各个通道的混合因子
Rd/Gd/Bd/Ad - 目标颜色 RGBA 各个通道的混合因子
Rc/Gc/Bc/Ac - 常量颜色 RGBA 各个通道的混合因子
Cs = 源颜色 = { 0.0f, 0.0f, 1.0f, 0.6f } 
Cd = 目标颜色 = { 1.0f, 0.0f, 0.0f, 1.0f } 

As = 源颜色 alpha 值 = 0.6f
Ad = 目标颜色 alpha 值 = 1.0f

S = 源颜色混合因子 = GL_SRC_ALPHA = As = 0.6f
D = 目标颜色混合因子 = GL_ONE_MINUS_SRC_COLOR = 1.0f - As = 0.4f

Cf = 最终产生颜色 = Cs * 0.6f + Cd * 0.4f = {
   0.0f * 0.6f + 1.0f * 0.4f,
   0.0f * 0.6f + 0.0f * 0.4f,
   1.0f * 0.6f + 0.0f * 0.4f,
   0.6f * 0.6f + 1.0f * 0.4f
} = { 0.4f, 0.6f, 0.0f, 0.76f }
*/
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

等价于:
Cf = (Blue * 0.6) + (Red * 0.4)

最终的颜色是原来的红色(目标颜色)与后来的颜色(源颜色)进行缩放后的结果,源颜色的alpha的值越高,天剑源颜色的成分就越多,目标颜色所保留的成分就越少.

  • 在屏幕上实现一个半透明红色与红,蓝,绿,黑1.0alpha混合之后结果.
  • 代码实现 :

void RenderScene(void)
{
    
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
   
    //1.开启混合
    glEnable(GL_BLEND);
    //2.开启组合函数 计算混合颜色因子
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    
    //定义4种颜色
    GLfloat vRed[] = { 1.0f, 0.0f, 0.0f, 0.5f };
     GLfloat vRed1[] = { 1.0f, 0.0f, 0.0f, 1.0f };
    GLfloat vGreen[] = { 0.0f, 1.0f, 0.0f, 1.0f };
    GLfloat vBlue[] = { 0.0f, 0.0f, 1.0f, 1.0f };
    GLfloat vBlack[] = { 0.0f, 0.0f, 0.0f, 1.0f };
    

    shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vGreen);
    greenBatch.Draw();
    
    shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vRed1);
    redBatch.Draw();
    
    shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vBlue);
    blueBatch.Draw();
    

    shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vBlack);
    blackBatch.Draw();
    
    
    shaderManager.UseStockShader(GLT_SHADER_IDENTITY, vRed);
    squareBatch.Draw();
    
    //5.关闭混合功能
    glDisable(GL_BLEND);
    
    //同步绘制命令
    glutSwapBuffers();
}

image.png

绿色是红色变淡,红色还是红色,蓝色变成紫色,黑色是红色变深.

改变混合方程式

混合方程式 :

Cf = (Cs * S) + (Cd * D)

是默认的方程式,实际上,我们可以从5个不同的混合方程式中进行选择.

image.png

除了使用glBlendFunc之外,还可以利用下面函数更加灵活地选择.

    glBlendFuncSeparate(GLenum srcRGB, GLenum destRGB, GLenum srcAlpha, GLenum destAlpha);

参数srcRGB表示颜色值的源混合因子,参数destRGB表示颜色在的目标混合因子,参数srcAlpha表示Alpha值的源混合因子,参数destAlpha表示Alpha值的目标混合因子

  • 常量混合颜色修改

在使用glBlendFunc传入GL_ONE_MINUS_SRC_ALPHA这种值都会在混合方程式混合一个常量颜色值.

  1. 常量混合颜色初始值{ 0.0f,0.0f,0.0f,0.0f}
  2. 使用如下函数进行修改:

void glBlendColor(GLclampf red ,GLclampf green ,GLclampf blue ,GLclam pf alpha )

抗锯齿

OpenGL 混合的另外一种用途就是抗锯齿。图形边缘会出现一些吸引眼睛的注意力而让人感觉图形不自然的像素点,称为 锯齿。我们需要尽可能的逼真,尤其在游戏、模拟和艺术创造中。为了消除图元之间的锯齿,OpenGL 利用混合功能,把像素的目标颜色和周围像素的颜色进行混合。

  • 抗锯齿开启条件
  1. 开启混合功能

glEnable(GL_BLEND);

  1. 设置混合方程式为 GL_ADD

这个是默认值,所以不用代码再去设置一次

  1. 设置混合方程式因子为 S = GL_SRC_ALPHA, D = GL_ONE_MINUS_SRC_ALPHA

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

  1. 开启抗锯齿功能(点/线/多边形)

glEnable(GL_LINE_SMOOTH) : 线
glEnable(GL_POINT_SMOOTH): 点
glEnable(GL_POLYGON_SMOOTH): 多边形(任何实心图元)

  • 举例子,未启用抗锯齿之前
image.png
  • 启用抗锯齿代码
image.png

glHint

有许多算法和方法可以实现抗锯齿处理的图元.任何特定的OpenGL实现都可以选择其中的一种,或者支持两种方法.

glHint函数原型:

   ```swift

void glHint(GLenum target,GLenum mod)

```

参数说明:

  • target:指定所控制行为的符号常量,可以是以下值
  1. GL_FOG_HINT:指定雾化计算的精度。如果 OpenGL实现不能有效的支持每个像素的雾化计算,则 GL_DONT_CARE和GL_FASTEST 雾化效果中每个定点的计算。

  2. GL_LINE_SMOOTH_HINT:指定反走样线段的采样质量。如果应用较大的滤波函数,GL_NICEST 在光栅化期间可以生成更多的像素段。

  3. GL_PERSPECTIVE_CORRECTION_HINT:指定颜色和纹理坐标的差值质量。如果OpenGL不能有效的支持透视修正参数差值,那么 GL_DONT_CAREGL_FASTEST 可以执行颜色、纹理坐标的简单线性差值计算。

  4. GL_POINT_SMOOTH_HINT:指定反走样点的采样质量,如果应用较大的滤波函数,GL_NICEST 在光栅化期间可以生成更多的像素段。

  5. GL_POLYGON_SMOOTH_HINT:指定反走样多边形的采样质量,如果应用较大的滤波函数,GL_NICEST 在光栅化期间可以生成更多的像素段。

  • mod:指定所采取行为的符号常量,可以是以下值
  1. GL_FASTEST:选择速度最快选项。

  2. GL_NICEST:选择最高质量选项。

  3. GL_DONT_CARE:对选项不做考虑。

  • 打开抗锯齿,并给出尽可能进行最佳的处理展示

glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    
glEnable(GL_LINE_SMOOTH);
glHint(GL_LINE_SMOOTH, GL_NICEST);
glEnable(GL_POINT_SMOOTH);
glHint(GL_POINT_SMOOTH, GL_NICEST);
glEnable(GL_POLYGON_SMOOTH);
glHint(GL_POLYGON_SMOOTH, GL_NICEST);

image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342