Implemented basic SurfaceClosure compression for storing per-vertex.

This is really simple, and doesn't account for things like constant
parameters yet.  It's just to get things rolling.
This commit is contained in:
Nathan Vegdahl 2019-07-13 11:15:04 +09:00
parent 41c2174d59
commit 5c5a01ecee
4 changed files with 253 additions and 8 deletions

View File

@ -5,7 +5,9 @@ pub use color::{
xyz_to_rec709_e, xyz_to_rec709_e,
}; };
use float4::Float4; use float4::Float4;
use half::f16;
use spectral_upsampling::meng::{spectrum_xyz_to_p_4, EQUAL_ENERGY_REFLECTANCE}; use spectral_upsampling::meng::{spectrum_xyz_to_p_4, EQUAL_ENERGY_REFLECTANCE};
use trifloat::signed48;
use crate::{lerp::Lerp, math::fast_exp}; use crate::{lerp::Lerp, math::fast_exp};
@ -138,6 +140,115 @@ impl Color {
Color::Temperature { factor, .. } => factor, Color::Temperature { factor, .. } => factor,
} }
} }
/// Returns the post-compression size of this color.
pub fn compressed_size(&self) -> usize {
match self {
Color::XYZ(_, _, _) => 7,
Color::Blackbody { .. } => 5,
Color::Temperature { .. } => 5,
}
}
/// Writes the compressed form of this color to `out_data`.
///
/// `out_data` must be at least `compressed_size()` bytes long, otherwise
/// this method will panic.
///
/// Returns the number of bytes written.
pub fn write_compressed(&self, out_data: &mut [u8]) -> usize {
match *self {
Color::XYZ(x, y, z) => {
out_data[0] = 0; // Discriminant
let col = signed48::encode((x, y, z));
let col = col.to_le_bytes();
(&mut out_data[1..7]).copy_from_slice(&col[0..6]);
}
Color::Blackbody {
temperature,
factor,
} => {
out_data[0] = 1; // Discriminant
let tmp = (temperature.min(std::u16::MAX as f32) as u16).to_le_bytes();
let fac = f16::from_f32(factor).to_bits().to_le_bytes();
out_data[1] = tmp[0];
out_data[2] = tmp[1];
out_data[3] = fac[0];
out_data[4] = fac[1];
}
Color::Temperature {
temperature,
factor,
} => {
out_data[0] = 2; // Discriminant
let tmp = (temperature.min(std::u16::MAX as f32) as u16).to_le_bytes();
let fac = f16::from_f32(factor).to_bits().to_le_bytes();
out_data[1] = tmp[0];
out_data[2] = tmp[1];
out_data[3] = fac[0];
out_data[4] = fac[1];
}
}
self.compressed_size()
}
/// Constructs a Color from compressed color data, and also returns the
/// number of bytes consumed from `in_data`.
pub fn from_compressed(in_data: &[u8]) -> (Color, usize) {
match in_data[0] {
0 => {
// XYZ
let mut bytes = [0u8; 8];
(&mut bytes[0..6]).copy_from_slice(&in_data[1..7]);
let (x, y, z) = signed48::decode(u64::from_le_bytes(bytes));
(Color::XYZ(x, y, z), 7)
}
1 => {
// Blackbody
let mut tmp = [0u8; 2];
let mut fac = [0u8; 2];
tmp[0] = in_data[1];
tmp[1] = in_data[2];
fac[0] = in_data[3];
fac[1] = in_data[4];
let tmp = u16::from_le_bytes(tmp);
let fac = f16::from_bits(u16::from_le_bytes(fac));
(
Color::Blackbody {
temperature: tmp as f32,
factor: fac.into(),
},
5,
)
}
2 => {
// Temperature
let mut tmp = [0u8; 2];
let mut fac = [0u8; 2];
tmp[0] = in_data[1];
tmp[1] = in_data[2];
fac[0] = in_data[3];
fac[1] = in_data[4];
let tmp = u16::from_le_bytes(tmp);
let fac = f16::from_bits(u16::from_le_bytes(fac));
(
Color::Temperature {
temperature: tmp as f32,
factor: fac.into(),
},
5,
)
}
_ => unreachable!(),
}
}
} }
impl Mul<f32> for Color { impl Mul<f32> for Color {

View File

@ -4,7 +4,7 @@ use std::fmt::Debug;
use crate::{color::Color, surface::SurfaceIntersectionData}; use crate::{color::Color, surface::SurfaceIntersectionData};
use self::surface_closure::SurfaceClosure; pub use self::surface_closure::SurfaceClosure;
/// Trait for surface shaders. /// Trait for surface shaders.
pub trait SurfaceShader: Debug + Sync { pub trait SurfaceShader: Debug + Sync {

View File

@ -6,7 +6,7 @@ use float4::Float4;
use crate::{ use crate::{
color::{Color, SpectralSample}, color::{Color, SpectralSample},
lerp::lerp, lerp::{lerp, Lerp},
math::{clamp, dot, zup_to_vec, Normal, Vector}, math::{clamp, dot, zup_to_vec, Normal, Vector},
sampling::cosine_sample_hemisphere, sampling::cosine_sample_hemisphere,
}; };
@ -153,6 +153,128 @@ impl SurfaceClosure {
), ),
} }
} }
/// Returns the post-compression size of this closure.
pub fn compressed_size(&self) -> usize {
1 + match *self {
Lambert(color) => color.compressed_size(),
GGX { color, .. } => {
2 // Roughness
+ 2 // Fresnel
+ color.compressed_size() // Color
}
Emit(color) => color.compressed_size(),
}
}
/// Writes the compressed form of this closure to `out_data`.
///
/// `out_data` must be at least `compressed_size()` bytes long, otherwise
/// this method will panic.
///
/// Returns the number of bytes written.
pub fn write_compressed(&self, out_data: &mut [u8]) -> usize {
match *self {
Lambert(color) => {
out_data[0] = 0; // Discriminant
color.write_compressed(&mut out_data[1..]);
}
GGX {
color,
roughness,
fresnel,
} => {
out_data[0] = 1; // Discriminant
// Roughness and fresnel (we write these first because they are
// constant-size, whereas the color is variable-size, so this
// makes things a little easier).
let rgh =
((roughness.max(0.0).min(1.0) * std::u16::MAX as f32) as u16).to_le_bytes();
let frs = ((fresnel.max(0.0).min(1.0) * std::u16::MAX as f32) as u16).to_le_bytes();
out_data[1] = rgh[0];
out_data[2] = rgh[1];
out_data[3] = frs[0];
out_data[4] = frs[1];
// Color
color.write_compressed(&mut out_data[5..]); // Color
}
Emit(color) => {
out_data[0] = 2; // Discriminant
color.write_compressed(&mut out_data[1..]);
}
}
self.compressed_size()
}
/// Constructs a SurfaceClosure from compressed closure data, and also
/// returns the number of bytes consumed from `in_data`.
pub fn from_compressed(in_data: &[u8]) -> (SurfaceClosure, usize) {
match in_data[0] {
0 => {
// Lambert
let (col, size) = Color::from_compressed(&in_data[1..]);
(SurfaceClosure::Lambert(col), 1 + size)
}
1 => {
// GGX
let mut rgh = [0u8; 2];
let mut frs = [0u8; 2];
rgh[0] = in_data[1];
rgh[1] = in_data[2];
frs[0] = in_data[3];
frs[1] = in_data[4];
let rgh = u16::from_le_bytes(rgh) as f32 * (1.0 / std::u16::MAX as f32);
let frs = u16::from_le_bytes(frs) as f32 * (1.0 / std::u16::MAX as f32);
let (col, size) = Color::from_compressed(&in_data[5..]);
(
SurfaceClosure::GGX {
color: col,
roughness: rgh,
fresnel: frs,
},
5 + size,
)
}
2 => {
// Emit
let (col, size) = Color::from_compressed(&in_data[1..]);
(SurfaceClosure::Emit(col), 1 + size)
}
_ => unreachable!(),
}
}
}
impl Lerp for SurfaceClosure {
fn lerp(self, other: SurfaceClosure, alpha: f32) -> SurfaceClosure {
match (self, other) {
(Lambert(col1), Lambert(col2)) => Lambert(lerp(col1, col2, alpha)),
(
GGX {
color: col1,
roughness: rgh1,
fresnel: frs1,
},
GGX {
color: col2,
roughness: rgh2,
fresnel: frs2,
},
) => GGX {
color: lerp(col1, col2, alpha),
roughness: lerp(rgh1, rgh2, alpha),
fresnel: lerp(frs1, frs2, alpha),
},
(Emit(col1), Emit(col2)) => Emit(lerp(col1, col2, alpha)),
_ => panic!("Cannot lerp between different surface closure types."),
}
}
} }
/// Lambert closure code. /// Lambert closure code.

