Найти тему
Один Rust не п...Rust

Rust и графика

Оглавление

Для чего нужна данная статья? :
Создать
мультиплатформенное графическое приложение с:

  1. 3D-сценой на движке Bevy, Fyrox или Godot Rust.
  2. Физикой с Rapier и Legion ECS.
  3. Низкоуровневым рендерингом через wgpu, Vulkano, OpenGL.
  4. GUI-интерфейсом (egui, iced, gtk-rs, slint).
  5. Веб-версией (WebGPU, WebGL, Canvas API).
  6. Обработкой изображений (image, resvg).
  7. Вычислениями на GPU (CUDA, OpenCL, wgpu-compute).

Найти альтернативы для работы с графикой:
Игровые движки (Bevy, Fyrox, Godot Rust)
Низкоуровневые графические API (wgpu, Vulkano, OpenGL)
2D/3D-визуализация (wgpu, raqote, Skia, Cairo)
GUI (egui, iced, slint, gtk-rs)
Веб-графика (WebGPU, WebGL, Canvas API)
Физика и игровые ECS (Rapier, Legion)
Обработка изображений (image, resvg)
GPGPU-вычисления (CUDA, OpenCL, wgpu-compute)

Зачем Вам это уметь? :

1. 2D-графика

Библиотеки для рендеринга 2D-графики:

  • wgpu – безопасная и кроссплатформенная графическая абстракция на основе Vulkan, Metal, DX12 и OpenGL.
  • vulkano – безопасная обертка над Vulkan.
  • glow – безопасная обертка над OpenGL.
  • minifb – минималистичная библиотека для создания окон и рендеринга пикселей.
  • pixels – простой буфер пикселей поверх wgpu.
  • softbuffer – библиотека для прямого управления буфером кадра без GPU.

Библиотеки для 2D-отрисовки и векторной графики:

  • image – работа с растровыми изображениями.
  • resvg – отрисовка SVG.
  • raqote – рендеринг векторной графики в стиле Cairo.
  • skia-safe – биндинги для Google Skia.
  • cairo-rs – биндинги для Cairo.

2. 3D-графика

Графические движки:

  • Bevy – современный ECS-движок с поддержкой wgpu.
  • Fyrox – удобный 3D-движок с GUI-редактором.
  • Godot Rust – Rust-биндинги для Godot.
  • Macroquad – удобный движок без зависимостей.
  • Tetra – 2D-игровой движок с OpenGL.

Прямой доступ к графическим API:

  • wgpu – поддерживает Vulkan, DX12, Metal.
  • vulkano – безопасная работа с Vulkan.
  • ash – низкоуровневые биндинги к Vulkan.
  • glium – удобный слой над OpenGL.
  • glutin – создание окон с OpenGL.

3. GUI (Графический интерфейс пользователя)

Фреймворки на Rust:

  • egui – быстрый и удобный GUI на wgpu.
  • druid – декларативный GUI с поддержкой piet.
  • iced – вдохновлен Flutter.
  • slint – декларативный GUI в стиле Qt/QML.
  • orbtk – для Redox OS и не только.
  • fltk-rs – биндинги для FLTK.

Биндинги для классических GUI-фреймворков:

4. Графика для научных расчетов и визуализации данных

  • plotters – графики и диаграммы.
  • plotlib – аналог Matplotlib.
  • Vello – для сложной графики и анимации.

5. Web-графика (WebAssembly + Rust)

  • WebGPU (wgpu-rs) – WebGPU в браузере.
  • Canvas API – рендеринг в <canvas> через wasm-bindgen.
  • WebGL (gl-rs) – биндинги к WebGL.
  • Three-d – 3D-рендеринг на WebAssembly.

6. GameDev и физика

  • Rapier – физический движок 2D/3D.
  • NPhysics – физический движок.
  • Legion – ECS-фреймворк для игр.
  • hecs – еще один ECS-фреймворк.

7. Инструменты для работы с шейдерами

  • naga – транслятор шейдеров.
  • spirv-cross – работа с SPIR-V.
  • rust-gpu – написание шейдеров на Rust.

8. Графика с использованием GPU (GPGPU)

  • cuda-rs – работа с CUDA.
  • wgpu-compute – вычисления на GPU через WebGPU.
  • ocl – OpenCL в Rust.
  • rust-gpu – Rust для GPU.

Давайте создадим простой пример, который рисует прямоугольник и круг на экране.

Перед тем как начать, убедитесь, что у вас установлен Rust и Cargo. Также вам нужно добавить Speedy2D в зависимости вашего проекта, отредактировав файл Cargo.toml:

[dependencies]
speedy2d = "1.0"

После настройки зависимостей, вы можете создать файл main.rs с следующим содержанием:

