基于Qt的OpenGL编程(3.x以上GLSL可编程管线版)---(十九)混合

2023-11-18

Vries的教程是我看过的最好的可编程管线OpenGL教程,没有之一其原地址如下,https://learnopengl-cn.github.io/04%20Advanced%20OpenGL/03%20Blending/ 关于混合的详细知识了解请看原教程,本篇旨在对Vires基于visual studio平台的编程思想与c++代码做纯Qt平台的移植,代码移植顺序基本按照原教程顺序,并附加一些学习心得,重在记录自身学习之用


Tip:这节的内容非常有趣,我强烈建议大家阅读完原教程的知识细节。

 

程序源代码链接:https://pan.baidu.com/s/1iO3tP1CCtfAIM0g6DdbI0g 提取码:wxjh

编译环境:Qt5.9.4

编译器:Desktop Qt5.9.4 MSVC2017 64bit

IDE:QtCreator

 

一,混合

    单纯的颜色渲染已经不能满足我们了,我们现在要追求带有透明度的颜色。也就是OpenGL颜色分量中的alpha分量,当alpha = 1.0f时,表示该颜色不透明;alpha = 0.4f时,片段的颜色有40%来自物体自身的颜色,60%来自物体背后的颜色。如图1所示

图1 透明颜色

二,丢弃片段

混合分为两种,一种是纹理中,透明部分与不透明部分泾渭分明,我们只需要不透明的部分,如图2的草。

Tip: 不带csdn水印的照片在源代码里。

图2 草

 

Vries的代码里,因为引入的是独立image库,所以需要修改纹理载入过程的两部分参数代码,如下所述:

......
//表明以RGBA的四分量颜色向量载入纹理

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);

......
//修改纹理的扩张方式,由重复改为边扩展
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

......

/*
在Qt里,我们需要修改Vries三行代码对应的三个参数,如何使用见下文
*/
  QOpenGLTexture::RGBAFormat;
  QOpenGLTexture::ClampToBorder; //在纹理的边界部分,按照Vries思想进行边扩展。
  QOpenGLTexture::ClampToBorder;

将代码移植到Qt时,因为Qt自带的QOpenGLTexture类参数的可控性,所以为方便控制,我们自设一个Texture2D类,方便对纹理进行管理。

 Texture2D.h 将纹理常用的参数,如颜色格式,扩展方式独立出来,源文件里有解释如何在Qt里确立与Vries所用的纹理函数等价的函数。

#include <QOpenGLTexture>

class Texture2D
{
  friend class ResourceManager;
public:
  Texture2D();
  ~Texture2D();
  void generate(const QString& file);
  void bind() const;

  QOpenGLTexture::TextureFormat internal_format;//Format of texture object
  QOpenGLTexture::WrapMode wrap_s;
  QOpenGLTexture::WrapMode wrap_t;
  QOpenGLTexture::Filter filter_min;
  QOpenGLTexture::Filter filter_max;
private:
  QOpenGLTexture *texture;
};

Texture2D.cpp源文件

#include "texture2d.h"

Texture2D::Texture2D():texture(NULL), internal_format(QOpenGLTexture::RGBFormat),
    wrap_s(QOpenGLTexture::Repeat), wrap_t(QOpenGLTexture::Repeat), filter_min(QOpenGLTexture::Linear),
    filter_max(QOpenGLTexture::Linear)
{

}

Texture2D::~Texture2D()
{
    //一样的析构问题,不会解决 擦,在ResourceManager里 
//  if(texture)
//    delete texture;

}