View File

@ -11,7 +11,7 @@ use crate::{
lerp::lerp_slice, lerp::lerp_slice,
math::{cross, dot, Matrix4x4, Normal, Point}, math::{cross, dot, Matrix4x4, Normal, Point},
ray::{RayBatch, RayStack}, ray::{RayBatch, RayStack},
shading::{SimpleSurfaceShader, SurfaceShader}, shading::SurfaceClosure,
}; };
use super::{triangle, SurfaceIntersection, SurfaceIntersectionData}; use super::{triangle, SurfaceIntersection, SurfaceIntersectionData};
@ -33,7 +33,9 @@ pub struct MicropolyBatch<'a> {
normals: &'a [Normal], normals: &'a [Normal],
// Per-vertex shading data. // Per-vertex shading data.
vertex_closures: &'a [SimpleSurfaceShader], compressed_vertex_closure_size: usize, // Size in bites of a single compressed closure
vertex_closure_time_sample_count: usize,
compressed_vertex_closures: &'a [u8], // Packed compressed closures
// Micro-triangle indices. Each element of the tuple specifies the index // Micro-triangle indices. Each element of the tuple specifies the index
// of a vertex, which indexes into all of the arrays above. // of a vertex, which indexes into all of the arrays above.
@ -127,7 +129,9 @@ impl<'a> MicropolyBatch<'a> {
time_sample_count: time_sample_count, time_sample_count: time_sample_count,
vertices: vertices, vertices: vertices,
normals: normals, normals: normals,
vertex_closures: &[], compressed_vertex_closure_size: 0,
vertex_closure_time_sample_count: 1,
compressed_vertex_closures: &[],
indices: indices, indices: indices,
accel: accel, accel: accel,
} }
@ -317,8 +321,16 @@ impl<'a> MicropolyBatch<'a> {
// Calculate interpolated surface closure. // Calculate interpolated surface closure.
// TODO: actually interpolate. // TODO: actually interpolate.
let closure = self.vertex_closures let closure = {
[hit_tri_indices.0 as usize * self.time_sample_count]; let start_byte = hit_tri_indices.0 as usize
* self.compressed_vertex_closure_size
* self.vertex_closure_time_sample_count;
let end_byte = start_byte + self.compressed_vertex_closure_size;
let (closure, _) = SurfaceClosure::from_compressed(
&self.compressed_vertex_closures[start_byte..end_byte],
);
closure
};
let intersection_data = SurfaceIntersectionData { let intersection_data = SurfaceIntersectionData {
incoming: rays.dir(ray_idx), incoming: rays.dir(ray_idx),
@ -334,7 +346,7 @@ impl<'a> MicropolyBatch<'a> {
// Fill in intersection data // Fill in intersection data
isects[ray_idx] = SurfaceIntersection::Hit { isects[ray_idx] = SurfaceIntersection::Hit {
intersection_data: intersection_data, intersection_data: intersection_data,
closure: closure.shade(&intersection_data, ray_time), closure: closure,
}; };
} }
}); });