从零开始编写自己的 C++ 软渲染器(一) 线与三角形的绘制 您所在的位置:网站首页 c开头的渲染器 从零开始编写自己的 C++ 软渲染器(一) 线与三角形的绘制

从零开始编写自己的 C++ 软渲染器(一) 线与三角形的绘制

2024-05-30 13:06| 来源: 网络整理| 查看: 265

所谓软渲染器就是使用 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 中的函数做注释)

再随便取个项目名字。

image-20220915093416640

好了,现在 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),得到如下结果:

image-20220920000316432



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有