#webgl #webgl2 #graphics #renderer #layer #interface #gpu

limelight

专注于使高性能图形代码更容易编写和维护的WebGL2包装器

4个版本

0.1.3 2021年12月15日
0.1.2 2021年12月12日
0.1.1 2021年12月11日
0.1.0 2021年11月26日

#220 in 图形API


3 crate 中使用

MIT 许可证

61KB
1.5K SLoC

limelight

GitHub Repo stars crates.io docs.rs Rust

此crate在WebGL之上提供两层抽象。第一层是 阴影GPU 层,它抽象了WebGL的状态性,以提供一个更面向功能和数据的接口。第二层是 Renderer API,它在上面的(未类型化的)阴影GPU之上提供类型化接口。

如果您不想使用自定义着色器,只想绘制很多形状,可以忽略下面的示例,查看 primitives crate 中的示例。

入门指南

查看 examples 目录中的可运行示例。

本教程假定您熟悉基本的WebGL术语,如顶点和片段着色器、统一变量和缓冲区。

绘制三角形

(完整代码, 演示)

A colorful triangle

此示例演示了使用limelight生成图像的三个主要步骤

  1. 创建一个 Program 对象。limelight中的 Program 包含顶点和片段着色器对(一个 WebGLProgram 对象),还包含程序特定的状态。
  2. 创建一个 Renderer。在我们初始化了所有程序并使用GL上下文后,我们将GL上下文的拥有权转移到 Renderer,然后它将负责所有GL端状态转换。
  3. 我们调用 renderer.render(program, buffer),这将导致绘制三角形。在这个示例中,我们没有附加顶点属性缓冲区,而是使用顶点着色器生成顶点。我们仍然需要告诉WebGL我们想要生成多少个顶点(3个),因此我们传递了一个大小为 3DummyBuffer
use web_sys::WebGl2RenderingContext;
use limelight::{Program, Renderer, DummyBuffer, DrawMode};

fn render_triangle(gl: WebGl2RenderingContext) {
  // limelight doesn't touch the DOM at all. Use your preferred
  // framework to create a canvas and create a WebGL2 context
  // from it.

  // Create a shader program by passing in GLSL code as strings for
  // the fragment and vertex shaders.
  let mut program = Program::new(
      include_str!("../../examples/01-triangle/shaders/shader.vert"),
      include_str!("../../examples/01-triangle/shaders/shader.frag"),
      DrawMode::Triangles
  );

  // Create a renderer. The renderer becomes the owner of the
  // WebGl2RenderingContext, to ensure that its internal representation
  // of the GPU state is always accureate.
  let mut renderer = Renderer::new(gl);

  // Run the program, rendering the results to the screen. We are
  // not passing any vertex attribute data, so we use a `DummyBuffer`
  // which renders three vertices: one for each corner of a triangle.
  renderer.render(&mut program, &DummyBuffer::new(3)).unwrap();
}

使用缓冲区

完整代码演示

Two small triangles

缓冲区允许将任意顶点属性数据传递到着色器中。Limelight提供了一个过程宏(attribute),用于从Rust端struct映射到GPU端的顶点属性集。要使用此宏,您的crate还必须依赖bytemuck及其derive功能。

use web_sys::WebGl2RenderingContext;
use limelight::{Program, Renderer, Buffer, DrawMode, BufferUsageHint, attribute};

// This attribute macro derives a number of traits, including `VertexAttribute`, which
// is required for a type to be used in an `Buffer`.
#[attribute]
struct VertexDescription {
    position: [f32; 2], // field names are mapped to variables in the shader.
}

impl VertexDescription {
    pub fn new(x: f32, y: f32) -> Self {
        VertexDescription { position: [x, y] }
    }
}

fn render_triangles(gl: WebGl2RenderingContext) {
  let mut program = Program::new(
      include_str!("../../examples/02-buffer/shaders/shader.vert"),
      include_str!("../../examples/02-buffer/shaders/shader.frag"),
      DrawMode::Triangles
  );

  let mut renderer = Renderer::new(gl);

  let data = vec![
      // Lower-left triangle.
      VertexDescription::new(-0.1, -0.1),
      VertexDescription::new(-0.5, -0.1),
      VertexDescription::new(-0.5, -0.5),
      // Upper-right triangle.
      VertexDescription::new(0.1, 0.1),
      VertexDescription::new(0.5, 0.1),
      VertexDescription::new(0.5, 0.5),
  ];

  // Declare a buffer.
  let mut buffer: Buffer<VertexDescription> =
    Buffer::new(data, BufferUsageHint::StaticDraw);

  renderer.render(&mut program, &buffer).unwrap();
}

