mini3d源码解析及功能扩展

简介

mini3d是前网易员工@韦易笑开发的3d软渲染引擎,总代码量不到1000行,短小精悍,适合初学者学习。

本文结合源码给出自己的理解,并在原作基础上实现功能扩展:

  1. 补充缺少的三维变换功能(平移、缩放)
  2. 增加简单光照(漫反射)

代码简析

这个项目实现了一个方块的3d渲染,主要功能点有:

  1. 支持平移和旋转
  2. 支持三种不同状态的显示(线框、颜色、纹理)
    在这里插入图片描述

下面来对代码做个简析,主要是把握脉络,不求深入细节。

先看主函数,第一行定义了类型为device_t的变量device
结构体device_t包含渲染方块用到的所有参数和数据结构:

typedef struct {
	transform_t transform;      // 坐标变换器
	int width;                  // 窗口宽度
	int height;                 // 窗口高度
	IUINT32 **framebuffer;      // 像素缓存:framebuffer[y] 代表第 y行
	float **zbuffer;            // 深度缓存:zbuffer[y] 为第 y行指针
	IUINT32 **texture;          // 纹理:同样是每行索引
	int tex_width;              // 纹理宽度
	int tex_height;             // 纹理高度
	float max_u;                // 纹理最大宽度:tex_width - 1
	float max_v;                // 纹理最大高度:tex_height - 1
	int render_state;           // 渲染状态
	IUINT32 background;         // 背景颜色
	IUINT32 foreground;         // 线框颜色
}	device_t;

其后完成一系列初始化:

函数作用
screen_init初始化屏幕(windows上显示窗口所必需)
device_init初始化设备(为device_t中的成员变量赋初值)
camera_at_zero初始化相机位置,建立相机坐标系
init_texture初始化纹理(蓝白相间图案,存在device_ttexture变量中)

随后进入主循环,每次循环会依次做如下操作:

  • device_clear:清空framebufferz-buffer
  • camera_at_zero:重设相机位置。
  • 监听键盘事件,修改旋转角度alpha和相机x坐标的值,以及切换render_state
  • draw_box:画方块,具体来说是从方块 -> 平面 -> 三角形 -> 线段 -> 像素,一层层深入;当然这个过程中还包括三维观察坐标变换,以及按z-buffer处理遮挡。最后在framebuffer中为每个像素点设置颜色。
  • screen_update:调用windows api将framebuffer中的像素显示到屏幕上。

功能扩展

以上只是概述。话说要深入理解一段代码,最好的办法还是动手改点东西。

我们来看看有哪些新feature是可以添加进去的。

  • 原作只支持旋转和前后平移(其实是相机的平移,而非方块),我们很容易想到可以完善三维变换的所有功能:平移(包括六个方向)、旋转、缩放。
  • 现在的方块无论从哪个角度看都一个样,原因是缺乏光照。我们可以添加简单的光照,如漫反射和镜面反射。

完善三维变换功能

添加三维变换的关键是:在三维观察坐标变换的流水线中,找到合适的地方对顶点做三维变换。

我们先处理缩放和旋转,通过改写draw_box:

    void draw_box(device_t *device, float alpha, float scale) {
    	matrix_t m1;
    	matrix_set_scale(&m1, scale, scale, scale);
    	matrix_t m2;
    	matrix_set_rotate(&m2, -1, -0.5, 1, alpha);
    	matrix_t m;
    	matrix_mul(&m, &m1, &m2);
    	device->transform.world = m;

再在transform_apply中处理平移, 插入点是在完成相机坐标系的变换之后:

void transform_apply(const transform_t *ts, vector_t *y, const vector_t *x, vector_t *_3d_transform) {
	vector_t t1;
	vector_t t2;
	vector_t t3;
	matrix_apply(&t1, x, &ts->world);
	matrix_apply(&t2, &t1, &ts->view);
	vector_add(&t3, &t2, _3d_transform);
	matrix_apply(y, &t3, &ts->projection);
}

增加简单光照

这里实现的是简单的漫反射。

根据漫反射的实现原理,物体上的顶点在光照下的颜色取决于以下因素:

  • 材质颜色
  • 光的颜色
  • 光的入射方向
  • 顶点所在平面的法向量

按照这些因素一个个来实现。

先定义光源:

// 平行光源
typedef struct {
	color_t color;				// 颜色
	vector_t direction;			// 方向
} light_t;

再根据平面上三点计算法向量:

// 计算平面法向量
void calc_plane_normal(const transform_t *ts, vector_t *normal, const vector_t *x, const vector_t *y, const vector_t *z) {
	vector_t wx;
	vector_t wy;
	vector_t wz;
	vector_t xy;
	vector_t yz;
	matrix_apply(&wx, x, &ts->world);
	matrix_apply(&wy, y, &ts->world);
	matrix_apply(&wz, z, &ts->world);
	vector_sub(&xy, &wy, &wx);
	vector_sub(&yz, &wz, &wy);
	vector_crossproduct(normal, &xy, &yz);
	vector_normalize(normal);
}

然后将法向量传入device_draw_scanline,插入下面一段代码,真正实现光照对颜色的影响:

// 处理光照
float dot = vector_dotproduct(&device->light.direction, normal);
if (dot < 0)
	dot = -dot;
b *= dot * device->light.color.b;
g *= dot * device->light.color.g;
r *= dot * device->light.color.r;

最后实现的效果如图(迎着摄像机的一面因光线影响较亮):
在这里插入图片描述

代码提交到github了,可供参考。

小结

阅读mini3d代码所需基础要求:

  • 理解三维观察坐标变换流水线
  • 理解z-buffer的用法
  • 理解三维变换(平移、旋转、缩放)
  • 了解windows编程

不清楚的部分需要查询计算机图形学书籍或相关资料。

实现这么个软渲染,最大意义在于理论联系实际,将理论知识真正落地。麻雀虽小,五脏俱全。以后还可以继续扩展更多的功能。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值