diff options
author | metamuffin <metamuffin@disroot.org> | 2025-03-26 16:07:20 +0100 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2025-03-26 16:07:20 +0100 |
commit | 4e8beb2270223bb3b6232773d82f18cc0e224435 (patch) | |
tree | 23fa130c2046a6bde74a50dbbcf9774a6df3ecb8 | |
parent | 6bdc402597ade997d1ceb69062b11248d32e9a6f (diff) | |
download | unity-tools-4e8beb2270223bb3b6232773d82f18cc0e224435.tar unity-tools-4e8beb2270223bb3b6232773d82f18cc0e224435.tar.bz2 unity-tools-4e8beb2270223bb3b6232773d82f18cc0e224435.tar.zst |
support texture arrays and cubemaps
-rw-r--r-- | src/classes/cubemap.rs | 41 | ||||
-rw-r--r-- | src/classes/material.rs | 4 | ||||
-rw-r--r-- | src/classes/mod.rs | 3 | ||||
-rw-r--r-- | src/classes/texture.rs | 29 | ||||
-rw-r--r-- | src/classes/texture2d.rs | 85 |
5 files changed, 109 insertions, 53 deletions
diff --git a/src/classes/cubemap.rs b/src/classes/cubemap.rs index 653ec38..0355a05 100644 --- a/src/classes/cubemap.rs +++ b/src/classes/cubemap.rs @@ -1,39 +1,30 @@ -use super::{streaming_info::StreamingInfo, texture2d::TextureFormat}; +use super::texture2d::Texture2D; use crate::object::{Value, parser::FromValue}; use anyhow::Result; use glam::{UVec2, Vec3Swizzles, uvec2, vec2, vec3}; -use image::{ImageBuffer, Rgba}; +use image::{DynamicImage, ImageBuffer, Rgba}; +use log::info; use serde::Serialize; use std::f32::consts::PI; #[derive(Debug, Serialize)] pub struct Cubemap { - pub width: i32, - pub height: i32, - pub mip_count: i32, - pub name: String, - #[serde(skip)] - pub image_data: Vec<u8>, - pub format: TextureFormat, - pub texture_dimension: i32, - pub stream_data: StreamingInfo, + pub texture: Texture2D, } impl FromValue for Cubemap { fn from_value(v: Value) -> Result<Self> { - let mut fields = v.as_class("Cubemap")?; + let fields = v.as_class("Cubemap")?; Ok(Cubemap { - width: fields.field("m_Width")?, - height: fields.field("m_Height")?, - mip_count: fields.field("m_MipCount")?, - texture_dimension: fields.field("m_TextureDimension")?, - format: fields.field("m_TextureFormat")?, - name: fields.field("m_Name")?, - stream_data: fields.field("m_StreamData")?, - image_data: fields.remove("image data").unwrap().as_typeless().unwrap(), + texture: Texture2D::from_fields(fields)?, }) } } +impl Cubemap { + pub fn to_image(&self) -> Result<DynamicImage> { + self.texture.to_image() + } +} /// Face ordering is Left, Right, Back, Front, Down, Up pub fn cubemap_to_equirectangular( @@ -41,6 +32,11 @@ pub fn cubemap_to_equirectangular( output_width: u32, output_height: u32, ) -> ImageBuffer<Rgba<f32>, Vec<f32>> { + info!( + "reprojecting {}x{} cubemap faces to {output_width}x{output_height} equirectangular", + faces[0].width(), + faces[0].height() + ); let mut output_image = ImageBuffer::<Rgba<f32>, Vec<f32>>::new(output_width, output_height); for (er_x, er_y, out) in output_image.enumerate_pixels_mut() { let xy_angle = er_x as f32 / output_width as f32 * PI * 2.; @@ -60,12 +56,9 @@ pub fn cubemap_to_equirectangular( }; let face = axis as usize * 2 + if max_comp < 0. { 1 } else { 0 }; - let flip_face = [1., 1., -1., -1., 1., 1.]; - let rotate_face = if face == 5 { -1. } else { 1. }; let face_texture = &faces[face]; let face_size = uvec2(face_texture.width(), face_texture.height()); - let face_uv = - rotate_face * (other_comps / max_comp * vec2(flip_face[face], -max_comp.signum())); + let face_uv = other_comps / max_comp * vec2(1., -max_comp.signum()); let face_uv = face_uv / 2. + 0.5; let face_xy = (face_uv * face_size.as_vec2()) diff --git a/src/classes/material.rs b/src/classes/material.rs index 9124b0b..b3055be 100644 --- a/src/classes/material.rs +++ b/src/classes/material.rs @@ -1,4 +1,4 @@ -use super::{pptr::PPtr, shader::Shader, texture2d::Texture2D, vectors::ColorRGBA}; +use super::{pptr::PPtr, shader::Shader, texture::AnyTexture, vectors::ColorRGBA}; use crate::object::{Value, parser::FromValue}; use glam::Vec2; use serde::Serialize; @@ -26,7 +26,7 @@ pub struct UnityPropertySheet { pub struct UnityTexEnv { pub offset: Vec2, pub scale: Vec2, - pub texture: PPtr<Texture2D>, + pub texture: PPtr<AnyTexture>, } impl UnityPropertySheet { diff --git a/src/classes/mod.rs b/src/classes/mod.rs index 2bba6c1..a5ba540 100644 --- a/src/classes/mod.rs +++ b/src/classes/mod.rs @@ -13,6 +13,7 @@ pub mod cubemap; pub mod render_settings; pub mod audio_clip; pub mod streamed_resource; +pub mod texture; use crate::object::{Value, parser::FromValue}; use anyhow::Result; @@ -58,7 +59,7 @@ impl HValue { "AssetInfo" => Self::AssetInfo(AssetInfo::from_value(value)?), "GameObject" => Self::GameObject(GameObject::from_value(value)?), "Transform" => Self::Transform(Transform::from_value(value)?), - "Texture2D" => Self::Texture2D(Texture2D::from_value(value)?), + // "Texture2D" => Self::Texture2D(Texture2D::from_value(value)?), "Mesh" => Self::Mesh(Mesh::from_value(value)?), "Material" => Self::Material(Material::from_value(value)?), _ => Self::Value([value]), diff --git a/src/classes/texture.rs b/src/classes/texture.rs new file mode 100644 index 0000000..6d1c102 --- /dev/null +++ b/src/classes/texture.rs @@ -0,0 +1,29 @@ +use super::{cubemap::Cubemap, texture2d::Texture2D}; +use crate::object::{Value, parser::FromValue}; +use anyhow::Result; +use serde::Serialize; + +#[derive(Debug, Serialize)] +pub enum AnyTexture { + Cubemap(Cubemap), + Texture2D(Texture2D), +} + +impl FromValue for AnyTexture { + fn from_value(v: Value) -> Result<Self> { + match v.class_name().unwrap().as_str() { + "Cubemap" => Ok(Self::Cubemap(v.parse()?)), + "Texture2D" => Ok(Self::Texture2D(v.parse()?)), + x => unreachable!("{x}"), + } + } +} +impl AnyTexture { + pub fn as_texture2d(self) -> Option<Texture2D> { + if let AnyTexture::Texture2D(x) = self { + Some(x) + } else { + None + } + } +} diff --git a/src/classes/texture2d.rs b/src/classes/texture2d.rs index 41820b0..6b96b17 100644 --- a/src/classes/texture2d.rs +++ b/src/classes/texture2d.rs @@ -1,5 +1,8 @@ use super::streaming_info::StreamingInfo; -use crate::object::{Value, parser::FromValue}; +use crate::object::{ + Value, + parser::{Fields, FromValue}, +}; use anyhow::{Result, anyhow, bail}; use image::{DynamicImage, ImageBuffer, Luma, Rgb, Rgba}; use log::info; @@ -14,13 +17,18 @@ pub struct Texture2D { #[serde(skip)] pub image_data: Vec<u8>, pub format: TextureFormat, + pub image_count: i32, pub texture_dimension: i32, pub stream_data: StreamingInfo, } impl FromValue for Texture2D { fn from_value(v: Value) -> Result<Self> { - let mut fields = v.as_class("Texture2D")?; + Self::from_fields(v.as_class("Texture2D")?) + } +} +impl Texture2D { + pub(crate) fn from_fields(mut fields: Fields) -> Result<Self> { Ok(Texture2D { width: fields.field("m_Width")?, height: fields.field("m_Height")?, @@ -28,6 +36,7 @@ impl FromValue for Texture2D { texture_dimension: fields.field("m_TextureDimension")?, format: fields.field("m_TextureFormat")?, name: fields.field("m_Name")?, + image_count: fields.field("m_ImageCount")?, stream_data: fields.field("m_StreamData")?, image_data: fields.remove("image data").unwrap().as_typeless().unwrap(), }) @@ -36,28 +45,40 @@ impl FromValue for Texture2D { impl Texture2D { pub fn to_image(&self) -> Result<DynamicImage> { + Ok(self.to_images()?.remove(0)) + } + pub fn to_images(&self) -> Result<Vec<DynamicImage>> { let w = self.width as usize; let h = self.height as usize; use TextureFormat::*; - let u32_rgba_buf_to_image = |buf: Vec<u32>| { - let buf = buf.into_iter().flat_map(u32::to_be_bytes).collect(); - let mut im = - ImageBuffer::<Rgba<u8>, Vec<_>>::from_raw(w as u32, h as u32, buf).unwrap(); - for p in im.pixels_mut() { - let a = p.0[0]; - p.0[0] = p.0[1]; - p.0[1] = p.0[2]; - p.0[2] = p.0[3]; - p.0[3] = a; + let u32_argb_buf_to_images = |buf: Vec<u32>| { + let buf = buf + .into_iter() + .flat_map(u32::to_be_bytes) + .collect::<Vec<_>>(); + let mut images = Vec::new(); + for buf in buf.chunks_exact(w * h * 4) { + let mut im = + ImageBuffer::<Rgba<u8>, Vec<_>>::from_raw(w as u32, h as u32, buf.to_vec()) + .unwrap(); + for p in im.pixels_mut() { + let a = p.0[0]; + p.0[0] = p.0[1]; + p.0[1] = p.0[2]; + p.0[2] = p.0[3]; + p.0[3] = a; + } + images.push(DynamicImage::from(im)) } - im.into() + images }; match self.format { Alpha8 => { let buf = self.image_data.clone(); let im = ImageBuffer::<Luma<u8>, Vec<_>>::from_raw(w as u32, h as u32, buf).unwrap(); - Ok(im.into()) + assert_eq!(self.image_count, 1); + Ok(vec![im.into()]) } RGBAHalf => { let buf = self @@ -68,11 +89,11 @@ impl Texture2D { .map(|x| u16::from_be_bytes(x)) .map(|x| f16::from_bits(x) as f32) .collect::<Vec<f32>>(); - Ok( + Ok(vec![ ImageBuffer::<Rgba<f32>, Vec<_>>::from_raw(w as u32, h as u32, buf) .unwrap() .into(), - ) + ]) } DXT1 | DXT3 | DXT5 | BC4 | BC5 => { use texpresso::Format as F; @@ -88,11 +109,20 @@ impl Texture2D { "decompressing {w}x{h} {:?} ({:?}) texture", format, self.format ); - let mut buf = vec![0u8; w * h * 4]; + let mut buf = vec![0u8; w * h * 4 * self.image_count as usize]; format.decompress(&self.image_data, w, h, &mut buf); - let im = - ImageBuffer::<Rgba<u8>, Vec<_>>::from_raw(w as u32, h as u32, buf).unwrap(); - Ok(im.into()) + Ok(buf + .chunks_exact(w * h * 4) + .map(|slice| { + ImageBuffer::<Rgba<u8>, Vec<_>>::from_raw( + w as u32, + h as u32, + slice.to_vec(), + ) + .unwrap() + }) + .map(DynamicImage::from) + .collect()) } DXT1Crunched | DXT5Crunched => { info!( @@ -105,19 +135,19 @@ impl Texture2D { ); let mut buf = vec![0u32; w * h]; texture2ddecoder::decode_unity_crunch(&self.image_data, w, h, &mut buf).unwrap(); - Ok(u32_rgba_buf_to_image(buf)) + Ok(u32_argb_buf_to_images(buf)) } BC7 => { info!("decompressing {w}x{h} BC7 texture",); let mut buf = vec![0u32; w * h]; texture2ddecoder::decode_bc7(&self.image_data, w, h, &mut buf).unwrap(); - Ok(u32_rgba_buf_to_image(buf)) + Ok(u32_argb_buf_to_images(buf)) } BC6H => { info!("decompressing {w}x{h} BC6H texture",); let mut buf = vec![0u32; w * h]; texture2ddecoder::decode_bc6(&self.image_data, w, h, &mut buf, false).unwrap(); - Ok(u32_rgba_buf_to_image(buf)) + Ok(u32_argb_buf_to_images(buf)) } RGB24 => { let im = ImageBuffer::<Rgb<u8>, Vec<_>>::from_raw( @@ -126,7 +156,8 @@ impl Texture2D { self.image_data.clone(), ) .unwrap(); - Ok(im.into()) + assert_eq!(self.image_count, 1); + Ok(vec![im.into()]) } ARGB32 => { let mut buf = self.image_data.clone(); @@ -139,13 +170,15 @@ impl Texture2D { } let im = ImageBuffer::<Rgba<u8>, Vec<_>>::from_raw(w as u32, h as u32, buf).unwrap(); - Ok(im.into()) + assert_eq!(self.image_count, 1); + Ok(vec![im.into()]) } RGBA32 => { let buf = self.image_data.clone(); let im = ImageBuffer::<Rgba<u8>, Vec<_>>::from_raw(w as u32, h as u32, buf).unwrap(); - Ok(im.into()) + assert_eq!(self.image_count, 1); + Ok(vec![im.into()]) } x => bail!("texture format {x:?} not supported"), } |