记录:tinyrenderer

news/2025/3/3 22:11:38/文章来源:https://www.cnblogs.com/Elaina-/p/18745921

Bresenham’s line drawing(布雷森汉姆算法)

进行games101的光栅化作业时,对其渲染原理仍不甚了解,找到tinyrenderer软光栅项目。在此记录下试错的过程。

作者在最初为我们做好了framebuffer,读者入手的方向实际是从渲染的过程开始。对于如何渲染出像素显示在画面上,应该需要从其他博主那进行学习,或者从作者实现的文件中分析,这里就不做多余的解释。

友情提示:这里记录个人对tinyrenderer原理的理解,若需源代码请从原作者的github处下载。(作者的博客内蓝色高光处包含了不同阶段源代码的地址)

github地址在这里:https://ssloy.github.io/tinyrenderer

作者第一节从线的绘制开始,一个三角面有三个顶点,即绘制三条直线。

Bresenham’s line drawing(布雷森汉姆算法)

给出两个顶点a(ax,ay),b(bx,by),渲染出两点间的直线。
按照作者的说法,直接抛出算法会很难以理解,采取渐进的形式,逐渐演化算法的执行。

假设参数t \(\in\)[0,1],定义二维点(x(t),y(t))如下:


x(t) = ax + t * (bx - ax)
y(t) = ay + t * (by - by)
对于该公式的推导 可以想象直线上两点,在两点间再取一点(x(t),y(t)),(这里设为(x,y)或许更好一些,但是图画好了懒得改了)
很容易可以想到它们间存在的相似三角形,
(y(t) - ay) / (by - ay) = t
t为0时,y(t) = ay,t为1时,y(t) = by
之后,简单推导即可得到参数方程,x(t)同理可得。

img

代码实现

void line(int ax,int ay,int bx,int by, TGAImage &framebuffer,TGAColor color){for(float t = 0; t < 1; t += 0.02){int x = ax + std::round(t * (bx - ax));int y = ay + std::round(t * (by - ay));framebuffer.set(x,y,color);}
}

img

可以注意到红线部分存在四个缺口,仔细观察一下可以发现

t的取值为0-1,每0.02取值进行运算,可取51次

cx - ax = 62 - 7 = 55 次

其gap = 55 - 1 = 4

t的取值不足导致了gap的出现:
一个直接的解决方法是:

可以将 t += .02改为 t += .01,这样确实可以解决当前的gap问题,但当cx - ax的差值更大时,t的取值仍然会不足;
作者给出的方法则是使用t的定义式,而不是直接赋值:
t = (x(t) - ax) / (bx - ax)
或者
t = (y(t) - ay) / (by - ay)
对于t定义式可行的个人见解 每一个(x(t),y(t))坐标表示一个像素点位置,两点间横坐标差值即缺少的横向像素点的数量,纵坐标同理,这样即可遍历每一个单位像素点的横坐标,得到对应的t值,从而求得相应的纵坐标。

函数实现

