当前位置: 首页 > news >正文

LearnOpenGL——OIT

教程地址:简介 - LearnOpenGL CN


简介

  • 原文链接:LearnOpenGL - Introduction

前言

在混合(Blending)章节中,我们介绍了颜色混合的主题。混合是在3D场景中实现透明表面的方法。简而言之,透明度涉及到在计算机图形学中绘制半透明或完全透明的对象(如玻璃)的问题。这个概念在混合章节中已经得到了适当的解释,因此如果你对这个话题不熟悉,最好先阅读它。

在这篇文章中,我们将进一步探讨这个话题,因为在 3D 环境中实现这种效果涉及许多技术。

首先,我们将讨论图形库/硬件的限制以及它们带来的困难,以及为什么透明度是一个如此棘手的问题。之后,我们将介绍并简要回顾过去二十年中与当前硬件相关的一些较为知名的透明技术,这些技术已被发明并使用。最后,我们将重点解释并实现其中一种技术,这将成为本文下一部分的内容。

注意,本文的目标是介绍比混合章节中使用的透明度技术性能显著更好的技术。否则,就没有真正令人信服的理由来扩展这个话题。

图形库/硬件限制

本文之所以存在,你之所以在阅读它,是因为当前技术无法直接绘制透明表面。许多人希望绘制透明表面就像在他们的图形API中打开一个开关那么简单,但那只是一个童话故事。至于这是图形库还是显卡的限制,这一点尚有争议。

正如混合章节中所解释的,这个问题的根源在于深度测试和颜色混合的结合。在片段着色器阶段,没有像深度缓冲区那样的缓冲区来处理透明像素,以告诉图形库哪些像素是完全可见或半可见的。其中一个原因可能是,没有一种有效的方法能将透明像素的信息存储在这样一个缓冲区中,而这个缓冲区需要为屏幕上的每个坐标保存无限数量的像素。由于每个透明像素都可能暴露其下方(深度靠后)的像素,因此需要一种方法来存储所有屏幕坐标的所有像素的不同层次。

这种限制让我们不得不思考如何克服这个问题。由于图形库和硬件都无法提供帮助,这一切都必须由开发者使用手头的工具来完成。我们将探讨两种在这个主题中突出的方法:一种是 有序透明 OT(Ordered Transparency),另一种是 无序透明 OIT(Order-Independent Transparency)

有序透明

克服这一问题最便捷的解决方案是:根据相机位置,将透明物体按从远到近或从近到远的顺序排序后再绘制。通过这种方式,深度测试将不会影响以下像素的最终渲染结果: 这些像素可能因绘制顺序靠后/靠前,但实际上位于更远/更近的物体的上方或下方。尽管这种方法对CPU的开销很大,但在许多我们可能玩过的早期游戏中都使用了这种方法。

例如,下面的示例图像展示了混合渲染顺序的重要性。图像的上半部分显示了无序 alpha 混合的错误结果,而下半部分正确地对几何体进行了排序。可见,如果没有正确的深度排序,骨骼结构的可见度会显著降低。此图像来自 ATI Mecha Demo:

image.png

到目前为止,我们已经明白,为了克服当前技术在绘制透明对象时的限制,我们需要对透明对象进行排序,以便它们在屏幕上正确显示。排序会降低应用程序的性能,由于大多数3D应用程序是实时运行的,每帧执行排序性能影响会更加明显。

因此,我们将探索无序透明——OIT技术的世界,寻找一种更适合我们目的且与我们的渲染管线兼容的技术,这样我们就不需要在绘制之前对对象进行排序。

无序透明

无序透明(Order-Independent Transparency,简称OIT)是一种不需要我们按照顺序绘制透明对象的技术。乍一看,这将为我们节省用于排序对象的CPU周期,但与此同时,OIT技术也有其优缺点。

OIT技术的目标是消除在绘制时对透明对象进行排序的需求。根据具体技术的不同,其中一些技术必须在后期对片段进行排序以获得准确的结果,但这是在所有绘制调用(draw call)完成后进行的;而另一些技术则不需要排序,但结果是近似的。

历史

为了克服渲染透明表面的限制而发明的一些更高级的技术,明确使用了一种缓冲区(例如链表或如 [x][y][z] 这样的3D数组),该缓冲区可以保存多层像素信息,并能在GPU上对像素进行排序,通常利用GPU的并行处理能力,而非CPU。

A-Buffer 是一种诞生于1984年的计算机图形学技术。它通过软件光栅化器(如专为抗锯齿设计的 REYES 架构)为每个像素存储片段数据列表(包括微多边形信息),最初是为抗锯齿设计的,但也可用于透明度。

与此同时,一些硬件能够通过执行硬件计算来辅助完成这项任务,这是开发者最方便的方式,可以开箱即用地实现透明度。

[SEGA Dreamcast](SEGA Dreamcast 是少数几个具有自动每个像素透明度排序功能的游戏机之一,该功能由其硬件实现。) 是少数几个具有自动对每个像素透明度排序功能的游戏机之一,该功能由其硬件实现。

通常,OIT 技术分为 精确(Exact)近似(Approximate) 两大类。精确方法会产生具有准确透明度的更好的图像,适用于所有场景,而近似方法虽然图像看起来不错,但在复杂场景中缺乏准确性。

精确 OIT

这些技术能够精确计算最终颜色,为此必须对所有片段进行排序。对于深度复杂度高的场景,排序会成为瓶颈。

排序阶段的一个问题是局部内存占用有限,在这种情况下,与GPU的吞吐量和操作延迟隐藏相关的单指令多线程属性是一个关键因素。尽管如此,反向内存分配(Backwards Memory Allocation,BMA) 可以按深度复杂度对像素进行分组并分批排序,从而在高深度复杂度场景下提升低深度复杂度像素的占用率及性能。据报道,这可使 OIT 整体性能最多可提升3倍。

排序阶段在着色器中需要相对大量的临时内存,通常会保守地分配到最大值,这会影响内存占用率和性能。

排序通常在局部数组中进行,但通过利用 GPU 的内存层次结构并在寄存器中进行排序,可以进一步提高性能,类似于外部归并排序,尤其是在与 BMA 结合使用时。

近似 OIT

