テクスチャを持ったモデルのロード < rusterizer-from-dots
PLYファイルをOBJ形式に変換したい....できるのか?
For converting PLY to OBJ/3DS formats, there used to be a free demo version of Deep Exploration, available here, but we hear it is no longer available.
ほう、一応アクセスしてみるか
...オンラインカジノの紹介サイトになっとるやないかい!!!!
フロリダのカジノがどうたらとか言うとる、怖いねえ
Bruce Merry of South Africa has written a script to import PLY files to Blender. Click here to download it.
うーんこれもリンク切れか....
公式に用意された手段では読み込むことは難しそう、かな?
あ、今のBlenderはplyファイルを読めるっぽいな?
http通信経由でDLしないといけない関係上、いったんブロックされてしまうが...
あぁ、本当に点群のデータだこれ...
思ったよりダウンロードが難しいな...?
こういうのもあるでGPT.icon
なるほどappbird.icon
こういうのもあるな
sponzaを表示してみる
sponzaを表示する
先は頂点データとして色属性をとったが、目的(ラズリちゃんレンダリング)のことを考えるならUV座標とそのテクスチャをデータとして取ってくる必要があるだろう
テクスチャをglTFデータから取ってくる
それらのデータはどうとってくればよいのか?
Overviewがわかりやすいappbird.icon
Textureのデータはどこに入れる?
今のところVertexArrayObjectに入れたら最も早く実装が済むかな
あるいはVAOColorと組で渡すことにするか
後から修正するが....。多分最終的にはMaterialを作ることになるんだと思う。
見渡しやすくて良いappbird.icon
PDF版の目次もある程度は見やすい。
上のglTFの形式を読んだりしてみる
あとはTexture構造体周りのコードも読んでみる
参照したいprimitiveにはmaterialの参照が入っている (id。materials系列のインデックスを意味する。)
materialにはpbr_metallic_roughnessが入っているので、こいつからbase_color (texture)に関する情報を引っ張ってくる
textureからimageの参照をとってくる
するとimageはenumで表現されたSource<'a>ってところに入っていそう。
んで、glTFクレートはここらの参照を自動で解決しておいてくれる。便利!
code:mesh/mesh_loader.rs
let tex_image:Source<'a> =
material.pbr_metallic_roughness()
.base_color_texture()
.expect(&format!("There should be texture in primitive {}.", primitive.index()))
.texture()
.source() // imagesの要素への参照
.source();// 埋め込まれたmimeのデータかURIの記述
かな...?
samplerなるものもあったけどこいつが一体何なのかはよくわからん!appbird.icon
glTFの定義はこうなってる
code:gltf-1.4.1/src/image.rs
pub enum Source<'a> {
View {
view: buffer::View<'a>,
mime_type: &'a str,
}
Uri {
uri: &'a str,
mime_type: Option<&'a str>,
},
}
Sourceは実データの参照が入っている。
binaryの位置参照かそれともURIが入っているかのどっちか
...これUriの場合はいいとして、Viewの場合どうすりゃいいの?appbird.icon
viewから直接データが得られなさそう
let (document, buffers, images) = gltf::import(path)?;やでGPT.icon
なるほど、glbで埋め込まれてる場合gltf::importで必要なデータへの参照はもう取れてるわけか。appbird.icon
let (document, buffers, images) = gltf::import(path)?;
Import glTF 2.0 from the file system
となれば、view.index()から取れたpixelsのデータをimageクレートのdynamic imageに変換出来りゃいいかな
code:rs
image::load_from_memory(buffer).unwrap();
エッそれでいいの width heightの情報必要なんじゃないの
埋め込まれてる場合、
バッファ領域からview情報にあるデータからそいつを読み込むことでも読める
code:rs
let image =
match tex_image {
gltf::image::Source::View { view, .. } => {
image::load_from_memory(buffer)
.expect("The buffer should be parsable as image")
},
gltf::image::Source::Uri { uri, .. } => {
ImageReader::open(uri)
.expect(&format!("Failed to open image in {}", uri))
.decode()
.expect(&format!("Failed to parse as image {}", uri))
},
};
こうかな~appbird.icon
ここらの動きは全部まとめちゃいましょうかね
code:rs
fn extract_image(images: &Vec<gltf::image::Data>, primitive:&Primitive) -> DynamicImage {
let material = primitive.material();
let tex_image =
material.pbr_metallic_roughness()
.base_color_texture()
.expect(&format!("There should be texture in primitive {}.", primitive.index()))
.texture()
.source()
.source();
let image =
match tex_image {
gltf::image::Source::View { view, .. } => {
image::load_from_memory(buffer)
.expect("The buffer should be parsable as image")
},
gltf::image::Source::Uri { uri, .. } => {
ImageReader::open(uri)
.expect(&format!("Failed to open image in {}", uri))
.decode()
.expect(&format!("Failed to parse as image {}", uri))
},
};
image
}
あとは....関数の戻り値がVAOUVでImageを返せないのでタプルにするわよ
1primitiveにそれぞれDynamicImageを結びつける実装になっている
もちろんこれは後でMaterialクラスかなんかへ分離する。
後々シェーディングとかやって、どんどん複雑化していくだろうけど、とりあえず今は動くもん優先
code:rs
type VAOUV = VertexArrayObject<texture_pipeline::Attribute>;
type VAOUVImg = (VAOUV, DynamicImage);
pub fn load_vao(path:&Path)
-> Throwable<Vec<VAOUVImg>> {
let (model, buffers, images) = gltf::import(path)?;
let mut vao_seq: Vec<VAOUVImg> = vec![];
for mesh in model.meshes() {
for primitive in mesh.primitives() {
let points = ...;
let uvs = ...;
assert_eq!(points.len(), uvs.len());
let attribute = ...;
let image= extract_image(&images, &primitive);
let idx = ...;
vao_seq.push((VAOUV{ attribute, idx }, image));
}
}
Ok(vao_seq)
}
よし、これをもともとのコードに統合していこう。
こいつからimageが帰ってくるので、Actor側でもtextureを持つようにする
後々こいつはmaterialになるんだと思う 多分
code:actor/gltf_test.rs
type TextureUni = <TexturePipeline as RenderingPipeline>::Uniform;
type TextureAttr = <TexturePipeline as RenderingPipeline>::Attribute;
pub struct GltfTestActor {
:
texture: Rc<Rgba32FImage>,
terminated:bool,
}
impl GltfTestActor {
pub fn new(
mesh:VertexArrayObject<TextureAttr>,
image: DynamicImage
) -> Self {
let mut mesh_renderer = MeshRenderer::new(TexturePipeline{});
let texture = image.to_rgba32f();
let texture = Rc::new(texture);
mesh_renderer.culling(true);
Self {
:
texture
}
}
}
impl Actor for GltfTestActor {
:
fn render(&self, _camera:&Camera, canvas:&mut Canvas, pv:&Mat4x4) -> () {
let uni = TextureUni{
pvm: pv * &self.transform.model_conversion(),
size: canvas.size(),
texture: self.texture.clone()
};
self.mesh_renderer.render(canvas, &self.mesh, &uni);
}
:
}
こうすれば、main.rsはここを変えるだけでおk
まず、sponzaをロードするように
code:main.rs
let vao_seq = load_vao(&Path::new("./resource/sponza.glb"))?;
そしてActorへの変数の渡し方を変える
code:main.rs
vao_seq.into_iter()
.map(|(vao, image)| GltfTestActor::new(vao, image))
.for_each(|actor| world.spawn(actor));
ヨシ!
bugfix: load_from_memoryからImageBuffer::from_rawへ
code:cmd
thread 'main' panicked at renderer_core\src\mesh\mesh_loader.rs:68:18:
The buffer should be parsable as image: Unsupported(UnsupportedError { format: Unknown, kind: Format(Unknown) })
あはん....appbird.icon
code:rs
image::load_from_memory(buffer)
.expect("The buffer should be parsable as image")
ダメらしい まぁ、そんな気はしていた...。
エッそれでいいの width heightの情報必要なんじゃないの
これで取れるpixelsってなんすか...?appbird.icon
多分色情報しか入ってなくて、width, heightは復元できないよね
でも多分load_from_memoryの説明を見る限り、これはこういう色情報の配列ではなくて、おそらくjpgとかpngとかそういうバイト配列を想定していそうだ
Create a new image from a byte slice
Makes an educated guess about the image format. TGA is not supported by this function.
Try ImageReader for more advanced uses.
ここでのeducatedって用法、多分さまざまなフォーマットを考慮に入れてってぐらいの意味かな
じゃあ欲しいのはimage側の色情報だけ乗った配列とwidthとheightからDynamicImageを作る関数かな
imageクレート(最新版 0.25.x 時点)には、Vec<u8>+width+height から DynamicImage を直接作るユーティリティメソッドは存在しませんが、ImageBuffer 経由で簡単に構築できます。GPT.icon
なるほど?appbird.icon
code:rs
use image::{ImageBuffer, RgbaImage, DynamicImage};
fn from_rgba_bytes(bytes: Vec<u8>, width: u32, height: u32) -> DynamicImage {
// バイト列から RgbaImage を生成(長さチェックはしない)
let buffer: RgbaImage = ImageBuffer::from_raw(width, height, bytes)
.expect("invalid buffer length");
DynamicImage::ImageRgba8(buffer)
}
あ~一回Rgbaimageを経由するのか
じゃあRgba32FImage返しちゃっていいんじゃないか?結局ほしいのはそれだし
code:mesh_loader.rs
gltf::image::Source::View { view, .. } => {
let buffer =
image::ImageBuffer::from_raw(buffer.width, buffer.height, buffer.pixels.clone())
.expect(&format!("Failed to parse as image #{}", view.index())); DynamicImage::ImageRgba8(buffer).to_rgba32f()
},
はい
cloneするのは気に喰わないけど、同じテクスチャを何度も使いまわす場合は、今の実装だとこうせざるを得ないよね...
code:rs
gltf::image::Source::Uri { uri, .. } => {
ImageReader::open(uri)
.expect(&format!("Failed to open image in {}", uri))
.decode()
.expect(&format!("Failed to parse as image {}", uri))
.to_rgba32f() // inserted
},
こっちもそろえて
code:rs
type VAOUVImg = (VAOUV, Rgba32FImage);
fn extract_image(images: &Vec<gltf::image::Data>, primitive:&Primitive) -> Rgba32FImage {
code:actor/gltf_test.rs
impl GltfTestActor {
pub fn new(
mesh:VertexArrayObject<TextureAttr>,
image: Rgba32FImage
) -> Self {
bugfix: 異なる画像フォーマットへの対応
code:rs
thread 'main' panicked at renderer_core\src\mesh\mesh_loader.rs:69:22:
Failed to parse as image #5 えーーーーーーーーーーーーーーappbird.icon
うそでしょ?appbird.icon
...いや待って、そういえば
image::Dataのstructの中でformatってあったな...もしかして...
code:main.rs
println!("{:?}", buffer.format);
let buffer: ImageBuffer<image::Rgba<u8>, Vec<u8>> =
image::ImageBuffer::from_raw(buffer.width, buffer.height, buffer.pixels.clone())
.expect(&format!("Failed to parse as image #{}", view.index())); code:rs
R8G8B8
う~~んはいはいはい。
aチャネルがないからずれてるんだこれ
てぇ事はだよ このパターン全部網羅しなきゃいけない....ってコト!?
code:rs
pub enum Format {
R8,
R8G8B8,
R8G8B8A8,
R16,
R16G16,
R16G16B16,
R16G16B16A16,
R32G32B32FLOAT,
R32G32B32A32FLOAT,
}
そんなぁ...appbird.icon
いや、冷静に考えると、対応すりゃいいのは(画像として特に渡されがちなのは)
code:rs
R8G8B8,
R8G8B8A8,
この二つだけだろうから、これ以外は全部panic!すればいいんじゃない
そうするか~
2025-10-21
そうした
R8だけの画像もあったから追加してこう
code:rs
fn construct_image(buffer:&Data, index:usize) -> Rgba32FImage {
println!("{:?}", buffer.format);
match buffer.format {
gltf::image::Format::R8 => {
let buffer: ImageBuffer<Luma<u8>, _> =
image::ImageBuffer::from_raw(buffer.width, buffer.height, buffer.pixels.clone())
.expect(&format!("Failed to parse as image #{}", index)); DynamicImage::ImageLuma8(buffer).to_rgba32f()
},
gltf::image::Format::R8G8B8 => {
let buffer: ImageBuffer<Rgb<u8>, _> =
image::ImageBuffer::from_raw(buffer.width, buffer.height, buffer.pixels.clone())
.expect(&format!("Failed to parse as image #{}", index)); DynamicImage::ImageRgb8(buffer).to_rgba32f()
},
gltf::image::Format::R8G8B8A8 => {
let buffer: ImageBuffer<Rgba<u8>, _> =
image::ImageBuffer::from_raw(buffer.width, buffer.height, buffer.pixels.clone())
.expect(&format!("Failed to parse as image #{}", index)); DynamicImage::ImageRgba8(buffer).to_rgba32f()
},
_ => panic!("This image format is not supported.")
}
}
こうするとこうなる
code:rs
thread 'main' panicked at renderer_core\src\mesh\mesh_loader.rs:59:10:
There should be a texture in primitive 2.
note: run with RUST_BACKTRACE=1 environment variable to display a backtrace
なんと、テクスチャが含まれていないヤツがいるappbird.icon
...そいつはいったん読み込まないという手もある、か?
逆に何に含まれてるんだ、カラー属性だけあるというのか...?appbird.icon