void line(int ax,int ay,int bx,int by, TGAImage &framebuffer,TGAColor color){for(int x = ax; x <= bx; x ++){//t需要float型,故须使用static_cast<float>来使计算返回浮点值float t = (x - ax) / static_cast <float> (bx - ax);int y = ay + std::round(t * (by - ay));framebuffer.set(x,y,color);}
}

img

好了,现在我们出现两个问题,黄线出现了大量gap,红线消失:

红线消失很好发现原因,在for循环中,若bx < ax,循环会被打破,也就不会有线产生。
在bx < ax时,我们交换两点的位置即可。


if(bx < ax){std::swap(ax,bx);std::swap(ay,by);
}

绿线的问题,观察下图的白框和黄点可以看出
img

由于取整的问题,一个单位横坐标跨越了多个单位纵坐标。也就是说一个纵坐标可以对应着不到一个单位的横坐标,取整后可以做到连续的纵坐标对应着同一个横坐标,这样就避免了gap的出现:


bool steep = std::abs(bx - ax) < std::abs(by - ay);
if(steep){std::swap(ax,ay);std::swap(bx,by);
}//在绘制直线时,须判断steep,换回x,y位置
if(steep)framebuffer.set(y,x,color)
elseframebuffer.set(x,y,color);

img

绘制成功!

接下来是性能上的优化,逐渐的接近布雷森汉姆算法
既然要优化性能,需要先了解花费性能较高的部分,首先是framebuffer.set()的调用,可惜我们此次不涉及该函数的设计,不考虑。其次就是y值的计算:
可以将t(x)式带入y(t)可得


y(x) = ay + (by - ay) * (x(t) - ax) / (bx - ax)

这样我们在代码上可以进一步处理:
我们知道,在for循环中,x(t)的值从ax开始,每次增加1,
所以(x(t) - ax)的值则为0,1,2,……;

y(i + 1) - y(i) = (by - ay) / (bx - ax)
在该值为0时,上式子中y(ax) = ay;
在这种条件下,y(x)的代码可转化为:

//此处y由int改为float
//每次循环y增值(by - ay) /static_cast  (bx - ax),该数为float,多次增加后其值对y的影响会越来越大,将y改为float,可以避免误差的发生
float y = ay;
for(int x = ax; x <= bx; x ++){if(steep)framebuffer.set(y,x,color);elseframebuffer.set(x,y,color);y += (by - ay) /static_cast <float> (bx - ax);
}
此次以整型转换为浮点型为代价,经过作者测试,由3.99s降至2.8s,有显著优化

避免浮点运算可以为我们提升很大的性能,布雷森汉姆算法即使如此。
我们将y由float改为int:
易知,增值(by - ay) /static_cast <float> (bx - ax)不会大于一,自动由float转为int时,会丢弃小数位,不进行四舍五入,这意味着y值将不会发生改变;
我们通过引入变量 float error = 0;每次循环error增值(by - ay) /static_cast (bx - ax);即error做了float y之前做的事;
超过+-0.5,便为y+-1,error +-1;
实现代码

//此处改为使用std::abs来使得每次error的增值为正,以省去判断正负的问题。
float error = 0;
int y = ay;
for(int x = ax; x < bx; x ++){if(steep)framebuffer.set(y,x,color);elseframebuffer.set(x,y,color);error += std::abs(by - ay) / static_cast <float> (bx - ax);if(error > .5){y += by > ay ? 1 : -1;error -= 1;}
}

不难猜出,这次引入float error会花费更多的性能,为进行接下来的消除浮点运算。

我在这里进行进一步的演算,以便可以更清晰的消除浮点运算(error由float转为int):

//这里的表现形式不太合规,但应该可以理解
[error += std::abs(by - ay)] / static_cast <float> (bx - ax) > 0.5
两边同乘static_cast <float> (bx - ax)
[error += std::abs(by - ay)] > 0.5 * static_cast <float> (bx - ax)
此时,不可变的浮点数仅有0.5,那么我们两边同乘2
[error += 2 * std::abs(by - ay)] > (bx - ax)
至此,我们成功消除了浮点运算,实现布雷森汉姆算法。
代码实现

int ierror = 0;
int y = ay;
for(int x = ax; x < bx; x ++){if(steep)framebuffer.set(y,x,color);elseframebuffer.set(x,y,color);ierror += 2 * std::abs(by - ay);if(ierror > (bx - ax)){y += by > ay ? 1 : -1;ierror -= 2 * (bx - ax);}
}

从作者的测试结果来看,布雷森汉姆算法的实际速度要慢于第一次的优化,原因则是现在的整数运算并不总是比浮点运算更高效。

至于无分支结构的优化,可以在作者的博客中进行了解,我实际进行的测试中,作者的无分支形式实际要花费更多的性能,可能是现在的CPU的对分支版本的优化更加高效,不充分的无分支形式无法在其中获得优势

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/892302.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2025-2-28-媒体查询

媒体查询示例 <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=devi…

Sunshine Moonlight 串流 再玩一把

前言 其实我已经配置好了,目前的配置是不过我额外安装vdd是因为基地版自带的那个显示屏有点问题。 只不过我自己也忘了有什么问题了。 但是现在什么?有更新? 更新来玩玩。 博主能这样随便更新,是因为这个软件不怎么用,而且喜欢折腾才这样做的,如果经常需要使用建议是稳定…

牛客 周赛82 20250227

牛客 周赛82 20250227 https://ac.nowcoder.com/acm/contest/102303 A: 题目大意:给定字符串 \(s\) ,判断首尾是否相同 #include<bits/stdc++.h> #define cintie ios::sync_with_stdio(false);cin.tie(0);cout.tie(0); #define Trd int T;cin>>T;while (T--)sol…

002搜索框-搜索功能的实现

1、设置js的data值 给Zcpmingxi一个测试值 2、把 sousuo这个名字 绑定到wxml里面的 搜索框上 <inpu/>         是一个搜索框标签 type="text"        表示格式是文本格式 value="{{sousuo}}"    …

Spring面试知识

Spring优点 1.通过控制反转和依赖注入实现低耦合 2.面向切面编程,将业务逻辑和系统功能区分开来(日志记录、事务管理、安全性) 3.切面和模板可以减少板式代码 4.声明式事务的支持(@Translation)可以实现对事务代码的灵活管理,提高代码的质量和效率 5.多种内置的框架(Mybaties…

GVIM环境配置记录

背景 GVIM只用自带安装的软件,可以完成文本编辑的功能,不过很多开发者编写了很多插件,配合这些插件来做文字代码编辑,能很好地提高速度与效率 GVIM的配置环境,装机后只配置一次,很容易忘记,下次重装系统或者配新机时,又要重新查找相关资料 这里做一次记录,方便后续重新…

VIM环境配置记录

背景 VIM只用自带安装的软件,可以完成文本编辑的功能,不过很多开发者编写了很多插件,配合这些插件来做文字代码编辑,能很好地提高速度与效率 VIM的配置环境,装机后只配置一次,很容易忘记,下次重装系统或者配新机时,又要重新查找相关资料 这里做一次记录,方便后续重新配…

endnote 基础使用

因为工作的原因,综述格式统一使用endnote。一小时速成,可以满足基础的综述引用。 安装 安装的是endnote 21,这个界面感觉比较简洁。 EndNote 21 的安装包中有汉化补丁 "D:\Program Files (x86)\EndNote 21\安装包\Crack\汉化补丁.exe" 2024-11-18 Endnote21更新-…

Day1-Markdown学习笔记

MarkDown学习 标题 一个#然后空格加内容是一级标题,两个是二级标题,三个是三级标题 字体 hello world hello world *** hello world*** hello world 一对*加上内容是斜体,两对是加粗,三对是既加粗又斜体, 一对~是加横线,就是废弃的意思 引用引用别人的内容一个> 加…

2025-03-02 闲话

2025-03-02 闲话走进这里时,求是园欢迎您。离开时,它会说,再见。昨天十一点,从这里走上蒋墩路,过了马路,是夜晚的学军。周末不再灯火通明,只剩下校门口的主灯,照亮着蓝色匾额的威严。昨天发了点牢骚,2023 年时进这个校园,不需要预约,刷脸时能发现不只一个我在这里的…

Semantic Kernel:接入azure中的deepseek-r1

SemanticKernel已经支持deepseek-r1了,官方的Blog地址是https://devblogs.microsoft.com/semantic-kernel/using-deepseek-models-in-semantic-kernel,同时给出了接入的Demo,遗憾的是deepseek不支持API的充值,没有办法测试。办法总比困难多,正好微软现在支持在Azure部署de…