近似 OIT 技术放宽了精确渲染的约束,以获得更快的计算结果。通过不必存储所有片段或仅部分排序几何体,可以获得更高的性能。一些技术还对片段数据进行压缩或减少。这些技术包括:

  • 随机透明(Stochastic Transparency):以更高的分辨率绘制完全不透明的图像,但丢弃一些片段。然后通过降采样来实现透明效果。
  • 自适应透明(Adaptive Transparency):一种两阶段技术,第一阶段构建一个可见性函数,该函数在运行时进行压缩(这种压缩避免了对片段进行完全排序),第二阶段使用这些数据合成无序片段。Intel 的像素同步技术避免了存储所有片段的需要,从而消除了许多其他 OIT 技术中无限内存需求的问题。

技术

以下是工业界中常用的一些 OIT 技术:

  • 深度剥离(Depth Peeling):2001 年提出,描述了一种硬件加速的OIT技术,通过深度缓冲区在每次渲染过程中剥离一层像素。由于早期图形硬件的限制,需多次渲染场景几何体。
  • 双重深度剥离(Dual Depth Peeling):2008年提出,优化了深度剥离的性能,但仍存在多次渲染的限制。
  • 加权混合(Weighted, Blended):于2013年发布,利用加权函数和两个缓冲区——一个用于像素颜色,一个用于像素可见性阈值——来进行最终合成。能在复杂场景中生成质量不错的近似图像。

实现

在 3D 应用程序中实现 OIT 的常用方法是进行多遍渲染。实现 OIT 技术至少需要三遍渲染,因此,为了做到这一点,你需要对 OpenGL 中的帧缓冲区有较好的理解。一旦你熟悉了帧缓冲区,一切问题就转化为了你试图实现的技术的复杂性是什么。

简单来说,涉及的三遍渲染如下:

  • 第一遍:绘制所有不透明对象,也就是任何不允许光线穿过其几何体的对象。
  • 第二遍:绘制所有半透明对象。需要丢弃 alpha 值的对象可以在第一遍中渲染。
  • 第三遍:将前两遍生成的图像进行合成,并将该图像绘制到你的后缓冲区(backbuffer)上。

在所有不同渲染管线中实现 OIT 技术,这个例程几乎是相同的。

在本文的下一部分,我们将实现 加权混合 OIT(Weighted, Blended OIT),这是过去十年中视频游戏行业中使用的最简单且具有较高性能的 OIT 技术之一。

进阶阅读

  • SEGA Dreamcast 硬件:Dreamcast 是少数几个实现了硬件级顺序无关透明度的游戏机之一。
  • OIT:一系列具有出色性能并能在近似方法下产生良好结果的技巧。
  • 加权混合 OIT:实现方面最简单的 OIT 技术之一,同时能为复杂场景生成接受度较高的图像。

Article by: Mahan Heshmati Moghaddam
Contact: e-mail(原作者的哈,不是博主)


加权混合

  • 原文链接:LearnOpenGL - Weighted Blended

前言

加权混合(Weighted, Blended) 是一种近似无序依赖的透明度技术,于 2013 年由 NVIDIA 的 Morgan McGuire 和 Louis Bavoil 在计算机图形技术杂志上发表,旨在解决当时游戏平台上广泛存在的透明度问题。

他们的方法通过改变合成算子使其与顺序无关,从而避免存储和排序图元或片段的成本,这样就可以实现一种纯粹的流式处理方式。

大多数游戏都有针对特定场景的临时方法来绕过透明表面渲染的限制。这些方法包括有限排序、仅加法混合以及硬编码的渲染和合成顺序。这些方法大多在游戏进行到某个阶段时会失效并产生视觉伪像。一种不可行的替代方案是深度剥离(Depth Peeling),它可以生成高质量的图像,但在理论和实践上对于多层场景来说都太慢。

透明渲染有许多渐进(asymptotically)快速的解决方案,例如使用可编程混合的有限 A-Buffer 近似(例如 Marco Salvi 的工作),随机透明度(Eric Enderton 等人的工作)以及光线跟踪。这些方法中有一个或多个可能会在未来占据主导地位,但在五六年前的游戏平台上,包括PC DX11/GL4 GPU、支持OpenGL ES 3.0 GPU的移动设备以及上一代主机如PlayStation 4,这些方法都不实用。

在数学分析中,渐进分析(Asymptotic Analysis),也称为渐进法(asymptotics),是一种描述极限行为的方法。

下图是使用该技术渲染的汽车引擎的透明CAD视图。

image.png

理论

该技术通过带有颜色的表面渲染非折射、单色透射效果 ,无需排序或依赖新硬件特性。事实上,只要支持每通道超过8位混合渲染目标的GPU,就可以用一个简单的着色器实现它。

在支持多渲染目标(MRT)和浮点纹理 的GPU上,其性能优于传统排序透明技术,且能避免粒子系统的排序伪像和闪烁问题。与4层深的RGBA8 K-buffer相比,其带宽消耗更低,并允许将低分辨率粒子与全分辨率表面(如玻璃)混合渲染。

在混合分辨率场景中,峰值内存消耗仍由高分辨率渲染目标决定,但带宽成本会根据低分辨率表面的占比而降低。

加权混合方法的基本思想是精确计算透明表面对背景的覆盖率,但仅近似计算透明表面本身向相机散射的光线。该算法对透明表层间的遮挡因子施加了一种启发式规则——遮挡因子随物体与摄像机的距离增加而增大

启发式技术(Heuristic)指一种解决问题或自我探索的实用方法,虽不保证最优、完美或完全合理,但足以实现短期目标或近似解。在本例中,启发式规则体现为加权函数 。

在所有透明表面渲染完成后,会执行一次全屏归一化和合成阶段,以减少因启发式规则对真实遮挡关系近似不足导致的误差。

下图是使用该技术渲染的玻璃棋子。注意,棋子本身未折射任何光线。

image.png

若需深入理解加权函数的技术细节,请参考原始论文第 5-7 页(论文链接见本文末)。加权混合 OIT 技术自提出以来,已通过多种方法改进并实现。

局限性

该技术的主要局限性在于,加权启发式规则必须针对预期的深度范围和透明表面的不透明度进行调整。