统一变量

完整代码演示

A scaled and rotated triangle

统一变量可以在着色器和片段程序中使用。它们可以在render调用之间变化,但对于给定的渲染调用,每个统一变量在所有顶点和片段中都有一个常量值。

use limelight::{DrawMode, DummyBuffer, Program, Renderer, Uniform};
use web_sys::WebGl2RenderingContext;

fn render_triangles_with_uniform(gl: WebGl2RenderingContext) {
    // This will correspond to "uniform float u_rotate" in the vertex shader.
    let rotate_uniform = Uniform::new(std::f32::consts::PI / 3.4);
    
    // This will correspond to "uniform vec2 u_scale" in the vertex shader.
    let scale_uniform = Uniform::new([0.5, 0.8]);

    // This will correspond to "uniform vec3 u_color" in the fragment shader.
    let color_uniform = Uniform::new([0.9, 0.2, 0.3]);

    let mut program = Program::new(
      include_str!("../../examples/03-uniform/shaders/shader.vert"),
      include_str!("../../examples/03-uniform/shaders/shader.frag"),
      DrawMode::Triangles,
    )
    // We need to map the uniforms when we create the program.
    // The GPU-side types are automatically inferred from the Rust types.
    .with_uniform("u_rotate", rotate_uniform)
    .with_uniform("u_scale", scale_uniform)
    .with_uniform("u_color", color_uniform);

    let mut renderer = Renderer::new(gl);
    renderer.render(&mut program, &DummyBuffer::new(3)).unwrap();
}

动画

完整代码演示

前面的示例已经渲染了静态图像,因此我们没有必要将设置初始数据结构的代码与更新GPU端数据并触发动画的代码分开。在这个示例中,我们将代码分为一个只调用一次的new()方法和每个帧都会调用的render方法。

limelight不是一个框架,为了与其他框架集成,它没有对如何组织您的代码有偏见。这个示例展示了您可以选择的一种为简单动画组织代码的方式(请参阅完整代码以了解如何与Yew网页框架集成)。

buffer.set_datauniform.set_data惰性的:它们不会在缓冲区在渲染调用中使用之前引起任何GPU活动。(见WebGL Insights第14.2节,延迟到绘制周期。)如果缓冲区或统一变量在渲染调用之间没有改变,则不会重新写入GPU。

use limelight::{Buffer, BufferUsageHint, DrawMode, Program, Renderer, Uniform, attribute};
use web_sys::WebGl2RenderingContext;

struct Animation {
    program: Program<VertexDescription, ()>,
    buffer: Buffer<VertexDescription>,
    uniform: Uniform<[f32; 3]>,
}

impl Animation {
    pub fn new(gl: &WebGl2RenderingContext) -> Self {
        let buffer = Buffer::new(vec![], BufferUsageHint::DynamicDraw);
        let uniform = Uniform::new([0., 0., 0.]);

        let program = Program::new(
            include_str!("../../examples/04-animate/shaders/shader.vert"),
            include_str!("../../examples/04-animate/shaders/shader.frag"),
            DrawMode::Triangles,
        )
        // Note that we clone uniform, so that we can retain a handle to it.
        // Cloning a `Uniform` results in a reference-counted pointer to the
        // same uniform.
        .with_uniform("u_color", uniform.clone());       
        
        Animation {
            buffer,
            program,
            uniform
        }
    }

    pub fn render(&mut self, time: f64, renderer: &mut Renderer) {
        let theta1 = time as f32 / 1000.;
        let theta2 = theta1 + (std::f32::consts::TAU / 3.);
        let theta3 = theta2 + (std::f32::consts::TAU / 3.);
        
        self.buffer.set_data(vec![
            VertexDescription::new(theta1.cos(), theta1.sin()),
            VertexDescription::new(theta2.cos(), theta2.sin()),
            VertexDescription::new(theta3.cos(), theta3.sin()),
        ]);

        let r = (time as f32 / 3000.).sin() / 2. + 0.5;
        let g = (time as f32 / 5000.).sin() / 2. + 0.5;
        let b = (time as f32 / 7000.).sin() / 2. + 0.5;

        self.uniform.set_value([r, g, b]);

        renderer.render(&mut self.program, &self.buffer).unwrap();
    }
}

#[attribute]
struct VertexDescription {
    position: [f32; 2],
}

impl VertexDescription {
    pub fn new(x: f32, y: f32) -> Self {
        VertexDescription { position: [x, y] }
    }
}

依赖项

~7–9MB
~175K SLoC