ゲームエンジンを作る
![yuma14](https://res.cloudinary.com/zenn/image/fetch/s--3L3qV3kT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/f5afc8d1a4.jpeg)
ゲームエンジンを作りたい。なのでRustでReverieEngineなどを開発している。
![yuma14](https://res.cloudinary.com/zenn/image/fetch/s--3L3qV3kT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/f5afc8d1a4.jpeg)
関連リポジトリ
ReverieEngine
ReverieEngineをWebGLに対応させたかったもの
ReverieEngineを使って作ったゲーム(?)
ReverieEngineを使って作ったゲーム
上のゲームをWebGLに対応させたかったもの
ReverieEngineのバックエンドをOpenGLからwgpuに変更するための布石として作ったリポジトリ
テトリスを作るつもりだったが、最終的にOBJファイルビューアになった
Learn WGPUを見ながら作業した
これはReverieEngineとはあまり関係がない
htmlのcanvasをバックエンドとして動作する独自のエンジンを実装している
これはECS (Entity Component System)のライブラリである
条件によってはBevy ECSよりも速い場合がある
![yuma14](https://res.cloudinary.com/zenn/image/fetch/s--3L3qV3kT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/f5afc8d1a4.jpeg)
ゲームエンジンの構成要素
ゲームエンジンは様々な構成要素でできている
Are we game yet?はゲーム開発に役立つクレートをまとめたサイトである。
いろいろなカテゴリがある。クレートの他にもRust製のゲーム、本、チュートリアルなどが紹介されている。
![yuma14](https://res.cloudinary.com/zenn/image/fetch/s--3L3qV3kT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/f5afc8d1a4.jpeg)
既存のソフトウェア
既存のソフトウェアを知ることは重要である。主に、APIの仕様や内部実装、提供する機能などを参考にしたい。
![yuma14](https://res.cloudinary.com/zenn/image/fetch/s--3L3qV3kT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/f5afc8d1a4.jpeg)
Bevy
BevyはRust製のゲームエンジン。ECSをベースにデザインされている。ゲームエンジンのユーザーはRustで自分のゲームを作る(このようなことをわざわざ書いているのは、ゲームエンジンの開発言語とゲームの開発言語が別である場合もあるからである。例えばGodotはC++で書かれているがユーザーはGDScript等でゲームを作る)。
Bevyについての情報。内部の実装についても書いてある。
Bevyの構成要素としてBevy ECSがある。Xanaduを開発するときに参考にした。
![yuma14](https://res.cloudinary.com/zenn/image/fetch/s--3L3qV3kT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/f5afc8d1a4.jpeg)
Lyon - 2D graphics rendering on the GPU in rust using path tessellation.
Rustで書かれたtessellatorである。雑に言うと図形や曲線をポリゴンに変換するものである。Servoにも使われているらしい。
READMEのFAQがわかりやすかったので簡単に翻訳して引用する。
- Tessellatorとは?
- 複雑な図形を入力すると三角形でできた形状を出力します。出力されたものはOpenGL、Vulkan、D3DなどのAPIで使用できます。
- LyonでSVGファイルを描画するには?
- LyonはSVGレンダラーではありません。Lyonは複雑なパスによる塗りつぶしや曲線をテッセレートするためのものです。gfx-rs, glium, OpenGL, D3DなどのGPU APIと一緒に使うと便利です。テッセレート結果をどのように描画するかはlyonのユーザー次第です。
- tessellatorの出力をどうやって描画しますか?
- tessellatorの出力フォーマットはカスタマイズ可能ですが、vertex bufferとindex bufferが出力されます。
- アンチエイリアシングはサポートされていますか?
- ビルトインのサポートはありません。しかし、ビデオゲームでよく使われるテクニックを使うことで、lyonのユーザーが自分で実現することができます(msaa, taa, fxaaなど)。
所感: Rustで書かれたレンダリングエンジンはOpenGLなどのバックエンドと密結合になっていたり、独自にウィンドウやイベントループなどを用意するものが大半だった。それに対してLyonはPathを入力すると三角形のリストを出力してくれるというシンプルな仕組みであり、自分のエンジンに組み込みやすいと感じた。
![yuma14](https://res.cloudinary.com/zenn/image/fetch/s--3L3qV3kT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/f5afc8d1a4.jpeg)
Cyllista Game Engine
Cygamesのゲームエンジンである。スマホゲームではなくコンシューマゲームの開発に使われているらしい。エンジン本体はC++とPythonで書かれている。
開発において以下のような工夫がなされている。
- テスト駆動開発
- コードが安定する
- エンジンのユーザー視点でAPIを吟味できる
- CUIのビルドツールcybuild
- 自動化しやすい
- Runtime Compiled C++
- ホットリロードができる
以下のような機能を持っている。
- アセットの変換
- レベルエディタ
- アセットの管理・配信
![yuma14](https://res.cloudinary.com/zenn/image/fetch/s--3L3qV3kT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/f5afc8d1a4.jpeg)
piston2d-graphics
Rust製のゲームエンジンPistonの一部である。2Dのレンダリングを行う。
注目すべき点は、OpenGL、gfx、gliumの3つのバックエンドに対応している点である。さらに、他のバックエンドに対応させることもできる。
Graphicsトレイトによって複数のバックエンドに対応している。自分でGraphicsトレイトを実装することで好きなバックエンドに対応させることもできる。
簡略化した定義は以下の通りである。
pub trait Graphics: Sized {
type Texture: ImageSize;
バックエンドによってテクスチャが異なるので関連型になっている。これは当然。
レンダリングにおけるテクスチャの扱いがよくわかっていないので後で調べたい。
ImageSizeはサイズを返すトレイト
fn clear_color(&mut self, color: Color);
指定した色でバッファをクリアする。背景色ということ。
fn clear_stencil(&mut self, value: u8);
stencilバッファをクリアする。stencilバッファは比較的具体的な概念だと思っていたので、複数のバックエンドを抽象化したGraphicsトレイトの定義に出てくるのは意外である。
// すべて同じ色の頂点。fはf(vertices)
fn tri_list<F>(&mut self, draw_state: &DrawState, color: &[f32; 4], f: F)
where F: FnMut(&mut dyn FnMut(&[[f32; 2]]))
// 色付きの頂点。fはf(vertices, colors)
fn tri_list_c<F>(&mut self, draw_state: &DrawState, f: F)
where F: FnMut(&mut dyn FnMut(&[[f32; 2]], &[[f32; 4]]))
// UV付きの頂点。fはf(vertices, texture_coords)
fn tri_list_uv<F>(&mut self, draw_state: &DrawState, color: &[f32; 4], texture: &Self::Texture, f: F)
where F: FnMut(&mut dyn FnMut(&[[f32; 2]], &[[f32; 2]]))
// UVと色付きの頂点。fはf(vertices, texture_coords, colors)
fn tri_list_uv_c<F>(&mut self, draw_state: &DrawState, texture: &Self::Texture, f: F)
where F: FnMut(&mut dyn FnMut(&[[f32; 2]], &[[f32; 2]], &[[f32; 4]]))
三角形の列を描画する。fはクロージャを受け取る関数である。tri_list
の実装の中でf(頂点を受け取って描画するクロージャ)
のようにして呼び出してあげる。
頂点の型に応じてメソッドが4種類ある。頂点に記録されうる情報には座標の他に色とUVがある。
しかしこれではバックエンド側で都度シェーダーを切り替える必要があるのでは? それからfinish_drawing
みたいなメソッドが無いのが気になる。実際に描画するタイミングはいつなんだろう。
- draw_stateが変わったとき
- バッファがいっぱいになったとき
- fに渡したクロージャの最後
などのタイミングで描画を行っていた(gl::DrawArrays
を呼び出していた)。つまりtri_list_**
の呼び出しごとに実際に描画が行われるということである。したがって、呼び出す側はできるだけまとめてtri_list
を呼び出すようにすべき、ということになる。
もう1つの疑問点として、クロージャやdynをたくさん使っていてパフォーマンス上の問題がないのかという点である。Pistonはいろいろなゲームで使われているので実際に問題はないのだと思うが。
DrawStateは描画時にクリッピングする領域(optional)、使用するstencil(optional)、ブレンド関数(optional)の組である。
fn rectangle(&mut self, rectangle)
fn polygon(&mut self, polygon)
ここからはデフォルト実装のあるメソッドである。パフォーマンスを向上させるためにデフォルト実装以外の実装を提供することもできる。面倒なので引数の型は適当に書いている。また、一部のみ書いている。
fn image(&mut self, image: &Image, texture: &Self::Texture)
画像を描画する。これのデフォルト実装を提供できるのは少し不思議な感じがする。実装を見たところ、内部的にrectangleを作ってそれをテクスチャとともに描画していた。考えてみれば当然である。
fn ellipse(&mut self, ellipse)
曲線を描画するにはどうするかというと、tesselationを行う。このクレートではtriangulationと呼んでいるようである。
triangulation::with_ellipse_tri_list()
Graphicsトレイトの設計は非常に参考になる。つまり、いろいろなバックエンドに共通するインターフェイスが示されているわけである。上手な抽象化だと思った。
![yuma14](https://res.cloudinary.com/zenn/image/fetch/s--3L3qV3kT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/f5afc8d1a4.jpeg)
piston_window
piston_windowはPistonの一部で、ウィンドウの作成やイベントループの処理などを行う。
バックエンドとしてglutin、winit、SDL2、GLFWを使うことができる。どのようにして対応しているのか興味があるのであとで実装を見てみたい。→Windowトレイトを実装すれば良いらしい
以下は公式サイトに載っているサンプルコード
extern crate piston_window;
use piston_window::*;
fn main() {
let mut window: PistonWindow =
WindowSettings::new("Hello Piston!", [640, 480])
.exit_on_esc(true).build().unwrap();
while let Some(e) = window.next() {
window.draw_2d(&e, |c, g, _device| {
clear([1.0; 4], g);
rectangle([1.0, 0.0, 0.0, 1.0], // red
[0.0, 0.0, 100.0, 100.0],
c.transform, g);
});
}
}
windowがイテレータになっていて、next()でイベントを取り出せるようである。
![yuma14](https://res.cloudinary.com/zenn/image/fetch/s--3L3qV3kT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/f5afc8d1a4.jpeg)
Piston
PistonはRust製のゲームエンジンである。デフォルトで提供している機能が四角形の描画、楕円の描画など素朴であるため、Pistonを使ったゲームも素朴な見た目のものが多い印象がある。
Pistonは複数の(かなりの数の)クレートから構成されている。ReverieEngineも複数クレートに分割する予定なので参考になると思う。
プロジェクト内に複数のクレートがある場合ワークスペースを使うこともできるが、Pistonでは1リポジトリ1クレートという構成になっている。
- examples/tutorials/getting started
- piston-examples
- piston-tutorials
- piston_window - A window wrapper for convenience
- demo
- skeletal_animation_demo - 3Dのスケルトンアニメーションの例
- piston-music - 音楽に関するライブラリ。Pistonのcurrentというライブラリを使用している
- hematite - Minecraft風ゲーム
- The Piston core - 翻訳: これらのライブラリは入力、ウィンドウ、イベントループをモデル化します。プラットフォーム固有のAPIには依存しません。(プラットフォーム側の?)破壊的変更の影響を減らすためモジュール化された設計になっています。複数のプロジェクト間で使える一般的なライブラリを書き、エコシステムの90%を再利用可能かつプラットフォーム/API非依存にすることが目的です。
- pistoncore-input
- pistoncore-window
- pistoncore-event_loop
- piston - コアライブラリをre-exportする
- Utility libraries - 翻訳: これらのライブラリは他のライブラリとのインテグレーションをサポートしています。これらは小規模でシンプルです。ユーザが高レベルのライブラリを自由に選択できるようにし、一方でPiston内部でのインテグレーションを維持することが目的です。
- vecmath - A simple and type agnostic Rust library for vector math designed for reexporting
- quaternion - A simple and type agnostic Rust library for quaternion math designed for reexporting
- dual_quaternion - A simple and type agnostic Rust library for dual-quaternion math designed for reexporting
- piston3d-cam - A Rust library for 3D camera and navigation
- interpolation - A library for interpolation
- shader_version - A helper library for detecting and picking compatible shaders
- piston-viewport - A library for storing viewport information
- piston-float - Traits for generic floats in game development ← コンセプトとしてはnum-traitsに似ている。型をfloat、トレイトをゲームに必要なもののみに絞っている点が異なる。
- piston-rect - Helper methods for computing simple rectangle layout
- piston-texture - A library for texture conventions
- current - A library for setting current values for stack scope, such as application structure
- table - Dynamical typed Lua-like table structure
- texture_packer - Pack small images together into larger ones
- select_color - Color selection
- read_color - Read hex colors
- read_token - Read tokens using look-ahead
- range - Range addressing
- fps_counter - FPS counter
- find_folder - Find a folder from current directory
-
array - Convenience methods for working with arrays ← Rust 1.63.0で追加された
std::array::from_fn
のような機能を提供する。最近のRustでは不要なライブラリだと思う - piston-shaders - Repository for GPU shaders
- piston-history_tree - A persistent history tree for undo/redo
- Bindings - Cライブラリのバインディング
- glfw-rs - GLFW3 bindings and idiomatic wrapper for Rust.
- freetype-rs - Safe wrappers for FreeType fonts
- freetype-sys - Low level wrappers for FreeType fonts
- physfs-rs - PhysFS bindings
- Standalone libraries
- piston_collada - Advanced standard format for 3D
- wavefront_obj - Simple 3D format
- hematite_nbt - Minecraft's Named Binary Tag (NBT) format
- Backends
piston-rect
Pistonの一部で、レイアウトのためのユーティリティである。長方形を基本として、中心を求めたり分割したりできる。
このような機能はあった方が良いし、すでにReverieEngineにも実装されている。piston-rectは機能不足であるように感じるので、自分で実装したほうがいいと思う。具体的には、長方形の中心に長方形を配置する、長方形の中にアンカー付きで長方形を配置するなどの機能がほしい。
piston-texture
Pistonの一部で、テクスチャを反転させたりフィルタをかけたりできる。テクスチャの型としては単なるfloatのスライスを使っている。
テクスチャを読み込んだりGPUに送ったりといったことは行わない。
current
Pistonの一部で、可変なグローバル変数を作れる。ただしunsafeである。
プロトタイピング、より高レベルなライブラリの作成、デバッグなどの目的で使用することが想定されている。
table
Pistonの一部で、Lua風の動的型付けのテーブルが作れる。Lua風なのでテーブルのキーとして文字列以外も使用できる。
texture_packer
Pistonの一部で、テクスチャのパッキングを行う。特定のAPIに依存していないのでReverieEngineでも使えるかもしれない。
テクスチャの型がpiston-textureよりもリッチな感じになっていて、これも参考になるかもしれない。
piston-shaders / piston-shaders_graphics2d
Pistonの一部で、2DCG用のGLSL形式のシェーダーが置いてある。WebGL、Core Profileなどのバリエーションがある。
![yuma14](https://res.cloudinary.com/zenn/image/fetch/s--3L3qV3kT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/f5afc8d1a4.jpeg)
glyphon
テキストの描画に関するライブラリ
-
cosmic-text
でグリフのレイアウト、ラスタライズを行う -
etagere
でグリフをtexture atlasにまとめる - texture atlasから一部を取り出して
wgpu
で描画する
https://github.com/grovesNL/glyphon/blob/main/examples/hello-world.rs から引用:
use glyphon::{
Attrs, Buffer, Cache, Color, Family, FontSystem, Metrics, Resolution, Shaping, SwashCache,
TextArea, TextAtlas, TextBounds, TextRenderer, Viewport,
};
use std::sync::Arc;
use wgpu::{
CommandEncoderDescriptor, CompositeAlphaMode, DeviceDescriptor, Instance, InstanceDescriptor,
LoadOp, MultisampleState, Operations, PresentMode, RenderPassColorAttachment,
RenderPassDescriptor, RequestAdapterOptions, SurfaceConfiguration, TextureFormat,
TextureUsages, TextureViewDescriptor,
};
use winit::{
dpi::LogicalSize,
event::{Event, WindowEvent},
event_loop::EventLoop,
window::WindowBuilder,
};
fn main() {
pollster::block_on(run());
}
async fn run() {
// Set up window
let (width, height) = (800, 600);
let event_loop = EventLoop::new().unwrap();
let window = Arc::new(
WindowBuilder::new()
.with_inner_size(LogicalSize::new(width as f64, height as f64))
.with_title("glyphon hello world")
.build(&event_loop)
.unwrap(),
);
let size = window.inner_size();
let scale_factor = window.scale_factor();
// Set up surface
let instance = Instance::new(InstanceDescriptor::default());
let adapter = instance
.request_adapter(&RequestAdapterOptions::default())
.await
.unwrap();
let (device, queue) = adapter
.request_device(&DeviceDescriptor::default(), None)
.await
.unwrap();
let surface = instance
.create_surface(window.clone())
.expect("Create surface");
let swapchain_format = TextureFormat::Bgra8UnormSrgb;
let mut config = SurfaceConfiguration {
usage: TextureUsages::RENDER_ATTACHMENT,
format: swapchain_format,
width: size.width,
height: size.height,
present_mode: PresentMode::Fifo,
alpha_mode: CompositeAlphaMode::Opaque,
view_formats: vec![],
desired_maximum_frame_latency: 2,
};
surface.configure(&device, &config);
// Set up text renderer
let mut font_system = FontSystem::new();
let mut swash_cache = SwashCache::new();
let cache = Cache::new(&device);
let mut viewport = Viewport::new(&device, &cache);
let mut atlas = TextAtlas::new(&device, &queue, &cache, swapchain_format);
let mut text_renderer =
TextRenderer::new(&mut atlas, &device, MultisampleState::default(), None);
let mut buffer = Buffer::new(&mut font_system, Metrics::new(30.0, 42.0));
let physical_width = (width as f64 * scale_factor) as f32;
let physical_height = (height as f64 * scale_factor) as f32;
buffer.set_size(&mut font_system, physical_width, physical_height);
buffer.set_text(&mut font_system, "Hello world! 👋\nThis is rendered with 🦅 glyphon 🦁\nThe text below should be partially clipped.\na b c d e f g h i j k l m n o p q r s t u v w x y z", Attrs::new().family(Family::SansSerif), Shaping::Advanced);
buffer.shape_until_scroll(&mut font_system, false);
event_loop
.run(move |event, target| {
if let Event::WindowEvent {
window_id: _,
event,
} = event
{
match event {
WindowEvent::Resized(size) => {
config.width = size.width;
config.height = size.height;
surface.configure(&device, &config);
window.request_redraw();
}
WindowEvent::RedrawRequested => {
viewport.update(
&queue,
Resolution {
width: config.width,
height: config.height,
},
);
text_renderer
.prepare(
&device,
&queue,
&mut font_system,
&mut atlas,
&viewport,
[TextArea {
buffer: &buffer,
left: 10.0,
top: 10.0,
scale: 1.0,
bounds: TextBounds {
left: 0,
top: 0,
right: 600,
bottom: 160,
},
default_color: Color::rgb(255, 255, 255),
}],
&mut swash_cache,
)
.unwrap();
let frame = surface.get_current_texture().unwrap();
let view = frame.texture.create_view(&TextureViewDescriptor::default());
let mut encoder = device
.create_command_encoder(&CommandEncoderDescriptor { label: None });
{
let mut pass = encoder.begin_render_pass(&RenderPassDescriptor {
label: None,
color_attachments: &[Some(RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: Operations {
load: LoadOp::Clear(wgpu::Color::BLACK),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
text_renderer.render(&atlas, &viewport, &mut pass).unwrap();
}
queue.submit(Some(encoder.finish()));
frame.present();
atlas.trim();
}
WindowEvent::CloseRequested => target.exit(),
_ => {}
}
}
})
.unwrap();
}
![yuma14](https://res.cloudinary.com/zenn/image/fetch/s--3L3qV3kT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/f5afc8d1a4.jpeg)
cosmic-text
主にテキストのレイアウトを行うライブラリ
-
fontdb
でフォントの読み込みを行う -
rustybuzz
でフォントのshapingを行う - フォントのフォールバックをサポートしている
- フォントのレイアウトを行う
-
swash
でフォントの描画を行う
https://pop-os.github.io/cosmic-text/cosmic_text/ から引用:
use cosmic_text::{Attrs, Color, FontSystem, SwashCache, Buffer, Metrics, Shaping};
// A FontSystem provides access to detected system fonts, create one per application
let mut font_system = FontSystem::new();
// A SwashCache stores rasterized glyphs, create one per application
let mut swash_cache = SwashCache::new();
// Text metrics indicate the font size and line height of a buffer
let metrics = Metrics::new(14.0, 20.0);
// A Buffer provides shaping and layout for a UTF-8 string, create one per text widget
let mut buffer = Buffer::new(&mut font_system, metrics);
// Borrow buffer together with the font system for more convenient method calls
let mut buffer = buffer.borrow_with(&mut font_system);
// Set a size for the text buffer, in pixels
buffer.set_size(80.0, 25.0);
// Attributes indicate what font to choose
let attrs = Attrs::new();
// Add some text!
buffer.set_text("Hello, Rust! 🦀\n", attrs, Shaping::Advanced);
// Perform shaping as desired
buffer.shape_until_scroll(true);
// Inspect the output runs
for run in buffer.layout_runs() {
for glyph in run.glyphs.iter() {
println!("{:#?}", glyph);
}
}
// Create a default text color
let text_color = Color::rgb(0xFF, 0xFF, 0xFF);
// Draw the buffer (for performance, instead use SwashCache directly)
buffer.draw(&mut swash_cache, text_color, |x, y, w, h, color| {
// Fill in your code here for drawing rectangles
});
![yuma14](https://res.cloudinary.com/zenn/image/fetch/s--3L3qV3kT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/f5afc8d1a4.jpeg)
swash
text shapingとglyph renderingを行うライブラリ
ラスタライズにzeno
を使用している (optional)
![yuma14](https://res.cloudinary.com/zenn/image/fetch/s--3L3qV3kT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/f5afc8d1a4.jpeg)
zeno
zenoはラスタライズを行うライブラリ
https://crates.io/crates/zeno から引用:
use zeno::{Cap, Join, Mask, PathData, Stroke};
// Buffer to store the mask
let mut mask = [0u8; 64 * 64];
/// Create a mask builder with some path data
Mask::new("M 8,56 32,8 56,56 Z")
.style(
// Stroke style with a width of 4
Stroke::new(4.0)
// Round line joins
.join(Join::Round)
// And round line caps
.cap(Cap::Round)
// Dash pattern followed by a dash offset
.dash(&[10.0, 12.0, 0.0], 0.0),
)
// Set the target dimensions
.size(64, 64)
// Render into the target buffer
.render_into(&mut mask, None);
![yuma14](https://res.cloudinary.com/zenn/image/fetch/s--3L3qV3kT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/f5afc8d1a4.jpeg)
wgpu_text
wgpu_textはglyph-brush
のラッパー
https://crates.io/crates/wgpu_text から引用:
use wgpu_text::{glyph_brush::{Section as TextSection, Text}, BrushBuilder, TextBrush};
let brush = BrushBuilder::using_font_bytes(font).unwrap()
/* .initial_cache_size((16_384, 16_384))) */ // use this to avoid resizing cache texture
.build(&device, config.width, config.height, config.format);
// Directly implemented from glyph_brush.
let section = TextSection::default().add_text(Text::new("Hello World"));
// on window resize:
brush.resize_view(config.width as f32, config.height as f32, &queue);
// window event loop:
winit::event::Event::RedrawRequested(_) => {
// Before are created Encoder and frame TextureView.
// Crashes if inner cache exceeds limits.
brush.queue(&device, &queue, vec![§ion, ...]).unwrap();
{
let mut rpass = encoder.begin_render_pass(...);
brush.draw(&mut rpass);
}
queue.submit([encoder.finish()]);
frame.present();
}
![yuma14](https://res.cloudinary.com/zenn/image/fetch/s--3L3qV3kT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/f5afc8d1a4.jpeg)
glyph-brush
glyph-brushはレンダリングAPI非依存でテキストのラスタライズを行うライブラリ。キャッシュ付き。
内部でab_glyph
を使っている。
https://github.com/alexheretic/glyph-brush/tree/main/glyph-brush から引用:
use glyph_brush::{ab_glyph::FontArc, BrushAction, BrushError, GlyphBrushBuilder, Section, Text};
let dejavu = FontArc::try_from_slice(include_bytes!("../../fonts/DejaVuSans.ttf"))?;
let mut glyph_brush = GlyphBrushBuilder::using_font(dejavu).build();
glyph_brush.queue(Section::default().add_text(Text::new("Hello glyph_brush")));
glyph_brush.queue(some_other_section);
match glyph_brush.process_queued(
|rect, tex_data| update_texture(rect, tex_data),
|vertex_data| into_vertex(vertex_data),
) {
Ok(BrushAction::Draw(vertices)) => {
// Draw new vertices.
}
Ok(BrushAction::ReDraw) => {
// Re-draw last frame's vertices unmodified.
}
Err(BrushError::TextureTooSmall { suggested }) => {
// Enlarge texture + glyph_brush texture cache and retry.
}
}
![yuma14](https://res.cloudinary.com/zenn/image/fetch/s--3L3qV3kT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/f5afc8d1a4.jpeg)
ab_glyph
OpenTypeフォントの読み込み、スケーリング、ポジショニング、ラスタライズを行う。
ラスタライズはab_glyph_rasterizer
で行う。
https://github.com/alexheretic/ab-glyph/tree/main/glyph から引用:
use ab_glyph::{FontRef, Font, Glyph, point};
let font = FontRef::try_from_slice(include_bytes!("../../dev/fonts/Exo2-Light.otf"))?;
// Get a glyph for 'q' with a scale & position.
let q_glyph: Glyph = font.glyph_id('q').with_scale_and_position(24.0, point(100.0, 0.0));
// Draw it.
if let Some(q) = font.outline_glyph(q_glyph) {
q.draw(|x, y, c| { /* draw pixel `(x, y)` with coverage: `c` */ });
}
![yuma14](https://res.cloudinary.com/zenn/image/fetch/s--3L3qV3kT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/f5afc8d1a4.jpeg)
ab_glyph_rasterizer
ラスタライズを行う。OTFフォントの描画に向いている。
外部依存がない。
https://crates.io/crates/ab_glyph_rasterizer から引用:
let mut rasterizer = ab_glyph_rasterizer::Rasterizer::new(106, 183);
// draw a 300px 'ę' character
rasterizer.draw_cubic(point(103.0, 163.5), point(86.25, 169.25), point(77.0, 165.0), point(82.25, 151.5));
rasterizer.draw_cubic(point(82.25, 151.5), point(86.75, 139.75), point(94.0, 130.75), point(102.0, 122.0));
rasterizer.draw_line(point(102.0, 122.0), point(100.25, 111.25));
rasterizer.draw_cubic(point(100.25, 111.25), point(89.0, 112.75), point(72.75, 114.25), point(58.5, 114.25));
rasterizer.draw_cubic(point(58.5, 114.25), point(30.75, 114.25), point(18.5, 105.25), point(16.75, 72.25));
rasterizer.draw_line(point(16.75, 72.25), point(77.0, 72.25));
rasterizer.draw_cubic(point(77.0, 72.25), point(97.0, 72.25), point(105.25, 60.25), point(104.75, 38.5));
rasterizer.draw_cubic(point(104.75, 38.5), point(104.5, 13.5), point(89.0, 0.75), point(54.25, 0.75));
rasterizer.draw_cubic(point(54.25, 0.75), point(16.0, 0.75), point(0.0, 16.75), point(0.0, 64.0));
rasterizer.draw_cubic(point(0.0, 64.0), point(0.0, 110.5), point(16.0, 128.0), point(56.5, 128.0));
rasterizer.draw_cubic(point(56.5, 128.0), point(66.0, 128.0), point(79.5, 127.0), point(90.0, 125.0));
rasterizer.draw_cubic(point(90.0, 125.0), point(78.75, 135.25), point(73.25, 144.5), point(70.75, 152.0));
rasterizer.draw_cubic(point(70.75, 152.0), point(64.5, 169.0), point(75.5, 183.0), point(105.0, 170.5));
rasterizer.draw_line(point(105.0, 170.5), point(103.0, 163.5));
rasterizer.draw_cubic(point(55.0, 14.5), point(78.5, 14.5), point(88.5, 21.75), point(88.75, 38.75));
rasterizer.draw_cubic(point(88.75, 38.75), point(89.0, 50.75), point(85.75, 59.75), point(73.5, 59.75));
rasterizer.draw_line(point(73.5, 59.75), point(16.5, 59.75));
rasterizer.draw_cubic(point(16.5, 59.75), point(17.25, 25.5), point(27.0, 14.5), point(55.0, 14.5));
rasterizer.draw_line(point(55.0, 14.5), point(55.0, 14.5));
// iterate over the resultant pixel alphas, e.g. save pixel to a buffer
rasterizer.for_each_pixel(|index, alpha| {
// ...
});
![yuma14](https://res.cloudinary.com/zenn/image/fetch/s--3L3qV3kT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/f5afc8d1a4.jpeg)
Étagère
texture packingを行うライブラリ。shelf packingというアルゴリズムを実装している。