可微渲染框架Nvdiffrast(一):配置与入门

零. 前言

在学习可微渲染前,需要掌握图形学入门知识,可参考Metal与图形渲染入门篇:绘制图片

一. 可微渲染与Nvdiffrast

1. 可微渲染的作用

在人工智能领域中,深度学习发挥着非常重要的作用,我们知道,在深度学习领域有两个非常重要的概念:前向传播和反向传播,反向传播要求有对每个输入值期望得到的已知输出,来计算损失函数的梯度,反向传播的前提之一,就是可微。

传统的光栅化渲染管线不可微,其原因是:在传统渲染中,光栅化、深度混合阶段都是离散的,这并不符合神经网络训练的要求。

而可微渲染,让传统的渲染赋予了深度学习的可能性,我们可以基于可微渲染做非常多的事情,如最近大火的AIGC,将人工智能和计算机图形学融合起来,是一件非常让人振奋的目标,能够创造无限的可能性。

2. Nvdiffrast的概述

Nvdiffrast支持开发者根据Rasterization,Interpolation,Texture filtering,Antialiasing四个接口进行自定义操作,实现自己想要的可微渲染的效果。可以基于Cuda和OpenGL进行开发,对于开发人员来说非常友好。

二. Nvdiffrast的配置和跑通

这次复现基本没踩什么坑,根据官网拉取仓库,并在根目录下执行

pip install .

此外还需要一些pip包的配置,在这里,我的requirement.txt是:

certifi==2022.12.7
charset-normalizer==2.1.1
filelock==3.9.0
fsspec==2023.4.0
glfw==2.6.2
idna==3.4
imageio==2.31.5
imageio-ffmpeg==0.4.9
Jinja2==3.1.2
MarkupSafe==2.1.2
mpmath==1.3.0
networkx==3.0
ninja==1.11.1.1
numpy==1.24.4
Pillow==9.3.0
psutil==5.9.6
PyOpenGL==3.1.7
requests==2.28.1
sympy==1.12
torch==2.1.0+cu118
torchaudio==2.1.0+cu118
torchvision==0.16.0+cu118
triton==2.1.0
typing_extensions==4.4.0
urllib3==1.26.13

执行代码输出三角形图片:

python samples/torch/triangle.py --cuda

三. 代码实战(triangle.py)

本文首先分析最简单的源码triangle.py,这个文件主要定义了一个三角形的顶点,并使用光栅化和插值操作,输出了一个三角形。

1 Rasterize(光栅化)

1.1 函数调用

rasterize函数参数如下:

  • 输入:

glctx:上下文
pos:顶点坐标,格式为(x, y, z, w)
tri:顶点的标号
resolution:生成的像素点的数量(height * width,必须是8的倍数)

  • 输出:

第一个元素rast:输出batch_size个、resolution的维度(height * width)个像素点,每个像素点的内容为(u, v, z/w, triangle_id),其中:(u, v)对应的是坐标,z/w代表深度,triangle_id对应该像素点所在的三角形id,在这个例子下,由于只有一个三角形,triangle_id恒为1);如果像素点不在三角形内,则输出(0, 0, 0, 0)。

第二个元素貌似是用于反向传播,本例子未使用,先不管

1.2 举例分析

以生成一个8 * 8个像素的三角形为例,其代码如下:

# 三角形的三个顶点:(x, y, z, w)
pos = tensor([[[-0.8, -0.8, 0, 1], [0.8, -0.8, 0, 1], [-0.8, 0.8, 0, 1]]], dtype=torch.float32)

# 三角形的三个顶点对应的标号
tri = tensor([[0, 1, 2]], dtype=torch.int32)

# 产生的像素点的数组,这里生成了8 * 8个像素点,每个像素点的内容为(u, v, z/w, triangle_id)
rast, _ = dr.rasterize(glctx, pos, tri, resolution=[8, 8])

print(rast, end='\n')

输出如下:

tensor([[[[0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000]],

         [[0.0000, 0.0000, 0.0000, 0.0000],
          [0.7812, 0.1094, 0.0000, 1.0000],
          [0.6250, 0.2656, 0.0000, 1.0000],
          [0.4688, 0.4219, 0.0000, 1.0000],
          [0.3125, 0.5781, 0.0000, 1.0000],
          [0.1563, 0.7344, 0.0000, 1.0000],
          [0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000]],

         [[0.0000, 0.0000, 0.0000, 0.0000],
          [0.6250, 0.1094, 0.0000, 1.0000],
          [0.4687, 0.2656, 0.0000, 1.0000],
          [0.3125, 0.4219, 0.0000, 1.0000],
          [0.1562, 0.5781, 0.0000, 1.0000],
          [0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000]],

         [[0.0000, 0.0000, 0.0000, 0.0000],
          [0.4687, 0.1094, 0.0000, 1.0000],
          [0.3125, 0.2656, 0.0000, 1.0000],
          [0.1562, 0.4219, 0.0000, 1.0000],
          [0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000]],

         [[0.0000, 0.0000, 0.0000, 0.0000],
          [0.3125, 0.1094, 0.0000, 1.0000],
          [0.1562, 0.2656, 0.0000, 1.0000],
          [0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000]],

         [[0.0000, 0.0000, 0.0000, 0.0000],
          [0.1562, 0.1094, 0.0000, 1.0000],
          [0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000]],

         [[0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000]],

         [[0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000, 0.0000]]]], device='cuda:0')

根据输出可以发现,共有64个像素点,从上到下,每一行在三角形内的像素点的数量分别为:0、5、4、3、2、1、0、0,下图是该三角形的示意图(但这一步还不会带颜色)。

