美文网首页从九开始——可微渲染专题
可微渲染框架Nvdiffrast(一):配置与入门

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

作者: 肠粉白粥_Hoben | 来源:发表于2023-10-15 22:41 被阅读0次

    零. 前言

    在学习可微渲染前,需要掌握图形学入门知识,可参考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

    相关文章

      网友评论

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

        本文链接:https://www.haomeiwen.com/subject/gxbtidtx.html