基于python与CUDA的N卡GPU并行程序——使用taichi语言实现三角形光栅化算法

  在计算机图形学中,需要扫描三角形以进行光栅化。比如渲染引擎的计算过程,需要计算从每个像素发出去的光线是否能够碰撞到某个三角形面片,这个时候需要将3D的三角形,按照几何透视原理,投影到2D的光源空间上,然后扫描每个三角形。

重心坐标系插值算法

  Barycentric Coordinates即重心坐标系插值算法,能够计算2D平面上的任意一点到三个顶点的相对位置,然后依次判断这个点是否在三角形内部,如果在三角形内部,就给这个像素图上颜色,这时就可以完成三角形光栅化。
  已知三角形的三个顶点A,B,C,设任意一点P能够表示成
P = A + u ⋅ A B ⃗ + v ⋅ A C ⃗ = A + u ⋅ ( B − A ) + v ⋅ ( C − A ) = ( 1 − u − v ) ⋅ A + u ⋅ B + v ⋅ C P=A+u\cdot\vec{AB}+v\cdot\vec{AC}\\ =A+u\cdot (B-A)+v\cdot(C-A)\\ =(1-u-v)\cdot A+u\cdot B+v\cdot C P=A+uAB +vAC =A+u(BA)+v(CA)=(1uv)A+uB+vC
这样就能产生一个方程
u ⋅ A B ⃗ + v ⋅ A C ⃗ + P A ⃗ = 0 { u ⋅ A B ⃗ x + v ⋅ A C ⃗ x + P A ⃗ x = 0 u ⋅ A B ⃗ y + v ⋅ A C ⃗ y + P A ⃗ y = 0 u\cdot\vec{AB}+v\cdot\vec{AC}+\vec{PA}=0\\ \left\{\begin{aligned}\\ u\cdot\vec{AB}_x+v\cdot\vec{AC}_x+\vec{PA}_x=0\\ u\cdot\vec{AB}_y+v\cdot\vec{AC}_y+\vec{PA}_y=0\\ \end{aligned}\right. uAB +vAC +PA =0{ uAB x+vAC x+PA x=0uAB y+vAC y+PA y=0
只有 u u u v v v两个未知量,所以可以直接解出方程,不过可以直接使用向量叉积运算来简化这个求解过程,设两个向量分别为
v e c 1 = [ A B ⃗ x , A C ⃗ x , P A ⃗ x ] v e c 2 = [ A B ⃗ y , A C ⃗ y , P A ⃗ y ] vec_1=[\vec{AB}_x,\vec{AC}_x,\vec{PA}_x]\\ vec_2=[\vec{AB}_y,\vec{AC}_y,\vec{PA}_y] vec1=[AB x,AC x,PA x]vec2=[AB y,AC y,PA y]
直接计算这两个向量的叉积,就可以知道最后的 u u u v v v的值了
v e c 3 = c r o s s ( v e c 1 , v e c 2 ) = [ a , b , c ] [ u , v , 1 ] = [ a / c , b / c , 1 ] vec_3=cross(vec_1,vec_2)=[a,b,c]\\ [u,v,1]=[a/c,b/c,1] vec3=cross(vec1,vec2)=[a,b,c][u,v,1]=[a/c,b/c,1]
这时只要判断,如果
{ 0 < = u < = 1 0 < = v < = 1 0 < = ( u + v ) < = 1 \left\{\begin{aligned}\\ 0<=u<=1\\ 0<=v<=1\\ 0<=(u+v)<=1 \end{aligned}\right. 0<=u<=10<=v<=10<=(u+v)<=1
那么点P必定在三角形内部。这个方法还有一个优点就是能够很方便的计算出纹理贴图的坐标,并且拟合插值出中间的颜色值。
  这个算法的缺点是没有利用上相邻像素往往很相似的假设条件,所以每个像素点都是独立计算的,不过也很容易直接应用在GPU并行计算的程序上,缺点是这样带来了很高的计算量。使用taichi语言实现并行程序,这时的三角形扫描速度很慢,1百万个三角形需要30秒的时间才能扫描完,以下是这个算法的程序,这个速度显然是远远不够的,因为著名的虚幻5引擎《UE5》可以一秒钟可以处理几十亿个三角形。笔者在此基础上也进行了改进,调整了数据内存的布局,提升缓存命中率,可以达到一分钟扫描三亿个三角形。

def triangle_rasterization(i: ti.i32):
    # 将三角形光栅化
    for i, j, k in ti.ndrange(pixels.shape[0], pixels.shape[1], (i*1000, (i+1)*1000)):
        vec_1 = ti.Vector([triangles[k][0, 1] - triangles[k][0, 0], 
                          triangles[k][0, 2] - triangles[k][0, 0], 
                          triangles[k][0, 0] - i])
        vec_2 = ti.Vector([triangles[k][1, 1] - triangles[k][1, 0], 
                          triangles[k][1, 2] - triangles[k][1, 0], 
                          triangles[k][1, 0] - j])
        vec_3 = ti.cross(vec_1, vec_2)
        u = vec_3[0] / vec_3[2]
        v = vec_3[1] / vec_3[2]
        if 0 <= u and u <= 1 and 0 <= v and v <= 1 and 0 <= (1-u-v) and (1-u-v) <= 1:
            pixels[i, j] = ti.Vector([255, 0, 0])

以下是扫描十万个三角形生成的图片

在这里插入图片描述

扫描线算法

  扫描线算法会利用到像素之间相似性的规律,只需要计算出两个交点,在两个交点之间的很多像素都是在三角形内部,不需要对每个像素都单独进行判断,所以可以减少很多计算量。主要将三角形分成平顶或者平底的,然后任意三角形可以拆分成两个三角形的组合。
在这里插入图片描述
在这里插入图片描述
  之后逐行扫描平顶或者平底三角形,光栅化平底三角形的原理很简单,就是从上往下画横线。在图里我们取任意的一条光栅化直线,这条直线左边的端点x值为XL,右边的为XR。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/wanchaochaochao/article/details/109095853