【重心坐标插值、透视矫正插值】原理以及用法见解(GAMES101深度测试部分讨论)

1、Barycentric Coordinates(重心坐标)

仅供参考,数学很弱,不保证正确性。。但是看了应该会有收获

1.1 重心坐标概念

以2D为例:

  • 三角形内部任何一个点(包括顶点)都可以表示成三个顶点的线性组合。满足以下关系式的三个系数(α,β,γ)就是该点的重心坐标。还有一个限制条件是这三个系数必须非负,否则就在三角形外了
  • 简而言之,就是三角形内部一个点(x,y)在重心坐标下的表示就是(α,β,γ)
    在这里插入图片描述
  • 其中三个顶点自己的重心坐标分别是
    A:(1,0,0)
    B:(0,1,0)
    C:(0,0,1)

1.2 重心坐标计算方式

对于三角形内部点P如何计算它的重心坐标

方法1:面积比

  • A的系数α:其对应的三角形面积AA(红色部分)占总面积的比值
  • B的系数β:其对应的三角形面积AB(黄色部分)占总面积的比值
  • C的系数γ:其对应的三角形面积AC(蓝色部分)占总面积的比值
    在这里插入图片描述

方法2:利用推导出来的公式计算
先说结论,后面第二部分详解推导过程
β = ( y − y A ) ( x C − x A ) − ( x − x A ) ( y C − y A ) ( y B − y A ) ( x C − x A ) − ( x B − x A ) ( y C − y A )   γ = ( y − y A ) ( x B − x A ) − ( x − x A ) ( y B − y A ) ( y C − y A ) ( x B − x A ) − ( x C − x A ) ( y B − y A )   α = 1 − β − γ                                                             \color{red}\large β=\frac{ (y- y_{\tiny A})(x_{\tiny C} - x_{\tiny A})-(x - x_{\tiny A})(y_{\tiny C} - y_{\tiny A}) }{(y_{\tiny B} - y_{\tiny A})(x_{\tiny C} - x_{\tiny A})-(x_{\tiny B} - x_{\tiny A})(y_{\tiny C} - y_{\tiny A})}\\ \: \\ \large γ=\frac{ (y- y_{\tiny A})(x_{\tiny B} - x_{\tiny A})-(x - x_{\tiny A})(y_{\tiny B} - y_{\tiny A}) }{(y_{\tiny C} - y_{\tiny A})(x_{\tiny B} - x_{\tiny A})-(x_{\tiny C} - x_{\tiny A})(y_{\tiny B} - y_{\tiny A})}\\ \: \\ α=1-β-γ\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\: β=(yByA)(xCxA)(xBxA)(yCyA)(yyA)(xCxA)(xxA)(yCyA)γ=(yCyA)(xBxA)(xCxA)(yByA)(yyA)(xBxA)(xxA)(yByA)α=1βγ

1.3 重心坐标插值

已知重心坐标的计算方法,怎么插值属性呢?

算出来的重心坐标(α,β,γ),其实就相当于一个权重比,直接把对应的VAVBVC换成uv坐标、颜色、法线、深度值、材质属性,V即所求
在这里插入图片描述

2 重心坐标计算公式推导

在这里插入图片描述

已知2D平面内 △ A B C △ABC ABC三个顶点的坐标 A : ( x A , y A ) , B : ( x B , y B ) , C : ( x C , y C ) \large A:(x_A,y_A),B:(x_B,y_B),C:(x_C,y_C) A:(xA,yA)B:(xB,yB)C:(xC,yC)以及三角形内部任意点P的坐标 P : ( x , y ) \large P:(x, y) P:(x,y)