void Texture2D::generate(const QString &file)
{
  texture = new QOpenGLTexture(QOpenGLTexture::Target2D); //直接生成绑定一个2d纹理, 并生成多级纹理MipMaps
  texture->setFormat(internal_format);
  texture->setData(QImage(file).mirrored(), QOpenGLTexture::GenerateMipMaps);

  texture->setWrapMode(QOpenGLTexture::DirectionS, wrap_s);// 等于glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  texture->setWrapMode(QOpenGLTexture::DirectionT, wrap_t);//    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

  texture->setMinificationFilter(filter_min);   //等价于glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  texture->setMagnificationFilter(filter_max);  //     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}

void Texture2D::bind() const
{
  texture->bind();
}

有了这个Texture2D类,我们就可以很方便的在ResourceManager类里对纹理进行管理,在函数列表里设计alpha参数决定要在如的纹理是否为透明纹理。(ResouceManager类的相关信息在博客编程(八)里)

Texture2D ResourceManager::loadTexture(const QString&  name, const QString& file, GLboolean alpha){
  Texture2D texture;

  if(alpha){
    texture.internal_format = QOpenGLTexture::RGBAFormat;
    texture.wrap_s = QOpenGLTexture::ClampToBorder; //在纹理的边界部分,按照Vries思想进行边扩展。
    texture.wrap_t = QOpenGLTexture::ClampToBorder;
  }

  texture.generate(file);
  map_Textures[name] = texture;
  return texture;
}

等做完这一切工作,在编程(十八)深度测试的场景上,将草的纹理映射到一个plane平面上(littlething.h里有这个类),并像Vries所做的那样,放在这五个位置。  

  grassOffset.push_back(QVector3D(-1.5f,  0.0f, -0.48f));
  grassOffset.push_back(QVector3D( 1.5f,  0.0f,  0.51f));
  grassOffset.push_back(QVector3D( 0.0f,  0.0f,  0.7f));
  grassOffset.push_back(QVector3D(-0.3f,  0.0f, -2.3f));
  grassOffset.push_back(QVector3D( 0.5f,  0.0f, -0.6f));

直接用plane的着色器对草进行渲染。

plane.vert


#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTex;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

out vec2 TexCoords;

void main(){
  gl_Position = projection * view * model * vec4(aPos, 1.0f);
  TexCoords = aTex;
}

plane.frag


#version 330 core
out vec4 FragColor;
in vec2 TexCoords;

uniform sampler2D ambientMap;

void main(){
  FragColor = texture2D(ambientMap, TexCoords);
}

因为plane的着色器不识别透明纹理,所以效果如图

图3 plane着色器渲染草

针对这种情况,在着色器里,GLSL给了一个特别棒的指令,“discard”,允许我们在着色器里根据某种条件丢弃片段。因为png格式的图片里每个像素颜色已经附带了透明信息,所以在片段着色器里,我们设计判断条件,当alpha值小于0.1时,丢弃片段。

grass.frag


#version 330 core
out vec4 FragColor;
in vec2 TexCoords;

uniform sampler2D ambientMap;

void main(){
  vec4 texColor = texture2D(ambientMap, TexCoords);
  if(texColor.a < 0.1f)
    discard;

  FragColor = texColor;
}

再看程序效果:

图4 discard丢弃片段

这次正确识别出了透明纹理。

三,渲染半透明纹理

    这是混合的第二种形式,要求整张纹理都呈半透明状态,还是Vries的例子,将小草纹理替换成窗户的纹理。

图5 window纹理

这时我们不能再丢弃片段了。而是开启OpenGL的混合模式。指定透明纹理自带的alpha为源影响因子,1-alpha为目标影响因子。

    ............

    core->glEnable(GL_BLEND);
    core->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    ............

待GL_BLEND测试开启后,使用普通的plane着色器进行渲染,不再discard。很明显,我们有了半透明的纹理效果,同理开启混合测试后,不discard片段,直接渲染草的效果,也是透明的。但是这里有个缺陷。混合测试与深度测试冲突,深度测试不管纹理是否透明,都会按照纹理绘制的顺序,将纹理写进深度缓冲,所以会出现图6蓝框出现的问题。

图6 开启Blend测试后,使用plane着色器渲染窗户

为解决这个问题,我们需要在渲染前对这些窗户进行排序,按照各窗户据视角位置由远到近的顺序,依次绘制窗户。Vries使用了std::map容器,可以自动的按“key”值大小进行排序,同理,我们就使用QMap进行排序。但有一个问题,QMap是按照“key”的升序排序的,且没有反转迭代器,而我们需要的是窗户据视角位置距离的降序,即由大到小进行排序。所以我们迫不得已,为方便起见,将QMap排好的距离倒置写进另一个容器存储。

  /*************** 计算 排序后的窗户偏移量 *****************/
  QMap<float, QVector3D> sorted;
  for(int i = 0; i < grassOffset.size(); ++i){
    float dist = (camera->position - grassOffset[i]).length();
    sorted[dist] = grassOffset[i];
  }

  sortedOffset.clear();       //QVector<QVector3D> sortedOffset
  for(QMap<float, QVector3D>::iterator iter = sorted.begin(); iter != sorted.end(); ++iter)
    sortedOffset.push_front(iter.value());

当然,我们可以想到,这个窗户的绘制顺序是随着视角位置的改变而改变的,所以需要不断的进行计算,故我们将这个窗户排序函数放在不断进行计算的函数中,我就放在updateGL()函数中,这个函数每10ms调用一次。最终效果如下图。

图7 经过排序的窗户

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

基于Qt的OpenGL编程(3.x以上GLSL可编程管线版)---(十九)混合 的相关文章

  • Selenium控制已打开的chrome、IE浏览器

    0 为什么要接管打开的浏览器 1 重复重新登录 过程麻烦 2 拖慢爬虫的运行速度 3 容易让网站检测到账号异常 如何解决重复登录的问题 1 使用登录过的cookie 下次运行时设置保存 2 接管打开的浏览器 也是我们接下来重点讲的 1 控制
  • 9.Markdown 高级技巧(内嵌HTML+公式+对齐方式)

    支持的 HTML 元素 CSDN支持kbd标签 有道云目前不支持 使用
  • Java基础面试题

    1 面向对象的特征有哪些方面 1 抽象 抽象就是忽略一个主题中与当前目标无关的那些方面 以便更充分地注意与当前目标有关的方面 抽象并不打算了解全部问题 而只是选择其中的一部分 暂时不用部分细节 抽象包括两个方面 一是过程抽象 二是数据抽象
  • 十分钟让你明白Objective-C的语法(和Java、C++的对比)

    很多想开发iOS 或者正在开发iOS的程序员以前都做过Java或者C 当第一次看到Objective C的代码时都会头疼 Objective C的代码在语法上和Java C 有着很大的区别 有的同学会感觉像是看天书一样 不过 语言都是相通的
  • smp和mpp计算机

    SMP 是Symmetric Multi Processing的简称 意为对称多处理系统 内有许多紧耦合多处理器 这种系统的最 大特点就是共享所有资源 MPP 另外与之相对立的标准是MPP Massively Parallel Proces
  • Linux驱动

    Linux驱动入门系列 Linux驱动入门 一 字符设备驱动基础 Linux驱动入门 二 操作硬件 Linux驱动入门 三 Led驱动 Linux驱动入门 四 非阻塞方式实现按键驱动 Linux驱动入门 五 阻塞方式实现按键驱动 Linux
  • ​7.1 项目1 学生通讯录管理:文本文件增删改查(C++版本)(自顶向下设计+断点调试) (A)​

    C 自学精简教程 目录 必读 作业目标 这个作业中 你需要综合运用之前文章中的知识 来解决一个相对完整的应用程序 作业描述 1 在这个作业中你需要在文本文件中存储学生通讯录的信息 并在程序启动的时候加载这些数据到内存中 2 在程序运行过程中

随机推荐

  • 用Python绘制六种可视化图表,简直太好用了

    前言 本文的文字及图片来源于网络 仅供学习 交流使用 不具有任何商业用途 如有问题请及时联系我们以作处理 PS 如有需要Python学习资料的小伙伴可以加点击下方链接自行获取 python免费学习资料以及群交流解答点击即可加入 可视化图表
  • 邻接矩阵实现的带权有向图(C++)

    邻接矩阵实现的带权有向图 C 相关概念 定义和声明 实现 1 距离无穷大的定义 2 构造函数 3 深度优先遍历 4 广度优先遍历 6 将邻接矩阵转换为邻接表 7 重载 lt lt 运算符 打印输出 测试 测试代码 测试结果 源代码 相关概念
  • Callable 接口实现java 的多线程

    java 中创建多线程最常见的是继承Thread 的子类重写run 方法 还有就是实现Runnable 接口 我们最好使用实现了Runnable 接口的方法原因有两点 因为java 的单继承的特点 所以说使用第一种方法不能继承其他父类了 采
  • Lunix历史及如何学习

    1 Lunix是什么 1 1 Lunix是操作系统还是应用程序 Lunix是一套操作系统 它提供了一个完整的操作系统当中最底层的硬件控制与资源管理的完整架构 这个架构是沿袭Unix 良好的传统来的 所以相当的稳定而功能强大 Lunix具有核
  • SCI论文润色插件Product Content Checker扩展程序

    下载地址 https www gugeapps net webstore detail product content checker ilmaafbmfcklldgoehebccigadbkbdpc download 打开方式 直接将下载
  • simhash算法原理及实现

    一篇不错的介绍simhash的文章 如下 http blog csdn net chenguolinblog article details 50830948
  • 多个ajax请求时控制执行顺序或者等待执行完成后的操作

    当确保执行顺序时 一 请求加async false 这样所有的ajax就会同步执行 请求顺序就是代码顺序 代码部分 when ajax async false url url1 ajax async false url url2 done
  • ai绘画小程序基于novelai的tag列表源码展示(独家)

    视频 哔哩哔哩 看视频 介绍 一个tag列表展示
  • 代码行统计工具_cloc

    下载并运行 在Github下载稳定发布版本 Releases AlDanial cloc GitHub 直接下载exe文件 放在需要统计代码的文件夹下 用cmd或是powershell运行 cloc 1 96 exe 注意 之前有个空格 c
  • hive 错误 InvalidObjectException(message:Role admin already exists.)

    InvalidObjectException message Role admin already exists at org apache hadoop hive metastore ObjectStore addRole ObjectS
  • python去掉列表中的单引号_从Python中的列表中删除单引号

    我有一个输入字符串 result testing 0 8841 642000 0 80 014521 60 940653 4522126666 1500854400 1500842014000 name 80 014521 60 99653
  • C语言实现顺序表

    线性表是数据结构中的逻辑结构 线性表采用顺序存储的方式存储就称之为顺序表 数组是顺序表在实际编程中的具体实现方式之一 本篇主要介绍顺序表 顺序表的创建 添加元素 删除元素 遍历输出等操作 1 创建顺序表 1 1定义顺序表结构体 结构体包含三
  • Fisco Bcos区块链一(搭建单群组FISCO BCOS联盟链)

    文章目录 区块链开荒 技术文档 https fisco bcos documentation readthedocs io zh CN latest index html 一 搭建第一个区块链网络 1 搭建单群组FISCO BCOS联盟链
  • java基础语法

    java基础语法 1 Java概述 1 1 Java语言发展史 了解 1 2 Java语言跨平台原理 理解 1 3 JRE和JDK 记忆 1 4 JDK的下载和安装 应用 1 4 1 下载 1 4 2 安装 1 4 3 JDK的安装目录介绍
  • python进阶之多线程对同一个全局变量的处理

    通常情况下 from threading import Thread global num 0 def func1 global global num for i in range 1000000 global num 1 print fu
  • sklearn决策树怎么使用ccp_alpha进行剪枝

    本站原创文章 转载请说明来自 老饼讲解 机器学习 ml bbbdata com 目录 一 CCP后剪枝是什么 二 如何通过ccp alpha进行后剪枝 1 查看CCP路径 2 根据CCP路径剪树 三 完整CCP剪枝应用实操DEMO 四 CC
  • c语言初学者如何编写一个相加求和程序

    欢迎来到南方有乔木的博客 博主主页 点击点击 戳一戳 博主QQ 1636758318 博主简介 一名在校大学生 正在努力学习Java语言编程 穷且意坚 不坠青云之志 希望能在编程的世界里找到属于自己的光 跪谢帅气or美丽的朋友们能够帮我点赞
  • 【翻译】Dagre-D3 文档整理和翻译

    地址 github Dagre D3 目录 文章目录 dagre d3 设计优先级 安装 npm Bower Browser Scripts 源代码构建 如何使用Darge 聚焦渲染 例子 配置布局 生成的图像 第三部分例子 推荐阅读 da
  • Android——Intent用法

    Intent 意图 一般可以被用于启动活动 启动服务以及发送广播等场景 现在先说一下启动活动这部分 Intent分为显式Intent和隐式Intent 一 显式Intent的使用 Intent有多个构造函数的重载 常用的一个有Intent
  • 基于Qt的OpenGL编程(3.x以上GLSL可编程管线版)---(十九)混合

    Vries的教程是我看过的最好的可编程管线OpenGL教程 没有之一 其原地址如下 https learnopengl cn github io 04 20Advanced 20OpenGL 03 20Blending 关于混合的详细知识了