Revamp model lighting

This commit is contained in:
Connor Slade
2024-07-24 15:20:07 -04:00
parent 4fddb7022a
commit 1bb875c972
12 changed files with 213 additions and 14 deletions

32
Cargo.lock generated
View File

@@ -1157,6 +1157,26 @@ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]]
name = "const_format"
version = "0.2.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673"
dependencies = [
"const_format_proc_macros",
]
[[package]]
name = "const_format_proc_macros"
version = "0.2.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500"
dependencies = [
"proc-macro2 1.0.85",
"quote 1.0.36",
"unicode-xid 0.2.4",
]
[[package]] [[package]]
name = "const_panic" name = "const_panic"
version = "0.2.8" version = "0.2.8"
@@ -1456,6 +1476,15 @@ dependencies = [
"nohash-hasher", "nohash-hasher",
] ]
[[package]]
name = "egui-phosphor"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c873785789bc94a14dcbbd361cda1e498957fa1b4ec389ba650fd651849463b"
dependencies = [
"egui",
]
[[package]] [[package]]
name = "egui-wgpu" name = "egui-wgpu"
version = "0.27.2" version = "0.27.2"
@@ -2678,8 +2707,10 @@ dependencies = [
"bytemuck", "bytemuck",
"clone-macro", "clone-macro",
"common", "common",
"const_format",
"eframe", "eframe",
"egui", "egui",
"egui-phosphor",
"egui-wgpu", "egui-wgpu",
"egui_dock", "egui_dock",
"encase", "encase",
@@ -2688,6 +2719,7 @@ dependencies = [
"nalgebra 0.32.6", "nalgebra 0.32.6",
"parking_lot", "parking_lot",
"plexus", "plexus",
"rand 0.8.5",
"rfd", "rfd",
"slicer", "slicer",
"tracing", "tracing",

View File

@@ -1,4 +1,5 @@
pub mod config; pub mod config;
pub mod image; pub mod image;
pub mod misc; pub mod misc;
pub mod oklab;
pub mod serde; pub mod serde;

107
common/src/oklab.rs Normal file
View File

@@ -0,0 +1,107 @@
use std::f32::consts::PI;
#[derive(Debug, Clone, Copy)]
pub struct OkLab<T> {
pub l: T,
pub a: T,
pub b: T,
}
#[derive(Debug, Clone, Copy)]
pub struct Rgb<T> {
pub r: T,
pub g: T,
pub b: T,
}
/// A good starting color for hue shifting
pub const START_COLOR: OkLab<f32> = OkLab {
l: 0.773,
a: 0.1131,
b: 0.0,
};
impl OkLab<f32> {
pub fn new(l: f32, a: f32, b: f32) -> Self {
OkLab { l, a, b }
}
pub fn to_srgb(&self) -> Rgb<f32> {
oklab_to_linear_srgb(*self)
}
pub fn from_srgb(c: Rgb<f32>) -> Self {
linear_srgb_to_oklab(c)
}
pub fn to_lrgb(&self) -> Rgb<u8> {
let srgb = self.to_srgb();
Rgb {
r: (to_gamma(srgb.r) * 255.0).round() as u8,
g: (to_gamma(srgb.g) * 255.0).round() as u8,
b: (to_gamma(srgb.b) * 255.0).round() as u8,
}
}
pub fn hue_shift(&self, shift: f32) -> Self {
let hue = self.b.atan2(self.a);
let chroma = (self.a * self.a + self.b * self.b).sqrt();
let hue = (hue + shift) % (2.0 * PI);
let a = chroma * hue.cos();
let b = chroma * hue.sin();
Self { l: self.l, a, b }
}
}
impl<T> Rgb<T> {
pub fn map<U, F: Fn(T) -> U>(self, f: F) -> Rgb<U> {
Rgb {
r: f(self.r),
g: f(self.g),
b: f(self.b),
}
}
}
pub fn linear_srgb_to_oklab(c: Rgb<f32>) -> OkLab<f32> {
let l = 0.4122214708 * c.r + 0.5363325363 * c.g + 0.0514459929 * c.b;
let m = 0.2119034982 * c.r + 0.6806995451 * c.g + 0.1073969566 * c.b;
let s = 0.0883024619 * c.r + 0.2817188376 * c.g + 0.6299787005 * c.b;
let l_ = l.cbrt();
let m_ = m.cbrt();
let s_ = s.cbrt();
OkLab {
l: 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_,
a: 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_,
b: 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_,
}
}
pub fn oklab_to_linear_srgb(c: OkLab<f32>) -> Rgb<f32> {
let l_ = c.l + 0.3963377774 * c.a + 0.2158037573 * c.b;
let m_ = c.l - 0.1055613458 * c.a - 0.0638541728 * c.b;
let s_ = c.l - 0.0894841775 * c.a - 1.2914855480 * c.b;
let l = l_ * l_ * l_;
let m = m_ * m_ * m_;
let s = s_ * s_ * s_;
Rgb {
r: 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
g: -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
b: -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s,
}
}
fn to_gamma(u: f32) -> f32 {
if u >= 0.0031308 {
(1.055) * u.powf(1.0 / 2.4) - 0.055
} else {
12.92 * u
}
}

View File

@@ -7,15 +7,18 @@ edition = "2021"
anyhow = "1.0.86" anyhow = "1.0.86"
bytemuck = "1.16.1" bytemuck = "1.16.1"
clone-macro = "0.1.0" clone-macro = "0.1.0"
const_format = "0.2.32"
eframe = { version = "0.27.2", features = ["wgpu"] } eframe = { version = "0.27.2", features = ["wgpu"] }
egui = "0.27.2" egui = "0.27.2"
egui_dock = "0.12.0" egui_dock = "0.12.0"
egui-phosphor = "0.5.0"
egui-wgpu = "0.27.2" egui-wgpu = "0.27.2"
encase = { version = "0.8.0", features = ["nalgebra"] } encase = { version = "0.8.0", features = ["nalgebra"] }
image = "0.25.1" image = "0.25.1"
nalgebra = "0.32.6" nalgebra = "0.32.6"
parking_lot = "0.12.3" parking_lot = "0.12.3"
plexus = "0.0.11" plexus = "0.0.11"
rand = "0.8.5"
rfd = "0.14.1" rfd = "0.14.1"
tracing = "0.1.40" tracing = "0.1.40"
tracing-subscriber = "0.3.18" tracing-subscriber = "0.3.18"

View File

@@ -2,7 +2,7 @@ use std::sync::Arc;
use anyhow::Result; use anyhow::Result;
use eframe::NativeOptions; use eframe::NativeOptions;
use egui::{IconData, Vec2, ViewportBuilder}; use egui::{FontDefinitions, IconData, Vec2, ViewportBuilder};
use egui_wgpu::WgpuConfiguration; use egui_wgpu::WgpuConfiguration;
use tracing::level_filters::LevelFilter; use tracing::level_filters::LevelFilter;
use tracing_subscriber::{filter, layer::SubscriberExt, util::SubscriberInitExt}; use tracing_subscriber::{filter, layer::SubscriberExt, util::SubscriberInitExt};
@@ -56,6 +56,11 @@ fn main() -> Result<()> {
}, },
Box::new(|cc| { Box::new(|cc| {
render::init_wgpu(cc); render::init_wgpu(cc);
let mut fonts = FontDefinitions::default();
egui_phosphor::add_to_fonts(&mut fonts, egui_phosphor::Variant::Regular);
cc.egui_ctx.set_fonts(fonts);
Box::new(App::default()) Box::new(App::default())
}), }),
) )