2 插值(Interpolation)

2.1 函数调用

interpolate的调用参数如下:

  • 输入:

attr:感觉说attributes这个说得不是很清楚,可能是每个顶点对应的RGB值,插值时,会根据和三个顶点的距离及这三个顶点对应的RGB,去计算当前像素的RGB值。

rast:上面光栅化的输出作为这里的输入

tri:和光栅化的一样,顶点的标号,可能对应的就是attr。

  • 输出:

第一个元素out:输出batch_size个、(height * width)个像素点,每个像素点的内容为归一化后的(r, g, b);如果像素点不在三角形内,则输出(0, 0, 0, 0)。

第二个元素貌似是用于反向传播,本例子未使用,先不管,输出

2.2 举例分析

继续以上面的例子,其代码如下:

# 三角形的三个顶点对应的标号
tri = tensor([[0, 1, 2]], dtype=torch.int32)

# 光栅化的输出
rast, _ = dr.rasterize(glctx, pos, tri, resolution=[8, 8])

# 插值时三个顶点对应的RGB权重
col = tensor([[[1, 0, 0], [0, 1, 0], [0, 0, 1]]], dtype=torch.float32)

out, _ = dr.interpolate(col, rast, tri)
Tensor([[[[0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000]],

         [[0.0000, 0.0000, 0.0000],
          [0.7812, 0.1094, 0.1094],
          [0.6250, 0.2656, 0.1094],
          [0.4688, 0.4219, 0.1094],
          [0.3125, 0.5781, 0.1094],
          [0.1563, 0.7344, 0.1094],
          [0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000]],

         [[0.0000, 0.0000, 0.0000],
          [0.6250, 0.1094, 0.2656],
          [0.4687, 0.2656, 0.2656],
          [0.3125, 0.4219, 0.2656],
          [0.1562, 0.5781, 0.2656],
          [0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000]],

         [[0.0000, 0.0000, 0.0000],
          [0.4687, 0.1094, 0.4219],
          [0.3125, 0.2656, 0.4219],
          [0.1562, 0.4219, 0.4219],
          [0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000]],

         [[0.0000, 0.0000, 0.0000],
          [0.3125, 0.1094, 0.5781],
          [0.1562, 0.2656, 0.5781],
          [0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000]],

         [[0.0000, 0.0000, 0.0000],
          [0.1562, 0.1094, 0.7344],
          [0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000]],

         [[0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000]],

         [[0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000],
          [0.0000, 0.0000, 0.0000]]]], device='cuda:0')

这一步相对于上一步会有个颜色的插值操作,左上角对应的[R, G, B]接近[1, 0, 0]。

(PS:为什么不是严格的[1, 0, 0],个人见解是:将画布分割成了一个8 * 8的像素格子,但三角形左上角真正的顶点坐标位于[-1, 1]区间的(-0.8, -0.8)中,假设画布大小为8 * 8,那左上角的像素点距离上方和左方1个格子,而真正的顶点距离上方和左方8 * 0.2 / 2 = 0.8个格子,因此,左上角的像素点的R值约为1 - 0.2 = 0.8)

大概是下面的图的意思,白色的代表真正的顶点。

3. 坐标和色值转换、输出图像

根据上面插值步骤得到的64个像素点的RGB值,需要对其进行坐标和色值转换,并输出图像,这一步比较好理解,不赘述了,看看代码和输出:

img = out.cpu().numpy()[0, ::-1, :, :] # Flip vertically.
img = np.clip(np.rint(img * 255), 0, 255).astype(np.uint8) # Quantize to np.uint8

print("Saving to 'tri.png'.")
imageio.imsave('tri.png', img)
[[[  0   0   0]
  [  0   0   0]
  [  0   0   0]
  [  0   0   0]
  [  0   0   0]
  [  0   0   0]
  [  0   0   0]
  [  0   0   0]]

 [[  0   0   0]
  [  0   0   0]
  [  0   0   0]
  [  0   0   0]
  [  0   0   0]
  [  0   0   0]
  [  0   0   0]
  [  0   0   0]]

 [[  0   0   0]
  [ 40  28 187]
  [  0   0   0]
  [  0   0   0]
  [  0   0   0]
  [  0   0   0]
  [  0   0   0]
  [  0   0   0]]

 [[  0   0   0]
  [ 80  28 147]
  [ 40  68 147]
  [  0   0   0]
  [  0   0   0]
  [  0   0   0]
  [  0   0   0]
  [  0   0   0]]

 [[  0   0   0]
  [120  28 108]
  [ 80  68 108]
  [ 40 108 108]
  [  0   0   0]
  [  0   0   0]
  [  0   0   0]
  [  0   0   0]]

 [[  0   0   0]
  [159  28  68]
  [120  68  68]
  [ 80 108  68]
  [ 40 147  68]
  [  0   0   0]
  [  0   0   0]
  [  0   0   0]]

 [[  0   0   0]
  [199  28  28]
  [159  68  28]
  [120 108  28]
  [ 80 147  28]
  [ 40 187  28]
  [  0   0   0]
  [  0   0   0]]

 [[  0   0   0]
  [  0   0   0]
  [  0   0   0]
  [  0   0   0]
  [  0   0   0]
  [  0   0   0]
  [  0   0   0]
  [  0   0   0]]]

值得注意的是,坐标转换过来后,每一行在三角形内的像素点的数量分别为:0、0、1、2、3、4、5、0了,即对应之前那个图像,就对了

参考

https://zhuanlan.zhihu.com/p/636433780

https://zhuanlan.zhihu.com/p/631784361

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容