P P P一定满足两个条件:
{ P = α A + β B + γ C ⋯ ⋯ ⋯ ⋯ ① α + β + γ = 1 ⋯ ⋯ ⋯ ⋯ ⋯ ⋯ ② \begin{cases} P=\alpha A + \beta B +\gamma C\cdots\cdots\cdots\cdots① \\ \alpha+\beta+\gamma = 1 \cdots\cdots\cdots\cdots\cdots\cdots② \end{cases} {P=αA+βB+γC⋯⋯⋯⋯α+β+γ=1⋯⋯⋯⋯⋯⋯

如何根据已知条件计算出 α 、 β 、 γ \alpha、\beta、\gamma αβγ


Step 1
公式②代入①,我们可以消除一个未知量(这里我把α先干掉了,101课程中是干掉的γ,过程都是一样的)
P = ( 1 − β − γ ) A + β B + γ C P = (1-β-γ)A+βB+γC P=(1βγ)A+βB+γC
打开括号,合并同类项并简单移项,很容易能得出
P − A = β ( B − A ) + γ ( C − A ) ⋯ ⋯ ⋯ ⋯ ③ P-A=β(B-A)+γ(C-A)\cdots\cdots\cdots\cdots③ PA=β(BA)+γ(CA)⋯⋯⋯⋯

这个公式,可以按照向量运算来理解, A P → = β A B → + γ A C → \overrightarrow {AP} = β\overrightarrow {AB}+γ\overrightarrow {AC} AP =βAB +γAC
【突然发现图中我两个向量写反了。。向量AB和向量AC应该互换一下,我有空再改过来吧】
在这里插入图片描述

Step 2
由公式③,将已知点 A 、 B 、 C 、 P A、B、C、P ABCP的坐标代入,可得
( x − x A y − y A ) = β ( x B − x A y B − y A ) + γ ( x C − x A y C − y A ) \large \begin{pmatrix} x - x_{\tiny A} \\y- y_{\tiny A} \end{pmatrix}= β\large\begin{pmatrix} x_{\tiny B} - x_{\tiny A} \\ y_{\tiny B} - y_{\tiny A} \end{pmatrix}+ γ\large\begin{pmatrix}x_{\tiny C} - x_{\tiny A} \\ y_{\tiny C} - y_{\tiny A} \end{pmatrix} (xxAyyA)=β(xBxAyByA)+γ(xCxAyCyA)
拆开写,可得方程组(两个未知数,两个方程)
{ ( x − x A ) = β ( x B − x A ) + γ ( x C − x A ) ⋯ ⋯ ④ ( y − y A ) = β ( y B − y A ) + γ ( y C − y A ) ⋯ ⋯ ⋅ ⑤ \begin{cases} (x - x_{\tiny A}) =β(x_{\tiny B} - x_{\tiny A})+γ(x_{\tiny C} - x_{\tiny A})\cdots\cdots④\\ (y- y_{\tiny A})=β(y_{\tiny B} - y_{\tiny A})+γ(y_{\tiny C} - y_{\tiny A})\cdots\cdots\cdot⑤ \end{cases} {(xxA)=β(xBxA)+γ(xCxA)⋯⋯(yyA)=β(yByA)+γ(yCyA)⋯⋯

④ × ( y C − y A ) ,⑤ × ( x C − x A ) ④\times(y_{\tiny C} - y_{\tiny A}),⑤\times(x_{\tiny C} - x_{\tiny A}) ×(yCyA)×(xCxA)得:
( x − x A ) ( y C − y A ) = β ( x B − x A ) ( y C − y A ) + γ ( x C − x A ) ( y C − y A ) ⋯ ⋯ ⑥ ( y − y A ) ( x C − x A ) = β ( y B − y A ) ( x C − x A ) + γ ( y C − y A ) ( x C − x A ) ⋯ ⋯ ⑦ (x- x_{\tiny A})(y_{\tiny C} - y_{\tiny A}) = β(x_{\tiny B} - x_{\tiny A})(y_{\tiny C} - y_{\tiny A})+\cancel{γ(x_{\tiny C} - x_{\tiny A})(y_{\tiny C} - y_{\tiny A})} \cdots\cdots⑥\\ (y- y_{\tiny A})(x_{\tiny C} - x_{\tiny A}) = β(y_{\tiny B} - y_{\tiny A})(x_{\tiny C} - x_{\tiny A})+\cancel{γ(y_{\tiny C} - y_{\tiny A})(x_{\tiny C} - x_{\tiny A})}\cdots\cdots⑦ (xxA)(yCyA)=β(xBxA)(yCyA)+γ(xCxA)(yCyA) ⋯⋯(yyA)(xCxA)=β(yByA)(xCxA)+γ(yCyA)(xCxA) ⋯⋯
⑦ − ⑥ ⑦-⑥ 得到β,同理可得γ

Step 3
最终 α 、 β 、 γ α、β、γ αβγ表达式
β = ( y − y A ) ( x C − x A ) − ( x − x A ) ( y C − y A ) ( y B − y A ) ( x C − x A ) − ( x B − x A ) ( y C − y A )   γ = ( y − y A ) ( x B − x A ) − ( x − x A ) ( y B − y A ) ( y C − y A ) ( x B − x A ) − ( x C − x A ) ( y B − y A )   α = 1 − β − γ                                                             \color{red}\large β=\frac{ (y- y_{\tiny A})(x_{\tiny C} - x_{\tiny A})-(x - x_{\tiny A})(y_{\tiny C} - y_{\tiny A}) }{(y_{\tiny B} - y_{\tiny A})(x_{\tiny C} - x_{\tiny A})-(x_{\tiny B} - x_{\tiny A})(y_{\tiny C} - y_{\tiny A})}\\ \: \\ \large γ=\frac{ (y- y_{\tiny A})(x_{\tiny B} - x_{\tiny A})-(x - x_{\tiny A})(y_{\tiny B} - y_{\tiny A}) }{(y_{\tiny C} - y_{\tiny A})(x_{\tiny B} - x_{\tiny A})-(x_{\tiny C} - x_{\tiny A})(y_{\tiny B} - y_{\tiny A})}\\ \: \\ α=1-β-γ\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\: β=(yByA)(xCxA)(xBxA)(yCyA)(yyA)(xCxA)(xxA)(yCyA)γ=(yCyA)(xBxA)(xCxA)(yByA)(yyA)(xBxA)(xxA)(yByA)α=1βγ

到此,重心坐标的插值就算结束了,单独在二维平面或者三维平面根据四个点的坐标算出重心坐标(权重)然后插值P的法线、uv坐标等等一切信息,都可得到非常正确的结果了

但是,投影后的插值就很有讲究了


透视投影的基础知识,这里就不再赘述了,其相关详细步骤,可看 这篇文章的4.3部分

对于空间中一个特定的三角形而言,只有三个顶点携带了坐标信息 ( x , y , z ) (x,y,z) (x,y,z),对其做透视投影操作,这三个点变到了裁剪空间,此时,如果我们想要得到一张深度图的做法是什么?

建立深度缓存的方法

  • 先建立一个深度缓存,其实就是个二维数组zbuffer[width][height],并且初始化每个数组元素为max
  • 遍历每个三角形所覆盖的每个像素,将该像素所对应的场景中的那个点的z值跟zbuffer对应位置的z作比较,小则覆盖

问题就是怎么知道该像素的z是多少?

(1) 你可能会想到,我们已经知道投影变换后的三个顶点在屏幕上的坐标 ( x A , y A ) , ( x B , y B ) , ( x C , y C ) \large(x_{\tiny A},y_{\tiny A}),(x_{\tiny B},y_{\tiny B}),(x_{\tiny C},y_{\tiny C}) (xA,yA)(xB,yB)(xC,yC)和目标像素P ( x , y ) (x,y) (x,y)。算出P点的重心坐标,然后我又已知三个顶点的深度Z,插值即可。想法很美好,但这样的做法是错的,不能用插值后算出的 ( α , β , γ ) (α,β,γ) (αβγ)直接插值的原因很多,部分原因如下:

  • 投影后三角形的形状一般情况下会发生变化。空间中一个正三角形,投影后可能变成一个特别窄的三角形,这时候我算出来的该像素的权重比(重心坐标)肯定跟空间中该像素对应的点在三角形内的权重比不同,所以直接插值,不可取

  • 投影后,视椎体远/近平面的点,经过Squish变换后,z值是不变的,但是位于视椎体中间的点,变换后z的绝对值会变大,因此在投影后的三角形上直接插值,不可取

(2)可能还会想到,对屏幕空间的目标像素P点应用透视投影矩阵的逆,做个逆变换变回原来的空间中,那就知道P点在空间中的准确位置了,然后再插值深度,再写回深度缓存中P像素位置中。 OK这样做很准确,但是,每个像素都要做一次逆矩阵运算,我深度图的分辨率可能几百万个像素,这计算量不能接受

啰里巴嗦一大堆,希望我表达得没有问题,有些问题脑子里清楚,但是说出来就不知道怎么说。如果有错也请大家多多指教


3 透视投影插值矫正

在这里插入图片描述

3.1 透视矫正后的深度插值公式

国际惯例,先说结论,如果对推导没兴趣的直接套公式即可
1 Z = α ′ 1 Z A + β ′ 1 Z B + γ ′ 1 Z C \displaystyle\large\color{red} \frac{1}{Z}=α'\frac{1}{Z_A}+β'\frac{1}{Z_B}+γ'\frac{1}{Z_C} Z1=αZA1+βZB1+γZC1
符号说明:带 ′ ' 的都是投影后的值,不带的是投影前的值

Z :屏幕空间的点 P 所对应的投影前的点的深度值,我们求解的目标值 α ′ 、 β ′ 、 γ ′ :屏幕空间上算出来的重心坐标(权重) Z A 、 Z B 、 Z C :空间中 A B C 三个顶点的深度值 Z:屏幕空间的点P所对应的投影前的点的深度值,我们求解的目标值 \\α'、β'、γ':屏幕空间上算出来的重心坐标(权重) \\Z_A、Z_B、Z_C:空间中ABC三个顶点的深度值 Z:屏幕空间的点P所对应的投影前的点的深度值,我们求解的目标值αβγ:屏幕空间上算出来的重心坐标(权重)ZAZBZC:空间中ABC三个顶点的深度值


推导过程核心思想:通过投影后的三角形内点 P ′ P' P α ′ 、 β ′ 、 γ ′ α'、β'、γ' αβγ,计算出投影前三角形内点 P P P的深度值

Step 1:找到投影前后的重心坐标的关系

对公式 1 = α ′ + β ′ + γ ′ 1=α'+β'+γ' 1=α+β+γ做恒等变形
Z Z = Z A Z A α ′ + Z B Z B β ′ + Z C Z C γ ′ \frac{Z}{Z}=\frac{Z_A}{Z_A}α'+\frac{Z_B}{Z_B}β'+\frac{Z_C}{Z_C}γ' ZZ=ZAZAα+ZBZBβ+ZCZCγ
左右同×Z
Z = ( Z Z A α ′ ) Z A + ( Z Z B β ′ ) Z B + ( Z Z C γ ′ ) Z C … … … ① Z =(\frac{Z}{Z_A}α')Z_A+(\frac{Z}{Z_B}β')Z_B+(\frac{Z}{Z_C}γ')Z_C\dots\dots\dots① Z=(ZAZα)ZA+(ZBZβ)ZB+(ZCZγ)ZC………
对比投影前的插值公式
Z = α Z A + β Z B + γ Z C ⋯ ⋯ ⋯ ⋯ ② Z = αZ_A+βZ_B+γZ_C\cdots\cdots\cdots\cdots② Z=αZA+βZB+γZC⋯⋯⋯⋯
①②公式对应项系数相等
α = Z Z A α ′ , β = Z Z B β ′ , γ = Z Z C γ ′ α=\frac{Z}{Z_A}α',β=\frac{Z}{Z_B}β',γ=\frac{Z}{Z_C}γ' α=ZAZαβ=ZBZβγ=ZCZγ
Step 2:得到透视修正后深度插值公式

因为 1 = α + β + γ 1=α+β+γ 1=α+β+γ,且 α = Z Z A α ′ , β = Z Z B β ′ , γ = Z Z C γ ′ \displaystyle α=\frac{Z}{Z_A}α',β=\frac{Z}{Z_B}β',γ=\frac{Z}{Z_C}γ' α=ZAZαβ=ZBZβγ=ZCZγ

所以
1 = Z Z A α ′ + Z Z B β ′ + Z Z C γ ′ ⋯ ⋯ ⋯ ③ 1 = \frac{Z}{Z_A}α'+\frac{Z}{Z_B}β'+\frac{Z}{Z_C}γ'\cdots\cdots\cdots③ 1=ZAZα+ZBZβ+ZCZγ⋯⋯⋯
对③左右同 × 1 Z \times \frac{1}{Z} ×Z1
1 Z = α ′ 1 Z A + β ′ 1 Z B + γ ′ 1 Z C \displaystyle\color{red}\Large\frac{1}{Z}=α' \frac{1}{Z_A}+β'\frac{1}{Z_B}+γ'\frac{1}{Z_C} Z1=αZA1+βZB1+γZC1

到此已经能很好的生成一张深度缓存、shadow map了

3.2 一个非常重要的透视投影的细节

注意在完成透视投影后,在屏幕空间上的三个顶点 A ′ B ′ C ′ A'B'C' ABC z z z分量 Z A , Z B , Z C Z_A,Z_B,Z_C ZAZBZC存放的并不是原本位于观察空间中的三个顶点的正确深度,应用投影矩阵后这个z分量存放的深度已经变远了,而原本的Z,经过变换后,跑到的齐次坐标下的w分量上

下面我们取视椎体内部任意一点 P = ( x , y , z , 1 ) P=(x,y,z,1) P=(x,y,z,1),应用透视投影矩阵
[ 2 n r − l 0 l + r l − r 0 0 2 n t − b b + t b − t 0 0 0 n + f n − f − 2 n f n − f 0 0 1 0 ] ⋅ [ x y z 1 ] = [ 不关心 懒得算 不重要 z ] \Large \begin{bmatrix} \frac{2n}{r-l}&0&\frac{l+r}{l-r}&0 \\ 0 & \frac{2n}{t-b}&\frac{b+t}{b-t}&0 \\ 0&0&\frac{n+f}{n-f}&\frac{-2nf}{n-f}\\ 0&0&1&0 \end{bmatrix}·\begin{bmatrix}x\\y\\z\\1\end{bmatrix} = \begin{bmatrix} \small 不关心\\\small懒得算\\ \small不重要 \\ \color{red}\mathbf z \end{bmatrix} rl2n0000tb2n00lrl+rbtb+tnfn+f100nf2nf0 xyz1 = 不关心懒得算不重要z

  • ok,应用透视投影矩阵后,屏幕空间的三角形顶点,vertex[0].w() 就是顶点0在透视投影之前的正确的深度值,带插值公式的时候 Z A Z_A ZA取这个值,不要取.z()分量

关于Squish矩阵会把点推向远处的问题也特别简单,任选一点,左乘Msquish然后判断z的变化即可


3.2 更通用的:任意属性插值公式

I I I 为三角形内目标点的目标属性

插值公式: I = α I A + β I B + γ I C \large I = αI_A+βI_B+γI_C I=αIA+βIB+γIC
又因为
α = Z Z A α ′ , β = Z Z B β ′ , γ = Z Z C γ ′ \largeα=\frac{Z}{Z_A}α',β=\frac{Z}{Z_B}β',γ=\frac{Z}{Z_C}γ' α=ZAZαβ=ZBZβγ=ZCZγ
因此 I = α ′ I A + β ′ I B + γ ′ I C I = α'I_A+β'I_B+γ'I_C I=αIA+βIB+γIC又能写成
I = Z ⋅ ( α ′ I A Z A + β ′ I B Z B + γ ′ I C Z C ) \large\color{red} I=Z·(α'\frac{I_A}{Z_A}+β'\frac{I_B}{Z_B}+γ'\frac{I_C}{Z_C}) I=Z(αZAIA+βZBIB+γZCIC)

I : I: I: 屏幕空间三角形内部点的目标属性(颜色、法线、纹理坐标等)
I A 、 I B 、 I C I_A、I_B、I_C IAIBIC:三个顶点的对应属性
Z A 、 Z B 、 Z C Z_A、Z_B、Z_C ZAZBZC:三个顶点的观察空间的Z值(投影前的)


以下是GAMES101作业中的部分代码,以此作为案例分析如何插值任意属性

//这里计算得到重心坐标
auto [alpha, beta, gamma] = computeBarycentric2D(i+0.5, j+0.5, t.v);

// w_reciprocal就是该像素p对应投影前的深度值z
float w_reciprocal = 1.0 / (alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());

// 但是这里又用了任意属性插值公式, 并且IA取的v[0].z()
float z_interpolated = w_reciprocal*(alpha*v[0].z()/v[0].w() + beta*v[1].z()/v[1].w() + gamma*v[2].z()/v[2].w());

w _ r e c i p r o c a l = 1 α ′ 1 Z A + β ′ 1 Z B + γ ′ 1 Z C \displaystyle\large w\_reciprocal=\frac{1}{α' \frac{1}{Z_A}+β'\frac{1}{Z_B}+γ'\frac{1}{Z_C}} w_reciprocal=αZA1+βZB1+γZC11

\quad\quad w_reciprocal就是 1 w \large \frac{1}{w} w1,w就是投影前的深度值Z。计算出来的Z作为下面的Z,去计算任意属性

z _ i n t e r p o l a t e d = Z ⋅ ( α ′ I A Z A + β ′ I B Z B + γ ′ I C Z C ) \displaystyle\large z\_interpolated =\color{red}Z\color{normal} ·(α'\frac{I_A}{Z_A}+β'\frac{I_B}{Z_B}+γ'\frac{I_C}{Z_C}) z_interpolated=Z(αZAIA+βZBIB+γZCIC)

\quad\quad OK任意属性就是的深度值。也可以取别的属性。

通过上面可以总结,插值任意属性的过程分两步:

  • (1)插值 z z z,因为插值任意属性的公式里必须用到z
  • (2)插值任意属性 I I I I A 、 I B 、 I C I_A、I_B、I_C IAIBIC的选取方法是屏幕空间的三个顶点所包含的信息(如上面的AZ用到的就真的是V[0].z()),不管投影后有没有变,直接带入,应该就是这个公式的价值。目前我能想到的是这样,留个坑以后确定了再填

可以看到,上面很多都是不太严谨的基于案例的个人猜测,但是应该对跟我一样的对此过程不太熟悉的朋友们有一些些帮助,具体的更严谨的推导,建议搜索别的文章进行学习

  • 24
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 22
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宗浩多捞

您的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值