Add textured OBJ model rendering

This commit is contained in:
lenn
2026-06-25 17:41:18 +08:00
parent ec46e53c75
commit 156df50d33
14 changed files with 1777 additions and 20 deletions

128
src/resources.rs Normal file
View File

@@ -0,0 +1,128 @@
use crate::{model, texture};
use eframe::wgpu;
use eframe::wgpu::util::DeviceExt;
use std::path::{Path, PathBuf};
fn resource_path(file_name: &str) -> PathBuf {
Path::new(env!("RESOURCE_DIR")).join(file_name)
}
pub fn load_string(file_name: &str) -> anyhow::Result<String> {
Ok(std::fs::read_to_string(resource_path(file_name))?)
}
pub fn load_binary(file_name: &str) -> anyhow::Result<Vec<u8>> {
Ok(std::fs::read(resource_path(file_name))?)
}
pub fn load_texture(
file_name: &str,
device: &wgpu::Device,
queue: &wgpu::Queue,
) -> anyhow::Result<texture::Texture> {
let data = load_binary(file_name)?;
texture::Texture::from_bytes(device, queue, &data, file_name)
}
pub fn load_model(
file_name: &str,
device: &wgpu::Device,
queue: &wgpu::Queue,
texture_bind_group_layout: &wgpu::BindGroupLayout,
) -> anyhow::Result<model::Model> {
let path = resource_path(file_name);
let (models, obj_materials) = tobj::load_obj(
&path,
&tobj::LoadOptions {
single_index: true,
triangulate: true,
..Default::default()
},
)?;
let mut materials = Vec::new();
for material in obj_materials? {
let diffuse_texture = match material.diffuse_texture.as_deref() {
Some(texture_path) => load_texture(texture_path, device, queue)?,
None => texture::Texture::from_rgba8(
device,
queue,
&[255, 255, 255, 255],
1,
1,
&material.name,
)?,
};
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some(&format!("{} Texture Bind Group", material.name)),
layout: texture_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&diffuse_texture.view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler),
},
],
});
materials.push(model::Material {
name: material.name,
diffuse_texture,
bind_group,
});
}
let meshes = models
.into_iter()
.map(|m| {
let vertices = (0..m.mesh.positions.len() / 3)
.map(|i| model::ModelVertex {
position: [
m.mesh.positions[i * 3],
m.mesh.positions[i * 3 + 1],
m.mesh.positions[i * 3 + 2],
],
tex_coords: if m.mesh.texcoords.len() >= i * 2 + 2 {
[m.mesh.texcoords[i * 2], m.mesh.texcoords[i * 2 + 1]]
} else {
[0.0, 0.0]
},
normal: if m.mesh.normals.len() >= i * 3 + 3 {
[
m.mesh.normals[i * 3],
m.mesh.normals[i * 3 + 1],
m.mesh.normals[i * 3 + 2],
]
} else {
[0.0, 1.0, 0.0]
},
})
.collect::<Vec<_>>();
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(&format!("{} Vertex Buffer", m.name)),
contents: bytemuck::cast_slice(&vertices),
usage: wgpu::BufferUsages::VERTEX,
});
let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(&format!("{} Index Buffer", m.name)),
contents: bytemuck::cast_slice(&m.mesh.indices),
usage: wgpu::BufferUsages::INDEX,
});
model::Mesh {
name: m.name,
vertex_buffer,
index_buffer,
num_elements: m.mesh.indices.len() as u32,
material: m.mesh.material_id.unwrap_or(0),
}
})
.collect::<Vec<_>>();
Ok(model::Model { meshes, materials })
}