Revamp model lighting
This commit is contained in:
32
Cargo.lock
generated
32
Cargo.lock
generated
@@ -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",
|
||||||
|
@@ -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
107
common/src/oklab.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
@@ -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"
|
||||||
|
@@ -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())
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
|
@@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -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(),
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -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();
|
||||||
|
@@ -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(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user