该技术已在 OpenGL 的 G3D 创新引擎和 DirectX 的虚幻引擎中实现,用于实时演示及论文中的效果呈现。Dan Bagnell 和 Patrick Cozzi 在他们的开源Cesium 引擎中用 WebGL 实现了它(详见他们的相关技术博客)。

通过上述实践,研究者总结出一组效果良好的加权函数(详见期刊论文)。论文还讨论了如何识别并修复因权重函数调整不当而导致的瑕疵。

此外,目前尚未找到在延迟渲染器中合理实现该技术的方案。由于延迟渲染中像素会互相覆盖,从而导致前几层信息丢失,因此无法正确累积(accumulate)用于光照阶段的颜色值。

一种可行的解决方案是:沿用前向渲染的透明渲染阶段逻辑,将其直接整合到延迟渲染管线中。这相当于从前向渲染中“借用”透明处理流程,并嵌入到延迟渲染架构里。

实现

该技术的实现非常直接,且着色器的修改非常简单。如果熟悉OpenGL中帧缓冲区(Framebuffer)的工作原理,你几乎已经完成了一半的工作。

唯一需注意的是 :必须使用OpenGL 4.0及以上版本才能支持多渲染目标混合(例如使用 glBlendFunci 函数)。在论文中,作者还讨论了针对不支持多目标渲染或混合的图形库的替代实现方案。

初始化 GLFW 时别忘了更改你的 OpenGL 版本,同样也要更改着色器中的 GLSL 版本。

概述

在透明表面渲染阶段,按常规方式对表面进行着色,但需输出到两个渲染目标。第一个渲染目标(accum ——累积值)需至少为 RGBA16F 精度,第二个渲染目标(revealage ——可见性)需至少为 R8 精度。初始化时,将第一个渲染目标清空为 vec4(0),第二个渲染目标清空为 1(可通过片段着色器或 glClearBuffer + glClear 实现)。

随后,以任意顺序渲染表面到这两个渲染目标,并在片段着色器末尾添加以下代码,同时使用指定的混合模式:

// 第一个渲染目标:用于累积预乘颜色值
layout (location = 0) out vec4 accum;// 第二个渲染目标:用于存储像素的可见性(revealage)值
layout (location = 1) out float reveal;...// 输出线性颜色(非伽马编码!),且为非预乘格式
vec4 color = ... // 常规着色代码// 插入你选择的加权函数。
// 基于颜色的因子避免薄雾状云边缘颜色污染,
// 基于深度的因子优先处理更近的表面
float weight =max(min(1.0, max(max(color.r, color.g), color.b) * color.a), color.a) *clamp(0.03 / (1e-5 + pow(z / 200, 4.0)), 1e-2, 3e3);// 混合模式:GL_ONE, GL_ONE
// 切换为预乘Alpha并应用权重
accum = vec4(color.rgb * color.a, color.a) * weight;// 混合模式:GL_ZERO, GL_ONE_MINUS_SRC_ALPHA
reveal = color.a;

在所有表面渲染完成后,使用全屏渲染将结果进行合成:

// 绑定第一个渲染目标到此纹理单元
layout (binding = 0) uniform sampler2D rt0;// 绑定第二个渲染目标到此纹理单元
layout (binding = 1) uniform sampler2D rt1;// 着色器输出
out vec4 color;// 采样像素信息
vec4 accum = texelFetch(rt0, int2(gl_FragCoord.xy), 0);
float reveal = texelFetch(rt1, int2(gl_FragCoord.xy), 0).r;// 混合模式:GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA
color = vec4(accum.rgb / max(accum.a, 1e-5), reveal);

使用下表作为渲染目标的参考:

Render TargetFormatClearSrc BlendDst BlendWrite (“Src”)
accumRGBA16F(0,0,0,0)ONEONE(r*a, g*a, b*a, a) * w
revealageR8(1,0,0,0)ZEROONE_MINUS_SRC_COLORa

总共需要三个渲染过程来完成下面的最终结果:

image.png

详情

实现之初,我们需要为不透明和透明表面设置一个四边形。红色四边形将是不透明的,而绿色和蓝色四边形将是透明的。由于我们也将使用相同的四边形作为屏幕四边形,这里我们定义了 UV 值,以便进行纹理映射。

float quadVertices[] = {// 位置              // UV坐标-1.0f, -1.0f, 0.0f,  0.0f, 0.0f,1.0f, -1.0f, 0.0f,  1.0f, 0.0f,1.0f,  1.0f, 0.0f,  1.0f, 1.0f,1.0f,  1.0f, 0.0f,  1.0f, 1.0f,-1.0f,  1.0f, 0.0f,  0.0f, 1.0f,-1.0f, -1.0f, 0.0f,  0.0f, 0.0f
};// 四边形VAO配置
unsigned int quadVAO, quadVBO;
glGenVertexArrays(1, &quadVAO);
glGenBuffers(1, &quadVBO);
glBindVertexArray(quadVAO);
glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), quadVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
glBindVertexArray(0);

接下来,我们将为不透明和透明通道创建两个帧缓冲区。

  1. 不透明通道 FBO :包含颜色缓冲(存储颜色)和深度缓冲。
  2. 透明通道 FBO :包含两个颜色缓冲(分别存储颜色累积值和像素可见性阈值)。

透明 FBO 需共享不透明 FBO 的深度纹理,以便透明物体渲染时进行深度测试。

// 创建帧缓冲区
unsigned int opaqueFBO, transparentFBO;
glGenFramebuffers(1, &opaqueFBO);
glGenFramebuffers(1, &transparentFBO);// 配置不透明FBO的附件
unsigned int opaqueTexture;
glGenTextures(1, &opaqueTexture);
glBindTexture(GL_TEXTURE_2D, opaqueTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_HALF_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);unsigned int depthTexture;
glGenTextures(1, &depthTexture);
glBindTexture(GL_TEXTURE_2D, depthTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, SCR_WIDTH, SCR_HEIGHT,0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);// 配置透明FBO的附件
unsigned int accumTexture;
glGenTextures(1, &accumTexture);
glBindTexture(GL_TEXTURE_2D, accumTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_HALF_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);unsigned int revealTexture;
glGenTextures(1, &revealTexture);
glBindTexture(GL_TEXTURE_2D, revealTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, SCR_WIDTH, SCR_HEIGHT, 0, GL_RED, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);// 不要忘记明确告诉OpenGL你的透明帧缓冲区有两个绘制缓冲区
const GLenum transparentDrawBuffers[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 };
glDrawBuffers(2, transparentDrawBuffers);

