本文为L_Ares个人写作,包括图片皆为个人亲自操作,以任何形式转载请表明原文出处。
混合是一个非常非常重要的内容,和上两节的深度和正背面剔除一样,因为我们在绘制图形的时候,一般的图形都是带有颜色的,当两个图形存在重叠区域的时候,深度问题在上一节已经学习了一些内容了,但是如果z值较小的图形是半透明状态的话,重叠区域的颜色就不可能是这两个图形的任何一种,那么这个颜色如何来实现呢?这也就是混合的重要之处。
一、什么是混合
这里存在着和深度的配合。我们已经知道在OpenGL中存在着深度缓冲区和颜色缓冲区,其中存储了像素点的深度和颜色值,当深度测试开启的时候,深度缓冲区会对比已存储的深度值和新图形的像素点深度值,这有可能会发生改变,就需要更新深度缓冲区的值。
同时,由于该像素点的深度发生了变化,那么这个像素点之前的颜色值就已经不是现在要绘制的图形的颜色值了,新的像素点颜色值也将覆盖原来深度缓冲区中的颜色值。
同理,当关闭深度测试的时候,也可能会发生变化。
开启颜色混合
glEnable(GL_BLEND)
颜色混合不是开启了就可以的,因为它有一个颜色混合的计算方程式,我们先看方程式。
当混合功能被启用时,默认情况下的混合方程式如(1.1)式所示
其中,
:计算所得最终颜色参数
:源颜色
:目标颜色
:源混合因子
: 目标混合因子
好理解,那么余下4个都是什么意思呢?
源颜色
渲染命令结果进入颜色缓冲区的颜色。也就是新来的颜色
目标颜色
之前存储在颜色缓冲区中的颜色
而混合因子则需要手动设置,利用函数void glBlendFunc(GLenum S , GLenum D)
这函数的两个参数都是枚举类型,所以也是由系统提供来进行选择的,可选择的值如下图1.1所示。
其中,是源混合因子,是目标混合因子。
1.1.png图中的RGBA就是正常的Red、Green、Blue、Alpha。S代表源,D代表目标,C代表常量颜色,默认是黑色。
举例:
若设置源混合因子和目标混合因子的参数如下:
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA)
若颜色缓冲区颜色即目标颜色为红色(1.f,0.f,0.f,0.f)
,新进入的颜色即源颜色为透明度为0.6的蓝色(0.f,0.f,1.f,0.6f)
那么混合完成后的颜色可如下计算:
所以方程式 =>
混合计算的方式就是如上所示。
可以看出,如果使用这样的混合因子,那么源颜色蓝色的alpha值越高,目标颜色的alpha值就越低,原来存储在颜色缓冲区中的红色的成分也就越少。
混合函数的使用场景:在不透明的物体前增加了一块儿透明带颜色的物体的效果。
二、扩展——其他的组合方程式和复杂一点的混合因子选择函数
这一模块作为扩展来说。混合方程式肯定不止一种,那么下面来介绍一下如何选用其他的混合方程式。另外,混合因子的选择也可以有一种适应复杂情况的函数可以了解一下。
颜色混合方程式选择
默认颜色混合方程式:
选择混合方程式的函数:glBlendEquation(GLenum mode)
,mode的可选值如下图4.2.1所示。
复杂一点的混合因子选择函数
void glBlendFuncSeparate(GLenum strRGB,GLenum dstRGB ,GLenum strAlpha,GLenum dstAlpha)
strRGB:源颜色的混合因子
dstRGB:目标颜色的混合因子
strAlpha:源颜色的Alpha因子
dstAlpha:目标颜色的Alpha因子
也就是说这个函数可以单独的为源和目标颜色的alpha值单独指定混合因子。
在混合因子的可选列表中存在GL_CONSTANT_COLOR
、GL_ONE_MINUS_CONSTANT_COLOR
、GL_CONSTANT_ALPHA
、GL _ONE_MINUS_CONSTANT
允许混合方程式中引入一个常量混合颜色。
常量混合颜色默认是黑色可以通过如下函数修改:
void glBlendColor(GLclampf red ,GLclampf green ,GLclampf blue ,GLclampf alpha )
三、代码实例
//
// main.cpp
// 05颜色混合
//
// Created by EasonLi on 2020/8/27.
// Copyright © 2020 EasonLi. All rights reserved.
//
#pragma mark - 引用类
//这里面包括了大多数GLTool类似于C语言的类。
//包括了GLBatch三角形批次类和gltSetWorkingDirectory
#include "GLTools.h"
//包括了管理和创建管理着色器的管理者的方法,还有一组存储着色器,可用于一些初级操作
#include "GLShaderManager.h"
//利用宏定义来判断系统,根据不同系统引入不同的glut
#ifdef __APPLE__
#include <glut/glut.h>
#else
#define FREEGLUT_STATIC
#include <GL/glut.h>
#endif
#pragma mark - 定义需要用到的变量
//先定义五个三角形批次类,分别用于四个固定矩形和一个移动的带透明度的矩形
GLBatch redBatch;
GLBatch blueBatch;
GLBatch greenBatch;
GLBatch blackBatch;
GLBatch moveBatch;
//定义着色器管理者
GLShaderManager shaderManager;
//定义矩形的边长的一半
GLfloat blockSize = 0.2f;
//定义一个移动的矩形的顶点坐标数组
GLfloat moveVerts[] = {
-blockSize,blockSize,0,
blockSize,blockSize,0,
blockSize,-blockSize,0,
-blockSize,-blockSize,0
};
//定义红色矩形顶点坐标数组
GLfloat redVerts[] = {
0.25f, 0.25f, 0.0f,
0.75f, 0.25f, 0.0f,
0.75f, 0.75f, 0.0f,
0.25f, 0.75f, 0.0f
};
//定义绿色矩形顶点坐标数组
GLfloat greenVerts[] = {
-0.75f, 0.25f, 0.0f,
-0.25f, 0.25f, 0.0f,
-0.25f, 0.75f, 0.0f,
-0.75f, 0.75f, 0.0f
};
//定义蓝色矩形顶点坐标数组
GLfloat blueVerts[] = {
-0.75f, -0.75f, 0.0f,
-0.25f, -0.75f, 0.0f,
-0.25f, -0.25f, 0.0f,
-0.75f, -0.25f, 0.0f
};
//定义黑色矩形顶点坐标数组
GLfloat blackVerts[] = {
0.25f, -0.75f, 0.0f,
0.75f, -0.75f, 0.0f,
0.75f, -0.25f, 0.0f,
0.25f, -0.25f, 0.0f
};
//定义颜色数组
//可移动的矩形颜色数组
GLfloat vMove[] = {0.8,0.5,0.3,0.5};
//红色
GLfloat vRed[] = {1.f,0.f,0.f,1.f};
//绿色
GLfloat vGreen[] = {0.f,1.f,0.f,1.f};
//蓝色
GLfloat vBlue[] = {0.f,0.f,1.f,1.f};
//黑色
GLfloat vBlack[] = {0.f,0.f,0.f,1.f};
#pragma mark - 函数
/**
创建并设置视口
1.触发本函数的条件
(1)视口第一次创建
(2)视口大小发生改变
2.处理的业务
(1)设置OpenGL的视口
(2)设置投影方式
3.参数说明
(1)x,y都是代表了窗口的左下角坐标,所以我设置了0,0
(2)w和h是宽和高的单位都是像素
*/
void ChangeSize(int w, int h)
{
glViewport(0, 0, w, h);
}
/**
设置渲染环境
1.触发本函数的条件
(1)手动触发
2.处理的业务
(1)设置窗口的清屏颜色
(2)初始化着色器管理器
(3)设置顶点信息和绘制方式
(4)利用三角形批次类将渲染需要的数据拷贝到存储着色器
*/
void SetUpRC()
{
//设置清屏颜色
glClearColor(0.3f, 0.3f, 0.3f, 0.f);
//初始化着色器管理器
shaderManager.InitializeStockShaders();
//设置顶点信息和绘制方式
//移动的矩形
moveBatch.Begin(GL_TRIANGLE_FAN, 4);
moveBatch.CopyVertexData3f(moveVerts);
moveBatch.End();
//红色矩形
redBatch.Begin(GL_TRIANGLE_FAN, 4);
redBatch.CopyVertexData3f(redVerts);
redBatch.End();
//绿色矩形
greenBatch.Begin(GL_TRIANGLE_FAN, 4);
greenBatch.CopyVertexData3f(greenVerts);
greenBatch.End();
//蓝色矩形
blueBatch.Begin(GL_TRIANGLE_FAN, 4);
blueBatch.CopyVertexData3f(blueVerts);
blueBatch.End();
//黑色矩形
blackBatch.Begin(GL_TRIANGLE_FAN, 4);
blackBatch.CopyVertexData3f(blackVerts);
blackBatch.End();
}
/**
渲染
1.触发条件
(1)手动触发
(2)系统自动触发
2.处理业务
(1)清空缓冲区
(2)调用着色器
(3)绘制图形
*/
void RenderScene()
{
//清空缓冲区
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
//选定着色器种类和要绘制的颜色
//种类:统一都是单元着色器
//颜色:用各自对应的颜色
//开启颜色混合
glEnable(GL_BLEND);
//设置混合因子
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
//红色矩形
shaderManager.UseStockShader(GLT_SHADER_IDENTITY,vRed);
redBatch.Draw();
//绿色矩形
shaderManager.UseStockShader(GLT_SHADER_IDENTITY,vGreen);
greenBatch.Draw();
//蓝色矩形
shaderManager.UseStockShader(GLT_SHADER_IDENTITY,vBlue);
blueBatch.Draw();
//黑色矩形
shaderManager.UseStockShader(GLT_SHADER_IDENTITY,vBlack);
blackBatch.Draw();
//移动矩形
shaderManager.UseStockShader(GLT_SHADER_IDENTITY,vMove);
moveBatch.Draw();
glDisable(GL_BLEND);
glutSwapBuffers();
}
#pragma mark - 特殊键位
//移动方块
void SpecialKeys(int key, int x, int y)
{
//每次移动的距离
GLfloat stepSize = 0.025f;
//只要计算一个坐标顶点的移动,然后加上边长就可以计算其他的顶点
//此处拿出第一个顶点做移动
GLfloat blockX = moveVerts[0];
GLfloat blockY = moveVerts[1];
//判定键位做移动
switch (key) {
case GLUT_KEY_UP:
blockY += stepSize;
break;
case GLUT_KEY_DOWN:
blockY -= stepSize;
break;
case GLUT_KEY_LEFT:
blockX -= stepSize;
break;
case GLUT_KEY_RIGHT:
blockX += stepSize;
break;
}
if (blockX < -1.f) {
blockX = -1.f;
}
if (blockX > (1.f - 2 * blockSize)) {
blockX = 1.f - 2 * blockSize;
}
if (blockY > 1.f) {
blockY = 1.f;
}
if (blockY < (-1.f + 2 * blockSize)) {
blockY = -1.f + 2 * blockSize;
}
//计算每一个点的坐标重新赋值
moveVerts[0] = blockX;
moveVerts[1] = blockY;
moveVerts[3] = blockX + 2 * blockSize;
moveVerts[4] = blockY;
moveVerts[6] = blockX + 2 * blockSize;
moveVerts[7] = blockY - 2 * blockSize;
moveVerts[9] = blockX;
moveVerts[10] = blockY - 2 * blockSize;
moveBatch.CopyVertexData3f(moveVerts);
glutPostRedisplay();
}
#pragma mark - main函数
int main (int argc,char *argv[])
{
//设置工作区间
//MacOS和Windows不一样,工作目录和项目目录可能不在同一个文件夹下
//需要将工作目录设置到程序的/Resource目录下
//GLUT的优先级设定已经做过这样的操作了,但是手动设置一下可以保证百分百
gltSetWorkingDirectory(argv[0]);
//初始化GLUT库
//GLUT是一个程序内部的本地消息循环,会不断的拦截适当的消息
//调用我们在不同时间注册的回调函数
glutInit(&argc, argv);
//初始化显示模式
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
//初始化窗口大小
glutInitWindowSize(800, 600);
//创建窗口并命名
glutCreateWindow("颜色混合");
//注册函数
//视口大小发生改变时调用的函数
glutReshapeFunc(ChangeSize);
//渲染函数
glutDisplayFunc(RenderScene);
//注册特殊键位函数
glutSpecialFunc(SpecialKeys);
//初始化一个Glew库,确保OpenGL的API完全可用
//在做任何的渲染尝试之前,都要确认驱动程序的初始化不能出现任何的问题
GLenum status = glewInit();
if (GLEW_OK != status) {
printf("glew init error : %s \n",glewGetErrorString(status));
return 1;
}
//设置渲染环境
SetUpRC();
//只是一个无限循环的函数
//在运行的过程中会一直接收并执行窗口或者系统传来的用户指令
//但是它只执行在它之前键入的指令,在它之后键入的指令则不执行
glutMainLoop();
return 0;
}
效果如下图3.1所示:
3.1.png
网友评论