005 --- 多边形偏移、裁剪、混合、 抗锯齿
多边形偏移
前言
虽然深度测试能够实现真实的视觉并提高性能,但有时也会带来一些小麻.如果有意将两个几何图形绘制到同一位置时,会带来一些问题.
例如,如果绘制一架大型飞机,然后在飞机在绘制一个较小的的但却与飞机在同一物理空间的图形,这就叫做 贴花
.
这种情况下星形图案的深度值将会与绘制原来的飞机的深度缓冲区值相同,或者机会相同,这会导致片段深度测试不可预料的通过或失败,这种情况称为 z-fighting(z冲突)
.
如何处理z冲突
另外一种情况,绘制实心的几何图形但又要突出它的边时.
image.png image.png问题在于,如果我们在实体条带的同一位置绘制线框,就会遇到
z-fighting(z冲突)
的问题.
- 处理深度值冲突的方法:
- 手动调整
z
值进行一点点偏移,但可能会出现图形悬浮(不推荐
) - 利用
多边形偏移
调节片段的深度值,但实际不改变3D
空间物理位置(推荐
)
- glPolygonOffset
GLAPI void GLAPIENTRY glPolygonOffset (GLfloat factor, GLfloat units);
- 应用到片段上的总偏移可以通过下面方程式表示:
-
其中
DZ
是深度值相对多边形屏幕区域的变化量,r 是
使深度缓冲区值产生变化的最小值,这2个值都是 OpenGL 内部的值,我们不用关心,我们是通过控制factor
和units
达到效果. -
负值将是z值距离我们
更近
,而正直则会将他们移动的更远
. -
一般而言,只要将
-1 和 -1
这样简单赋值给glPolygonOffset
基本可以满足需求.
- 多边形单独偏移
- 绘制一个绿色有黑色线框的几何图形
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();
}
- 效果如图
混合
首先了解一个概念:
-
颜色缓冲区(
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);
- 这个函数表示OpenGL接受
源颜色
并将这个颜色(RGB值)
与alpha
值相乘
- 目标颜色乘以 乘以
"1-源颜色的alpha值"
- 最后相加
/*
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
这种值都会在混合方程式混合一个常量颜色值
.
- 常量混合颜色初始值{ 0.0f,0.0f,0.0f,0.0f}
- 使用如下函数进行修改:
void glBlendColor(GLclampf red ,GLclampf green ,GLclampf blue ,GLclam pf alpha )
抗锯齿
OpenGL
混合的另外一种用途就是抗锯齿。图形边缘会出现一些吸引眼睛的注意力而让人感觉图形不自然的像素点,称为 锯齿
。我们需要尽可能的逼真,尤其在游戏、模拟和艺术创造中。为了消除图元之间的锯齿,OpenGL
利用混合功能,把像素的目标颜色和周围像素的颜色进行混合。
- 抗锯齿开启条件
- 开启混合功能
glEnable(GL_BLEND);
- 设置混合方程式为 GL_ADD
这个是默认值,所以不用代码再去设置一次
- 设置混合方程式因子为 S = GL_SRC_ALPHA, D = GL_ONE_MINUS_SRC_ALPHA
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
- 开启抗锯齿功能(点/线/多边形)
glEnable(GL_LINE_SMOOTH)
: 线
glEnable(GL_POINT_SMOOTH)
: 点
glEnable(GL_POLYGON_SMOOTH)
: 多边形(任何实心图元)
- 举例子,未启用抗锯齿之前
- 启用抗锯齿代码
glHint
有许多算法和方法可以实现抗锯齿处理的图元.任何特定的OpenGL实现都可以选择其中的一种,或者支持两种方法.
glHint函数原型:
```swift
void glHint(GLenum target,GLenum mod)
```
参数说明:
-
target
:指定所控制行为的符号常量,可以是以下值
-
GL_FOG_HINT
:指定雾化计算的精度。如果OpenGL
实现不能有效的支持每个像素的雾化计算,则GL_DONT_CARE和GL_FASTEST
雾化效果中每个定点的计算。 -
GL_LINE_SMOOTH_HINT
:指定反走样线段的采样质量。如果应用较大的滤波函数,GL_NICEST
在光栅化期间可以生成更多的像素段。 -
GL_PERSPECTIVE_CORRECTION_HINT
:指定颜色和纹理坐标的差值质量。如果OpenGL不能有效的支持透视修正参数差值,那么GL_DONT_CARE
和GL_FASTEST
可以执行颜色、纹理坐标的简单线性差值计算。 -
GL_POINT_SMOOTH_HINT
:指定反走样点的采样质量,如果应用较大的滤波函数,GL_NICEST
在光栅化期间可以生成更多的像素段。 -
GL_POLYGON_SMOOTH_HINT
:指定反走样多边形的采样质量,如果应用较大的滤波函数,GL_NICEST
在光栅化期间可以生成更多的像素段。
-
mod
:指定所采取行为的符号常量,可以是以下值
-
GL_FASTEST
:选择速度最快选项。 -
GL_NICEST
:选择最高质量选项。 -
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