为了便于理解,在本文中我们创建了两个独立的 FBO,实际开发中可优化为:

  • 省略不透明 FBO,直接使用后缓冲区(backbuffer)。
  • 创建单一 FBO 并附加4个纹理(不透明颜色、累积颜色、可见性、深度),通过切换渲染目标实现多通道渲染。

在渲染之前,为你的四边形设置一些模型矩阵。你可以随意设置 Z 轴,因为这是一个无序技术,物体离相机较近或较远都不会造成问题。

glm::mat4 redModelMat = calculate_model_matrix(glm::vec3(0.0f, 0.0f, 0.0f)); // 红色四边形(不透明)
glm::mat4 greenModelMat = calculate_model_matrix(glm::vec3(0.0f, 0.0f, 1.0f)); // 绿色四边形(透明)
glm::mat4 blueModelMat = calculate_model_matrix(glm::vec3(0.0f, 0.0f, 2.0f)); // 蓝色四边形(透明)

此时,我们需要进行不透明物体的渲染阶段。配置渲染状态并绑定不透明帧缓冲区:

// 配置渲染状态
glEnable(GL_DEPTH_TEST);      // 启用深度测试
glDepthFunc(GL_LESS);         // 深度函数:通过更近的片段
glDepthMask(GL_TRUE);         // 允许写入深度缓冲区
glDisable(GL_BLEND);          // 禁用混合
glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // 清屏颜色// 绑定不透明FBO并清空缓冲区
glBindFramebuffer(GL_FRAMEBUFFER, opaqueFBO);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

由于渲染管线会在后续步骤中更改这些状态,我们必须每帧都重置深度函数和深度掩码。

现在,使用不透明着色器绘制不透明对象。你可以在此阶段和下一阶段都绘制带有 alpha 裁剪的对象。不透明着色器是一个简单的着色器,仅变换顶点并使用提供的颜色绘制网格:

// 使用不透明着色器
solidShader.use();// 绘制红色四边形
solidShader.setMat4("mvp", vp * redModelMat);
solidShader.setVec3("color", glm::vec3(1.0f, 0.0f, 0.0f));
glBindVertexArray(quadVAO);
glDrawArrays(GL_TRIANGLES, 0, 6);// 绘制6个顶点(2个三角形)

到目前为止一切顺利。对于透明物体渲染阶段,与不透明阶段类似,配置渲染状态以混合到以下渲染目标,然后绑定透明帧缓冲区并将其两个颜色缓冲区清为 vec4(0.0f)vec4(1.0)

// 配置渲染状态
glDepthMask(GL_FALSE);        // 禁止写入深度缓冲区(避免覆盖不透明阶段的深度值)
glEnable(GL_BLEND);           // 启用混合
glBlendFunci(0, GL_ONE, GL_ONE); // accum(累积)缓冲区混合模式:加法
glBlendFunci(1, GL_ZERO, GL_ONE_MINUS_SRC_COLOR); // revealage(可见性)缓冲区混合模式
glBlendEquation(GL_FUNC_ADD); // 混合方程:加法// 绑定透明FBO并清空两个颜色缓冲区
glBindFramebuffer(GL_FRAMEBUFFER, transparentFBO);
glClearBufferfv(GL_COLOR, 0, &zeroFillerVec[0]); // accum初始化为vec4(0)
glClearBufferfv(GL_COLOR, 1, &oneFillerVec[0]);  // reveal初始化为1.0

然后,使用你喜欢的 alpha 值绘制透明表面:

// 使用透明着色器
transparentShader.use();// 绘制绿色四边形
transparentShader.setMat4("mvp", vp * greenModelMat);
transparentShader.setVec4("color", glm::vec4(0.0f, 1.0f, 0.0f, 0.5f));
glBindVertexArray(quadVAO);
glDrawArrays(GL_TRIANGLES, 0, 6);// 绘制蓝色四边形
transparentShader.setMat4("mvp", vp * blueModelMat);
transparentShader.setVec4("color", glm::vec4(0.0f, 0.0f, 1.0f, 0.5f));
glBindVertexArray(quadVAO);
glDrawArrays(GL_TRIANGLES, 0, 6);

透明着色器完成了大约一半的工作。它的主要工作为收集像素信息以供合成阶段使用:

layout (location = 0) out vec4 accum;  // 输出到累积缓冲区
layout (location = 1) out float reveal; // 输出到可见性缓冲区uniform vec4 color; // 传入的材质颜色void main() {// 加权函数:结合透明度和深度float weight = clamp(pow(min(1.0, color.a * 10.0) + 0.01, 3.0) * 1e8 * pow(1.0 - gl_FragCoord.z * 0.9, 3.0), 1e-2, 3e3);// 存储像素颜色累积accum = vec4(color.rgb * color.a, color.a) * weight;// 存储像素可见性阈值reveal = color.a;
}

注意,我们直接使用传递给着色器的颜色作为最终片段颜色。通常,如果你在光照着色器中,你应该使用光照的最终结果存储到累积和可见性渲染目标中。

现在一切都已经渲染完毕,我们需要合成这两张图像,以便得到最终结果。

合成是许多技术中常用的方法,使用覆盖整个屏幕的后处理四边形。将其想象为在 Photoshop 或 Gimp 等照片编辑软件中合并两个图层。

在 OpenGL 中,我们可以通过颜色混合功能实现这一点:

// 设置渲染状态
glDepthFunc(GL_ALWAYS);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);// 绑定不透明FBO作为合成目标
glBindFramebuffer(GL_FRAMEBUFFER, opaqueFBO);// 使用合成着色器
compositeShader.use();// 绘制屏幕四边形
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, accumTexture);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, revealTexture);
glBindVertexArray(quadVAO);
glDrawArrays(GL_TRIANGLES, 0, 6);

合成着色器是完成另一半工作的地方。我们的工作基本上是将两个图层合并,一个是不透明对象图像,另一个是透明对象图像。累积缓冲区告诉我们颜色信息,可见性缓冲区决定下方像素的可见性:

// 着色器输出
layout (location = 0) out vec4 frag;
// 颜色累积缓冲区
layout (binding = 0) uniform sampler2D accum;
// 可见性阈值缓冲区
layout (binding = 1) uniform sampler2D reveal;// 极小值
const float EPSILON = 0.00001f;// 精确计算浮点数的相等性
bool isApproximatelyEqual(float a, float b)
{return abs(a - b) <= (abs(a) < abs(b) ? abs(b) : abs(a)) * EPSILON;
}// 获取三个值中的最大值
float max3(vec3 v)
{return max(max(v.x, v.y), v.z);
}void main()
{// 片段坐标ivec2 coords = ivec2(gl_FragCoord.xy);// 片段可见性float revealage = texelFetch(reveal, coords, 0).r;// 如果没有透明片段,则节省混合和颜色纹理获取成本if (isApproximatelyEqual(revealage, 1.0f))discard;// 片段颜色vec4 accumulation = texelFetch(accum, coords, 0);// 抑制溢出if (isinf(max3(abs(accumulation.rgb))))accumulation.rgb = vec3(accumulation.a);// 防止浮点精度错误vec3 average_color = accumulation.rgb / max(accumulation.a, EPSILON);// 混合像素frag = vec4(average_color, 1.0f - revealage);
}

注意,我们使用了一些辅助函数,如 isApproximatelyEqualmax3,以帮助我们精确计算浮点数。由于当前一代处理器在浮点数计算上的不精确性,我们需要使用一个极小的值(称为 epsilon)来比较值,以避免下溢或溢出。

此外,我们不需要中间帧缓冲区来进行合成。我们可以使用不透明帧缓冲区作为基础帧缓冲区并在其上绘制,因为它已经包含了不透明通道的信息。而且,我们声明所有深度测试都应通过,因为我们希望在不透明图像上绘制。

最后,将合成的图像(即不透明纹理附件,因为你在最后一遍中在其上渲染了透明图像)绘制到后缓冲区并观察结果:

// 设置渲染状态
glDisable(GL_DEPTH);
glDepthMask(GL_TRUE); // 启用深度写入,以免 glClear 忽略清除深度缓冲区
glDisable(GL_BLEND);// 绑定后缓冲区
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);// 使用屏幕着色器
screenShader.use();// 绘制最终屏幕四边形
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, opaqueTexture);
glBindVertexArray(quadVAO);
glDrawArrays(GL_TRIANGLES, 0, 6);

屏幕着色器只是一个简单的后处理着色器,用于绘制全屏四边形。

在常规渲染管线中,你还会在渲染到后缓冲区之前,在中间后处理帧缓冲区中应用伽马校正、色调映射等。但要确保在渲染不透明和透明表面时,以及在合成之前不应用这些处理,因为此透明技术需要原始颜色值来计算透明像素。

现在,你可以调整对象的 Z 轴,以观察无序透明的效果。尝试将透明对象放置在不透明对象后面,或完全打乱顺序。

image.png

在上图中,绿色四边形位于红色四边形后方,但绿色四边形在红色四边形之后才被渲染。如果移动相机从后面观察绿色四边形,你将不会看到任何伪像。

如前所述,该技术的一个局限性是,对于深度或 alpha 复杂度较高的场景,我们需要调整加权函数以获得正确的结果。幸运的是,论文中提供了一些经过测试的权重函数,你可以参考并测试它们以适应你的环境。

另请查看下面链接中的彩色透射透明(colored transmission transparency)技术,这是该技术的改进版本。

您可以在此处找到此演示的源代码。(博主源码:OIT - GitCode)


ke lin 在讨论区分享了实例化 + OIT 渲染数十万窗口的成果(令人惊叹),原作者指出了一个值得注意的地方:

Some objects have opaque and transparent surfaces mixed as one so you have to render them separately. All the other major renderers do it the same way too.(有些物体表面既有不透明的也有透明的部分,所以你必须分别渲染它们。)

建议大家去原文讨论区查看。

本篇教程也并没有对加权混合 OIT 的原理进行深入讲解,如原文所述:若需深入理解加权函数的技术细节,请参考原始论文第 5-7 页


进阶阅读

  • Weighted, Blended paper:发表于《计算机图形学杂志》的原始论文,简要回顾了透明渲染技术的发展历史及该技术的诞生背景。对于希望深入理解的读者,此论文为必读材料。
  • Weighted, Blended introduction:Casual Effects 是 Morgan McGuire 的个人博客。这篇帖子介绍了他们的技术,并深入探讨了更多细节,绝对值得一读。此外,还有他们实现技术的现场视频,你绝对不想错过。
  • Weighted, Blended for implementors:Morgan McGuire实现该技术的另一篇博客文章。
  • Weighted, Blended and colored transmission:探讨如何将加权混合技术扩展到彩色透射(Colored Transmission)场景,例如彩色玻璃的渲染。
  • A live implementation of the technique:Cesium引擎提供的WebGL实时演示工具,允许用户在浏览器中直接测试不同加权函数的效果。

Article by: Mahan Heshmati Moghaddam
Contact: e-mail(原作者的哈,不是博主)

相关文章:

LearnOpenGL——OIT

教程地址&#xff1a;简介 - LearnOpenGL CN 简介 原文链接&#xff1a;LearnOpenGL - Introduction 前言 在混合&#xff08;Blending&#xff09;章节中&#xff0c;我们介绍了颜色混合的主题。混合是在3D场景中实现透明表面的方法。简而言之&#xff0c;透明度涉及到在计算…...

【蓝桥杯】Python大学A组第十五届省赛

1.填空题 1.1.拼正方形 问题描述 小蓝正在玩拼图游戏,他有个的方块和个的方块,他需要从中挑出一些来拼出一个正方形。 比如用个和个的方块可以拼出一个的正方形;用个的方块可以拼出一个的正方形。 请问小蓝能拼成的最大的正方形的边长为多少。 import math # 2*2的个数 a =…...

使用JS+HTML+CSS编写提词器实例

手搓提词器网页版&#xff0c;有些BUG但是基本功能使用没有问题&#xff0c;有需要的可复制粘贴&#xff0c;BUG自行修复。下面直接进入代码&#xff1a; <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><me…...

人工智能基础知识详解:从概念到前沿技术与应用

在数字化浪潮席卷全球的今天&#xff0c;人工智能&#xff08;Artificial Intelligence&#xff0c;简称AI&#xff09;已不再是科技前沿的神秘概念&#xff0c;而是融入我们日常工作的实用工具。从智能语音助手到自动驾驶汽车&#xff0c;从医疗影像诊断到生成式艺术创作&…...

重温经典,畅享怀旧游戏盛宴

FC街机是一款专为安卓设备设计的经典游戏合集应用&#xff0c;它将玩家熟悉的红白机&#xff08;FC&#xff09;游戏体验带到了移动设备上。这款应用不仅提供了丰富的游戏选择&#xff0c;还通过优化的界面和操作&#xff0c;让玩家能够随时随地享受经典游戏的乐趣。 游戏非常…...

CPU 压力测试命令大全

CPU 压力测试命令大全 以下是 Linux/Unix 系统下常用的 CPU 压力测试命令和工具&#xff0c;可用于测试 CPU 性能、稳定性和散热能力。 1. 基本压力测试命令 1.1 使用 yes 命令 yes > /dev/null & # 启动一个无限循环进程 yes > /dev/null & # 启动第二个进…...

Windows 系统下用 VMware 安装 CentOS 7 虚拟机超详细教程(包含VMware和镜像安装包)

前言 资源 一、准备工作 &#xff08;一&#xff09;下载 VMware Workstation &#xff08;二&#xff09;下载 CentOS 7 镜像 二、安装 VMware Workstation&#xff08;比较简单&#xff0c;按下面走即可&#xff09; 三、创建 CentOS 7 虚拟机 四、安装 CentOS 7 系统…...

QLineEdit的提交前验证

QLineEdit是pyqt中常用的输入控件&#xff0c;默认情况下&#xff0c;它可以接受键盘输入的任何可打印字符。有时候&#xff0c;我们需要在用户提交前对其输入的内容先行验证&#xff0c;当用户输入不符合预期时予以清空&#xff0c;这就需要对QLineEdit控件进行以下操作&#…...

【Linux高级IO(二)】初识epoll

目录 1、epoll的接口 2、epoll原理 3、epoll工作方式 1、epoll的接口 #include <sys/epoll.h> 1、int epoll_create(int size) &#xff1a;创建epoll模型 返回值是一个文件描述符&#xff0c;创建一个struct file结构体&#xff0c;指向epoll模型&#xff0c;返回的…...

2018年真题

数学基础 一、 &#xff08;共4分&#xff09;用逻辑符号表达下列语句&#xff08;论域为包含一切事物的集合&#xff09; 1、&#xff08;2分&#xff09;集合A的任一元素的元素都是A的元素 经过对图片文字的识别与逻辑分析&#xff0c;结果如下&#xff1a; 符号定义&…...

Linux xxd命令

目录 一. xxd命令简介二. 简单使用三. -p选项纯16进制输出四. -r选项将十六进制还原成原始内容五. 小应用 一. xxd命令简介 xxd 是一个将文件或输入内容转换为十六进制&#xff08;Hex Dump&#xff09;格式的工具&#xff0c;也可以将十六进制恢复成原始数据。 它在调试二进制…...

高校实验室安全数智化分级分类管理-危化品管理LIMS

一、背景与依据 传统实验室安全管理如同老式挂钟&#xff0c;齿轮咬合处总会随时间产生间隙。为进一步规范学校实验室建设与适用&#xff0c;从源头管控实验室和实验项目安全风险&#xff0c;确保教学科研活动安全有序开展&#xff0c;分级分类体系构建如同绘制实验室的"…...

春芽儿智能跳绳:以创新技术引领运动健康新潮流

在全球运动健康产业蓬勃发展的浪潮中&#xff0c;智能健身器材正成为连接科技与生活的重要纽带。据《中国体育用品产业发展报告》显示&#xff0c;2023年中国智能运动装备市场规模突破千亿元&#xff0c;其中跳绳类目因兼具大众普及性与技术升级空间&#xff0c;年均增速超30%。…...

Fast网络速度测试工具

目录 网站简介 功能特点 测试过程 为什么使用Fast 如果网络速度不达标 网站简介 Fast是一个由Netflix提供的网络速度测试工具&#xff0c;主要用来测试用户的互联网下载速度。它以其简洁的界面和快速的测试过程而受到用户的欢迎。 功能特点 下载速度测试&#xff1a;这是…...

java的文件输入输出流(FileInputStream、FileOutputStream、FileReader、FileWriter)

文章目录 文件输入输出流1 java I/O 流的原理流的分类 2 FileInputStream 文件字节输入流3 FileOutputStream 文件字节输出流4 使用文件字节输入输出流完成对文件的拷贝5 FileReader 文件字符输入流6 FileWriter 文件字符输出流 文件输入输出流 1 java I/O 流的原理 I/O 是 In…...

stm32week10

stm32学习 七.CAN 7.STM32 CAN外设 标识符过滤器&#xff1a; 每个过滤器的核心由两个32位寄存器组成&#xff1a;R1[31:0]和R2[31:0] FSCx&#xff1a;位宽设置&#xff0c;置0为16位&#xff0c;置1为32位 FBMx&#xff1a;模式设置&#xff0c;置0为屏蔽模式&#xff0c;…...

【UnityEditor扩展】如何在 Unity 中创建棱柱体(用作VR安全区检测),同时在编辑器插件中实现与撤销/恢复功能

Unity 编辑器扩展&#xff1a;3D 空间中绘制安全区棱柱体&#xff08;含撤销/恢复/保存/读取&#xff09; 在虚拟现实&#xff08;VR&#xff09;和增强现实&#xff08;AR&#xff09;开发中&#xff0c;安全区是保障用户安全的重要组成部分。通过精确控制用户活动范围&#…...

C++小游戏 合集

生化危机 #include<conio.h> #include<string.h> #include<stdio.h> #include<stdlib.h> #include<windows.h> #include<time.h> #include<direct.h> int n,round,gold0; bool f1,f2,f3,deadfalse,PC_64Bit; char str[4]; struct n…...

常州 d??

回来了&#xff01; 今天&#xff08;&#xff1f;发出来的时候可能已经是第二天了吧 真的爆零了qaq 挺难的 最高分只有100 而且t2t3t4一个人都没拿分qaq 这段时间在写网络流 唉博客还是不要写太水了 抄一点网络流代码上来 EK不写了 dinic会就行了 板子题洛谷p3376 dini…...

juc并发包的常用类、线程安全实现方式、锁机制及 JVM 优化策略

juc并发包的常用类、线程安全实现方式、锁机制及 JVM 优化策略 1. juc包下的常用类&#xff1a;线程池&#xff1a;并发集合类&#xff1a;同步工具类&#xff1a;原子类&#xff1a; 2. 怎么保证多线程安全&#xff1a;3. Java中常用锁及使用场景&#xff1a;4. 线程同步的方法…...

学习日记-0407(Inductive Matrix Completion Using Graph Autoencoder)

论文阅读&#xff1a;Inductive Matrix Completion Using Graph Autoencoder 代码&#xff1a;swtheing/IMC-GAE 总而言之就是设计了一个不同评分下的邻接图&#xff0c;然后对每一个评分图T进行独立GNN编码。这个 GNN 编码器主要由三个组件构成&#xff1a;嵌入层、消息传递层…...

FPGA入门:状态机思想编程

一、状态机思想编写流水灯 1、状态机思想的概念 状态机思想是一种用于描述和处理具有多个状态以及状态之间转换关系的系统的思维方式。以下是对其主要概念、应用场景和优势的介绍&#xff1a; 主要概念 状态&#xff1a;指系统在某一时刻的状况或条件。例如&#xff0c;在一…...

【电路笔记】-切换触发器

切换触发器 文章目录 切换触发器1、概述2、切换触发器3、JK触发器转换为D型触发器4、D型触发器转换为切换触发器切换触发器是常用的时序逻辑电路,作为单个比特双稳态存储元件,在计数器、存储器设备中经常使用,或作为响应时钟脉冲的分频器。 1、概述 切换触发器是另一种基于…...

示例项目文档模板集:TaskBoard 任务管理系统

一套完整、高可读性、结构清晰的项目文档模板,适用于中小型软件项目的设计、开发、交接与展示全流程。 📌 项目概述文档(overview.md) 📂 项目名称:TaskBoard 🧭 项目简介 TaskBoard 是一款专为敏捷团队打造的任务管理系统,支持任务分配、状态追踪与协作沟通,帮…...

TF-IDF忽略词序问题思考

自从开始做自然语言处理的业务&#xff0c;TF-IDF就是使用很频繁的文本特征技术&#xff0c;他的优点很多&#xff0c;比如&#xff1a;容易理解&#xff0c;不需要训练&#xff0c;提取效果好&#xff0c;可以给予大规模数据使用&#xff0c;总之用的很顺手&#xff0c;但是人…...

代理模式的优缺点是什么?

什么是代理模式&#xff1f; 代理模式&#xff08;Proxy Pattern&#xff09;是一种结构型设计模式&#xff0c;它通过创建代理对象来控制对原始对象的访问。 这种模式在前端开发中广泛应用&#xff0c;特别是在需要控制对象访问、添加额外逻辑或优化性能的场景中。 ​​核心…...

十分钟上手:Distilling the Knowledge in a Neural Network

概述&#xff1a;知识蒸馏是一种模型压缩技术&#xff0c;通过让轻量化的学生模型模仿复杂教师模型的输出概率分布&#xff0c;结合软目标和硬目标进行训练&#xff0c;从而将教师模型的泛化能力迁移至学生模型&#xff0c;实现小模型的高效部署而不显著降低性能。 硬目标&…...

百度的deepseek与硅基模型的差距。

问题&#xff1a; 已经下载速度8兆每秒&#xff0c;请问下载30G的文件需要多长时间&#xff1f; 关于这个问题。百度的回答如下&#xff1a; ‌30GB文件下载时间计算‌ ‌理论计算‌&#xff08;基于十进制单位&#xff09;&#xff1a; ‌单位换算‌ 文件大小&#xff1a;3…...

OpenCV 图形API(18)用于执行两个矩阵(或数组)的逐元素减法操作函数sub()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 描述 计算两个矩阵之间的逐元素差值。 sub 函数计算两个矩阵之间的差值&#xff0c;要求这两个矩阵具有相同的尺寸和通道数&#xff1a; dst ( I ) src…...

布谷一对一直播源码android版环境配置流程及功能明细

一&#xff1a;举例布谷交友&#xff08;一对一直播源码&#xff09;搭建部署的基本环境说明 1. 首先安装Center OS 7.9系统&#xff0c;硬盘最低 40G 2. 安装宝塔环境 https://bt.cn&#xff08;强烈推荐使用&#xff09; 3. 安装环境 ● PHP 7.3&#xff08;安装redis扩展…...

#MongoDB 快速上手

docker pull mongo docker run -d --name my-mongo -p 27017:27017 mongo docker exec -it my-mongo mongo &#x1f6aa;进入 Mongo Shell 后的第一步 你进入后会看到类似提示符&#xff1a; >说明已经进入 Mongo Shell&#xff0c;现在就可以操作数据库了。 &#x1…...

docker相关命令

常用命令 #创建并启动 docker-compose up -d # 启动之后就可以通过浏览器访问了 #停止并删除 docker-compose down #重启 docker-compose restart #停止 docker-compose stop #启动 docker-compose startdocker search #搜索镜像(只搜索官方仓库的&#xff0c;官方仓库地址&am…...

浅谈进程与程序的区别

如大家所了解的&#xff0c;进程与程序是有区别的。 下面做了一个总结&#xff0c;供大家参考、学习&#xff1a; 1. 程序是指令的有序集合&#xff0c;是一个静态的概念&#xff0c;其本身没有任何运行的含义。进程是程序在 CPU 上的一次执行过程&#xff0c;是一个动态的概…...

redis 和 MongoDB都可以存储键值对,并且值可以是复杂json,用完整例子分别展示说明两者在存储json键值对上的使用对比

Redis 存储 JSON 键值对示例 存储操作&#xff1a; // 存储用户信息&#xff08;键&#xff1a;user:1001&#xff0c;值&#xff1a;JSON对象&#xff09; SET user:1001 {"name":"Alice", "age":30, "address":"New York&quo…...

基于chatgpt得到的生活成本计算

