深入探索透视纹理映射(下)

2023-11-19

在上一篇文章中,我们探讨了学习透视纹理映射所需要的基础知识。我们知道了顶点在通过透视投影变换之后,是如何一步一步通过流水线进入屏幕空间的。也知道了一个非常简单的三角形扫描线转换算法,以及通过线性插值实现的仿射纹理映射。尽管我们使用的这个流程非常的直接、简洁,还有大量的细节没有添加(片元操作、雾化、颜色累加、混合等等等等),但这些真的就是组成一个固定流水线的简单光栅器的基本步骤了。但我们目前所提及的光栅化算法完全局限于屏幕空间——我们完全没有考虑进入屏幕空间之前的转换过程,只是在屏幕空间里面对纹理坐标玩弄线性插值。可正如我所说的,仿射纹理映射所基于的假设是不对的,对纹理坐标本身做线性插值也是不对的。那么,错在什么地方呢?我们来分析一下。

仿射纹理映射错在什么地方?

到底错在什么地方呢?我们再来看看我们上一篇的仿射纹理映射算法,我们把其中的一部分伪代码实现出来:

 

 

double x, y, xleft, xright;

double s, t, sleft, sright, tleft, tright, sstep, tstep;

for(y = y0; y < y1; ++y)

{

       xleft = y和左边的直线方程来求出左边的x

       xright = y和右边的直线方程来求出右边的x

       sleft = (y – y0) * (s1 – s0) / (y1 – y0) + s0;

       sright = (y – y0) * (s2 – s0) / (y2 – y0) + s0;

       tleft = (y – y0) * (t1 – t0) / (y1 – y0) + t0;

       tright = (y – y0) * (t2 – t0) / (y2 – y0) + t0;

       sstep = (sright – sleft) / (xright – xleft);

       tstep = (tright – tleft) / ( xright – xleft);

       for(x = xleft, s = sleft, t = tleft; x < xright;

++x, s += sstep, t += tstep)

       {

               帧缓冲像素[x, y] = 纹理[s, t];

       }

}

 

请注意,在上面的算法中,我们计算sleftsright以及tlefttright的时候,是做了关于y的线性插值。

 

 

这表明在y方向上,纹理坐标st的变化和y的变化是按照线性、均匀的方式处理的。另外,纹理坐标st的扫描线步长ssteptstep的计算,是根据扫描线的长度平均分配纹理变化量,也是按照线性、均匀的方式处理的。但是问题在于:投影平面上的线性关系,还原到空间中,就不是那么回事了,这还要从透视投影那段说起,请看下图。

 

 

 

这张图是相机空间的一张俯视图。我们把一个多边形通过透视投影的方式变换到了投影平面上,图中红色的是空间中的多边形,蓝色的是变换到投影平面之后的多边形。现在我们暂时在投影面上插值,而不在视口中,后面我们会把结论推广到视口中,而上面那个算法放在投影平面上同样适用。可以看到,在投影平面上的蓝色线段被表示成若干个相等的单位步长线段,相当于我们在上面的算法中递增扫描线位置的步骤——“++x”。而同时也可以看到,投影面上单位步长的线段所对应的投影之前的红色线段的长度却不是相等的,从左到右所对应的长度依次递增。而实际上,我们的纹理坐标是定义在红色的多边形上的,因此纹理坐标的增量应该是和红色线段的步长对应的。但我们的线性插值却把纹理坐标增量根据蓝色线段的步长平均分配了,就是

 

sstep = (sright – sleft) / (xright – xleft);

tstep = (tright – tleft) / ( xright – xleft);

 

这两步。此外在y方向上的插值slefttleftsrighttright全部都是这样处理的——全部都是错误的!则我们得出的结论是:投影平面上的xy和纹理坐标st不是线性关系。即

 

 

 

说了这么半天,我们还没看过仿射纹理映射和透视纹理映射到底差在哪里。下面这张图展示了使用仿射纹理映射导致的错误渲染:

 

 

 

左边是让多边形和投影平面平行时候的渲染,这个时候没有任何问题。右边两个是让多边形和投影平面倾斜一定角度,可以看到中间的仿射纹理映射出现了渲染错误——纹理扭曲了——直接对纹理坐标使用线性插值的结果。右边是使用带透视校正的透视纹理映射的效果,不错吧?

以上我们从几何直观上感性地认识了仿射纹理映射的错误,现在,我们要从理性上认识它的错误——从数学上来推导正确的方式。

透视纹理映射的数学推导

这个题目看起来有点严肃。但是请放松,只要掌握了第一篇提到的线性关系和线性插值的理论,并且理解透视投影变换,你完全能够理解这些推导,并把它应用到自己需要解决的问题当中。我们先从最原始的透视投影关系开始推导纹理映射,然后再考虑完整的透视投影变换矩阵下的透视纹理映射关系(二者其实是一样的,但我要证明给你看)。还是来看我们在推导透视投影变换的时候用到的关系图:

 

 

 

上图是在相机空间的俯视图,eye是眼睛的位置,也就是原点。npfp分别是近、远裁剪平面,NF分别是z=0到两个裁剪平面的距离。pq是一个三角形pqrxy平面上的两个点,p的坐标为(x, y, z),p’ p投影之后的点,坐标为(x’, y’, z’),则有

 

 

 

 

这个结果就是我们在《深入探索透视投影变换》中所说的野蛮的、原始的投影目的(90年代透视投影)。另外,在相机空间中,三角形pqr是一个平面,因此它内部的每一条边上的xz,以及yz都是线性关系,即

 

 

 

这样,把上面投影之后的结果(1)带入这个线性式(2)(为了书写方便,现在开始我只处理x方向计算,y的情况一致),有

 

 

 

则我们通过这个式子推出了投影之后的x’和原始z之间的关系——x’1/z是线性关系,y’1/z也是线形关系。现在回忆我们上一篇文章中讲道的线性插值理论,我们可以说:因为x’y’1/z是线形关系,因此我们可以在投影面上通过x’y’1/z进行线性插值。至此我们可以得到这样的透视纹理映射思路:在投影平面上通过x’y’1/z线性插值,计算出1/z后,通过上面的(1)式计算出原始的xy,然后在3D空间中通过xy计算出stxyst都是在3D空间中的三角形上定义的,是线性关系)。这样就找到了投影面上一个点所对应的纹理坐标的正确值了。这个思路没有问题,可以正确的解决透视纹理映射问题了。算法修改如下:

 

double x, y, xleft, xright; // 插值xy,左右线段x

double oneoverz_left, oneoverz_right; // 左右线段1/z

double oneoverz_top, oneoverz_bottom; // 上下顶点1/z

double oneoverz, oneoverz_step;   // 插值1/z以及扫描线1/z步长

double originalx, originaly, originalz; // 空间中的原始xyz

double s, t; // 要求的原始st

for(y = y0; y < y1; ++y)

{

       xleft = y和左边的直线方程来求出左边的x

       xright = y和右边的直线方程来求出右边的x

       oneoverz_top = 1.0 / z0;

       oneoverz_bottom = 1.0 / z1;

       oneoverz_left = (y – y0) * (oneoverz_bottom – oneoverz_top) / (y1 – y0) + oneoverz_top;

       oneoverz_bottom = 1.0 / z2;

       oneoverz_right = (y – y0) * (oneoverz_bottom – oneoverz_top) / (y2 – y0) + oneoverz_top;

       oneoverz_step = (oneoverz_right – oneoverz_left) / (xright – xleft);

       for(x = xleft, oneoverz = oneoverz_left; x < xright;

++x, oneoverz += oneoverz_step)

       {

              originalz = 1.0 / oneoverz;

              originalx = -x * originalz / N;

              originaly = -y * originalz / N;

              originalxoriginaly以及originalz在空间中通过线性插值找到相应的st

              帧缓冲像素[x, y] = 纹理[s, t];

       }

}

上面的算法根据x’y’1/z进行线性插值,是完全正确的,因为它们是线性关系。在第一层循环中,通过插值计算出左边线段的1/z和右边线段的1/z。然后在第二层循环中计算扫描线上的每一个1/z——oneoverz。接着把1/z取倒数得到原始z,用上边的(1)式计算出原始xy,此时就得到了扫描线上一点所对应的原始3D点,用这个点关于原始的P0P1P2三个点在空间做线性插值(空间中这些量都是线性的)就可以得到当前点的纹理坐标[st]。这就是一个简单、正确的透视纹理映射算法!

看起来还不错,我们已经找到了正确的透视纹理映射方法,但是上面的算法中有个地方似乎写得有点模凌两可:

 

originalxoriginaly以及originalz在空间中通过线性插值找到相应的st

 