use speedy2d::color::Color;
use speedy2d::window::{WindowHandler, WindowHelper, WindowStartupInfo};
use speedy2d::{Graphics2D, Window};
struct MyWindowHandler;
impl WindowHandler for MyWindowHandler {
fn on_draw(&mut self, helper: &mut WindowHelper, graphics: &mut Graphics2D) {
// Очищаем экран, используя черный цвет
graphics.clear_screen(Color::from_rgb(0.0, 0.0, 0.0));
// Рисуем прямоугольник красного цвета
graphics.draw_rectangle(
(100.0, 100.0), // верхний левый угол
(200.0, 200.0), // нижний правый угол
Color::from_rgb(1.0, 0.0, 0.0), // цвет
);
// Рисуем круг зеленого цвета
graphics.draw_circle(
(300.0, 300.0), // центр
50.0, // радиус
Color::from_rgb(0.0, 1.0, 0.0), // цвет
);
// Запланируйте следующую перерисовку
helper.request_redraw();
}
}
fn main() {
let window = Window::new_centered("Speedy2D: Simple 2D Graphics", (800, 600)).unwrap();
window.run_loop(MyWindowHandler);
}
Этот пример создает окно с размером 800x600 пикселей и рисует в нем красный прямоугольник и зеленый круг. Метод on_draw вызывается каждый раз, когда требуется перерисовка окна, и здесь мы реализуем нашу логику отрисовки.

Для запуска этого примера, сохраните файл и выполните команду cargo run в терминале из корневой директории вашего проекта. Cargo скомпилирует и запустит ваше приложение, отобразив окно с прямоугольником и кругом.

Давайте адаптируем предоставленный вами код для создания простого графика в Speedy2D. Мы сделаем линейный график, который может быть использован, например, для визуализации температуры за неделю. Для этого мы добавим в код рисование линий, соединяющих точки на графике, где каждая точка представляет значение температуры в определенный день недели.

Мы модифицируем метод on_draw для рисования линейного графика:

use speedy2d::color::Color;
use speedy2d::shape::Rectangle;
use speedy2d::window::{WindowHandler, WindowHelper, WindowStartupInfo};
use speedy2d::{Graphics2D, Window};
struct MyWindowHandler {
// Данные для графика
data_points: Vec<(f32, f32)>, // Вектор с координатами точек (X, Y)
}
impl WindowHandler for MyWindowHandler {
fn on_draw(&mut self, helper: &mut WindowHelper, graphics: &mut Graphics2D) {
// Очищаем экран, используя черный цвет
graphics.clear_screen(Color::from_rgb(0.0, 0.0, 0.0));
// Рисуем оси графика
let axis_color = Color::from_rgb(1.0, 1.0, 1.0); // Белый цвет для осей
// Ось X
graphics.draw_line((50.0, 550.0), (750.0, 550.0), 2.0, axis_color);
// Ось Y
graphics.draw_line((50.0, 50.0), (50.0, 550.0), 2.0, axis_color);
// Рисуем линейный график по данным
let data_color = Color::from_rgb(0.0, 1.0, 0.0); // Зеленый цвет для данных
for i in 0..self.data_points.len() - 1 {
graphics.draw_line(
self.data_points[i],
self.data_points[i + 1],
2.0,
data_color,
);
}
// Запланируйте следующую перерисовку
helper.request_redraw();
}
}
fn main() {
let window = Window::new_centered("Speedy2D: Simple Data Visualization", (800, 600)).unwrap();
// Создаем вектор с данными для графика (примерные координаты)
let data_points = vec![
(100.0, 500.0), // День 1
(200.0, 300.0), // День 2
(300.0, 450.0), // День 3
(400.0, 350.0), // День 4
(500.0, 400.0), // День 5
(600.0, 250.0), // День 6
(700.0, 300.0), // День 7
];
window.run_loop(MyWindowHandler { data_points });
}
В этом примере мы добавили вектор data_points в структуру MyWindowHandler, который содержит координаты точек на графике. В методе on_draw мы сначала рисуем оси графика, а затем соединяем точки линиями, чтобы получить линейный график.

Давайте создадим простое GUI-приложение с использованием Speedy2D, где пользователь может нажимать на кнопки, изменяя цвет фона окна. Это базовый пример, иллюстрирующий, как можно реализовать взаимодействие с пользователем и обновлять графический интерфейс в ответ на его действия.

