92次发布

0.47.0 2022年4月19日
0.46.0 2021年12月31日
0.45.0 2021年11月26日
0.44.0 2021年6月28日
0.6.0 2016年7月12日

#52图形API

Download history 121/week @ 2024-03-11 144/week @ 2024-03-18 122/week @ 2024-03-25 481/week @ 2024-04-01 94/week @ 2024-04-08 148/week @ 2024-04-15 200/week @ 2024-04-22 117/week @ 2024-04-29 129/week @ 2024-05-06 118/week @ 2024-05-13 143/week @ 2024-05-20 116/week @ 2024-05-27 118/week @ 2024-06-03 101/week @ 2024-06-10 142/week @ 2024-06-17 130/week @ 2024-06-24

每月499次下载
27 个库(直接使用 19 个) 中使用

BSD-3-Clause 许可协议

305KB
5K SLOC

crates.io License

一个简单、类型安全且具有观点的图形库

luminance旨在使图形渲染简单而优雅。它是一个低级和具有观点的图形API,高度类型化(类型级计算、精细类型等),旨在简单且高效。luminance不提供尽可能多的低级功能,而是提供了一些进行渲染的方式。这既有优点也有缺点

  • 一方面,因为API具有观点性,一些动态分支和决策被完全移除/优化。一些操作会破坏状态突变或违反不变性,无法静态构造,从而确保安全性。由于使用了强类型,许多运行时检查也不需要,有助于性能。
  • 另一方面,如果您想做一些非常特定和非常低级的事情,您会发现luminance并不友好,因为它通常不向外界暴露其内部设计——这主要是出于运行时安全的原因。

关于 安全性 的注意事项:在这里,安全性 并不是像Rust定义的那样使用,而是大多数情况下指的是未定义的行为和不受欢迎的行为。如果某些操作可能导致奇怪的行为、崩溃、恐慌或黑屏,则被认为是 unsafe。这个定义显然包括了Rust定义的安全性——内存安全性。

包含的内容?

luminance是一个渲染库,而不是3D引擎或视频游戏框架。因此,它不包括特定的概念,如灯光、材质、资源管理和场景描述。它只提供可以在任何需要的地方插入的渲染库。

crates.io上有几个所谓的3D引擎。您可以自由地四处看看。

然而,luminance还包含一些有趣的功能

  • 帧缓冲区:帧缓冲区用于存储渲染结果。每次您想要进行渲染时,都需要在帧缓冲区中进行。帧缓冲区可以相互组合以产生效果和设计渲染层,这被称为合成。
  • 着色器:亮度支持五种着色器阶段
    • 顶点着色器。
    • 细分控制着色器。
    • 细分评估着色器。
    • 几何着色器。
    • 片元着色器。
  • 顶点、索引、原语和细分:这些用于定义可以由着色器渲染到帧缓冲区中的形状。在渲染时它们是必需的。即使不需要顶点数据,您仍然需要细分来发出绘制调用。
  • 纹理:纹理表示存储在GPU数组中的信息,可用于自定义视觉方面或在着色器中传递信息。它们有几种类型——例如,1D、2D、立方体贴图等。
  • 对渲染状态的控制:渲染状态是一组您可以调整以绘制帧的能力。它包括
    • 混合方程和因素。混合是从两个帧缓冲区中取两个颜色并将它们混合的过程。
    • 是否执行深度测试。
    • 面剔除。
    • 等等。
  • 以及许多其他酷炫的东西,如GPU命令管线统一接口等等…

如何深入研究?

亮度被设计得相当简单。有几种方法可以学习如何使用亮度

  • 在线文档是新手的必读。
  • “学习亮度”书籍。对新手以及已经熟悉亮度的人来说都很理想,因为它总是更新到最新版本——你可能会学到新东西!
  • 亮度示例项目。它包含大量示例,描述了如何执行具体操作。这些示例并不适合新手,如果您已经熟悉图形编程,可以查阅这些示例以查找如何执行特定操作。仍然,对于新手,hello-world示例可能值得一读。

实现和架构

亮度最初是围绕OpenGL 3.3和OpenGL 4.5 API设计的。然而,它经历了大量的变化以适应新技术和现代图形编程。尽管其API并不旨在收敛到类似Vulkan的东西,但随着时间的推移,它正在改变以更好地满足设计决策和性能影响。

亮度的当前状态由几个crate组成

  • 一个“核心”crate,亮度,它包含所有抽象的、通用的和接口代码。
  • 一个proc-macro crate,[亮度-derive],当您使用"derive"功能标志时由亮度导出。该crate允许实现核心crate的多种重要特性。
  • 一套后端实现crate,实现亮度crate的后端接口。
  • 一套窗口crate,在本地系统上执行使用核心和后端crate编写的代码(大多数情况下是窗口平台,但不仅限于)。
  • 一个特殊的crate,亮度前端,一个特殊的后端crate,允许将几个“官方”crate组合起来,提供跨平台体验,而无需选择多个后端crate——该crate为您完成这项工作。此crate主要针对最终用户crate,对于大多数用户来说应该是一个不错的选择。

核心crate

luminance 库收集了编写代码所需的所有图形技术逻辑和渲染抽象。它包含抽象实际实现类型的参数化类型和函数——按照惯例,类型变量 B(表示后端)被使用。

后端类型——即 B —— 不是由 luminance 直接提供的。通常,它们由包含技术名称后缀的库提供,例如 luminance-gl,[luminance-webgl],luminance-vk 等。这些后端库与 luminance 之间的接口在 luminance::backend 中定义。

一般来说,Something<ConcreteType, u8> 是一个单态类型,它将仅在 ConcreteType 后端上使用的代码中可用。如果你想要编写一个接受一个未指定具体类型的 8 位整数的函数,你将不得不写一些类似下面的代码

use luminance::backend::something::Something as SomethingBackend;
use luminance::something::Something;

fn work<B>(b: &Something<B, u8>) where B: SomethingBackend<u8> {
  todo!();
}

这类代码是为编写 luminance 库的人准备的。对于更常见的使用 luminance-front 库的情况,你将得到如下代码

use luminance_front::something::Something;

fn work(b: &Something<u8>) {
  todo()!;
}

luminance-front 中,后端类型是在编译和链接时选择的。这通常是人们想要的,但请记住,luminance-front 不允许同时拥有多个后端类型,这可能也是你想要使用的。

后端实现

后端实现 luminance::backend 特性,并为每个实现提供大多数情况下单一类型。重要的是要理解后端库可以提供多个后端(例如,luminance-gl 可以为每个支持的 OpenGL 版本提供一个后端——所以一个类型)。该后端类型将在整个生态系统中被用于推导后续实现者和关联类型。

如果你想实现一个后端,你不需要向任何 luminance 库推送任何代码。 luminance-* 库是 官方 的,但你也可以编写自己的后端。然而,该接口是高度 unsafe 的,主要基于 unsafe implunsafe trait 上。有关更多信息,请查阅 luminance::backend 模块的文档。

窗口

luminance 对其执行的上下文一无所知。这意味着它不知道它是否用于 SDL、GLFW、glutin、Qt、网页画布或如任天堂 Switch 这样的嵌入式特定硬件。这实际上是非常强大的,因为它允许 luminance 对其运行的执行平台完全无感知:少一个问题。然而,有一个重要的问题需要注意:单个后端类型可以与多个窗口库/实现一起使用。这允许重复使用后端与多个窗口实现。后端通常将说明创建它的条件(例如,在 OpenGL 中,窗口库必须在创建 OpenGL 上下文时设置一些特定的标志)。

luminance 不提供创建窗口的方式,因为它不依赖于窗口库——这样最终用户就可以使用他们喜欢的任何库。此外,这类库通常实现窗口和事件功能,但这些与我们最初的目标无关。

支持亮度功能的平台crate通常通过重新导出符号(类型、函数等)从窗口crate以及使它与亮度兼容所需的代码来提供本地类型。这意味着提供了一种访问后端类型的方法,该类型实现了luminance::backend接口。然而,平台crate不应取代底层平台系统;你通常仍然需要依赖它。

luminance-derive

如果你正在针对"derive"功能编译,你将自动获得对luminance-derive的访问权限,它提供了一套过程宏

顶点

Vertex derive过程宏。

该过程宏允许你轻松创建自定义顶点类型,而无需担心实现所需的特性。

如果你想要将类型用作顶点(由Tess消费),则必须实现Vertex特质。你可以自行决定实现它,或者让这个crate为你完成这项工作。

重要:由于顶点格式包含有关将发送到后端的数据结构的信息的特性,且不良实现可能导致未定义的行为,所以Vertex特质是unsafe的,这意味着所有实现者都必须是安全的。

如果你的类型满足以下条件,则可以派生Vertex特质

  • 它必须是一个具有命名字段的struct。这只是暂时性的限制,一旦crate足够稳定,就会取消。
  • 其字段必须具有实现VertexAttrib的类型。这是强制性的,以便后端了解结构体中使用的类型,以便正确对齐内存、选择正确的类型等。
  • 其字段必须具有实现HasSemantics的类型。这个特质只是一个类型族,它将单个常量(即语义)与顶点属性关联起来。
  • 每个字段的类型必须不同。

一旦满足所有这些要求,就可以轻松派生Vertex特质。

注意:也可以查看Semantics过程宏,它提供了一种生成语义类型的方法,以便完全为你的选择实现Semantics,同时生成定义你的顶点类型时可以使用的行为类型。

语法如下

use luminance::{Vertex, Semantics};

// visit the Semantics proc-macro documentation for further details
#[derive(Clone, Copy, Debug, PartialEq, Semantics)]
pub enum Semantics {
  #[sem(name = "position", repr = "[f32; 3]", wrapper = "VertexPosition")]
  Position,
  #[sem(name = "color", repr = "[f32; 4]", wrapper = "VertexColor")]
  Color,
}

#[derive(Clone, Copy, Debug, PartialEq, Vertex)]
#[vertex(sem = "Semantics")] // specify the semantics to use for this type
struct MyVertex {
  position: VertexPosition,
  color: VertexColor,
}

注意:由于HasSemantics特质的实现,Semantics枚举必须是公共的。

除了与Semantics相关的代码,这还将

  • 创建一个名为MyVertex的类型,一个将包含单个顶点的结构。
  • 实现Vertex for MyVertex

该proc-macro还支持一个可选的#[vertex(instanced = "<bool>")]结构属性。该属性允许您指定字段是否要进行实例化。更多详情,请参阅VertexInstancing

语义

Semantics derive proc-macro。

UniformInterface

UniformInterface derive proc-macro。

该过程宏非常简单易用。您就像通常声明结构一样声明一个结构

use luminance::{shader::{types::Vec4, Uniform}, UniformInterface};

#[derive(Debug, UniformInterface)]
struct MyIface {
  time: Uniform<f32>,
  resolution: Uniform<Vec4<f32>>,
}

此声明的效果是声明了MyIface结构,并附带了一个有效的UniformInterface实现,该实现将尝试获取相应着色器程序中的"time""resolution"统一变量。如果其中任何一个统一变量无法映射(例如,非活动统一变量),则整个结构无法生成,并引发错误(有关详细信息,请参阅UniformInterface::uniform_interface的文档)。

如果您在着色器中没有使用参数,那么当该参数无法映射时,您可能不希望整个接口构建失败。您可以通过#[unbound]字段属性来实现这一点

#[derive(Debug, UniformInterface)]
struct MyIface {
  #[uniform(unbound)]
  time: Uniform<f32>, // if this field cannot be mapped, it’ll be ignored
  resolution: Uniform<Vec4<f32>>,
}

您还可以使用#[uniform(name = "string_mapping")]属性来更改默认映射。这更改了必须从着色器程序中查询以完成映射的名称

#[derive(Debug, UniformInterface)]
struct MyIface {
  time: Uniform<f32>,
  #[uniform(name = "res")]
  resolution: Uniform<Vec4<f32>>, // maps "res" from the shader program
}

最后,如果您想更改映射并有一个无法映射的无界统一变量,您可以将这两个属性混合在一起

#[derive(Debug, UniformInterface)]
struct MyIface {
  time: Uniform<f32>,
  #[uniform(name = "res", unbound)]
  resolution: Uniform<Vec4<f32>>,
}

依赖关系