这个步骤是正确的,但是有一个问题——计算次数太多了,有些繁琐——我们还需要在空间中再进行几次线性插值才能得到想要的东西。有没有更简单的方式呢?当然了!

我们注意到,在空间中,xyst都是线性的(因为三角形是平面),所以有关系

 

 

 

把(4)带入(1),有

 

 

 

把(3)带入上式的中间项,得到(常数都进行合并)

 

 

 

我们发现s/zt/zx’y’也是线性关系。而我们之前知道1/zx’y’是线性关系。则我们得出新的思路:对1/z关于x’y’插值得到1/z’,然后对s/zt/z关于x’y’进行插值得到s’/z’t’/z’,然后用s’/z’t’/z’分别除以1/z’,就得到了插值s’t’。这样就不用空间中的插值步骤了!我们看看这个算法:

 

double x, y, xleft, xright; // 插值xy,左右线段x

double oneoverz_left, oneoverz_right; // 左右线段1/z

double oneoverz_top, oneoverz_bottom; // 上下顶点1/z

double oneoverz, oneoverz_step;   // 插值1/z以及扫描线步长

double soverz_top, soverz_bottom; // 上下顶点s/z

double toverz_top, toverz_bottom; // 上下顶点t/z

double soverz_left, soverz_right; // 左右线段s/z

double toverz_left, toverz_right; // 左右线段t/z

double soverz, soverz_step; // 插值s/z以及扫描线步长

double toverz, toverz_step; // 插值t/z以及扫描线步长

double s, t; // 要求的原始st

for(y = y0; y < y1; ++y)

{

       xleft = y和左边的直线方程来求出左边的x

       xright = y和右边的直线方程来求出右边的x

       oneoverz_top = 1.0 / z0;

       oneoverz_bottom = 1.0 / z1;

       oneoverz_left = (y – y0) * (oneoverz_bottom – oneoverz_top) / (y1 – y0) + oneoverz_top;

       oneoverz_bottom = 1.0 / z2;

       oneoverz_right = (y – y0) * (oneoverz_bottom – oneoverz_top) / (y2 – y0) + oneoverz_top;

       oneoverz_step = (oneoverz_right – oneoverz_left) / (xright – xleft);

       soverz_top = s0 / z0;

       soverz_bottom = s1 / z1;

       soverz_left = (y – y0) * (soverz_bottom – soverz_top) / (y1 – y0) + soverz_top;

       soverz_bottom = s2 / z2;

       soverz_right = (y – y0) * (soverz_bottom – soverz_top) / (y2 – y0) + soverz_top;

       soverz_step = (soverz_right – soverz_left) / (xright – xleft);

       toverz_top = t0 / z0;

       toverz_bottom = t1 / z1;

       toverz_left = (y – y0) * (toverz_bottom – toverz_top) / (y1 – y0) + toverz_top;

       toverz_bottom = t2 / z2;

       toverz_right = (y – y0) * (toverz_bottom – toverz_top) / (y2 – y0) + toverz_top;

       toverz_step = (toverz_right – toverz_left) / (xright – xleft);

       for(x = xleft, oneoverz = oneoverz_left,

              soverz = soverz_left, toverz = toverz_lef,t

              x < xright; ++x, oneoverz += oneoverz_step,

              soverz += soverz_step, toverz += toverz_step)

       {

              s = soverz / oneoverz;

              t = toverz / oneoverz;

              帧缓冲像素[x, y] = 纹理[s, t];

       }

}

上述算法对1/z以及s/zt/z进行线性插值,得到结果之后就地相除,得到了插值点对应的原始纹理坐标,避免了在空间中再次插值,实现了正确的透视纹理映射。可以看到透视纹理映射实质上使用的仍然是线性插值,但关键点在于找到了投影前后具有正确线性关系的几个量。此外,可以看到这个算法的性能还有很大的提升空间,我们在后面还会提到这一点。

推广到视口

前面我们推导这个算法的时候使用的是野蛮版本透视投影关系,但实际我们在流水线中使用的透视投影矩阵是经过了CVV规划的版本,也就是我们在《深入探索透视投影变换》一文中导出的最终矩阵。如果使用这个最终矩阵,会不会对上面的算法有所影响呢?答案是不会。我在前面说过要证明一下(如果你对这个证明不感兴趣,可以直接跳到下一节)。我们在投影平面上的这个透视投影算法其实有两个关键点,只要满足了这两个关键点,算法就是正确的。

 

