[GAMES101]现代计算机图形学课程总结4:重心坐标,作业2

重心坐标与三角形内部插值

在三角形内部插值

为什么要插值

  • 经常要在顶点上设置属性
  • 需要在三角形内部得到平滑的值

哪些属性需要插值

  • 纹理坐标,颜色,法线,等等。。。

如何插值

  • 使用重心坐标(Barycentric coordinates)

重心坐标:在三角形上使用的一种坐标系统

  • 使用alpha, beta, gamma三个系数对三角形三个顶点进行线性组合,可以得到三角形所在平面任意点,需要满足条件 alpha+beta+gamma=1
  • 如果这三个系数都大于等于0,则该点在三角形内部
    在这里插入图片描述

三角形顶点的重心坐标

  • 对于上图的A点,其重心坐标为(1,0,0),同样对于B点为(0,1,0),C点为(0,0,1)
    在这里插入图片描述

重心坐标的计算:通过三角形面积比

在这里插入图片描述
三个系数分别使用相应顶点对面的小三角形面积比上三角形总面积。

重心的重心坐标:

在这里插入图片描述
重心到三个顶点的连线构成的三个三角形的面积相等,因此重心的重心坐标为(1/3,1/3,1/3)

重心坐标公式

在这里插入图片描述

使用重心坐标线性插值

在这里插入图片描述

  • 直接使用alpha, beta, gamma对任意顶点属性进行线性插值
  • 注意:在投影变换下,不能保证重心坐标不变。三维空间的三角形中的一点的重心坐标是使用三维空间中的三角形三个顶点计算的,而投影后二维空间的该点的重心坐标是用投影后的三角形三个顶点计算的。这两个重心坐标很可能是不一样的。
  • 因此要算三维空间中三角形内一点的插值需要计算出三维空间中的重心坐标,然后使用这个重心坐标插值。而不能使用投影后的2D点计算出重心坐标插值。
  • 比如深度插值,如果使用投影后的顶点计算出重心坐标进行深度值的插值其实是错误的。
  • 101上讲的方法是要将投影后顶点逆变换回3D空间计算重心坐标再用来插值。工业界常用方法是透视投影校正,这种方法还是使用投影后的坐标计算重心坐标,但是插值时却不直接使用重心坐标。具体参考图形学基础之透视校正插值

作业2总结

关于作业框架的一些问题

s2021(包括s2020)的光栅化部分的作业框架有一点问题,具体可以参考BBS的这个帖子:作业3 关于深度值问题自己踩的坑和一些想法
除了框架本身的小错误外,造成困扰的主要是约定不明,我是完全按照101课程的约定去做的,但是貌似框架不这么想,所以最后深度比较的时候就不能使用小于了,虽然作业框架说深度值都已经转成正数了,越大离视点越远,但如果按101的规范(使用右手坐标系的NDC)的话,简单的反转深度值为正数,实际效果是值越大离视点越近。不过其实弄明白所有的过程也没什么好纠结的了。作业2中我就是把已经反转的z值又取负然后继续用小于来测试。然后在URasterizer中,也是使用了101的约定,我是将-1,1的z值转到0到1,但是没有取反,这样就需要用大于测试,正好和Unity的ReverseZ一样。

关于作业框架中的透视校正插值

作业框架中的重心坐标插值其实是使用了透视校正的方式。

 auto[alpha, beta, gamma] = computeBarycentric2D(x, y, t.v);
 float w_reciprocal = 1.0/(alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());
 float z_interpolated = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w();
 z_interpolated *= w_reciprocal;

在上文引用的图形学基础之透视校正插值中,我们得出一个结论:
在这里插入图片描述
这个式子是通过线段内部插值进行推导的,对于三角形,我们使用重心坐标作为插值系数,可以类比得到:
1 Z t = a l p h a Z 1 + b e t a Z 2 + g a m m a Z 3 \frac{1}{Z_t} = \frac{alpha}{Z_1} + \frac{beta}{Z_2} + \frac{gamma}{Z_3} Zt1=Z1alpha+Z2beta+Z3gamma
因为我们根据101的约定计算的投影矩阵,将顶点变换到NDC后,其w值就是视图空间的z值。所以框架代码中:

float w_reciprocal = 1.0 / (alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());

w_reciprocal就是我们上面推导的式子中的 Z t Z_t Zt,既然如此为啥后面还要计算z_interpolated呢?是因为作业框架中在viewport transform时,将z值映射到了near,far之间:

