引言
本文实现了一个包含矩阵变化、光栅化、面剔除、深度测试等功能的软渲染器。 所谓软渲染器就是使用 CPU 渲染 3D 模型的程序。 因此请记住我们的最终目的:将3D模型显示在屏幕上 。 本文分为两个部分:预备知识、渲染器核心实现。预备知识概述了简化的渲染管线,渲染器核心实现将讨论核心算法,软渲染器完整的源代码请参见文末附录。
软光栅渲染效果预览
如果您迫不及待体验下图中的渲染效果,可以直接转到附录中的工程代码构建运行。
正视效果图
侧视效果图
整体效果图
一、预备知识
从计算机中二进制的模型数据到屏幕上五彩缤纷的渲染画面,我们需要知道这之间发生了什么。 首先我们需要了解模型数据是怎样表示模型的,其次是模型如何进行移动、旋转之类的变换,最后是如何将模型显示在屏幕的一个个像素上。 如果您已熟悉标题内容,可以选择直接查看核心代码和工程源文件。 如果您不了解标题内容,请您参阅:GAMES101现代计算机图形学入门。 如果您对渲染管线的工作流程觉得一知半解,请您参阅:渲染管线流程概述。
1.1 模型文件
模型是什么?模型是以特定格式存储的文本文件,常见的存储格式有:OBJ、FBX、GLTF等。 上图中模型包含了一个立方体网格和一个贴图,我们将一个固定的贴图贴在立方体不同的面上,就组成了这样一个生动地立方体模型。 本文使用OBJ格式的模型文件,一个典型的OBJ文件信息如下:
# Blender v2. 83.5 OBJ File: ''
# www . blender. org
mtllib Cube. mtl
o Cube
v 1.000000 1.000000 - 1.000000
v 1.000000 - 1.000000 - 1.000000
v 1.000000 1.000000 1.000000
v 1.000000 - 1.000000 1.000000
vt 0.625000 0.500000
vt 0.875000 0.500000
vt 0.875000 0.750000
vt 0.625000 0.750000
vt 0.375000 0.750000
vn 0.0000 1.0000 0.0000
vn 0.0000 0.0000 1.0000
vn - 1.0000 0.0000 0.0000
vn 0.0000 - 1.0000 0.0000
vn 1.0000 0.0000 0.0000
vn 0.0000 0.0000 - 1.0000
usemtl Material
s off
f 1 / 1 / 1 5 / 2 / 1 7 / 3 / 1 3 / 4 / 1
f 4 / 5 / 2 3 / 4 / 2 7 / 6 / 2 8 / 7 / 2
f 8 / 8 / 3 7 / 9 / 3 5 / 10 / 3 6 / 11 / 3
f 6 / 12 / 4 2 / 13 / 4 4 / 5 / 4 8 / 14 / 4
文件的前四行是说明数据,以v、vt或vn开头的行表示模型数据。 v开头的行表示一个顶点的位置信息,其后的3个浮点数表示顶点坐标的x、y、z值。 vt开头的行表示一个纹理坐标,其后紧跟的2个浮点数表示纹理坐标的u、v值。 vn开头的行表示一个法向量,其后紧跟的3个浮点数表示向量的x、y、z值。在计算机图形学中,法向量常用于表示三维模型的表面法线方向,是计算光照、阴影、碰撞检测等操作的重要参数 f 开头的行表示一个面,一个面可以包含多个顶点。上文文件中每个面f包含了4个顶点,每个顶点表示为:顶点位置索引 / 顶点纹理索引 / 顶点法向量索引。4个顶点组成的两个三角形表示出一个四边形。 v、vt和vn各自表示各自的信息,不具有顺序对应关系。当我们需要v、vt和vn时,我们只需要说明它是第几个,即提供它的索引信息。 为了简洁的展现OBJ文件的格式,上文示例OBJ文件经过裁剪,原始文件见文末附录:完整的OBJ文件。
1.2 变换
模型数据是位于局部坐标系下的,我们需要对其进行变换,变换都以矩阵的形式实现。 下表列举了各种变换,下图展示了通过矩阵实现坐标变换。
变换名称 意义 模型变换 将模型从局部坐标系移动到世界坐标系 观察变换 按照摄像机位置将世界空间转换到观察空间 投影变换 按照视口参数将观察空间转换为标准投影立方体 视口变换 按照屏幕大小将标准投影立方体转换为屏幕视口
请参阅:计算机图形学笔记:3-5
1.2.1 模型变换
模型创作者们通常使用3D建模软件创作模型,他们并不关心模型的使用者会将模型放于游戏世界的何处,他们的工作是在三维空间的原点附近创作出炫酷的3D模型。 我们称模型创作者建立的模型位于对象空间中,对象空间中的坐标系为局部坐标系,即OBJ文件中表示的信息都是在局部坐标系下。 模型的使用者是游戏创作者,他们负责把模型放到宏大世界中的每一个角落,以使得整个游戏世界充实而丰富。我们称宏大的游戏世界为世界空间,世界空间中的坐标系为世界坐标系。 在我们渲染3D模型时,如果直接渲染会导致所有模型位于一处,即坐标系原点。这是因为我们使用模型在局部坐标系下的位置坐标作为其在世界坐标系下的位置坐标,我们并不希望出现这样的画面,我们希望的是模型分散于世界空间中各个角落。 因此我们需要对模型的坐标进行变换,使得变换后模型的位置移动到我们期望的位置,而这个变换是通过矩阵实现的,这个矩阵称为模型矩阵。模型矩阵负责将模型从局部坐标系变换到空间坐标系,它可以表示模型的缩放、旋转、平移。 请参阅:模型矩阵原理推导
1.2.2 观察变换
在现实世界中我们使用眼睛观察整个世界,我们看到物体只因其处于我们的视野范围内,而游戏世界也同理,我们需要在游戏世界中使用一双"眼睛"来表示玩家的可见范围,这就是摄像机。 摄像机具有位置和方向属性,位置表示了摄像机位于世界空间何处,方向表示摄像机指向哪个方向。在确定了摄像机的摆放状态后,我们对世界空间中的所有物体进行观察变换,将摄像机变换到标准位置,这将极大利于后面各种的计算。 请参阅:观察矩阵原理推导
1.2.3 投影变换
我们不需要将整个游戏世界渲染在屏幕上,我们只需要将摄像机视野中的画面渲染在屏幕上,怎样得到摄像机视野中的画面呢?我们需要先定义摄像机的成像方式,这涉及两种不同的投影原理。 我们人眼看世界会产生 “近大远小” 的效果,这种根据物体与摄像机之间距离进行投影的方式称为 “透视投影”。我们使用一个视锥来表示透视投影下的视野范围,视锥可参数化为:远近屏幕的距离n和f、垂直的可视角度fov、长宽比aspect。 视锥的近平面表示要呈现画面的大小,通过 从透视到正交 变换将所有平面压缩为和近平面一样大,远处的物体因此会缩小。 根据视锥的参数,我们可以推出 从透视到正交 的变换矩阵,此变换可将视锥的远平面压为和近平面一样大,即把视锥挤成一个立方体。 把视锥挤成立方体后,再进行正交投影,即可将空间中的所有物体压缩到一个【-1,1】三次方的立方体中。 请参阅:投影矩阵原理推导
1.2.4 视口变换
模型变换是为了进行模型的缩放、旋转和平移,观察变换是为了将摄像机移动到标准位置便于计算,投影变换是为了定义视野、成像方式并压缩物体到标准立方体。这些操纵模型数据的方式都不是我们的最终目的,我们的最终目的是将模型显示到屏幕上。 经过最后的正交投影后,视野内的所有物体都被压缩到了【-1,1】三次方的立方体中,我们现在想要把它呈现在屏幕上。如作者电脑的分辨率为1920 * 1080,那么我们就需要将【-1,1】三次方的立方体中的内容映射到1920 * 1080的屏幕上,这需要进行视口变换。 我们定义显示渲染画面的窗口为视口,依据视口的大小对标准立方体进行拉伸的变换被称为:视口变换。 请参阅:视口矩阵原理推导
1.3 小结
通过上述过程我们了解了3D模型文件的格式、变换方法和流程。 3D模型文件中定义了许多顶点信息和索引,我们通过模型变换将顶点位置、法向量等信息转变到世界空间中,使得我们的模型可以以多种姿态出现在世界空间的所有位置。 摄像机定义了我们在什么位置、往什么方向看,而投影变换根据摄像机的视野锥体参数,返回给我们一个标准立方体。 根据视口的大小,我们对标准立方体进行视口变换,得到了一个前后平面为视口大小的立方体。 经过所有变换后需要显示的内容都位于一个立方体中,它的远近平面大小等于视口大小、远近平面上坐标的z值分别为-1和1。 你可以选择忽略z值,这样立方体直接变成一个平面,这样就可以呈现一幅画面了。当然忽略z值会导致前后遮挡关系的错乱,如果你进行简单的深度测试,即仅保留最前面的画面,那么就可以得到完美画面了! 经过上述过程,我们已经可以得到一幅完美的画面了,那么我们为什么还要继续?因为得到画面是理论上的,即理论上我们可以得到完美的画面,但是现实我们还没有把它展示出来,这涉及到现代设备的成像原理。
1.4 光栅化
屏幕是由离散的像素组成的,比如1920 * 1080表示横向分布1920个像素点,而纵向分布1080个像素点。每个像素可以显示出不同的颜色,上百万个像素点呈现出了您现在所看到的画面。 当我们的模型数据经过上述所有过程后,虽然模型顶点信息可能发生变换,它依旧还是模型格式数据,即模型的每个面由三角形或四边形数据表示。我们无法直接提供给屏幕硬件这些数据,它无法识别这其中的含义,我们只能告诉屏幕硬件在某个像素点应该显示什么颜色。 我们现在拥有的并不比1.1中所具有的多,我们仅拥有变换后的v、vn和无需变换的vt、f 。 什么时候需要渲染一个像素点?当像素点位于模型的某个三角形内时我们需要渲染像素点,当然这个"在三角形内"不用考虑z值,可以想象将三角形投影到近平面上,我们只需要检查近平面上的像素点是否在这个投影的三角形内即可。 像素点可能数以百万,因此一个个像素点去检查是非常低效的,因为一个三角形只可能覆盖一块区域,因此我们检查模型中每一个三角形包含了哪些像素会更高效。这个将顶点数据转换为片元的过程就是光栅化,片元不仅包含某一像素,还包含这个像素所具有的世界坐标系位置、颜色、纹理坐标等。 我们只具有顶点的数据,而顶点几乎不可能刚好在某个像素上,那么我们如何得到像素点的z坐标、颜色、纹理坐标等信息呢?这需要使用插值算法,而对于三角形图元来说,常用重心坐标进行插值计算。 请参阅:三角形重心坐标插值原理
1.5 深度测试
当进行光栅化后,我们得到了一系列片段,片段都位于像素点的位置处,并且片段中记录了这个像素点经过插值后得到的坐标z值、纹理坐标、颜色值等信息。这样的片段会很多,因为一个像素可能被很多个三角形包含,因此一个像素点对应的片段常常有很多个,为了实现现实中的遮挡关系,我们必须仅留下最前面的片段,这就是深度测试。 在深度测试中我们需要指定片段的丢弃方式,比如设定当一个片段的深度大于其他同位置处的片段时,我们就将这个片段进行丢弃,这就是深度测试。
二、渲染器核心实现
2.1 读取模型文件
struct Mesh
{
std:: vector< Vector3< float >> positionBuffer;
std:: vector< Vector2< float >> uvBuffer;
std:: vector< Vector3< float >> normalBuffer;
std:: vector< Vector3< float >> colorBuffer;
std:: vector< Vector3< int >> indexBuffer;
void stringSplit ( std:: string s, char splitchar, std:: vector< std:: string> & vec)
{
if ( vec. size ( ) > 0 )
vec. clear ( ) ;
int length = s. length ( ) ;
int start = s[ 0 ] == splitchar ? 1 : 0 ;
for ( int i = 0 ; i < length; ++ i)
{
if ( s[ i] == splitchar)
{
vec. push_back ( s. substr ( start, i - start) ) ;
start = i + 1 ;
}
else if ( i == length - 1 )
vec. push_back ( s. substr ( start, i + 1 - start) ) ;
}
}
void readObjFile ( std:: string path)
{
std:: ifstream in ( path) ;
std:: string txt = "" ;
if ( in)
{
while ( std:: getline ( in, txt) )
{
if ( txt[ 0 ] == 'v' && txt[ 1 ] == ' ' )
{
std:: vector< std:: string> num;
txt. erase ( 0 , 2 ) ;
stringSplit ( txt, ' ' , num) ;
Vector3< float > pos;
pos = Vector3 < float > ( ( float ) atof ( num[ 0 ] . c_str ( ) ) , ( float ) atof ( num[ 1 ] . c_str ( ) ) , ( float ) atof ( num[ 2 ] . c_str ( ) ) ) ;
this -> positionBuffer. push_back ( pos) ;
}
else if ( txt[ 0 ] == 'v' && txt[ 1 ] == 'n' )
{
std:: vector< std:: string> num;
txt. erase ( 0 , 3 ) ;
stringSplit ( txt, ' ' , num) ;
Vector3< float > n = Vector3 < float > ( ( float ) atof ( num[ 0 ] . c_str ( ) ) , ( float ) atof ( num[ 1 ] . c_str ( ) ) , ( float ) atof ( num[ 2 ] . c_str ( ) ) , 0.0 ) ;
this -> normalBuffer. push_back ( n) ;
}
else if ( txt[ 0 ] == 'v' && txt[ 1 ] == 't' )
{
std:: vector< std:: string> num;
txt. erase ( 0 , 3 ) ;
stringSplit ( txt, ' ' , num) ;
this -> uvBuffer. push_back ( Vector2 < float > ( ( float ) atof ( num[ 0 ] . c_str ( ) ) , ( float ) atof ( num[ 1 ] . c_str ( ) ) ) ) ;
}
else if ( txt[ 0 ] == 'f' && txt[ 1 ] == ' ' )
{
std:: vector< std:: string> num;
txt. erase ( 0 , 2 ) ;
stringSplit ( txt, ' ' , num) ;
for ( int i = 0 ; i < num. size ( ) ; ++ i)
{
std:: vector< std:: string> threeIndex;
stringSplit ( num[ i] , '/' , threeIndex) ;
Vector3< int > indexes = { atoi ( threeIndex[ 0 ] . c_str ( ) ) - 1 , atoi ( threeIndex[ 1 ] . c_str ( ) ) - 1 , atoi ( threeIndex[ 2 ] . c_str ( ) ) - 1 } ;
this -> indexBuffer. push_back ( indexes) ;
}
}
}
}
else
std:: cout << "no file" << std:: endl;
}
void print ( )
{
std:: cout << "Mesh data:" << std:: endl;
for ( int i = 0 ; i < positionBuffer. size ( ) ; ++ i)
{
std:: cout << "v " ;
positionBuffer[ i] . print ( ) ;
std:: cout << std:: endl;
}
std:: cout << std:: endl;
for ( int i = 0 ; i < uvBuffer. size ( ) ; ++ i)
{
std:: cout << "vt " ;
uvBuffer[ i] . print ( ) ;
std:: cout << std:: endl;
}
std:: cout << std:: endl;
for ( int i = 0 ; i < normalBuffer. size ( ) ; ++ i)
{
std:: cout << "vn " ;
normalBuffer[ i] . print ( ) ;
std:: cout << std:: endl;
}
std:: cout << std:: endl;
for ( int i = 0 ; i <= indexBuffer. size ( ) - 3 ; i += 3 )
{
std:: cout << "f " ;
indexBuffer[ i] . print ( ) ;
std:: cout << " " ;
indexBuffer[ i + 1 ] . print ( ) ;
std:: cout << " " ;
indexBuffer[ i + 2 ] . print ( ) ;
std:: cout << std:: endl;
}
std:: cout << "end" << std:: endl;
}
} ;
2.2 变换矩阵的构造
template < typename T >
struct Matrix4
{
Matrix4 ( ) { }
Matrix4 ( const std:: initializer_list< float > & list)
{
auto begin = list. begin ( ) ;
auto end = list. end ( ) ;
int i = 0 , j = 0 ;
while ( begin != end)
{
data[ i] [ j++ ] = * begin;
if ( j > 3 )
{
++ i;
j = 0 ;
}
++ begin;
}
}
T data[ 4 ] [ 4 ] = { } ;
void Identity ( )
{
for ( int i = 0 ; i < 4 ; ++ i)
data[ i] [ i] = 1 ;
}
Matrix4< T> operator * ( const Matrix4< T> & right) const
{
Matrix4 res;
for ( int i = 0 ; i < 4 ; ++ i)
{
for ( int j = 0 ; j < 4 ; ++ j)
{
for ( int k = 0 ; k < 4 ; ++ k)
{
res. data[ i] [ j] += this -> data[ i] [ k] * right. data[ k] [ j] ;
}
}
}
return res;
}
Vector3< T> operator * ( const Vector3< T> & v) const
{
float x = v. x * data[ 0 ] [ 0 ] + v. y * data[ 0 ] [ 1 ] + v. z * data[ 0 ] [ 2 ] + v. w * data[ 0 ] [ 3 ] ;
float y = v. x * data[ 1 ] [ 0 ] + v. y * data[ 1 ] [ 1 ] + v. z * data[ 1 ] [ 2 ] + v. w * data[ 1 ] [ 3 ] ;
float z = v. x * data[ 2 ] [ 0 ] + v. y * data[ 2 ] [ 1 ] + v. z * data[ 2 ] [ 2 ] + v. w * data[ 2 ] [ 3 ] ;
float w = v. x * data[ 3 ] [ 0 ] + v. y * data[ 3 ] [ 1 ] + v. z * data[ 3 ] [ 2 ] + v. w * data[ 3 ] [ 3 ] ;
Vector3< float > returnValue ( x, y, z, w) ;
return returnValue;
}
static Matrix4< T> get_model_matrix_translation ( const Matrix4& model, const Vector3< float > & trs)
{
Matrix4 trsModel;
trsModel. Identity ( ) ;
for ( int i = 0 ; i < 3 ; i++ )
trsModel. data[ i] [ 3 ] = trs[ i] ;
return trsModel * model;
}
static Matrix4 get_model_matrix_scale ( const Matrix4& model, const Vector3< float > & scale)
{
Matrix4 scaModel;
scaModel. Identity ( ) ;
for ( int i = 0 ; i < 3 ; i++ )
scaModel. data[ i] [ i] = scale[ i] ;
return scaModel * model;
}
static Matrix4 get_model_matrix_rotateX ( const Matrix4& model, float rotation_angle)
{
rotation_angle = rotation_angle / 180 * PI;
Matrix4 rotateModel;
rotateModel. Identity ( ) ;
rotateModel. data[ 1 ] [ 1 ] = cos ( rotation_angle) ;
rotateModel. data[ 1 ] [ 2 ] = - sin ( rotation_angle) ;
rotateModel. data[ 2 ] [ 1 ] = - rotateModel. data[ 1 ] [ 2 ] ;
rotateModel. data[ 2 ] [ 2 ] = rotateModel. data[ 1 ] [ 1 ] ;
return rotateModel * model;
}
static Matrix4 get_model_matrix_rotateY ( const Matrix4& model, float rotation_angle)
{
rotation_angle = rotation_angle / 180 * PI;
Matrix4 rotateModel;
rotateModel. Identity ( ) ;
rotateModel. data[ 0 ] [ 0 ] = cos ( rotation_angle) ;
rotateModel. data[ 0 ] [ 2 ] = sin ( rotation_angle) ;
rotateModel. data[ 2 ] [ 0 ] = - rotateModel. data[ 0 ] [ 2 ] ;
rotateModel. data[ 2 ] [ 2 ] = rotateModel. data[ 0 ] [ 0 ] ;
return rotateModel * model;
}
static Matrix4 get_model_matrix_rotateZ ( const Matrix4& model, float rotation_angle)
{
rotation_angle = rotation_angle / 180 * PI;
Matrix4 rotateModel;
rotateModel. Identity ( ) ;
rotateModel. data[ 0 ] [ 0 ] = cos ( rotation_angle) ;
rotateModel. data[ 0 ] [ 1 ] = - sin ( rotation_angle) ;
rotateModel. data[ 1 ] [ 0 ] = - rotateModel. data[ 0 ] [ 1 ] ;
rotateModel. data[ 1 ] [ 1 ] = rotateModel. data[ 0 ] [ 0 ] ;
return rotateModel * model;
}
static Matrix4 get_model_matrix_Rotate ( const Matrix4& model,
const Vector3< float > & axisT, float rotation_angle)
{
rotation_angle = rotation_angle / 180 * PI;
Vector3< float > axis = axisT;
axis. normalize ( ) ;
Matrix4 rotateModel;
rotateModel. Identity ( ) ;
rotateModel = { ( 1 - cos ( rotation_angle) ) * ( axis. x * axis. x) + cos ( rotation_angle) ,
( 1 - cos ( rotation_angle) ) * ( axis. x * axis. y) - axis. z * sin ( rotation_angle) ,
( 1 - cos ( rotation_angle) ) * ( axis. x * axis. z) + axis. y * sin ( rotation_angle) ,
( 1 - cos ( rotation_angle) ) * ( axis. x * axis. y) + axis. z * sin ( rotation_angle) ,
( 1 - cos ( rotation_angle) ) * ( axis. y * axis. y) + cos ( rotation_angle) ,
( 1 - cos ( rotation_angle) ) * ( axis. y * axis. z) - axis. x * sin ( rotation_angle) ,
( 1 - cos ( rotation_angle) ) * ( axis. x * axis. z) - axis. y * sin ( rotation_angle) ,
( 1 - cos ( rotation_angle) ) * ( axis. y * axis. z) + axis. x * sin ( rotation_angle) ,
( 1 - cos ( rotation_angle) ) * ( axis. z * axis. z) + cos ( rotation_angle) } ;
return rotateModel * model;
}
static Matrix4 get_view_matrix ( const Vector3< float > & eye_pos, const Vector3< float > & front,
const Vector3< float > & up)
{
Matrix4 view;
Matrix4 translate;
translate = { 1 , 0 , 0 , - eye_pos[ 0 ] , 0 , 1 , 0 , - eye_pos[ 1 ] , 0 , 0 , 1 ,
- eye_pos[ 2 ] , 0 , 0 , 0 , 1 } ;
Matrix4 rotate;
Vector3< float > gxt = Vector3 < float > :: Cross ( front, up) ;
rotate = { gxt[ 0 ] , gxt[ 1 ] , gxt[ 2 ] , 0 ,
up[ 0 ] , up[ 1 ] , up[ 2 ] , 0 ,
- front[ 0 ] , - front[ 1 ] , - front[ 2 ] , 0 ,
0 , 0 , 0 , 1 } ;
return rotate * translate;
}
static Matrix4 get_projection_matrix ( float eye_fov, float aspect_ratio,
float zNear, float zFar)
{
Matrix4 projection;
float f, n, l, r, b, t, fov;
fov = eye_fov / 180 * PI;
n = - zNear;
f = zFar;
t = tan ( fov / 2 ) * zNear;
b = - t;
r = t * aspect_ratio;
l = - r;
Matrix4 pertoorth;
pertoorth = { n, 0 , 0 , 0 ,
0 , n, 0 , 0 ,
0 , 0 , n + f, - n * f,
0 , 0 , 1 , 0 } ;
Matrix4 orth1;
orth1 = { 1 , 0 , 0 , - ( r + l) / 2 ,
0 , 1 , 0 , - ( t + b) / 2 ,
0 , 0 , 1 , - ( n + f) / 2 ,
0 , 0 , 0 , 1 } ;
Matrix4 orth2;
orth2 = { 2 / ( r - l) , 0 , 0 , 0 ,
0 , 2 / ( t - b) , 0 , 0 ,
0 , 0 , 2 / ( n - f) , 0 ,
0 , 0 , 0 , 1 } ;
projection = orth2 * orth1 * pertoorth;
return projection;
}
static Matrix4 get_viewport_matrix ( float width, float height)
{
Matrix4 viewport;
viewport. Identity ( ) ;
viewport. data[ 0 ] [ 0 ] = width / 2 ;
viewport. data[ 1 ] [ 1 ] = height / 2 ;
viewport. data[ 0 ] [ 3 ] = width / 2 ;
viewport. data[ 1 ] [ 3 ] = height / 2 ;
return viewport;
}
void Print ( )
{
std:: cout << "-----------------Matrix Begin--------------" << std:: endl;
for ( int i = 0 ; i < 4 ; ++ i)
{
for ( int j = 0 ; j < 4 ; ++ j)
{
std:: cout << "[" << data[ i] [ j] << "] " ;
}
std:: cout << std:: endl;
}
std:: cout << "-----------------Matrix End----------------" << std:: endl;
}
} ;
2.3 数学方法类
struct Myth
{
static float clampe ( float x, float mi, float ma)
{
if ( x < mi) x = mi;
if ( x > mi) x = ma;
return x;
}
template < typename T >
static Vector3< T> centerOfGravity ( const Vector3< T> & v1, const Vector3< T> & v2,
const Vector3< T> & v3, const Vector2< int > & p)
{
if ( ( - ( v1. x - v2. x) * ( v3. y - v2. y) + ( v1. y - v2. y) * ( v3. x - v2. x) ) == 0 )
return Vector3 < T> ( 1 , 0 , 0 ) ;
if ( - ( v2. x - v3. x) * ( v1. y - v3. y) + ( v2. y - v3. y) * ( v1. x - v3. x) == 0 )
return Vector3 < T> ( 1 , 0 , 0 ) ;
float alpha = ( - ( p. x - v2. x) * ( v3. y - v2. y) + ( p. y - v2. y) * ( v3. x - v2. x) ) / ( - ( v1. x - v2. x) * ( v3. y - v2. y) + ( v1. y - v2. y) * ( v3. x - v2. x) ) ;
float beta = ( - ( p. x - v3. x) * ( v1. y - v3. y) + ( p. y - v3. y) * ( v1. x - v3. x) ) / ( - ( v2. x - v3. x) * ( v1. y - v3. y) + ( v2. y - v3. y) * ( v1. x - v3. x) ) ;
float gamma = 1 - alpha - beta;
return Vector3 < T> ( alpha, beta, gamma) ;
}
template < typename T >
static Vector2< float > get_leftTop ( const Vector3< T> & v0, const Vector3< T> & v1, const Vector3< T> & v2)
{
return Vector2 < float > ( min ( v0. x, min ( v1. x, v2. x) ) , max ( v0. y, max ( v1. y, v2. y) ) ) ;
}
template < typename T >
static Vector2< float > get_rightBottom ( const Vector3< T> & v0, const Vector3< T> & v1, const Vector3< T> & v2)
{
return Vector2 < float > ( max ( v0. x, max ( v1. x, v2. x) ) , min ( v0. y, min ( v1. y, v2. y) ) ) ;
}
static bool isInTriangle ( const Vector3< float > & pos, const Vector3< float > & pos0,
const Vector3< float > & pos1, const Vector3< float > & pos2)
{
Vector3< float > res1 = Vector3 < float > :: Cross ( ( pos - pos0) , ( pos1 - pos0) ) ;
Vector3< float > res2 = Vector3 < float > :: Cross ( ( pos - pos1) , ( pos2 - pos1) ) ;
Vector3< float > res3 = Vector3 < float > :: Cross ( ( pos - pos2) , ( pos0 - pos2) ) ;
if ( res1. z * res2. z > 0 && res1. z * res3. z > 0 && res2. z * res3. z > 0 )
return true ;
else
return false ;
}
static bool isInNDC ( const Vector3< float > & pos)
{
return ( ( abs ( pos. x) > 1 ) + ( abs ( pos. y) > 1 ) + ( abs ( pos. z) > 1 ) ) != 3 ;
}
} ;
2.4 图元
struct VertexData
{
VertexData ( ) { }
VertexData ( Vector3< float > pos, Vector2< float > texCoor = Vector2 < float > ( 0 , 0 ) , Vector3< float > nor = Vector3 < float > ( 0 , 0 , 0 , 1 ) , Vector3< float > colr = Vector3 < float > ( 255 , 255 , 255 , 1 ) ) :
position ( pos) , uv ( texCoor) , normal ( nor) , color ( colr) { }
Vector3< float > position;
Vector2< float > uv;
Vector3< float > normal;
Vector3< float > color;
} ;
struct Triangle
{
VertexData vertex[ 3 ] ;
Vector3< float > getNormal ( )
{
Vector3< float > v1 = vertex[ 1 ] . position - vertex[ 0 ] . position;
Vector3< float > v2 = vertex[ 2 ] . position - vertex[ 1 ] . position;
return Vector3 < float > :: Cross ( v2, v1) ;
}
} ;
2.5 渲染管线
struct Pixel
{
Pixel ( VertexData data, Vector2< int > posi) :
verdata ( data) , pos ( posi) { }
VertexData verdata;
Vector2< int > pos;
} ;
class Camera
{
public :
Camera ( ) { }
Camera ( Vector3< float > posT, Vector3< float > frontT,
Vector3< float > upT) : pos ( posT) , front ( frontT) , up ( upT) { }
Vector3< float > pos;
Vector3< float > front;
Vector3< float > up;
} ;
class Render
{
public :
int width, height;
Render ( int screenWidth, int screenHeight) : width ( screenWidth) , height ( screenHeight) { }
std:: vector< Triangle> in_triangle;
void assemblingElements ( const Mesh& mesh)
{
for ( int i = 0 ; i <= mesh. indexBuffer. size ( ) - 3 ; i += 3 )
{
Triangle trian;
for ( int j = 0 ; j < 3 ; ++ j)
{
trian. vertex[ j] . position = mesh. positionBuffer[ mesh. indexBuffer[ i + j] . x] ;
trian. vertex[ j] . uv = mesh. uvBuffer[ mesh. indexBuffer[ i + j] . y] ;
trian. vertex[ j] . normal = mesh. normalBuffer[ mesh. indexBuffer[ i + j] . z] ;
trian. vertex[ j] . color = mesh. colorBuffer[ mesh. indexBuffer[ i + j] . x] ;
}
in_triangle. push_back ( trian) ;
}
}
Matrix4< float > model, view, projection, viewport;
void setMatrix ( Matrix4< float > modelT, Matrix4< float > viewT,
Matrix4< float > projectionT, Matrix4< float > viewportT)
{
model = modelT;
view = viewT;
projection = projectionT;
viewport = viewportT;
}
std:: vector< Triangle> out_triangle;
Camera camera;
bool isBackCulling;
void backfaceCulling ( )
{
std:: vector< Triangle> :: iterator it = out_triangle. begin ( ) ;
while ( it != out_triangle. end ( ) )
{
Vector3< float > v = camera. front;
Vector3< float > n = ( * it) . getNormal ( ) ;
float value = v * n;
if ( value < 0 )
it = out_triangle. erase ( it) ;
else
++ it;
}
}
bool isViewClipping;
void viewFrustumClipping ( )
{
std:: vector< Triangle> :: iterator it = out_triangle. begin ( ) ;
while ( it != out_triangle. end ( ) )
{
int index = 0 ;
for ( int j = 0 ; j < 3 ; ++ j)
index += Myth :: isInNDC ( ( * it) . vertex[ j] . position) ;
if ( ! index)
it = out_triangle. erase ( it) ;
else
++ it;
}
}
void vertexShader ( )
{
out_triangle. clear ( ) ;
for ( int i = 0 ; i < in_triangle. size ( ) ; ++ i)
{
Triangle trans;
trans = in_triangle[ i] ;
for ( int j = 0 ; j < 3 ; ++ j)
{
trans. vertex[ j] . position = projection * view * model * in_triangle[ i] . vertex[ j] . position;
trans. vertex[ j] . position. standard ( ) ;
}
out_triangle. push_back ( trans) ;
}
if ( isViewClipping)
viewFrustumClipping ( ) ;
if ( isBackCulling)
backfaceCulling ( ) ;
for ( int i = 0 ; i < out_triangle. size ( ) ; i++ )
for ( int j = 0 ; j < 3 ; ++ j)
out_triangle[ i] . vertex[ j] . position =
viewport * out_triangle[ i] . vertex[ j] . position;
}
std:: vector< Pixel> pixels;
class map_key_comp
{
public :
bool operator ( ) ( const Vector2< int > & lhs, const Vector2< int > & rhs) const
{
return lhs. x < rhs. x || ( lhs. x == rhs. x && lhs. y < rhs. y) ;
}
} ;
std:: map< Vector2< int > , float , map_key_comp> zBuffer;
bool isTestZ;
void rasterization ( )
{
pixels. clear ( ) ;
zBuffer. clear ( ) ;
for ( int i = 0 ; i < out_triangle. size ( ) ; ++ i)
{
Vector3< float > posArr[ 3 ] = {
out_triangle[ i] . vertex[ 0 ] . position ,
out_triangle[ i] . vertex[ 1 ] . position ,
out_triangle[ i] . vertex[ 2 ] . position } ;
Vector2< float > leftTop = Myth :: get_leftTop ( posArr[ 0 ] , posArr[ 1 ] , posArr[ 2 ] ) ;
Vector2< float > rightBottom = Myth :: get_rightBottom ( posArr[ 0 ] , posArr[ 1 ] , posArr[ 2 ] ) ;
for ( int x = leftTop. x; x <= rightBottom. x; ++ x)
{
for ( int y = leftTop. y; y >= rightBottom. y; -- y)
{
const Vector2< int > pixPos ( x, y) ;
bool isInTriangle = Myth :: isInTriangle ( Vector3 < float > ( x, y, 0 ) , posArr[ 0 ] , posArr[ 1 ] , posArr[ 2 ] ) ;
if ( isInTriangle)
{
Vector3< float > abg = Myth :: centerOfGravity (
posArr[ 0 ] , posArr[ 1 ] , posArr[ 2 ] , pixPos) ;
float z = posArr[ 0 ] . z * abg. x +
posArr[ 1 ] . z * abg. y + posArr[ 2 ] . z * abg. z;
if ( zBuffer. count ( pixPos) )
{
if ( z <= zBuffer[ pixPos] )
;
else
zBuffer[ pixPos] = z;
}
else
zBuffer[ pixPos] = z;
Vector3< float > color = out_triangle[ i] . vertex[ 0 ] . color * abg. x +
out_triangle[ i] . vertex[ 1 ] . color * abg. y + out_triangle[ i] . vertex[ 2 ] . color * abg. z;
Vector3< float > normal = out_triangle[ i] . vertex[ 0 ] . normal * abg. x +
out_triangle[ i] . vertex[ 1 ] . normal * abg. y + out_triangle[ i] . vertex[ 2 ] . normal * abg. z;
Vector2< float > uv = out_triangle[ i] . vertex[ 0 ] . uv * abg. x +
out_triangle[ i] . vertex[ 1 ] . uv * abg. y + out_triangle[ i] . vertex[ 2 ] . uv * abg. z;
VertexData verdata ( Vector3 < float > ( 0 , 0 , z) , uv, normal, color) ;
pixels. push_back ( Pixel ( verdata, Vector2 < int > ( x, y) ) ) ;
}
}
}
}
}
void testZ ( )
{
std:: vector< Pixel> :: iterator it = pixels. begin ( ) ;
while ( it != pixels. end ( ) )
{
if ( ( * it) . verdata. position. z < zBuffer[ ( * it) . pos] )
it = pixels. erase ( it) ;
else
++ it;
}
}
void renderingPipeline ( )
{
vertexShader ( ) ;
rasterization ( ) ;
if ( isTestZ)
testZ ( ) ;
}
} ;
三、附录
工程使用EasyX进行绘制,可以下载EasyX安装其库。 ScreenWindow负责读取render的pixel片段缓冲进行绘制,如果您对哪里有疑问,欢迎评论! 本系列还将继续完善此软光栅,如果您觉得本文有哪里欠缺,请一定不吝赐教,期待您的评论!
3.1 完整工程源代码
按键控制摄像机移动,而摄像机移动方向和物体移动方向相反,因此你按D键物体会向左移动。 请修改工程代码main函数中OBJ文件路径,您可以将3.2中OBJ文件复制保存到本地.txt文件中,使用.txt的文件地址替换OBJ文件路径即可。
# include <map>
# include <time.h>
# include <conio.h>
# include <vector>
# include <Windows.h>
# include <fstream>
# include <string>
# include <iostream>
# include <graphics.h>
# define PI 3.1415926535
# define DEBUG 1
const int screenWidth = 800 , screenHeight = 600 ;
template < typename T >
struct Vector3
{
T x, y, z, w;
Vector3 < T> ( ) : x ( 0 ) , y ( 0 ) , z ( 0 ) , w ( 1 ) { }
Vector3 < T> ( T vx, T vy, T vz) : x ( vx) , y ( vy) , z ( vz) , w ( 1 ) { }
Vector3 < T> ( T vx, T vy, T vz, T vw) : x ( vx) , y ( vy) , z ( vz) , w ( vw) { }
Vector3< T> operator * ( const T right) const
{
return Vector3 < T> ( x * right, y * right, z * right) ;
}
T operator * ( const Vector3< T> right) const
{
return this -> x * right. x + this -> y * right. y + this -> z * right. z;
}
T operator [ ] ( int index) const
{
return index == 0 ? x : ( index == 1 ? y : ( index == 2 ? z : w) ) ;
}
Vector3< T> operator + ( const Vector3< T> & right) const
{
return Vector3 < T> ( this -> x + right. x, this -> y + right. y, this -> z + right. z) ;
}
Vector3< T> operator - ( ) const
{
return Vector3 < T> ( - x, - y, - z) ;
}
Vector3< T> operator - ( const Vector3< T> & right) const
{
return Vector3 < T> ( this -> x - right. x, this -> y - right. y, this -> z - right. z) ;
}
static Vector3< T> Dot ( const Vector3< T> & left, const Vector3< T> & right)
{
return left. x * right. x + left. y * right. y + left. z * right. z;
}
static Vector3< T> Cross ( const Vector3< T> & left, const Vector3< T> & right)
{
return Vector3 < T> (
left. y * right. z - left. z * right. y,
left. z * right. x - left. x * right. z,
left. x * right. y - left. y * right. x
) ;
}
float getLength ( )
{
return sqrt ( x * x + y * y + z * z) ;
}
void normalize ( )
{
float length = getLength ( ) ;
if ( length== 0 )
return ;
x /= length;
y /= length;
z /= length;
}
void standard ( )
{
if ( ! w)
return ;
x /= w;
y /= w;
z /= w;
w = 1 ;
}
void print ( )
{
std:: cout << "[" << x << "," << y << "," << z << "]" ;
}
} ;
typedef Vector3< float > Color;
template < typename T >
struct Vector2
{
Vector2 < T> ( ) : x ( 0 ) , y ( 0 ) { }
Vector2 < T> ( T vx, T vy) : x ( vx) , y ( vy) { }
T x, y;
Vector2< T> operator + ( const Vector2& right)
{
return Vector2 ( x + right. x, y + right. y) ;
}
Vector2< T> operator * ( float value)
{
return Vector2 ( x * value, y * value) ;
}
void print ( )
{
std:: cout << "[" << x << "," << y << "]" ;
}
} ;
struct Mesh
{
std:: vector< Vector3< float >> positionBuffer;
std:: vector< Vector2< float >> uvBuffer;
std:: vector< Vector3< float >> normalBuffer;
std:: vector< Vector3< float >> colorBuffer;
std:: vector< Vector3< int >> indexBuffer;
void stringSplit ( std:: string s, char splitchar, std:: vector< std:: string> & vec)
{
if ( vec. size ( ) > 0 )
vec. clear ( ) ;
int length = s. length ( ) ;
int start = s[ 0 ] == splitchar ? 1 : 0 ;
for ( int i = 0 ; i < length; ++ i)
{
if ( s[ i] == splitchar)
{
vec. push_back ( s. substr ( start, i - start) ) ;
start = i + 1 ;
}
else if ( i == length - 1 )
vec. push_back ( s. substr ( start, i + 1 - start) ) ;
}
}
void readObjFile ( std:: string path)
{
std:: ifstream in ( path) ;
std:: string txt = "" ;
if ( in)
{
while ( std:: getline ( in, txt) )
{
if ( txt[ 0 ] == 'v' && txt[ 1 ] == ' ' )
{
std:: vector< std:: string> num;
txt. erase ( 0 , 2 ) ;
stringSplit ( txt, ' ' , num) ;
Vector3< float > pos;
pos = Vector3 < float > ( ( float ) atof ( num[ 0 ] . c_str ( ) ) , ( float ) atof ( num[ 1 ] . c_str ( ) ) , ( float ) atof ( num[ 2 ] . c_str ( ) ) ) ;
this -> positionBuffer. push_back ( pos) ;
}
else if ( txt[ 0 ] == 'v' && txt[ 1 ] == 'n' )
{
std:: vector< std:: string> num;
txt. erase ( 0 , 3 ) ;
stringSplit ( txt, ' ' , num) ;
Vector3< float > n = Vector3 < float > ( ( float ) atof ( num[ 0 ] . c_str ( ) ) , ( float ) atof ( num[ 1 ] . c_str ( ) ) , ( float ) atof ( num[ 2 ] . c_str ( ) ) , 0.0 ) ;
this -> normalBuffer. push_back ( n) ;
}
else if ( txt[ 0 ] == 'v' && txt[ 1 ] == 't' )
{
std:: vector< std:: string> num;
txt. erase ( 0 , 3 ) ;
stringSplit ( txt, ' ' , num) ;
this -> uvBuffer. push_back ( Vector2 < float > ( ( float ) atof ( num[ 0 ] . c_str ( ) ) , ( float ) atof ( num[ 1 ] . c_str ( ) ) ) ) ;
}
else if ( txt[ 0 ] == 'f' && txt[ 1 ] == ' ' )
{
std:: vector< std:: string> num;
txt. erase ( 0 , 2 ) ;
stringSplit ( txt, ' ' , num) ;
for ( int i = 0 ; i < num. size ( ) ; ++ i)
{
std:: vector< std:: string> threeIndex;
stringSplit ( num[ i] , '/' , threeIndex) ;
Vector3< int > indexes = { atoi ( threeIndex[ 0 ] . c_str ( ) ) - 1 , atoi ( threeIndex[ 1 ] . c_str ( ) ) - 1 , atoi ( threeIndex[ 2 ] . c_str ( ) ) - 1 } ;
this -> indexBuffer. push_back ( indexes) ;
}
}
}
}
else
std:: cout << "no file" << std:: endl;
}
void print ( )
{
std:: cout << "Mesh data:" << std:: endl;
for ( int i = 0 ; i < positionBuffer. size ( ) ; ++ i)
{
std:: cout << "v " ;
positionBuffer[ i] . print ( ) ;
std:: cout << std:: endl;
}
std:: cout << std:: endl;
for ( int i = 0 ; i < uvBuffer. size ( ) ; ++ i)
{
std:: cout << "vt " ;
uvBuffer[ i] . print ( ) ;
std:: cout << std:: endl;
}
std:: cout << std:: endl;
for ( int i = 0 ; i < normalBuffer. size ( ) ; ++ i)
{
std:: cout << "vn " ;
normalBuffer[ i] . print ( ) ;
std:: cout << std:: endl;
}
std:: cout << std:: endl;
for ( int i = 0 ; i <= indexBuffer. size ( ) - 3 ; i += 3 )
{
std:: cout << "f " ;
indexBuffer[ i] . print ( ) ;
std:: cout << " " ;
indexBuffer[ i + 1 ] . print ( ) ;
std:: cout << " " ;
indexBuffer[ i + 2 ] . print ( ) ;
std:: cout << std:: endl;
}
std:: cout << "end" << std:: endl;
}
} ;
template < typename T >
struct Matrix4
{
Matrix4 ( ) { }
Matrix4 ( const std:: initializer_list< float > & list)
{
auto begin = list. begin ( ) ;
auto end = list. end ( ) ;
int i = 0 , j = 0 ;
while ( begin != end)
{
data[ i] [ j++ ] = * begin;
if ( j > 3 )
{
++ i;
j = 0 ;
}
++ begin;
}
}
T data[ 4 ] [ 4 ] = { } ;
void Identity ( )
{
for ( int i = 0 ; i < 4 ; ++ i)
data[ i] [ i] = 1 ;
}
Matrix4< T> operator * ( const Matrix4< T> & right) const
{
Matrix4 res;
for ( int i = 0 ; i < 4 ; ++ i)
{
for ( int j = 0 ; j < 4 ; ++ j)
{
for ( int k = 0 ; k < 4 ; ++ k)
{
res. data[ i] [ j] += this -> data[ i] [ k] * right. data[ k] [ j] ;
}
}
}
return res;
}
Vector3< T> operator * ( const Vector3< T> & v) const
{
float x = v. x * data[ 0 ] [ 0 ] + v. y * data[ 0 ] [ 1 ] + v. z * data[ 0 ] [ 2 ] + v. w * data[ 0 ] [ 3 ] ;
float y = v. x * data[ 1 ] [ 0 ] + v. y * data[ 1 ] [ 1 ] + v. z * data[ 1 ] [ 2 ] + v. w * data[ 1 ] [ 3 ] ;
float z = v. x * data[ 2 ] [ 0 ] + v. y * data[ 2 ] [ 1 ] + v. z * data[ 2 ] [ 2 ] + v. w * data[ 2 ] [ 3 ] ;
float w = v. x * data[ 3 ] [ 0 ] + v. y * data[ 3 ] [ 1 ] + v. z * data[ 3 ] [ 2 ] + v. w * data[ 3 ] [ 3 ] ;
Vector3< float > returnValue ( x, y, z, w) ;
return returnValue;
}
static Matrix4< T> get_model_matrix_translation ( const Matrix4& model, const Vector3< float > & trs)
{
Matrix4 trsModel;
trsModel. Identity ( ) ;
for ( int i = 0 ; i < 3 ; i++ )
trsModel. data[ i] [ 3 ] = trs[ i] ;
return trsModel * model;
}
static Matrix4 get_model_matrix_scale ( const Matrix4& model, const Vector3< float > & scale)
{
Matrix4 scaModel;
scaModel. Identity ( ) ;
for ( int i = 0 ; i < 3 ; i++ )
scaModel. data[ i] [ i] = scale[ i] ;
return scaModel * model;
}
static Matrix4 get_model_matrix_rotateX ( const Matrix4& model, float rotation_angle)
{
rotation_angle = rotation_angle / 180 * PI;
Matrix4 rotateModel;
rotateModel. Identity ( ) ;
rotateModel. data[ 1 ] [ 1 ] = cos ( rotation_angle) ;
rotateModel. data[ 1 ] [ 2 ] = - sin ( rotation_angle) ;
rotateModel. data[ 2 ] [ 1 ] = - rotateModel. data[ 1 ] [ 2 ] ;
rotateModel. data[ 2 ] [ 2 ] = rotateModel. data[ 1 ] [ 1 ] ;
return rotateModel * model;
}
static Matrix4 get_model_matrix_rotateY ( const Matrix4& model, float rotation_angle)
{
rotation_angle = rotation_angle / 180 * PI;
Matrix4 rotateModel;
rotateModel. Identity ( ) ;
rotateModel. data[ 0 ] [ 0 ] = cos ( rotation_angle) ;
rotateModel. data[ 0 ] [ 2 ] = sin ( rotation_angle) ;
rotateModel. data[ 2 ] [ 0 ] = - rotateModel. data[ 0 ] [ 2 ] ;
rotateModel. data[ 2 ] [ 2 ] = rotateModel. data[ 0 ] [ 0 ] ;
return rotateModel * model;
}
static Matrix4 get_model_matrix_rotateZ ( const Matrix4& model, float rotation_angle)
{
rotation_angle = rotation_angle / 180 * PI;
Matrix4 rotateModel;
rotateModel. Identity ( ) ;
rotateModel. data[ 0 ] [ 0 ] = cos ( rotation_angle) ;
rotateModel. data[ 0 ] [ 1 ] = - sin ( rotation_angle) ;
rotateModel. data[ 1 ] [ 0 ] = - rotateModel. data[ 0 ] [ 1 ] ;
rotateModel. data[ 1 ] [ 1 ] = rotateModel. data[ 0 ] [ 0 ] ;
return rotateModel * model;
}
static Matrix4 get_model_matrix_Rotate ( const Matrix4& model,
const Vector3< float > & axisT, float rotation_angle)
{
rotation_angle = rotation_angle / 180 * PI;
Vector3< float > axis = axisT;
axis. normalize ( ) ;
Matrix4 rotateModel;
rotateModel. Identity ( ) ;
rotateModel = { ( 1 - cos ( rotation_angle) ) * ( axis. x * axis. x) + cos ( rotation_angle) ,
( 1 - cos ( rotation_angle) ) * ( axis. x * axis. y) - axis. z * sin ( rotation_angle) ,
( 1 - cos ( rotation_angle) ) * ( axis. x * axis. z) + axis. y * sin ( rotation_angle) ,
( 1 - cos ( rotation_angle) ) * ( axis. x * axis. y) + axis. z * sin ( rotation_angle) ,
( 1 - cos ( rotation_angle) ) * ( axis. y * axis. y) + cos ( rotation_angle) ,
( 1 - cos ( rotation_angle) ) * ( axis. y * axis. z) - axis. x * sin ( rotation_angle) ,
( 1 - cos ( rotation_angle) ) * ( axis. x * axis. z) - axis. y * sin ( rotation_angle) ,
( 1 - cos ( rotation_angle) ) * ( axis. y * axis. z) + axis. x * sin ( rotation_angle) ,
( 1 - cos ( rotation_angle) ) * ( axis. z * axis. z) + cos ( rotation_angle) } ;
return rotateModel * model;
}
static Matrix4 get_view_matrix ( const Vector3< float > & eye_pos, const Vector3< float > & front,
const Vector3< float > & up)
{
Matrix4 view;
Matrix4 translate;
translate = { 1 , 0 , 0 , - eye_pos[ 0 ] , 0 , 1 , 0 , - eye_pos[ 1 ] , 0 , 0 , 1 ,
- eye_pos[ 2 ] , 0 , 0 , 0 , 1 } ;
Matrix4 rotate;
Vector3< float > gxt = Vector3 < float > :: Cross ( front, up) ;
rotate = { gxt[ 0 ] , gxt[ 1 ] , gxt[ 2 ] , 0 ,
up[ 0 ] , up[ 1 ] , up[ 2 ] , 0 ,
- front[ 0 ] , - front[ 1 ] , - front[ 2 ] , 0 ,
0 , 0 , 0 , 1 } ;
return rotate * translate;
}
static Matrix4 get_projection_matrix ( float eye_fov, float aspect_ratio,
float zNear, float zFar)
{
Matrix4 projection;
float f, n, l, r, b, t, fov;
fov = eye_fov / 180 * PI;
n = - zNear;
f = zFar;
t = tan ( fov / 2 ) * zNear;
b = - t;
r = t * aspect_ratio;
l = - r;
Matrix4 pertoorth;
pertoorth = { n, 0 , 0 , 0 ,
0 , n, 0 , 0 ,
0 , 0 , n + f, - n * f,
0 , 0 , 1 , 0 } ;
Matrix4 orth1;
orth1 = { 1 , 0 , 0 , - ( r + l) / 2 ,
0 , 1 , 0 , - ( t + b) / 2 ,
0 , 0 , 1 , - ( n + f) / 2 ,
0 , 0 , 0 , 1 } ;
Matrix4 orth2;
orth2 = { 2 / ( r - l) , 0 , 0 , 0 ,
0 , 2 / ( t - b) , 0 , 0 ,
0 , 0 , 2 / ( n - f) , 0 ,
0 , 0 , 0 , 1 } ;
projection = orth2 * orth1 * pertoorth;
return projection;
}
static Matrix4 get_viewport_matrix ( float width, float height)
{
Matrix4 viewport;
viewport. Identity ( ) ;
viewport. data[ 0 ] [ 0 ] = width / 2 ;
viewport. data[ 1 ] [ 1 ] = height / 2 ;
viewport. data[ 0 ] [ 3 ] = width / 2 ;
viewport. data[ 1 ] [ 3 ] = height / 2 ;
return viewport;
}
void Print ( )
{
std:: cout << "-----------------Matrix Begin--------------" << std:: endl;
for ( int i = 0 ; i < 4 ; ++ i)
{
for ( int j = 0 ; j < 4 ; ++ j)
{
std:: cout << "[" << data[ i] [ j] << "] " ;
}
std:: cout << std:: endl;
}
std:: cout << "-----------------Matrix End----------------" << std:: endl;
}
} ;
struct VertexData
{
VertexData ( ) { }
VertexData ( Vector3< float > pos, Vector2< float > texCoor = Vector2 < float > ( 0 , 0 ) , Vector3< float > nor = Vector3 < float > ( 0 , 0 , 0 , 1 ) , Vector3< float > colr = Vector3 < float > ( 255 , 255 , 255 , 1 ) ) :
position ( pos) , uv ( texCoor) , normal ( nor) , color ( colr) { }
Vector3< float > position;
Vector2< float > uv;
Vector3< float > normal;
Vector3< float > color;
} ;
struct Triangle
{
VertexData vertex[ 3 ] ;
Vector3< float > getNormal ( )
{
Vector3< float > v1 = vertex[ 1 ] . position - vertex[ 0 ] . position;
Vector3< float > v2 = vertex[ 2 ] . position - vertex[ 1 ] . position;
return Vector3 < float > :: Cross ( v2, v1) ;
}
} ;
struct Pixel
{
Pixel ( VertexData data, Vector2< int > posi) :
verdata ( data) , pos ( posi) { }
VertexData verdata;
Vector2< int > pos;
} ;
struct Myth
{
static float clampe ( float x, float mi, float ma)
{
if ( x < mi) x = mi;
if ( x > mi) x = ma;
return x;
}
template < typename T >
static Vector3< T> centerOfGravity ( const Vector3< T> & v1, const Vector3< T> & v2,
const Vector3< T> & v3, const Vector2< int > & p)
{
if ( ( - ( v1. x - v2. x) * ( v3. y - v2. y) + ( v1. y - v2. y) * ( v3. x - v2. x) ) == 0 )
return Vector3 < T> ( 1 , 0 , 0 ) ;
if ( - ( v2. x - v3. x) * ( v1. y - v3. y) + ( v2. y - v3. y) * ( v1. x - v3. x) == 0 )
return Vector3 < T> ( 1 , 0 , 0 ) ;
float alpha = ( - ( p. x - v2. x) * ( v3. y - v2. y) + ( p. y - v2. y) * ( v3. x - v2. x) ) / ( - ( v1. x - v2. x) * ( v3. y - v2. y) + ( v1. y - v2. y) * ( v3. x - v2. x) ) ;
float beta = ( - ( p. x - v3. x) * ( v1. y - v3. y) + ( p. y - v3. y) * ( v1. x - v3. x) ) / ( - ( v2. x - v3. x) * ( v1. y - v3. y) + ( v2. y - v3. y) * ( v1. x - v3. x) ) ;
float gamma = 1 - alpha - beta;
return Vector3 < T> ( alpha, beta, gamma) ;
}
template < typename T >
static Vector2< float > get_leftTop ( const Vector3< T> & v0, const Vector3< T> & v1, const Vector3< T> & v2)
{
return Vector2 < float > ( min ( v0. x, min ( v1. x, v2. x) ) , max ( v0. y, max ( v1. y, v2. y) ) ) ;
}
template < typename T >
static Vector2< float > get_rightBottom ( const Vector3< T> & v0, const Vector3< T> & v1, const Vector3< T> & v2)
{
return Vector2 < float > ( max ( v0. x, max ( v1. x, v2. x) ) , min ( v0. y, min ( v1. y, v2. y) ) ) ;
}
static bool isInTriangle ( const Vector3< float > & pos, const Vector3< float > & pos0,
const Vector3< float > & pos1, const Vector3< float > & pos2)
{
Vector3< float > res1 = Vector3 < float > :: Cross ( ( pos - pos0) , ( pos1 - pos0) ) ;
Vector3< float > res2 = Vector3 < float > :: Cross ( ( pos - pos1) , ( pos2 - pos1) ) ;
Vector3< float > res3 = Vector3 < float > :: Cross ( ( pos - pos2) , ( pos0 - pos2) ) ;
if ( res1. z * res2. z > 0 && res1. z * res3. z > 0 && res2. z * res3. z > 0 )
return true ;
else
return false ;
}
static bool isInNDC ( const Vector3< float > & pos)
{
return ( ( abs ( pos. x) > 1 ) + ( abs ( pos. y) > 1 ) + ( abs ( pos. z) > 1 ) ) != 3 ;
}
} ;
class Camera
{
public :
Camera ( ) { }
Camera ( Vector3< float > posT, Vector3< float > frontT,
Vector3< float > upT) : pos ( posT) , front ( frontT) , up ( upT) { }
Vector3< float > pos;
Vector3< float > front;
Vector3< float > up;
} ;
class Render
{
public :
int width, height;
Render ( int screenWidth, int screenHeight) : width ( screenWidth) , height ( screenHeight) { }
std:: vector< Triangle> in_triangle;
void assemblingElements ( const Mesh& mesh)
{
for ( int i = 0 ; i <= mesh. indexBuffer. size ( ) - 3 ; i += 3 )
{
Triangle trian;
for ( int j = 0 ; j < 3 ; ++ j)
{
trian. vertex[ j] . position = mesh. positionBuffer[ mesh. indexBuffer[ i + j] . x] ;
trian. vertex[ j] . uv = mesh. uvBuffer[ mesh. indexBuffer[ i + j] . y] ;
trian. vertex[ j] . normal = mesh. normalBuffer[ mesh. indexBuffer[ i + j] . z] ;
trian. vertex[ j] . color = mesh. colorBuffer[ mesh. indexBuffer[ i + j] . x] ;
}
in_triangle. push_back ( trian) ;
}
}
Matrix4< float > model, view, projection, viewport;
void setMatrix ( Matrix4< float > modelT, Matrix4< float > viewT,
Matrix4< float > projectionT, Matrix4< float > viewportT)
{
model = modelT;
view = viewT;
projection = projectionT;
viewport = viewportT;
}
std:: vector< Triangle> out_triangle;
Camera camera;
bool isBackCulling;
void backfaceCulling ( )
{
# if DEBUG
std:: cout << std:: endl << "-----------------backfaceCulling Begin-----------------" << std:: endl;
# endif
std:: vector< Triangle> :: iterator it = out_triangle. begin ( ) ;
while ( it != out_triangle. end ( ) )
{
Vector3< float > v = camera. front;
Vector3< float > n = ( * it) . getNormal ( ) ;
float value = v * n;
if ( value < 0 )
it = out_triangle. erase ( it) ;
else
++ it;
}
# if DEBUG
std:: cout << std:: endl << "out:" << out_triangle. size ( ) << std:: endl;
for ( int i = 0 ; i < out_triangle. size ( ) ; ++ i)
{
std:: cout << "Triangle " << i << " :" << std:: endl;
out_triangle[ i] . vertex[ 0 ] . position. print ( ) ;
std:: cout << std:: endl;
out_triangle[ i] . vertex[ 1 ] . position. print ( ) ;
std:: cout << std:: endl;
out_triangle[ i] . vertex[ 2 ] . position. print ( ) ;
std:: cout << std:: endl;
}
std:: cout << std:: endl << "-----------------backfaceCulling End-----------------" << std:: endl;
# endif
}
bool isViewClipping;
void viewFrustumClipping ( )
{
# if DEBUG
std:: cout << std:: endl << "-----------------viewFrustumClipping Begin-----------------" << std:: endl;
# endif
std:: vector< Triangle> :: iterator it = out_triangle. begin ( ) ;
while ( it != out_triangle. end ( ) )
{
int index = 0 ;
for ( int j = 0 ; j < 3 ; ++ j)
index += Myth :: isInNDC ( ( * it) . vertex[ j] . position) ;
if ( ! index)
it = out_triangle. erase ( it) ;
else
++ it;
}
# if DEBUG
std:: cout << std:: endl << "out:" << out_triangle. size ( ) << std:: endl;
for ( int i = 0 ; i < out_triangle. size ( ) ; ++ i)
{
std:: cout << "Triangle " << i << " :" << std:: endl;
out_triangle[ i] . vertex[ 0 ] . position. print ( ) ;
std:: cout << std:: endl;
out_triangle[ i] . vertex[ 1 ] . position. print ( ) ;
std:: cout << std:: endl;
out_triangle[ i] . vertex[ 2 ] . position. print ( ) ;
std:: cout << std:: endl;
}
std:: cout << std:: endl << "-----------------viewFrustumClipping End-----------------" << std:: endl;
# endif
}
void vertexShader ( )
{
# if DEBUG
std:: cout << std:: endl << "-----------------vertexShader Begin-----------------" << std:: endl;
std:: cout << std:: endl << "in:" << in_triangle. size ( ) << std:: endl;
for ( int i = 0 ; i < in_triangle. size ( ) ; ++ i)
{
std:: cout << "Triangle " << i << " :" << std:: endl;
in_triangle[ i] . vertex[ 0 ] . position. print ( ) ;
std:: cout << std:: endl;
in_triangle[ i] . vertex[ 1 ] . position. print ( ) ;
std:: cout << std:: endl;
in_triangle[ i] . vertex[ 2 ] . position. print ( ) ;
std:: cout << std:: endl;
}
# endif
out_triangle. clear ( ) ;
for ( int i = 0 ; i < in_triangle. size ( ) ; ++ i)
{
Triangle trans;
trans = in_triangle[ i] ;
for ( int j = 0 ; j < 3 ; ++ j)
{
trans. vertex[ j] . position = projection * view * model * in_triangle[ i] . vertex[ j] . position;
trans. vertex[ j] . position. standard ( ) ;
}
out_triangle. push_back ( trans) ;
}
# if DEBUG
std:: cout << std:: endl << "out:" << out_triangle. size ( ) << std:: endl;
for ( int i = 0 ; i < out_triangle. size ( ) ; ++ i)
{
std:: cout << "Triangle " << i << " :" << std:: endl;
out_triangle[ i] . vertex[ 0 ] . position. print ( ) ;
std:: cout << std:: endl;
out_triangle[ i] . vertex[ 1 ] . position. print ( ) ;
std:: cout << std:: endl;
out_triangle[ i] . vertex[ 2 ] . position. print ( ) ;
std:: cout << std:: endl;
}
std:: cout << std:: endl << "-----------------vertexShader endmodel-----------------" << std:: endl;
# endif
if ( isViewClipping)
viewFrustumClipping ( ) ;
if ( isBackCulling)
backfaceCulling ( ) ;
for ( int i = 0 ; i < out_triangle. size ( ) ; i++ )
for ( int j = 0 ; j < 3 ; ++ j)
out_triangle[ i] . vertex[ j] . position =
viewport * out_triangle[ i] . vertex[ j] . position;
# if DEBUG
std:: cout << std:: endl << "out:" << out_triangle. size ( ) << std:: endl;
for ( int i = 0 ; i < out_triangle. size ( ) ; ++ i)
{
std:: cout << "Triangle " << i << " :" << std:: endl;
out_triangle[ i] . vertex[ 0 ] . position. print ( ) ;
std:: cout << std:: endl;
out_triangle[ i] . vertex[ 1 ] . position. print ( ) ;
std:: cout << std:: endl;
out_triangle[ i] . vertex[ 2 ] . position. print ( ) ;
std:: cout << std:: endl;
}
std:: cout << std:: endl << "-----------------vertexShader end-----------------" << std:: endl;
# endif
}
std:: vector< Pixel> pixels;
class map_key_comp
{
public :
bool operator ( ) ( const Vector2< int > & lhs, const Vector2< int > & rhs) const
{
return lhs. x < rhs. x || ( lhs. x == rhs. x && lhs. y < rhs. y) ;
}
} ;
std:: map< Vector2< int > , float , map_key_comp> zBuffer;
bool isTestZ;
void rasterization ( )
{
# if DEBUG
std:: cout << std:: endl << "-----------------rasterization Begin-----------------" << std:: endl;
std:: cout << out_triangle. size ( ) << std:: endl;
# endif
pixels. clear ( ) ;
zBuffer. clear ( ) ;
for ( int i = 0 ; i < out_triangle. size ( ) ; ++ i)
{
Vector3< float > posArr[ 3 ] = {
out_triangle[ i] . vertex[ 0 ] . position ,
out_triangle[ i] . vertex[ 1 ] . position ,
out_triangle[ i] . vertex[ 2 ] . position } ;
Vector2< float > leftTop = Myth :: get_leftTop ( posArr[ 0 ] , posArr[ 1 ] , posArr[ 2 ] ) ;
Vector2< float > rightBottom = Myth :: get_rightBottom ( posArr[ 0 ] , posArr[ 1 ] , posArr[ 2 ] ) ;
for ( int x = leftTop. x; x <= rightBottom. x; ++ x)
{
for ( int y = leftTop. y; y >= rightBottom. y; -- y)
{
const Vector2< int > pixPos ( x, y) ;
bool isInTriangle = Myth :: isInTriangle ( Vector3 < float > ( x, y, 0 ) , posArr[ 0 ] , posArr[ 1 ] , posArr[ 2 ] ) ;
if ( isInTriangle)
{
Vector3< float > abg = Myth :: centerOfGravity (
posArr[ 0 ] , posArr[ 1 ] , posArr[ 2 ] , pixPos) ;
float z = posArr[ 0 ] . z * abg. x +
posArr[ 1 ] . z * abg. y + posArr[ 2 ] . z * abg. z;
if ( zBuffer. count ( pixPos) )
{
if ( z <= zBuffer[ pixPos] )
;
else
zBuffer[ pixPos] = z;
}
else
zBuffer[ pixPos] = z;
Vector3< float > color = out_triangle[ i] . vertex[ 0 ] . color * abg. x +
out_triangle[ i] . vertex[ 1 ] . color * abg. y + out_triangle[ i] . vertex[ 2 ] . color * abg. z;
Vector3< float > normal = out_triangle[ i] . vertex[ 0 ] . normal * abg. x +
out_triangle[ i] . vertex[ 1 ] . normal * abg. y + out_triangle[ i] . vertex[ 2 ] . normal * abg. z;
Vector2< float > uv = out_triangle[ i] . vertex[ 0 ] . uv * abg. x +
out_triangle[ i] . vertex[ 1 ] . uv * abg. y + out_triangle[ i] . vertex[ 2 ] . uv * abg. z;
VertexData verdata ( Vector3 < float > ( 0 , 0 , z) , uv, normal, color) ;
pixels. push_back ( Pixel ( verdata, Vector2 < int > ( x, y) ) ) ;
}
}
}
}
# if DEBUG
std:: cout << std:: endl << "-----------------rasterization End-----------------" << std:: endl;
std:: cout << pixels. size ( ) << std:: endl;
std:: cout << zBuffer. size ( ) << std:: endl;
# endif
}
void testZ ( )
{
# if DEBUG
std:: cout << std:: endl << "-----------------testZ Begin-----------------" << std:: endl;
# endif
std:: vector< Pixel> :: iterator it = pixels. begin ( ) ;
while ( it != pixels. end ( ) )
{
if ( ( * it) . verdata. position. z < zBuffer[ ( * it) . pos] )
it = pixels. erase ( it) ;
else
++ it;
}
# if DEBUG
std:: cout << pixels. size ( ) << std:: endl;
std:: cout << std:: endl << "-----------------testZ End-----------------" << std:: endl;
# endif
}
void renderingPipeline ( )
{
vertexShader ( ) ;
rasterization ( ) ;
if ( isTestZ)
testZ ( ) ;
}
} ;
struct ScreenWindow
{
const int width, height;
Color clearBack;
bool isClearBack = false ;
std:: vector< Pixel> pixels;
ScreenWindow ( int screenWidth, int screenHeight) : width ( screenWidth) , height ( screenHeight)
{
initgraph ( screenWidth, screenHeight) ;
}
~ ScreenWindow ( )
{
closegraph ( ) ;
}
void update ( const std:: vector< Pixel> & input, bool clearBackColor = false , Color color = Color ( 1 , 1 , 1 , 1 ) )
{
this -> pixels = input;
if ( clearBackColor)
{
isClearBack = true ;
clearBack = color;
setbkcolor ( RGB ( clearBack. x, clearBack. y, clearBack. z) ) ;
}
}
void show ( )
{
setorigin ( 0 , 600 ) ;
setaspectratio ( 1 , - 1 ) ;
if ( isClearBack)
cleardevice ( ) ;
BeginBatchDraw ( ) ;
for ( int i = 0 ; i < pixels. size ( ) ; ++ i)
{
putpixel ( pixels[ i] . pos. x, pixels[ i] . pos. y, RGB ( pixels[ i] . verdata. color. x, pixels[ i] . verdata. color. y, pixels[ i] . verdata. color. z) ) ;
}
FlushBatchDraw ( ) ;
}
} ;
int main ( )
{
initgraph ( screenWidth, screenHeight, SHOWCONSOLE) ;
std:: string meshlLocation = "C:\\Users\\32156\\source\\repos\\SoftRender\\OBJ\\Cube.txt" ;
Mesh mesh;
mesh. readObjFile ( meshlLocation) ;
mesh. colorBuffer = {
Color ( 255 , 255 , 255 ) ,
Color ( 255 , 0 , 0 ) ,
Color ( 0 , 255 , 0 ) ,
Color ( 0 , 0 , 255 ) ,
Color ( 255 , 255 , 255 ) ,
Color ( 255 , 0 , 0 ) ,
Color ( 0 , 255 , 0 ) ,
Color ( 0 , 0 , 255 ) ,
} ;
Vector3< float > scale ( 0.5 , 0.5 , 0.5 ) , position ( 0 , 0 , - 2 ) ;
Matrix4< float > model;
model. Identity ( ) ;
model = Matrix4 < float > :: get_model_matrix_scale ( model, scale) ;
model = Matrix4 < float > :: get_model_matrix_translation ( model, position) ;
model. Print ( ) ;
float fov = 90 , aspecet = 1 , n = 1 , f = 2 ;
Matrix4< float > projection;
projection. Identity ( ) ;
projection = Matrix4 < float > :: get_projection_matrix ( fov, aspecet, n, f) ;
projection. Print ( ) ;
Matrix4< float > viewport = Matrix4 < float > :: get_viewport_matrix ( screenWidth, screenHeight) ;
ScreenWindow window ( screenWidth, screenHeight) ;
Vector3< float > cameraPos ( 0.0f , 0.0f , 0.0f ) , cameraFront ( 0 , 0 , - 1 ) , cameraUp ( 0 , 1 , 0 ) ;
cameraPos. normalize ( ) ; cameraFront. normalize ( ) ; cameraUp. normalize ( ) ;
Matrix4< float > view = Matrix4 < float > :: get_view_matrix ( cameraPos, cameraFront, cameraUp) ;
Render render ( screenWidth, screenHeight) ;
render. assemblingElements ( mesh) ;
render. camera = Camera ( cameraPos, cameraFront, cameraUp) ;
render. isViewClipping = true ;
render. isBackCulling = true ;
render. isTestZ = true ;
render. model = model;
render. view = view;
render. projection = projection;
render. viewport = viewport;
# if DEBUG
std:: cout << "While out:" << std:: endl;
mesh. print ( ) ;
std:: cout << std:: endl;
render. model. Print ( ) ;
std:: cout << std:: endl;
render. view. Print ( ) ;
std:: cout << std:: endl;
render. projection. Print ( ) ;
std:: cout << std:: endl;
render. viewport. Print ( ) ;
std:: cout << std:: endl;
# endif
clock_t begin = clock ( ) ;
while ( true )
{
char key = _getch ( ) ;
switch ( key)
{
case 'w' :
cameraPos. y += 0.1 ;
break ;
case 's' :
cameraPos. y -= 0.1 ;
break ;
case 'a' :
cameraPos. x += 0.1 ;
break ;
case 'd' :
cameraPos. x -= 0.1 ;
break ;
default :
break ;
}
clock_t now = clock ( ) ;
float deletime = static_cast < float > ( now - begin) / CLOCKS_PER_SEC * 1000 ;
begin = clock ( ) ;
int fps = 1.0 / deletime;
std:: cout << "FPS:" << fps << std:: endl;
cameraFront. normalize ( ) ;
render. view = Matrix4 < float > :: get_view_matrix ( cameraPos, cameraFront, cameraUp) ;
cameraPos. print ( ) ;
std:: cout << std:: endl;
render. view. Print ( ) ;
std:: cout << std:: endl;
render. renderingPipeline ( ) ;
window. update ( render. pixels, true , Color ( 0 , 0 , 0 ) ) ;
window. show ( ) ;
}
}
3.2 完整的OBJ文件
# Blender v2. 83.5 OBJ File: ''
# www . blender. org
mtllib Cube. mtl
o Cube
v 1.000000 1.000000 - 1.000000
v 1.000000 - 1.000000 - 1.000000
v 1.000000 1.000000 1.000000
v 1.000000 - 1.000000 1.000000
v - 1.000000 1.000000 - 1.000000
v - 1.000000 - 1.000000 - 1.000000
v - 1.000000 1.000000 1.000000
v - 1.000000 - 1.000000 1.000000
vt 0.625000 0.500000
vt 0.875000 0.500000
vt 0.875000 0.750000
vt 0.625000 0.750000
vt 0.375000 0.750000
vt 0.625000 1.000000
vt 0.375000 1.000000
vt 0.375000 0.000000
vt 0.625000 0.000000
vt 0.625000 0.250000
vt 0.375000 0.250000
vt 0.125000 0.500000
vt 0.375000 0.500000
vt 0.125000 0.750000
vn 0.0000 1.0000 0.0000
vn 0.0000 0.0000 1.0000
vn - 1.0000 0.0000 0.0000
vn 0.0000 - 1.0000 0.0000
vn 1.0000 0.0000 0.0000
vn 0.0000 0.0000 - 1.0000
usemtl Material
s off
f 1 / 1 / 1 5 / 2 / 1 7 / 3 / 1 3 / 4 / 1
f 4 / 5 / 2 3 / 4 / 2 7 / 6 / 2 8 / 7 / 2
f 8 / 8 / 3 7 / 9 / 3 5 / 10 / 3 6 / 11 / 3
f 6 / 12 / 4 2 / 13 / 4 4 / 5 / 4 8 / 14 / 4
f 2 / 13 / 5 1 / 1 / 5 3 / 4 / 5 4 / 5 / 5
f 6 / 11 / 6 5 / 10 / 6 1 / 1 / 6 2 / 13 / 6
3.3 参考资料
GAMES101现代计算机图形学入门 入门渲染管线的详细讲解 从零开始写一个软渲染器 OpenGL中文学习网(旧版)