1)最终投影点xy1/z是线性关系

2)最终投影点xys/zt/z是线性关系

 

我们已经证明了投影点

 

 

 

1/zs/zt/z是线性关系(上面的推导)。我们的最终投影点应该是在CVV中的(如果对此感到迷惑,请参考《深入探索透视投影变换》),我们要把目前的x’y’变换到CVV[-1, 1]中,得到最终的投影点,这是通过线性插值得到的,也就是

 

 

 

其中Ax’+BAy’+B是最终的投影点。因为x’1/zs/zt/z是线性关系,而Ax’+Bx’是线性关系,则根据线性关系的传递性,Ax’+B1/zs/zt/z是线性关系,Ay’+B同理,从而证明了(1)(2)。此时就证明了:用最终的透视投影变换得到的最终投影点也是满足这个算法的。至此我们在投影平面和CVV中都证明了这个透视纹理映射算法的正确性。

    下一个要证明的就是从CVV通过视口变换,进入到视口中的图元点,是否也可以使用这个算法。其实稍微想一下就知道,视口变换本身就是一个线性变换(请参考上一篇文章的视口变换一节),因此对于上面推导出的CVV中的投影点

 

 

 

进行视口变换不过就是对它们再次进行线性插值

 

 

 

根据线性关系的传递性,这两个点和1/zs/z以及t/z也是线性关系。则算法在视口中同样适用。所有证明完毕。

意外知识收获——w缓冲

一些题外话。不知道你想过没有,仿射纹理映射算法不仅可以用来计算st,还可以用来计算z值。由于同样的原因,得到的z值也是不正确的,但仿射计算效率比较高。另外,因为z只是用来决定遮挡关系,虽然数值上是错误的,但先后的顺序影响不大,所以大多流水线计算z缓冲时候都用这种仿射方法。而我们在透视纹理映射算法中计算出来的1/z,却获得了数值正确的深度值,使用这种正确的1/z的缓冲叫做w缓冲(也叫OOZ缓冲、One Over Z缓冲、1/z缓冲),但并不是所有的图形硬件都支持这种缓冲——有些只能靠软件来实现。关于z缓冲和w缓冲的一些知识和使用经验,Steve Baker的文章《Learning to love your z_buffer》值得一看。

Perspective Texture Mapping》导读

上面我们通过数学推导,实现了一个正确的透视纹理映射算法。实际上,实现一个完整的软件光栅器还有很多的事情要做。但至少你已经找到了一把打开这扇门的钥匙——我们在核心层面上已经掌握了透视纹理映射技术——现代光栅器的核心。另外,如果你真的要实现一个软件光栅器,我给你推荐Chris Hecker的系列文章《Perspective Texture Mapping》。我们这里的很多知识,都是来源于这个系列。另外,Chris Hecker的好朋友Michael Abrash,有一个系列文章叫做《Ramblings in Realtime》(我管它叫《Quake技术内幕》),里面记载了他和John Carmack一起研制Quake时候关于技术的方方面面。其中就提到了关于Quake的透视纹理映射,也是基于Chris Hecker在文章中所提到的技术。因此,可以说Quake引擎中的透视纹理映射就是使用这样的插值技术实现的。《Perspective Texture Mapping》中使用了很多非常棒的技巧,比如三角形的整体坡度计算,可以不用像我们上面的算法中,每次都重复计算三角形内部的一些增量。还有像素的填充规则,这个是非常重要的光栅化技巧,没有填充规则,模型的很多部分都会重复绘制或者无法被绘制。基于误差项的前向微分的DDA迭代方法,避免了浮点数运算等等。他把一个简单的透视纹理映射光栅器进行了一次又一次的优化、升级,最终写成一个能够实际运用到游戏中的软件渲染器。下面就是关于这个系列文章的导读,对你理解这个系列应该有所帮助。

第一章Chris Hecker完全用浮点数进行透视纹理映射,然而因为浮点数强制转换成整数速度比较慢,因此在第二章对光栅化采用了带有误差项的前向微分的DDA方式,同时将所有三角形顶点在光栅化初始阶段变成了整数形式,在速度上有所提升。但就是因为这个整数转换,导致三角形的整体梯度计算在纯整数范围产生较大变化量,出现了纹理抖动情况。故在第三篇文章引入了28.4的定点数处理三角形梯度计算,从而解决了这个问题。但同时又发现一个新的纹理坐标问题:纹理坐标在插值后是个小数,不是整数。这个透视纹理映射器在光栅化的时候直接把当前像素的纹理坐标截断成了整数,从而使所有纹理坐标都落到了小于或等于它的整数上,但像素不是一个点,而是一个边长为1的方块。从而使得下边这样的情况下

 

 

 

