可微渲染框架Nvdiffrast(一):配置與入門

零. 前言

在學(xué)習(xí)可微渲染前,需要掌握圖形學(xué)入門知識,可參考Metal與圖形渲染入門篇:繪制圖片。

一. 可微渲染與Nvdiffrast

1. 可微渲染的作用

在人工智能領(lǐng)域中,深度學(xué)習(xí)發(fā)揮著非常重要的作用,我們知道,在深度學(xué)習(xí)領(lǐng)域有兩個非常重要的概念:前向傳播和反向傳播,反向傳播要求有對每個輸入值期望得到的已知輸出,來計算損失函數(shù)的梯度,反向傳播的前提之一,就是可微。

傳統(tǒng)的光柵化渲染管線不可微,其原因是:在傳統(tǒng)渲染中,光柵化、深度混合階段都是離散的,這并不符合神經(jīng)網(wǎng)絡(luò)訓(xùn)練的要求。

而可微渲染,讓傳統(tǒng)的渲染賦予了深度學(xué)習(xí)的可能性,我們可以基于可微渲染做非常多的事情,如最近大火的AIGC,將人工智能和計算機圖形學(xué)融合起來,是一件非常讓人振奮的目標,能夠創(chuàng)造無限的可能性。

2. Nvdiffrast的概述

Nvdiffrast支持開發(fā)者根據(jù)Rasterization,Interpolation,Texture filtering,Antialiasing四個接口進行自定義操作,實現(xiàn)自己想要的可微渲染的效果??梢曰贑uda和OpenGL進行開發(fā),對于開發(fā)人員來說非常友好。

二. Nvdiffrast的配置和跑通

這次復(fù)現(xiàn)基本沒踩什么坑,根據(jù)官網(wǎng)拉取倉庫,并在根目錄下執(zhí)行

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

執(zhí)行代碼輸出三角形圖片:

python samples/torch/triangle.py --cuda

三. 代碼實戰(zhàn)(triangle.py)

本文首先分析最簡單的源碼triangle.py,這個文件主要定義了一個三角形的頂點,并使用光柵化和插值操作,輸出了一個三角形。

1 Rasterize(光柵化)

1.1 函數(shù)調(diào)用

rasterize函數(shù)參數(shù)如下:

  • 輸入:

glctx:上下文
pos:頂點坐標,格式為(x, y, z, w)
tri:頂點的標號
resolution:生成的像素點的數(shù)量(height * width,必須是8的倍數(shù))

  • 輸出:

第一個元素rast:輸出batch_size個、resolution的維度(height * width)個像素點,每個像素點的內(nèi)容為(u, v, z/w, triangle_id),其中:(u, v)對應(yīng)的是坐標,z/w代表深度,triangle_id對應(yīng)該像素點所在的三角形id,在這個例子下,由于只有一個三角形,triangle_id恒為1);如果像素點不在三角形內(nèi),則輸出(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)

# 三角形的三個頂點對應(yīng)的標號
tri = tensor([[0, 1, 2]], dtype=torch.int32)

# 產(chǎn)生的像素點的數(shù)組,這里生成了8 * 8個像素點,每個像素點的內(nèi)容為(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')

根據(jù)輸出可以發(fā)現(xiàn),共有64個像素點,從上到下,每一行在三角形內(nèi)的像素點的數(shù)量分別為:0、5、4、3、2、1、0、0,下圖是該三角形的示意圖(但這一步還不會帶顏色)。

2 插值(Interpolation)

2.1 函數(shù)調(diào)用

interpolate的調(diào)用參數(shù)如下:

  • 輸入:

attr:感覺說attributes這個說得不是很清楚,可能是每個頂點對應(yīng)的RGB值,插值時,會根據(jù)和三個頂點的距離及這三個頂點對應(yīng)的RGB,去計算當前像素的RGB值。

rast:上面光柵化的輸出作為這里的輸入

tri:和光柵化的一樣,頂點的標號,可能對應(yīng)的就是attr。

  • 輸出:

第一個元素out:輸出batch_size個、(height * width)個像素點,每個像素點的內(nèi)容為歸一化后的(r, g, b);如果像素點不在三角形內(nèi),則輸出(0, 0, 0, 0)。

第二個元素貌似是用于反向傳播,本例子未使用,先不管,輸出

2.2 舉例分析

繼續(xù)以上面的例子,其代碼如下:

# 三角形的三個頂點對應(yīng)的標號
tri = tensor([[0, 1, 2]], dtype=torch.int32)

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

# 插值時三個頂點對應(yīng)的RGB權(quán)重
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')

這一步相對于上一步會有個顏色的插值操作,左上角對應(yīng)的[R, G, B]接近[1, 0, 0]。

(PS:為什么不是嚴格的[1, 0, 0],個人見解是:將畫布分割成了一個8 * 8的像素格子,但三角形左上角真正的頂點坐標位于[-1, 1]區(qū)間的(-0.8, -0.8)中,假設(shè)畫布大小為8 * 8,那左上角的像素點距離上方和左方1個格子,而真正的頂點距離上方和左方8 * 0.2 / 2 = 0.8個格子,因此,左上角的像素點的R值約為1 - 0.2 = 0.8)

大概是下面的圖的意思,白色的代表真正的頂點。

3. 坐標和色值轉(zhuǎn)換、輸出圖像

根據(jù)上面插值步驟得到的64個像素點的RGB值,需要對其進行坐標和色值轉(zhuǎn)換,并輸出圖像,這一步比較好理解,不贅述了,看看代碼和輸出:

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]]]

值得注意的是,坐標轉(zhuǎn)換過來后,每一行在三角形內(nèi)的像素點的數(shù)量分別為:0、0、1、2、3、4、5、0了,即對應(yīng)之前那個圖像,就對了

參考

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

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容