[TinyRenderer] Lesson 1 布兰森汉姆绘制线算法

2024-04-14 04:58

本文主要是介绍[TinyRenderer] Lesson 1 布兰森汉姆绘制线算法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 翻译
    • 1 第一次尝试
    • 2 第二次尝试
    • 3 第三次尝试
    • 4 第四次尝试
    • 5 第四次尝试+
    • 6 第五次和最后一次尝试
    • 7 线框渲染
  • 实操效果

翻译

布兰森汉姆绘制线算法

1 第一次尝试

第一课的目标是渲染金属丝网。要做到这一点,我们应该学习如何绘制线段。我们可以简单地读懂Bresenham的行算法,但是让我们自己写代码。在(x0, y0)点和(x1, y1)点之间绘制线段的最简单代码是什么样子的?
显然,是这样的:

void line(int x0, int y0, int x1, int y1, TGAImage &image, TGAColor color) { for (float t=0.; t<1.; t+=.01) { int x = x0 + (x1-x0)*t; int y = y0 + (y1-y0)*t; image.set(x, y, color); } 
}

在这里插入图片描述

可用代码片段here

2 第二次尝试

这段代码的问题(除了效率低下)是选择常数,我取它等于.01。
如果取它为.1,线段就会像这样
在这里插入图片描述
我们可以很容易地找到必要的步骤:它就是要绘制的像素数。
最简单的(但错误的)代码看起来像以下:

void line(int x0, int y0, int x1, int y1, TGAImage &image, TGAColor color) { for (int x=x0; x<=x1; x++) { float t = (x-x0)/(float)(x1-x0); int y = y0*(1.-t) + y1*t; image.set(x, y, color); } 
}

注意!
在我的学生的代码中,第一个错误的来源是整数除法,比如’ (x-x0)/(x1-x0) '。
然后,如果我们尝试用这段代码绘制以下线条:

line(13, 20, 80, 40, image, white); 
line(20, 13, 40, 80, image, red); 
line(80, 40, 13, 20, image, red);

在这里插入图片描述

原来一条线是好的,第二条是有孔的,第三条线根本就没有。
注意,第一行和第三行(在代码中)以不同的颜色和不同的方向绘制了同一条线(源点和目标点翻转了)。
我们已经看过白色的了,画得很好。
我希望把白线的颜色改成红色,但是我做不到。
这是对对称性的测试:绘制线段的结果不应该依赖于点的顺序:(a,b)线段应该与(b,a)线段完全相同。

3 第三次尝试

我们通过交换点来修复缺失的红线,使x0总是小于x1。

在其中一个线段上有洞,这是由于它的高度大于宽度。
我的学生经常提出以下解决办法:

if (dx>dy) {for (int x)} else {for (int y)}

马萨卡!