C落入了N-1像素上,而D落在N像素上,但根据位置关系,CD都应该属于N纹理像素上。这就需要把小数纹理坐标转换成整数纹理坐标的约定。两个约定方式

 

 

 

都可以实现把CD局限在N纹理像素上,但二者的区别在于,当u正好落在两个纹理像素的边界上时,前者会把坐标右移,而后者会把坐标左移。从而产生了左上舍入和右下舍入两种模式。如果纹理坐标在[0, 0][TextureWidth, TextureHeight],用这两种方式都可以。但纹理像素和屏幕像素一样是边长为1的方块,不是点,而同时屏幕像素的范围是[-0.5, -0.5][ScreenWidth-0.5, ScreenHeight-0.5],因此纹理像素坐标应该是[-0.5, -0.5][TextureWidth-0.5, TextureHeight-0.5]。这样就产生了一个问题:如果在纹理坐标左边界u= -0.5使用右下舍入或者在右边界u=TextureWidth-0.5使用左上舍入,则纹理坐标会越界。因此需要根据不同情况采用这两种舍入约定。这一点在文章附带的代码文件GRADIENT.TXT中给出了一个具体的实现方案。

在第四篇文章中对光栅器进行了性能剖析,发现速度瓶颈主要在于计算扫描线中1/z这个除法上(考虑文章发表在90年代)。因此需要对扫描线算法进行改进。这一点可以从视口x和采样纹理坐标的关系出发,它们的关系是如下一个图形:

 

 

 

可以通过三种办法来实现这个优化:

(1)       固定z的直线方法:找到多边形的一个特殊方向,在这个方向上,所有投影后的片元的z值都相等。这样就在一个非轴对齐的扫描线上进行纹理坐标线性插值(DOOM使用的就是这个方法)。

(2)       用二次曲线去逼近上述图形。

(3)       用分段仿射纹理映射的方法。对每一行扫描线,取固定长度线段用仿射方式作近似,可以达到一个非常逼近上述图形的曲线。

第四篇文章最终选择了用第三种方法来优化程序。第五篇文章使用了终极武器——汇编语言的方式作了最终优化,把这个软件光栅器优化到了一个能够在实际项目中使用的程度(考虑90年代个人计算机硬件能力)。

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

