init
This commit is contained in:
commit
497799f638
7 changed files with 3181 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/target
|
2807
Cargo.lock
generated
Normal file
2807
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
13
Cargo.toml
Normal file
13
Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "pathtracer_2d"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
eframe = "0.21.3"
|
||||||
|
glam = { version = "0.23.0", features = ["std"] }
|
||||||
|
rand = "0.8.5"
|
||||||
|
rayon = "1.7.0"
|
||||||
|
tokio = {version = "1.27.0", features = ["full"] }
|
215
refrence.glsl
Normal file
215
refrence.glsl
Normal file
|
@ -0,0 +1,215 @@
|
||||||
|
/*
|
||||||
|
uniform vec3 iResolution; // viewport resolution (in pixels)
|
||||||
|
uniform float iTime; // shader playback time (in seconds)
|
||||||
|
uniform float iTimeDelta; // render time (in seconds)
|
||||||
|
uniform float iFrameRate; // shader frame rate
|
||||||
|
uniform int iFrame; // shader playback frame
|
||||||
|
uniform float iChannelTime[4]; // channel playback time (in seconds)
|
||||||
|
uniform vec3 iChannelResolution[4]; // channel resolution (in pixels)
|
||||||
|
uniform vec4 iMouse; // mouse pixel coords. xy: current (if MLB down), zw: click
|
||||||
|
uniform samplerXX iChannel0..3; // input channel. XX = 2D/Cube
|
||||||
|
uniform vec4 iDate; // (year, month, day, time in seconds)
|
||||||
|
uniform float iSampleRate; // sound sample rate (i.e., 44100)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define samples 8
|
||||||
|
|
||||||
|
#define pi 3.141592564
|
||||||
|
|
||||||
|
// Scene definition is kinda inspired by .obj file formatting but idk if I'll keep it
|
||||||
|
|
||||||
|
vec3 materials[] = vec3[](
|
||||||
|
vec3(1.000,1.000,1.000), // white
|
||||||
|
vec3(1.000,0.067,0.157), // red
|
||||||
|
vec3(0.027,0.945,0.259), // green
|
||||||
|
vec3(0.318,0.553,0.992)); // blue
|
||||||
|
|
||||||
|
vec2 points[] = vec2[](
|
||||||
|
vec2(.1,-.25),
|
||||||
|
vec2(.3,-.25),
|
||||||
|
vec2(.1,-.05),
|
||||||
|
vec2(.3,-.05),
|
||||||
|
vec2(-.9,-.4),
|
||||||
|
vec2(.8,-.4),
|
||||||
|
vec2(-.9,-1.),
|
||||||
|
vec2(.8,1.),
|
||||||
|
vec2(-.4,-.3),
|
||||||
|
vec2(-.2,-.3),
|
||||||
|
vec2(-.4,-.1),
|
||||||
|
vec2(-.2,-.1),
|
||||||
|
vec2(-.05,-.05),
|
||||||
|
vec2(-.05,-.15),
|
||||||
|
vec2(0,-.1),
|
||||||
|
vec2(-.1,-.1));
|
||||||
|
|
||||||
|
int segmentCount = 15;
|
||||||
|
ivec3 segments[] = ivec3[](
|
||||||
|
ivec3(0,1,1), // ivec3(a,b,c)
|
||||||
|
ivec3(0,2,1), // a = endpoint a index
|
||||||
|
ivec3(1,3,1), // b = endpoint b index
|
||||||
|
ivec3(2,3,1), // c = material index
|
||||||
|
ivec3(4,5,0),
|
||||||
|
ivec3(4,6,0),
|
||||||
|
ivec3(5,7,0),
|
||||||
|
ivec3(8,9,3),
|
||||||
|
ivec3(8,10,3),
|
||||||
|
ivec3(9,11,3),
|
||||||
|
ivec3(10,11,3),
|
||||||
|
ivec3(12,14,2),
|
||||||
|
ivec3(14,13,2),
|
||||||
|
ivec3(13,15,2),
|
||||||
|
ivec3(15,12,2));
|
||||||
|
|
||||||
|
// https://www.shadertoy.com/view/4djSRW
|
||||||
|
vec2 hash21(float p) {
|
||||||
|
vec3 p3 = fract(vec3(p) * vec3(.1031, .1030, .0973));
|
||||||
|
p3 += dot(p3, p3.yzx + 33.33);
|
||||||
|
return fract((p3.xx+p3.yz)*p3.zy);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interleaved gradient noise
|
||||||
|
// https://blog.demofox.org/2022/01/01/interleaved-gradient-noise-a-different-kind-of-low-discrepancy-sequence/
|
||||||
|
float IGN(ivec2 p) {
|
||||||
|
return mod(52.9829189f * mod(.06711056f*float(p.x) + .00583715f*float(p.y), 1.), 1.);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ray intersection with line segment
|
||||||
|
float segmentIntersect(vec2 ro, vec2 rd, vec2 a, vec2 b) {
|
||||||
|
vec2 v1 = ro - a;
|
||||||
|
vec2 v2 = b - a;
|
||||||
|
vec2 v3 = vec2(-rd.y, rd.x);
|
||||||
|
|
||||||
|
float d = dot(v2, v3);
|
||||||
|
float t1 = cross(vec3(v2,0), vec3(v1,0)).z / d;
|
||||||
|
float t2 = dot(v1, v3) / d;
|
||||||
|
|
||||||
|
if (t1 >= 0.0 && (t2 >= 0.0 && t2 <= 1.0)) {
|
||||||
|
return t1;
|
||||||
|
}
|
||||||
|
return 1000.;
|
||||||
|
}
|
||||||
|
|
||||||
|
//ray intersection with scene
|
||||||
|
//sceneIntersect.w is the distance, sceneIntersect.xyz is the color
|
||||||
|
vec4 sceneIntersect(vec2 ro, vec2 rd) {
|
||||||
|
float v0 = 1000.;
|
||||||
|
vec3 col;
|
||||||
|
|
||||||
|
for(int i=0; i<segmentCount; i++) {
|
||||||
|
|
||||||
|
vec2 a = points[segments[i].x];
|
||||||
|
vec2 b = points[segments[i].y];
|
||||||
|
|
||||||
|
float v1 = segmentIntersect(ro, rd, a, b);
|
||||||
|
if(v1<v0) {
|
||||||
|
col = materials[segments[i].z];
|
||||||
|
v0 = v1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vec4(col,v0);
|
||||||
|
}
|
||||||
|
|
||||||
|
//line segment SDF
|
||||||
|
float line(vec2 p, vec2 a,vec2 b) {
|
||||||
|
p -= a, b -= a;
|
||||||
|
float h = clamp(dot(p, b) / dot(b, b), 0., 1.);
|
||||||
|
return length(p - b * h);
|
||||||
|
}
|
||||||
|
|
||||||
|
//scene SDF
|
||||||
|
//sceneDist.w is the distance, sceneDist.xyz is the color
|
||||||
|
vec4 sceneDist(vec2 p) {
|
||||||
|
float v0 = 1000.;
|
||||||
|
vec3 col;
|
||||||
|
|
||||||
|
for(int i=0; i<segmentCount; i++) {
|
||||||
|
|
||||||
|
vec2 a = points[segments[i].x];
|
||||||
|
vec2 b = points[segments[i].y];
|
||||||
|
|
||||||
|
float v1 = line(p, a, b);
|
||||||
|
if(v1<v0) {
|
||||||
|
col = materials[segments[i].z];
|
||||||
|
v0 = v1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vec4(col,v0);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 sceneNormal(vec2 p) {
|
||||||
|
vec2 epsilon = vec2(.001, -.001);
|
||||||
|
return normalize(vec2(sceneDist(p+epsilon.xx).w) - vec2(sceneDist(p-epsilon.xy).w,sceneDist(p-epsilon.yx).w));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ACES Tonemapping
|
||||||
|
vec3 ACESFilm(vec3 x) {
|
||||||
|
|
||||||
|
float a = 2.51f;
|
||||||
|
float b = 0.03f;
|
||||||
|
float c = 2.43f;
|
||||||
|
float d = 0.59f;
|
||||||
|
float e = 0.14f;
|
||||||
|
return clamp((x*(a*x + b)) / (x*(c*x + d) + e), 0.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define lightFalloff 2.
|
||||||
|
void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
|
||||||
|
|
||||||
|
vec3 col;
|
||||||
|
vec2 p = (2.*fragCoord-iResolution.xy-.5)/iResolution.x;
|
||||||
|
float rand = IGN(ivec2(fragCoord.xy));
|
||||||
|
|
||||||
|
vec3 spot;
|
||||||
|
vec3 gi;
|
||||||
|
|
||||||
|
vec2 lightPos = vec2(sin(iTime*.5)*.75,cos(iTime*.25)*.25+.25);
|
||||||
|
|
||||||
|
vec2 lightDir = normalize(vec2(sin(iTime*1.5),-1));
|
||||||
|
if (iMouse.z > 0.){
|
||||||
|
lightPos = vec2(2,-2)*iMouse.zw/iResolution.x-vec2(1.,.56);
|
||||||
|
lightDir = normalize(2.*iMouse.xy/iResolution.x-vec2(1.,.561)-lightPos);
|
||||||
|
}
|
||||||
|
float lightRad = .005;
|
||||||
|
|
||||||
|
if (sceneIntersect(p, normalize(lightPos-p)).w > distance(p,lightPos)) {
|
||||||
|
spot = vec3(max((.5*float(dot(normalize(p-lightPos),lightDir))-.5)/lightRad+1.,0. ));
|
||||||
|
}
|
||||||
|
|
||||||
|
vec2 hit;
|
||||||
|
for (int i=0; i<samples; i++) {
|
||||||
|
vec2 ro = lightPos;
|
||||||
|
float rot = .08*pi*((float(i)+rand)/float(samples)-.5) + atan(lightDir.y,lightDir.x);
|
||||||
|
vec2 rd = vec2(cos(rot),sin(rot));
|
||||||
|
vec2 lightDirSampled = rd;
|
||||||
|
|
||||||
|
float d = sceneIntersect(ro, rd).w;
|
||||||
|
hit = ro + rd*d;
|
||||||
|
vec2 nor = sceneNormal(hit - rd*.01);
|
||||||
|
|
||||||
|
ro = p;
|
||||||
|
rd = normalize(hit-p);
|
||||||
|
|
||||||
|
// Circle arc for bounce light falloff just beause I thought it looked better than inverse square law :p
|
||||||
|
float hitDist = min(distance(p,hit)/lightFalloff,1.);
|
||||||
|
|
||||||
|
vec4 lightRay = sceneIntersect(ro, rd);
|
||||||
|
d = lightRay.w;
|
||||||
|
|
||||||
|
if (d + .01 > distance(p,hit)) {
|
||||||
|
gi += 1./float(samples) * lightRay.rgb * clamp(dot(-rd,nor),0.,1.) * ( 1.-sqrt(2.*hitDist-hitDist*hitDist) )
|
||||||
|
* (sceneDist(p).w > .005 ? 1. : dot(sceneNormal(p),lightDirSampled)*.5+.5 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 scene = sceneDist(p);
|
||||||
|
col = spot*.5 + gi*1.;
|
||||||
|
col *= scene.w > .005 ? vec3(.25) : 3.*scene.rgb;
|
||||||
|
|
||||||
|
// Tonemapping
|
||||||
|
|
||||||
|
col = ACESFilm(col);
|
||||||
|
col = pow(col,vec3(1./2.2));
|
||||||
|
|
||||||
|
fragColor = vec4(col,1);
|
||||||
|
}
|
65
src/main.rs
Normal file
65
src/main.rs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
use eframe::{
|
||||||
|
egui::{self, Frame},
|
||||||
|
CreationContext,
|
||||||
|
};
|
||||||
|
use path_tracer::PathTracer;
|
||||||
|
use tokio::runtime;
|
||||||
|
mod path_tracer;
|
||||||
|
fn main() {
|
||||||
|
let mut options = eframe::NativeOptions::default();
|
||||||
|
eframe::run_native(
|
||||||
|
"2D Pathtracer",
|
||||||
|
options,
|
||||||
|
Box::new(|ctx| Box::new(PathTracerWindow::new(ctx))),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
struct PathTracerWindow {
|
||||||
|
runtime: runtime::Runtime,
|
||||||
|
started: bool,
|
||||||
|
path_tracer: Option<PathTracer>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PathTracerWindow {
|
||||||
|
fn new(ctx: &CreationContext) -> Self {
|
||||||
|
Self {
|
||||||
|
runtime: runtime::Builder::new_multi_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
started: false,
|
||||||
|
path_tracer: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl eframe::App for PathTracerWindow {
|
||||||
|
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||||
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
if ui.button("Render!").clicked() {
|
||||||
|
self.started = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Frame::canvas(ui.style()).show(ui, |ui| {
|
||||||
|
if !self.started {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ui.ctx().request_repaint();
|
||||||
|
let time = ui.input(|i| i.time);
|
||||||
|
let (_rect_id, rect) = ui.allocate_space(ui.available_size());
|
||||||
|
|
||||||
|
if let Some(path_tracer) = &mut self.path_tracer {
|
||||||
|
ui.image(
|
||||||
|
&path_tracer.texture_handle,
|
||||||
|
path_tracer.texture_handle.size_vec2(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
self.path_tracer = Some(PathTracer::new(
|
||||||
|
ui,
|
||||||
|
[rect.size().x as usize, rect.size().y as usize],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
79
src/path_tracer.rs
Normal file
79
src/path_tracer.rs
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
pub mod common;
|
||||||
|
use eframe::egui;
|
||||||
|
use glam::*;
|
||||||
|
use rand::prelude::random;
|
||||||
|
use rayon::prelude::*;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
pub struct PathTracer {
|
||||||
|
pub texture_handle: egui::TextureHandle,
|
||||||
|
texture: egui::ColorImage,
|
||||||
|
next_texture: egui::ColorImage,
|
||||||
|
render_size: [usize; 2],
|
||||||
|
max_bounces: u8,
|
||||||
|
}
|
||||||
|
impl PathTracer {
|
||||||
|
pub fn new(ui: &mut eframe::egui::Ui, size: [usize; 2]) -> Self {
|
||||||
|
let texture = egui::ColorImage::new(size, egui::Color32::DARK_GRAY);
|
||||||
|
let texture_handle = ui.ctx().load_texture(
|
||||||
|
"render",
|
||||||
|
texture.to_owned(),
|
||||||
|
egui::TextureOptions::default(),
|
||||||
|
);
|
||||||
|
Self {
|
||||||
|
texture_handle,
|
||||||
|
texture,
|
||||||
|
next_texture: texture.clone(),
|
||||||
|
render_size: size,
|
||||||
|
max_bounces: 7,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn resize(&mut self, size: [usize; 2]) {
|
||||||
|
self.render_size = size
|
||||||
|
}
|
||||||
|
pub fn render(&mut self) {
|
||||||
|
tokio::spawn(async move { self.cast_rows() });
|
||||||
|
}
|
||||||
|
async fn cast_rows(&mut self) {
|
||||||
|
let width = self.next_texture.width();
|
||||||
|
self.next_texture
|
||||||
|
.pixels
|
||||||
|
.chunks_exact_mut(width)
|
||||||
|
.par_bridge()
|
||||||
|
.for_each(|pixel_row| {
|
||||||
|
for pixel in pixel_row {
|
||||||
|
pixel.from_pixel()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trait RayCast {
|
||||||
|
fn from_pixel(&mut self);
|
||||||
|
}
|
||||||
|
impl RayCast for egui::Color32 {
|
||||||
|
fn from_pixel(&mut self) {}
|
||||||
|
}
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct Ray {
|
||||||
|
origin: Vec2,
|
||||||
|
direction: Vec2,
|
||||||
|
}
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct Material {
|
||||||
|
mat_type: MaterialType,
|
||||||
|
color: Vec3,
|
||||||
|
}
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum MaterialType {
|
||||||
|
Dialectric,
|
||||||
|
Lambertian,
|
||||||
|
Light,
|
||||||
|
}
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct Circle {
|
||||||
|
origin: Vec2,
|
||||||
|
radius: f32,
|
||||||
|
material: Material,
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- INTERSECTION HITS --- */
|
1
src/path_tracer/common.rs
Normal file
1
src/path_tracer/common.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
|
Loading…
Reference in a new issue