用C#实现一个简易的软件光栅化渲染器和第一人称相机

  主要参考博客:https://blog.csdn.net/aceyan0718/article/details/51659381 文本能力有限,没错,下面的话基本也是抄的。。

这是一个用C#+winform实现的软件光栅化渲染器,今天拿出来与大家分享一下,希望能起到抛砖引玉的作用,给新人一点启发(结构比较简单,注释比较详细^_^)(新人+1,也给我很多启发),也欢迎司机们拍砖指点和交流~

目的:

 巩固图形编程知识,理解渲染流水线所做的事情。

实现功能:

1、将顶点数据进行一系列处理显示到屏幕上(废话。。。(¬_¬))

2、线框渲染模式、纹理渲染模式、顶点色模式

3、纹理uv坐标和顶点色等的透视校正插值

4、纹理双线性过滤采样

5、背面消隐

6、Cvv简单裁剪(后面笔者(我)又加上了一个稍微复杂的裁剪,重构三角形,下面会详细介绍)

7、“基础光照模型”(相当于D3D、OpenGL中的固定管线顶点光照)
 

截图

gif软件的问题。。注意中间的模型就好

GitHub地址: https://github.com/TonyZgj/graphic_test

零、准备阶段

强烈推荐如果想了解矩阵和空间变换的知识,参考三蓝一棕,讲的可以说非常好了

https://space.bilibili.com/88461692/channel/detail?cid=9450

想要实现渲染器首先我们得知道“给定视点、三维物体、光源、照明模式,和纹理等元素,如何绘制一幅二维图像”,这就必须提到一个词“图像绘制管线”(也称绘制流水线),我们得渲染器正是要以软件的形式来模拟这条流水线的运作。

流水线:http://www.cnblogs.com/wonderKK/p/5022226.html

理解了绘制管线,我们发现其中充满对矩阵向量等数学工具的运用,那么再实现流水线之前,我们必须先实现这些数学工具,这些数学类网上已经有很多的资料了,但是有几点需要特别说明:

1、本程序中使用行矩阵与行向量
相关资料:D3d和openGl矩阵区别

http://blog.csdn.net/Nightmare/article/details/3983724

2、本程序使用左手坐标系
3、实现了color类来方便进行颜色的运算,因为进行光照的时候要使用颜色乘以颜色的计算方法,它跟向量的乘法不同,应该称为“Modulate(调制)”,注意不能用向量乘法的计算公式哦。
相关资料:

https://www.zhihu.com/question/24026277/answer/26433842

@Milo Yip 大神的博文里面有一段讲颜色计算的:

http://www.cnblogs.com/miloyip/archive/2010/03/29/1698953.html#1794025

4、矩阵求逆

由于要进行光照,我们需要变换顶点法线,那么就需要矩阵求逆和求转置的运算。(为什么需要逆转置呢?请看:https://www.zhihu.com/question/27739027

其中矩阵求逆比较复杂,本程序使用的是伴随矩阵的方法求矩阵的逆, 必须先求出矩阵的行列式和伴随矩阵,这部分需要一定的线性代数基础知识。

4.1矩阵求逆重要定理:


http://wenku.baidu.com/link?url=dga27GT5oSK8SDwHaeqPGL_raHnMLNP54oad7q8wJnMOqEpuT4_UAC-BHAeTsL8vGpYkRuphnXng99TLfHOx8_Nn7WMVycFRlUiMgSS-2Ve

4.2求矩阵行列式


本程序使用递归算法求行列式,请查看矩阵行列式的递归定义:

http://wenku.baidu.com/link?url=9TSCuxJdfypmsqZLYegHNuKYP1TABFwGT22v-cJut5wenWM-Ll2Bp6zqgDEeIvd9ChV3Laj3czAbdWtjSwaulHHSkUZtrLNcgLqNw2xrjb7,

4.3伴随矩阵:


http://wenku.baidu.com/link?url=BVbtO30v7eygbZUloybJtpFkCrpAwypYNyIx7XGX-OItxdWz0w4Y2hpzux729bq4MLKnx0sWwS4mJwm8Nrnf_3zxG9RqzD7LND3mklrwyDe

实现了数学类,就可以正式进入流水线了:
 

至于矩阵可以看我另外一篇博客,https://blog.csdn.net/qq_33413868/article/details/88182378,我觉得讲的还是挺详细的。。

 

一、几何阶段

1、顶点从模型空间----->世界空间


这一步要生成世界矩阵(简称m),m是一系列平移、选择等变换的组合

如果开启了光照,我们还需要把模型空间的法线信息变换到世界空间,对顶点进行光照计算并保存光照结果颜色,以便在光栅化的时候进行差值和颜色调制(Modulate)

这里实现的“简单光照模型”可以参见《Cg教程_可编程实时图形权威指南》第五章光照

2、世界空间----->相机空间


这一步需要生成视矩阵(简称v)

推导过程:@zdd的博客 http://www.cnblogs.com/graphics/archive/2012/07/12/2476413.html

上面的推导中写错了

应该是 

  rxry rz

[ ux uy uz ]

  dx dy dz

但是推导结果是对的

3、相机空间--->齐次剪裁空间


这里要生成投影矩阵,简称p

Dx风格的投影矩阵推导:

http://www.cnblogs.com/graphics/archive/2012/07/25/2582119.html

 

本程序使用的投影矩阵:

视空间的顶点乘以这个矩阵之后被变换到齐次剪裁空间,并且w分量保存着视空间的z信息。

在齐次剪裁空间我们可以对顶点进行简单的裁剪,既将不在

-w <= x <= w

-w<= y <= w

0 <= z <= w

这个范围的顶点剔除掉,不进行渲染。

我这里采用的是生成新的顶点的较为复杂的裁剪方法,但是是屏幕坐标裁剪,在网上找了好久没找到视锥体裁剪的教程,水平有限自己肝不出来,所以还有很多可以深入挖掘的地方:)

