本文正在参加「金石计划」
OpenGL混合基础
OpenGL中的混合简单理解就是两个颜色的中和色,比如常见的透明色,我们有一块玻璃,玻璃后面有个物体,那么要显示一种透明的效果,就需要使用到混合的概念。
透明物体可以是完全透明(它使颜色完全穿透)或者半透明的(它使颜色穿透的同时也显示自身颜色)。一个物体的透明度,被定义为它的颜色的alpha值,alpha颜色值使用颜色向量的第四个元素。
混合方程
OpenGL中有两种混合方式:
- 第一种:可以简单的给定一个透明度alpha的阈值,对于低于阈值alpha的片段进行丢弃,高于阈值的片段进行保留,可以得到一个混合后的效果。但是这种混合方式会使得部分区域完全保留,部分区域完全丢弃,不适合透明下的场景。
- 第二种:采用一个混合公式对片段颜色进行混合。
OpenGL以下面的方程进行混合:
这里简单说明下源向量和目标向量的概念:
源向量:就是将要使用的覆盖目标向量的颜色。
目标向量:将要被源向量覆盖的颜色向量。
通过定义也可以看出,我们在绘制的时候需要先绘制目标向量,再绘制源向量。
如果顺序搞反了,先绘制了源向量,再绘制了目标向量,那么在绘制源向量的过程中,由于目标向量还不存在,就会出现目标向量被完全覆盖的情况(假设开启了深度测试)。
混合API详解
-
开启混合
glEnable(GL_BLEND);
GL_BLEND
用来启用混合(Blending) 功能。
-
混合函数因子设置:
void glBlendFunc(GLenum sfactor, GLenum dfactor)
glBlendFunc接收两个参数:用来设置源和目标因子。
OpenGL为我们定义了很多选项,我们把最常用的列在下面:
选项 值 GL_ZERO 00 GL_ONE 11 GL_SRC_COLOR 源颜色向量C_source GL_ONE_MINUS_SRC_COLOR 1−C_source GL_DST_COLOR 目标颜色向量C_destination GL_ONE_MINUS_DST_COLOR 1−C_destination GL_SRC_ALPHA C_source alpha值 GL_ONE_MINUS_SRC_ALPHA 1−C_source alpha值 GL_DST_ALPHA C_destination的alpha值 GL_ONE_MINUS_DST_ALPHA 1−C_destination的alpha值 GL_CONSTANT_COLOR 常颜色向量C_constant GL_ONE_MINUS_CONSTANT_COLOR 1−C_constant GL_CONSTANT_ALPHA C_constant的alpha值 GL_ONE_MINUS_CONSTANT_ALPHA 1−C_constant的alpha值 注意,颜色常数向量可以用
glBlendColor
函数分开来设置。为了将两个颜色按alpha透明度进行混合,我们需要把源颜色的alpha给源因子,1−alpha给目标因子,如下设置:
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
当然你也可以使用
glBlendFuncSeparate
函数来分别对RGB和alpha因子设置:glBlendFuncSeparate (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha);
-
设置混合模式是相加还是相减
void glBlendEquation(GLenum mode)
有三种模式可选:
- GL_FUNC_ADD:默认的,彼此元素相加:result = Src+Dst;
- GL_FUNC_SUBTRACT:彼此元素相减:result = Src−Dst;
- GL_FUNC_REVERSE_SUBTRACT:彼此元素相减,但顺序相反:result = Dst−Src;
一般我们使用默认的GL_FUNC_ADD即可,如果需要实现一些其他效果,可以选择其他模式、
了解了以上api之后,下面我们来写个demo
混合操作实践
下面我们在之前代码基础上,给模型加上一个透明窗户的效果:渲染半透明纹理
代码如下:
- 顶点着色器代码:
"#version 300 es \n"
"layout(location = 0) in vec3 vPosition;\n"
"layout(location = 1) in vec2 texCords;\n"
"out vec2 TexCords;\n"
"uniform mat4 model;\n"
"uniform mat4 view;\n"
"uniform mat4 projection;\n"
"void main()"
"{ \n"
" gl_Position = projection*view*model*vec4(vPosition,1.0f); \n"
" TexCords = texCords; \n"
"} \n";
- 片段着色器代码:
"#version 300 es \n"
"precision mediump float;\n"
"uniform sampler2D cubeTexture;"
"in vec2 TexCords;"
"out vec4 outColor;\n"
"void main()"
"{ \n"
" vec3 _texture = vec3(texture(cubeTexture,TexCords));\n"
" outColor = vec4(_texture, 0.6f); \n"
"} \n";
- 绘制代码:
void MyGLRenderContext::OnDrawFrame() {
isDrawing = true;
beforeDraw();
if(programObj == 0){
return;
}
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
//清除buffer
glEnable(GL_DEPTH_TEST);
glEnable(GL_STENCIL_TEST);
glStencilOp(GL_KEEP,GL_KEEP,GL_REPLACE);
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
//part1:绘制立方体
{
//总是写入模板缓冲区,并让模板缓冲区可写
glStencilFunc(GL_ALWAYS,1,0xff);
glStencilMask(0xff);
//使用立方体程序着色器对象
useProgram(programObj);
//绑定VAO
glBindVertexArray(VAO);
//观察者位置
setCordder(model);
//绑定纹理
setUniformTexture("textureColor",textureID);
//开始绘制
glDrawArrays(GL_TRIANGLES,0,36);
//解绑VAO
glBindVertexArray(GL_NONE);
}
//part2:绘制物体轮廓
{
//绘制模板缓冲区中不等于1的部分,并让模板缓冲区不可写,且屏蔽深度测试
glStencilFunc(GL_NOTEQUAL,1,0xff);
glStencilMask(0x00);
glDisable(GL_DEPTH_TEST);
//使用物体轮廓绘制的着色器对象
useProgram(singleColorShader);
glBindVertexArray(VAO);
GLfloat scale = 1.1f;
//第一次绘制时缩放到1.1倍
if(isFirstDraw){
modelScale = glm::scale(modelScale, glm::vec3(scale, scale, scale));
isFirstDraw = false;
}
//设置坐标系的变换矩阵
setCordder(modelScale);
//开始绘制
glDrawArrays(GL_TRIANGLES,0,36);
//重置模板缓冲区参数并开启深度测试,释放资源
glStencilMask(0xFF);
glStencilFunc(GL_ALWAYS, 0, 0xFF);
glEnable(GL_DEPTH_TEST);
glBindVertexArray(GL_NONE);
//解绑程序着色器对象
glUseProgram(GL_NONE);
}
//part3:绘制窗户
{
//使用一个新的着色器对象
useProgram(cubeProObj);
//绑定窗户顶点数据
glBindVertexArray(cubeVAO);
//设置坐标系
setCordder(modelCube);
//设置纹理坐标:窗户
setUniformTexture("cubeTexture",cubeTextureID);
//绘制正方形
glDrawElements(GL_TRIANGLES,6,GL_UNSIGNED_INT,0);
isDrawing = false;
glBindVertexArray(GL_NONE);
//解绑程序着色器对象
useProgram(GL_NONE);
}
}
- 最后效果: