This commit is contained in:
djkato 2023-05-02 14:35:48 +02:00
commit 497799f638
7 changed files with 3181 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

2807
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

13
Cargo.toml Normal file
View 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
View 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
View 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
View 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 --- */

View file

@ -0,0 +1 @@