View File

@@ -35,9 +35,9 @@ impl Camera {
} }
if response.dragged_by(PointerButton::Secondary) { if response.dragged_by(PointerButton::Secondary) {
self.target -= let facing = self.position().neg().normalize();
self.position().neg().normalize().cross(&Vector3::z_axis()) * drag_delta.x; self.target -= facing.cross(&Vector3::z_axis()) * drag_delta.x;
self.target += Vector3::new(0.0, 0.0, drag_delta.y * 0.5); self.target += facing.cross(&Vector3::x_axis()) * drag_delta.y;
} }
if response.hovered() { if response.hovered() {
@@ -46,7 +46,7 @@ impl Camera {
} }
} }
fn position(&self) -> Vector3<f32> { pub fn position(&self) -> Vector3<f32> {
Vector3::new(self.angle.x.sin(), self.angle.x.cos(), self.angle.y.tan()).normalize() Vector3::new(self.angle.x.sin(), self.angle.x.cos(), self.angle.y.tan()).normalize()
* self.distance * self.distance
} }

View File

@@ -1,6 +1,6 @@
use egui_wgpu::ScreenDescriptor; use egui_wgpu::ScreenDescriptor;
use encase::{ShaderType, UniformBuffer}; use encase::{ShaderType, UniformBuffer};
use nalgebra::Matrix4; use nalgebra::{Matrix4, Vector3, Vector4};
use wgpu::{ use wgpu::{
util::{BufferInitDescriptor, DeviceExt}, util::{BufferInitDescriptor, DeviceExt},
BindGroup, BindGroupEntry, BindGroupLayout, BufferUsages, ColorTargetState, ColorWrites, BindGroup, BindGroupEntry, BindGroupLayout, BufferUsages, ColorTargetState, ColorWrites,
@@ -31,6 +31,9 @@ pub struct ModelPipeline {
struct ModelUniforms { struct ModelUniforms {
transform: Matrix4<f32>, transform: Matrix4<f32>,
model_transform: Matrix4<f32>, model_transform: Matrix4<f32>,
model_color: Vector4<f32>,
camera_position: Vector3<f32>,
camera_target: Vector3<f32>,
render_style: u32, render_style: u32,
} }
@@ -114,6 +117,9 @@ impl Pipeline<WorkspaceRenderCallback> for ModelPipeline {
let uniforms = ModelUniforms { let uniforms = ModelUniforms {
transform: resources.transform * model_transform, transform: resources.transform * model_transform,
model_transform, model_transform,
model_color: model.color.to_normalized_gamma_f32().into(),
camera_position: resources.camera.position(),
camera_target: resources.camera.target,
render_style: resources.render_style as u32, render_style: resources.render_style as u32,
}; };

View File

@@ -1,5 +1,7 @@
use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::atomic::{AtomicU32, Ordering};
use common::oklab::START_COLOR;
use egui::Color32;
use nalgebra::Vector3; use nalgebra::Vector3;
use wgpu::{ use wgpu::{
util::{BufferInitDescriptor, DeviceExt}, util::{BufferInitDescriptor, DeviceExt},
@@ -14,6 +16,7 @@ pub struct RenderedMesh {
pub name: String, pub name: String,
pub id: u32, pub id: u32,
pub mesh: Mesh, pub mesh: Mesh,
pub color: Color32,
pub hidden: bool, pub hidden: bool,
pub locked_scale: bool, pub locked_scale: bool,
@@ -58,9 +61,10 @@ impl RenderedMesh {
} }
Self { Self {
mesh,
id: next_id(),
name: String::new(), name: String::new(),
id: next_id(),
mesh,
color: Color32::WHITE,
hidden: false, hidden: false,
locked_scale: true, locked_scale: true,
vertices, vertices,
@@ -73,6 +77,21 @@ impl RenderedMesh {
self self
} }
pub fn with_random_color(mut self) -> Self {
self.randomize_color();
self
}
pub fn randomize_color(&mut self) -> &mut Self {
let shift = rand::random::<f32>() * std::f32::consts::PI;
let color = START_COLOR
.hue_shift(shift)
.to_srgb()
.map(|x| (x.clamp(0.0, 1.0) * 255.0) as u8);
self.color = Color32::from_rgb(color.r, color.g, color.b);
self
}
pub fn align_to_bed(&mut self) { pub fn align_to_bed(&mut self) {
let (bottom, _) = self.mesh.minmax_point(); let (bottom, _) = self.mesh.minmax_point();
@@ -116,6 +135,7 @@ impl Clone for RenderedMesh {
name: self.name.clone(), name: self.name.clone(),
id: next_id(), id: next_id(),
mesh: self.mesh.clone(), mesh: self.mesh.clone(),
color: self.color,
hidden: self.hidden, hidden: self.hidden,
locked_scale: self.locked_scale, locked_scale: self.locked_scale,
vertices: self.vertices.clone(), vertices: self.vertices.clone(),

View File

@@ -3,6 +3,9 @@
struct Context { struct Context {
transform: mat4x4<f32>, transform: mat4x4<f32>,
model_transform: mat4x4<f32>, model_transform: mat4x4<f32>,
model_color: vec4<f32>,
camera_position: vec3<f32>,
camera_target: vec3<f32>,
render_style: u32, render_style: u32,
} }
@@ -36,8 +39,14 @@ fn frag(in: VertexOutput) -> @location(0) vec4<f32> {
if context.render_style == 0 { if context.render_style == 0 {
return vec4<f32>(in.normal, 1.0); return vec4<f32>(in.normal, 1.0);
} else { } else {
let light_dir = normalize(vec3<f32>(0.0, 0.0, 1.0)); let camera_direction = normalize(context.camera_position + context.camera_target);
let intensity = max(dot(in.normal, light_dir), 0.0);
return vec4<f32>(intensity, intensity, intensity, 1.0); let diffuse = max(dot(in.normal, camera_direction), 0.0);
let reflect_dir = reflect(-camera_direction, in.normal);
let specular = pow(max(dot(camera_direction, reflect_dir), 0.0), 32.0);
let intensity = (diffuse + specular + 0.03) * context.model_color.rgb;
return vec4<f32>(intensity, context.model_color.a);
} }
} }

View File

@@ -1,4 +1,6 @@
use const_format::concatcp;
use egui::{Context, Grid, Id, Ui}; use egui::{Context, Grid, Id, Ui};
use egui_phosphor::regular::DICE_THREE;
use slicer::Pos; use slicer::Pos;
use crate::{ use crate::{
@@ -106,6 +108,14 @@ pub fn ui(app: &mut App, ui: &mut Ui, _ctx: &Context) {
.then(|| mesh.mesh.set_rotation(deg_to_rad(rotation))); .then(|| mesh.mesh.set_rotation(deg_to_rad(rotation)));
ui.end_row(); ui.end_row();
ui.label("Color");
ui.horizontal(|ui| {
ui.color_edit_button_srgba(&mut mesh.color);
ui.button(concatcp!(DICE_THREE, " Random"))
.clicked()
.then(|| mesh.randomize_color());
});
ui.label("Name"); ui.label("Name");
ui.text_edit_singleline(&mut mesh.name); ui.text_edit_singleline(&mut mesh.name);
ui.end_row(); ui.end_row();

View File

@@ -31,9 +31,11 @@ pub fn ui(app: &mut App, ctx: &Context) {
let model = slicer::mesh::load_mesh(&mut buf, &format).unwrap(); let model = slicer::mesh::load_mesh(&mut buf, &format).unwrap();
info!("Loaded model `{name}` with {} faces", model.faces.len()); info!("Loaded model `{name}` with {} faces", model.faces.len());
app.meshes app.meshes.write().push(
.write() RenderedMesh::from_mesh(model)
.push(RenderedMesh::from_mesh(model).with_name(name)); .with_name(name)
.with_random_color(),
);
} }
} }

View File

@@ -57,6 +57,10 @@ impl Segments {
let mut out = Vec::new(); let mut out = Vec::new();
let layer = (height - self.start_height) / self.layer_height; let layer = (height - self.start_height) / self.layer_height;
if layer < 0.0 || layer >= self.layers.len() as f32 {
return out;
}
for &face in self.layers[layer as usize].iter() { for &face in self.layers[layer as usize].iter() {
intersect_triangle(mesh, &self.transformed_points, face, height, &mut out); intersect_triangle(mesh, &self.transformed_points, face, height, &mut out);
} }