1 个不稳定版本
0.1.0 | 2021年12月11日 |
---|
#236 in 渲染
43KB
889 行
基于纹理图集的瓦片地图网格构建器
这个库可以帮助你创建用于绘制大量小型静态2D图像的网格。
想象一下创建一个带有大范围可缩放的瓦片地图的2D游戏,就像Factorio。常规的精灵批处理针对动态精灵,其数据每帧都上传到GPU。使用它们来处理大量静态图像的大范围场景可能会太慢。所以,当你在牺牲GPU内存的代价下需要甜美的渲染速度时,就是你自己为地图的块创建网格的时候了。这正是这个库帮助你做到的。
简要指南
features = [ "ggez" ]
或features = [ "tetra" ]
应当设置,如果你计划使用其中之一。- 使用ggez、Tetra或你自己的自定义顶点类型(实现了
From<PosUvColor>
)创建网格构建器MeshFromQuads。
- 使用构建器的
set
方法(如set_pos_color_source
)以任何顺序将网格四边形设置为各种图像。 - 完成后,调用
create_mesh
或,如果你没有使用ggez和Tetra,调用into_vertices_and_indices
。 - 以您喜欢的方式将网格或顶点绘制到屏幕上,只需确保顶点按顺时针顺序排列即可。您甚至可以在
set
方法中控制 UV 翻转,并使用您想要的任何坐标系。默认情况下是针对 OpenGL 的从左到右、从下到上的系统,但在示例中我垂直翻转了 UV,因为 ggez 和 Tetra 都使用从上到下。
更详细的指南
查看 ggez 测试 (ggez.rs) 或 Tetra 测试 (tetra.rs)。像往常一样,在启动 Tetra 测试之前,请确保 SDL2 开发库可用。在 Windows 上,最快的方法是将它们直接放在 crate 根目录下。长话短说,这里是一个使用 Tetra 顶点类型创建瓦片地图的示例,ggez 几乎相同。
use stabilkon::*;
use tetra::{
graphics::{
mesh::{IndexBuffer, Mesh, Vertex, VertexBuffer},
Color, Rectangle, Texture,
},
math::Vec2,
Context, TetraError,
};
// Load texture atlas with tile images:
let tiles_texture_atlas = Texture::new(ctx, "./path/to/texture_atlas.png")?;
// We won't be using custom shaders and such - let the mesh builder fix UVs for us:
let use_half_pixel_offset = true;
// Single tile is 32×32:
let tile_size = 32.0_f32;
// Let's make test map 256×256:
let map_size = Vec2::from(256);
// Calculate required quad limit for the map:
let quad_count = map_size.x * map_size.y;
// Pick grass tile image from atlas; it is located at the very top-left of the texture atlas.
let grass_tile_source = [0.0, 0.0, 32.0, 32.0];
// Standard white color, means tile images will be drawn as-is.
let white_color = [1.0_f32, 1.0, 1.0, 1.0];
// When adding a quad to a mesh builder, you can control UV flipping with `UvFlip` parameter.
// By default the usual left-to-right, bottom-to-top system is used.
// But we decided to use left-to-right, top-to-bottom coordinate system in tile source rectangle above, so when
// adding quads using `grass_tile_source` a `UvFlip::Vertical` will be supplied.
// Create a mesh builder for an indexed mesh capable of holding entire map...
let mut terrain_mesh_builder: MeshFromQuads<Vertex> = MeshFromQuads::new(
[
tiles_texture_atlas.width() as f32,
tiles_texture_atlas.height() as f32,
],
use_half_pixel_offset,
quad_count,
)?;
// ... and add a lot of quads with grass tile texture region:
let mut quad_index = 0_u32;
for y in 0..map_size.y {
for x in 0..map_size.x {
let position = [x as f32 * tile_size, y as f32 * tile_size];
terrain_mesh_builder.set_pos_color_source(
quad_index,
position,
white_color,
grass_tile_source,
UvFlip::Vertical,
);
quad_index += 1;
}
}
// Finally, create a mesh consisting of quads covered with grass tile texture region:
let (terrain_mesh, terrain_vb) = terrain_mesh_builder.create_mesh(ctx, texture_atlas)?;
// All done, now you can use this mesh as usual!
在创建静态网格后更新四边形
让我们用 Tetra 来举四边形更新的例子。ggez 几乎相同,只是您直接在创建的网格上更改顶点缓冲区。
如您所注意到的,在先前的例子中,create_mesh
调用不仅返回一个网格,还返回其顶点缓冲区。要更改四边形顶点,您需要调用顶点缓冲区的 set_data
方法,并传递更改后的顶点和它们的偏移量。
更改单个四边形
// Assume we have created indexed mesh before, it is `terrain_vb` from the upper example.
let use_indices = true;
// ...and we want to change the eight quad we have added into a hole tile.
let new_quad_index = 7;
let hole_tile_source = [160.0, 0.0, 32.0, 32.0];
// Calculate its vertex offset:
let offset = new_quad_index * vertices_per_quad(use_indices);
// Get new quad vertices:
let new_quad_params =
PosColorSource::new([512.0, 128.0], white_color, hole_tile_source, UvFlip::Vertical);
let new_quad_vertices = new_quad_params.to_vertices(texture_size, use_indices);
// Alright, now upload new vertices at the changed offset:
terrain_vb.set_data(ctx, &new_quad_vertices, offset as usize);
更改多个四边形
// Again, we will be changing `terrain_vb`, but this time we need to have access to entire vertex buffer data.
// Because we are changing random quads, we need to get proper vertices for all quads between the first
// (with the smallest index) and the last (with the biggest index) changed quads.
// And for that we need to keep `terrain_mesh_builder` around.
// Alright, these are the quads we are going to change into hole tiles:
let changed_quads = [7, 17, 13, 11];
let hole_tile_source = [160.0, 0.0, 32.0, 32.0];
// Find the first quad and its vertex offset:
let first_changed_quad = changed_quads.iter().min().unwrap();
let first_changed_quad_vertex_offset =
(first_changed_quad * terrain_mesh_builder.vertices_per_quad()) as usize;
// Find the last quad and its 'end' vertex offset:
let last_changed_quad = changed_quads.iter().max().unwrap();
let last_changed_quad_vertex_offset =
(last_changed_quad * terrain_mesh_builder.vertices_per_quad()) as usize;
let after_last_changed_quad_vertex_offset =
last_changed_quad_vertex_offset + terrain_mesh_builder.vertices_per_quad() as usize;
// Perform our changes:
for changed_quad_index in changed_quads {
terrain_mesh_builder.set_pos_color_source(
changed_quad_index,
// Just extrude tiles diagonally from the map for demo purposes:
[-(changed_quad_index as f32 * 32.0), -(changed_quad_index as f32 * 32.0)],
white_color,
hole_tile_source,
UvFlip::Vertical,
);
}
// Upload our changes to terrain vertex buffer:
let vertices_to_upload = &terrain_mesh_builder.vertices()
[first_changed_quad_vertex_offset..after_last_changed_quad_vertex_offset];
terrain_vb.set_data(ctx, vertices_to_upload, first_changed_quad_vertex_offset);
局限性
这里有 3 件事您可能想要记住
- 静态图像构建器假设每个网格使用 1 个纹理,因此所有图像都应该打包到纹理图中。如果单个图集不足以,则创建多个网格并将它们叠加。
- 请确保您的顶点类型是纯文本的:它应该只包含值,不应该包含指针。
- 网格本身应该尽可能大,但不能太大以至于 GPU 无法处理。如果有疑问,以 32 MiB 块为目标。
依赖关系
~1–36MB
~555K SLoC