软渲染示例程序Tiny3D的实现简介
Table of Contents
请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com
绘制图形的代码
Tiny3D核心的图形绘制代码调用栈如下图所示
Device::DrawBox函数
Tiny3D的核心绘制代码的入口函数Device::DrawBox
函数
// theta mesh的旋转角度
// box_vertices mesh的原始模型顶点数据
void Device::DrawBox(float theta, const T3DVertex* box_vertices)
Device::DrawBox
函数的实现如下代码所示:
void Device::DrawBox(float theta, const T3DVertex* box_vertices)
{
T3DMatrix4X4 m;
T3DMatrixMakeRotation(&m, -1.0f, -0.5f, 1.0f, theta);
transform_.SetWorldMatrix(m);
transform_.Update();
DrawPlane(&box_vertices[0], &box_vertices[1], &box_vertices[2], &box_vertices[3]);
DrawPlane(&box_vertices[4], &box_vertices[5], &box_vertices[6], &box_vertices[7]);
DrawPlane(&box_vertices[0], &box_vertices[4], &box_vertices[5], &box_vertices[1]);
DrawPlane(&box_vertices[1], &box_vertices[5], &box_vertices[6], &box_vertices[2]);
DrawPlane(&box_vertices[2], &box_vertices[6], &box_vertices[7], &box_vertices[3]);
DrawPlane(&box_vertices[3], &box_vertices[7], &box_vertices[4], &box_vertices[0]);
}
步骤就是如下的几步:
- 根据传递进来的mesh的旋转角度theta,调用
T3DMatrixMakeRotation
函数构建world matrix,然后更新到Transform
类中去 - 调用
Transform::Update
函数,更新world-view-projection matrix,这WVP matrix将在后面的用来变换顶点。 - 把一个六面体BOX拆分成6个矩形面,调用DrawPlane函数进行绘制。
Device::DrawPlane函数
Device::DrawPlane
函数的实现如下代码所示:
void Device::DrawPlane(const T3DVertex* p1, const T3DVertex* p2, const T3DVertex* p3, const T3DVertex* p4)
{
T3DVertex _p1 = *p1, _p2 = *p2, _p3 = *p3, _p4 = *p4;
_p1.tc.u = 0.0f, _p1.tc.v = 0.0f;
_p2.tc.u = 0.0f, _p2.tc.v = 1.0f;
_p3.tc.u = 1.0f, _p3.tc.v = 1.0f;
_p4.tc.u = 1.0f, _p4.tc.v = 0.0f;
DrawPrimitive(&_p1, &_p2, &_p3);
DrawPrimitive(&_p3, &_p4, &_p1);
}
在Device::DrawPlane
函数中,就是把四边形的四个顶点,分别组成两个三角形,然后调用Device::DrawPrimitive
函数绘制这两个三角形。
Device::DrawPrimitive函数
此函数比较长,就不在这里摘录了,该函数的签名如下:
void Device::DrawPrimitive(const T3DVertex* v1, const T3DVertex* v2, const T3DVertex* v3)
该函数的内部实现步骤如下:
- 使用
Transform::Apply
函数,对组成三角形的三个顶点v1,v2,v3,进行空间变换,将这几个顶点变换到裁剪空间。如下面的“裁剪空间和NDC坐标系”图所示。 - 使用
Transform::CheckCVV
函数,判断这三个顶点是否有超出裁剪空间范围内,目前的算法是:这三个顶点只要有一个超出范围,即放弃渲染此三角形。 - 完成了裁剪操作之后,使用
Transform::Homogenize
函数执行透视除法,将顶点变换从clip space变换到NDC坐标系以及屏幕坐标系中去。 - 如果是纹理映射模式(
RENDER_STATE_TEXTURE
)或者颜色填充模式(RENDER_STATE_COLOR
),使用Trapezoid::SplitTriangleIntoTrapezoids
函数,将三角形分割成两个梯形,然后调用Device::RenderTrapezoid
函数进行绘制。 - 如果是线框模式(
RENDER_STATE_WIREFRAME
),即调用Device::DrawLine
函数直接绘制三角形的三条边。
把三角形拆分梯形并渲染的Trapezoid::SplitTriangleIntoTrapezoids函数
Trapezoid::SplitTriangleIntoTrapezoids函数
Trapezoid::SplitTriangleIntoTrapezoids
函数把一个三角形拆分成两个梯形。这个函数代码较长很长,在此不做转录。具体步骤也在代码中已经注释清楚,这里贴一个辅助阅读代码的图
Device::RenderTrapezoid函数
Device::RenderTrapezoid
函数就是绘制梯形的函数,截下上面的“把三角形分割成梯形并渲染的示意图”来看。可分为以下步骤:
- 根据传递进来的梯形对象指针trap,算出该梯形的顶边和底边分别对应于屏幕中的哪几行。假设从top行开始到bottom行结束
- 在top行至bottom行范围内循环,在当前的第y行屏幕行中,trap调用其成员函数
Trapezoid::CalculateEdgeInterpolatedPoint
。处理梯形在当前行中左右腰边的插值点,这插值点用来作为扫描线的端点 - 在top行至bottom行范围内循环,在当前的第y行屏幕行中,trap调用其成员函数
Trapezoid::InitializeScanline
。处理梯形在当前行中,对应生成的逐行扫描的扫描线。 - 在top行至bottom行范围内循环,在当前的第y行屏幕行中,调用
Device::DrawScanline
函数,绘制扫描线