use speedy2d::color::Color;
use speedy2d::window::{MouseButton, WindowHandler, WindowHelper, WindowStartupInfo};
use speedy2d::{Graphics2D, Window};
struct MyWindowHandler {
background_color: Color,
}
impl WindowHandler for MyWindowHandler {
fn on_draw(&mut self, helper: &mut WindowHelper, graphics: &mut Graphics2D) {
// Очищаем экран, используя текущий цвет фона
graphics.clear_screen(self.background_color);
// Рисуем кнопки
// Кнопка для смены фона на красный
graphics.draw_rectangle(
(50.0, 50.0),
(150.0, 100.0),
Color::from_rgb(1.0, 0.0, 0.0),
);
// Кнопка для смены фона на зеленый
graphics.draw_rectangle(
(200.0, 50.0),
(300.0, 100.0),
Color::from_rgb(0.0, 1.0, 0.0),
);
// Запланируйте следующую перерисовку
helper.request_redraw();
}
fn on_mouse_button_down(&mut self, helper: &mut WindowHelper, button: MouseButton, x: f32, y: f32) {
if button == MouseButton::Left {
// Проверяем, была ли нажата красная кнопка
if x >= 50.0 && x <= 150.0 && y >= 50.0 && y <= 100.0 {
self.background_color = Color::from_rgb(1.0, 0.0, 0.0);
}
// Проверяем, была ли нажата зеленая кнопка
if x >= 200.0 && x <= 300.0 && y >= 50.0 && y <= 100.0 {
self.background_color = Color::from_rgb(0.0, 1.0, 0.0);
}
}
}
}
fn main() {
let window = Window::new_centered("Speedy2D: Simple GUI Example", (800, 600)).unwrap();
window.run_loop(MyWindowHandler {
background_color: Color::from_rgb(0.0, 0.0, 0.0), // Начальный цвет фона
});
}

Создадим код проекта который объединяет:

  • Bevy для сцены.
  • Rapier для физики.
  • wgpu для сложного рендеринга.
  • egui для GUI.
  • CUDA и OpenCL для вычислений.
  • WebAssembly для WebGL-версии.

Сцена рендерит 3D-объекты, использует SVG-текстуры, поддерживает вычисления на GPU и имеет GUI-кнопку для их запуска.

Главный файл: main.rs

use bevy::prelude::*;

use bevy_rapier3d::prelude::*;

use slint::SharedString;

use wgpu::util::DeviceExt;

use resvg::{usvg, tiny_skia};

use std::fs;

fn main() {

App::new()

.insert_resource(WindowDescriptor {

title: "Rust Graphics Engine".to_string(),

width: 1280.,

height: 720.,

..Default::default()

})

.add_plugins(DefaultPlugins)

.add_plugin(RapierPhysicsPlugin::<NoUserData>::default())

.add_startup_system(setup)

.add_system(update)

.run();

}

fn setup(mut commands: Commands) {

commands.spawn(Camera3dBundle {

transform: Transform::from_xyz(0.0, 5.0, 10.0)

.looking_at(Vec3::ZERO, Vec3::Y),

..Default::default()

});

let svg_data = fs::read("assets/image.svg").unwrap();

let tree = usvg::Tree::from_data(&svg_data, &usvg::Options::default()).unwrap();

let mut pixmap = tiny_skia::Pixmap::new(512, 512).unwrap();

resvg::render(&tree, tiny_skia::Transform::default(), &mut pixmap.as_mut());

println!("SVG rendered to pixmap!");

commands.spawn((

RigidBody::Dynamic,

Collider::cuboid(0.5, 0.5, 0.5),

));

}

fn update(time: Res<Time>, mut query: Query<&mut Transform>) {

for mut transform in query.iter_mut() {

transform.rotation *= Quat::from_rotation_y(1.5 * time.delta_seconds());

}

}

// --- wgpu-compute ---

fn run_wgpu_compute() {

let instance = wgpu::Instance::new(wgpu::Backends::all());

let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {

power_preference: wgpu::PowerPreference::HighPerformance,

compatible_surface: None,

force_fallback_adapter: false,

})).unwrap();

let (device, queue) = pollster::block_on(adapter.request_device(&Default::default(), None)).unwrap();

println!("wgpu-compute initialized!");

}

// --- GUI (slint) ---

slint::slint! {

export component MainApp inherits Window {

Text {

text: "Rust Graphics Engine";

horizontal-alignment: center;

}

Button {

text: "Run wgpu-compute";

clicked => { rust_run_wgpu_compute(); }

}

}

}

fn rust_run_wgpu_compute() {

run_wgpu_compute();

}

fn launch_gui() {

let ui = MainApp::new().unwrap();

ui.run().unwrap();

}

#[cfg(target_os = "windows")]

mod cuda {

extern crate cust;

use cust::prelude::*;

pub fn run_cuda() {

let ctx = cust::context::Context::create_and_push(

cust::context::ContextFlags::MAP_HOST, Device::get_device(0).unwrap()

).unwrap();

println!("CUDA context initialized: {:?}", ctx);

}

}