void line(int x0, int y0, int x1, int y1, TGAImage &image, TGAColor color) { bool steep = false; if (std::abs(x0-x1)<std::abs(y0-y1)) { // if the line is steep, we transpose the image std::swap(x0, y0); std::swap(x1, y1); steep = true; } if (x0>x1) { // make it left−to−right std::swap(x0, x1); std::swap(y0, y1); } for (int x=x0; x<=x1; x++) { float t = (x-x0)/(float)(x1-x0); int y = y0*(1.-t) + y1*t; if (steep) { image.set(y, x, color); // if transposed, de−transpose } else { image.set(x, y, color); } } 
}

在这里插入图片描述

4 第四次尝试

_警告:编译器的优化器(g++ -O3)往往比你(和我)在创建一个快速代码。这一部分是出于历史/文化原因。

这段代码效果很好。这正是我希望在最终版本或我们的渲染器中看到的那种复杂性。它绝对是低效的(多分区等),但它简短易读。请注意,它没有声明,也没有检查越界,这很糟糕。在这些文章中,我尽量不重载这个特定的代码,因为它会被大量阅读。同时,我系统地提醒进行检查的必要性。

所以,前面的代码工作正常,但我们可以优化它。优化是一件危险的事情。我们应该清楚代码将在哪个平台上运行。为显卡或仅为 CPU 优化代码是完全不同的事情。在任何优化之前和期间,必须对代码进行概要分析。试着猜一下,这里哪个操作是最耗费资源的操作?

对于测试,我绘制了我们之前绘制的 3 条线段 1,000,000 次。我的 CPU 是 Intel® Core™ i5-3450 CPU @ 3.10GHz。对于每个像素,此代码调用 TGAColor 复制构造函数。即 1000000 * 3 条线段 * 每条线段大约 50 个像素。引起了很多问题,不是吗?从哪里开始优化?分析器会告诉我们。

I compiled the code with g++ -ggdb -g -pg -O0 keys, and then ran gprof:

我用g++ -ggdb -g -pg -O0命令编译代码,然后运行 gprof:

%   cumulative   self              self     total time   seconds   seconds    calls  ms/call  ms/call  name 69.16      2.95     2.95  3000000     0.00     0.00  line(int, int, int, int, TGAImage&, TGAColor) 19.46      3.78     0.83 204000000     0.00     0.00  TGAImage::set(int, int, TGAColor) 8.91      4.16     0.38 207000000     0.00     0.00  TGAColor::TGAColor(TGAColor const&) 1.64      4.23     0.07        2    35.04    35.04  TGAColor::TGAColor(unsigned char, unsigned char, unsigned char, unsigned char) 0.94      4.27     0.04                             TGAImage::get(int, int)

10% 的时间花在复制颜色上。 但是随后 70% 的时间都花在了调用 line() 上! 这就是我们要优化的地方。

5 第四次尝试+

我们应该注意到每个部门都有相同的除数。 让我们把它排除在外。 误差变量为我们提供了从当前 (x, y) 像素到最佳直线的距离。 每次误差大于一个像素时,我们将 y 增加(或减少)1,并将误差也减少 1。

可用代码here.

void line(int x0, int y0, int x1, int y1, TGAImage &image, TGAColor color) { bool steep = false; if (std::abs(x0-x1)<std::abs(y0-y1)) { std::swap(x0, y0); std::swap(x1, y1); steep = true; } if (x0>x1) { std::swap(x0, x1); std::swap(y0, y1); } int dx = x1-x0; int dy = y1-y0; float derror = std::abs(dy/float(dx)); float error = 0; int y = y0; for (int x=x0; x<=x1; x++) { if (steep) { image.set(y, x, color); } else { image.set(x, y, color); } error += derror; if (error>.5) { y += (y1>y0?1:-1); error -= 1.; } } 
} 

这是 gprof 的输出:

%   cumulative   self              self     total time   seconds   seconds    calls  ms/call  ms/call  name 38.79      0.93     0.93  3000000     0.00     0.00  line(int, int, int, int, TGAImage&, TGAColor) 37.54      1.83     0.90 204000000     0.00     0.00  TGAImage::set(int, int, TGAColor) 19.60      2.30     0.47 204000000     0.00     0.00  TGAColor::TGAColor(int, int) 2.09      2.35     0.05        2    25.03    25.03  TGAColor::TGAColor(unsigned char, unsigned char, unsigned char, unsigned char) 1.25      2.38     0.03                             TGAImage::get(int, int) 

6 第五次和最后一次尝试

为什么我们需要浮点数? 唯一的原因是 dx 除以 dx 并与循环体中的 .5 进行比较。 我们可以通过用另一个变量替换错误变量来摆脱浮点数。 我们称之为error2,假设它等于error * dx * 2。这是等效的代码:

void line(int x0, int y0, int x1, int y1, TGAImage &image, TGAColor color) { bool steep = false; if (std::abs(x0-x1)<std::abs(y0-y1)) { std::swap(x0, y0); std::swap(x1, y1); steep = true; } if (x0>x1) { std::swap(x0, x1); std::swap(y0, y1); } int dx = x1-x0; int dy = y1-y0; int derror2 = std::abs(dy)*2; int error2 = 0; int y = y0; for (int x=x0; x<=x1; x++) { if (steep) { image.set(y, x, color); } else { image.set(x, y, color); } error2 += derror2; if (error2 > dx) { y += (y1>y0?1:-1); error2 -= dx*2; } } 
} 
%   cumulative   self              self     total time   seconds   seconds    calls  ms/call  ms/call  name 42.77      0.91     0.91 204000000     0.00     0.00  TGAImage::set(int, int, TGAColor) 30.08      1.55     0.64  3000000     0.00     0.00  line(int, int, int, int, TGAImage&, TGAColor) 21.62      2.01     0.46 204000000     0.00     0.00  TGAColor::TGAColor(int, int) 1.88      2.05     0.04        2    20.02    20.02  TGAColor::TGAColor(unsigned char, unsigned char, unsigned char, unsigned char) 

现在,通过引用传递颜色(或仅启用编译标志 -O3),在函数调用期间删除不必要的副本就足够了,就大功告成了。 代码中没有一个乘法或除法。 执行时间从 2.95 秒减少到 0.64 秒。

我还建议检查 this issue。 优化很棘手!

7 线框渲染

所以现在我们准备好创建一个线渲染。 你可以在这里找到代码和测试模型的快照。
我使用文件的 wavefront obj 格式来存储模型。 我们渲染所需的全部内容是从文件中读取以下类型的顶点数组:

v 0.608654 -0.568839 -0.416318

是 x,y,z 坐标,每个文件行一个顶点和面孔

f 1193/1240/1193 1180/1227/1180 1179/1226/1179

我们对每个空格后的第一个数字感兴趣。 它是我们之前读过的数组中顶点的编号。 因此,这条线表示 1193、1180 和 1179 个顶点形成一个三角形。 请注意,在 obj 文件中,索引从 1 开始,这意味着您应该分别查找 1192、1179 和 1178 个顶点。
model.cpp 文件包含一个简单的解析器。 将以下循环写入我们的 main.cpp 和 voilà,我们的线渲染器已准备就绪。

for (int i=0; i<model->nfaces(); i++) { std::vector<int> face = model->face(i); for (int j=0; j<3; j++) { Vec3f v0 = model->vert(face[j]); Vec3f v1 = model->vert(face[(j+1)%3]); int x0 = (v0.x+1.)*width/2.; int y0 = (v0.y+1.)*height/2.; int x1 = (v1.x+1.)*width/2.; int y1 = (v1.y+1.)*height/2.; line(x0, y0, x1, y1, image, white); } 
}

在这里插入图片描述

下次我们将绘制 2D 三角形并改进我们的渲染器。

实操效果

在这里插入图片描述

这篇关于[TinyRenderer] Lesson 1 布兰森汉姆绘制线算法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/902123

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

康拓展开(hash算法中会用到)

康拓展开是一个全排列到一个自然数的双射(也就是某个全排列与某个自然数一一对应) 公式: X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0! 其中,a[i]为整数,并且0<=a[i]<i,1<=i<=n。(a[i]在不同应用中的含义不同); 典型应用: 计算当前排列在所有由小到大全排列中的顺序,也就是说求当前排列是第

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

poj 3974 and hdu 3068 最长回文串的O(n)解法(Manacher算法)

求一段字符串中的最长回文串。 因为数据量比较大,用原来的O(n^2)会爆。 小白上的O(n^2)解法代码:TLE啦~ #include<stdio.h>#include<string.h>const int Maxn = 1000000;char s[Maxn];int main(){char e[] = {"END"};while(scanf("%s", s) != EO

秋招最新大模型算法面试,熬夜都要肝完它

💥大家在面试大模型LLM这个板块的时候,不知道面试完会不会复盘、总结,做笔记的习惯,这份大模型算法岗面试八股笔记也帮助不少人拿到过offer ✨对于面试大模型算法工程师会有一定的帮助,都附有完整答案,熬夜也要看完,祝大家一臂之力 这份《大模型算法工程师面试题》已经上传CSDN,还有完整版的大模型 AI 学习资料,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

【WebGPU Unleashed】1.1 绘制三角形

一部2024新的WebGPU教程,作者Shi Yan。内容很好,翻译过来与大家共享,内容上会有改动,加上自己的理解。更多精彩内容尽在 dt.sim3d.cn ,关注公众号【sky的数孪技术】,技术交流、源码下载请添加微信号:digital_twin123 在 3D 渲染领域,三角形是最基本的绘制元素。在这里,我们将学习如何绘制单个三角形。接下来我们将制作一个简单的着色器来定义三角形内的像素

dp算法练习题【8】

不同二叉搜索树 96. 不同的二叉搜索树 给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。 示例 1: 输入:n = 3输出:5 示例 2: 输入:n = 1输出:1 class Solution {public int numTrees(int n) {int[] dp = new int

Codeforces Round #240 (Div. 2) E分治算法探究1

Codeforces Round #240 (Div. 2) E  http://codeforces.com/contest/415/problem/E 2^n个数,每次操作将其分成2^q份,对于每一份内部的数进行翻转(逆序),每次操作完后输出操作后新序列的逆序对数。 图一:  划分子问题。 图二: 分而治之,=>  合并 。 图三: 回溯: