从零开始写一个光栅软渲染器

结束了GAMES101的学习(好久了,又因为大作业的毛发渲染由于生病暂时没法摸到自己的电脑,突发奇想的能不能做一个光栅渲染器(因为做raytracer的人实在太多力),于是便开了一个仓库来放相关的东西:Tiny Rasterizer

渲染管线

pipeline

这里,我们实现的时候因为偷懒因为方便,我只实现了以下的部分:

  • 顶点定义
  • 顶点变换
  • 光栅化
  • 片元着色
  • 样本操作(暂未实现)

目前的效果还是不错的,具体的实现细节可以参考我上面列出的GitHub代码。

踩坑日记

OBJ的mesh三角化

最开始我是直接从GAMES101的作业3的框架里把三角形的加载代码抄了过来,后面加载顶点数大于3的face时出现了非常严重的错误(是的,非常严重),研究了一番后发现是没有根据OBJ_Loader的内部下标来读取,更改之后就好了。

法线的变换

首先,要清楚的是法线是不归由于透视投影或者正交投影而发生改变,投影前后是保持一致的;其次,法线的变换是\(((M_{view}\cdot M_{model})^{-1})^T\),具体推导过程也非常简单,此处不再赘述。

贴图坐标

一开始,我直接用贴图的(u, v)坐标做双线性插值,结果十分奇怪,贴图出现了各种错位,后面经过考证,发现贴图坐标是从左下角开始算的(发现之后真的蚌埠住了),于是对v进行修正之后正常。

额外需要注意的是,tga文件本身作为贴图的时候已经进行了uv坐标的换算了,也就是说这个时候我们反而需要在读取的时候对整个文件做一个y方向的flip才行。

Objective-C++&C++混合编译链接

不知道什么原因,在引入了mm文件之后,hpp文件就不能直接定义裸奔的函数了,解决的方法也很简单,就是函数定义和实现分开到.hpp和.cpp中即可。

左右颠倒?

昨天发现我试图渲染出来的图片是左右颠倒的,查了半天发现问题出现在投影矩阵和y坐标的换算上,首先投影矩阵的near和far在计算camera的width和height的时候需要取相反数,这里的原因是摄像机是从z正半轴往负半轴看去,近、远平面其实都是位于负半轴上的;其次世界y坐标正方向向上,屏幕的y坐标正方向向下,需要颠倒一下。

大三角形平面无法显示

这是跟NDC空间、透视空间变换有关系的,当一个点在摄像机后方时,经过mv变换后,得到的\(z\)坐标分量是一个负数,这个时候通过透视变换之后得到的\(w\)分量也一定是一个负数,由于在mvp变换之后有一个除以\(w\)的透视除法,这个时候我们得到的Vec3(x, y, z)中的前两个分量将会正好是正确坐标值的相反数(为什么z分量不是?因为他本身就是负的!)所以我们需要在判断\(w\)分量为负的时候将前两个分量取各自的相反数才能得到正确的坐标值。

RoadMap

目前这个光栅化渲染器已经大概能用了(虽然我知道还藏着很多bug),但是由于每次都需要dump到图片中进行预览,效率很低,接下来打算通过窗口化的方式来提高debug效率。

作者

Carbene Hu

发布于

2021-11-15

更新于

2024-11-19

许可协议

评论