深入探索透视纹理映射(下) 的相关文章

  • C++17中utf-8 character literal的使用

    一个形如42的值被称作字面值常量 literal 这样的值一望而知 每个字面值常量都对应一种数据类型 字面值常量的形式和值决定了它的数据类型 由单引号括起来的一个字符称为char型字面值 双引号括起来的零个或多个字符则构成字符串型字面值 字
  • 两万字带你认识黑客在kali中使用的工具

    目录 前言 一 信息收集工具 二 脆弱性分析工具 三 漏洞利用工具 四 嗅探与欺骗工具 五 密码攻击工具 六 权限提升工具 七 Web应用工具 八 无线攻击工具 九 硬件黑客工具 十 维持访问工具 十一 取证工具 十二 逆向工程工具 十三
  • Linux动态链接库.so文件的创建与使用

    1 介绍 使用GNU的工具我们如何在Linux下创建自己的程序函数库 一个 程序函数库 简单的说就是一个文件包含了一些编译好的代码和数据 这些编译好的代码和数据可以在事后供其他的程序使用 程序函数库可以使整个程序更加模块化 更容易重新编译
  • Qt 简述QSettings配置文件保存使用数据

    前言 在开发中 需要将一些信息保存到本地 以便下次程序启动时使用 文件读写 数据库都是可以的 但是Qt提供了QSettings接口方法 将需要的信息写入或者读取配置文件中 其方法类似键值对 QSettings可以存储一系列设置 每个设置包括
  • 探讨Socks5代理IP在跨境电商与网络游戏中的网络安全应用

    随着全球互联网的迅猛发展 跨境电商和在线游戏成为了跨国公司和游戏开发商的新战场 然而 与此同时 网络安全问题也日益突出 本文将探讨如何利用Socks5代理IP来增强跨境电商和网络游戏的网络安全 保障数据传输的隐私和安全性 第一部分 Sock
  • 五分钟,带你彻底掌握 MyBatis 缓存的工作原理

    点击上方 芋道源码 选择 设为星标 管她前浪 还是后浪 能浪的浪 才是好浪 每天 8 55 更新文章 每天掉亿点点头发 源码精品专栏 原创 Java 2020 超神之路 很肝 中文详细注释的开源项目 RPC 框架 Dubbo 源码解析 网络
  • Quartz框架多个trigger任务执行出现漏执行的问题分析

    一 问题描述 使用Quartz配置定时任务 配置了超过10个定时任务 这些定时任务配置的触发时间都是5分钟执行一次 实际运行时 发现总有几个定时任务不能执行到 二 示例程序 1 简单介绍 采用spring quartz整合方案实现定时任务
  • docker菜鸟入门

    菜鸟入门Docker 说明 一 什么是Docker 1 虚拟机和Linux容器 二 Docker用途 三 Docker安装 1 设置仓库 2 安装 Docker Engine Community 3 验证安装成功 四 Docker启动与停止
  • VoVNet论文解读

    摘要 1 介绍 2 高效网络设计的影响因素 2 1 内存访问代价 2 2 GPU计算效率 3 建议的方法 3 1 重新思考密集连接 3 2 One Shot Aggregation 3 3 构建 VoVNet 网络 4 实验 5 代码解读
  • 01背包(c++版)

    dp i j 表示从下标为 0 i 的物品里任意取 放进容量为j的背包 价值总和最大是多少 void test 2 wei bag problem1 vector
  • 上班族为何需要做副业?如何靠副业月入过万?

    网上统计某某城市平均工资8千以上 诶 一看自己3 4千 其实现在每个背井离乡在外上班的打工人都挺难的 城市很繁华 工资很现实 在工厂打螺丝的更是苦逼的生活 被资本家无情的压榨 在豪华的写字楼上班的白领 也过着996的生活 甚至是007的日子
  • 操作系统复习题

    一 选择题 在计算机系统中 操作系统是 核心系统软件 网络操作系统 不是基本的操作系统 实时性 不是分时系统的基本特征 关于操作系统的叙述 能方便用户编程的程序 是不正确的 操作系统的发展过程是 设备驱动程序组成的原始操作系统 管理程序 操
  • Response 456错误

    今天使用某share拉取股票数据时 遇到了Response 456错误 然而在网上查也没有查到 感觉是是较为少见的错误 http response code HTTP状态码对照表 t 332741160的专栏 CSDN博客 后来发现这个错误

