从零开始的OpenGL光栅化渲染器构建4-延迟渲染及其类似应用

news2025/1/13 10:13:13

前言

首先介绍一下什么是延迟渲染。延迟渲染是一种先计算场景中的顶点、颜色、法线等信息,将其存入缓冲,再进行光照计算的渲染技术,与直接渲染是相对的概念。为了详细介绍延迟渲染,我们首先需要了解帧缓冲,以及帧缓冲的应用,之后介绍基于帧缓冲技术的延迟渲染。

帧缓冲

我们在使用OpenGL去渲染各种效果时,用到许多屏幕缓冲:用于写入颜色值的颜色缓冲、用于写入深度信息的深度缓冲和允许我们根据一些条件丢弃特定片段的模板缓冲。这些缓冲结合起来叫做帧缓冲(Framebuffer),它被存储在内存中。OpenGL允许我们定义我们自己的帧缓冲,也就是说我们能够定义我们自己的颜色缓冲,乃至深度缓冲和模板缓冲。

一个完整的帧缓冲需要满足以下的条件:

  • 附加至少一个缓冲(颜色、深度或模板缓冲)。
  • 至少有一个纹理附件(Attachment)。
  • 所有的附件都必须是完整的(保留了内存)。
  • 每个缓冲都应该有相同的样本数。

纹理附件可以包含颜色纹理、深度纹理、模板纹理。当把一个纹理附加到帧缓冲的时候,所有的渲染指令将会写入到这个纹理中,就像它是一个普通的颜色/深度或模板缓冲一样。使用纹理的优点是,所有渲染操作的结果将会被储存在一个纹理图像中,我们之后可以在着色器中很方便地使用它。

渲染缓冲对象是在纹理之后被引入到OpenGL中,作为一个可用的帧缓冲附件类型的,所以在过去纹理是唯一可用的附件。和纹理图像一样,渲染缓冲对象是一个真正的缓冲,即一系列的字节、整数、像素等。渲染缓冲对象附加的好处是,它会将数据储存为OpenGL原生的渲染格式,它是为离屏渲染到帧缓冲优化过的。渲染缓冲对象直接将所有的渲染数据储存到它的缓冲中,不会做任何针对纹理格式的转换,让它变为一个更快的可写储存介质。然而,渲染缓冲对象通常都是只写的,所以你不能读取它们(比如使用纹理访问)。当然你仍然还是能够使用glReadPixels来读取它,这会从当前绑定的帧缓冲,而不是附件本身,中返回特定区域的像素。

渲染缓冲对象能为你的帧缓冲对象提供一些优化,但知道什么时候使用渲染缓冲对象,什么时候使用纹理是很重要的。通常的规则是,如果你不需要从一个缓冲中采样数据,那么对这个缓冲使用渲染缓冲对象会是明智的选择。如果你需要从缓冲中采样颜色或深度值等数据,那么你应该选择纹理附件。性能方面它不会产生非常大的影响的。

帧缓冲的简单应用

我们可以利用帧缓冲实现一些简单的后处理。

我们可以从屏幕纹理中取颜色值,然后用1.0减去它,对它进行反相:

在这里插入图片描述

除此之外,我们可以取屏幕上每个像素所有的颜色分量,将它们平均化,实现灰度效果:

在这里插入图片描述

另外,我们可以在当前纹理坐标的周围取一小块区域,对当前纹理值周围的多个纹理值进行采样。

核(Kernel)(或卷积矩阵(Convolution Matrix))是一个类矩阵的数值数组,它的中心为当前的像素,它会用它的核值乘以周围的像素值,并将结果相加变成一个值。所以,基本上我们是在对当前像素周围的纹理坐标添加一个小的偏移量,并根据核将结果合并。下面是核的一个例子:
[ 2 2 2 2 − 15 2 2 2 2 ] \begin{bmatrix} 2 & 2 & 2\\ 2 & -15 & 2\\ 2 & 2 & 2 \end{bmatrix} 2222152222
这个核取了8个周围像素值,将它们乘以2,而把当前的像素乘以-15。这个核的例子将周围的像素乘上了一个权重,并将当前像素乘以一个比较大的负权重来平衡结果。采用这个核来对像素周围进行采样,效果图如下:

在这里插入图片描述

HDR

帧缓冲的另一个应用是HDR。显示器被限制为只能显示值为0.0到1.0间的颜色,但是在光照方程中却没有这个限制。通过使片段的颜色超过1.0,我们有了一个更大的颜色范围,这也被称作HDR(High Dynamic Range, 高动态范围)。有了HDR,亮的东西可以变得非常亮,暗的东西可以变得非常暗,而且充满细节。

在HDR渲染中,我们允许用更大范围的颜色值渲染从而获取大范围的黑暗与明亮的场景细节,将这些场景细节通过帧缓冲渲染到纹理上,最后将所有HDR值转换成在[0.0, 1.0]范围的LDR(Low Dynamic Range,低动态范围),得到最终结果。转换HDR值到LDR值得过程叫做色调映射(Tone Mapping),现在现存有很多的色调映射算法,这些算法致力于在转换过程中保留尽可能多的HDR细节。这些色调映射算法经常会包含一个选择性倾向黑暗或者明亮区域的参数。

**色调映射(Tone Mapping)**是一个损失很小的转换浮点颜色值至我们所需的LDR[0.0, 1.0]范围内的过程,通常会伴有特定的风格的色平衡(Stylistic Color Balance)。

最简单的色调映射算法是Reinhard色调映射,它涉及到分散整个HDR颜色值到LDR颜色值上,所有的值都有对应。Reinhard色调映射算法平均地将所有亮度值分散到LDR上。我们可以利用Reinhard色调映射对帧缓冲渲染得到的纹理进行应用,并且为了更好的测量加上一个Gamma校正过滤(包括SRGB纹理的使用):

void main()
{             
    const float gamma = 2.2;
    vec3 hdrColor = texture(hdrBuffer, TexCoords).rgb;

    // Reinhard色调映射
    vec3 mapped = hdrColor / (hdrColor + vec3(1.0));
    // Gamma校正
    mapped = pow(mapped, vec3(1.0 / gamma));

    color = vec4(mapped, 1.0);
}   

在这里插入图片描述

上图是应用Reinhard色调映射的渲染结果,我们不再会在场景明亮的地方损失细节。当然,这个算法是倾向明亮的区域的,暗的区域会不那么精细也不那么有区分度。另外一种有趣的色调映射方法是采用曝光参数。如果我们有一个场景要展现日夜交替,我们当然会在白天使用低曝光,在夜间使用高曝光,就像人眼调节方式一样。有了这个曝光参数,我们可以去设置可以同时在白天和夜晚不同光照条件工作的光照参数,我们只需要调整曝光参数就行了。

一个见到的曝光色调映射算法会是这样:

uniform float exposure;

void main()
{             
    const float gamma = 2.2;
    vec3 hdrColor = texture(hdrBuffer, TexCoords).rgb;

    // 曝光色调映射
    vec3 mapped = vec3(1.0) - exp(-hdrColor * exposure);
    // Gamma校正 
    mapped = pow(mapped, vec3(1.0 / gamma));

    color = vec4(mapped, 1.0);
}  

包含曝光等级的色调映射效果如下:

在这里插入图片描述

泛光

明亮的光源和区域经常很难向观察者表达出来,因为显示器的亮度范围有限,一种区分明亮光源的方法是使它们在监视器上发出光芒,光源的光芒向四周发散。这样观察者就会产生光源或亮区的确是强光区。

光晕效果可以使用一个后处理特效泛光来实现。泛光使所有明亮区域产生光晕效果。

泛光和HDR结合使用效果很好。常见的一个误解是HDR和泛光是一样的,很多人认为两种技术是可以互换的。但是它们是两种不同的技术,用于各自不同的目的上。

为了实现泛光,我们首先需要渲染一个有光场景,提取出场景的HDR颜色缓冲以及只有这个场景明亮区域可见的图片。被提取的带有亮度的图片接着被模糊,结果被添加到HDR场景上面。泛光的主要流程图如下图所示:

在这里插入图片描述

其中每一个中间过程都通过帧缓冲来实现和保存。

泛光的实现效果如下:

在这里插入图片描述

延迟渲染

接下来开始详细介绍延迟渲染,或延迟着色法。

延迟着色法基于我们**延迟(Defer)推迟(Postpone)**大部分计算量非常大的渲染(像是光照)到后期进行处理的想法。它包含两个处理阶段(Pass):

在第一个几何处理阶段(Geometry Pass)中,我们先渲染场景一次,之后获取对象的各种几何信息,并储存在一系列叫做G缓冲(G-buffer)的纹理中;想想位置向量(Position Vector)、颜色向量(Color Vector)、法向量(Normal Vector)和/或镜面值(Specular Value)。场景中这些储存在G缓冲中的几何信息将会在之后用来做(更复杂的)光照计算。

我们会在第二个光照处理阶段(Lighting Pass)中使用G缓冲内的纹理数据。在光照处理阶段中,我们渲染一个屏幕大小的方形,并使用G缓冲中的几何数据对每一个片段计算场景的光照;在每个像素中我们都会对G缓冲进行迭代。我们对于渲染过程进行解耦,将它高级的片段处理挪到后期进行,而不是直接将每个对象从顶点着色器带到片段着色器。光照计算过程还是和我们以前一样,但是现在我们需要从对应的G缓冲而不是顶点着色器(和一些uniform变量)那里获取输入变量了。

下面这幅图片很好地展示了延迟着色法的整个过程:
在这里插入图片描述

整个过程的伪代码是这样的:

while(...) // 循环
{
    // 1. 几何处理阶段:渲染所有的几何/颜色数据到G缓冲 
    glBindFramebuffer(GL_FRAMEBUFFER, gBuffer);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    gBufferShader.Use();
    for(Object obj : Objects)
    {
        ConfigureShaderTransformsAndUniforms();
        obj.Draw();
    }  
    // 2. 光照处理阶段:使用G缓冲计算场景的光照
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glClear(GL_COLOR_BUFFER_BIT);
    lightingPassShader.Use();
    BindAllGBufferTextures();
    SetLightingUniforms();
    RenderQuad();
}

在代码中,我们使用RGB纹理来存储位置和法线的数据,因为每个对象只有三个分量;将颜色和镜面强度数据合并到一起,存储到一个单独的RGBA纹理里面,这样我们就不需要声明一个额外的颜色缓冲纹理了。

因为有光照计算,所以保证所有变量在一个坐标空间当中至关重要。在这里我们在世界空间中存储(并计算)所有的变量。

延迟着色法的其中一个缺点就是它不能进行[混合](https://learnopengl-cn.github.io/04 Advanced OpenGL/03 Blending/)(Blending),因为G缓冲中所有的数据都是从一个单独的片段中来的,而混合需要对多个片段的组合进行操作。延迟着色法另外一个缺点就是它迫使你对大部分场景的光照使用相同的光照算法,可以通过包含更多关于材质的数据到G缓冲中来减轻这一缺点。

为了克服这些缺点(特别是混合),我们通常分割我们的渲染器为两个部分:一个是延迟渲染的部分,另一个是专门为了混合或者其他不适合延迟渲染管线的着色器效果而设计的的正向渲染的部分。

延迟渲染的初始结果如下:

在这里插入图片描述

我们可以将延迟渲染中的深度信息采集下来,存入当前屏幕的帧缓冲中,用于正向渲染,这样就不会产生正向渲染的正方体直接绘制在延迟渲染的物体前方的问题。结合延迟渲染和正向渲染的效果图如下:

在这里插入图片描述

延迟渲染一直被称赞的原因就是它能够渲染大量的光源而不消耗大量的性能。然而,延迟渲染它本身并不能支持非常大量的光源,因为我们仍然必须要对场景中每一个光源计算每一个片段的光照分量。真正让大量光源成为可能的是我们能够对延迟渲染管线引用的一个非常棒的优化:光体积(Light Volumes)

隐藏在光体积背后的想法就是计算光源的半径,或是体积,也就是光能够到达片段的范围。由于大部分光源都使用了某种形式的衰减(Attenuation),我们可以用它来计算光源能够到达的最大路程,或者说是半径。我们接下来只需要对那些在一个或多个光体积内的片段进行繁重的光照运算就行了。这可以给我们省下来很可观的计算量,因为我们现在只在需要的情况下计算光照。

如何计算一个光源的体积或半径?

为了获取一个光源的体积半径,我们需要解一个对于一个我们认为是**黑暗(Dark)**的亮度(Brightness)的衰减方程,它可以是0.0,或者是更亮一点的但仍被认为黑暗的值,像是0.03。为了展示我们如何计算光源的体积半径,我们将会使用一个在[投光物](http://learnopengl-cn.readthedocs.org/zh/latest/02 Lighting/05 Light casters/)这节中引入的一个更加复杂,但非常灵活的衰减方程:
F l i g h t = I K c + K l ∗ d + K q ∗ d 2 \mathbf F_{light} = \frac{I}{K_c + K_l * d + K_q * d^2} Flight=Kc+Kld+Kqd2I
我们现在想要在 F l i g h t F_{light} Flight等于0的前提下解这个方程,也就是说光在该距离完全是黑暗的。然而这个方程永远不会真正等于0.0,所以它没有解。所以,我们不会求表达式等于0.0时候的解,相反我们会求当亮度值靠近于0.0的解,这时候它还是能被看做是黑暗的。在这个教程的演示场景中,我们选择5/256作为一个合适的光照值;除以256是因为默认的8-bit帧缓冲可以每个分量显示这么多强度值(Intensity)。

我们要求的衰减方程会是这样:
5 256 = I m a x A t t e n u a t i o n \frac{5}{256} = \frac{I_{max}}{Attenuation} 2565=AttenuationImax
在这里, I m a x I_{max} Imax是光源最亮的颜色分量。我们之所以使用光源最亮的颜色分量是因为解光源最亮的强度值方程最好地反映了理想光体积半径。

继续解方程:
5 256 ∗ A t t e n u a t i o n = I m a x 5 ∗ A t t e n u a t i o n = I m a x ∗ 256 A t t e n u a t i o n = I m a x ∗ 256 5 K c + K l ∗ d + K q ∗ d 2 = I m a x ∗ 256 5 K c + K l ∗ d + K q ∗ d 2 − I m a x ∗ 256 5 = 0 \frac{5}{256} * Attenuation = I_{max} \\ 5 * Attenuation = I_{max} * 256 \\ Attenuation = I_{max} * \frac{256}{5} \\ K_c + K_l * d + K_q * d^2 = I_{max} * \frac{256}{5} \\ K_c + K_l * d + K_q * d^2 - I_{max} * \frac{256}{5} = 0 2565Attenuation=Imax5Attenuation=Imax256Attenuation=Imax5256Kc+Kld+Kqd2=Imax5256Kc+Kld+Kqd2Imax5256=0
我们可以用求根公式来解这个二次方程:
d = − K l + K l − 4 ∗ K q ∗ ( K c − I m a x ∗ 256 5 ) 2 ∗ K q d = \frac{-K_l + \sqrt{K_l - 4 * K_q * (K_c - I_{max} * \frac{256}{5})}}{2 * K_q} d=2KqKl+Kl4Kq(KcImax5256)
在计算光源与物体的光照时,我们可以先判断一下物体是否在光源的覆盖范围内,如果不在,直接跳过计算。

SSAO

SSAO(屏幕空间环境光遮蔽)的着色流程与延迟渲染类似,这里也顺带介绍一下SSAO的原理和实现。

环境光遮蔽(Ambient Occlusion, AO)是一种对间接光的模拟,它的原理是通过将褶皱、孔洞和非常靠近的墙面变暗的方法近似模拟出间接光照。这些区域很大程度上是被周围的几何体遮蔽的,光线会很难流失,所以这些地方看起来会更暗一些。

这一小节所谈论的是屏幕空间环境光遮蔽(Screen-Space Ambient Occlusion, SSAO),这一技术使用屏幕空间场景的深度而不是真实的几何体数据来确定遮蔽量。这一做法相对于真正的环境光遮蔽不但速度快,而且能够获得很好的效果。

SSAO背后的原理很简单:对于铺屏四边形(Screen-filled Quad)上的每一个片段,我们都会根据周边深度值计算一个遮蔽因子(Occlusion Factor)。这个遮蔽因子之后会被用来减少或者抵消片段的环境光照分量。遮蔽因子是通过采集片段周围球型核心(Kernel)的多个深度样本,并和当前片段深度值对比而得到的。高于片段深度值样本的个数就是我们想要的遮蔽因子。

在这里插入图片描述

采用上图所示的球体进行采样,会导致平整的墙面也会显得灰蒙蒙的,因为核心中一半的样本都会在墙这个几何体上。下面这幅图展示了孤岛危机的SSAO,它清晰地展示了这种灰蒙蒙的感觉:

在这里插入图片描述

出于这个原因,我们将不会使用球体的采样核心,而使用一个沿着表面法向量的半球体采样核心。

在这里插入图片描述

样本缓冲

SSAO需要获取几何体的信息,因为我们需要一些方式来确定一个片段的遮蔽因子。对于每一个片段,我们将需要这些数据:

  • 逐片段位置向量
  • 逐片段的法线向量
  • 逐片段的反射颜色
  • 采样核心
  • 用来旋转采样核心的随机旋转矢量

SSAO的整个绘制过程如下图所示:

在这里插入图片描述

法向半球

我们需要沿着表面法线方向生成大量的样本。由于对每个表面法线方向生成采样核心非常困难,也不合实际,可以在切线空间中生成采样核心,法向量将指向正z方向。

随机核心转动

通过引入一些随机性到采样核心上,我们可以大大减少获得不错结果所需的样本数量。我们可以对场景中每一个片段创建一个随机旋转向量,但这会很快将内存耗尽。所以,更好的方法是创建一个小的随机旋转向量纹理平铺在屏幕上。

最终生成的SSAO渲染结果如下图所示:

在这里插入图片描述

参考

https://learnopengl-cn.github.io/04%20Advanced%20OpenGL/05%20Framebuffers/

https://learnopengl-cn.github.io/05%20Advanced%20Lighting/06%20HDR/

https://learnopengl-cn.github.io/05%20Advanced%20Lighting/07%20Bloom/

https://learnopengl-cn.github.io/05%20Advanced%20Lighting/08%20Deferred%20Shading/

https://learnopengl-cn.github.io/05%20Advanced%20Lighting/09%20SSAO/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1401393.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++入门学习(十)如何显示浮点数的完整形态

在C中&#xff0c;如果你想要显示浮点数的完整数字&#xff08;包括小数部分和指数部分&#xff09;&#xff0c;可以使用 std::setprecision 和 std::fixed 来设置精度和固定小数点表示&#xff1a; #include <iostream> #include <iomanip> // 必须包含这个头…

深入剖析:Kafka流数据处理引擎的核心面试问题解析75问(5.7万字参考答案)

Kafka 是一款开源的分布式流处理平台&#xff0c;被广泛应用于构建实时数据管道、日志聚合、事件驱动的架构等场景。本文将深入探究 Kafka 的基本原理、特点以及其在实际应用中的价值和作用。 Kafka 的基本原理是建立在发布-订阅模式之上的。生产者将消息发布到主题&#xff08…

Kafka-消费者-KafkaConsumer分析-Heartbeat

在前面分析Rebalance操作的原理时介绍到&#xff0c;消费者定期向服务端的GroupCoordinator发送HeartbeatRequest来确定彼此在线。 下面就来详细分析KafkaConsumer中Heartbeat的相关实现。 首先了解一下心跳请求和响应的格式。HeartbeatRequest的消息体格式比较简单&#xff…

CTFhub-bak文件

CTFhub-Web-信息泄露-备份文件下载-bak文件 题目信息 解题过程 看到提示说和index.php有关&#xff0c;在url后面加index.php.bak&#xff0c;跳转到http://challenge-7a4da2076cfabae6.sandbox.ctfhub.com:10800/index.php.bak网址&#xff0c;即&#xff1a; 跳转到下载页…

安装ddddocr中遇到的问题

1、需要先安装&#xff1a; pip3 install pyinstaller --no-use-pep517 pip install scikit-build pip install setuptools pip install pyinstaller pip install pillow 重要是的是保证一个python 环境&#xff0c;多个python环境会导致各种问题。并且保证python>3.8…

万辰集团十年经营首度亏损,泡沫式增长是喜是忧?

2023年&#xff0c;线下消费回暖复苏&#xff0c;零售领域更是繁花似锦。以量贩零食为代表的新业态蓬勃发展&#xff0c;引得众多资本和企业纷至沓来。 这一背景下&#xff0c;作为新兴量贩零食的典型代表之一&#xff0c;福建万辰生物科技集团股份有限公司&#xff08;以下简…

JavaEE中的监听器的作用和工作原理

在JavaEE&#xff08;Java Platform, Enterprise Edition&#xff09;中&#xff0c;监听器&#xff08;Listener&#xff09;是一种重要的组件&#xff0c;用于监听和响应Web应用程序中的事件。监听器的作用是在特定的事件发生时执行一些自定义的逻辑。常见的监听器包括Servle…

vue3中Fragment特性的一个bug,需要留意的注意事项

vue3中的Fragment 模版碎片特性是什么&#xff0c;简单的理解就是template模板代码不在像vue2中那样必须在根节点在包裹一层节点了。 vue2写法 <template><div><h1>标题</h1><p>正文内容</p></div> </template>vue3写法 &l…

医学图像的数据增强技术 --- 切割-拼接数据增强(CS-DA)

医学图像的新型数据增强技术 CS-DA 核心思想自然图像和医学图像之间的关键差异CS-DA 步骤确定增强后的数据数量 代码复现 CS-DA 核心思想 论文链接&#xff1a;https://arxiv.org/ftp/arxiv/papers/2210/2210.09099.pdf 大多数用于医学分割的数据增强技术最初是在自然图像上开…

滑动窗口求连续数列最大值(固定窗长和非固定窗长)

非固定窗长 思路&#xff1a; sum 0&#xff0c;max num[0], 依次遍历一个一个往前加&#xff0c;sum sum num[i], 如果sum[i] 小&#xff0c;则替换sum 如果sum > max&#xff0c; 则max sum; int maxSubArray(int* nums, int numsSize){int max nums[0];int sum …

圆的参数方程是如何推导的?

圆的参数方程是如何推导的? 1. 圆的三种参数表示2. 三角函数万能公式3. 回到圆的参数方程1. 圆的三种参数表示 已知圆的第一种参数方程为: x 2 + y 2 = r x^2+y^2=r x2+y2=r   圆的图像如下: 通过上图,不难理解,圆的参数方程还可以用三角函数表示,也就是第二种参数表…

从零开始配置pwn环境:sublime配置并解决pwn脚本报错问题

1.sublime安装 Download - Sublime Text ──(holyeyes㉿kali2023)-[~] └─$ sudo dpkg -i sublime-text_build-4169_amd64.deb [sudo] password for holyeyes: Selecting previously unselected package sublime-text. (Reading database ... 409163 files and directori…

大创项目推荐 深度学习驾驶行为状态检测系统(疲劳 抽烟 喝水 玩手机) - opencv python

文章目录 1 前言1 课题背景2 相关技术2.1 Dlib人脸识别库2.2 疲劳检测算法2.3 YOLOV5算法 3 效果展示3.1 眨眼3.2 打哈欠3.3 使用手机检测3.4 抽烟检测3.5 喝水检测 4 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于深度学习的驾…

(超详细)9-YOLOV5改进-添加EffectiveSEModule注意力机制

1、在yolov5/models下面新建一个EffectiveSEModule.py文件&#xff0c;在里面放入下面的代码 代码如下&#xff1a; import torch from torch import nn as nn from timm.models.layers.create_act import create_act_layerclass EffectiveSEModule(nn.Module):def __init__…

使用DALL-E 3模型模拟AI女友的一天 |【人人都是算法专家】

Rocky Ding 公众号&#xff1a;WeThinkIn 知乎&#xff1a;Rocky Ding 写在前面 【人人都是算法专家】栏目专注于分享AI行业中业务/竞赛/研究/产品维度的思考与感悟。欢迎大家一起交流学习&#x1f4aa; 大家好&#xff0c;我是Rocky。 我们都知道DALL-E 3是和Stable Diffusio…

Leetcode的AC指南 —— 栈与队列:232.用栈实现队列

摘要&#xff1a; **Leetcode的AC指南 —— 栈与队列&#xff1a;232.用栈实现队列 **。题目介绍&#xff1a;请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作&#xff08;push、pop、peek、empty&#xff09;&#xff1a; 实现 MyQueue 类&#xff1a;…

【Linux系统编程二十九】基于信号量的环形队列生产消费模型

【Linux系统编程二十九】基于信号量的环形队列生产消费模型 一.信号量1.P操作2.V操作 二.环形队列三.单生产单消费场景1.信号量维持生产消费之间互斥同步 四.多生产多消费场景1.加锁维持生产生产&#xff0c;消费消费互斥 五.总结 一.信号量 当共享资源被当成整体使用时&#…

Java-NIO篇章(4)——Reactor反应器模式

前面已经讲过了Java-NIO中的三大核心组件Selector、Channel、Buffer&#xff0c;现在组件我们回了&#xff0c;但是如何实现一个超级高并发的socket网络通信程序呢&#xff1f;假设&#xff0c;我们只有一台内存为32G的Intel-i710八核的机器&#xff0c;如何实现同时2万个客户端…

PID笔记

Improving the Beginner’s PID 参考资料 Improving the Beginner’s PID – Introduction The Beginner’s PID 以下是每个人第一次学习的PID方程&#xff1a; 这导致几乎每个人都编写了以下PID控制器&#xff1a; /*working variables*/ unsigned long lastTime; double…

Mongo集群入门

一、前言 MongoDB 有三种集群架构模式&#xff0c;分别为主从复制&#xff08;Master-Slaver&#xff09;、副本集&#xff08;Replica Set&#xff09;和分片&#xff08;Sharding&#xff09;模式。 Master-Slaver 是一种主从复制的模式&#xff0c;目前已经不推荐使用。 Re…