技术宅的结界

 找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 223|回复: 3
收起左侧

【汇编】NASM编译期间光追渲染——直接生成图像bmp

[复制链接]

1074

主题

2508

帖子

6万

积分

用户组: 管理员

一只技术宅

UID
1
精华
225
威望
432 点
宅币
20384 个
贡献
42455 次
宅之契约
0 份
在线时间
1932 小时
注册时间
2014-1-26
发表于 2020-12-27 13:19:48 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有帐号?立即注册→加入我们

x
https://github.com/0xAA55/NASMCompileTimeRayTracing

rtdemo.png

上面这副图像,是NASM编译器在编译asm源码的期间渲染出来的。渲染是在编译期间完成的,并非编译后得到的可执行文件完成的。源码编译后并不会产生任何obj或者exe,而是通过生成bmp的文件结构直接产生bmp图像。这,就是nasm平坦模型汇编能轻易做到的事情——生成任意二进制文件。

当然使用gcc进行编译期间光追渲染也是可以的,过程类似于给STM32搭建编译环境用于生成STM32的ROM镜像。使用C++的constexpr关键字进行编译期间表达式的运行,生成光追图像的像素数组,然后使用链接器脚本捏bmp文件结构并植入光追图像的像素数组,链接得到elf后使用objcopy工具将bmp位图抓出来即可。只不过这样的话我感觉没多少挑战性——你可以使用浮点数,并且语法上你可以写得很像样,对比使用NASM的方式,你既不能使用浮点数进行计算,又不能写函数。

虽然提到了光追,但是它和NVIDIA RTX显卡的光追技术毫无关联。此处使用的光追算法叫SDF光追。图中的三个镜面反射球体之间相互映照倒影,并且地面上也有球体和球体里的球体的倒影,所有球体和地面都映照出了太阳光和天空。传统渲染方式是难以做到曲面倒影的多重映照的,但光追方式渲染则很轻松。

使用NASM生成bmp位图文件本身不难,只要做到以下几点就可以生成格式正确的bmp位图:
  • 生成正确的BMP文件头部。
    一个BMP文件头部至少需要一个 BITMAPFILEHEADER 和 BITMAPINFOHEADER ,并且根据情况你可能需要调色板或者位域表。
    不过我打算使用最经典的24位真彩色格式,这个格式既不需要调色板,也不需要位域表。24位真彩色位图每个像素是由三个字节构成的,从前到后分别是蓝、绿、红的分量。非常直观简单。
    对于24位真彩色格式,BITMAPFILEHEADER 只需要填写 bfType、bfSize、bfOffBits三个字段,然后 BITMAPINFOHEADER 只需要填写 biSize、biWidth、biHeight、biPlanes、biBitCount 这几个字段就行,其它的填 0 就行。
  • 每一行像素的字节数要补齐到4的倍数。不仅仅是BMP位图文件要求这样做,通常情况下无压缩的图像文件都需要满足这个要求,包括GDI内存图像。
    以24位真彩色位图格式为例子,假设我的图像宽度是1像素,相当于每一行像素是3字节,此时我们需要在行尾补充一个字节(数值随意,但一般补充0)来保证每一行都是4个字节长度的。同理,如果图像宽度是2,那么每行像素的字节数是6,对应的我们要补充两个字节来保证每行的长度是8(也就是4的2倍)。
  • BMP位图的行序默认是从图像底部到顶部排列的,但可以通过把头部的biHeight字段设为负数来反转行序。
    我感觉这倒不是一个事儿。只是在使用libpng、libjpeg这些库的时候,或者使用OpenGL、DirectX的时候需要注意行序。


对于NASM而言,也就是使用几个 DD DW DB 语句就行。这些语句用于在当前文件的二进制的当前位置插入二进制数据。

bmp24.png

如上图,我使用了一些宏和常量定义等,用来保证在可以灵活调整位图大小的同时生成的头部数据是正确的。

在填写好了头部信息后,接下来就是图像数据,也就是一行一行来的像素颜色值数据了。众所周知,BMP图像是由一个个像素构成的,每个像素相当于一个小格子,每个格子有自己的颜色。这些格子共同组成一幅图像。所以我们要通过使用这些格子的颜色来表现图像。

而光线追踪算法,就是通过对每一个像素进行光源的追踪,来计算这个像素的颜色,最终生成完整图像的算法。

通常情况下,这样的计算由GPU来完成。GPU擅长对大批量的数据做相同的计算,从摄像头发射出大量的“视线”,由GPU对每一根“视线”所经过的路径做折射、反射的计算,对“视线”经过的所有场景物体表面进行累计,直到“视线”到达光源后,根据光源的颜色可以计算出该“视线”对应像素的颜色。顺带一提,Autodesk Maya使用的Arnold渲染器是完全依靠CPU进行光追渲染的,效率极低,根本做不到实时光追渲染,虽然它的目标也并不是实时光追渲染,但这令我感到很low。因为很多地方我感觉明明可以使用GPU实现的

