diff --git a/scenes/disp_rect.json b/scenes/disp_rect.json new file mode 100644 index 000000000..adf85db49 --- /dev/null +++ b/scenes/disp_rect.json @@ -0,0 +1,50 @@ +{ + "technique": { + "type": "path", + "max_depth": 8 + }, + "camera": { + "type": "perspective", + "fov": 40, + "near_clip": 0.1, + "far_clip": 100, + "transform": [ -1,0,0,0, 0,1,0,0, 0,0,-1,10, 0,0,0,1 ] + }, + "film": { + "size": [1000, 1000] + }, + "textures": [ + { + "type": "bitmap", + "name": "env", + "filename": "textures/environment/phalzer_forest_01_4k.exr" + }, + { + "type": "checkerboard", + "name": "check", + "scale_x": 10, + "scale_y": 10, + "color0": [0,0,0], + "color1": [1,1,1] + }, + { + "type": "bitmap", + "name": "tex", + "filename": "textures/grid_weight.png", + "filter_type": "trilinear" + } + ], + "bsdfs": [ + {"type":"diffuse", "name": "mat-Fallback"}, + {"type":"diffuse", "name": "mat-Plane", "reflectance":"tex"} + ], + "shapes": [ + {"type":"rectangle", "name":"Plane", "width": 4, "height": 4} + ], + "entities": [ + {"name":"Plane","shape":"Plane", "bsdf":"mat-Plane", "transform": {"rotate": [0,0,45], "translate": [0,0,0]}} + ], + "lights": [ + {"type":"sky", "name":"Sky"} + ] +} diff --git a/scenes/disp_rect_color.json b/scenes/disp_rect_color.json new file mode 100644 index 000000000..5073287cc --- /dev/null +++ b/scenes/disp_rect_color.json @@ -0,0 +1,50 @@ +{ + "technique": { + "type": "path", + "max_depth": 8 + }, + "camera": { + "type": "perspective", + "fov": 40, + "near_clip": 0.1, + "far_clip": 100, + "transform": [ -1,0,0,0, 0,1,0,0, 0,0,-1,10, 0,0,0,1 ] + }, + "film": { + "size": [1000, 1000] + }, + "textures": [ + { + "type": "bitmap", + "name": "env", + "filename": "textures/environment/phalzer_forest_01_4k.exr" + }, + { + "type": "checkerboard", + "name": "check", + "scale_x": 10, + "scale_y": 10, + "color0": [0,0,0], + "color1": [1,1,1] + }, + { + "type": "bitmap", + "name": "tex", + "filename": "textures/colorgrid.png", + "filter_type": "trilinear" + } + ], + "bsdfs": [ + {"type":"diffuse", "name": "mat-Fallback"}, + {"type":"diffuse", "name": "mat-Plane", "reflectance":"tex"} + ], + "shapes": [ + {"type":"rectangle", "name":"Plane", "width": 4, "height": 4} + ], + "entities": [ + {"name":"Plane","shape":"Plane", "bsdf":"mat-Plane", "transform": {"rotate": [0,0,45], "translate": [0,0,0]}} + ], + "lights": [ + {"type":"sky", "name":"Sky"} + ] +} diff --git a/scenes/disp_rect_light.json b/scenes/disp_rect_light.json new file mode 100644 index 000000000..80cb6535e --- /dev/null +++ b/scenes/disp_rect_light.json @@ -0,0 +1,50 @@ +{ + "technique": { + "type": "path", + "max_depth": 8 + }, + "camera": { + "type": "perspective", + "fov": 40, + "near_clip": 0.1, + "far_clip": 100, + "transform": [ -1,0,0,0, 0,1,0,0, 0,0,-1,10, 0,0,0,1 ] + }, + "film": { + "size": [1000, 1000] + }, + "textures": [ + { + "type": "bitmap", + "name": "env", + "filename": "textures/environment/phalzer_forest_01_4k.exr" + }, + { + "type": "checkerboard", + "name": "check", + "scale_x": 10, + "scale_y": 10, + "color0": [0,0,0], + "color1": [1,1,1] + }, + { + "type": "bitmap", + "name": "tex", + "filename": "textures/grid_weight.png", + "filter_type": "trilinear" + } + ], + "bsdfs": [ + {"type":"diffuse", "name": "mat-Fallback"}, + {"type":"diffuse", "name": "mat-Plane", "reflectance":"tex"} + ], + "shapes": [ + {"type":"rectangle", "name":"Plane", "width": 4, "height": 4} + ], + "entities": [ + {"name":"Plane","shape":"Plane", "bsdf":"mat-Plane", "transform": {"rotate": [0,0,45], "translate": [0,0,0]}} + ], + "lights": [ + {"type":"point", "name":"L1", "intensity": [3.0, 3.0, 3.0], "position": [1, 1, 1]} + ] +} diff --git a/scenes/disp_test.json b/scenes/disp_test.json new file mode 100644 index 000000000..ff1b0bbb3 --- /dev/null +++ b/scenes/disp_test.json @@ -0,0 +1,37 @@ +{ + "technique": { + "type": "path", + "max_depth": 64 + }, + "camera": { + "type": "perspective", + "fov": 39.597755, + "near_clip": 0.1, + "far_clip": 100, + "transform": [ -1,0,0,0, 0,1,0,0, 0,0,-1,3.849529, 0,0,0,1 ] + }, + "film": { + "size": [1000, 1000] + }, + "textures": [ + { + "type": "bitmap", + "name": "tex", + "filename": "textures/bumpmap.png", + "filter_type": "trilinear" + } + ], + "bsdfs": [ + {"type":"diffuse", "name": "mat-Sphere", "reflectance":"tex"} + ], + "shapes": [ + {"type":"uvsphere", "name":"Sphere"} + ], + "entities": [ + {"name":"Sphere","shape":"Sphere", "bsdf":"mat-Sphere"} + ], + "lights": [ + {"type":"sky", "name":"Sky"}, + {"type":"sun", "name":"Sun", "sun_direction":[0.4082482904638631,0.4082482904638631,0.8164965809277261]} + ] +} diff --git a/scenes/textures/bumpmap.bmp b/scenes/textures/bumpmap.bmp new file mode 100644 index 000000000..dbe56d022 Binary files /dev/null and b/scenes/textures/bumpmap.bmp differ diff --git a/scenes/textures/bumpmap2.png b/scenes/textures/bumpmap2.png new file mode 100644 index 000000000..4f7d0a412 Binary files /dev/null and b/scenes/textures/bumpmap2.png differ diff --git a/scenes/textures/colorgrid.png b/scenes/textures/colorgrid.png new file mode 100644 index 000000000..7ab1c8b45 Binary files /dev/null and b/scenes/textures/colorgrid.png differ diff --git a/scenes/textures/rect.png b/scenes/textures/rect.png new file mode 100644 index 000000000..a08a278ab Binary files /dev/null and b/scenes/textures/rect.png differ diff --git a/scenes/textures/smooth.png b/scenes/textures/smooth.png new file mode 100644 index 000000000..34a47f42d Binary files /dev/null and b/scenes/textures/smooth.png differ diff --git a/src/artic/impl/bsdf/measured.art b/src/artic/impl/bsdf/measured.art new file mode 100644 index 000000000..e69de29bb diff --git a/src/artic/render/device.art b/src/artic/render/device.art index cd68297e0..bfa632ec5 100644 --- a/src/artic/render/device.art +++ b/src/artic/render/device.art @@ -67,7 +67,7 @@ struct Device { load_custom_dyntable: fn (&[u8] /* Filename */) -> DynTable, // [Internal] Load the whole shape bvh table from a dynamic table - load_bvh_table: fn (DynTable) -> BVHTable, + load_bvh_table: fn (DynTable, Image) -> BVHTable, // [Internal] Will load the rays given in 'tracing' mode load_rays: fn () -> &[StreamRay], diff --git a/src/artic/render/mapping_cpu.art b/src/artic/render/mapping_cpu.art index 30bd83a0e..1355ac597 100644 --- a/src/artic/render/mapping_cpu.art +++ b/src/artic/render/mapping_cpu.art @@ -802,7 +802,7 @@ fn @make_cpu_device(vector_compact: bool, single: bool, min_max: MinMax, vector_ ignis_load_custom_dyntable(0, name, &mut table); table }, - load_bvh_table = @ |dtb| -> BVHTable { + load_bvh_table = @ |dtb, _| -> BVHTable { @ |id| { let entry = get_lookup_entry(id as u64, dtb, @|ptr| make_cpu_buffer(ptr, 0)); let header = get_table_entry(entry.offset, dtb, @|ptr| make_cpu_buffer(ptr, 0)); diff --git a/src/artic/render/mapping_gpu.art b/src/artic/render/mapping_gpu.art index 0cccceea4..1f28305fc 100644 --- a/src/artic/render/mapping_gpu.art +++ b/src/artic/render/mapping_gpu.art @@ -860,15 +860,16 @@ fn @make_gpu_device( dev_id: i32 ignis_load_custom_dyntable(dev_id, name, &mut table); table }, - load_bvh_table = @ |dtb| -> BVHTable { + load_bvh_table = @ |bvhs, disp_map| -> BVHTable { @ |id| { - let entry = get_lookup_entry(id as u64, dtb, accb); - let header = get_table_entry(entry.offset, dtb, accb); + let entry = get_lookup_entry(id as u64, bvhs, accb); + let header = get_table_entry(entry.offset, bvhs, accb); let leaf_offset = header.load_i32(0) as u64; - let nodes = get_table_ptr(entry.offset + 16, dtb) as &[Node2]; - let tris = get_table_ptr(entry.offset + 16 + leaf_offset * (sizeof[Node2]() as u64), dtb) as &[Tri1]; - make_gpu_bvh2_tri1(nodes, tris, is_nvvm) + let nodes = get_table_ptr(entry.offset + 16, bvhs) as &[Node2]; + let tris = get_table_ptr(entry.offset + 16 + leaf_offset * (sizeof[Node2]() as u64), bvhs) as &[Tri1]; + + make_gpu_bvh2_tri1(nodes, tris, is_nvvm, min_max, disp_map) } }, load_image = @ |filename| { diff --git a/src/artic/traversal/intersection.art b/src/artic/traversal/intersection.art index 940b52e85..52376a6d1 100644 --- a/src/artic/traversal/intersection.art +++ b/src/artic/traversal/intersection.art @@ -14,6 +14,21 @@ struct Tri { n: Vec3 // Geometric normal (= cross(e1, e2)) } +struct UVTri { + v0: Vec3, // First vertex + v1: Vec3, // Second vertex + v2: Vec3, // Third vertex + e1: Vec3, // First edge (= v0 - v1) + e2: Vec3, // Second edge (= v2 - v0) + n: Vec3, // Geometric normal (= cross(e1, e2)) + c0: Vec2, + c1: Vec2, + c2: Vec2, + wc0: Vec2, + wc1: Vec2, + wc2: Vec2 +} + // Min/max functions required to perform the ray-box test struct MinMax { fmaxmaxf: fn (f32, f32, f32) -> f32, @@ -63,6 +78,30 @@ fn @make_tri(v0: Vec3, e1: Vec3, e2: Vec3, n: Vec3) = Tri { n = n }; +fn @make_uv_tri(v0: Vec3, e1: Vec3, e2: Vec3, n: Vec3, c0: Vec2, c1: Vec2, c2: Vec2, wc0: Vec2, wc1: Vec2, wc2: Vec2) = UVTri { + v0 = v0, + v1 = vec3_sub(v0, e1), + v2 = vec3_add(v0, e2), + e1 = e1, + e2 = e2, + n = n, + c0 = c0, + c1 = c1, + c2 = c2, + wc0 = wc0, + wc1 = wc1, + wc2 = wc2 +}; + +fn @uvtri2tri(uvtri : UVTri) -> Tri { + make_tri( + uvtri.v0, + uvtri.e1, + uvtri.e2, + uvtri.n + ) +} + fn @empty_hit(tmax: f32) = make_hit(-1, -1, tmax, undef[Vec2]()); fn @intersect_ray_tri_mt(backface_culling: bool, ray: Ray, tri: Tri) -> Option[(f32, f32, f32)] { diff --git a/src/artic/traversal/mapping_cpu.art b/src/artic/traversal/mapping_cpu.art index 64f6c2bb0..f8a0685f9 100644 --- a/src/artic/traversal/mapping_cpu.art +++ b/src/artic/traversal/mapping_cpu.art @@ -5,7 +5,10 @@ struct Tri4 { e1: [[f32 * 4] * 3], e2: [[f32 * 4] * 3], n: [[f32 * 4] * 3], - prim_id: [i32 * 4] + prim_id: [i32 * 4], + c0: [[f32 * 4] * 2], + c1: [[f32 * 4] * 2], + c2: [[f32 * 4] * 2], } struct Node4 { @@ -86,6 +89,7 @@ fn @make_cpu_node8(j: i32, nodes: &[Node8]) = Node { }; fn @make_cpu_tri4(tris: &[Tri4]) -> fn (i32) -> Prim { + @ |j| Prim { intersect = @ |i, ray| -> Option[Hit] { let tri_ptr = rv_align(&tris(j) as &i8, 32) as &Tri4; @@ -189,6 +193,7 @@ fn @cpu_traverse_single_helper_prim( mut ray: Ray , bvh: PrimBvh , any_hit: bool , root: i32 + , _shape : Shape ) -> Hit { let sorting_network = match bvh.arity { 8 => batcher_sort, @@ -313,6 +318,7 @@ fn @cpu_traverse_helper_prim( mut ray: Ray , single: bool , any_hit: bool , root: i32 + , shape : Shape ) -> Hit { let switch_threshold = match vector_width { @@ -360,7 +366,7 @@ fn @cpu_traverse_helper_prim( mut ray: Ray for lane in cpu_one_bits(mask) { let lane_ray = load_ray(&ray, lane); let lane_octant = bitcast[RayOctant](rv_extract(bitcast[f32](octant), lane)); - let lane_hit = cpu_traverse_single_helper_prim(lane_ray, lane_octant, min_max, bvh, any_hit, stack.top().node); + let lane_hit = cpu_traverse_single_helper_prim(lane_ray, lane_octant, min_max, bvh, any_hit, stack.top().node, shape); if lane_hit.prim_id >= 0 { store_hit(&mut hit, lane, lane_hit); if !any_hit { rv_store(&mut ray.tmax, lane, lane_hit.distance); } @@ -504,7 +510,8 @@ fn @cpu_traverse_helper( mut ray: Ray if tmin <= hit.distance { let local_ray = transform_ray(ray, leaf.local); let shape_bvh = @prim_bvhs(leaf.shape_id); - let local_hit = cpu_traverse_helper_prim(local_ray, vector_width, min_max, shape_bvh, single, any_hit, 1/*root*/); + let shape = scene.database.shapes(leaf.shape_id); + let local_hit = cpu_traverse_helper_prim(local_ray, vector_width, min_max, shape_bvh, single, any_hit, 1/*root*/, shape); if active { if local_hit.prim_id != -1 { diff --git a/src/artic/traversal/mapping_gpu.art b/src/artic/traversal/mapping_gpu.art index a00bd4c82..7867748be 100644 --- a/src/artic/traversal/mapping_gpu.art +++ b/src/artic/traversal/mapping_gpu.art @@ -12,7 +12,11 @@ struct Tri1 { e1: [f32 * 3], pad2: i32, e2: [f32 * 3], - prim_id: i32 + prim_id: i32, + c0: [f32 * 2], + c1: [f32 * 2], + c2: [f32 * 2], + pad3: [i32 * 2] } fn @make_gpu_node(j: i32, nodes: &[Node2], is_nvvm: bool) -> Node { @@ -40,7 +44,7 @@ fn @make_gpu_node(j: i32, nodes: &[Node2], is_nvvm: bool) -> Node { } } -fn @make_gpu_prim(j: i32, tris: &[Tri1], is_nvvm: bool) -> Prim { +fn @make_gpu_prim(j: i32, tris: &[Tri1], is_nvvm: bool, minmax : MinMax, disp_map : Image) -> Prim { let load4_f32 = @ |p: &addrspace(1)[simd[f32 * 4]], i: i32| if is_nvvm { nvvm_ldg4_f32(&p(i)) } else { p(i) }; let simd_ptr = &tris(j) as &addrspace(1)[simd[f32 * 4]]; @@ -48,15 +52,252 @@ fn @make_gpu_prim(j: i32, tris: &[Tri1], is_nvvm: bool) -> Prim { let tri1 = load4_f32(simd_ptr, 1); let tri2 = load4_f32(simd_ptr, 2); let prim_id = bitcast[i32](tri2(3)); + + // uv-coordinates(vertex 0, 1, 2) + padding + let c01 = load4_f32(simd_ptr, 3); + let c2p = load4_f32(simd_ptr, 4); + + // create base triangle + let v0 = make_vec3(tri0(0), tri0(1), tri0(2)); + let e1 = make_vec3(tri1(0), tri1(1), tri1(2)); + let e2 = make_vec3(tri2(0), tri2(1), tri2(2)); + let n = vec3_cross(e1, e2); + + // absolute uv-coordinate of base triangle + let wc0 = make_vec2(c01(0), c01(1)); + let wc1 = make_vec2(c01(2), c01(3)); + let wc2 = make_vec2(c2p(0), c2p(1)); + + // local uv-coordinate of base triangle + let c0 = make_vec2(0,0); + let c1 = make_vec2(1,0); + let c2 = make_vec2(0,1); + + let uvtri = make_uv_tri(v0,e1,e2, n, c0, c1, c2, wc0, wc1, wc2); + + /* + // Deprecated: Originally used to compute minimum and maximum displacement of BVH but has proven to be + // too inefficient in the current implementation + let min_max_displacement = @ |c1 : Vec2, c2 : Vec2| -> (f32, f32) { + let min_px = (math_builtins::floor(minmax.fminf(c1.x, c2.x)) as i32) * disp_map.width; + let min_py = (math_builtins::floor(minmax.fminf(c1.y, c2.y)) as i32) * disp_map.height; + + let max_px = (math_builtins::ceil(minmax.fmaxf(c1.x, c2.x)) as i32) * disp_map.width; + let max_py = (math_builtins::ceil(minmax.fmaxf(c1.y, c2.y)) as i32) * disp_map.height; + + let mut min = flt_max; + let mut max = flt_min; + + for y in range(min_py, max_py) { + for x in range(min_px, max_px) { + let d = disp_map.pixels(x,y).r; + min = minmax.fminf(min, d); + max = minmax.fmaxf(max, d); + } + } + + (min, max) + }; + */ + + // computes displacement from bumpmap, given an absolute surface coordinate + let displacement = @|c: Vec2| -> f32 { + let px = c.x * (disp_map.width as f32); + let py = c.y * (disp_map.height as f32); + + let mut lo_x = math_builtins::floor(px) as i32; + let mut lo_y = math_builtins::floor(py) as i32; + + if (lo_x == disp_map.width) {lo_x = disp_map.width -1;} + if (lo_y == disp_map.height) {lo_y = disp_map.height -1;} + + clampf(disp_map.pixels(lo_x, lo_y).r, 0, 1) + }; + + //let displacement = @|c: Vec2| -> f32 { + // (math_builtins::cos(c.x * 7.0) + 1.0) * 0.5 + //}; + + /* // Deprecated: Switched to Tesselation into 2 triangles (see tesselate2) + let tesselate4 = @|t:UVTri| { + let v0 = t.v0; + let e1 = t.e1; + let e2 = t.e2; + + let c01 = vec2_lerp(t.c0, t.c1, 0.5); + let c02 = vec2_lerp(t.c0, t.c2, 0.5); + let c12 = vec2_lerp(t.c1, t.c2, 0.5); + + let wc01 = vec2_lerp(t.wc0, t.wc1, 0.5); + let wc02 = vec2_lerp(t.wc0, t.wc2, 0.5); + let wc12 = vec2_lerp(t.wc1, t.wc2, 0.5); + + let half_e1 = vec3_mulf(t.e1, 0.5); + let half_e2 = vec3_mulf(t.e2, 0.5); + + let v01 = vec3_lerp(t.v0, t.v1, 0.5); + let v02 = vec3_lerp(t.v0, t.v2, 0.5); + let v12 = vec3_lerp(t.v1, t.v2, 0.5); + + let n_new = vec3_cross(half_e1, half_e2); + let tri0 = make_uv_tri(v0, half_e1, half_e2, n_new, c0, c01, c02, wc0, wc01, wc02); + + let tri1 = make_uv_tri(v01, half_e1, half_e2, n_new, c01, c1, c12, wc01, wc1, wc12); + + let tri2 = make_uv_tri(v02, half_e1, half_e2, n_new, c02, c12, c2, wc02, wc12, wc2); + + let ne1 = vec3_mulf(half_e1, 1); + let ne2 = vec3_add(half_e1, half_e2); + let tri3 = make_uv_tri(v01, ne1, ne2, vec3_cross(ne1, ne2), c12, c02, c01, wc12, wc02, wc01); + + (tri0, tri1, tri2, tri2) + }; */ + + // Tesselates a single triangle into 2 new triangles + let tesselate2 = @|t:UVTri| { + + let tv0 = t.v0; + let tv1 = t.v1; + let tv2 = t.v2; + let tv12 = vec3_lerp(tv1, tv2, 0.5); + + let tc0 = t.c0; + let tc1 = t.c1; + let tc2 = t.c2; + let tc12 = vec2_lerp(t.c1, t.c2, 0.5); + + let twc0 = t.wc0; + let twc1 = t.wc1; + let twc2 = t.wc2; + let twc12 = vec2_lerp(t.wc1, t.wc2, 0.5); + + let diag = vec3_sub(tv12, tv0); + let te1 = vec3_sub(tv1, tv12); + let ttri0 = make_uv_tri(tv12, diag, te1, vec3_cross(diag, te1), tc12, tc0, tc1, twc12, twc0, twc1); + let te2 = vec3_sub(tv2, tv12); + let ttri1 = make_uv_tri(tv12, diag, te2, vec3_cross(diag, te2), tc12, tc0, tc2, twc12, twc0, twc2); + + (ttri0, ttri1) + }; + + // computes bounding box around a single triangle + let tri_bbox = @|t:UVTri| { + let min_x = minmax.fminminf(t.v0.x, t.v1.x, t.v2.x); + let min_y = minmax.fminminf(t.v0.y, t.v1.y, t.v2.y); + let min_z = minmax.fminminf(t.v0.z, t.v1.z, t.v2.z); + + let max_x = minmax.fmaxmaxf(t.v0.x, t.v1.x, t.v2.x); + let max_y = minmax.fmaxmaxf(t.v0.y, t.v1.y, t.v2.y); + let max_z = minmax.fmaxmaxf(t.v0.z, t.v1.z, t.v2.z); + + make_bbox( + make_vec3(min_x, min_y, min_z), + make_vec3(max_x, max_y, max_z) + ) + }; + + // displaces a single triangle by applying the bumpmap displacement to the triangle vertices + let displace_tri = @ |t : UVTri| -> UVTri { + let d0 = displacement(t.wc0); + let d1 = displacement(t.wc1); + let d2 = displacement(t.wc2); + + let nn = vec3_normalize(uvtri.n); + + let dv0 = vec3_add(t.v0, vec3_mulf(nn, d0)); + let dv1 = vec3_add(t.v1, vec3_mulf(nn, d1)); + let dv2 = vec3_add(t.v2, vec3_mulf(nn, d2)); + + let de1 = vec3_sub(dv0, dv1); + let de2 = vec3_sub(dv2, dv0); + + let normal = vec3_cross(de1, de2); + + make_uv_tri(dv0, de1, de2, normal, t.c0, t.c1, t.c2, t.wc0, t.wc1, t.wc2) + }; + Prim { + // traverses the displacement BVH by storing triangles that still need to be traversed in a queue + // and traversing that frontier intersect = @ |_, ray| { - let v0 = make_vec3(tri0(0), tri0(1), tri0(2)); - let e1 = make_vec3(tri1(0), tri1(1), tri1(2)); - let e2 = make_vec3(tri2(0), tri2(1), tri2(2)); - let n = vec3_cross(e1, e2); - let tri = make_tri(v0, e1, e2, n); - if let Option[(f32, f32, f32)]::Some((t, u, v)) = intersect_ray_tri(false /*backface_culling*/, ray, tri) { - make_option(make_hit(-1/* Will be set later*/, prim_id & 0x7FFFFFFF, t, make_vec2(u, v))) + + // initialize queue for storing triangles + let mut tri_queue : [UVTri * 100]; + let mut queue_index = 0; + + let queue_tri = @|t:UVTri| { + tri_queue(queue_index) = t; + queue_index++; + }; + + let dequeue_tri = @|| -> Option[UVTri] { + if (queue_index > 0) { + queue_index--; + Option[UVTri]::Some(tri_queue(queue_index)) + } else { + Option[UVTri]::None + } + }; + + // start with base triangle + queue_tri(uvtri); + + // record closest intersection and uv + let mut closest = flt_max; + let mut uv = make_vec2(0,0); + + // traverse queue frontier (BVH traversal) + while queue_index > 0 { + if let Option[UVTri]::Some(current_tri) = dequeue_tri() { + let (t0, t1) = tesselate2(current_tri); + let ttris = [t0,t1]; + + for i in unroll(0,2) { + let tri = ttris(i); + + let disp_tri = (displace_tri(tri)); + + // triangle is considered a leaf under the following condition + // (length of diagonal across triangle is smaller than 0.05) + let leaf = vec2_len(make_vec2( + minmax.fmaxmaxf(tri.wc0.x, tri.wc1.x, tri.wc2.x) - minmax.fminminf(tri.wc0.x, tri.wc1.x, tri.wc2.x), + minmax.fmaxmaxf(tri.wc0.y, tri.wc1.y, tri.wc2.y) - minmax.fminminf(tri.wc0.y, tri.wc1.y, tri.wc2.y) + )) < 0.05; + + if (leaf) { + let isec_tri = uvtri2tri(disp_tri); + // intersect leaf-level triangle for final traversal result + if let Option[(f32, f32, f32)]::Some((t, _u, _v)) = intersect_ray_tri(false, ray, isec_tri) { + + closest = t; + // use these uv-coordinates to visualize the tesselated triangles + // get overwritten later on by default + uv = make_vec2(current_tri.v1.x, current_tri.v1.y); + break; + } + } else { + // queue next tesselation level if current level is not a leaf and next level's bbox is intersected + if let Option[f32]::Some(_t) = intersect_ray_box_single(minmax, false, ray, tri_bbox(disp_tri)) { + queue_tri(tri); + } + } + } + } + } + + // if a hit was found, return it + if closest < flt_max { + let hit = vec3_add(ray.org, vec3_mulf(ray.dir, closest)); + let dir = uvtri.n; + + // reproject final intersection onto the base triangle to compute + // the correct uv-coordinates + // comment this block out if triangle visualization is wanted + if let Option[(f32, f32, f32)]::Some((_t,u,v)) = intersect_ray_tri(false, make_ray(hit, dir, -1, flt_max), uvtri2tri(uvtri)) { + uv = make_vec2(u, v); + } + + make_option(make_hit(-1/* Will be set later*/, prim_id & 0x7FFFFFFF, closest, uv)) } else { Option[Hit]::None } @@ -81,10 +322,10 @@ fn @make_gpu_ent(j: i32, objs: &[EntityLeaf1], is_nvvm: bool) -> EntityLeaf { ) } -fn @make_gpu_bvh2_tri1(nodes: &[Node2], tris: &[Tri1], is_nvvm: bool) -> PrimBvh { +fn @make_gpu_bvh2_tri1(nodes: &[Node2], tris: &[Tri1], is_nvvm: bool, minmax : MinMax, disp_map: Image) -> PrimBvh { PrimBvh { node = @ |j| @make_gpu_node(j, nodes, is_nvvm), - prim = @ |j| @make_gpu_prim(j, tris , is_nvvm), + prim = @ |j| @make_gpu_prim(j, tris , is_nvvm, minmax, disp_map), prefetch = @ |_| (), // Not implemented arity = 2 } @@ -117,7 +358,7 @@ fn @make_amdgpu_min_max() = make_min_max(ocml_fminf, ocml_fmaxf, false); // Traverses a single ray at a time. fn @gpu_traverse_single_helper_prim( min_max: MinMax - , mut ray: Ray + , ray: Ray , bvh: PrimBvh , any_hit: bool , root: i32 @@ -185,12 +426,13 @@ fn @gpu_traverse_single_helper_prim( min_max: MinMax while true { let prim = bvh.prim(prim_id++); + let mut tmin = ray.tmax; for k in unroll(0, prim.size) { if !prim.is_valid(k) { break() } if let Option[Hit]::Some(prim_hit) = prim.intersect(k, ray) { hit = prim_hit; - ray.tmax = prim_hit.distance; + tmin = prim_hit.distance; if any_hit { return(hit) } } @@ -281,6 +523,7 @@ fn @gpu_traverse_single_helper( min_max: MinMax if tmin <= hit.distance { let local_ray = transform_ray(ray, leaf.local); let shape_bvh = @prim_bvhs(leaf.shape_id); + let local_hit = gpu_traverse_single_helper_prim(min_max, local_ray, shape_bvh, any_hit, 1); if local_hit.prim_id != -1 { diff --git a/src/backend/driver/tracer.art b/src/backend/driver/tracer.art index 940289c3e..f37eda758 100644 --- a/src/backend/driver/tracer.art +++ b/src/backend/driver/tracer.art @@ -5,11 +5,15 @@ fn ig_render(settings: &Settings) -> () { let device = @get_device(settings.device); let dtb = device.load_scene_database(); + + // load displacement map as image and pass it to the BVH + let displacement_map = device.load_image("/home/niklas/hiwi/Ignis/scenes/textures/smooth.png"); + let acc = TraceAccessor { info = device.load_scene_info(), shapes = device.load_shape_table(dtb.shapes), entities = device.load_entity_table(dtb.entities), - bvhs = device.load_bvh_table(dtb.bvhs) + bvhs = device.load_bvh_table(dtb.bvhs, displacement_map) }; let scene = SceneGeometry { diff --git a/src/backend/runtime/Image.cpp b/src/backend/runtime/Image.cpp index f52cd1b91..89063156b 100644 --- a/src/backend/runtime/Image.cpp +++ b/src/backend/runtime/Image.cpp @@ -294,8 +294,10 @@ Image Image::load(const std::filesystem::path& path, ImageMetaData* metaData) int width = 0, height = 0, channels = 0; float* data = stbi_loadf(path.generic_u8string().c_str(), &width, &height, &channels, 0); - if (data == nullptr) - throw ImageLoadException("Could not load image", path); + if (data == nullptr) { + auto reason = stbi_failure_reason(); + throw ImageLoadException(reason, path); + } img.width = width; img.height = height; diff --git a/src/backend/runtime/bvh/TriBVHAdapter.h b/src/backend/runtime/bvh/TriBVHAdapter.h index 307a89310..117aad889 100644 --- a/src/backend/runtime/bvh/TriBVHAdapter.h +++ b/src/backend/runtime/bvh/TriBVHAdapter.h @@ -5,6 +5,8 @@ #include "math/Triangle.h" #include "mesh/TriMesh.h" +#include "Image.h" + IG_BEGIN_IGNORE_WARNINGS #include #include @@ -45,6 +47,7 @@ struct BvhNTriM<2, 1> { struct TriangleProxy : public bvh::Triangle { using bvh::Triangle::Triangle; + Vector2f c0, c1, c2; int prim_id; }; @@ -104,6 +107,15 @@ class BvhNTriMAdapter : public BvhNAdapter::Node, Tri tri.n.e[2].e[j] = in_tri.n[2]; tri.prim_id.e[j] = in_tri.prim_id; + + tri.c0.e[0].e[j] = in_tri.c0[0]; + tri.c0.e[1].e[j] = in_tri.c0[1]; + + tri.c1.e[0].e[j] = in_tri.c1[0]; + tri.c1.e[1].e[j] = in_tri.c1[1]; + + tri.c2.e[0].e[j] = in_tri.c2[0]; + tri.c2.e[1].e[j] = in_tri.c2[1]; } for (size_t j = c; j < M; ++j) @@ -152,7 +164,12 @@ class BvhNTriMAdapter<2, 1, Allocator> : public BvhNAdapter<2, typename BvhNTriM { in_tri.e1[0], in_tri.e1[1], in_tri.e1[2] }, 0, { in_tri.e2[0], in_tri.e2[1], in_tri.e2[2] }, - in_tri.prim_id }); + in_tri.prim_id, + { in_tri.c0[0], in_tri.c0[1] }, //c0 + { in_tri.c1[0], in_tri.c1[1] }, //c1 + { in_tri.c2[0], in_tri.c2[1] }, //c2 + {0,0} // additional padding + }); } // Add sentinel @@ -160,6 +177,11 @@ class BvhNTriMAdapter<2, 1, Allocator> : public BvhNAdapter<2, typename BvhNTriM } }; +float disp(float x) { + if (x > 0.5) return 1; + return 0; +} + template typename Allocator> inline void build_bvh(const TriMesh& tri_mesh, std::vector::Node, Allocator::Node>>& nodes, @@ -176,7 +198,17 @@ inline void build_bvh(const TriMesh& tri_mesh, auto& v0 = tri_mesh.vertices[tri_mesh.indices[i * 4 + 0]]; auto& v1 = tri_mesh.vertices[tri_mesh.indices[i * 4 + 1]]; auto& v2 = tri_mesh.vertices[tri_mesh.indices[i * 4 + 2]]; - primitives[i] = TriangleProxy(v0, v1, v2); + + auto& c0 = tri_mesh.texcoords[tri_mesh.indices[i * 4 + 0]]; + auto& c1 = tri_mesh.texcoords[tri_mesh.indices[i * 4 + 1]]; + auto& c2 = tri_mesh.texcoords[tri_mesh.indices[i * 4 + 2]]; + + auto tri = TriangleProxy(v0, v1, v2); + tri.c0 = c0; + tri.c1 = c1; + tri.c2 = c2; + + primitives[i] = tri; primitives[i].prim_id = (int)i; } @@ -184,6 +216,31 @@ inline void build_bvh(const TriMesh& tri_mesh, auto [bboxes, centers] = bvh::compute_bounding_boxes_and_centers(primitives.data(), primitives.size()); auto global_bbox = bvh::compute_bounding_boxes_union(bboxes.get(), primitives.size()); + for (size_t i = 0; i < num_tris; ++i) { + TriangleProxy t = primitives[i]; + + auto normal = normalize(cross(t.e1, t.e2)); + + auto& c0 = tri_mesh.texcoords[tri_mesh.indices[i * 4 + 0]]; + auto& c1 = tri_mesh.texcoords[tri_mesh.indices[i * 4 + 1]]; + auto& c2 = tri_mesh.texcoords[tri_mesh.indices[i * 4 + 2]]; + + primitives[i].c0 = c0; + primitives[i].c1 = c1; + primitives[i].c2 = c2; + + bvh::BoundingBox bb; + bb.extend(t.p0 ); + bb.extend(t.p1()); + bb.extend(t.p2()); + bb.extend(t.p0 + normal * 1.01f); + bb.extend(t.p1() + normal * 1.01f); + bb.extend(t.p2() + normal * 1.01f); + + bboxes[i] = bb; + + } + Bvh bvh; BvhBuilder builder(bvh); builder.build(global_bbox, primitives.data(), bboxes.get(), centers.get(), primitives.size()); diff --git a/src/backend/runtime/loader/LoaderShape.cpp b/src/backend/runtime/loader/LoaderShape.cpp index 6be15ef1e..51f8a3ab8 100644 --- a/src/backend/runtime/loader/LoaderShape.cpp +++ b/src/backend/runtime/loader/LoaderShape.cpp @@ -307,8 +307,11 @@ bool LoaderShape::load(LoaderContext& ctx, LoaderResult& result) // Build bounding box BoundingBox& bbox = boxes[i]; bbox = BoundingBox::Empty(); - for (const auto& v : mesh.vertices) - bbox.extend(v); + for (long unsigned int i = 0; i < mesh.vertices.size(); i++) { + bbox.extend(mesh.vertices[i]); + // extend to account for displaced surface(maximum displacement of 1) + bbox.extend(mesh.vertices[i] + mesh.normals[i] * 1.01f); + } bbox.inflate(1e-5f); // Make sure it has a volume };