0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

使用Nuba扩展在Python中编写光线跟踪应用程序

星星科技指导员 来源:NVIDIA 作者:Michael Yh Wang 2022-06-21 15:03 次阅读

光线跟踪是一种渲染算法,它可以通过模拟光如何传输以及与不同材质的交互来生成照片级真实感图像。如今,它被广泛应用于游戏开发、电影制作和物理模拟中,将图像带入生活。

然而,光线跟踪算法计算量大,需要在 GPU 上进行硬件加速才能实现实时性能。

为了利用光线跟踪的硬件功能,人们发明了各种工具链和语言来满足需要,例如 openGL 和着色语言。

通常,这些软件工具链的构建过程会给 Python 开发人员带来重大挑战。为了减轻困难并为编写光线跟踪内核提供熟悉的环境, NVIDIA 为 PyOptiX 开发了 Numba 扩展。这种扩展使图形研究人员和应用程序开发人员能够减少从构思到实现的时间,并缩短每次迭代的开发周期。

在本文中,我将概述 NVIDIA 光线跟踪引擎 PyOptiX ,并解释 Python JIT 编译器 Numba 如何加速 Python 代码。最后,通过一个完整的光线跟踪示例,我将引导您完成使用 PyOptiX 的 Nuba 扩展的步骤,并用 Python 编写一个加速的光线跟踪内核。

什么是 NVIDIA OptiX 和 PyOptiX ?

NVIDIA RTX 技术使光线跟踪成为许多现代渲染管道中的默认渲染算法。由于对独特外观的需求是无限的,因此需要灵活定制渲染管道。

NVIDIA RTX 光线跟踪管道是可定制的。通过配置光在各种材质上的传输、反射和折射方式,可以在对象上实现独特的外观,例如有光泽、有光泽或半透明。通过配置光线的生成方式,可以相应地更改视图的视野和透视效果。

为了满足这一需求, NVIDIA 开发了 NVIDIA OptiX ,这是一种光线跟踪引擎,可用于配置硬件加速的光线跟踪管道。 PyOptiX 是 NVIDIA OptiX Python 接口。此接口为 Python 开发人员提供了与使用 C ++编写的 NVIDIA OptiX 开发人员相同的功能。

内核函数

要自定义图像方面,可以使用内核函数,也称为内核方法或内核。您可以将内核视为一组将数据输入转换为所需形式的算法。本地 NVIDIA OptiX 开发人员可以使用 CUDA 编写内核。使用 Nuba 扩展,您可以在 Python 中编写光线跟踪内核。

Numba 和 Numba 的性能更高。库达大学

光线跟踪是一种计算密集型算法。虽然理论上可以使用标准 C Python 解释器运行光线跟踪内核,但渲染常规光线跟踪图像需要几天的时间。此外, NVIDIA OptiX 要求内核可以在 GPU 设备上运行,以便与其余渲染管道集成。

使用 Numba ,一个实时的 GPU 函数编译器,您可以使用 Python 硬件执行并加速您的 Python 光线跟踪内核。 Numba 解析 Python 功能代码并将其转换为有效的机器代码。在较高层次上,该过程分为七个步骤:

该函数的字节码由字节码编译器生成。

分析了字节码。生成控制流图( CFG )和数据流图( DFG )。

通过字节码、 CFG 和 DFG ,可以生成 Numba 中间表示( IR )。

根据函数输入的类型,推断每个 IR 变量的类型。

Nuba IR 被重写,并得到 Python 特定的优化。

Numba IR 降低到 LLVM IR ,并执行更一般的优化。

LLVM IR 由 LLVM 后端使用,并生成优化的 GPU 机器代码。

poYBAGKxbeyAeBLfAACSiNqpRA4493.png

图 1 Numba 编译管道的高级视图

图 1 显示了前面提到的编译管道的图形概述。这篇关于 Numba 编译器管道的快速教程只提供了对 Numba 内部架构的一点了解。

下面的代码显示了一个示例 GPU 内核,该内核计算两个 3 元素向量的点积。

@cuda.jit(device=True)
def dot(a, b): return a.x * b.x + a.y * b.y + a.z * b.z

因为 Numba 可以将任何 Python 函数转换为本机代码,所以在 Numba CUDA 内核中, Python 用户拥有同等的权限,就像他们在用本机 CUDA 编写内核一样。此代码显示可在设备上执行的点产品。有关更多信息,请参阅 Numba Examples 。

介绍 PyOptiX 的 Nuba 扩展

要自定义光线跟踪管道的特定阶段,必须将 Nuba 内核转换为 NVIDIA OptiX 引擎可以理解的内容。 NVIDIA 为 PyOptiX 开发了 Numba 扩展以实现这一目标。

扩展包括自定义类型定义和内部函数降维。 NVIDIA OptiX 附带一组内部类型:

OptixTraversableHandle

OptixVisibilityMask

SbtDataPointer

功能,如optix.Trace

为了让 Nuba 对这些新类型和方法执行类型推断,您必须注册这些类型并在编译用户内核之前提供这些方法的实现。目前, NVIDIA 正在扩展支持的类型和内部函数,以添加更多示例。

通过向 Numba 公开这些类型和内部函数,您现在可以编写内核,它不仅针对 GPU ,而且可以专门针对 GPU 进行光线跟踪内核。与 Numba CUDA 结合使用,您可以编写功率相等的光线跟踪内核,就像为 NVIDIA OptiX 编写本机 CUDA 光线跟踪内核一样。

在下一节中,我将介绍一个带有 PyOptiX-Numba 扩展的 Hello-World 示例。在此之前,让我快速回顾一些光线跟踪算法的基础知识。

射线追踪基础

假设您使用相机拍摄图像。场景中的光源发射光线,光线沿直线传播。当光线击中物体时,它会从表面反射,最终到达相机传感器

从较高的层次来看,光线跟踪算法将遍历到达图像平面的所有光线,以在场景中确定光线的相交位置和相交内容。找到交点后,可以采用各种着色技术来确定交点的颜色。然而,也有一些射线不会击中场景中的任何东西。在这种情况下,这些光线被视为“丢失”目标。

使用 PyOptiX 的 Numba 扩展对三角形进行光线跟踪的步骤

在下面的示例中,我将展示 PyOptiX 的 Numba 扩展如何帮助您编写自定义内核,以定义光线生成、光线命中和光线未命中时的光线行为。

场景设置

我将您看到的视图建模为一个图像平面,它通常略位于相机前面。相机被建模为三维空间中的一个点和一组相互正交的向量。

图 2 三角形渲染示例的场景设置

照相机

相机建模为三维中的一个点。摄像机的三个矢量, U 、 V 和 W 、 用于显示侧面、向上和正面方向 。这唯一地确定了相机的位置和方向。

为了简化后续光线生成的计算, U 和 V 矢量不是单位矢量。相反,它们的长度与图像的纵横比成比例匹配。最后, W 向量的长度是相机和图像平面之间的距离。

射线生成内核

射线生成内核是该算法的核心。射线原点和方向在此处生成,然后传递给跟踪调用。它的强度从其他内核中检索出来,并作为图像数据写入。在本节中,我将讨论在此内核中生成光线的方法。

使用相机和图像平面,可以生成光线。采用以图像中心为原点的坐标系约定。图像像素中坐标的符号表示其相对于原点的相对位置,其大小表示距离。使用此属性,将相机的 U 和 V 矢量与像素位置的相应元素相乘,然后将它们相加。结果是从图像中心指向像素的向量。

最后,将该向量添加到 W 或前向量,这将生成一条光线,该光线从相机位置开始,穿过图像平面上的像素。图 3 显示了一条光线的分解,该光线起源于相机,并穿过图像平面中的点( x 、 y )。

图 3 穿过像素的光线分解 ( x , y )

在代码中,可以使用 optix 的两个内在函数optix.GetLaunchIndex和optix.GetLaunchDimensions检索图像平面的像素索引和图像尺寸。接下来,像素索引被归一化为[-1.0 , 1.0]。下面的代码示例显示了 Nuba CUDA 内核中的这种逻辑。

@cuda.jit(device=True, fast_math=True)
def computeRay(idx, dim): U = params.cam_u V = params.cam_v W = params.cam_w # Normalizing coordinates to [-1.0, 1.0] d = float32(2.0) * make_float2( float32(idx.x) / float32(dim.x), float32(idx.y) / float32(dim.y) ) - float32(1.0) origin = params.cam_eye direction = normalize(d.x * U + d.y * V + W) return origin, direction def __raygen__rg():
 # Look up your location within the launch grid
 idx = optix.GetLaunchIndex() dim = optix.GetLaunchDimensions()  # Map your launch idx to a screen location and create a ray from the camera # location through the screen ray_origin, ray_direction = computeRay(make_uint3(idx.x, idx.y, 0), dim)

此代码示例显示了computeRay的助手函数,该函数计算光线的原点和方向向量。

接下来,将生成的光线传递给内部函数optix.Trace。这将初始化光线跟踪算法。底层 optiX 引擎遍历基本体,计算场景中的交点,最后返回光线的强度。下面的代码示例显示了对optix.Trace的调用。

# In __raygen__rg
 payload_pack = optix.Trace( params.handle, ray_origin, ray_direction, float32(0.0), # Min intersection distance float32(1e16), # Max intersection distance float32(0.0), # rayTime -- used for motion blur OptixVisibilityMask(255),  # Specify always visible uint32(OPTIX_RAY_FLAG_NONE), uint32(0), # SBT offset -- Refer to OptiX Manual for SBT uint32(1),  # SBT stride -- Refer to OptiX Manual for SBT uint32(0),  # missSBTIndex -- Refer to OptiX Manual for SBT )

射线命中内核

在光线命中内核中,您可以编写代码来确定光线的每个通道的强度。如果三角形顶点是使用 NVIDIA OptiX 内部数据结构设置的,则可以调用 NVIDIA OptiX 内在optix.GetTriangleBarycentrics来检索命中点的重心坐标。

要使颜色更有趣,请将此坐标插入该像素的颜色中。颜色的蓝色通道设置为 1.0 。光线的强度应传递给光线生成内核进行进一步的后处理,并写入图像。

NVIDIA OptiX 通过有效负载寄存器在内核之间共享数据。使用setPayload功能将有效负载寄存器的值设置为光线强度。默认情况下,有效负载寄存器是整数类型。使用 CUDA 内部函数float_as_int将浮点值解释为整数,而不更改位。

@cuda.jit(device=True, fast_math=True)
def setPayload(p): optix.SetPayload_0(float_as_int(p.x)) optix.SetPayload_1(float_as_int(p.y)) optix.SetPayload_2(float_as_int(p.z)) def __closesthit__ch():  # When a built-in triangle intersection is used, a number of fundamental # attributes are provided by the NVIDIA OptiX API, including barycentric coordinates. barycentrics = optix.GetTriangleBarycentrics() setPayload(make_float3(barycentrics, float32(1.0)))

射线未命中内核

“光线未命中”内核设置未命中场景中任何对象的光线的颜色。在这里,您可以将它们设置为背景色。

bg_color是在设置渲染管道期间在着色器绑定表中指定的一些数据。现在,请注意,这是一组硬编码的浮点数,表示场景的背景色。

def __miss__ms(): miss_data = MissDataStruct(optix.GetSbtDataPointer()) setPayload(miss_data.bg_color)

将强度转换为颜色并写入图像

现在,您已经为所有光线定义了颜色。颜色在光线生成内核中作为payload_pack数据结构从optix.trace调用中检索。还记得在 ray hit 和 ray miss 内核中,必须将浮点数的位解释为整数吗?使用int_as_float功能还原此步骤。

现在,您可以直接将这些值写入图像,它仍然看起来很棒。再多做一步,对原始像素值执行后处理步骤,这对于更复杂场景中的出色图像非常重要。

您检索到的值只是光线的原始强度,它与光线携带的能量级别成线性比例。虽然这符合你的物理世界模型,但人眼不会以线性方式对光刺激作出反应。相反,它遵循输入的映射,通过幂函数进行响应。

为此,对强度进行 gamma correction 测试。此外,大多数查看此图像结果的用户都在观看具有 sRGB 颜色空间的监视器。假设光线跟踪世界中的值位于 CIE-XYZ color space 中,并应用颜色空间转换。最后,将颜色值量化为 8 位无符号整数。

下面的代码示例显示了用于后期处理颜色强度并将其写入光线生成内核中的像素阵列的辅助函数。

@cuda.jit(device=True, fast_math=True)
def toSRGB(c):
 # Use float32 for constants
 invGamma = float32(1.0) / float32(2.4) powed = make_float3( fast_powf(c.x, invGamma), fast_powf(c.y, invGamma), fast_powf(c.z, invGamma), ) return make_float3( float32(12.92) * c.x if c.x < float32(0.0031308) else float32(1.055) * powed.x - float32(0.055), float32(12.92) * c.y if c.y < float32(0.0031308) else float32(1.055) * powed.y - float32(0.055), float32(12.92) * c.z if c.z < float32(0.0031308) else float32(1.055) * powed.z - float32(0.055), ) @cuda.jit(device=True, fast_math=True)
def make_color(c): srgb = toSRGB(clamp(c, float32(0.0), float32(1.0))) return make_uchar4( quantizeUnsigned8Bits(srgb.x), quantizeUnsigned8Bits(srgb.y), quantizeUnsigned8Bits(srgb.z), uint8(255), ) # In __raygen__rg result = make_float3( int_as_float(payload_pack.p0), int_as_float(payload_pack.p1), int_as_float(payload_pack.p2), )  # Record results in your output raster params.image[idx.y * params.image_width + idx.x] = make_color(result)

总结

PyOptiX 允许您使用 Python 设置光线跟踪渲染管道。 Nuba 将 Python 函数转换为与渲染管道兼容的设备代码。 NVIDIA 将这两个库组合到 PyOptiX 的 Nuba 扩展中,使您能够在完整的 Python 环境中编写加速光线跟踪应用程序。

结合 Python 已经拥有的丰富而活跃的环境,您现在可以解锁构建光线跟踪应用程序的真正能力,硬件加速。 下载演示 亲自体验 PyOptiX 的 Numba 扩展!

下一步是什么?

PyOptiX Numba 扩展正处于开发阶段, NVIDIA 正在努力添加更多示例,并使 NVIDIA OptiX 原语的键入更加灵活和 Pythonic

关于作者

Michael Yh Wang 是 NVIDIA Rapids 的软件工程师。目前,他将自己的工程技能贡献给了 cuDF 、 cuSpatial 和 Numba 。在加入 NVIDIA 之前,他获得了耶鲁大学的理学硕士学位。他早期的经验包括在一个独立电影项目中担任视觉效果主管,并在 WAIC 2020 hackathon 竞赛中获得第一名。 Michael 对软件工程、计算机图形算法和编译器技术有浓厚的兴趣。他相信,在未来,通过编译器和语言创新,加速计算将更容易为公众所接受。

审核编辑:郭婷

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • NVIDIA
    +关注

    关注

    14

    文章

    4585

    浏览量

    101694
  • gpu
    gpu
    +关注

    关注

    27

    文章

    4413

    浏览量

    126640
  • 编译器
    +关注

    关注

    1

    文章

    1575

    浏览量

    48606
收藏 人收藏

    评论

    相关推荐

    如何用RK3588编写应用程序

    由于笔者平时的嵌入式开发工作,主要是集中嵌入式Linux和Qt应用开发方向,因此,本篇文章我们尝试参考飞凌官方提供的嵌入式Linux+Qt相关的资料,编写一个Qt应用程序,并运行在OK3588-C
    发表于 12-19 13:24

    python程序文件扩展名主要有哪两种

    Python程序文件的扩展名主要有两种,分别是.py和.pyw。 .py扩展名: .py是Python
    的头像 发表于 11-29 14:30 2603次阅读

    python程序的文件扩展

    Python是一种高级编程语言,它的文件扩展名为.py。在本文中,我们将详细讨论Python程序文件的扩展名,包括其含义、用途以及与其他文件
    的头像 发表于 11-29 14:25 867次阅读

    开发java应用程序的基本步骤是

    Java应用程序。确定您希望应用程序能够执行的任务和提供的功能。这将有助于指导您在开发过程中进行决策并确定实现代码的方式。 2.设计应用程序:在开始编写代码之前,您应该设计
    的头像 发表于 11-28 16:52 660次阅读

    python怎么运行程序

    Python程序 使用任何文本编辑器(如记事本、Sublime Text、Atom等)编写Python程序
    的头像 发表于 11-24 09:25 2029次阅读

    python编写斐波那契数列

    斐波那契数列是一个非常经典的数学问题,它具有广泛的应用和研究价值。在这篇文章中,我将使用Python编写斐波那契数列的代码,并详细解释代码的逻辑和执行过程。 首先,让我们来介绍一下斐波那契数列的定义
    的头像 发表于 11-21 15:04 719次阅读

    TinyDB :一个纯Python编写的轻量级数据库

    TinyDB 是一个纯 Python 编写的轻量级数据库,一共只有1800行代码,没有外部依赖项。 TinyDB的目标是降低小型 Python 应用程序使用数据库的难度,对于一些简单
    的头像 发表于 10-21 10:22 390次阅读

    一行代码将Python程序转换为GUI应用程序

    Gooey项目支持用一行代码将(几乎)任何Python 2或3控制台程序转换为GUI应用程序。 1.快速开始 开始之前,你要确保Python和pip已经成功安装在电脑上,如果没有,可以
    的头像 发表于 10-17 11:41 422次阅读
    一行代码将<b class='flag-5'>Python</b><b class='flag-5'>程序</b>转换为GUI<b class='flag-5'>应用程序</b>

    如何使用Tokio 和 Tracing模块构建异步的网络应用程序

    在 Rust 语言中,Tokio 是一个非常流行的异步运行时,它提供了高效的异步 I/O 操作和任务调度。而 Tracing 则是一个用于应用程序跟踪的框架,它可以帮助我们理解应用程序的行为和性能
    的头像 发表于 09-19 15:29 340次阅读

    基于OpenHarmony编写GPIO平台驱动和应用程序

    程序是基于OpenHarmony标准系统编写的基础外设类:GPIO驱动。
    的头像 发表于 09-12 10:04 469次阅读
    基于OpenHarmony<b class='flag-5'>编写</b>GPIO平台驱动和<b class='flag-5'>应用程序</b>

    为什么人工智能用python

    Python语言的语法简洁、易学易用,可以快速入门并快速开发出具有一定实用性的应用程序Python使用简单的缩进对代码进行结构化处理,代码逻辑更加清晰有效,便于编写和维护。
    的头像 发表于 08-13 11:34 562次阅读

    RealView调试器4.1版跟踪用户指南

    ·启用ETM的硬件的嵌入式跟踪宏单元™配置·将跟踪限制应用程序的特定区域的跟踪点·分析跟踪输出
    发表于 08-12 07:34

    RTOS应用程序设计的五个实践技巧

    我在编写RTOS应用程序的过程中,经常会遇到这些困难,包括正确确定系统中有多少任务、如何设置优先级、协调任务行为、避免常见陷阱,有时只是为了让应用程序正常工作!
    发表于 07-07 16:10 574次阅读
    RTOS<b class='flag-5'>应用程序</b>设计的五个实践技巧

    如何在python实现ESP TOUCH协议?

    设备连接到路由器。 有人能告诉我如何在 python 实现 ESP TOUCH 协议吗?这样所有模块连同树莓派都可以从一个应用程序同时配置。
    发表于 05-17 08:20

    Python写机器人上位机

    工具,Python使用 Tkinter 可以快速的创建 GUI 应用程序。由于 Tkinter 是内置到 python的安装包中、只要安装好 Python之后就能 import Tki
    发表于 05-09 11:10 0次下载
    <b class='flag-5'>Python</b>写机器人上位机