随机推荐

  • 【Bun1.0】使用 Bun.js 构建快速、可靠和安全的 JavaScript 应用程序

    bun js Bun 是一个现代的JavaScript运行环境 如Node Deno 主要特性如下 启动速度快 更高的性能 完整的工具 打包器 转码器 包管理 官网 https bun sh 优点 与传统的 Node js 不同 Bun j
  • python数据分析与挖掘实战 -第四章数据预处理

    数据清洗 目的 删除原始数据集中的无关数据 重复数据 平滑噪声数据 筛选掉与挖掘主题无关的数据 处理缺失值 异常值等 缺失值处理方法 删除记录 数据插补和不处理 拉格朗日插值法 对于平面上已知的N个点 无两点在一条直线上 可以找到一个N 1
  • 解决vue中样式不起作用:样式穿透/深度选择器(/deep/)

    原因1 组件内部使用组件 添加了scoped属性 原因2 动态引入html 也添加了scoped属性 原因3 非以上两种 一 添加了scoped属性 Vue中的scoped属性的效果主要是通过PostCss实现的 以下是转译前的代码
  • 关于数据结构中的叶节点和二度节点的关系(通俗的理解)。

    简单记录一下自己的一些地方和对于这个问题我的一些见解 有说的不好的地方欢迎指正 这里只给出一种理解 另一种利用公式进行理解的 我就不写了 因为csdn里面太多了 先说结论 叶节点的数目 二度节点 1 首先来看这张图 可以看到这个图大体是包含
  • redis 基础概述与使用

    目录 一 redis 概述 redis 主从同步执行流程 redis 淘汰策略 缓存常见问题 KEYS指令与SCAN指令 SpringBoot 整合 redis StringRedisTemplate 与 RedisTemplate red
  • Android练手完整项目app(三)商品分类+流式布局Tag

    1 整体布局 结合项目 一 在FunctionFragment创建整体布局 搜索框布局应该include引入 这里我就没单独抽取
  • java springboot实现手机短信发送

    以下是一个使用Spring Boot实现手机短信发送的示例 首先添加pom依赖 需要引入阿里云的短信SDK和Spring Boot的web依赖
  • 【Linux】fork()

    目录 1 fork是什么 2 fork复制原理 3 逻辑地址与物理地址 4 计算fork 输出次数 1 fork是什么 linux下创建新进程的系统调用的是fork 其定义如下 include
  • 蓝桥杯获奖比例java_2019年第十届蓝桥杯省赛总结(JavaA组)

    update3 28 省一rank4 莫名进了国赛好神奇 记yzm10第一次体验A组 纯粹瞎水 早闻山东的JavaA组神仙打架 进国赛都成了奢望 往年只有五个名额 因此抱着做分母的心态来为学弟学妹试水 来到考场发现同组中光认识的大佬就不止五
  • Glide图片加载回调监听

    前两篇文章从源码的角度对Glide的加载流程进行了分析 这篇文章将对Glide的回调进行总结 1 方法一 设置图片中监听 方法一使用的是SimpleTarget类 他继承自BaseTarget 需要重写onResourceReady方法 o
  • 观点

    原文地址 https www sohu com a 315434322 672569 作者 中国工商银行业务研发中心 郝毅 霍嘉 肖烨 金石乔 本文笔者着重介绍了金融行业软件自动化测试的相关实践与思考 近两年来 多家金融机构和专业测试组织开
  • 超级完整 的 Maven 讲解 以及私服搭建

    第一章 Maven 简介 1 1 Maven 概述 Maven 是一款基于 Java 平台的项目管理和整合工具 它将项目的开发和管理过程抽象成一个项目对象模型 POM 开发人员只需要做一些简单的配置 Maven 就可以自动完成项目的编译 测
  • LLVM IR入门指南(7)——异常处理

    在这篇文章中 我主要介绍的是LLVM IR中的异常处理的方法 主要的参考文献是Exception Handling in LLVM 异常处理的要求 异常处理在许多高级语言中都是很常见的 在诸多语言的异常处理的方法中 try catch块的方
  • 什么是反向代理服务器

    我们常会看到 反向代理服务器 这个名词 例如常看到文章上说 nginx 是一个反向代理服务器 varnish 是一个反向代理服务器 下面就了解下这个概念 含义 反向代理服务器 有两个概念 一是 代理服务器 二是 反向 代理服务器 比较好理解
  • 【日记】转行驱动

    从今天开始换到驱动组了 完成手头上的应用任务就彻底挥别应用 Unity再见
  • apache httpclient 连接池 工具_HttpClient连接池的一些思考

    前言 使用apache的httpclient进行http的交互处理已经很长时间了 而httpclient实例则使用了http连接池 想必大家也没有关心过连接池的管理 事实上 通过分析httpclient源码 发现它很优雅地隐藏了所有的连接池
  • 汇编语言mov al,0c5h,用汇编语言编程

    匿名用户 1级 2008 10 30 回答 写一下简单的算法吧 先提供一个简单的画点子程序 在屏幕显示一点或一像素子程序 输入参数 BX 行地址 0 479 SI 列地址 0 639 DL 颜色 0 15 640 480显示模式 DOT p
  • unity数学函数mathf.PinPong的实现原理

    mathf这个库十分强大 基本上封装了 游戏数学方面的函数 先介绍一个Mathf pingpong 官方api Mathf PingPong 乒乓 static function PingPong t float length float
  • 《高效能程序员的修炼》目录及部分精彩章节

    高效能程序员的修炼 本书已上市 各大书店均有销售 谢谢支持 目 录 1 入门须知 1 1 你想当一个程序员 1 2 程序员的八种境界 1 3 如何培养写作习惯 2 把一堆烂事搞定的艺术 2 1 学海无边 2 2 磨刀不误砍柴工 2 3 一路
  • 深入探索透视纹理映射(下)

    在上一篇文章中 我们探讨了学习透视纹理映射所需要的基础知识 我们知道了顶点在通过透视投影变换之后 是如何一步一步通过流水线进入屏幕空间的 也知道了一个非常简单的三角形扫描线转换算法 以及通过线性插值实现的仿射纹理映射 尽管我们使用的这个流程