文章目录
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-β-γ\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:
β=(yB−yA)(xC−xA)−(xB−xA)(yC−yA)(y−yA)(xC−xA)−(x−xA)(yC−yA)γ=(yC−yA)(xB−xA)−(xC−xA)(yB−yA)(y−yA)(xB−xA)−(x−xA)(yB−yA)α=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③
P−A=β(B−A)+γ(C−A)⋯⋯⋯⋯③
这个公式,可以按照向量运算来理解, 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
A、B、C、P的坐标代入,可得
(
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}
(x−xAy−yA)=β(xB−xAyB−yA)+γ(xC−xAyC−yA)
拆开写,可得方程组(两个未知数,两个方程)
{
(
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}
{(x−xA)=β(xB−xA)+γ(xC−xA)⋯⋯④(y−yA)=β(yB−yA)+γ(yC−yA)⋯⋯⋅⑤
④
×
(
y
C
−
y
A
)
,⑤
×
(
x
C
−
x
A
)
④\times(y_{\tiny C} - y_{\tiny A}),⑤\times(x_{\tiny C} - x_{\tiny A})
④×(yC−yA),⑤×(xC−xA)得:
(
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⑦
(x−xA)(yC−yA)=β(xB−xA)(yC−yA)+γ(xC−xA)(yC−yA)
⋯⋯⑥(y−yA)(xC−xA)=β(yB−yA)(xC−xA)+γ(yC−yA)(xC−xA)
⋯⋯⑦
⑦
−
⑥
⑦-⑥
⑦−⑥得到β,同理可得γ
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-β-γ\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:
β=(yB−yA)(xC−xA)−(xB−xA)(yC−yA)(y−yA)(xC−xA)−(x−xA)(yC−yA)γ=(yC−yA)(xB−xA)−(xC−xA)(yB−yA)(y−yA)(xB−xA)−(x−xA)(yB−yA)α=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所对应的投影前的点的深度值,我们求解的目标值α′、β′、γ′:屏幕空间上算出来的重心坐标(权重)ZA、ZB、ZC:空间中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'
A′B′C′的
z
z
z分量
Z
A
,
Z
B
,
Z
C
Z_A,Z_B,Z_C
ZA,ZB,ZC存放的并不是原本位于观察空间中的三个顶点的正确深度,应用投影矩阵后这个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}
r−l2n0000t−b2n00l−rl+rb−tb+tn−fn+f100n−f−2nf0
⋅
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
IA、IB、IC:三个顶点的对应属性
Z
A
、
Z
B
、
Z
C
Z_A、Z_B、Z_C
ZA、ZB、ZC:三个顶点的观察空间的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
IA、IB、IC的选取方法是屏幕空间的三个顶点所包含的信息(如上面的AZ用到的就真的是V[0].z()),
不管投影后有没有变,直接带入,应该就是这个公式的价值。目前我能想到的是这样,留个坑以后确定了再填
可以看到,上面很多都是不太严谨的基于案例的个人猜测,但是应该对跟我一样的对此过程不太熟悉的朋友们有一些些帮助,具体的更严谨的推导,建议搜索别的文章进行学习