Ps:为了方便后续的透视校正插值,程序中还将1/z 保存在顶点数据中。

4、进行图元装配,也就是将顶点以一定的顺序组装成三角形(Primitive Assembly && Trianglesetup )


本程序中使用了顶点索引的方式来组织顶点数据,并且约定以逆时针顺序组织的三角形的法线朝向屏幕外,换句话说就是逆时针顺序组织的三角形看起来是正面。

有了这个约定就可以进行背面消隐:

http://blog.csdn.net/cppyin/article/details/6207206

5、透视除法


  对透视变换得到的含有深度信息(z)的齐次坐标做透视除法。所谓透视除法,
  就是把透视变换后的齐次坐标除以(z)。由于透视变换矩阵已经构造好了,当
  视锥体内部点经透视处理后的齐次坐标除以(z)后,使得顶点进入
-1<= x <= 1

-1<= y <= 1

0 <= z <= 1  (CVV正方体中)。

6、映射到视口


将cvv正方体的顶点根据屏幕大小或者视口大小转换为屏幕坐标
 

二、光栅化阶段

1、Rasterization光栅化


光栅化决定哪些像素被几何图元覆盖的过程

1.1 如果选择线框模式,本程序使用的画线方法是Bresenham快速画直线算法


相关资料:http://www.cnblogs.com/gamesky/archive/2012/08/21/2648623.html

 

1.2顶点色模式和纹理模式


三角形光栅化算法:http://www.sunshine2k.de/coding/java/TriangleRasterization/TriangleRasterization.html 文章虽然是英文的,但是很好理解

光栅化过程当中,我们要对uv坐标,顶点颜色、我们保存在顶点信息中的1/z等信息进行透视校正插值:

http://blog.csdn.net/popy007/article/details/5570803

文章中的重要结论:

我们发现s/z、t/z和x’、y’也是线性关系。而我们之前知道1/z和x’、y’是线性关系。则我们得出新的思路:对1/z关于x’、y’插值得到1/z’,然后对s/z、t/z关于x’、y’进行插值得到s’/z’、t’/z’,然后用s’/z’和t’/z’分别除以1/z’,就得到了s’和t’。

这就是为什么我们要保存1/z的原因。

2、Pixel Operation 像素操作


2.1消除遮挡面
根据zbuff  使用1/z来进行ztest,这样就不用给zbuff初始化一个很大的值了。

2.2 Texture operation 纹理操作,也就是根据像素的纹理坐标,查询对应的纹理值
纹理坐标的透视校正: http://blog.csdn.net/popy007/article/details/5556639

纹理采样,双线性纹理过滤:http://dev.gameres.com/Program/Visual/3D/Bilinear.htm

http://blog.csdn.net/i_dovelemon/article/details/27839279

三:相机的旋转和移动

移动实际就是更改相机的eyePosition,旋转更改其LookAt,需要配合点乘叉乘,其中要注意相机是使用的右手坐标系,而我们的世界坐标是使用的左手坐标系,需要对应更改,这也是为了符合unity的情况

 

经历了上面的步骤,把要渲染的像素写到framebuff中,再将framebuff渲染到屏幕上就完成了所有步骤(本程序简单的生成了一个bitmap来作为framebuff),这时你应该可以在屏幕上看到你所渲染的东西。如果看起来不太对,那么进入debug吧。一开始也许会觉得调试这样一个绘制流水线会无从下手,因为其中任何一步的偏差都会使最后的渲染发生错误。

请假了一些大神后找到了不错的调试方法,下面给出一些tips:

1、调试数学库,用笔算来验证结果的正确性

2、调试流水线,可以建立一个简单多边形,比如一个quad,用笔算来验证m、v、p三个矩阵的正确性。光栅化的阶段由于要进行插值,计算量比较大,建议使用心算和单步跟踪的方式来进行调试。

 

最后,再次感谢 https://blog.csdn.net/aceyan0718/article/details/51659381 大佬,我的代码在其基础上精简和修改了一些,但大体思路还是跟其保持一致

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值