shadertoy网站上,经常可以看到各路大佬实现的复杂场景渲染,基本都通过使用SDF光追算法完成,在细节上见仁见智,实现风格迥异的作品。这些大佬的作品,经过我的测试,绝大多数都可以在 GTX 980 显卡上实现达到垂直同步(60 fps)的帧数,即使使用了光追算法,也不依赖NVIDIA的RTX显卡。毕竟,RTX在光追渲染的贡献上,也只是提供了一个单独的硬件单元,用于进行批量的射线到三角形面片的相交计算上,某种程度上可以理解为对某种暴力算法提供了硬件单元而已,并不代表它对任何方式的光追都有帮助。

虽然尝试实现高效率的GPU光追渲染非常有意义,但在看到有人用Excel的表格做光追以后,我对其行为艺术感到十分折服。我打算以我的方式也秀一把,在各种馊主意之中我随机抽选了这个——使用NASM汇编器实现编译期间光追。

其实完全可以把渲染过程包装得像写着色器那样。我使用NASM的宏的循环语句 %rep 与 %endrep 实现循环,对图像的宽度和高度做循环从而遍历图像的全部像素。然后使用宏变量 x 和 y 判断当前像素的坐标,并根据像素坐标,计算出发射出的视线的方向,并进行光追计算。

其中NASM的宏变量使用%assign的方式进行赋值。%assign 与 %define 很接近,但是有差别,那就是 %assign 会直接进行表达式展开,然后将结果作为宏定义的内容,而 %define 则并不立即展开表达式。非常适合使用 %assign 来控制宏变量。

为了便于书写,计算的过程使用来协助完成。通常情况下,编写光追算法的实例,我们会需要很多函数来协助运算,但说到函数,在当前的只能使用NASM的情况下,像%define那样的单行宏可以写成“宏函数”,但是会非常麻烦而且低效,且不允许在宏函数里定义局部的变量。而NASM的多行宏%macro虽然允许定义局部的变量和行标签,却不存在返回值的说法。对此,我的做法是使用NASM的多行宏,但让宏通过对特定名称的全局变量进行赋值来实现多行宏的函数返回值的输出。

varret.png

在计算方面,按照NASM的文档:为了让编译器尽可能跨平台,以及为了避免多余但是无用的模拟浮点数代码的使用,NASM不支持浮点数的计算,只支持浮点数的生成(比如“ DD 3.1415926f ”或者“ DQ 3.14159265358979 ”这样的语句)。只能做整数运算的话,倒是问题不大,因为我们可以使用定点数来实现小数计算,也就是自己钦定小数点的位置实现小数,VB的Currency就是典型的定点数。而定点数的加减乘除只依赖整数加减乘除计算。

fixedarith.png

此处有个问题:我们需要实现对定点数的开方计算。这是因为我们需要求向量的长度,从而求出单位向量。SDF光追是一种逼近算法,所谓SDF指的是Signed Distance Function,也就是有正负符号的距离算法,使用这个算法来计算某个点位置距离场景的远近。比方说一个点到一个球体的距离,对于SDF算法,相当于这个点到球心的距离减去球的半径,也就意味着这个点如果在球体内部,得到的距离值是负值。而为了正确利用这个距离值,使作为“视线”的射线的原点能顺着射线的方向向前,以正确的步长前进。因此我们需要得到射线的单位向量,也就是长度为 1.0 的向量。求单位向量的方法,就是先求出向量长度(用勾股定理),再让向量的各分量除以该长度值,从而得到单位向量。对于定点数开平方,最初我尝试过牛顿开方法,但感觉进行整数除法的做法令我感到不舒服(性能很伤,虽然对于编译期间的计算本质上属于跑脚本,光是解释脚本也已经够慢了)。于是使用了另一种开方算法,只使用移位和逻辑运算可以对整数进行开平方运算,而定点数可以直接使用这个开方算法。

isqr.png

有了各种各样帮助的函数,接下来就是场景的配置了,比如球体的位置和大小,光源的方向,天光和雾效果颜色等。设计一个场景的时候,这些东西都是必须的。

scenesetup.png

渲染的算法里,我是用一个叫 Map_Dist 的宏,用来计算某个点到达距离场景任意物体表面的最短距离;使用 Map_Normal 取得对应物体表面的法向量;使用 Map_Color 取得对应物体的颜色。然后使用 Map_Cast 通过给定的射线计算其对场景的交点,最后使用 RenderScene 通过给定的对应像素的“视线”计算出像素颜色。这和常见的shadertoy光追的套路是一样的

rtmain.png

这样一来应该可以了。我使用WSL进行编译。然而实际上却编译失败了,因为我的 64 GB 物理内存不够用……

QQ图片20201227123517.jpg

QQ图片20201227125227.jpg

编译过程也非常缓慢。事实上我让它就这么编译了一天,结果最后没编译成。毕竟编译期间光追渲染,本质上就是在跑脚本,而且并不是那种重视运行效率的脚本。NASM汇编器嘛,只追求编译出来的汇编指令是最优的,编译器慢点似乎无所谓……当前场景除外。