float f1 = (50 - 0.1) / 2.0;
float f2 = (50 + 0.1) / 2.0;

vert.z() = vert.z() * f1 + f2;

按照101的约定,view space的z值是负数,经过这个映射变成了[0.1,50]之间的正数了,所以即便我们使用透视校正插值计算出了真实的view space的z值 Z t Z_t Zt(也就是框架中的w_reciprocal),也不能直接用来做深度测试。不过没关系,我们可以把这个映射过的z值作为一个顶点属性进行透视校正插值。顶点属性的透视校正插值公式为:
在这里插入图片描述
这个式子的分母就是上面的 1 Z t \frac{1}{Z_t} Zt1,而分子部分是对属性线性插值时每个属性值多除了一个顶点z值。作业框架中,z_interpolated计算的就是分子部分,然后再乘以w_reciprocal(这个就是分母的倒数了,所以用了reciprocal,也就是 Z t Z_t Zt)就得到了透视校正插值的属性值。
由于可能要做多次插值,所以先将w_reciprocal计算出来,就可以方便插值时再乘一下了。在作业3中是这么做的,注释比作业2的清晰多了:

//    * v[i].w() is the vertex view space depth value z.
//    * Z is interpolated view space depth for the current pixel
//    * zp is depth between zNear and zFar, used for z-buffer

float Z = 1.0 / (alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());
float zp = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w();
zp *= Z;

然后下面插值各个属性,原始是普通的线性插值:

// Interpolate the attributes:
auto interpolated_color = alpha * t.color[0] + beta * t.color[1] + gamma * t.color[2];
auto interpolated_normal = alpha * t.normal[0] + beta * t.normal[1] + gamma * t.normal[2];
auto interpolated_texcoords = alpha * t.tex_coords[0] + beta * t.tex_coords[1] + gamma * t.tex_coords[2];
auto interpolated_shadingcoords = alpha * view_pos[0] + beta * view_pos[1] + gamma * view_pos[2];

可以改成透视校正插值:

// Interpolate the attributes:
auto interpolated_color = (alpha * t.color[0]/v[0].w() + beta * t.color[1]/v[1].w() + gamma * t.color[2]/v[2].w()) * Z;
//下同

实现三角形栅格化算法以及z-buffer算法

rasterize_triangle函数中

首先计算当前三角形的AABB,判断三个顶点的x,y的极大极小值得到。

根据AABB计算出需要采样的像素坐标,通过floor,ceil以及边界检测得到。

遍历需要检测的像素,在没有MSAA的情况下,判断每个像素是否在三角形内部,
如果在内部则计算重心坐标插值后的深度值,使用深度值和深度缓冲中该像素位置的值进行比较
如果通过深度测试则写入深度值,同时取三角形的颜色值写入frame_buf

测试点是否在三角形内的具体算法:

insideTriangle函数中,
首先像素坐标加上偏移值得到sampler位置。对于非MSAA,sampler位置取像素中心,偏移值为(0.5,0.5)
分别计算从三个顶点出发到sampler的向量,以及按环绕顺序计算三个顶点到下一个顶点的向量。
计算向量的叉积,如果该叉积和上一个叉积的z值的符号相同则继续计算。
所有三个叉积的z值符号都相同则该点在三角形内部。

实现MSAA (2x2):

首先扩大了深度缓冲和颜色缓冲为原先的4倍,并且建立一个masks缓冲来存储sub pixel是否可见。

std::vectorEigen::Vector4f depth_buf_2x2;
std::vectorEigen::Vector3f sampler_colors[4];
std::vectorEigen::Vector4f sampler_colors_masks;

在rasterize_triangle函数中

对于每个需要遍历的像素,使用2x2共4个sampler判断是否在三角形内,如果在内,则分别计算每个sub pixel的深度值,
并使用depth_buf_2x2缓冲进行深度测试,如果通过测试则写入该缓冲,同时将颜色值记录到sampler_colors中,
并且sampler_colors_masks该sub pixel的值设置为1。

在draw函数中

所有三角形光栅化完毕后,增加一个Resolve AA的过程:
遍历所有像素,检查他们的子像素的masks值是否为1,如果是将颜色取出进行平均,得到的颜色值设置给该像素。注意平均时的分母是4,而不是masks为1的子像素数目。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值