diff --git a/build/_esbuild-config.mjs b/build/_esbuild-config.mjs index 791780d..291274d 100644 --- a/build/_esbuild-config.mjs +++ b/build/_esbuild-config.mjs @@ -14,10 +14,11 @@ export const get_esbuild_config = (outdir, prod) => { return ({ entryPoints: { - index: project_path('src/js/index.ts'), - render_worker: project_path('src/js/render_worker.ts'), - coi: project_path('src/js/coi-service-worker.js'), - styles: project_path('src/js/main.css') + styles: project_path('src/js/main.css'), + render: project_path('src/js/entry-points/render.ts'), + select_scene: project_path('src/js/entry-points/select_scene.ts'), + render_worker: project_path('src/js/entry-points/render_worker.ts'), + coi: project_path('src/js/coi-service-worker.js') }, bundle: true, sourcemap: dev, diff --git a/build/static-files.mjs b/build/static-files.mjs index 1ae412b..913d309 100644 --- a/build/static-files.mjs +++ b/build/static-files.mjs @@ -29,12 +29,13 @@ export const prepareSingleFile = async (basePath, filePath) => { export const createPrepareFile = (pathFallbackChain) => async (url) => { let preparation = null; - for (const basePath of pathFallbackChain) { - const filePath = url.endsWith("/") - ? path.join(basePath, url, 'index.html') - : path.join(basePath, url); + for (const base_file_path of pathFallbackChain) { + const url_path = new URL(url, 'http://dummy').pathname; + const file_path = url.endsWith("/") + ? path.join(base_file_path, url_path, 'index.html') + : path.join(base_file_path, url_path); - preparation = await prepareSingleFile(basePath, filePath); + preparation = await prepareSingleFile(base_file_path, file_path); if (preparation.found) { return preparation; diff --git a/src/js/index.ts b/src/js/entry-points/render.ts similarity index 68% rename from src/js/index.ts rename to src/js/entry-points/render.ts index edce0f9..625c807 100644 --- a/src/js/index.ts +++ b/src/js/entry-points/render.ts @@ -1,10 +1,10 @@ -import { create_array_writer, create_canvas_color_writer } from './ui/color-writers'; -import { single_threaded_render } from './single_threaded_render'; -import { multi_threaded_render } from './multi_threaded_render'; -import { generate_random_permutation_u16, generate_straight_order_u16 } from './utils'; -import { ConsoleProgressReporter, MultipleReporters, ProgressBar, ProgressText } from './progress-reporters'; -import { ACES, apply_gamma, clip_to_unit_range, compose_color_flow, expose } from './color-flow'; -import { select_model_ui } from './ui/select_model_ui'; +import { create_array_writer, create_canvas_color_writer } from '../ui/color-writers'; +import { single_threaded_render } from '../single_threaded_render'; +import { multi_threaded_render } from '../multi_threaded_render'; +import { generate_random_permutation_u16, generate_straight_order_u16 } from '../utils'; +import { ConsoleProgressReporter, MultipleReporters, ProgressBar, ProgressText } from '../progress-reporters'; +import { ACES, apply_gamma, clip_to_unit_range, compose_color_flow, expose } from '../color-flow'; +import { scenes } from '../scenes/index'; const aspect_ratio = 1; const image_width = 840; @@ -16,7 +16,25 @@ const thread_count = globalThis?.navigator?.hardwareConcurrency ? globalThis?.navigator?.hardwareConcurrency - 1 : 4; -const scene = await select_model_ui(document.getElementById('top-row') as HTMLDivElement); +const params = new URLSearchParams(location.search); +const scene_index_str = params.get('scene'); +if (scene_index_str === null) { + throw new Error(`No scene index`); +} +const scene_index = parseInt(scene_index_str); +if (Number.isNaN(scene_index)) { + throw new Error(`Bad scene index.`); +} +const scene_tuple = scenes[scene_index]; +if (scene_tuple === undefined) { + throw new Error(`No such scene index ${scene_index}`); +} + +const [scene_name, _scene_tag, load_scene] = scene_tuple; + +document.title = scene_name + ' - Ray tracing in one weekend.' + +const scene = await load_scene(); const progress_reporter = new MultipleReporters([ // new ConsoleProgressReporter(image_height, thread_count), diff --git a/src/js/render_worker.ts b/src/js/entry-points/render_worker.ts similarity index 89% rename from src/js/render_worker.ts rename to src/js/entry-points/render_worker.ts index 8de4541..71edfa6 100644 --- a/src/js/render_worker.ts +++ b/src/js/entry-points/render_worker.ts @@ -1,13 +1,13 @@ -import { add_vec3_r, ArenaVec3Allocator, use_vec3_allocator } from './math/vec3.gen'; -import { ray_color } from './ray_color'; -import { RenderWorkerParametersMessage } from './types'; -import { ArenaQuatAllocator, use_quat_allocator } from './math/quat.gen'; -import { run_with_hooks } from './utils'; -import { configure_camera, get_ray } from './camera'; +import { add_vec3_r, ArenaVec3Allocator, use_vec3_allocator } from '../math/vec3.gen'; +import { ray_color } from '../ray_color'; +import { RenderWorkerParametersMessage } from '../types'; +import { ArenaQuatAllocator, use_quat_allocator } from '../math/quat.gen'; +import { run_with_hooks } from '../utils'; +import { configure_camera, get_ray } from '../camera'; -import './hittable'; -import './materials'; -import './texture'; +import '../hittable'; +import '../materials'; +import '../texture'; export interface RenderWorkerMessageData { y: number; diff --git a/src/js/entry-points/select_scene.ts b/src/js/entry-points/select_scene.ts new file mode 100644 index 0000000..f1500f4 --- /dev/null +++ b/src/js/entry-points/select_scene.ts @@ -0,0 +1,21 @@ +import { scenes } from '../scenes/index'; + +const render_links = (id: string, tag: string) => { + const book1_scenes = document.getElementById(id) as HTMLUListElement; + + book1_scenes.innerHTML = scenes + .map(([name, tag_name], i) => { + if (tag_name === tag) { + return `
  • ${name}
  • `; + } + return null; + }) + .filter(str => str !== null) + .join(''); + +} + +render_links('book1_scenes', 'book1'); +render_links('book2_scenes', 'book2'); +render_links('book3_scenes', 'book3'); +render_links('gltf_scenes', 'gltf'); diff --git a/src/js/main.css b/src/js/main.css index 95a8cda..95905a6 100644 --- a/src/js/main.css +++ b/src/js/main.css @@ -16,6 +16,10 @@ body { gap: 12px; } +a { + color: #4493f8; +} + #top-row { flex: 0 0 auto; padding: 0 12px; diff --git a/src/js/multi_threaded_render.ts b/src/js/multi_threaded_render.ts index 87268f8..56e2d98 100644 --- a/src/js/multi_threaded_render.ts +++ b/src/js/multi_threaded_render.ts @@ -1,6 +1,6 @@ import { RenderParameters, RenderWorkerParametersMessage } from './types'; import { ColorWriter } from './ui/color-writers'; -import { RenderWorkerMessageData } from './render_worker'; +import { RenderWorkerMessageData } from './entry-points/render_worker'; import { color } from './math/vec3.gen'; import { ProgressReporter } from './progress-reporters'; import { ColorFlowItem } from './color-flow'; diff --git a/src/js/scenes/book-1-final-scene.ts b/src/js/scenes/book-1-final-scene.ts index 5ca0652..139c80b 100644 --- a/src/js/scenes/book-1-final-scene.ts +++ b/src/js/scenes/book-1-final-scene.ts @@ -1,11 +1,16 @@ import { - add_vec3, ArenaVec3Allocator, + add_vec3, + ArenaVec3Allocator, len_vec3, - point3, rand_vec3, rand_vec3_min_max, sub_vec3, use_vec3_allocator, + point3, + rand_vec3, + rand_vec3_min_max, + sub_vec3, + use_vec3_allocator, vec3 } from '../math/vec3.gen'; import { create_checker_3d_texture } from '../texture/checker_3d_texture'; -import { solid_color, create_solid_color } from '../texture/solid_color'; +import { create_solid_color, solid_color } from '../texture/solid_color'; import { random, random_min_max } from '../math/random'; import { create_camera } from '../camera'; import { create_scene, Scene } from './scene'; @@ -73,7 +78,7 @@ function create_lots_of_spheres(): Hittable { }); } -export const book1_final_scene = (): Scene => create_scene({ +export const create = (): Scene => create_scene({ camera: create_camera({ look_from: point3(13, 2, 3), look_at: point3(0, 0, 0), diff --git a/src/js/scenes/book-2-final-scene.ts b/src/js/scenes/book-2-final-scene.ts index e19f342..df91f2f 100644 --- a/src/js/scenes/book-2-final-scene.ts +++ b/src/js/scenes/book-2-final-scene.ts @@ -1,12 +1,5 @@ import { create_scene, Scene } from './scene'; -import { - add_vec3, - ArenaVec3Allocator, - point3, - rand_vec3_min_max, - use_vec3_allocator, - vec3 -} from '../math/vec3.gen'; +import { add_vec3, ArenaVec3Allocator, point3, rand_vec3_min_max, use_vec3_allocator, vec3 } from '../math/vec3.gen'; import { solid_color } from '../texture/solid_color'; import { random_min_max } from '../math/random'; import earthUrl from './earthmap.jpg'; @@ -34,7 +27,7 @@ import { create_bvh_node } from '../hittable/bvh'; import { create_image_texture } from '../texture/image_texture'; import { create_noise_texture } from '../texture/noise_texture'; -export const book2_final_scene = async (): Promise => { +export const create = async (): Promise => { return async_run_with_hooks(async (): Promise => { use_vec3_allocator(new ArenaVec3Allocator(1024 * 6)); @@ -47,23 +40,23 @@ export const book2_final_scene = async (): Promise => { const y0 = 0.0; const boxes1 = create_zx_grid(boxes_per_side, boxes_per_side, 101, w, point3(-1000, 0, -1000)); for (let i = 0; i < boxes_per_side; i++) { - const x0 = -1000.0 + i*w; + const x0 = -1000.0 + i * w; const x1 = x0 + w; for (let j = 0; j < boxes_per_side; j++) { - const z0 = -1000.0 + j*w; - const y1 = random_min_max(1,101); + const z0 = -1000.0 + j * w; + const y1 = random_min_max(1, 101); const z1 = z0 + w; - zx_grid_add_hittable(boxes1, i, j, create_box(point3(x0,y0,z0), point3(x1,y1,z1), ground)); + zx_grid_add_hittable(boxes1, i, j, create_box(point3(x0, y0, z0), point3(x1, y1, z1), ground)); } } objects.objects.push(boxes1); const light = create_diffuse_light(solid_color(7, 7, 7)); - const light_hittable = create_quad(point3(123, 554, 147), vec3(300,0,0), vec3(0,0,265), light); + const light_hittable = create_quad(point3(123, 554, 147), vec3(300, 0, 0), vec3(0, 0, 265), light); objects.objects.push(light_hittable); const center1 = point3(400, 400, 200); - const center2 = add_vec3(center1, vec3(30,0,0)); + const center2 = add_vec3(center1, vec3(30, 0, 0)); const moving_sphere_material = create_lambertian(solid_color(0.7, 0.3, 0.1)); objects.objects.push(create_moving_sphere(center1, center2, 0, 1, 50, moving_sphere_material)); @@ -73,16 +66,19 @@ export const book2_final_scene = async (): Promise => { point3(0, 150, 145), 50, create_metal(solid_color(0.8, 0.8, 0.9), 1.0) )); - const subsurface_scattering_sphere = create_sphere(point3(360,150,145), 70, create_dielectric(1.5)); + const subsurface_scattering_sphere = create_sphere(point3(360, 150, 145), 70, create_dielectric(1.5)); objects.objects.push(subsurface_scattering_sphere); objects.objects.push(create_constant_medium(subsurface_scattering_sphere, 0.2, create_isotropic_phase_function(solid_color(0.2, 0.4, 0.9)))); const fog_boundary = create_sphere(point3(0, 0, 0), 5000, create_dielectric(1.5)); objects.objects.push(create_constant_medium(fog_boundary, .0001, create_isotropic_phase_function(solid_color(1, 1, 1)))); - const emat = create_lambertian(create_image_texture(await load_dom_image(earthUrl), {flip_y: true, decode_srgb: true})); - objects.objects.push(create_sphere(point3(400,200,400), 100, emat)); + const emat = create_lambertian(create_image_texture(await load_dom_image(earthUrl), { + flip_y: true, + decode_srgb: true + })); + objects.objects.push(create_sphere(point3(400, 200, 400), 100, emat)); const pertext = create_noise_texture(0.2); - objects.objects.push(create_sphere(point3(220,280,300), 80, create_lambertian(pertext))); + objects.objects.push(create_sphere(point3(220, 280, 300), 80, create_lambertian(pertext))); const spheres: Hittable[] = []; const white = create_lambertian(solid_color(.73, .73, .73)); @@ -93,7 +89,7 @@ export const book2_final_scene = async (): Promise => { objects.objects.push(create_transform( trs_to_mat3x4( - vec3(-100,270,395), + vec3(-100, 270, 395), axis_angle_to_quat(vec3(0, 1, 0), degrees_to_radians(15)), vec3(1, 1, 1) ), diff --git a/src/js/scenes/cornell_box.ts b/src/js/scenes/cornell_box.ts index 1fe26db..8417168 100644 --- a/src/js/scenes/cornell_box.ts +++ b/src/js/scenes/cornell_box.ts @@ -16,70 +16,72 @@ import { create_box } from '../hittable/box'; import { create_sphere } from '../hittable/sphere'; import { create_hittable_list } from '../hittable/hittable_list'; -const hittables = run_with_hooks(() => { - use_vec3_allocator(new ArenaVec3Allocator(128)); +export const create = (): Scene => { + const hittables = run_with_hooks(() => { + use_vec3_allocator(new ArenaVec3Allocator(128)); - const aluminum = create_metal(solid_color(0.8, 0.85, 0.88), 0); - const glass = create_dielectric(1.5); - const red = create_lambertian(solid_color(.65, .05, .05)); - const white = create_lambertian(solid_color(.73, .73, .73)); - const green = create_lambertian(solid_color(.12, .45, .15)); - const light = create_diffuse_light(solid_color(15, 15, 15)); + const aluminum = create_metal(solid_color(0.8, 0.85, 0.88), 0); + const glass = create_dielectric(1.5); + const red = create_lambertian(solid_color(.65, .05, .05)); + const white = create_lambertian(solid_color(.73, .73, .73)); + const green = create_lambertian(solid_color(.12, .45, .15)); + const light = create_diffuse_light(solid_color(15, 15, 15)); - const light_hittable = create_quad(point3(343, 554, 332), vec3(-130,0,0), vec3(0,0,-105), light); + const light_hittable = create_quad(point3(343, 554, 332), vec3(-130,0,0), vec3(0,0,-105), light); - const metal_box = create_transform( - trs_to_mat3x4( - vec3(265, 0, 295), - axis_angle_to_quat(vec3(0, 1, 0), degrees_to_radians(15)), - vec3(1, 1, 1) - ), - create_box(point3(0, 0, 0), point3(165, 330, 165), aluminum) - ); + const metal_box = create_transform( + trs_to_mat3x4( + vec3(265, 0, 295), + axis_angle_to_quat(vec3(0, 1, 0), degrees_to_radians(15)), + vec3(1, 1, 1) + ), + create_box(point3(0, 0, 0), point3(165, 330, 165), aluminum) + ); - const glass_sphere = create_sphere(point3(190,90,190), 90, glass); - const root = create_hittable_list([ - create_quad(point3(555, 0, 0), vec3(0, 555, 0), vec3(0, 0, 555), green), - create_quad(point3(0, 0, 0), vec3(0, 555, 0), vec3(0, 0, 555), red), - light_hittable, - create_quad(point3(0, 555, 0), vec3(555, 0, 0), vec3(0, 0, 555), white), - create_quad(point3(0, 0, 0), vec3(555, 0, 0), vec3(0, 0, 555), white), - create_quad(point3(0, 0, 555), vec3(555, 0, 0), vec3(0, 555, 0), white), - - metal_box, - glass_sphere, - // create_transform( - // trs_to_mat3x4( - // vec3(130, 0, 65), - // axis_angle_to_quat(vec3(0, 1, 0), degrees_to_radians(-18)), - // vec3(1, 1 ,1) - // ), - // create_box(point3(0, 0, 0), point3(165, 165, 165), white) - // ) - ]); - - return { - root, - light: create_hittable_list([ + const glass_sphere = create_sphere(point3(190,90,190), 90, glass); + const root = create_hittable_list([ + create_quad(point3(555, 0, 0), vec3(0, 555, 0), vec3(0, 0, 555), green), + create_quad(point3(0, 0, 0), vec3(0, 555, 0), vec3(0, 0, 555), red), light_hittable, + create_quad(point3(0, 555, 0), vec3(555, 0, 0), vec3(0, 0, 555), white), + create_quad(point3(0, 0, 0), vec3(555, 0, 0), vec3(0, 0, 555), white), + create_quad(point3(0, 0, 555), vec3(555, 0, 0), vec3(0, 555, 0), white), + + metal_box, glass_sphere, - metal_box - ]) - }; -}); + // create_transform( + // trs_to_mat3x4( + // vec3(130, 0, 65), + // axis_angle_to_quat(vec3(0, 1, 0), degrees_to_radians(-18)), + // vec3(1, 1 ,1) + // ), + // create_box(point3(0, 0, 0), point3(165, 165, 165), white) + // ) + ]); + + return { + root, + light: create_hittable_list([ + light_hittable, + glass_sphere, + metal_box + ]) + }; + }); -export const cornell_box: Scene = create_scene({ - root_hittable: hittables.root, - light: hittables.light, - camera: create_camera({ - look_from: point3(278, 278, -800), - look_at: point3(278, 278, 0), - v_up: vec3(0, 1, 0), - focus_dist: 10, - aperture: 0, - y_fov: 40, - time0: 0, - time1: 1 - }), - background: Skybox.create_black() -}); + return create_scene({ + root_hittable: hittables.root, + light: hittables.light, + camera: create_camera({ + look_from: point3(278, 278, -800), + look_at: point3(278, 278, 0), + v_up: vec3(0, 1, 0), + focus_dist: 10, + aperture: 0, + y_fov: 40, + time0: 0, + time1: 1 + }), + background: Skybox.create_black() + }); +}; diff --git a/src/js/scenes/cornell_box_with_smoke.ts b/src/js/scenes/cornell_box_with_smoke.ts index e3fe0e9..e3ab1dd 100644 --- a/src/js/scenes/cornell_box_with_smoke.ts +++ b/src/js/scenes/cornell_box_with_smoke.ts @@ -1,4 +1,4 @@ -import { create_scene, Scene } from './scene'; +import { create_scene } from './scene'; import { create_camera } from '../camera'; import { solid_color } from '../texture/solid_color'; import { ArenaVec3Allocator, point3, use_vec3_allocator, vec3 } from '../math/vec3.gen'; @@ -15,63 +15,65 @@ import { create_constant_medium } from '../hittable/constant_medium'; import { create_transform } from '../hittable/transform'; import { create_box } from '../hittable/box'; -const red = create_lambertian(solid_color(.65, .05, .05)); -const white = create_lambertian(solid_color(.73, .73, .73)); -const green = create_lambertian(solid_color(.12, .45, .15)); -const light = create_diffuse_light(solid_color(7, 7, 7)); +export const create = () => { + const red = create_lambertian(solid_color(.65, .05, .05)); + const white = create_lambertian(solid_color(.73, .73, .73)); + const green = create_lambertian(solid_color(.12, .45, .15)); + const light = create_diffuse_light(solid_color(7, 7, 7)); -const light_hittable = create_quad(point3(113,554,127), vec3(330,0,0), vec3(0,0,305), light); + const light_hittable = create_quad(point3(113,554,127), vec3(330,0,0), vec3(0,0,305), light); -export const cornell_box_with_smoke: Scene = run_with_hooks(() => { - use_vec3_allocator(new ArenaVec3Allocator(1024)); - return create_scene({ - root_hittable: create_bvh_node([ - create_quad(point3(555,0,0), vec3(0,555,0), vec3(0,0,555), green), - create_quad(point3(0,0,0), vec3(0,555,0), vec3(0,0,555), red), - light_hittable, - create_quad(point3(0,555,0), vec3(555,0,0), vec3(0,0,555), white), - create_quad(point3(0,0,0), vec3(555,0,0), vec3(0,0,555), white), - create_quad(point3(0,0,555), vec3(555,0,0), vec3(0,555,0), white), + return run_with_hooks(() => { + use_vec3_allocator(new ArenaVec3Allocator(1024)); + return create_scene({ + root_hittable: create_bvh_node([ + create_quad(point3(555, 0, 0), vec3(0, 555, 0), vec3(0, 0, 555), green), + create_quad(point3(0, 0, 0), vec3(0, 555, 0), vec3(0, 0, 555), red), + light_hittable, + create_quad(point3(0, 555, 0), vec3(555, 0, 0), vec3(0, 0, 555), white), + create_quad(point3(0, 0, 0), vec3(555, 0, 0), vec3(0, 0, 555), white), + create_quad(point3(0, 0, 555), vec3(555, 0, 0), vec3(0, 555, 0), white), - create_constant_medium( - create_transform( - trs_to_mat3x4( - vec3(265, 0, 295), - axis_angle_to_quat(vec3(0, 1, 0), degrees_to_radians(15)), - vec3(1, 1, 1) + create_constant_medium( + create_transform( + trs_to_mat3x4( + vec3(265, 0, 295), + axis_angle_to_quat(vec3(0, 1, 0), degrees_to_radians(15)), + vec3(1, 1, 1) + ), + create_box(point3(0, 0, 0), point3(165, 330, 165), white), ), - create_box(point3(0, 0, 0), point3(165, 330, 165), white), + 0.01, + create_isotropic_phase_function(solid_color(0, 0, 0)) ), - 0.01, - create_isotropic_phase_function(solid_color(0, 0, 0)) - ), - create_constant_medium( - create_transform( - trs_to_mat3x4( - vec3(130, 0, 65), - axis_angle_to_quat(vec3(0, 1, 0), degrees_to_radians(-18)), - vec3(1, 1, 1) + create_constant_medium( + create_transform( + trs_to_mat3x4( + vec3(130, 0, 65), + axis_angle_to_quat(vec3(0, 1, 0), degrees_to_radians(-18)), + vec3(1, 1, 1) + ), + create_box(point3(0, 0, 0), point3(165, 165, 165), white) ), - create_box(point3(0, 0, 0), point3(165, 165, 165), white) - ), - 0.01, - create_isotropic_phase_function(solid_color(1, 1, 1)) - ) - ], 0, 1), + 0.01, + create_isotropic_phase_function(solid_color(1, 1, 1)) + ) + ], 0, 1), - light: light_hittable, + light: light_hittable, - camera: create_camera({ - look_from: point3(278, 278, -800), - look_at: point3(278, 278, 0), - v_up: vec3(0, 1, 0), - focus_dist: 10, - aperture: 0, - y_fov: 40, - time0: 0, - time1: 1 - }), - background: Skybox.create_black() + camera: create_camera({ + look_from: point3(278, 278, -800), + look_at: point3(278, 278, 0), + v_up: vec3(0, 1, 0), + focus_dist: 10, + aperture: 0, + y_fov: 40, + time0: 0, + time1: 1 + }), + background: Skybox.create_black() + }); }); -}); +}; diff --git a/src/js/scenes/damaged_helmet_gltf.ts b/src/js/scenes/damaged_helmet_gltf.ts index a9cb408..b123944 100644 --- a/src/js/scenes/damaged_helmet_gltf.ts +++ b/src/js/scenes/damaged_helmet_gltf.ts @@ -5,7 +5,7 @@ import { create_camera } from '../camera'; import { load_rgbe } from '../texture/image-parsers/rgbe_image_parser'; import { Skybox } from '../hittable/skybox'; -export const load_damaged_helmet_gltf = async (): Promise => { +export const create = async (): Promise => { const scene = await load_gltf('gltf/DamagedHelmet/glTF/DamagedHelmet.gltf', 1024000, 1024, 25); const env = await load_rgbe(2000, 'hdr/Cannon_Exterior.hdr'); const skybox = Skybox.create_hdr(env); @@ -26,9 +26,9 @@ export const load_damaged_helmet_gltf = async (): Promise => { background: skybox, exposure_config: { aperture: 16, - shutter_speed: 1/25, + shutter_speed: 1 / 25, ISO: 100, exp_comp: 0 } }); -} +}; diff --git a/src/js/scenes/earth.ts b/src/js/scenes/earth.ts index 2251311..03575bf 100644 --- a/src/js/scenes/earth.ts +++ b/src/js/scenes/earth.ts @@ -8,10 +8,13 @@ import { load_dom_image } from '../texture/image-parsers/image-bitmap'; import { create_image_texture } from '../texture/image_texture'; import { create_sphere } from '../hittable/sphere'; -export const create_earth_scene = async (): Promise => { +export const create = async (): Promise => { const earth_image = await load_dom_image(earthUrl); return create_scene({ - root_hittable: create_sphere(point3(0, 0, 0), 2, create_lambertian(create_image_texture(earth_image, {flip_y: true, decode_srgb: true}))), + root_hittable: create_sphere(point3(0, 0, 0), 2, create_lambertian(create_image_texture(earth_image, { + flip_y: true, + decode_srgb: true + }))), camera: create_camera({ look_from: point3(13, 2, 3), look_at: point3(0, 0, 0), @@ -24,4 +27,4 @@ export const create_earth_scene = async (): Promise => { }), background: Skybox.create_solid(0.7, 0.8, 1.0) }); -} +}; diff --git a/src/js/scenes/index.ts b/src/js/scenes/index.ts new file mode 100644 index 0000000..e1f2efd --- /dev/null +++ b/src/js/scenes/index.ts @@ -0,0 +1,13 @@ +import { Scene } from './scene'; + +export const scenes: [string, string, () => Promise][] = [ + ['Two spheres', 'book1', () => import('../scenes/two_spheres').then(({create}) => create())], + ['Book 1. Final scene', 'book1', () => import('../scenes/book-1-final-scene').then(({create}) => create())], + ['Earth texture', 'book2', () => import('../scenes/earth').then(({create}) => create())], + ['Simple light', 'book2', () => import('../scenes/simple_light').then(({create}) => create())], + ['Book 2. Final scene', 'book2', () => import('../scenes/book-2-final-scene').then(({create}) => create())], + ['Cornell box', 'book3', () => import('../scenes/cornell_box').then(({create}) => create())], + ['Cornel box with smoke', 'book3', () => import('../scenes/cornell_box_with_smoke').then(({create}) => create())], + ['simple.gltf', 'gltf', () => import('../scenes/simple_gltf').then(({create}) => create())], + ['DamagedHelmet.gltf', 'gltf', () => import('../scenes/damaged_helmet_gltf').then(({create}) => create())], +]; diff --git a/src/js/scenes/simple_gltf.ts b/src/js/scenes/simple_gltf.ts index 8ca7861..f7b51d6 100644 --- a/src/js/scenes/simple_gltf.ts +++ b/src/js/scenes/simple_gltf.ts @@ -5,7 +5,7 @@ import { create_camera } from '../camera'; import { load_rgbe } from '../texture/image-parsers/rgbe_image_parser'; import { Skybox } from '../hittable/skybox'; -export const load_simple_gltf = async (): Promise => { +export const create = async (): Promise => { const scene = await load_gltf('gltf/simple/model.gltf', 102400, 1024); const env = await load_rgbe(2048, 'hdr/street.hdr'); const skybox = Skybox.create_hdr(env); @@ -26,9 +26,9 @@ export const load_simple_gltf = async (): Promise => { background: skybox, exposure_config: { aperture: 16, - shutter_speed: 1/10, + shutter_speed: 1 / 10, ISO: 400, exp_comp: 0 } }); -} +}; diff --git a/src/js/scenes/simple_light.ts b/src/js/scenes/simple_light.ts index 752f88c..ec0dafd 100644 --- a/src/js/scenes/simple_light.ts +++ b/src/js/scenes/simple_light.ts @@ -10,30 +10,33 @@ import { create_quad } from '../hittable/quad'; import { create_sphere } from '../hittable/sphere'; import { create_hittable_list } from '../hittable/hittable_list'; -const perlin_texture = create_noise_texture(4); -const light1 = create_quad(point3(3,1,-2), vec3(2,0,0), vec3(0,2,0), create_diffuse_light(solid_color(0.5, 4, 1))); -const light2 = create_sphere(point3(0, 7, 0), 2, create_diffuse_light(solid_color(4, 1, 0.5))); -export const simple_light: Scene = create_scene({ - root_hittable: create_hittable_list([ - create_sphere(point3(0, -1000, 0), 1000, create_lambertian(perlin_texture)), - create_sphere(point3(0, 2, 0), 2, create_lambertian(perlin_texture)), - light1, - light2 - ]), - light: create_hittable_list([ - light1, - light2 - ]), - camera: create_camera({ - look_from: point3(26, 3, 6), - look_at: point3(0, 2, 0), - v_up: vec3(0, 1, 0), - focus_dist: 10, - aperture: 0, - y_fov: 20, - time0: 0, - time1: 1 - }), - background: Skybox.create_black() -}); +export const create = (): Scene => { + const perlin_texture = create_noise_texture(4); + + const light1 = create_quad(point3(3,1,-2), vec3(2,0,0), vec3(0,2,0), create_diffuse_light(solid_color(0.5, 4, 1))); + const light2 = create_sphere(point3(0, 7, 0), 2, create_diffuse_light(solid_color(4, 1, 0.5))); + return create_scene({ + root_hittable: create_hittable_list([ + create_sphere(point3(0, -1000, 0), 1000, create_lambertian(perlin_texture)), + create_sphere(point3(0, 2, 0), 2, create_lambertian(perlin_texture)), + light1, + light2 + ]), + light: create_hittable_list([ + light1, + light2 + ]), + camera: create_camera({ + look_from: point3(26, 3, 6), + look_at: point3(0, 2, 0), + v_up: vec3(0, 1, 0), + focus_dist: 10, + aperture: 0, + y_fov: 20, + time0: 0, + time1: 1 + }), + background: Skybox.create_black() + }); +}; diff --git a/src/js/scenes/two_perlin_spheres.ts b/src/js/scenes/two_perlin_spheres.ts index b816ebc..8f57908 100644 --- a/src/js/scenes/two_perlin_spheres.ts +++ b/src/js/scenes/two_perlin_spheres.ts @@ -1,29 +1,31 @@ import { create_noise_texture } from '../texture/noise_texture'; import { point3, vec3 } from '../math/vec3.gen'; -import { create_scene, Scene } from './scene'; +import { create_scene } from './scene'; import { create_camera } from '../camera'; import { create_lambertian } from '../materials/lambertian'; import { Skybox } from '../hittable/skybox'; import { create_hittable_list } from '../hittable/hittable_list'; import { create_sphere } from '../hittable/sphere'; -const pertext = create_noise_texture(4); +export const create = () => { + const pertext = create_noise_texture(4); -export const two_perlin_spheres: Scene = create_scene({ - root_hittable: create_hittable_list([ - create_sphere(point3(0, -1000, 0), 1000, create_lambertian(pertext)), - create_sphere(point3(0, 2, 0), 2, create_lambertian(pertext)) - ]), + return create_scene({ + root_hittable: create_hittable_list([ + create_sphere(point3(0, -1000, 0), 1000, create_lambertian(pertext)), + create_sphere(point3(0, 2, 0), 2, create_lambertian(pertext)) + ]), - camera: create_camera({ - look_from: point3(13, 2, 3), - look_at: point3(0, 0, 0), - v_up: vec3(0, 1, 0), - focus_dist: 10, - aperture: 0, - y_fov: 20, - time0: 0, - time1: 1 - }), - background: Skybox.create_solid(0.7, 0.8, 1.0) -}); + camera: create_camera({ + look_from: point3(13, 2, 3), + look_at: point3(0, 0, 0), + v_up: vec3(0, 1, 0), + focus_dist: 10, + aperture: 0, + y_fov: 20, + time0: 0, + time1: 1 + }), + background: Skybox.create_solid(0.7, 0.8, 1.0) + }); +}; diff --git a/src/js/scenes/two_spheres.ts b/src/js/scenes/two_spheres.ts index f005828..e60b78a 100644 --- a/src/js/scenes/two_spheres.ts +++ b/src/js/scenes/two_spheres.ts @@ -17,7 +17,7 @@ const create_two_spheres = (): Hittable => { ]); }; -export const two_spheres: Scene = create_scene({ +export const create = (): Scene => create_scene({ root_hittable: create_two_spheres(), camera: create_camera({ diff --git a/src/js/ui/select_model_ui.ts b/src/js/ui/select_model_ui.ts deleted file mode 100644 index a8f1280..0000000 --- a/src/js/ui/select_model_ui.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { Scene } from '../scenes/scene'; - -export const select_model_ui = async (container: HTMLDivElement): Promise => { - - const scenes: [string, () => Promise][] = [ - ['Two spheres', async () => { - const {two_spheres} = await import('../scenes/two_spheres'); - return two_spheres; - }], - ['Earth texture', async () => { - const {create_earth_scene} = await import('../scenes/earth'); - return await create_earth_scene(); - }], - ['Book 1. Final scene', async () => { - const {book1_final_scene} = await import('../scenes/book-1-final-scene'); - return book1_final_scene(); - }], - ['Simple light', async () => { - const {simple_light} = await import('../scenes/simple_light'); - return simple_light; - }], - ['Cornell box', async () => { - const {cornell_box} = await import('../scenes/cornell_box'); - return cornell_box; - }], - ['Cornel box with smoke', async () => { - const {cornell_box_with_smoke} = await import('../scenes/cornell_box_with_smoke'); - return cornell_box_with_smoke; - }], - ['Book 2. Final scene', async () => { - const {book2_final_scene} = await import('../scenes/book-2-final-scene'); - return await book2_final_scene(); - }], - ['simple.gltf', async () => { - const {load_simple_gltf} = await import('../scenes/simple_gltf'); - return await load_simple_gltf(); - }], - ['DamagedHelmet.gltf', async () => { - const {load_damaged_helmet_gltf} = await import('../scenes/damaged_helmet_gltf'); - return await load_damaged_helmet_gltf(); - }] - ]; - - try { - const ui = document.createElement('div'); - ui.innerHTML = [ - '
    ', - scenes.map(([name], i) => { - return ``; - }).join('\n'), - '
    ', - ].join('\n'); - - container.appendChild(ui); - - const select = document.getElementById('select-model-ui__select') as HTMLSelectElement; - const load_button = document.getElementById('select-model-ui__load-button') as HTMLButtonElement; - - - const model_index = await new Promise((resolve) => { - for (let i = 0; i < scenes.length; i++) { - const btn = document.getElementById(`selection_${i}`) as HTMLButtonElement; - btn.onclick = () => resolve(i); - } - }); - - container.removeChild(ui); - - const model_loader = scenes[model_index][1]; - - if (model_loader === undefined) { - throw new Error(`Unknown scene ${select.value}`); - } - - return model_loader(); - } - catch { - alert('Failed to load model, reloading the page...'); - window.location.reload(); - } -} diff --git a/src/public/index.html b/src/public/index.html index d2bf7b1..88ed2b0 100644 --- a/src/public/index.html +++ b/src/public/index.html @@ -3,14 +3,26 @@ Ray tracing in one weekend - - -
    -
    -
    -
    -
    - + +

    Ray tracing in one weekend demos

    +

    Book 1

    +
      +
    + +

    Book 2

    +
      +
    + +

    Book 3

    +
      +
    + +

    Gltf

    +
      +
    + + + diff --git a/src/public/renderer.html b/src/public/renderer.html new file mode 100644 index 0000000..2ca09e6 --- /dev/null +++ b/src/public/renderer.html @@ -0,0 +1,16 @@ + + + Ray tracing in one weekend + + + + + + +
    +
    +
    +
    +
    + + diff --git a/todo.md b/todo.md index b0253be..51ac12b 100644 --- a/todo.md +++ b/todo.md @@ -1,8 +1,8 @@ - **bug** Sometimes according to normal-map or/and vertex normals, combined with micro-facets distribution, reflected ray must go below the surface. This is currently rendered as black. Should probably do something smarter in that case. +- **bug** with new scene selection phase everything is slow - matrix-based camera - hitting bvh with triangles is too slow. Need more efficient in-memory structure after loading gltf. This may also help with messy UV/vertex-normals related code. - pdf mixer with explicit weights. When using image based importance sampling with PBR material, specular/diffuse rays will get only quarter priority instead of one third. More general mixer may fix that. -- separate page for selecting a scene - configuration UI - schedule thread load more evenly. Think something like Masonry layout, but for threads and ray counts - glTF