群友们也纷纷尝试编译,然后发现就算有 128 GB 的内存也不够用……

gf.png

有的群友一看形势糟糕,吓得赶紧 Ctrl+C 终止编译…

tp.png

NASM竟然这么吃内存,我严重怀疑它的 %rep 的功能本质上就是在内存里把循环节内容给复制了N遍。

为了至少能编译,我给源码多增加了一些宏,用来实现分段编译,也就是把BMP位图可以拆分为多个块,每个块单独编译。这样做的好处是在编译每个小块的时候,内存的占用不至于离谱,编译好了的话只需要把小块组合到一起即可;而编译的小块如果足够小,那我可以借助makefile -j的功能实现并行编译,也就是使用多个进程同时编译多个小块,似乎会快很多。毕竟NASM汇编器怎么看都像是单线程的东西。

结果这样一来,就成了我在makefile里面写bash做数学运算(而且我强行对美元符号 $ 做了各种转义),不那么纯粹是nasm汇编了,不过依然还是十分有节目效果的。

mkfl.png

然后我尝试编译非常小的图像,看起来非常小并且放大后非常“马赛克”,编译成功了,内存的使用看起来是“阶梯状”的。而且因为是并行编译,CPU使用率已经可以所有核心都 100% 了,但这也意味着电脑没法用了,编译期间巨卡。

QQ图片20201227124530.jpg

QQ图片20201227124525.jpg

虽说当时我并没有给球体上色(我指定了白色),也没有把地板设置为格子砖纹路,但渲染出画面后还是十分兴奋的。

但因为渲染的过程非常缓慢,我觉得至少也要能看到一个预览图,所以我继续修改makefile,使其能出预览图,看到渲染的过程。

pv.png

不过效果还是挺呵呵的。这个 preview.asm 的汇编文件的生成非常慢,而它的编译也很慢。应该是bash的锅。所以实际上预览图不仅很慢,看到的也是“很久以前的预览图”。

在调整好分片段编译后,我成功实现了编译较大尺寸的图像。当然当时的场景设置比较粗糙,球体和地面都是白的,天空的颜色是灰色的,太阳在头顶,所以看起来很单调。

3b.png

经过场景的细致化调整后,我给球体上了颜色,增加了定点数的小数位数,并且增加了Bounding box用于优化渲染速度,然后在优化了渲染速度的前提下增加了步进的深度。在渲染耗时稍微减小的同时,场景更加精细了。

cb.png

再在这个基础上,我把太阳光的方向设置为斜向,然后把天空设置为蓝色,就成了现在的最终效果了。

同样的场景和光追算法,我在Shadertoy上实现了一下,场景流畅,帧数稳定。很显然,算法本身并没有什么问题。只是因为我交给NASM编译器的任务过于繁重,才导致编译期间的纯CPU光追渲染如此缓慢。

fsc.png

在GPU上只需要不到0.005秒的时间就可以渲染这样一个分辨率为1920x1080的画面。Autodesk Maya的Arnold渲染器如果也使用了GPU的话,说不定在渲染性能上能缩短到只需要原先 5% 的时间呢。

评分

参与人数 1威望 +10 宅币 +30 贡献 +10 收起 理由
watermelon + 10 + 30 + 10 牛!

查看全部评分

回复

使用道具 举报

8

主题

21

帖子

337

积分

用户组: 中·技术宅

UID
5148
精华
2
威望
16 点
宅币
244 个
贡献
30 次
宅之契约
0 份
在线时间
16 小时
注册时间
2019-7-17
发表于 2020-12-27 14:37:56 | 显示全部楼层
A5太厉害啦!!!

28

主题

308

帖子

1811

积分

用户组: 上·技术宅

UID
3808
精华
10
威望
99 点
宅币
1100 个
贡献
155 次
宅之契约
0 份
在线时间
332 小时
注册时间
2018-5-6
发表于 2020-12-29 10:30:17 | 显示全部楼层
这工作量,很丰满啊!
真·计 算 机
渲染的真的挺逼真的, 就像真的一样。
Passion Coding!

1074

主题

2508

帖子

6万

积分

用户组: 管理员

一只技术宅

UID
1
精华
225
威望
432 点
宅币
20384 个
贡献
42455 次
宅之契约
0 份
在线时间
1932 小时
注册时间
2014-1-26
 楼主| 发表于 2020-12-29 15:24:31 | 显示全部楼层
watermelon 发表于 2020-12-29 10:30
这工作量,很丰满啊!
真·计 算 机
渲染的真的挺逼真的, 就像真的一样。 ...

接下来我计划做另外两个好玩的,一个C++的,一个批处理的。

本版积分规则

QQ|申请友链||Archiver|手机版|小黑屋|技术宅的结界 ( 滇ICP备16008837号 )|网站地图  

GMT+8, 2021-1-15 23:28 , Processed in 0.100840 second(s), 35 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表