aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2025-03-26 16:07:20 +0100
committermetamuffin <metamuffin@disroot.org>2025-03-26 16:07:20 +0100
commit4e8beb2270223bb3b6232773d82f18cc0e224435 (patch)
tree23fa130c2046a6bde74a50dbbcf9774a6df3ecb8
parent6bdc402597ade997d1ceb69062b11248d32e9a6f (diff)
downloadunity-tools-4e8beb2270223bb3b6232773d82f18cc0e224435.tar
unity-tools-4e8beb2270223bb3b6232773d82f18cc0e224435.tar.bz2
unity-tools-4e8beb2270223bb3b6232773d82f18cc0e224435.tar.zst
support texture arrays and cubemaps
-rw-r--r--src/classes/cubemap.rs41
-rw-r--r--src/classes/material.rs4
-rw-r--r--src/classes/mod.rs3
-rw-r--r--src/classes/texture.rs29
-rw-r--r--src/classes/texture2d.rs85
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"),
}