【UnityShader入门精要学习笔记】(2)GPU流水线

在这里插入图片描述
本系列为作者学习UnityShader入门精要而作的笔记,内容将包括:

  • 书本中句子照抄 + 个人批注
  • 项目源码
  • 一堆新手会犯的错误
  • 潜在的太监断更,有始无终

总之适用于同样开始学习Shader的同学们进行有取舍的参考。


文章目录

  • 上节复习
  • GPU流水线
    • 顶点着色器
    • 裁剪
    • 屏幕映射
    • 三角形设置
    • 三角形遍历
    • 简单拓展:重心坐标系
    • 片元着色器
    • 逐片元操作
  • 总结


上节复习

在上节笔记中,我们学习了图像渲染流水线的基本过程,从应用阶段的CPU处理,输出渲染图元到几何阶段,再输出屏幕空间的顶点信息到光栅化阶段。

上节详细介绍了应用阶段,在这一阶段主要由我们人为控制,在程序中设定材质,网格,纹理,着色器等数据的渲染,并对不必要的渲染进行剔除。整个渲染流程是从硬盘加载数据到RAM再到VRAM由显卡进行调用,RAM中的数据在被调用到VRAM后就会被丢弃(除了部分用于进行物理计算的网格信息),所有工作在渲染状态中打包成数据准备好后,将由CPU进行DrawCall来通知GPU对相应的图元(primitives)进行处理。那么接下来就是GPU流水线阶段,也对应了我们后面的几何阶段和光栅化阶段。

GPU流水线

在GPU流水线阶段,开发者无法完全操控整个GPU流水线,不过GPU还是为我们提供了一些阶段的控制权,如下图所示:

在这里插入图片描述

上图可以抽象成2个大阶段,其中起点是接收应用阶段加载到显存的顶点数据,准备好了由CPU通过DrawCall调用GPU。接下来就是GPU的处理流程,几何阶段和光栅化阶段。最后处理完成输出为屏幕图像。

接下来是书中的介绍,请对照上图查看,可能初学会难懂拗口,不过没关系,先记个名字,后续会逐一介绍。

在几何阶段,顶点着色器(Vertex Shader) 是完全可编程的,它通常用于实现顶点的空间变换,顶点着色等功能。曲面细分着色器(Tessellation Shader) 是一个可选的着色器,用于细分图元。几何着色器(Geometry Shader) 也是可选的着色器, 可以被用于执行逐图元(Pre-Primitive) 的着色操作,或者被用于产生更多图元。(其实这三个阶段可以简单理解为点到线到面的处理,在我理解里几何阶段就是一种对图元在几何性质的点线面上的规划和渲染)

经历了着色器渲染后,接着进行裁剪(Clipping) ,这一阶段的目的是将那些不被渲染的顶点裁剪掉,并剔除某些三角面元的面片。这一阶段我们可配置但不可编程(也就是说我们只决定裁剪哪些,而裁剪的算法是固定的)。我们可以使用自定义的裁剪平面来配置裁剪区域,也可以通过指令控制裁剪三角图元的正面还是侧面。(接触过Shader中的Clip的同学们应该更有体会)

几何阶段的最后一个流水线是屏幕映射(Screen Mapping) ,这一阶段是不可配置和编程的,它负责把每个图元的坐标转换到屏幕坐标中,毕竟在从CPU到GPU处理这些数据的时候,它们常常不是在同一个坐标空间下的。

接着是光栅化阶段,其中的三角形设置(Triangle Setup)三角形遍历(Triangle Traversal) 阶段都是固定函数(Fixed-Function)的阶段。接下来的片元着色器(Fragment Shader) 则是完全可编程的,用于实现逐片元(Pre-Fragment) 的着色操作,在逐片元操作(Pre-Fragment Operations) 阶段负责执行很多重要的操作,例如修改颜色、深度缓冲、进行混合等,它不是可编程的,但具有很高的可配置性。

上述内容看着复杂,但是学习shader必须掌握的,接着我们要一一描述这些概念


顶点着色器

顶点着色器(Vertex Shader) 是流水线的第一个阶段,学习几何的时候总是由点到线到面嘛。它的输入来自于CPU,对每个输入的顶点都会调用一次顶点着色器。它本身不创建或销毁顶点,无法得到顶点与顶点的关系,因此它无法判断某几个点是不是同属于一个三角网格。由于其独立性,只要计算就好了,因此顶点着色器处理速度也很快。

顶点着色器主要完成的工作是:坐标变换和逐顶点光照。除此之外还可以输出后续所需的数据。下图展示了顶点着色器对顶点进行坐标变换并计算顶点颜色的过程:

在这里插入图片描述

我们可以通过坐标变换改变顶点位置,从而实现顶点动画,例如模拟水面,布料等等。但无论我们在顶点着色器中如何改变顶点的位置,一个最基本的顶点着色器必须完成的一个工作是:把顶点坐标从模型空间转换到齐次裁剪空间

在顶点着色器中可能会经常看到如下代码:

o.pos = mul(UNITY_MVP, v.position);

上述代码的功能就是将顶点坐标转化为齐次坐标,通常再由硬件做透视算法后,最终得到归一化的设备坐标(Normalized Device Coordinates,NDC)。(其实看到归一化不少同学可能就理解了,就是为了将不同坐标转化到同一个齐次坐标再进行处理嘛)

在这里插入图片描述
(上图给出的分量范围是OpenGL同时也是Unity使用的NDC,z分量范围为[-1,1]。在DirectX中z分量范围为[0,1])

(左图是模型空间,右图是NDC,齐次裁剪坐标空间是四维的)

顶点着色器可以有不同的输出方式,最常见的输出路径是经光栅化后交给片元着色器进行处理,而在现代的Shader Model中还可以将数据发送给曲面细分着色器或几何着色器。

裁剪

为什么裁剪。简单来说我们不需要渲染不在摄像机视野内的物体,因此这些部分需要被裁剪掉。

一个图元和摄像机的视野有三种位置关系:完全在视野内,部分在视野内,不在视野内。完全在视野内的处理完就传递给下一个流水线阶段,不在视野内的就不传递,因为它不显示也就不参与渲染,而部分在视野内的则需要进行裁剪处理:例如,一条线段的一个顶点在视野内,而另一个顶点不在视野内,那么在视野外部的顶点应该使用一个新的顶点来替代,这个新顶点位于线段和视野边界的交点处。

在这里插入图片描述
(上图的单位立方体代表的是NDC,而实际裁剪工作是在裁剪空间内完成的)

如上图所示,视野边界就是NDC的坐标分量的上界和下界,我们绘制NDC的单位立方体,则保留在立方体内的部分进行渲染,被立方体边缘裁剪的部分产生新顶点。

虽然我们无法通过编程来控制裁剪的过程,不过是可以自定义裁剪操作的。

屏幕映射

屏幕映射是几何阶段的最后一步,其输出将作为光栅化阶段的输入。这一步接收的输入坐标是归一化的齐次坐标。屏幕映射(Screen Mapping) 的任务是将每个图元的x和y坐标转换到屏幕坐标系(Screen Coordinates) 下。屏幕坐标系是一个二维坐标系,它和我们显示画面的分辨率相关。

实际上屏幕映射就是二维上对齐次坐标的缩放变换。屏幕坐标系的最小坐标是左下角(x1,y1),而最大坐标是右上角(x2,y2),与图元的齐次坐标系是不一样的。

在这里插入图片描述

(屏幕映射对齐次坐标进行了x和y分量上的缩放变换,并且对应的坐标也改变了,其中x1<x2且y1<y2)

那么z轴呢?z跑哪里去了?屏幕映射不会对z轴进行任何处理,屏幕坐标会和z轴一起构成一个新坐标系,称为窗口坐标系(Window Coordinates) ,这些值会被一起传递到光栅化阶段。

屏幕映射得到的屏幕坐标决定了这个顶点对应屏幕上哪个像素以及离这个像素有多远。

此外,OpenGL和DirectX的屏幕坐标也存在差异,OpenGL将屏幕左下角作为最小窗口值,而DirectX将屏幕右下角作为最小窗口值。

在这里插入图片描述
如果你发现得到的图像是倒转的,那么很有可能就是这个原因造成的。

三角形设置

三角形设置是光栅化的第一个阶段,上一个阶段屏幕映射给出了屏幕坐标系下的顶点信息和其他额外信息,例如深度值(z坐标),法线方向,视角方向等。光栅化阶段有两个重要的目标:计算每个图元覆盖了哪些像素,以及为这些像素计算它们的颜色

三角形设置会计算光栅化一个三角网格所需的信息。具体来说,在几何阶段输出的都是三角网格的顶点,即我们所得到的是三角形网格每条边的两个端点。但如果需要得到整个三角网格对像素的覆盖情况,我们就必须计算每条边上的像素坐标。为了能够计算边界像素的坐标信息,我们就需要得到三角形边界的表示方式。这样一个计算三角网格表示数据的过程叫做三角形设置。

说白了,说人话就是连线,把点连成三角形或者边。

三角形遍历

三角形遍历阶段会检查每个像素是否被一个三角网格所覆盖。如果被覆盖的话,就会生成一个片元(fragment) 。而找到哪些像素被三角网格覆盖的过程就是三角形遍历,这个阶段也被称为扫描变换(Scan Conversion) (我想是因为扫描线算法?)。

根据三角形设置的计算结果来判断每个像素是否被一个三角网格覆盖,并使用三角网格3个顶点的顶点信息对整个覆盖区域的像素进行插值。下图展示了三角形遍历阶段简化的计算过程:

在这里插入图片描述

这一步的输出结果得到一个片元序列。一个片元并不是真正意义上的像素,而是包含了很多种状态的集合,这些状态用于计算每个像素的最终颜色(最终结果)。状态包括且不限于:屏幕坐标,深度信息,以及其他从几何阶段输出的顶点信息,例如法线、纹理坐标等。

(图元和片元虽然在英文上是fragment,如果翻译成碎片很容易被误解为个体,实际上它们应当被视为多种状态的集合,一个元包括了很多的信息,而不仅仅是某个图形或者某个像素)

简单拓展:重心坐标系

推荐阅读计算机图形学 1:重心坐标系(Barycentric coordinate system)详解

上图展示的片元颜色渲染,我们看到重心插值的深度为-10。既然要计算插值,如果我们以三角形某一个顶点为原点,去构建一个直角坐标系再计算,显然并不是那么好,最简单的方案构建一个非正交的坐标系,这个坐标系就是重心坐标系,请看下图:

在这里插入图片描述
上图三角形,以abc代表顶点。假设我们以点 a a a为原点,那么 a b → = b − a \overrightarrow {ab} = b-a ab =ba a c → = c − a \overrightarrow {ac} = c-a ac =ca。如果以 a b → , a c → \overrightarrow {ab} ,\overrightarrow {ac} ab ac 作为基向量建立坐标系。 p p p点为三角形的重心,设 p p p点的坐标为 ( β , γ ) (\beta,\gamma) (β,γ),那么点 p p p的坐标表示即为:
p = a + β a b → + γ a c → p= a + \beta \overrightarrow {ab} + \gamma \overrightarrow {ac} p=a+βab +γac
p = a + β ( b − a ) + γ ( c − a ) p= a + \beta (b-a) + \gamma(c-a) p=a+β(ba)+γ(ca)
p = ( 1 − β − γ ) a + β b + γ c p= (1-\beta - \gamma)a + \beta b+ \gamma c p=(1βγ)a+βb+γc
因此令 ( 1 − β − γ ) = α (1-\beta - \gamma) = \alpha (1βγ)=α 即为:
p ( α , β , γ ) = α a + β b + γ c p(\alpha,\beta,\gamma)= \alpha a + \beta b+ \gamma c p(α,β,γ)=αa+βb+γc
其中 α + β + γ = 1 \alpha+\beta +\gamma = 1 α+β+γ=1

好了,这样重心坐标系就建立好了。在这个坐标系下,三角形内任意一点的位置可视为三个顶点的线性组合,通过重心坐标系我们可以很简单判断某个点是否在三角形内部,只需
0 < α < 1 , 0 < β < 1 , 0 < γ < 1 0<\alpha<1,\newline 0<\beta<1,\newline 0<\gamma<1\newline 0<α<1,0<β<1,0<γ<1即可
α = β = γ = 1 3 \alpha = \beta = \gamma = \frac{1}{3} α=β=γ=31时则为重心位置。


片元着色器

片元着色器(Fragment Shader) 是另一个非常重要的可编程着色器阶段,在DirectX中,片元着色器也被称为像素着色器(Pixel Shader) ,但是我们说过片元包含像素,但不等同于像素,所以片元着色器是更适合的名字。

前面的光栅化阶段实际并不影响屏幕上每个像素颜色,而是产生一系列数据信息,用于描述一个三角形网格是怎样覆盖每个像素的,而每个片元负责存储这一系列信息。真正会对像素产生影响的是逐片元操作(Pre-Fragment Operation)阶段

片元着色器的输入是三角形遍历阶段对顶点信息进行插值后得到的结果,更具体的说是对顶点着色器的输出数据进行插值后得到的。而片元着色器的输出是一个或多个的颜色值,如下图所示。
在这里插入图片描述

在这一阶段会完成许多重要的渲染技术,其中最重要的技术之一就是纹理采样,为了在片元着色器进行纹理采样,我们会在顶点着色器阶段输出每个顶点对应的纹理坐标,经过插值之后就能得到其覆盖的每个片元的纹理坐标了。

虽然片元着色器很重要,但其局限性在于仅能影响单个片元。也就是说当执行片元着色器时,它不可以将自己的任何结果直接发送给它的邻居(相邻的其他片元)。除了当片元着色器可以访问到导数信息(gradient或者说derivative)时例外(本章拓展阅读部分补充)。

逐片元操作

最后一步是逐片元操作,在DirectX中称为输出合并阶段(Output-Merger)。最主要的目的还是Merge合并,合并的目标就是每一个片元。

这一阶段有几个主要任务:

  1. 决定每个片元的可见性。这涉及很多测试工作,例如深度测试、模板测试等。
  2. 如果一个片元通过了所有的测试,就需要把这个片元的颜色值和已经存储在颜色缓冲区中的颜色进行合并,或者说是混合。

首先要进行的就是片元测试,这些测试决定了哪些片元可以被渲染,哪些片元会被舍弃。简单来说就是一个资格考试,淘汰不合格的片元。只要有一个测试没通过就会被舍弃,之前为这个片元做的一切工作都会白费。只有通过测试的片元才有资格和颜色缓冲区合并。

在这里插入图片描述
书中给出了模板测试和深度测试的简化流程图:
在这里插入图片描述

片元所需要经历的模板测试和深度测试这两大测试,都是可以由开发者自行配置的。在模板测试中,GPU首先读取(使用读取掩码)模板缓冲区中该片元位置的模板之,然后将该值与参考值(也使用读取掩码读取)进行比较判断是否舍弃(舍弃条件可以是小于或者大于等于)。然后根据模板测试和深度测试结果来修改模板缓冲区。这个修改操作也是由开发者指定的,模板测试通常用于限制渲染的区域,另外还有例如渲染阴影,轮廓渲染等高级用法。

如果片元通过模板测试,那么还会进行深度测试。这个测试同样是高度可配置的,GPU会将片元的深度值与深度缓冲区的深度值进行比较。比较舍弃和上述模板测试一样可以定义,通常是小于等于保留,大于等于舍弃。因为我们想渲染离摄像机更近,深度更低的物体。与模板测试不同的是,模板测试在保留或舍弃时都可以修改模板缓冲区,但是如果一个片元没有通过深度测试,它没有权利更改深度缓冲区的值,如果它通过了测试,开发者可以指定是否用这个片元的深度值覆盖掉原有片元的深度值,这是通过开启/关闭深度写入做到的。透明效果和深度测试以及深度写入的关系非常密切。

最后这些片元需要被合并。现在模板缓冲区已经修改了,深度缓冲区也进行了对应操作。其实所谓的渲染过程是一个物体一个物体地画到屏幕上的,因此我们还需要对像素颜色进行处理,而每个像素的颜色信息都被存储在一个名为颜色缓冲区的地方。当我们执行完这次渲染后,相同位置的颜色缓冲已经有了上次处理的结果,那么这次是直接覆盖?还是其它操作?这就是合并需要解决的问题。

例如对于不透明的物体,我们可以关闭混合(Blend) 操作,让颜色直接覆盖颜色缓冲。而对于半透明物体,我们需要混合颜色像素让其看起来像是透明的。
在这里插入图片描述

混合操作也是高度可配置的,我们可以选择是否开启混合,如果开启混合,GPU就会去除源颜色和目标颜色,源颜色指的是片元着色器得到的颜色值,而目标颜色是已经存在于颜色缓冲区的颜色值。之后使用一个混合函数来进行混合操作,这个混合函数与透明通道息息相关,例如根据透明通道的值进行相加、相减、相乘等。


那么我们就有疑问了,既然有的片元会在测试阶段被舍弃,这样不是很浪费吗?那么为什么不先进行测试再进行渲染呢?这样不是可以提高性能吗?就像下图的例子一样:
在这里插入图片描述
(上图先渲染球再渲染长方体,但是由于球先被渲染且深度更低,因此长方体大部分片元无法通过深度测试,对这些片元执行片元着色器造成了很大的性能浪费)

提前进行测试当然是可以的,例如文中就提到了可以提前进行深度测试的技术(Early-Z)。但是如果将测试提前的话,其检验结果可能与片元着色器中的一些操作冲突。例如,如果片元着色器中的代码进行了透明度测试,而片元没能通过,那么它将在着色器中被调用API(例如clip函数)被手动舍弃。这就导致GPU无法提前进行各种测试。因此,现代的GPU会判断片元着色器中的操作是否和提前测试冲突了,如果有冲突就会禁用提前测试。这样反而导致性能下降了,本来可以提前测试,由于添加了透明度测试反而不能提前测试了。

最终图元被渲染完成后会被呈现在屏幕上,我们的屏幕显示的就是颜色缓冲区中的颜色值。但是,为了避免我们看到那些正在进行光栅化的图元,GPU会使用双重缓冲(Double Buffering) 的策略,这意味着,对场景的渲染是在幕后发生的,即在后置缓冲(Back Buffer) 中。一旦场景被渲染到了后置缓冲中,GPU就会交换后置缓冲区和前置缓冲(Front Buffer) 中的内容。而前置缓冲区是之前显示在屏幕上的图像。因此保证了我们看到的图像总是连续的。(巧妙的方法,避免了渲染导致的画面不连续问题)

总结

虽然上述流程描述了很多(其实曲面细分着色器和几何着色器都没讲),但实际过程要更加复杂。当然上述内容与其他资料会产生差异,这是由于图像编程接口的实现不尽相同,而GPU在底层也做了很多优化。基本原理都是融会贯通的,未来可在学习Games101或者RTR4时重拾。

在Unity中为我们封装了很多功能,更多时候我们只需要在一个Unity Shader设置一些输入,编写顶点着色器和片元着色器,设置一些状态就能达到大部分常见的屏幕效果。(更别说现在Unity提供了URP和SRP等渲染管线)

虽然概念生疏,但是坚持才是胜利。无论经历了什么,选择了什么,既然选择了从事这个行业,那么都应该贯彻到底。勉励自己,也勉励诸位。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【git使用】一个非常适合新手的代码管理方法——master/develop模型

生产/开发模型【支持master/develop 类型分支】 1.gitee、github创建仓库2.使用sourcetree拉取仓库3. 检出develop分支&#xff0c;并初始化工作流4.模拟日常开发流程5.具备发布条件&#xff0c;进行发布6.将master和develop分支的版本都推送到远程仓库7.继续循环4-5-6进行开发…

Python 面向对象之多态和鸭子类型

Python 面向对象之多态和鸭子类型 【一】多态 【1】概念 多态是面向对象的三大特征之一多态&#xff1a;允许不同的对象对同一操作做出不同的反应多态可以提高代码的灵活性&#xff0c;可扩展性&#xff0c;简化代码逻辑 【2】代码解释 在植物大战僵尸中&#xff0c;有寒冰…

数据结构和算法-希尔排序(增量序列 算法实现 性能分析 稳定性)

文章目录 希尔排序过程小结增量序列不是固定的 算法实现算法性能分析稳定性小结 希尔排序 基本有序&#xff0c;就是存在有序的子序列 通过增量4得到各个子表 对各个子表分别进行插入排序 缩小增量&#xff0c;再除2&#xff0c;此时的子表 对各个子表插入排序 缩小增量&…

STC进阶开发(四)SPI协议、矩阵键盘、EEPROM

前言 这一期我们简单介绍一下SPI协议&#xff0c;然后我们学习一下矩阵键盘&#xff0c;了解EEPROM是干什么用的&#xff0c;话不多说&#xff0c;开整&#xff01; SPI协议 SPI&#xff08;Serial Peripheral Interface&#xff09;是一种同步串行通信协议&#xff0c;用于在…

OJ刷题 第十七篇()

34005 - 汽水瓶&#xff08;有意思&#xff09; 时间限制 : 1 秒 内存限制 : 128 MB 有这样一道智力题&#xff1a;“某商店规定&#xff1a;三个空汽水瓶可以换一瓶汽水。小张手上有十个空汽水瓶&#xff0c;她最多可以换多少瓶汽水喝&#xff1f;”答案是5瓶&#xff0c;方…

Python 测试框架 Pytest 入门

简介 pytest 是一个功能强大而易于使用的 Python 测试框架。它提供了简单的语法和灵活的功能&#xff0c;用于编写和组织测试代码。 1、简单易用&#xff1a;pytest 的语法简洁明了&#xff0c;使得编写测试用例更加直观和易于理解。它使用 assert 语句来验证预期结果&#x…

SpringBoot内嵌的Tomcat启动过程以及请求

1.springboot内嵌的tomcat的pom坐标 启动后可以看到tomcat版本为9.0.46 2.springboot 内嵌tomcat启动流程 点击进入SpringApplication.run()方法里面 看这次tomcat启动相关的核心代码refreshContext(context);刷新上下文方法 public ConfigurableApplicationContext run(Stri…

银行卡号识别

导入库 from typing import Any, Union, Sequencefrom cv2.mat_wrapper import Mat from imutils import contours import numpy as np import argparse import imutils import cv2 import myutils设置参数 # 设置参数 from numpy import dtype, ndarray, genericap argpars…

“巴渝工匠杯”2022年重庆市职业院校技能大赛(高职组)云计算样题

“巴渝工匠杯”2022年重庆市职业院校技能大赛&#xff08;高职组&#xff09;云计算样题 需要软件包环境可私信博主 【赛程名称】云计算赛项第一场次-私有云 某企业拟使用OpenStack搭建一个企业云平台&#xff0c;以实现资源池化弹性管理、企业应用集中管理、统一安全认证和授…

Visual Studio 2022进行文件差异比较

前言 Visual Studio 2022在版本17.7.4中发布在解决方案资源管理器中比较文件的功能&#xff0c;通过使用此功能&#xff0c;可以轻松地查看两个文件之间的差异&#xff0c;包括添加、删除和修改的代码行。可以逐行查看差异&#xff0c;并根据需要手动调整和编辑文件内容以进行…

易图讯便携式三维电子沙盘实战应用系统

便携式三维电子沙盘采用军工加固三防高性能笔记本&#xff0c;具有IP65级防尘防水防摔性能&#xff0c;以大数据、云计算、虚拟现实、物联网、AI等先进技术为支撑&#xff0c;支持高清卫星影像、DEM高程数据、矢量数据、三维模型、倾斜摄像、BIM、点云、城市白模、等高线、标高…

数据结构学习 jz56数组中数字出现的次数

关键词&#xff1a;位运算 异或性质 虽然有两道题&#xff0c;但是其实应该分成三个级别的题目。 题目一&#xff1a; 一个整型数组 sockets 里除 一个 数字之外&#xff0c;其他数字都出现了两次。 思路&#xff1a;异或的性质 复杂度计算&#xff1a; 时间复杂度O(n) 空…

在Go语言中处理HTTP请求中的Cookie

在Web开发中&#xff0c;Cookie是一种常用的技术&#xff0c;用于在客户端存储数据&#xff0c;并在随后的请求中发送回服务器。Go语言的标准库提供了强大的支持来处理HTTP请求中的Cookie。 首先&#xff0c;让我们了解如何在Go语言中设置Cookie。以下是一个简单的示例&#x…

three.js场景设计器-小地图的视角参考功能

three.js实现场景方向的左上角小地图 思路 1&#xff1a;创建单独场景 2.添加辅助线 3.添加坐标轴的XYZ文字-使用sprite实现 4.旋转主视图时同步相机位置到小地图。 <template> <div ref"miniMapContainer" class"mini-map"></div>…

Apache Commons Email在邮件发送中的应用

第1章&#xff1a;简介 大家好&#xff0c;我是小黑&#xff0c;今天咱们聊聊Apache Commons Email这个库&#xff0c;它在发送邮件方面可谓是小而美的利器。Apache Commons Email基于JavaMail API&#xff0c;但它提供了更简洁、更易用的接口&#xff0c;让咱们在处理电子邮件…

如果PostgreSQL有两层nginx代理,会发生什么事?

转载说明&#xff1a;如果您喜欢这篇文章并打算转载它&#xff0c;请私信作者取得授权。感谢您喜爱本文&#xff0c;请文明转载&#xff0c;谢谢。 1. 前言 PostgreSQL默认只能本机连接&#xff0c;若要在别的客户端远程连接pgsql&#xff0c;则需要修改配置文件pg_hba.conf&a…

CommonJS 和 ES6 Module:一场模块规范的对决(下)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

QML —— SwipeView、PageIndicator组合示例(附完整源码)

示例效果 介绍 SwipeView提供了一个基于滑动的导航模型,由一组页面组成。一次只能看到一个页面。用户可以通过横向滑动在页面之间导航。请注意,SwipeView本身是完全不可见的。建议将其与PageIndicator结合使用,以向用户提供有多个页面的视觉线索。 PageIndicator用于指示包含…

UG装配-沿线运动

如果希望图中圆柱销沿着槽运动&#xff0c;直接约束面是困难的&#xff0c;我们可以画出圆弧的中心线和圆柱销的中心点&#xff0c;约束点在线上&#xff0c;进行移动 需要注意的是&#xff0c;我们在零件中画点和线的时候&#xff0c;在装配体默认加载模型引用集的时候是无法显…

生活中的物理3——神奇陷阱(随机倒下的抽屉柜门)

1实验 材料&#xff1a;大自然&#xff08;风&#xff09;、抽屉门松掉的抽屉 实验 1、找一个大风的日子&#xff0c;打开窗户&#xff08;不要找下雨天&#xff0c;不然你会被你亲爱的嫲嫲KO&#xff09; 2、让风在抽屉面前刮过 3、你发现了什么&#xff1f;&#xff1f;&…