意大利的生活成本因城市而异&#xff0c;比如米兰和罗马相对较贵&#xff0c;而南部城市如那不勒斯或巴勒莫则便宜一些。下面是意大利大致的基本生活成本和费用明细&#xff08;以欧元€为单位&#xff0c;2025年初数据为基础&#xff0c;具体数值可能随时间和汇率略有变化&…...

C和C++有什么区别?

C和C是两种不同的编程语言&#xff0c;虽然它们有许多相似之处&#xff0c;但也存在一些关键的区别。 C是一种过程化编程语言&#xff0c;专注于函数和流程控制&#xff0c;非常适合系统级编程。而 C是一种面向对象编程语言&#xff0c;支持类、对象和封装、继承、多态等特性。…...

力扣1338 === 贪心算法解决数组减半问题

目录 问题分析 方法思路&#xff1a;贪心算法 步骤分解 代码解释 复杂度分析 正确性证明 示例验证 边界情况 总结 要解决这个问题&#xff0c;我们需要找到最少需要删除的不同整数集合&#xff0c;使得剩余的元素个数不超过原数组的一半。以下是对该问题的详细分析和解…...

企业知识库如何搭建?应对高频咨询的AI自助问答系统

在客户服务和内部沟通中&#xff0c;“同样的问题被反复问”、“信息找不到”、“新员工上手慢”等现象屡见不鲜。为了提升企业运营效率&#xff0c;越来越多企业开始重视知识库建设&#xff0c;而“企业知识库如何搭建”也成为热门话题。 尤其在AI技术快速发展的今天&#xf…...

UE5学习笔记 FPS游戏制作44 统一UI大小 sizeBox

如果我们希望多个类似的UI大小一样&#xff0c;例如不同菜单的标题&#xff0c;可以使用sizeBox组件 我们在标题控件上&#xff0c;用sizeBox包裹所有子物体 然后指定他的最小宽高&#xff0c;或最大宽高 如果指定的是最小宽高&#xff0c;当子元素&#xff08;如图片&#xf…...

SpringAOP新链浅析

前言 在复现CCSSSC软件攻防赛的时候发现需要打SpringAOP链子&#xff0c;于是跟着前人的文章自己动手调试了一下 参考了大佬的文章 https://gsbp0.github.io/post/springaop/#%E6%B5%81%E7%A8%8B https://mp.weixin.qq.com/s/oQ1mFohc332v8U1yA7RaMQ 正文 依赖于Spring-AO…...

高效网页截图利器:支持长截图、异步加载内容截图、API调用、Docker一键部署!

一、简介 利用playwright自动化工具&#xff0c;模拟浏览器打开网页&#xff0c;实现完整网页截图功能支持长截图&#xff0c;支持异步加载动态渲染内容截图支持docker一键部署支持API调用项目地址&#xff1a;https://github.com/luler/hello_screenshot 二、安装 提前安装好d…...

处理语言模型返回的响应

completion.choices[0].message.content 是在处理语言模型&#xff08;如 OpenAI 的 GPT 系列&#xff09;返回的响应时&#xff0c;用于 访问模型生成的文本内容的代码路径。为了更好地理解它&#xff0c;我们需要先了解语言模型响应的结构。 1. 响应的结构 当使用语言模型&…...

Go语言类型捕获及内存大小判断

代码如下&#xff1a; 类型捕获可使用&#xff1a;reflect.TypeOf()&#xff0c;fmt.Printf在的%T。 内存大小判断&#xff1a;len()&#xff0c;unsafe.Sizeof。 package mainimport ("fmt""unsafe""reflect" )func main(){var i , j 1, 2f…...

Java 大视界 -- Java 大数据机器学习模型在智能客服多轮对话系统中的优化策略(179)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…...

CAS号:288574-78-7,Zinpyr-1可用作PET传感器

试剂描述&#xff1a; Zinpyr-1&#xff08;ZP-1&#xff09;是一种具细胞膜渗透性的荧光探针&#xff0c;选择性检测锌离子&#xff08;Zn2&#xff09;&#xff08;Kd 0.7 0.1 nM&#xff09;。一旦与金属离子复合&#xff0c;诱发荧光信号产生。活细胞内&#xff0c;Zinpyr…...

【JVM调优实战指南:从案例分析到性能优化】

一、JVM 调优核心原则 JVM 调优旨在平衡系统的吞吐量、延迟和内存使用。在进行 JVM 调优时&#xff0c;我们可以遵循以下原则&#xff1a; 先优化代码&#xff1a;优先排查业务逻辑中的内存泄漏、对象滥用等问题。优化代码不仅能从根本上解决性能问题&#xff0c;还能减少对 J…...

交换机转发原理 和 DNS服务

1. 收到报文后&#xff0c;将其转换为二进制&#xff0c;并记录在缓存当中 2. 根据二进制中的源 MAC 地址&#xff0c;与接收报文的接口&#xff0c;记录对应关系&#xff0c;在 MAC 地址表中&#xff0c;每个动态表项 300S 老化时间。 3. 判断 如果目的 MAC 是组播或广…...

强化学习Q-Learning:DQN

强化学习Q-Learning/DQN 本文是一篇学习笔记&#xff0c;主要参考李宏毅老师的强化学习课程。 目前主流的强化学习方法大致可以分为 policy-based 和 value-based 两大类。之前我们介绍的 policy gradient 策略梯度&#xff0c;就是 policy-based 的方法。本文要介绍的 Q-learn…...

OpenCv(七)——模板匹配、打包、图像的旋转

目录 一、模板匹配 模板匹配原理 1、单模板之间的匹配 &#xff08;1&#xff09;读取并显示待匹配的图片和模板图片 &#xff08;2&#xff09;模板匹配并绘制匹配位置的外接矩形 &#xff08;3&#xff09;显示最终的效果 2、模板与多个对象匹配&#xff0c;仅匹配当前…...

汽车售后诊断 ODX 和 OTX 对比分析报告

一、引言 在汽车行业不断发展的当下&#xff0c;汽车售后诊断技术对于保障车辆性能、维护车主权益以及提升汽车品牌服务质量起着至关重要的作用。随着汽车电子化程度的不断提高&#xff0c;售后诊断所涉及的数据和流程愈发复杂&#xff0c;这就促使行业需要更加标准化、高效化…...