从零开始编写自己的 C++ 软渲染器(一) 线与三角形的绘制 | 您所在的位置:网站首页 › c开头的渲染器 › 从零开始编写自己的 C++ 软渲染器(一) 线与三角形的绘制 |
所谓软渲染器就是使用 CPU 渲染 3D 模型的程序。通常我们编程时调用不到 GPU,学习时的一些实验玩法其实用 CPU 也足够,写一个软渲染器是个很好的乱搞路子学习方法。 前置技能: C++ 基础 线性代数 四月份的时候曾依葫芦画瓢写过一个很烂的软渲,参考了以下内容: GAMES 101 tinyrenderer 其一是最近两年图形学领域非常热门的一个入门网课,很多地方值得暂停做笔记思考。不过个人觉得网课如果不写作业交作业的话学起来会感觉很宽泛,做不到面面俱到,难以仔细消化。 与之相对应的 Tinyrenderer 是 github 上一个很有名的仓库,手把手教我们搭建一个自己的软渲染器。我自己的软渲大体就是依照这个项目写的。它的教程英语用语也比较通俗易懂,其实也是非常适合在了解一些图形学基本内容后作为参考的。(不过其内有一些实现是错误的) 我初入图形学时写的一个软渲: Eykenis/KERenderer: My first attempt to build my own software renderer (github.com) 实现了以下功能: 深度测试透视投影Flat ShadingGouraud ShadingPhong ShadingZ-Buffering背面剔除高光以及 Blinn 光照方法单张UV纹理渲染但还有太多工作没做了。当时其实很多概念都摸不着头脑,边学边写~~(抄)~~,最后的代码就非常难维护。 所以我现在打算重新写一个,尝试把代码维护得更优雅一点。(可能写着写着就烂了)顺便也在这里记下自己对基础知识的复习笔记。 我尽量做一个跨平台的软渲染器,再次实现各种经验光照模型,并做好相机系统(轨道相机和 FPS 相机)。除此以外还有一些有趣的东西(法线/高度图,RampTex,PBR…)。 那么从新建项目并画线开始吧。 新建项目编译啥的不是我们关心的,还是交给 Visual Studio 来做吧。 Windows GDI 又不熟练了,要不我们用 graphics.h 库吧… graphics 库的文档可以查 https://docs.easyx.cn/zh-cn/intro。虽然这个 API 比较原始,但在我们的小玩具中用一用是无妨的。 (本文不再对引用 graphics.h 中的函数做注释) 再随便取个项目名字。 好了,现在 initgraph,得到一个空窗口~ 我们先设置窗口为 800x600 吧。 想想接下来要做什么。 Bresenham 画线法如果我们现在可以选择改变屏幕上某个像素的颜色,那么如何画一条线呢? 先说一下自己 yy 的一个朴素的想法。 我们不妨确定两个点,然后一步一步走过来。 我们可以认为一条线是从起点 A A A 指向终点 B B B 的向量 v \boldsymbol v v. 然后我们计算这两个点的像素距离 d i s dis dis,然后写出以下代码: void drawLine(double x0, double y0, double x1, double y1, int color) { double vx = x1 - x0, vy = y1 - y0; double dis = sqrt(vy * vy + vx * vx); double wx = x0, wy = y0; vx /= dis, vy /= dis; for (int i = 0; i int dx = abs(x1 - x0); int dy = abs(y1 - y0); int gx = x0 template struct vec2 { union { struct { T x, y; }; struct { T u, v; }; T a[2]; }; vec2() { x = y = 0; } ~vec2() { } vec2(T _x, T _y) { x = _x; y = _y; } vec2(const vec2& v) { x = v.x; y = v.y; } T operator * (const vec2& v) { return this->x * v.x + this->y * v.y; } vec2 operator + (const vec2& v) { return vec2(this->x + v.x, this->y + v.y); } vec2 operator - (const vec2& v) { return vec2(this->x - v.x, this->y - v.y); } vec2 operator * (const T t) { return vec2(this->x * t, this->y * t); } vec2& operator = (const vec2& v) { this->x = v.x; this->y = v.y; return *this; } }; template struct vec3 { union { struct { T x, y, z; }; struct { T r, g, b; }; T a[3]; }; vec3() { x = y = z = 0; } ~vec3() { } vec3(T _x, T _y, T _z) { x = _x; y = _y; z = _z; } vec3(const vec3& v) { x = v.x; y = v.y; z = v.z; } T operator * (const vec3& v) { return this->x * v.x + this->y * v.y + this->z * v.z; } vec3 operator + (const vec3& v) { return vec3(this->x + v.x, this->y + v.y, this->z + v.z); } vec3 operator - (const vec3& v) { return vec3(this->x - v.x, this->y - v.y, this->z - v.z); } vec3 operator * (const T t) { return vec3(this->x * t, this->y * t, this->z * t); } vec3& operator = (const vec3& v) { this->x = v.x; this->y = v.y; this->z = v.z; return *this; } }; template T cross(const vec2& u, const vec2& v) { return u.x * v.y - u.y * v.x; } template vec3 cross(const vec3& u, const vec3& v) { return vec3(u.y * v.z - u.z * v.y, u.z * v.x - u.x * v.z, u.x * v.y - u.y * v.x); } };那么我们依靠其写出一个判断点是否在三角形内的 check 函数: bool inTriangle(int x1, int y1, int x2, int y2, int x3, int y3, int xp, int yp) { kmath::vec2 v1(x2 - x1, y2 - y1), v2(x3 - x2, y3 - y2), v3(x1 - x3, y1 - y3); kmath::vec2 p1(xp - x1, yp - y1), p2(xp - x2, yp - y2), p3(xp - x3, yp - y3); int r1 = cross(v1, p1), r2 = cross(v2, p2), r3 = cross(v3, p3); if (!r1 || !r2 || !r3) return true; if (r1 > 0 && r2 > 0 && r3 > 0) return true; if (r1 for (int j = dy; j putpixel(i, j, color); } } } }在主程序中调用 drawTriangle(400, 100, 200, 500, 600, 400, 0xffffff),得到如下结果: |
CopyRight 2018-2019 实验室设备网 版权所有 |