diff --git a/CMakeLists.txt b/CMakeLists.txt index 52f4926..ee6f590 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,7 @@ add_subdirectory(ray_tracing__simple) add_subdirectory(ray_tracing_animation) add_subdirectory(ray_tracing_anyhit) add_subdirectory(ray_tracing_callable) +add_subdirectory(ray_tracing_gltf) add_subdirectory(ray_tracing_instances) add_subdirectory(ray_tracing_intersection) add_subdirectory(ray_tracing_jitter_cam) @@ -46,5 +47,3 @@ add_subdirectory(ray_tracing_manyhits) add_subdirectory(ray_tracing_rayquery) add_subdirectory(ray_tracing_reflections) - - diff --git a/docs/Images/vk_ray_tracing_gltf_KHR.png b/docs/Images/vk_ray_tracing_gltf_KHR.png new file mode 100644 index 0000000..ab21864 Binary files /dev/null and b/docs/Images/vk_ray_tracing_gltf_KHR.png differ diff --git a/docs/Images/vk_ray_tracing_gltf_KHR_2.png b/docs/Images/vk_ray_tracing_gltf_KHR_2.png new file mode 100644 index 0000000..a5aeed1 Binary files /dev/null and b/docs/Images/vk_ray_tracing_gltf_KHR_2.png differ diff --git a/media/scenes/cornellBox.bin b/media/scenes/cornellBox.bin new file mode 100644 index 0000000..70a0008 Binary files /dev/null and b/media/scenes/cornellBox.bin differ diff --git a/media/scenes/cornellBox.gltf b/media/scenes/cornellBox.gltf new file mode 100644 index 0000000..7c03f73 --- /dev/null +++ b/media/scenes/cornellBox.gltf @@ -0,0 +1,1570 @@ +{ + "accessors": [ + { + "bufferView": 0, + "componentType": 5126, + "count": 24, + "max": [ + 5.0, + 5.0, + 0.05000000074505806 + ], + "min": [ + -5.0, + -5.0, + -0.05000000074505806 + ], + "type": "VEC3" + }, + { + "bufferView": 1, + "componentType": 5126, + "count": 24, + "type": "VEC3" + }, + { + "bufferView": 2, + "componentType": 5126, + "count": 24, + "type": "VEC2" + }, + { + "bufferView": 3, + "componentType": 5125, + "count": 36, + "type": "SCALAR" + }, + { + "bufferView": 4, + "componentType": 5126, + "count": 24, + "max": [ + 5.0, + 0.05000000074505806, + 5.0 + ], + "min": [ + -5.0, + -0.05000000074505806, + -5.0 + ], + "type": "VEC3" + }, + { + "bufferView": 5, + "componentType": 5126, + "count": 24, + "type": "VEC3" + }, + { + "bufferView": 6, + "componentType": 5126, + "count": 24, + "type": "VEC2" + }, + { + "bufferView": 7, + "componentType": 5125, + "count": 36, + "type": "SCALAR" + }, + { + "bufferView": 8, + "componentType": 5126, + "count": 24, + "max": [ + 0.05000000074505806, + 5.0, + 5.0 + ], + "min": [ + -0.05000000074505806, + -5.0, + -5.0 + ], + "type": "VEC3" + }, + { + "bufferView": 9, + "componentType": 5126, + "count": 24, + "type": "VEC3" + }, + { + "bufferView": 10, + "componentType": 5126, + "count": 24, + "type": "VEC2" + }, + { + "bufferView": 11, + "componentType": 5125, + "count": 36, + "type": "SCALAR" + }, + { + "bufferView": 12, + "componentType": 5126, + "count": 24, + "max": [ + 0.5, + 0.05000000074505806, + 0.5 + ], + "min": [ + -0.5, + -0.05000000074505806, + -0.5 + ], + "type": "VEC3" + }, + { + "bufferView": 13, + "componentType": 5126, + "count": 24, + "type": "VEC3" + }, + { + "bufferView": 14, + "componentType": 5126, + "count": 24, + "type": "VEC2" + }, + { + "bufferView": 15, + "componentType": 5125, + "count": 36, + "type": "SCALAR" + }, + { + "bufferView": 16, + "componentType": 5126, + "count": 24, + "max": [ + 0.5, + 0.5, + 0.5 + ], + "min": [ + -0.5, + -0.5, + -0.5 + ], + "type": "VEC3" + }, + { + "bufferView": 17, + "componentType": 5126, + "count": 24, + "type": "VEC3" + }, + { + "bufferView": 18, + "componentType": 5126, + "count": 24, + "type": "VEC2" + }, + { + "bufferView": 19, + "componentType": 5125, + "count": 36, + "type": "SCALAR" + }, + { + "bufferView": 20, + "componentType": 5126, + "count": 24, + "max": [ + 1.25, + 3.0, + 1.25 + ], + "min": [ + -1.25, + -3.0, + -1.25 + ], + "type": "VEC3" + }, + { + "bufferView": 21, + "componentType": 5126, + "count": 24, + "type": "VEC3" + }, + { + "bufferView": 22, + "componentType": 5126, + "count": 24, + "type": "VEC2" + }, + { + "bufferView": 23, + "componentType": 5125, + "count": 36, + "type": "SCALAR" + }, + { + "bufferView": 24, + "componentType": 5126, + "count": 8320, + "max": [ + 1.0, + 1.0, + 1.0 + ], + "min": [ + -1.0, + -1.0, + -1.0 + ], + "type": "VEC3" + }, + { + "bufferView": 25, + "componentType": 5126, + "count": 8320, + "type": "VEC3" + }, + { + "bufferView": 26, + "componentType": 5126, + "count": 8320, + "type": "VEC2" + }, + { + "bufferView": 27, + "componentType": 5125, + "count": 24954, + "type": "SCALAR" + }, + { + "bufferView": 28, + "componentType": 5126, + "count": 8320, + "max": [ + 1.0, + 1.0, + 1.0 + ], + "min": [ + -1.0, + -1.0, + -1.0 + ], + "type": "VEC3" + }, + { + "bufferView": 29, + "componentType": 5126, + "count": 8320, + "type": "VEC3" + }, + { + "bufferView": 30, + "componentType": 5126, + "count": 8320, + "type": "VEC2" + }, + { + "bufferView": 31, + "componentType": 5125, + "count": 24954, + "type": "SCALAR" + } + ], + "asset": { + "copyright": "NVIDIA Corporation", + "generator": "Iray glTF plugin", + "version": "2.0" + }, + "bufferViews": [ + { + "buffer": 0, + "byteLength": 288, + "byteStride": 12 + }, + { + "buffer": 0, + "byteLength": 288, + "byteOffset": 288, + "byteStride": 12 + }, + { + "buffer": 0, + "byteLength": 192, + "byteOffset": 576, + "byteStride": 8 + }, + { + "buffer": 0, + "byteLength": 144, + "byteOffset": 768 + }, + { + "buffer": 0, + "byteLength": 288, + "byteOffset": 912, + "byteStride": 12 + }, + { + "buffer": 0, + "byteLength": 288, + "byteOffset": 1200, + "byteStride": 12 + }, + { + "buffer": 0, + "byteLength": 192, + "byteOffset": 1488, + "byteStride": 8 + }, + { + "buffer": 0, + "byteLength": 144, + "byteOffset": 1680 + }, + { + "buffer": 0, + "byteLength": 288, + "byteOffset": 1824, + "byteStride": 12 + }, + { + "buffer": 0, + "byteLength": 288, + "byteOffset": 2112, + "byteStride": 12 + }, + { + "buffer": 0, + "byteLength": 192, + "byteOffset": 2400, + "byteStride": 8 + }, + { + "buffer": 0, + "byteLength": 144, + "byteOffset": 2592 + }, + { + "buffer": 0, + "byteLength": 288, + "byteOffset": 2736, + "byteStride": 12 + }, + { + "buffer": 0, + "byteLength": 288, + "byteOffset": 3024, + "byteStride": 12 + }, + { + "buffer": 0, + "byteLength": 192, + "byteOffset": 3312, + "byteStride": 8 + }, + { + "buffer": 0, + "byteLength": 144, + "byteOffset": 3504 + }, + { + "buffer": 0, + "byteLength": 288, + "byteOffset": 3648, + "byteStride": 12 + }, + { + "buffer": 0, + "byteLength": 288, + "byteOffset": 3936, + "byteStride": 12 + }, + { + "buffer": 0, + "byteLength": 192, + "byteOffset": 4224, + "byteStride": 8 + }, + { + "buffer": 0, + "byteLength": 144, + "byteOffset": 4416 + }, + { + "buffer": 0, + "byteLength": 288, + "byteOffset": 4560, + "byteStride": 12 + }, + { + "buffer": 0, + "byteLength": 288, + "byteOffset": 4848, + "byteStride": 12 + }, + { + "buffer": 0, + "byteLength": 192, + "byteOffset": 5136, + "byteStride": 8 + }, + { + "buffer": 0, + "byteLength": 144, + "byteOffset": 5328 + }, + { + "buffer": 0, + "byteLength": 99840, + "byteOffset": 5472, + "byteStride": 12 + }, + { + "buffer": 0, + "byteLength": 99840, + "byteOffset": 105312, + "byteStride": 12 + }, + { + "buffer": 0, + "byteLength": 66560, + "byteOffset": 205152, + "byteStride": 8 + }, + { + "buffer": 0, + "byteLength": 99816, + "byteOffset": 271712 + }, + { + "buffer": 0, + "byteLength": 99840, + "byteOffset": 371528, + "byteStride": 12 + }, + { + "buffer": 0, + "byteLength": 99840, + "byteOffset": 471368, + "byteStride": 12 + }, + { + "buffer": 0, + "byteLength": 66560, + "byteOffset": 571208, + "byteStride": 8 + }, + { + "buffer": 0, + "byteLength": 99816, + "byteOffset": 637768 + } + ], + "buffers": [ + { + "byteLength": 737584, + "uri": "cornellBox.bin" + } + ], + "cameras": [ + { + "extras": { + "attributes_iray": { + "mip_burn_highlights": 0.699999988079071, + "mip_burn_highlights_per_component": false, + "mip_camera_shutter": 4.0, + "mip_cm2_factor": 1.0, + "mip_crush_blacks": 0.5, + "mip_f_number": 1.0, + "mip_film_iso": 100.0, + "mip_gamma": 2.200000047683716, + "mip_saturation": 1.0, + "mip_vignetting": 0.0, + "mip_whitepoint": [ + 1.0, + 1.0, + 1.0, + 1.0 + ], + "tm_enable_tonemapper": true, + "tm_tonemapper": "mia_exposure_photographic" + }, + "resolution": [ + 640, + 480 + ] + }, + "name": "default", + "perspective": { + "aspectRatio": 1.3333333333333333, + "yfov": 0.7175414562225342, + "zfar": 1000.0, + "znear": 0.1 + }, + "type": "perspective" + } + ], + "extensions": { + "NV_materials_mdl": { + "modules": [ + "mdl::base", + "mdl::gltf_support", + "mdl::nvidia::core_definitions", + "mdl::state" + ], + "shaders": [ + { + "arguments": { + "texture": 0 + }, + "definition": "mdl::base::environment_spherical(texture_2d)", + "name": "env_shd" + }, + { + "arguments": { + "metallic_factor": 0.0 + }, + "definition": "mdl::gltf_support::gltf_material", + "hash": [ + "0x2b557fab", + "0x5f2f13b5", + "0x5dbe3123", + "0x469f59c1" + ], + "name": "cube_instance_material" + }, + { + "arguments": { + "metallic_factor": 0.0 + }, + "definition": "mdl::gltf_support::gltf_material", + "hash": [ + "0x2b557fab", + "0x5f2f13b5", + "0x5dbe3123", + "0x469f59c1" + ], + "name": "cube_instance_2_material" + }, + { + "arguments": { + "base_color_factor": [ + 0.054592281579971313, + 1.0, + 0.0 + ] + }, + "definition": "mdl::gltf_support::gltf_material", + "hash": [ + "0x2b557fab", + "0x5f2f13b5", + "0x5dbe3123", + "0x469f59c1" + ], + "name": "gltf_material_mat" + }, + { + "arguments": { + "base_color_factor": [ + 1.0, + 0.0, + 0.00010718735575210303 + ], + "metallic_factor": 0.0 + }, + "definition": "mdl::gltf_support::gltf_material", + "hash": [ + "0x2b557fab", + "0x5f2f13b5", + "0x5dbe3123", + "0x469f59c1" + ], + "name": "cube_instance_5_material" + }, + { + "arguments": { + "emissive_factor": [ + 10.0, + 10.0, + 10.0 + ], + "metallic_factor": 0.0, + "roughness_factor": 0.0 + }, + "definition": "mdl::gltf_support::gltf_material", + "hash": [ + "0x2b557fab", + "0x5f2f13b5", + "0x5dbe3123", + "0x469f59c1" + ], + "name": "cube_instance_8_material" + }, + { + "arguments": { + "normal=": 7 + }, + "definition": "mdl::nvidia::core_definitions::flex_material", + "hash": [ + "0xcdccb2f5", + "0xccfa4281", + "0x41aa45c6", + "0x53936269" + ], + "name": "cube_instance_14_material" + }, + { + "definition": "mdl::state::normal()", + "name": "mdl::state::normal_801" + }, + { + "definition": "mdl::gltf_support::gltf_material", + "hash": [ + "0x2b557fab", + "0x5f2f13b5", + "0x5dbe3123", + "0x469f59c1" + ], + "name": "cube_instance_16_material" + }, + { + "arguments": { + "normal=": 10 + }, + "definition": "mdl::nvidia::core_definitions::thick_glass", + "hash": [ + "0x45a51489", + "0x068b105e", + "0xcfed7f3b", + "0x1b45b4f8" + ], + "name": "sphere_instance_material" + }, + { + "definition": "mdl::state::normal()", + "name": "mdl::state::normal_804" + }, + { + "arguments": { + "roughness_factor": 0.0 + }, + "definition": "mdl::gltf_support::gltf_material", + "hash": [ + "0x2b557fab", + "0x5f2f13b5", + "0x5dbe3123", + "0x469f59c1" + ], + "name": "sphere_instance_22_material" + } + ] + } + }, + "extensionsUsed": [ + "NV_materials_mdl", + "KHR_lights_punctual" + ], + "materials": [ + { + "doubleSided": true, + "extensions": { + "NV_materials_mdl": { + "mdl_shader": 1 + } + }, + "name": "cube_instance_material", + "pbrMetallicRoughness": { + "metallicFactor": 0.0 + } + }, + { + "doubleSided": true, + "extensions": { + "NV_materials_mdl": { + "mdl_shader": 2 + } + }, + "name": "cube_instance_2_material", + "pbrMetallicRoughness": { + "metallicFactor": 0.0 + } + }, + { + "doubleSided": true, + "extensions": { + "NV_materials_mdl": { + "mdl_shader": 3 + } + }, + "name": "gltf_material_mat", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.054592281579971313, + 1.0, + 0.0, + 1.0 + ] + } + }, + { + "doubleSided": true, + "extensions": { + "NV_materials_mdl": { + "mdl_shader": 4 + } + }, + "name": "cube_instance_5_material", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 1.0, + 0.0, + 0.00010718735575210303, + 1.0 + ], + "metallicFactor": 0.0 + } + }, + { + "doubleSided": true, + "emissiveFactor": [ + 10.0, + 10.0, + 10.0 + ], + "extensions": { + "NV_materials_mdl": { + "mdl_shader": 5 + } + }, + "name": "cube_instance_8_material", + "pbrMetallicRoughness": { + "metallicFactor": 0.0, + "roughnessFactor": 0.0 + } + }, + { + "doubleSided": true, + "extensions": { + "NV_materials_mdl": { + "mdl_shader": 6 + } + }, + "name": "cube_instance_14_material", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.5, + 0.5, + 0.5, + 1.0 + ] + } + }, + { + "doubleSided": true, + "extensions": { + "NV_materials_mdl": { + "mdl_shader": 8 + } + }, + "name": "cube_instance_16_material" + }, + { + "alphaMode": "BLEND", + "doubleSided": true, + "extensions": { + "NV_materials_mdl": { + "mdl_shader": 9 + } + }, + "name": "sphere_instance_material", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.949999988079071, + 0.949999988079071, + 0.949999988079071, + 0.050000011920928955 + ], + "roughnessFactor": 0.0 + } + }, + { + "doubleSided": true, + "extensions": { + "NV_materials_mdl": { + "mdl_shader": 11 + } + }, + "name": "sphere_instance_22_material", + "pbrMetallicRoughness": { + "roughnessFactor": 0.0 + } + } + ], + "meshes": [ + { + "name": "cube", + "primitives": [ + { + "attributes": { + "NORMAL": 1, + "POSITION": 0, + "TEXCOORD_0": 2 + }, + "indices": 3, + "material": 0, + "mode": 4 + } + ] + }, + { + "name": "cube_1", + "primitives": [ + { + "attributes": { + "NORMAL": 5, + "POSITION": 4, + "TEXCOORD_0": 6 + }, + "indices": 7, + "material": 1, + "mode": 4 + } + ] + }, + { + "name": "cube_4", + "primitives": [ + { + "attributes": { + "NORMAL": 9, + "POSITION": 8, + "TEXCOORD_0": 10 + }, + "indices": 11, + "material": 2, + "mode": 4 + } + ] + }, + { + "name": "cube_4", + "primitives": [ + { + "attributes": { + "NORMAL": 9, + "POSITION": 8, + "TEXCOORD_0": 10 + }, + "indices": 11, + "material": 3, + "mode": 4 + } + ] + }, + { + "name": "cube_7", + "primitives": [ + { + "attributes": { + "NORMAL": 13, + "POSITION": 12, + "TEXCOORD_0": 14 + }, + "indices": 15, + "material": 4, + "mode": 4 + } + ] + }, + { + "name": "cube_13", + "primitives": [ + { + "attributes": { + "NORMAL": 17, + "POSITION": 16, + "TEXCOORD_0": 18 + }, + "indices": 19, + "material": 5, + "mode": 4 + } + ] + }, + { + "name": "cube_15", + "primitives": [ + { + "attributes": { + "NORMAL": 21, + "POSITION": 20, + "TEXCOORD_0": 22 + }, + "indices": 23, + "material": 6, + "mode": 4 + } + ] + }, + { + "name": "sphere", + "primitives": [ + { + "attributes": { + "NORMAL": 25, + "POSITION": 24, + "TEXCOORD_0": 26 + }, + "indices": 27, + "material": 7, + "mode": 4 + } + ] + }, + { + "name": "sphere_21", + "primitives": [ + { + "attributes": { + "NORMAL": 29, + "POSITION": 28, + "TEXCOORD_0": 30 + }, + "indices": 31, + "material": 8, + "mode": 4 + } + ] + } + ], + "nodes": [ + { + "camera": 0, + "extras": { + "attributes_iray": { + "iview:fkey": -1, + "iview:fov": 53.130104064941406, + "iview:interest": [ + 0.0, + 0.0, + 0.0 + ], + "iview:position": [ + 0.0, + 0.0, + 19.99209213256836 + ], + "iview:roll": 0.0, + "iview:up": [ + 0.0, + 1.0, + 0.0 + ] + } + }, + "matrix": [ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + -0.0, + -0.0, + 1.0, + -0.0, + 0.0, + 0.0, + 19.992091970966317, + 1.0 + ], + "name": "CamInst" + }, + { + "extras": { + "attributes_iray": { + "caustic": true, + "caustic_cast": true, + "caustic_recv": true, + "face_back": true, + "face_front": true, + "finalgather": true, + "finalgather_cast": true, + "finalgather_recv": true, + "globillum": true, + "globillum_cast": true, + "globillum_recv": true, + "label": 6740987, + "pickable": true, + "reflection_cast": true, + "reflection_recv": true, + "refraction_cast": true, + "refraction_recv": true, + "shadow_cast": true, + "shadow_recv": true, + "transparency_cast": true, + "transparency_recv": true, + "visible": true + } + }, + "matrix": [ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + -0.0, + -0.0, + -5.0, + 1.0 + ], + "mesh": 0, + "name": "cube_instance" + }, + { + "extras": { + "attributes_iray": { + "caustic": true, + "caustic_cast": true, + "caustic_recv": true, + "face_back": true, + "face_front": true, + "finalgather": true, + "finalgather_cast": true, + "finalgather_recv": true, + "globillum": true, + "globillum_cast": true, + "globillum_recv": true, + "label": 11403449, + "pickable": true, + "reflection_cast": true, + "reflection_recv": true, + "refraction_cast": true, + "refraction_recv": true, + "shadow_cast": true, + "shadow_recv": true, + "transparency_cast": true, + "transparency_recv": true, + "visible": true + } + }, + "matrix": [ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + -0.0, + -5.0, + -0.0, + 1.0 + ], + "mesh": 1, + "name": "cube_instance_2" + }, + { + "extras": { + "attributes_iray": { + "caustic": true, + "caustic_cast": true, + "caustic_recv": true, + "face_back": true, + "face_front": true, + "finalgather": true, + "finalgather_cast": true, + "finalgather_recv": true, + "globillum": true, + "globillum_cast": true, + "globillum_recv": true, + "label": 3789598, + "pickable": true, + "reflection_cast": true, + "reflection_recv": true, + "refraction_cast": true, + "refraction_recv": true, + "shadow_cast": true, + "shadow_recv": true, + "transparency_cast": true, + "transparency_recv": true, + "visible": true + } + }, + "matrix": [ + 1.0, + 0.0, + 0.0, + 0.0, + -0.0, + 1.0, + -0.0, + -0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 5.0, + 0.0, + 1.0 + ], + "mesh": 1, + "name": "cube_instance_3" + }, + { + "extras": { + "attributes_iray": { + "caustic": true, + "caustic_cast": true, + "caustic_recv": true, + "face_back": true, + "face_front": true, + "finalgather": true, + "finalgather_cast": true, + "finalgather_recv": true, + "globillum": true, + "globillum_cast": true, + "globillum_recv": true, + "label": 14125590, + "pickable": true, + "reflection_cast": true, + "reflection_recv": true, + "refraction_cast": true, + "refraction_recv": true, + "shadow_cast": true, + "shadow_recv": true, + "transparency_cast": true, + "transparency_recv": true, + "visible": true + } + }, + "matrix": [ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + -5.0, + -0.0, + -0.0, + 1.0 + ], + "mesh": 2, + "name": "cube_instance_5" + }, + { + "extras": { + "attributes_iray": { + "caustic": true, + "caustic_cast": true, + "caustic_recv": true, + "face_back": true, + "face_front": true, + "finalgather": true, + "finalgather_cast": true, + "finalgather_recv": true, + "globillum": true, + "globillum_cast": true, + "globillum_recv": true, + "label": 1448622, + "pickable": true, + "reflection_cast": true, + "reflection_recv": true, + "refraction_cast": true, + "refraction_recv": true, + "shadow_cast": true, + "shadow_recv": true, + "transparency_cast": true, + "transparency_recv": true, + "visible": true + } + }, + "matrix": [ + 1.0, + -0.0, + -0.0, + -0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 5.0, + 0.0, + 0.0, + 1.0 + ], + "mesh": 3, + "name": "cube_instance_6" + }, + { + "extras": { + "attributes_iray": { + "caustic": true, + "caustic_cast": true, + "caustic_recv": true, + "face_back": true, + "face_front": true, + "finalgather": true, + "finalgather_cast": true, + "finalgather_recv": true, + "globillum": true, + "globillum_cast": true, + "globillum_recv": true, + "label": 144522, + "pickable": true, + "reflection_cast": true, + "reflection_recv": true, + "refraction_cast": true, + "refraction_recv": true, + "shadow_cast": true, + "shadow_recv": true, + "transparency_cast": true, + "transparency_recv": true, + "visible": true + } + }, + "matrix": [ + 3.0, + 0.0, + 0.0, + 0.0, + -0.0, + 1.0000000000000022, + -0.0, + -0.0, + 0.0, + 0.0, + 3.0, + 0.0, + 0.0, + 4.626189558760184, + 0.0, + 1.0 + ], + "mesh": 4, + "name": "cube_instance_8" + }, + { + "extras": { + "attributes_iray": { + "caustic": true, + "caustic_cast": true, + "caustic_recv": true, + "face_back": true, + "face_front": true, + "finalgather": true, + "finalgather_cast": true, + "finalgather_recv": true, + "globillum": true, + "globillum_cast": true, + "globillum_recv": true, + "label": 16672517, + "pickable": true, + "reflection_cast": true, + "reflection_recv": true, + "refraction_cast": true, + "refraction_recv": true, + "shadow_cast": true, + "shadow_recv": true, + "transparency_cast": true, + "transparency_recv": true, + "visible": true + } + }, + "matrix": [ + 1.4339410908776151, + -0.0, + -2.0478801107224793, + -0.0, + -3.172065784643304e-16, + 2.4999999999999996, + -4.758098676964956e-16, + 0.0, + 2.0478801107224793, + 0.0, + 1.4339410908776151, + 0.0, + 3.0, + -3.5, + 1.6790000000000003, + 1.0 + ], + "mesh": 5, + "name": "cube_instance_14" + }, + { + "extras": { + "attributes_iray": { + "caustic": true, + "caustic_cast": true, + "caustic_recv": true, + "face_back": true, + "face_front": true, + "finalgather": true, + "finalgather_cast": true, + "finalgather_recv": true, + "globillum": true, + "globillum_cast": true, + "globillum_recv": true, + "label": 59962, + "pickable": true, + "reflection_cast": true, + "reflection_recv": true, + "refraction_cast": true, + "refraction_recv": true, + "shadow_cast": true, + "shadow_recv": true, + "transparency_cast": true, + "transparency_recv": true, + "visible": true + } + }, + "matrix": [ + 0.9986295347545739, + 0.0, + -0.05233595624294368, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.052335956242943835, + 0.0, + 0.9986295347545738, + 0.0, + -2.5, + -2.0, + -2.0, + 1.0 + ], + "mesh": 6, + "name": "cube_instance_16" + }, + { + "extras": { + "attributes_iray": { + "caustic": true, + "caustic_cast": true, + "caustic_recv": true, + "face_back": true, + "face_front": true, + "finalgather": true, + "finalgather_cast": true, + "finalgather_recv": true, + "globillum": true, + "globillum_cast": true, + "globillum_recv": true, + "label": 16188119, + "pickable": true, + "reflection_cast": true, + "reflection_recv": true, + "refraction_cast": true, + "refraction_recv": true, + "shadow_cast": true, + "shadow_recv": true, + "transparency_cast": true, + "transparency_recv": true, + "visible": true + } + }, + "matrix": [ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 3.3113, + -1.0, + 0.0876, + 1.0 + ], + "mesh": 7, + "name": "sphere_instance" + }, + { + "extras": { + "attributes_iray": { + "caustic": true, + "caustic_cast": true, + "caustic_recv": true, + "face_back": true, + "face_front": true, + "finalgather": true, + "finalgather_cast": true, + "finalgather_recv": true, + "globillum": true, + "globillum_cast": true, + "globillum_recv": true, + "label": 14590298, + "pickable": true, + "reflection_cast": true, + "reflection_recv": true, + "refraction_cast": true, + "refraction_recv": true, + "shadow_cast": true, + "shadow_recv": true, + "transparency_cast": true, + "transparency_recv": true, + "visible": true + } + }, + "matrix": [ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + -3.0, + -4.0, + 3.0, + 1.0 + ], + "mesh": 8, + "name": "sphere_instance_22" + } + ], + "scene": 0, + "scenes": [ + { + "extras": { + "attributes_iray": { + "IVP_color": [ + 1.0, + 0.0, + 0.0, + 1.0 + ], + "depth": 4, + "depth_cutout": 16, + "depth_reflect": 4, + "depth_transp": 4, + "environment_dome_depth": 200.0, + "environment_dome_ground": false, + "environment_dome_ground_glossiness": 0.0, + "environment_dome_ground_legacy_reflection": false, + "environment_dome_ground_position": [ + 0.0, + 0.0, + 0.0 + ], + "environment_dome_ground_reflectivity": [ + 1.0, + 1.0, + 1.0, + 1.0 + ], + "environment_dome_ground_shadow_intensity": 1.0, + "environment_dome_ground_texturescale": 1.0, + "environment_dome_height": 200.0, + "environment_dome_mode": "infinite", + "environment_dome_position": [ + 0.0, + 0.0, + 0.0 + ], + "environment_dome_radius": 100.0, + "environment_dome_rotation_angle": 0.0, + "environment_dome_width": 200.0, + "environment_function=": 0, + "environment_function_intensity": 1.0, + "iray_instancing": "off", + "iview::hdr_ev": 0.0, + "iview::inline_color": [ + 1.0, + 0.0, + 0.0, + 1.0 + ], + "iview::inline_width": 1.0, + "iview::magnifier_size": 300, + "iview::offset": 10.0, + "iview::outline_color": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "iview::outline_width": 2.0, + "iview::overview": true, + "iview::zoom_factor": 1.0, + "iview:sunsky:active": true, + "iview:sunsky:date": "Tue Jun 16 2020", + "iview:sunsky:dst": true, + "iview:sunsky:latitude": 37.31999969482422, + "iview:sunsky:longitude": -122.02999877929688, + "iview:sunsky:time": "12:00:00", + "iview:sunsky:timezone": "America/Los_Angeles", + "progressive_rendering_max_samples": 10, + "samples": 4.0, + "shadow_cast": true, + "shadow_recv": true + } + }, + "nodes": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10 + ] + } + ], + "textures": [ + { + "extras": { + "gamma": 0.0 + }, + "name": "tex", + "source": 0 + } + ] +} diff --git a/ray_tracing_gltf/CMakeLists.txt b/ray_tracing_gltf/CMakeLists.txt new file mode 100644 index 0000000..f2c44ad --- /dev/null +++ b/ray_tracing_gltf/CMakeLists.txt @@ -0,0 +1,104 @@ +cmake_minimum_required(VERSION 2.8) + +get_filename_component(PROJNAME ${CMAKE_CURRENT_SOURCE_DIR} NAME) +SET(PROJNAME vk_${PROJNAME}_KHR) + +Project(${PROJNAME}) +Message(STATUS "-------------------------------") +Message(STATUS "Processing Project ${PROJNAME}:") + +##################################################################################### +_add_project_definitions(${PROJNAME}) + +##################################################################################### +# Source files for this project +# +file(GLOB SOURCE_FILES *.cpp *.hpp *.inl *.h *.c) +file(GLOB EXTRA_COMMON "../common/*.*") +list(APPEND COMMON_SOURCE_FILES ${EXTRA_COMMON}) +include_directories("../common") + + +##################################################################################### +# GLSL to SPIR-V custom build +# +# more than one file can be given: _compile_GLSL("GLSL_mesh.vert;GLSL_mesh.frag" "GLSL_mesh.spv" GLSL_SOURCES) +# the SpirV validator is fine as long as files are for different pipeline stages (entry points still need to be main()) +#_compile_GLSL( ) +SET(VULKAN_TARGET_ENV vulkan1.2) + +UNSET(GLSL_SOURCES) +UNSET(SPV_OUTPUT) +file(GLOB_RECURSE GLSL_HEADER_FILES "shaders/*.h" "shaders/*.glsl") +file(GLOB_RECURSE GLSL_SOURCE_FILES + "shaders/*.comp" + "shaders/*.frag" + "shaders/*.vert" + "shaders/*.rchit" + "shaders/*.rahit" + "shaders/*.rmiss" + "shaders/*.rgen" + ) +foreach(GLSL ${GLSL_SOURCE_FILES}) + get_filename_component(FILE_NAME ${GLSL} NAME) + _compile_GLSL(${GLSL} "shaders/${FILE_NAME}.spv" GLSL_SOURCES SPV_OUTPUT) +endforeach(GLSL) + +list(APPEND GLSL_SOURCES ${GLSL_HEADER_FILES}) +source_group(Shader_Files FILES ${GLSL_SOURCES}) + + +##################################################################################### +# Executable +# +# if(WIN32 AND NOT GLUT_FOUND) +# add_definitions(/wd4996) #remove printf warning +# add_definitions(/wd4244) #remove double to float conversion warning +# add_definitions(/wd4305) #remove double to float truncation warning +# else() +# add_definitions(-fpermissive) +# endif() +add_executable(${PROJNAME} ${SOURCE_FILES} ${COMMON_SOURCE_FILES} ${PACKAGE_SOURCE_FILES} ${GLSL_SOURCES} ${CUDA_FILES} ${CUBIN_SOURCES}) + +#_set_subsystem_console(${PROJNAME}) + +##################################################################################### +# common source code needed for this sample +# +source_group(common FILES + ${COMMON_SOURCE_FILES} + ${PACKAGE_SOURCE_FILES} +) +source_group("Source Files" FILES ${SOURCE_FILES}) + +# if(UNIX) +# set(UNIXLINKLIBS dl pthread) +# else() +# set(UNIXLINKLIBS) +# endif() + +##################################################################################### +# Linkage +# +target_link_libraries(${PROJNAME} ${PLATFORM_LIBRARIES} shared_sources) + +foreach(DEBUGLIB ${LIBRARIES_DEBUG}) + target_link_libraries(${PROJNAME} debug ${DEBUGLIB}) +endforeach(DEBUGLIB) + +foreach(RELEASELIB ${LIBRARIES_OPTIMIZED}) + target_link_libraries(${PROJNAME} optimized ${RELEASELIB}) +endforeach(RELEASELIB) + +##################################################################################### +# copies binaries that need to be put next to the exe files (ZLib, etc.) +# +_copy_binaries_to_target( ${PROJNAME} ) + + +install(FILES ${SPV_OUTPUT} CONFIGURATIONS Release DESTINATION "bin_${ARCH}/${PROJNAME}/shaders") +install(FILES ${SPV_OUTPUT} CONFIGURATIONS Debug DESTINATION "bin_${ARCH}_debug/${PROJNAME}/shaders") +install(FILES ${CUBIN_SOURCES} CONFIGURATIONS Release DESTINATION "bin_${ARCH}/${PROJNAME}") +install(FILES ${CUBIN_SOURCES} CONFIGURATIONS Debug DESTINATION "bin_${ARCH}_debug/${PROJNAME}") +install(DIRECTORY "../media" CONFIGURATIONS Release DESTINATION "bin_${ARCH}/${PROJNAME}") +install(DIRECTORY "../media" CONFIGURATIONS Debug DESTINATION "bin_${ARCH}_debug/${PROJNAME}") diff --git a/ray_tracing_gltf/README.md b/ray_tracing_gltf/README.md new file mode 100644 index 0000000..255f01f --- /dev/null +++ b/ray_tracing_gltf/README.md @@ -0,0 +1,525 @@ +# NVIDIA Vulkan Ray Tracing Tutorial - glTF Scene + +This example is the result of the modification of the [simple ray tracing](../ray_tracing__simple) tutorial. +Instead of loading separated OBJ objects, the example was modified to load glTF scene files containing multiple objects. + +This example is not about shading, but using more complex data than OBJ. + +![img](../docs/Images/vk_ray_tracing_gltf_KHR.png) + +## Scene Data + +The OBJ models were loaded and stored in four buffers: + +* vertices: array of structure of position, normal, texcoord, color +* indices: index of the vertex, every three makes a triangle +* materials: the wavefront material structure +* material index: material index per triangle. + +Since we could have multiple OBJ, we would have arrays of those buffers. + +With glTF scene, the data will be organized differently a choice we have made for convenience. Instead of having structure of vertices, +positions, normals and other attributes will be in separate buffers. There will be one single position buffer, +for all geometries of the scene, same for indices and other attributes. But for each geometry there is the information +of the number of elements and offsets. + +From the source tutorial, we will not need the following and therefore remove it: + +~~~~C + struct ObjModel {..}; + struct ObjInstance {..}; + std::vector m_objModel; + std::vector m_objInstance; + nvvk::Buffer m_sceneDesc; // Device buffer of the OBJ instances +~~~~ + +But instead, we will use this following structure to retrieve the information of the primitive that has been hit in the closest hit shader; + +~~~~C + // Structure used for retrieving the primitive information in the closest hit + // The gl_InstanceCustomIndexNV + struct RtPrimitiveLookup + { + uint32_t indexOffset; + uint32_t vertexOffset; + int materialIndex; + }; + ~~~~ + + And for holding the information, we will be using a helper class to hold glTF scene and buffers for the data. + + ~~~~C + nvh::GltfScene m_gltfScene; + nvvk::Buffer m_vertexBuffer; + nvvk::Buffer m_normalBuffer; + nvvk::Buffer m_uvBuffer; + nvvk::Buffer m_indexBuffer; + nvvk::Buffer m_materialBuffer; + nvvk::Buffer m_matrixBuffer; + nvvk::Buffer m_rtPrimLookup; +~~~~ + +## Loading glTF scene + +To load the scene, we will be using [TinyGLTF](https://github.com/syoyo/tinygltf) from Syoyo Fujita, then to avoid traversing +the scene graph, the information will be flatten using the helper [gltfScene](https://github.com/nvpro-samples/shared_sources/tree/master/nvh#gltfscenehpp). + +### Loading Scene + +Instead of loading a model, we will be loading a scene, so we are replacing `loadModel()` by `loadScene()`. + +In the source file, loading the scene `loadScene()` will have first the glTF import with TinyGLTF. + +~~~~C + tinygltf::Model tmodel; + tinygltf::TinyGLTF tcontext; + std::string warn, error; + + if(!tcontext.LoadASCIIFromFile(&tmodel, &error, &warn, filename)) + assert(!"Error while loading scene"); +~~~~ + +Then we will flatten the scene graph and grab the information we will need using the gltfScene helper. + +~~~~C + m_gltfScene.importMaterials(tmodel); + m_gltfScene.importDrawableNodes(tmodel, + nvh::GltfAttributes::Normal | nvh::GltfAttributes::Texcoord_0); +~~~~ + +The next par is to allocate the buffers to hold the information, such as the positions, normals, texture coordinates, etc. + +~~~~C + m_vertexBuffer = + m_alloc.createBuffer(cmdBuf, m_gltfScene.m_positions, + vkBU::eVertexBuffer | vkBU::eStorageBuffer | vkBU::eShaderDeviceAddress); + m_indexBuffer = + m_alloc.createBuffer(cmdBuf, m_gltfScene.m_indices, + vkBU::eIndexBuffer | vkBU::eStorageBuffer | vkBU::eShaderDeviceAddress); + m_normalBuffer = m_alloc.createBuffer(cmdBuf, m_gltfScene.m_normals, + vkBU::eVertexBuffer | vkBU::eStorageBuffer); + m_uvBuffer = m_alloc.createBuffer(cmdBuf, m_gltfScene.m_texcoords0, + vkBU::eVertexBuffer | vkBU::eStorageBuffer); + m_materialBuffer = m_alloc.createBuffer(cmdBuf, m_gltfScene.m_materials, vkBU::eStorageBuffer); +~~~~ + +We could use `push_constant` to set the matrix of the node, but instead, we will push the index of the +node to draw and fetch the matrix from a buffer. + +~~~~C + std::vector nodeMatrices; + for(auto& node : m_gltfScene.m_nodes) + nodeMatrices.emplace_back(node.worldMatrix); + m_matrixBuffer = m_alloc.createBuffer(cmdBuf, nodeMatrices, vkBU::eStorageBuffer); +~~~~ + +To find the positions of the triangle hit in the closest hit shader, as well as the other +attributes, we will store the offsets information of that geometry. + +~~~~C + // The following is used to find the primitive mesh information in the CHIT + std::vector primLookup; + for(auto& primMesh : m_gltfScene.m_primMeshes) + primLookup.push_back({primMesh.firstIndex, primMesh.vertexOffset, primMesh.materialIndex}); + m_rtPrimLookup = + m_alloc.createBuffer(cmdBuf, primLookup, vk::BufferUsageFlagBits::eStorageBuffer); +~~~~ + + +## Converting geometry to BLAS + +Instead of `objectToVkGeometryKHR()`, we will be using `primitiveToGeometry(const nvh::GltfPrimMesh& prim)`. +The function is similar, only the input is different. + +~~~~C +//-------------------------------------------------------------------------------------------------- +// Converting a GLTF primitive in the Raytracing Geometry used for the BLAS +// +nvvk::RaytracingBuilderKHR::Blas HelloVulkan::primitiveToGeometry(const nvh::GltfPrimMesh& prim) +{ + // Setting up the creation info of acceleration structure + vk::AccelerationStructureCreateGeometryTypeInfoKHR asCreate; + asCreate.setGeometryType(vk::GeometryTypeKHR::eTriangles); + asCreate.setIndexType(vk::IndexType::eUint32); + asCreate.setVertexFormat(vk::Format::eR32G32B32Sfloat); + asCreate.setMaxPrimitiveCount(prim.indexCount / 3); // Nb triangles + asCreate.setMaxVertexCount(prim.vertexCount); + asCreate.setAllowsTransforms(VK_FALSE); // No adding transformation matrices + + // Building part + vk::DeviceAddress vertexAddress = m_device.getBufferAddress({m_vertexBuffer.buffer}); + vk::DeviceAddress indexAddress = m_device.getBufferAddress({m_indexBuffer.buffer}); + + vk::AccelerationStructureGeometryTrianglesDataKHR triangles; + triangles.setVertexFormat(asCreate.vertexFormat); + triangles.setVertexData(vertexAddress); + triangles.setVertexStride(sizeof(nvmath::vec3f)); + triangles.setIndexType(asCreate.indexType); + triangles.setIndexData(indexAddress); + triangles.setTransformData({}); + + // Setting up the build info of the acceleration + vk::AccelerationStructureGeometryKHR asGeom; + asGeom.setGeometryType(asCreate.geometryType); + asGeom.setFlags(vk::GeometryFlagBitsKHR::eNoDuplicateAnyHitInvocation); // For AnyHit + asGeom.geometry.setTriangles(triangles); + + + vk::AccelerationStructureBuildOffsetInfoKHR offset; + offset.setFirstVertex(prim.vertexOffset); + offset.setPrimitiveCount(prim.indexCount / 3); + offset.setPrimitiveOffset(prim.firstIndex * sizeof(uint32_t)); + offset.setTransformOffset(0); + + nvvk::RaytracingBuilderKHR::Blas blas; + blas.asGeometry.emplace_back(asGeom); + blas.asCreateGeometryInfo.emplace_back(asCreate); + blas.asBuildOffsetInfo.emplace_back(offset); + return blas; +} +~~~~ + +## Top Level creation + +There are almost no changes for creating the TLAS but is actually even simpler. Each +drawable node has a matrix and an index to the geometry, which in our case, also +correspond directly to the BLAS ID. To know which geometry is used, and to find back +all the data (see structure `RtPrimitiveLookup`), we will set the `instanceId` member +to the primitive mesh id. This value will be recovered with `gl_InstanceCustomIndexEXT` +in the closest hit shader. + +~~~~C + for(auto& node : m_gltfScene.m_nodes) + { + nvvk::RaytracingBuilderKHR::Instance rayInst; + rayInst.transform = node.worldMatrix; + rayInst.instanceId = node.primMesh; // gl_InstanceCustomIndexEXT: to find which primitive + rayInst.blasId = node.primMesh; + rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + rayInst.hitGroupId = 0; // We will use the same hit group for all objects + tlas.emplace_back(rayInst); + } +~~~~ + + +## Raster Rendering + +Raster rendering is simple. The shader was changed to use vertex, normal and texture coordinates. For +each node, we will be pushing the instance Id (retrieve the matrix) and the material Id. Since we +don't have a scene graph, we could loop over all drawable nodes. + +~~~~C + std::vector vertexBuffers = {m_vertexBuffer.buffer, m_normalBuffer.buffer, + m_uvBuffer.buffer}; + cmdBuf.bindVertexBuffers(0, static_cast(vertexBuffers.size()), vertexBuffers.data(), + offsets.data()); + cmdBuf.bindIndexBuffer(m_indexBuffer.buffer, 0, vk::IndexType::eUint32); + + uint32_t idxNode = 0; + for(auto& node : m_gltfScene.m_nodes) + { + auto& primitive = m_gltfScene.m_primMeshes[node.primMesh]; + + m_pushConstant.instanceId = idxNode++; + m_pushConstant.materialId = primitive.materialIndex; + cmdBuf.pushConstants( + m_pipelineLayout, vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, 0, + m_pushConstant); + cmdBuf.drawIndexed(primitive.indexCount, 1, primitive.firstIndex, primitive.vertexOffset, 0); + } +~~~~ + + +## Ray tracing change + +In `createRtDescriptorSet()`, the only change we will add is the primitive info buffer to retrieve +the data when hitting a triangle. + +~~~~C +m_rtDescSetLayoutBind.addBinding( + vkDSLB(2, vkDT::eStorageBuffer, 1, vkSS::eClosestHitNV | vkSS::eAnyHitNV)); // Primitive info +.... +vk::DescriptorBufferInfo primitiveInfoDesc{m_rtPrimLookup.buffer, 0, VK_WHOLE_SIZE}; +.... +writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, 2, &primitiveInfoDesc)); +~~~~ + + +## Descriptors and Pipeline Changes + +Since we are using different buffers and the vertex is no longer a struct but is using +3 different buffers for the position, normal and texture coord. +The methods `createDescriptorSetLayout()`, `updateDescriptorSet()` and `createGraphicsPipeline()` +will be changed accordingly. + +See [hello_vulkan](hello_vulkan.cpp) + + +## Shaders + +The shading is the same and is not reflecting the glTF PBR shading model, but the shaders were nevertheless +changed to fit the new incoming format. + +* Raster : [vertex](shaders/vert_shader.vert), [fragment](shaders/frag_shader.frag) +* Ray Trace: [RayGen](shaders/raytrace.rgen), [ClosestHit](shaders/raytrace.rchit) + + +## Other changes + +Small other changes were done, a different scene, different camera and light position. + +Camera position +~~~~C + CameraManip.setLookat(nvmath::vec3f(0, 0, 15), nvmath::vec3f(0, 0, 0), nvmath::vec3f(0, 1, 0)); +~~~~ + +Scene +~~~~C + helloVk.loadScene(nvh::findFile("media/scenes/cornellBox.gltf", defaultSearchPaths)); +~~~~ + +Light Position +~~~~C + nvmath::vec3f lightPosition{0.f, 4.5f, 0.f}; +~~~~ + +# Simple Path Tracing + +To convert this example to a simple path tracer (see Wikipedia [Path Tracing](https://en.wikipedia.org/wiki/Path_tracing)), we need to change the `RayGen` and the `ClosestHit` shaders. +Before doing this, we will modify the application to send the current rendering frame, allowing to accumulate +samples. + + +![img](../docs/Images/vk_ray_tracing_gltf_KHR_2.png) + +Add the following two functions in `hello_vulkan.cpp`: + +~~~~C +//-------------------------------------------------------------------------------------------------- +// If the camera matrix has changed, resets the frame. +// otherwise, increments frame. +// +void HelloVulkan::updateFrame() +{ + static nvmath::mat4f refCamMatrix; + + auto& m = CameraManip.getMatrix(); + if(memcmp(&refCamMatrix.a00, &m.a00, sizeof(nvmath::mat4f)) != 0) + { + resetFrame(); + refCamMatrix = m; + } + m_rtPushConstants.frame++; +} + +void HelloVulkan::resetFrame() +{ + m_rtPushConstants.frame = -1; +} +~~~~ + +And call `updateFrame()` in the begining of the `raytrace()` function. + +In `hello_vulkan.cpp`, add the function declarations + +~~~~C + void updateFrame(); + void resetFrame(); +~~~~ + +And add a new `frame` member at the end of `RtPushConstant` structure. + +## Ray Generation + +There are a few modifications to be done in the ray generation. First, it will use the clock for its random seed number. + +This is done by adding the [`GL_ARB_shader_clock`](https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_shader_clock.txt) extension. + +~~~~C +#extension GL_ARB_shader_clock : enable +~~~~ + +The random number generator is in `sampling.glsl`, `#include` this file. + +In `main()`, we will initialize the random number like this: (see tutorial on jitter camera) + +~~~~C + // Initialize the random number + uint seed = tea(gl_LaunchIDEXT.y * gl_LaunchSizeEXT.x + gl_LaunchIDEXT.x, int(clockARB())); +~~~~ + +To accumulate the samples, instead of only write to the image, we will also use the previous frame. + +~~~~C + // Do accumulation over time + if(pushC.frame > 0) + { + float a = 1.0f / float(pushC.frame + 1); + vec3 old_color = imageLoad(image, ivec2(gl_LaunchIDEXT.xy)).xyz; + imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(mix(old_color, prd.hitValue, a), 1.f)); + } + else + { + // First frame, replace the value in the buffer + imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(prd.hitValue, 1.f)); + } +~~~~ + +Extra information will be needed in the ray payload `hitPayload`, the `seed` and the `depth`. + +The modification in `raycommon.glsl` +~~~~C +struct hitPayload +{ + vec3 hitValue; + uint seed; + uint depth; +}; +~~~~ + +## Closest Hit Shader + +This modification will recursively trace until the `depth`hits 10 (hardcoded) or hit an emissive element (light). + +The only information that we will keep from the shader, is the calculation of the hit state: position, normal. So +all code from `// Vector toward the light` to the end can be remove and be replaced by the following. + +~~~~C + // https://en.wikipedia.org/wiki/Path_tracing + // Material of the object + GltfMaterial mat = materials[nonuniformEXT(matIndex)]; + vec3 emittance = mat.emissiveFactor; + + // Pick a random direction from here and keep going. + vec3 tangent, bitangent; + createCoordinateSystem(world_normal, tangent, bitangent); + vec3 rayOrigin = world_position; + vec3 rayDirection = samplingHemisphere(prd.seed, tangent, bitangent, world_normal); + + // Probability of the newRay (cosine distributed) + const float p = 1 / M_PI; + + // Compute the BRDF for this ray (assuming Lambertian reflection) + float cos_theta = dot(rayDirection, world_normal); + vec3 BRDF = mat.pbrBaseColorFactor.xyz / M_PI; + + // Recursively trace reflected light sources. + if(prd.depth < 10) + { + prd.depth++; + float tMin = 0.001; + float tMax = 100000000.0; + uint flags = gl_RayFlagsOpaqueEXT; + traceRayEXT(topLevelAS, // acceleration structure + flags, // rayFlags + 0xFF, // cullMask + 0, // sbtRecordOffset + 0, // sbtRecordStride + 0, // missIndex + rayOrigin, // ray origin + tMin, // ray min range + rayDirection, // ray direction + tMax, // ray max range + 0 // payload (location = 0) + ); + } + vec3 incoming = prd.hitValue; + + // Apply the Rendering Equation here. + prd.hitValue = emittance + (BRDF * incoming * cos_theta / p); +~~~~ + + +## Miss Shader + +To avoid contribution from the environment. + +~~~~C +void main() +{ + if(prd.depth == 0) + prd.hitValue = clearColor.xyz * 0.8; + else + prd.hitValue = vec3(0.01); // Tiny contribution from environment + prd.depth = 100; // Ending trace +} +~~~~ + +# Faster Path Tracer + +The implementation above is recursive and this is really not optimal. As described in the [reflection](../vk_ray_tracing_reflection) +tutorial, the best is to break the recursivity and do most of the work in the `RayGen`. + +The following change can give up to **3 time faster** rendering. + +To be able to do this, we need to extend the ray `payload` to bring data from the `Closest Hit` to the `RayGen`, which is the +ray origin and direction and the BRDF weight. + +~~~~C +struct hitPayload +{ + vec3 hitValue; + uint seed; + uint depth; + vec3 rayOrigin; + vec3 rayDirection; + vec3 weight; +}; +~~~~ + +## Closest Hit + +We don't need to trace anymore, so before tracing a new ray, we can store the information in +the `payload` and return before the recursion code. + +~~~~C + prd.rayOrigin = rayOrigin; + prd.rayDirection = rayDirection; + prd.hitValue = emittance; + prd.weight = BRDF * cos_theta / p; + return; +~~~~ + +## Ray Generation + +The ray generation is the one that will do the trace loop. + +First initialize the `payload` and variable to compute the accumulation. + +~~~~C + prd.rayOrigin = origin.xyz; + prd.rayDirection = direction.xyz; + prd.weight = vec3(0); + + vec3 curWeight = vec3(1); + vec3 hitValue = vec3(0); +~~~~ + +Now the loop over the trace function, will be like the following. + +**Note:** the depth is hardcode, but could be a parameter to the `push constant`. + +~~~~C + for(; prd.depth < 10; prd.depth++) + { + traceRayEXT(topLevelAS, // acceleration structure + rayFlags, // rayFlags + 0xFF, // cullMask + 0, // sbtRecordOffset + 0, // sbtRecordStride + 0, // missIndex + prd.rayOrigin, // ray origin + tMin, // ray min range + prd.rayDirection, // ray direction + tMax, // ray max range + 0 // payload (location = 0) + ); + + hitValue += prd.hitValue * curWeight; + curWeight *= prd.weight; + } +~~~~ + +**Note:** do not forget to use `hitValue` in the `imageStore`. + + diff --git a/ray_tracing_gltf/hello_vulkan.cpp b/ray_tracing_gltf/hello_vulkan.cpp new file mode 100644 index 0000000..f6fbcb5 --- /dev/null +++ b/ray_tracing_gltf/hello_vulkan.cpp @@ -0,0 +1,914 @@ +/* Copyright (c) 2014-2018, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +extern std::vector defaultSearchPaths; + +#define TINYGLTF_IMPLEMENTATION +#define STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_WRITE_IMPLEMENTATION + + +#include "hello_vulkan.h" +#include "nvh/cameramanipulator.hpp" +#include "nvh/fileoperations.hpp" +#include "nvh/gltfscene.hpp" +#include "nvvk/commands_vk.hpp" +#include "nvvk/descriptorsets_vk.hpp" +#include "nvvk/pipeline_vk.hpp" +#include "nvvk/renderpasses_vk.hpp" +#include "nvvk/shaders_vk.hpp" + +#include "shaders/binding.glsl" + +// Holding the camera matrices +struct CameraMatrices +{ + nvmath::mat4f view; + nvmath::mat4f proj; + nvmath::mat4f viewInverse; + // #VKRay + nvmath::mat4f projInverse; +}; + +//-------------------------------------------------------------------------------------------------- +// Keep the handle on the device +// Initialize the tool to do all our allocations: buffers, images +// +void HelloVulkan::setup(const vk::Instance& instance, + const vk::Device& device, + const vk::PhysicalDevice& physicalDevice, + uint32_t queueFamily) +{ + AppBase::setup(instance, device, physicalDevice, queueFamily); + m_alloc.init(device, physicalDevice); + m_debug.setup(m_device); +} + +//-------------------------------------------------------------------------------------------------- +// Called at each frame to update the camera matrix +// +void HelloVulkan::updateUniformBuffer() +{ + const float aspectRatio = m_size.width / static_cast(m_size.height); + + CameraMatrices ubo = {}; + ubo.view = CameraManip.getMatrix(); + ubo.proj = nvmath::perspectiveVK(CameraManip.getFov(), aspectRatio, 0.1f, 1000.0f); + // ubo.proj[1][1] *= -1; // Inverting Y for Vulkan + ubo.viewInverse = nvmath::invert(ubo.view); + // #VKRay + ubo.projInverse = nvmath::invert(ubo.proj); + + void* data = m_device.mapMemory(m_cameraMat.allocation, 0, sizeof(ubo)); + memcpy(data, &ubo, sizeof(ubo)); + m_device.unmapMemory(m_cameraMat.allocation); +} + +//-------------------------------------------------------------------------------------------------- +// Describing the layout pushed when rendering +// +void HelloVulkan::createDescriptorSetLayout() +{ + using vkDS = vk::DescriptorSetLayoutBinding; + using vkDT = vk::DescriptorType; + using vkSS = vk::ShaderStageFlagBits; + uint32_t nbTxt = static_cast(m_textures.size()); + + auto& bind = m_descSetLayoutBind; + // Camera matrices (binding = 0) + bind.addBinding(vkDS(B_CAMERA, vkDT::eUniformBuffer, 1, vkSS::eVertex | vkSS::eRaygenKHR)); + bind.addBinding( + vkDS(B_VERTICES, vkDT::eStorageBuffer, 1, vkSS::eClosestHitKHR | vkSS::eAnyHitKHR)); + bind.addBinding( + vkDS(B_INDICES, vkDT::eStorageBuffer, 1, vkSS::eClosestHitKHR | vkSS::eAnyHitKHR)); + bind.addBinding(vkDS(B_NORMALS, vkDT::eStorageBuffer, 1, vkSS::eClosestHitKHR)); + bind.addBinding(vkDS(B_TEXCOORDS, vkDT::eStorageBuffer, 1, vkSS::eClosestHitKHR)); + bind.addBinding(vkDS(B_MATERIALS, vkDT::eStorageBuffer, 1, + vkSS::eFragment | vkSS::eClosestHitKHR | vkSS::eAnyHitKHR)); + bind.addBinding(vkDS(B_MATRICES, vkDT::eStorageBuffer, 1, + vkSS::eVertex | vkSS::eClosestHitKHR | vkSS::eAnyHitKHR)); + auto nbTextures = static_cast(m_textures.size()); + bind.addBinding(vkDS(B_TEXTURES, vkDT::eCombinedImageSampler, nbTextures, + vkSS::eFragment | vkSS::eClosestHitKHR | vkSS::eAnyHitKHR)); + + + m_descSetLayout = m_descSetLayoutBind.createLayout(m_device); + m_descPool = m_descSetLayoutBind.createPool(m_device, 1); + m_descSet = nvvk::allocateDescriptorSet(m_device, m_descPool, m_descSetLayout); +} + +//-------------------------------------------------------------------------------------------------- +// Setting up the buffers in the descriptor set +// +void HelloVulkan::updateDescriptorSet() +{ + std::vector writes; + + // Camera matrices and scene description + vk::DescriptorBufferInfo dbiUnif{m_cameraMat.buffer, 0, VK_WHOLE_SIZE}; + vk::DescriptorBufferInfo vertexDesc{m_vertexBuffer.buffer, 0, VK_WHOLE_SIZE}; + vk::DescriptorBufferInfo indexDesc{m_indexBuffer.buffer, 0, VK_WHOLE_SIZE}; + vk::DescriptorBufferInfo normalDesc{m_normalBuffer.buffer, 0, VK_WHOLE_SIZE}; + vk::DescriptorBufferInfo uvDesc{m_uvBuffer.buffer, 0, VK_WHOLE_SIZE}; + vk::DescriptorBufferInfo materialDesc{m_materialBuffer.buffer, 0, VK_WHOLE_SIZE}; + vk::DescriptorBufferInfo matrixDesc{m_matrixBuffer.buffer, 0, VK_WHOLE_SIZE}; + + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, B_CAMERA, &dbiUnif)); + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, B_VERTICES, &vertexDesc)); + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, B_INDICES, &indexDesc)); + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, B_NORMALS, &normalDesc)); + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, B_TEXCOORDS, &uvDesc)); + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, B_MATERIALS, &materialDesc)); + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, B_MATRICES, &matrixDesc)); + + // All texture samplers + std::vector diit; + for(auto& texture : m_textures) + diit.emplace_back(texture.descriptor); + writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, B_TEXTURES, diit.data())); + + // Writing the information + m_device.updateDescriptorSets(static_cast(writes.size()), writes.data(), 0, nullptr); +} + +//-------------------------------------------------------------------------------------------------- +// Creating the pipeline layout +// +void HelloVulkan::createGraphicsPipeline() +{ + using vkSS = vk::ShaderStageFlagBits; + + vk::PushConstantRange pushConstantRanges = {vkSS::eVertex | vkSS::eFragment, 0, + sizeof(ObjPushConstant)}; + + // Creating the Pipeline Layout + vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo; + vk::DescriptorSetLayout descSetLayout(m_descSetLayout); + pipelineLayoutCreateInfo.setSetLayoutCount(1); + pipelineLayoutCreateInfo.setPSetLayouts(&descSetLayout); + pipelineLayoutCreateInfo.setPushConstantRangeCount(1); + pipelineLayoutCreateInfo.setPPushConstantRanges(&pushConstantRanges); + m_pipelineLayout = m_device.createPipelineLayout(pipelineLayoutCreateInfo); + + // Creating the Pipeline + std::vector paths = defaultSearchPaths; + nvvk::GraphicsPipelineGeneratorCombined gpb(m_device, m_pipelineLayout, m_offscreenRenderPass); + gpb.depthStencilState.depthTestEnable = true; + gpb.addShader(nvh::loadFile("shaders/vert_shader.vert.spv", true, paths), vkSS::eVertex); + gpb.addShader(nvh::loadFile("shaders/frag_shader.frag.spv", true, paths), vkSS::eFragment); + gpb.addBindingDescriptions( + {{0, sizeof(nvmath::vec3)}, {1, sizeof(nvmath::vec3)}, {2, sizeof(nvmath::vec2)}}); + gpb.addAttributeDescriptions({ + {0, 0, vk::Format::eR32G32B32Sfloat, 0}, // Position + {1, 1, vk::Format::eR32G32B32Sfloat, 0}, // Normal + {2, 2, vk::Format::eR32G32Sfloat, 0}, // Texcoord0 + }); + m_graphicsPipeline = gpb.createPipeline(); + m_debug.setObjectName(m_graphicsPipeline, "Graphics"); +} + +//-------------------------------------------------------------------------------------------------- +// Loading the OBJ file and setting up all buffers +// +void HelloVulkan::loadScene(const std::string& filename) +{ + using vkBU = vk::BufferUsageFlagBits; + tinygltf::Model tmodel; + tinygltf::TinyGLTF tcontext; + std::string warn, error; + + if(!tcontext.LoadASCIIFromFile(&tmodel, &error, &warn, filename)) + assert(!"Error while loading scene"); + + + m_gltfScene.importMaterials(tmodel); + m_gltfScene.importDrawableNodes(tmodel, + nvh::GltfAttributes::Normal | nvh::GltfAttributes::Texcoord_0); + + // Create the buffers on Device and copy vertices, indices and materials + nvvk::CommandPool cmdBufGet(m_device, m_graphicsQueueIndex); + vk::CommandBuffer cmdBuf = cmdBufGet.createCommandBuffer(); + + m_vertexBuffer = + m_alloc.createBuffer(cmdBuf, m_gltfScene.m_positions, + vkBU::eVertexBuffer | vkBU::eStorageBuffer | vkBU::eShaderDeviceAddress); + m_indexBuffer = + m_alloc.createBuffer(cmdBuf, m_gltfScene.m_indices, + vkBU::eIndexBuffer | vkBU::eStorageBuffer | vkBU::eShaderDeviceAddress); + m_normalBuffer = m_alloc.createBuffer(cmdBuf, m_gltfScene.m_normals, + vkBU::eVertexBuffer | vkBU::eStorageBuffer); + m_uvBuffer = m_alloc.createBuffer(cmdBuf, m_gltfScene.m_texcoords0, + vkBU::eVertexBuffer | vkBU::eStorageBuffer); + m_materialBuffer = m_alloc.createBuffer(cmdBuf, m_gltfScene.m_materials, vkBU::eStorageBuffer); + + // Instance Matrices used by rasterizer + std::vector nodeMatrices; + for(auto& node : m_gltfScene.m_nodes) + { + nodeMatrices.emplace_back(node.worldMatrix); + } + m_matrixBuffer = m_alloc.createBuffer(cmdBuf, nodeMatrices, vkBU::eStorageBuffer); + + // The following is used to find the primitive mesh information in the CHIT + std::vector primLookup; + for(auto& primMesh : m_gltfScene.m_primMeshes) + { + primLookup.push_back({primMesh.firstIndex, primMesh.vertexOffset, primMesh.materialIndex}); + } + m_rtPrimLookup = + m_alloc.createBuffer(cmdBuf, primLookup, vk::BufferUsageFlagBits::eStorageBuffer); + + + // Creates all textures found + createTextureImages(cmdBuf, tmodel); + cmdBufGet.submitAndWait(cmdBuf); + m_alloc.finalizeAndReleaseStaging(); + + m_debug.setObjectName(m_vertexBuffer.buffer, "Vertex"); + m_debug.setObjectName(m_indexBuffer.buffer, "Index"); + m_debug.setObjectName(m_normalBuffer.buffer, "Normal"); + m_debug.setObjectName(m_uvBuffer.buffer, "TexCoord"); + m_debug.setObjectName(m_materialBuffer.buffer, "Material"); + m_debug.setObjectName(m_matrixBuffer.buffer, "Matrix"); +} + + +//-------------------------------------------------------------------------------------------------- +// Creating the uniform buffer holding the camera matrices +// - Buffer is host visible +// +void HelloVulkan::createUniformBuffer() +{ + using vkBU = vk::BufferUsageFlagBits; + using vkMP = vk::MemoryPropertyFlagBits; + + m_cameraMat = m_alloc.createBuffer(sizeof(CameraMatrices), vkBU::eUniformBuffer, + vkMP::eHostVisible | vkMP::eHostCoherent); + m_debug.setObjectName(m_cameraMat.buffer, "cameraMat"); +} + +//-------------------------------------------------------------------------------------------------- +// Creating all textures and samplers +// +void HelloVulkan::createTextureImages(const vk::CommandBuffer& cmdBuf, tinygltf::Model& gltfModel) +{ + using vkIU = vk::ImageUsageFlagBits; + + vk::SamplerCreateInfo samplerCreateInfo{ + {}, vk::Filter::eLinear, vk::Filter::eLinear, vk::SamplerMipmapMode::eLinear}; + samplerCreateInfo.setMaxLod(FLT_MAX); + vk::Format format = vk::Format::eR8G8B8A8Srgb; + + if(gltfModel.images.empty()) + { + // Make dummy image(1,1), needed as we cannot have an empty array + nvvk::ScopeCommandBuffer cmdBuf(m_device, m_graphicsQueueIndex); + std::array white = {255, 255, 255, 255}; + m_textures.emplace_back(m_alloc.createTexture( + cmdBuf, 4, white.data(), nvvk::makeImage2DCreateInfo(vk::Extent2D{1, 1}), {})); + m_debug.setObjectName(m_textures[0].image, "dummy"); + return; + } + + m_textures.resize(gltfModel.images.size()); + for(size_t i = 0; i < gltfModel.images.size(); i++) + { + auto& gltfimage = gltfModel.images[i]; + void* buffer = &gltfimage.image[0]; + VkDeviceSize bufferSize = gltfimage.image.size(); + auto imgSize = vk::Extent2D(gltfimage.width, gltfimage.height); + vk::ImageCreateInfo imageCreateInfo = + nvvk::makeImage2DCreateInfo(imgSize, format, vkIU::eSampled, true); + + nvvk::Image image = m_alloc.createImage(cmdBuf, bufferSize, buffer, imageCreateInfo); + nvvk::cmdGenerateMipmaps(cmdBuf, image.image, format, imgSize, imageCreateInfo.mipLevels); + vk::ImageViewCreateInfo ivInfo = nvvk::makeImageViewCreateInfo(image.image, imageCreateInfo); + m_textures[i] = m_alloc.createTexture(image, ivInfo, samplerCreateInfo); + + m_debug.setObjectName(m_textures[i].image, std::string("Txt" + std::to_string(i)).c_str()); + } +} + +//-------------------------------------------------------------------------------------------------- +// Destroying all allocations +// +void HelloVulkan::destroyResources() +{ + m_device.destroy(m_graphicsPipeline); + m_device.destroy(m_pipelineLayout); + m_device.destroy(m_descPool); + m_device.destroy(m_descSetLayout); + m_alloc.destroy(m_cameraMat); + + m_alloc.destroy(m_vertexBuffer); + m_alloc.destroy(m_normalBuffer); + m_alloc.destroy(m_uvBuffer); + m_alloc.destroy(m_indexBuffer); + m_alloc.destroy(m_materialBuffer); + m_alloc.destroy(m_matrixBuffer); + m_alloc.destroy(m_rtPrimLookup); + + for(auto& t : m_textures) + { + m_alloc.destroy(t); + } + + //#Post + m_device.destroy(m_postPipeline); + m_device.destroy(m_postPipelineLayout); + m_device.destroy(m_postDescPool); + m_device.destroy(m_postDescSetLayout); + m_alloc.destroy(m_offscreenColor); + m_alloc.destroy(m_offscreenDepth); + m_device.destroy(m_offscreenRenderPass); + m_device.destroy(m_offscreenFramebuffer); + + // #VKRay + m_rtBuilder.destroy(); + m_device.destroy(m_rtDescPool); + m_device.destroy(m_rtDescSetLayout); + m_device.destroy(m_rtPipeline); + m_device.destroy(m_rtPipelineLayout); + m_alloc.destroy(m_rtSBTBuffer); +} + +//-------------------------------------------------------------------------------------------------- +// Drawing the scene in raster mode +// +void HelloVulkan::rasterize(const vk::CommandBuffer& cmdBuf) +{ + using vkPBP = vk::PipelineBindPoint; + using vkSS = vk::ShaderStageFlagBits; + + std::vector offsets = {0, 0, 0}; + + m_debug.beginLabel(cmdBuf, "Rasterize"); + + // Dynamic Viewport + cmdBuf.setViewport(0, {vk::Viewport(0, 0, (float)m_size.width, (float)m_size.height, 0, 1)}); + cmdBuf.setScissor(0, {{{0, 0}, {m_size.width, m_size.height}}}); + + // Drawing all triangles + cmdBuf.bindPipeline(vkPBP::eGraphics, m_graphicsPipeline); + cmdBuf.bindDescriptorSets(vkPBP::eGraphics, m_pipelineLayout, 0, {m_descSet}, {}); + std::vector vertexBuffers = {m_vertexBuffer.buffer, m_normalBuffer.buffer, + m_uvBuffer.buffer}; + cmdBuf.bindVertexBuffers(0, static_cast(vertexBuffers.size()), vertexBuffers.data(), + offsets.data()); + cmdBuf.bindIndexBuffer(m_indexBuffer.buffer, 0, vk::IndexType::eUint32); + + uint32_t idxNode = 0; + for(auto& node : m_gltfScene.m_nodes) + { + auto& primitive = m_gltfScene.m_primMeshes[node.primMesh]; + + m_pushConstant.instanceId = idxNode++; + m_pushConstant.materialId = primitive.materialIndex; + cmdBuf.pushConstants( + m_pipelineLayout, vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, 0, + m_pushConstant); + cmdBuf.drawIndexed(primitive.indexCount, 1, primitive.firstIndex, primitive.vertexOffset, 0); + } + + m_debug.endLabel(cmdBuf); +} + +//-------------------------------------------------------------------------------------------------- +// Handling resize of the window +// +void HelloVulkan::onResize(int /*w*/, int /*h*/) +{ + createOffscreenRender(); + updatePostDescriptorSet(); + updateRtDescriptorSet(); +} + +////////////////////////////////////////////////////////////////////////// +// Post-processing +////////////////////////////////////////////////////////////////////////// + +//-------------------------------------------------------------------------------------------------- +// Creating an offscreen frame buffer and the associated render pass +// +void HelloVulkan::createOffscreenRender() +{ + m_alloc.destroy(m_offscreenColor); + m_alloc.destroy(m_offscreenDepth); + + // Creating the color image + { + auto colorCreateInfo = nvvk::makeImage2DCreateInfo(m_size, m_offscreenColorFormat, + vk::ImageUsageFlagBits::eColorAttachment + | vk::ImageUsageFlagBits::eSampled + | vk::ImageUsageFlagBits::eStorage); + + + nvvk::Image image = m_alloc.createImage(colorCreateInfo); + vk::ImageViewCreateInfo ivInfo = nvvk::makeImageViewCreateInfo(image.image, colorCreateInfo); + m_offscreenColor = m_alloc.createTexture(image, ivInfo, vk::SamplerCreateInfo()); + m_offscreenColor.descriptor.imageLayout = VK_IMAGE_LAYOUT_GENERAL; + } + + // Creating the depth buffer + auto depthCreateInfo = + nvvk::makeImage2DCreateInfo(m_size, m_offscreenDepthFormat, + vk::ImageUsageFlagBits::eDepthStencilAttachment); + { + nvvk::Image image = m_alloc.createImage(depthCreateInfo); + + vk::ImageViewCreateInfo depthStencilView; + depthStencilView.setViewType(vk::ImageViewType::e2D); + depthStencilView.setFormat(m_offscreenDepthFormat); + depthStencilView.setSubresourceRange({vk::ImageAspectFlagBits::eDepth, 0, 1, 0, 1}); + depthStencilView.setImage(image.image); + + m_offscreenDepth = m_alloc.createTexture(image, depthStencilView); + } + + // Setting the image layout for both color and depth + { + nvvk::CommandPool genCmdBuf(m_device, m_graphicsQueueIndex); + auto cmdBuf = genCmdBuf.createCommandBuffer(); + nvvk::cmdBarrierImageLayout(cmdBuf, m_offscreenColor.image, vk::ImageLayout::eUndefined, + vk::ImageLayout::eGeneral); + nvvk::cmdBarrierImageLayout(cmdBuf, m_offscreenDepth.image, vk::ImageLayout::eUndefined, + vk::ImageLayout::eDepthStencilAttachmentOptimal, + vk::ImageAspectFlagBits::eDepth); + + genCmdBuf.submitAndWait(cmdBuf); + } + + // Creating a renderpass for the offscreen + if(!m_offscreenRenderPass) + { + m_offscreenRenderPass = + nvvk::createRenderPass(m_device, {m_offscreenColorFormat}, m_offscreenDepthFormat, 1, true, + true, vk::ImageLayout::eGeneral, vk::ImageLayout::eGeneral); + } + + // Creating the frame buffer for offscreen + std::vector attachments = {m_offscreenColor.descriptor.imageView, + m_offscreenDepth.descriptor.imageView}; + + m_device.destroy(m_offscreenFramebuffer); + vk::FramebufferCreateInfo info; + info.setRenderPass(m_offscreenRenderPass); + info.setAttachmentCount(2); + info.setPAttachments(attachments.data()); + info.setWidth(m_size.width); + info.setHeight(m_size.height); + info.setLayers(1); + m_offscreenFramebuffer = m_device.createFramebuffer(info); +} + +//-------------------------------------------------------------------------------------------------- +// The pipeline is how things are rendered, which shaders, type of primitives, depth test and more +// +void HelloVulkan::createPostPipeline() +{ + // Push constants in the fragment shader + vk::PushConstantRange pushConstantRanges = {vk::ShaderStageFlagBits::eFragment, 0, sizeof(float)}; + + // Creating the pipeline layout + vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo; + pipelineLayoutCreateInfo.setSetLayoutCount(1); + pipelineLayoutCreateInfo.setPSetLayouts(&m_postDescSetLayout); + pipelineLayoutCreateInfo.setPushConstantRangeCount(1); + pipelineLayoutCreateInfo.setPPushConstantRanges(&pushConstantRanges); + m_postPipelineLayout = m_device.createPipelineLayout(pipelineLayoutCreateInfo); + + // Pipeline: completely generic, no vertices + std::vector paths = defaultSearchPaths; + + nvvk::GraphicsPipelineGeneratorCombined pipelineGenerator(m_device, m_postPipelineLayout, + m_renderPass); + pipelineGenerator.addShader(nvh::loadFile("shaders/passthrough.vert.spv", true, paths), + vk::ShaderStageFlagBits::eVertex); + pipelineGenerator.addShader(nvh::loadFile("shaders/post.frag.spv", true, paths), + vk::ShaderStageFlagBits::eFragment); + pipelineGenerator.rasterizationState.setCullMode(vk::CullModeFlagBits::eNone); + m_postPipeline = pipelineGenerator.createPipeline(); + m_debug.setObjectName(m_postPipeline, "post"); +} + +//-------------------------------------------------------------------------------------------------- +// The descriptor layout is the description of the data that is passed to the vertex or the +// fragment program. +// +void HelloVulkan::createPostDescriptor() +{ + using vkDS = vk::DescriptorSetLayoutBinding; + using vkDT = vk::DescriptorType; + using vkSS = vk::ShaderStageFlagBits; + + m_postDescSetLayoutBind.addBinding(vkDS(0, vkDT::eCombinedImageSampler, 1, vkSS::eFragment)); + m_postDescSetLayout = m_postDescSetLayoutBind.createLayout(m_device); + m_postDescPool = m_postDescSetLayoutBind.createPool(m_device); + m_postDescSet = nvvk::allocateDescriptorSet(m_device, m_postDescPool, m_postDescSetLayout); +} + +//-------------------------------------------------------------------------------------------------- +// Update the output +// +void HelloVulkan::updatePostDescriptorSet() +{ + vk::WriteDescriptorSet writeDescriptorSets = + m_postDescSetLayoutBind.makeWrite(m_postDescSet, 0, &m_offscreenColor.descriptor); + m_device.updateDescriptorSets(writeDescriptorSets, nullptr); +} + +//-------------------------------------------------------------------------------------------------- +// Draw a full screen quad with the attached image +// +void HelloVulkan::drawPost(vk::CommandBuffer cmdBuf) +{ + m_debug.beginLabel(cmdBuf, "Post"); + + cmdBuf.setViewport(0, {vk::Viewport(0, 0, (float)m_size.width, (float)m_size.height, 0, 1)}); + cmdBuf.setScissor(0, {{{0, 0}, {m_size.width, m_size.height}}}); + + auto aspectRatio = static_cast(m_size.width) / static_cast(m_size.height); + cmdBuf.pushConstants(m_postPipelineLayout, vk::ShaderStageFlagBits::eFragment, 0, + aspectRatio); + cmdBuf.bindPipeline(vk::PipelineBindPoint::eGraphics, m_postPipeline); + cmdBuf.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, m_postPipelineLayout, 0, + m_postDescSet, {}); + cmdBuf.draw(3, 1, 0, 0); + + m_debug.endLabel(cmdBuf); +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +//-------------------------------------------------------------------------------------------------- +// Initialize Vulkan ray tracing +// #VKRay +void HelloVulkan::initRayTracing() +{ + // Requesting ray tracing properties + auto properties = m_physicalDevice.getProperties2(); + m_rtProperties = properties.get(); + m_rtBuilder.setup(m_device, &m_alloc, m_graphicsQueueIndex); +} + +//-------------------------------------------------------------------------------------------------- +// Converting a GLTF primitive in the Raytracing Geometry used for the BLAS +// +nvvk::RaytracingBuilderKHR::Blas HelloVulkan::primitiveToGeometry(const nvh::GltfPrimMesh& prim) +{ + // Setting up the creation info of acceleration structure + vk::AccelerationStructureCreateGeometryTypeInfoKHR asCreate; + asCreate.setGeometryType(vk::GeometryTypeKHR::eTriangles); + asCreate.setIndexType(vk::IndexType::eUint32); + asCreate.setVertexFormat(vk::Format::eR32G32B32Sfloat); + asCreate.setMaxPrimitiveCount(prim.indexCount / 3); // Nb triangles + asCreate.setMaxVertexCount(prim.vertexCount); + asCreate.setAllowsTransforms(VK_FALSE); // No adding transformation matrices + + // Building part + vk::DeviceAddress vertexAddress = m_device.getBufferAddress({m_vertexBuffer.buffer}); + vk::DeviceAddress indexAddress = m_device.getBufferAddress({m_indexBuffer.buffer}); + + vk::AccelerationStructureGeometryTrianglesDataKHR triangles; + triangles.setVertexFormat(asCreate.vertexFormat); + triangles.setVertexData(vertexAddress); + triangles.setVertexStride(sizeof(nvmath::vec3f)); + triangles.setIndexType(asCreate.indexType); + triangles.setIndexData(indexAddress); + triangles.setTransformData({}); + + // Setting up the build info of the acceleration + vk::AccelerationStructureGeometryKHR asGeom; + asGeom.setGeometryType(asCreate.geometryType); + asGeom.setFlags(vk::GeometryFlagBitsKHR::eNoDuplicateAnyHitInvocation); // For AnyHit + asGeom.geometry.setTriangles(triangles); + + + vk::AccelerationStructureBuildOffsetInfoKHR offset; + offset.setFirstVertex(prim.vertexOffset); + offset.setPrimitiveCount(prim.indexCount / 3); + offset.setPrimitiveOffset(prim.firstIndex * sizeof(uint32_t)); + offset.setTransformOffset(0); + + nvvk::RaytracingBuilderKHR::Blas blas; + blas.asGeometry.emplace_back(asGeom); + blas.asCreateGeometryInfo.emplace_back(asCreate); + blas.asBuildOffsetInfo.emplace_back(offset); + return blas; +} + +//-------------------------------------------------------------------------------------------------- +// +// +void HelloVulkan::createBottomLevelAS() +{ + // BLAS - Storing each primitive in a geometry + std::vector allBlas; + allBlas.reserve(m_gltfScene.m_primMeshes.size()); + for(auto& primMesh : m_gltfScene.m_primMeshes) + { + auto geo = primitiveToGeometry(primMesh); + allBlas.push_back({geo}); + } + m_rtBuilder.buildBlas(allBlas, vk::BuildAccelerationStructureFlagBitsKHR::ePreferFastTrace); +} + +void HelloVulkan::createTopLevelAS() +{ + std::vector tlas; + tlas.reserve(m_gltfScene.m_nodes.size()); + uint32_t instID = 0; + for(auto& node : m_gltfScene.m_nodes) + { + nvvk::RaytracingBuilderKHR::Instance rayInst; + rayInst.transform = node.worldMatrix; + rayInst.instanceId = node.primMesh; // gl_InstanceCustomIndexEXT: to find which primitive + rayInst.blasId = node.primMesh; + rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + rayInst.hitGroupId = 0; // We will use the same hit group for all objects + tlas.emplace_back(rayInst); + } + m_rtBuilder.buildTlas(tlas, vk::BuildAccelerationStructureFlagBitsKHR::ePreferFastTrace); +} + +//-------------------------------------------------------------------------------------------------- +// This descriptor set holds the Acceleration structure and the output image +// +void HelloVulkan::createRtDescriptorSet() +{ + using vkDT = vk::DescriptorType; + using vkSS = vk::ShaderStageFlagBits; + using vkDSLB = vk::DescriptorSetLayoutBinding; + + m_rtDescSetLayoutBind.addBinding(vkDSLB(0, vkDT::eAccelerationStructureKHR, 1, + vkSS::eRaygenKHR | vkSS::eClosestHitKHR)); // TLAS + m_rtDescSetLayoutBind.addBinding( + vkDSLB(1, vkDT::eStorageImage, 1, vkSS::eRaygenKHR)); // Output image + m_rtDescSetLayoutBind.addBinding( + vkDSLB(2, vkDT::eStorageBuffer, 1, vkSS::eClosestHitNV | vkSS::eAnyHitNV)); // Primitive info + + m_rtDescPool = m_rtDescSetLayoutBind.createPool(m_device); + m_rtDescSetLayout = m_rtDescSetLayoutBind.createLayout(m_device); + m_rtDescSet = m_device.allocateDescriptorSets({m_rtDescPool, 1, &m_rtDescSetLayout})[0]; + + vk::AccelerationStructureKHR tlas = m_rtBuilder.getAccelerationStructure(); + vk::WriteDescriptorSetAccelerationStructureKHR descASInfo; + descASInfo.setAccelerationStructureCount(1); + descASInfo.setPAccelerationStructures(&tlas); + vk::DescriptorImageInfo imageInfo{ + {}, m_offscreenColor.descriptor.imageView, vk::ImageLayout::eGeneral}; + vk::DescriptorBufferInfo primitiveInfoDesc{m_rtPrimLookup.buffer, 0, VK_WHOLE_SIZE}; + + std::vector writes; + writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, 0, &descASInfo)); + writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, 1, &imageInfo)); + writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, 2, &primitiveInfoDesc)); + m_device.updateDescriptorSets(static_cast(writes.size()), writes.data(), 0, nullptr); +} + + +//-------------------------------------------------------------------------------------------------- +// Writes the output image to the descriptor set +// - Required when changing resolution +// +void HelloVulkan::updateRtDescriptorSet() +{ + using vkDT = vk::DescriptorType; + + // (1) Output buffer + vk::DescriptorImageInfo imageInfo{ + {}, m_offscreenColor.descriptor.imageView, vk::ImageLayout::eGeneral}; + vk::WriteDescriptorSet wds{m_rtDescSet, 1, 0, 1, vkDT::eStorageImage, &imageInfo}; + m_device.updateDescriptorSets(wds, nullptr); +} + + +//-------------------------------------------------------------------------------------------------- +// Pipeline for the ray tracer: all shaders, raygen, chit, miss +// +void HelloVulkan::createRtPipeline() +{ + std::vector paths = defaultSearchPaths; + + vk::ShaderModule raygenSM = + nvvk::createShaderModule(m_device, // + nvh::loadFile("shaders/pathtrace.rgen.spv", true, paths)); + vk::ShaderModule missSM = + nvvk::createShaderModule(m_device, // + nvh::loadFile("shaders/pathtrace.rmiss.spv", true, paths)); + + // The second miss shader is invoked when a shadow ray misses the geometry. It + // simply indicates that no occlusion has been found + vk::ShaderModule shadowmissSM = + nvvk::createShaderModule(m_device, + nvh::loadFile("shaders/raytraceShadow.rmiss.spv", true, paths)); + + + std::vector stages; + + // Raygen + vk::RayTracingShaderGroupCreateInfoKHR rg{vk::RayTracingShaderGroupTypeKHR::eGeneral, + VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR, + VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR}; + stages.push_back({{}, vk::ShaderStageFlagBits::eRaygenKHR, raygenSM, "main"}); + rg.setGeneralShader(static_cast(stages.size() - 1)); + m_rtShaderGroups.push_back(rg); + // Miss + vk::RayTracingShaderGroupCreateInfoKHR mg{vk::RayTracingShaderGroupTypeKHR::eGeneral, + VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR, + VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR}; + stages.push_back({{}, vk::ShaderStageFlagBits::eMissKHR, missSM, "main"}); + mg.setGeneralShader(static_cast(stages.size() - 1)); + m_rtShaderGroups.push_back(mg); + // Shadow Miss + stages.push_back({{}, vk::ShaderStageFlagBits::eMissKHR, shadowmissSM, "main"}); + mg.setGeneralShader(static_cast(stages.size() - 1)); + m_rtShaderGroups.push_back(mg); + + // Hit Group - Closest Hit + AnyHit + vk::ShaderModule chitSM = + nvvk::createShaderModule(m_device, // + nvh::loadFile("shaders/pathtrace.rchit.spv", true, paths)); + + vk::RayTracingShaderGroupCreateInfoKHR hg{vk::RayTracingShaderGroupTypeKHR::eTrianglesHitGroup, + VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR, + VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR}; + stages.push_back({{}, vk::ShaderStageFlagBits::eClosestHitKHR, chitSM, "main"}); + hg.setClosestHitShader(static_cast(stages.size() - 1)); + m_rtShaderGroups.push_back(hg); + + vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo; + + // Push constant: we want to be able to update constants used by the shaders + vk::PushConstantRange pushConstant{vk::ShaderStageFlagBits::eRaygenKHR + | vk::ShaderStageFlagBits::eClosestHitKHR + | vk::ShaderStageFlagBits::eMissKHR, + 0, sizeof(RtPushConstant)}; + pipelineLayoutCreateInfo.setPushConstantRangeCount(1); + pipelineLayoutCreateInfo.setPPushConstantRanges(&pushConstant); + + // Descriptor sets: one specific to ray tracing, and one shared with the rasterization pipeline + std::vector rtDescSetLayouts = {m_rtDescSetLayout, m_descSetLayout}; + pipelineLayoutCreateInfo.setSetLayoutCount(static_cast(rtDescSetLayouts.size())); + pipelineLayoutCreateInfo.setPSetLayouts(rtDescSetLayouts.data()); + + m_rtPipelineLayout = m_device.createPipelineLayout(pipelineLayoutCreateInfo); + + // Assemble the shader stages and recursion depth info into the ray tracing pipeline + + vk::RayTracingPipelineCreateInfoKHR rayPipelineInfo; + rayPipelineInfo.setStageCount(static_cast(stages.size())); // Stages are shaders + rayPipelineInfo.setPStages(stages.data()); + + rayPipelineInfo.setGroupCount(static_cast( + m_rtShaderGroups.size())); // 1-raygen, n-miss, n-(hit[+anyhit+intersect]) + rayPipelineInfo.setPGroups(m_rtShaderGroups.data()); + + rayPipelineInfo.setMaxRecursionDepth(2); // Ray depth + rayPipelineInfo.setLayout(m_rtPipelineLayout); + m_rtPipeline = + static_cast(m_device.createRayTracingPipelineKHR({}, rayPipelineInfo)); + + m_device.destroy(raygenSM); + m_device.destroy(missSM); + m_device.destroy(shadowmissSM); + m_device.destroy(chitSM); +} + +//-------------------------------------------------------------------------------------------------- +// The Shader Binding Table (SBT) +// - getting all shader handles and writing them in a SBT buffer +// - Besides exception, this could be always done like this +// See how the SBT buffer is used in run() +// +void HelloVulkan::createRtShaderBindingTable() +{ + auto groupCount = + static_cast(m_rtShaderGroups.size()); // 3 shaders: raygen, miss, chit + uint32_t groupHandleSize = m_rtProperties.shaderGroupHandleSize; // Size of a program identifier + uint32_t baseAlignment = m_rtProperties.shaderGroupBaseAlignment; // Size of shader alignment + + // Fetch all the shader handles used in the pipeline, so that they can be written in the SBT + uint32_t sbtSize = groupCount * baseAlignment; + + std::vector shaderHandleStorage(sbtSize); + m_device.getRayTracingShaderGroupHandlesKHR(m_rtPipeline, 0, groupCount, sbtSize, + shaderHandleStorage.data()); + // Write the handles in the SBT + m_rtSBTBuffer = m_alloc.createBuffer(sbtSize, vk::BufferUsageFlagBits::eTransferSrc, + vk::MemoryPropertyFlagBits::eHostVisible + | vk::MemoryPropertyFlagBits::eHostCoherent); + m_debug.setObjectName(m_rtSBTBuffer.buffer, std::string("SBT").c_str()); + + // Write the handles in the SBT + void* mapped = m_alloc.map(m_rtSBTBuffer); + auto* pData = reinterpret_cast(mapped); + for(uint32_t g = 0; g < groupCount; g++) + { + memcpy(pData, shaderHandleStorage.data() + g * groupHandleSize, groupHandleSize); // raygen + pData += baseAlignment; + } + m_alloc.unmap(m_rtSBTBuffer); + + + m_alloc.finalizeAndReleaseStaging(); +} + +//-------------------------------------------------------------------------------------------------- +// Ray Tracing the scene +// +void HelloVulkan::raytrace(const vk::CommandBuffer& cmdBuf, const nvmath::vec4f& clearColor) +{ + updateFrame(); + + m_debug.beginLabel(cmdBuf, "Ray trace"); + // Initializing push constant values + m_rtPushConstants.clearColor = clearColor; + m_rtPushConstants.lightPosition = m_pushConstant.lightPosition; + m_rtPushConstants.lightIntensity = m_pushConstant.lightIntensity; + m_rtPushConstants.lightType = m_pushConstant.lightType; + + cmdBuf.bindPipeline(vk::PipelineBindPoint::eRayTracingKHR, m_rtPipeline); + cmdBuf.bindDescriptorSets(vk::PipelineBindPoint::eRayTracingKHR, m_rtPipelineLayout, 0, + {m_rtDescSet, m_descSet}, {}); + cmdBuf.pushConstants(m_rtPipelineLayout, + vk::ShaderStageFlagBits::eRaygenKHR + | vk::ShaderStageFlagBits::eClosestHitKHR + | vk::ShaderStageFlagBits::eMissKHR, + 0, m_rtPushConstants); + + vk::DeviceSize progSize = + m_rtProperties.shaderGroupBaseAlignment; // Size of a program identifier + vk::DeviceSize rayGenOffset = 0u * progSize; // Start at the beginning of m_sbtBuffer + vk::DeviceSize missOffset = 1u * progSize; // Jump over raygen + vk::DeviceSize hitGroupOffset = 3u * progSize; // Jump over the previous shaders + + vk::DeviceSize sbtSize = progSize * (vk::DeviceSize)m_rtShaderGroups.size(); + + const vk::StridedBufferRegionKHR raygenShaderBindingTable = {m_rtSBTBuffer.buffer, rayGenOffset, + progSize, sbtSize}; + const vk::StridedBufferRegionKHR missShaderBindingTable = {m_rtSBTBuffer.buffer, missOffset, + progSize, sbtSize}; + const vk::StridedBufferRegionKHR hitShaderBindingTable = {m_rtSBTBuffer.buffer, hitGroupOffset, + progSize, sbtSize}; + const vk::StridedBufferRegionKHR callableShaderBindingTable; + + cmdBuf.traceRaysKHR(&raygenShaderBindingTable, &missShaderBindingTable, &hitShaderBindingTable, + &callableShaderBindingTable, // + m_size.width, m_size.height, 1); // + + + m_debug.endLabel(cmdBuf); +} + +//-------------------------------------------------------------------------------------------------- +// If the camera matrix has changed, resets the frame. +// otherwise, increments frame. +// +void HelloVulkan::updateFrame() +{ + static nvmath::mat4f refCamMatrix; + + auto& m = CameraManip.getMatrix(); + if(memcmp(&refCamMatrix.a00, &m.a00, sizeof(nvmath::mat4f)) != 0) + { + resetFrame(); + refCamMatrix = m; + } + m_rtPushConstants.frame++; +} + +void HelloVulkan::resetFrame() +{ + m_rtPushConstants.frame = -1; +} diff --git a/ray_tracing_gltf/hello_vulkan.h b/ray_tracing_gltf/hello_vulkan.h new file mode 100644 index 0000000..9d71ca9 --- /dev/null +++ b/ray_tracing_gltf/hello_vulkan.h @@ -0,0 +1,162 @@ +/* Copyright (c) 2014-2018, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#pragma once +#include + +#define NVVK_ALLOC_DEDICATED +#include "nvvk/allocator_vk.hpp" +#include "nvvk/appbase_vkpp.hpp" +#include "nvvk/debug_util_vk.hpp" +#include "nvvk/descriptorsets_vk.hpp" + +// #VKRay +#include "nvh/gltfscene.hpp" +#include "nvvk/raytraceKHR_vk.hpp" + +//-------------------------------------------------------------------------------------------------- +// Simple rasterizer of OBJ objects +// - Each OBJ loaded are stored in an `ObjModel` and referenced by a `ObjInstance` +// - It is possible to have many `ObjInstance` referencing the same `ObjModel` +// - Rendering is done in an offscreen framebuffer +// - The image of the framebuffer is displayed in post-process in a full-screen quad +// +class HelloVulkan : public nvvk::AppBase +{ +public: + void setup(const vk::Instance& instance, + const vk::Device& device, + const vk::PhysicalDevice& physicalDevice, + uint32_t queueFamily) override; + void createDescriptorSetLayout(); + void createGraphicsPipeline(); + void loadScene(const std::string& filename); + void updateDescriptorSet(); + void createUniformBuffer(); + void createTextureImages(const vk::CommandBuffer& cmdBuf, tinygltf::Model& gltfModel); + void updateUniformBuffer(); + void onResize(int /*w*/, int /*h*/) override; + void destroyResources(); + void rasterize(const vk::CommandBuffer& cmdBuff); + + // Structure used for retrieving the primitive information in the closest hit + // The gl_InstanceCustomIndexNV + struct RtPrimitiveLookup + { + uint32_t indexOffset; + uint32_t vertexOffset; + int materialIndex; + }; + + + nvh::GltfScene m_gltfScene; + nvvk::Buffer m_vertexBuffer; + nvvk::Buffer m_normalBuffer; + nvvk::Buffer m_uvBuffer; + nvvk::Buffer m_indexBuffer; + nvvk::Buffer m_materialBuffer; + nvvk::Buffer m_matrixBuffer; + nvvk::Buffer m_rtPrimLookup; + + // Information pushed at each draw call + struct ObjPushConstant + { + nvmath::vec3f lightPosition{0.f, 4.5f, 0.f}; + int instanceId{0}; // To retrieve the transformation matrix + float lightIntensity{10.f}; + int lightType{0}; // 0: point, 1: infinite + int materialId{0}; + }; + ObjPushConstant m_pushConstant; + + // Graphic pipeline + vk::PipelineLayout m_pipelineLayout; + vk::Pipeline m_graphicsPipeline; + nvvk::DescriptorSetBindings m_descSetLayoutBind; + vk::DescriptorPool m_descPool; + vk::DescriptorSetLayout m_descSetLayout; + vk::DescriptorSet m_descSet; + + nvvk::Buffer m_cameraMat; // Device-Host of the camera matrices + std::vector m_textures; // vector of all textures of the scene + + nvvk::AllocatorDedicated m_alloc; // Allocator for buffer, images, acceleration structures + nvvk::DebugUtil m_debug; // Utility to name objects + + // #Post + void createOffscreenRender(); + void createPostPipeline(); + void createPostDescriptor(); + void updatePostDescriptorSet(); + void drawPost(vk::CommandBuffer cmdBuf); + + nvvk::DescriptorSetBindings m_postDescSetLayoutBind; + vk::DescriptorPool m_postDescPool; + vk::DescriptorSetLayout m_postDescSetLayout; + vk::DescriptorSet m_postDescSet; + vk::Pipeline m_postPipeline; + vk::PipelineLayout m_postPipelineLayout; + vk::RenderPass m_offscreenRenderPass; + vk::Framebuffer m_offscreenFramebuffer; + nvvk::Texture m_offscreenColor; + vk::Format m_offscreenColorFormat{vk::Format::eR32G32B32A32Sfloat}; + nvvk::Texture m_offscreenDepth; + vk::Format m_offscreenDepthFormat{vk::Format::eD32Sfloat}; + + // #VKRay + nvvk::RaytracingBuilderKHR::Blas primitiveToGeometry(const nvh::GltfPrimMesh& prim); + + void initRayTracing(); + void createBottomLevelAS(); + void createTopLevelAS(); + void createRtDescriptorSet(); + void updateRtDescriptorSet(); + void createRtPipeline(); + void createRtShaderBindingTable(); + void raytrace(const vk::CommandBuffer& cmdBuf, const nvmath::vec4f& clearColor); + void updateFrame(); + void resetFrame(); + + vk::PhysicalDeviceRayTracingPropertiesKHR m_rtProperties; + nvvk::RaytracingBuilderKHR m_rtBuilder; + nvvk::DescriptorSetBindings m_rtDescSetLayoutBind; + vk::DescriptorPool m_rtDescPool; + vk::DescriptorSetLayout m_rtDescSetLayout; + vk::DescriptorSet m_rtDescSet; + std::vector m_rtShaderGroups; + vk::PipelineLayout m_rtPipelineLayout; + vk::Pipeline m_rtPipeline; + nvvk::Buffer m_rtSBTBuffer; + + struct RtPushConstant + { + nvmath::vec4f clearColor; + nvmath::vec3f lightPosition; + float lightIntensity; + int lightType; + int frame{0}; + } m_rtPushConstants; +}; diff --git a/ray_tracing_gltf/main.cpp b/ray_tracing_gltf/main.cpp new file mode 100644 index 0000000..ecf4cac --- /dev/null +++ b/ray_tracing_gltf/main.cpp @@ -0,0 +1,302 @@ +/* Copyright (c) 2014-2018, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// ImGui - standalone example application for Glfw + Vulkan, using programmable +// pipeline If you are new to ImGui, see examples/README.txt and documentation +// at the top of imgui.cpp. + +#include +#include +VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE + +#include "imgui.h" +#include "imgui_impl_glfw.h" + +#include "hello_vulkan.h" +#include "nvh/cameramanipulator.hpp" +#include "nvh/fileoperations.hpp" +#include "nvpsystem.hpp" +#include "nvvk/appbase_vkpp.hpp" +#include "nvvk/commands_vk.hpp" +#include "nvvk/context_vk.hpp" + + +////////////////////////////////////////////////////////////////////////// +#define UNUSED(x) (void)(x) +////////////////////////////////////////////////////////////////////////// + +// Default search path for shaders +std::vector defaultSearchPaths; + +// GLFW Callback functions +static void onErrorCallback(int error, const char* description) +{ + fprintf(stderr, "GLFW Error %d: %s\n", error, description); +} + +// Extra UI +void renderUI(HelloVulkan& helloVk) +{ + static int item = 1; + if(ImGui::Combo("Up Vector", &item, "X\0Y\0Z\0\0")) + { + nvmath::vec3f pos, eye, up; + CameraManip.getLookat(pos, eye, up); + up = nvmath::vec3f(item == 0, item == 1, item == 2); + CameraManip.setLookat(pos, eye, up); + } + ImGui::SliderFloat3("Light Position", &helloVk.m_pushConstant.lightPosition.x, -20.f, 20.f); + ImGui::SliderFloat("Light Intensity", &helloVk.m_pushConstant.lightIntensity, 0.f, 100.f); + ImGui::RadioButton("Point", &helloVk.m_pushConstant.lightType, 0); + ImGui::SameLine(); + ImGui::RadioButton("Infinite", &helloVk.m_pushConstant.lightType, 1); +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +static int const SAMPLE_WIDTH = 1280; +static int const SAMPLE_HEIGHT = 720; + +//-------------------------------------------------------------------------------------------------- +// Application Entry +// +int main(int argc, char** argv) +{ + UNUSED(argc); + + // Setup GLFW window + glfwSetErrorCallback(onErrorCallback); + if(!glfwInit()) + { + return 1; + } + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + GLFWwindow* window = glfwCreateWindow(SAMPLE_WIDTH, SAMPLE_HEIGHT, + "NVIDIA Vulkan Raytracing Tutorial", nullptr, nullptr); + + // Setup camera + CameraManip.setWindowSize(SAMPLE_WIDTH, SAMPLE_HEIGHT); + CameraManip.setLookat(nvmath::vec3f(0, 0, 15), nvmath::vec3f(0, 0, 0), nvmath::vec3f(0, 1, 0)); + + // Setup Vulkan + if(!glfwVulkanSupported()) + { + printf("GLFW: Vulkan Not Supported\n"); + return 1; + } + + // setup some basic things for the sample, logging file for example + NVPSystem system(argv[0], PROJECT_NAME); + + // Search path for shaders and other media + defaultSearchPaths = { + PROJECT_ABSDIRECTORY, + PROJECT_ABSDIRECTORY "../", + NVPSystem::exePath() + std::string(PROJECT_RELDIRECTORY), + NVPSystem::exePath() + std::string(PROJECT_RELDIRECTORY) + std::string("../"), + }; + + + // Requesting Vulkan extensions and layers + nvvk::ContextCreateInfo contextInfo(true); + contextInfo.setVersion(1, 2); + contextInfo.addInstanceLayer("VK_LAYER_LUNARG_monitor", true); + contextInfo.addInstanceExtension(VK_KHR_SURFACE_EXTENSION_NAME); +#ifdef WIN32 + contextInfo.addInstanceExtension(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); +#else + contextInfo.addInstanceExtension(VK_KHR_XLIB_SURFACE_EXTENSION_NAME); + contextInfo.addInstanceExtension(VK_KHR_XCB_SURFACE_EXTENSION_NAME); +#endif + contextInfo.addInstanceExtension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); + contextInfo.addDeviceExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME); + contextInfo.addDeviceExtension(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME); + contextInfo.addDeviceExtension(VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME); + // #VKRay: Activate the ray tracing extension + vk::PhysicalDeviceRayTracingFeaturesKHR raytracingFeature; + contextInfo.addDeviceExtension(VK_KHR_RAY_TRACING_EXTENSION_NAME, false, &raytracingFeature); + contextInfo.addDeviceExtension(VK_KHR_MAINTENANCE3_EXTENSION_NAME); + contextInfo.addDeviceExtension(VK_KHR_PIPELINE_LIBRARY_EXTENSION_NAME); + contextInfo.addDeviceExtension(VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME); + contextInfo.addDeviceExtension(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME); + + + // Creating Vulkan base application + nvvk::Context vkctx{}; + vkctx.initInstance(contextInfo); + // Find all compatible devices + auto compatibleDevices = vkctx.getCompatibleDevices(contextInfo); + assert(!compatibleDevices.empty()); + // Use a compatible device + vkctx.initDevice(compatibleDevices[0], contextInfo); + + + // Create example + HelloVulkan helloVk; + + // Window need to be opened to get the surface on which to draw + const vk::SurfaceKHR surface = helloVk.getVkSurface(vkctx.m_instance, window); + vkctx.setGCTQueueWithPresent(surface); + + helloVk.setup(vkctx.m_instance, vkctx.m_device, vkctx.m_physicalDevice, + vkctx.m_queueGCT.familyIndex); + helloVk.createSurface(surface, SAMPLE_WIDTH, SAMPLE_HEIGHT); + helloVk.createDepthBuffer(); + helloVk.createRenderPass(); + helloVk.createFrameBuffers(); + + // Setup Imgui + helloVk.initGUI(0); // Using sub-pass 0 + + // Creation of the example + helloVk.loadScene(nvh::findFile("media/scenes/cornellBox.gltf", defaultSearchPaths)); + + + helloVk.createOffscreenRender(); + helloVk.createDescriptorSetLayout(); + helloVk.createGraphicsPipeline(); + helloVk.createUniformBuffer(); + helloVk.updateDescriptorSet(); + + // #VKRay + helloVk.initRayTracing(); + helloVk.createBottomLevelAS(); + helloVk.createTopLevelAS(); + helloVk.createRtDescriptorSet(); + helloVk.createRtPipeline(); + helloVk.createRtShaderBindingTable(); + + helloVk.createPostDescriptor(); + helloVk.createPostPipeline(); + helloVk.updatePostDescriptorSet(); + + + nvmath::vec4f clearColor = nvmath::vec4f(1, 1, 1, 1.00f); + bool useRaytracer = true; + + + helloVk.setupGlfwCallbacks(window); + ImGui_ImplGlfw_InitForVulkan(window, true); + + // Main loop + while(!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + if(helloVk.isMinimized()) + continue; + + // Start the Dear ImGui frame + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + + // Updating camera buffer + helloVk.updateUniformBuffer(); + + // Show UI window. + if(1 == 1) + { + ImGui::ColorEdit3("Clear color", reinterpret_cast(&clearColor)); + ImGui::Checkbox("Ray Tracer mode", &useRaytracer); // Switch between raster and ray tracing + + renderUI(helloVk); + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", + 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); + ImGui::Render(); + } + + // Start rendering the scene + helloVk.prepareFrame(); + + // Start command buffer of this frame + auto curFrame = helloVk.getCurFrame(); + const vk::CommandBuffer& cmdBuff = helloVk.getCommandBuffers()[curFrame]; + + cmdBuff.begin({vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + + // Clearing screen + vk::ClearValue clearValues[2]; + clearValues[0].setColor( + std::array({clearColor[0], clearColor[1], clearColor[2], clearColor[3]})); + clearValues[1].setDepthStencil({1.0f, 0}); + + // Offscreen render pass + { + vk::RenderPassBeginInfo offscreenRenderPassBeginInfo; + offscreenRenderPassBeginInfo.setClearValueCount(2); + offscreenRenderPassBeginInfo.setPClearValues(clearValues); + offscreenRenderPassBeginInfo.setRenderPass(helloVk.m_offscreenRenderPass); + offscreenRenderPassBeginInfo.setFramebuffer(helloVk.m_offscreenFramebuffer); + offscreenRenderPassBeginInfo.setRenderArea({{}, helloVk.getSize()}); + + // Rendering Scene + if(useRaytracer) + { + helloVk.raytrace(cmdBuff, clearColor); + } + else + { + cmdBuff.beginRenderPass(offscreenRenderPassBeginInfo, vk::SubpassContents::eInline); + helloVk.rasterize(cmdBuff); + cmdBuff.endRenderPass(); + } + } + + // 2nd rendering pass: tone mapper, UI + { + vk::RenderPassBeginInfo postRenderPassBeginInfo; + postRenderPassBeginInfo.setClearValueCount(2); + postRenderPassBeginInfo.setPClearValues(clearValues); + postRenderPassBeginInfo.setRenderPass(helloVk.getRenderPass()); + postRenderPassBeginInfo.setFramebuffer(helloVk.getFramebuffers()[curFrame]); + postRenderPassBeginInfo.setRenderArea({{}, helloVk.getSize()}); + + cmdBuff.beginRenderPass(postRenderPassBeginInfo, vk::SubpassContents::eInline); + // Rendering tonemapper + helloVk.drawPost(cmdBuff); + // Rendering UI + ImGui::RenderDrawDataVK(cmdBuff, ImGui::GetDrawData()); + cmdBuff.endRenderPass(); + } + + // Submit for display + cmdBuff.end(); + helloVk.submitFrame(); + } + + // Cleanup + helloVk.getDevice().waitIdle(); + helloVk.destroyResources(); + helloVk.destroy(); + + vkctx.deinit(); + + glfwDestroyWindow(window); + glfwTerminate(); + + return 0; +} diff --git a/ray_tracing_gltf/shaders/binding.glsl b/ray_tracing_gltf/shaders/binding.glsl new file mode 100644 index 0000000..618111b --- /dev/null +++ b/ray_tracing_gltf/shaders/binding.glsl @@ -0,0 +1,10 @@ + + +#define B_CAMERA 0 +#define B_VERTICES 1 +#define B_NORMALS 2 +#define B_TEXCOORDS 3 +#define B_INDICES 4 +#define B_MATERIALS 5 +#define B_MATRICES 6 +#define B_TEXTURES 7 diff --git a/ray_tracing_gltf/shaders/frag_shader.frag b/ray_tracing_gltf/shaders/frag_shader.frag new file mode 100644 index 0000000..937fbd7 --- /dev/null +++ b/ray_tracing_gltf/shaders/frag_shader.frag @@ -0,0 +1,74 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable +#extension GL_EXT_nonuniform_qualifier : enable +#extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_scalar_block_layout : enable + +#include "binding.glsl" +#include "gltf.glsl" + + +layout(push_constant) uniform shaderInformation +{ + vec3 lightPosition; + uint instanceId; + float lightIntensity; + int lightType; + int matetrialId; +} +pushC; + +// clang-format off +// Incoming +//layout(location = 0) flat in int matIndex; +layout(location = 1) in vec2 fragTexCoord; +layout(location = 2) in vec3 fragNormal; +layout(location = 3) in vec3 viewDir; +layout(location = 4) in vec3 worldPos; +// Outgoing +layout(location = 0) out vec4 outColor; +// Buffers +layout(set = 0, binding = B_MATERIALS) buffer _GltfMaterial { GltfMaterial materials[]; }; +layout(set = 0, binding = B_TEXTURES) uniform sampler2D[] textureSamplers; + +// clang-format on + + +void main() +{ + // Material of the object + GltfMaterial mat = materials[nonuniformEXT(pushC.matetrialId)]; + + vec3 N = normalize(fragNormal); + + // Vector toward light + vec3 L; + float lightIntensity = pushC.lightIntensity; + if(pushC.lightType == 0) + { + vec3 lDir = pushC.lightPosition - worldPos; + float d = length(lDir); + lightIntensity = pushC.lightIntensity / (d * d); + L = normalize(lDir); + } + else + { + L = normalize(pushC.lightPosition - vec3(0)); + } + + + // Diffuse + vec3 diffuse = computeDiffuse(mat, L, N); + if(mat.pbrBaseColorTexture > -1) + { + uint txtId = mat.pbrBaseColorTexture; + vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], fragTexCoord).xyz; + diffuse *= diffuseTxt; + } + + // Specular + vec3 specular = computeSpecular(mat, viewDir, L, N); + + // Result + outColor = vec4(lightIntensity * (diffuse + specular), 1); +} diff --git a/ray_tracing_gltf/shaders/gltf.glsl b/ray_tracing_gltf/shaders/gltf.glsl new file mode 100644 index 0000000..60ca195 --- /dev/null +++ b/ray_tracing_gltf/shaders/gltf.glsl @@ -0,0 +1,60 @@ + +struct GltfMaterial +{ + int shadingModel; // 0: metallic-roughness, 1: specular-glossiness + + // PbrMetallicRoughness + vec4 pbrBaseColorFactor; + int pbrBaseColorTexture; + float pbrMetallicFactor; + float pbrRoughnessFactor; + int pbrMetallicRoughnessTexture; + + // KHR_materials_pbrSpecularGlossiness + vec4 khrDiffuseFactor; + int khrDiffuseTexture; + vec3 khrSpecularFactor; + float khrGlossinessFactor; + int khrSpecularGlossinessTexture; + + int emissiveTexture; + vec3 emissiveFactor; + int alphaMode; + float alphaCutoff; + bool doubleSided; + + int normalTexture; + float normalTextureScale; + int occlusionTexture; + float occlusionTextureStrength; +}; + +struct PrimMeshInfo +{ + uint indexOffset; + uint vertexOffset; + int materialIndex; +}; + + +vec3 computeDiffuse(GltfMaterial mat, vec3 lightDir, vec3 normal) +{ + // Lambertian + float dotNL = max(dot(normal, lightDir), 0.0); + return mat.pbrBaseColorFactor.xyz * dotNL; +} + +vec3 computeSpecular(GltfMaterial mat, vec3 viewDir, vec3 lightDir, vec3 normal) +{ + // Compute specular only if not in shadow + const float kPi = 3.14159265; + const float kShininess = 60.0; + + // Specular + const float kEnergyConservation = (2.0 + kShininess) / (2.0 * kPi); + vec3 V = normalize(-viewDir); + vec3 R = reflect(-lightDir, normal); + float specular = kEnergyConservation * pow(max(dot(V, R), 0.0), kShininess); + + return vec3(specular); +} diff --git a/ray_tracing_gltf/shaders/passthrough.vert b/ray_tracing_gltf/shaders/passthrough.vert new file mode 100644 index 0000000..3e15d82 --- /dev/null +++ b/ray_tracing_gltf/shaders/passthrough.vert @@ -0,0 +1,15 @@ +#version 450 +layout (location = 0) out vec2 outUV; + + +out gl_PerVertex +{ + vec4 gl_Position; +}; + + +void main() +{ + outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2); + gl_Position = vec4(outUV * 2.0f - 1.0f, 1.0f, 1.0f); +} diff --git a/ray_tracing_gltf/shaders/pathtrace.rchit b/ray_tracing_gltf/shaders/pathtrace.rchit new file mode 100644 index 0000000..37fe3b8 --- /dev/null +++ b/ray_tracing_gltf/shaders/pathtrace.rchit @@ -0,0 +1,162 @@ +#version 460 +#extension GL_EXT_ray_tracing : require +#extension GL_EXT_nonuniform_qualifier : enable +#extension GL_EXT_scalar_block_layout : enable +#extension GL_GOOGLE_include_directive : enable + +#include "binding.glsl" +#include "gltf.glsl" +#include "raycommon.glsl" +#include "sampling.glsl" + + +hitAttributeEXT vec2 attribs; + +// clang-format off +layout(location = 0) rayPayloadInEXT hitPayload prd; +layout(location = 1) rayPayloadEXT bool isShadowed; + +layout(set = 0, binding = 0 ) uniform accelerationStructureEXT topLevelAS; +layout(set = 0, binding = 2) readonly buffer _InstanceInfo {PrimMeshInfo primInfo[];}; + +layout(set = 1, binding = B_VERTICES) readonly buffer _VertexBuf {float vertices[];}; +layout(set = 1, binding = B_INDICES) readonly buffer _Indices {uint indices[];}; +layout(set = 1, binding = B_NORMALS) readonly buffer _NormalBuf {float normals[];}; +layout(set = 1, binding = B_TEXCOORDS) readonly buffer _TexCoordBuf {float texcoord0[];}; +layout(set = 1, binding = B_MATERIALS) readonly buffer _MaterialBuffer {GltfMaterial materials[];}; +layout(set = 1, binding = B_TEXTURES) uniform sampler2D texturesMap[]; // all textures + + +// clang-format on + +layout(push_constant) uniform Constants +{ + vec4 clearColor; + vec3 lightPosition; + float lightIntensity; + int lightType; +} +pushC; + +// Return the vertex position +vec3 getVertex(uint index) +{ + vec3 vp; + vp.x = vertices[3 * index + 0]; + vp.y = vertices[3 * index + 1]; + vp.z = vertices[3 * index + 2]; + return vp; +} + +vec3 getNormal(uint index) +{ + vec3 vp; + vp.x = normals[3 * index + 0]; + vp.y = normals[3 * index + 1]; + vp.z = normals[3 * index + 2]; + return vp; +} + +vec2 getTexCoord(uint index) +{ + vec2 vp; + vp.x = texcoord0[2 * index + 0]; + vp.y = texcoord0[2 * index + 1]; + return vp; +} + + +void main() +{ + // Retrieve the Primitive mesh buffer information + PrimMeshInfo pinfo = primInfo[gl_InstanceCustomIndexEXT]; + + // Getting the 'first index' for this mesh (offset of the mesh + offset of the triangle) + uint indexOffset = pinfo.indexOffset + (3 * gl_PrimitiveID); + uint vertexOffset = pinfo.vertexOffset; // Vertex offset as defined in glTF + uint matIndex = max(0, pinfo.materialIndex); // material of primitive mesh + + // Getting the 3 indices of the triangle (local) + ivec3 triangleIndex = ivec3(indices[nonuniformEXT(indexOffset + 0)], // + indices[nonuniformEXT(indexOffset + 1)], // + indices[nonuniformEXT(indexOffset + 2)]); + triangleIndex += ivec3(vertexOffset); // (global) + + const vec3 barycentrics = vec3(1.0 - attribs.x - attribs.y, attribs.x, attribs.y); + + // Vertex of the triangle + const vec3 pos0 = getVertex(triangleIndex.x); + const vec3 pos1 = getVertex(triangleIndex.y); + const vec3 pos2 = getVertex(triangleIndex.z); + const vec3 position = pos0 * barycentrics.x + pos1 * barycentrics.y + pos2 * barycentrics.z; + const vec3 world_position = vec3(gl_ObjectToWorldEXT * vec4(position, 1.0)); + + // Normal + const vec3 nrm0 = getNormal(triangleIndex.x); + const vec3 nrm1 = getNormal(triangleIndex.y); + const vec3 nrm2 = getNormal(triangleIndex.z); + vec3 normal = normalize(nrm0 * barycentrics.x + nrm1 * barycentrics.y + nrm2 * barycentrics.z); + const vec3 world_normal = normalize(vec3(normal * gl_WorldToObjectEXT)); + const vec3 geom_normal = normalize(cross(pos1 - pos0, pos2 - pos0)); + + // TexCoord + const vec2 uv0 = getTexCoord(triangleIndex.x); + const vec2 uv1 = getTexCoord(triangleIndex.y); + const vec2 uv2 = getTexCoord(triangleIndex.z); + const vec2 texcoord0 = uv0 * barycentrics.x + uv1 * barycentrics.y + uv2 * barycentrics.z; + + // https://en.wikipedia.org/wiki/Path_tracing + // Material of the object + GltfMaterial mat = materials[nonuniformEXT(matIndex)]; + vec3 emittance = mat.emissiveFactor; + + // Pick a random direction from here and keep going. + vec3 tangent, bitangent; + createCoordinateSystem(world_normal, tangent, bitangent); + vec3 rayOrigin = world_position; + vec3 rayDirection = samplingHemisphere(prd.seed, tangent, bitangent, world_normal); + + // Probability of the newRay (cosine distributed) + const float p = 1 / M_PI; + + // Compute the BRDF for this ray (assuming Lambertian reflection) + float cos_theta = dot(rayDirection, world_normal); + vec3 albedo = mat.pbrBaseColorFactor.xyz; + if(mat.pbrBaseColorTexture > -1) + { + uint txtId = mat.pbrBaseColorTexture; + albedo *= texture(texturesMap[nonuniformEXT(txtId)], texcoord0).xyz; + } + vec3 BRDF = albedo / M_PI; + + prd.rayOrigin = rayOrigin; + prd.rayDirection = rayDirection; + prd.hitValue = emittance; + prd.weight = BRDF * cos_theta / p; + return; + + // Recursively trace reflected light sources. + if(prd.depth < 10) + { + prd.depth++; + float tMin = 0.001; + float tMax = 100000000.0; + uint flags = gl_RayFlagsOpaqueEXT; + traceRayEXT(topLevelAS, // acceleration structure + flags, // rayFlags + 0xFF, // cullMask + 0, // sbtRecordOffset + 0, // sbtRecordStride + 0, // missIndex + rayOrigin, // ray origin + tMin, // ray min range + rayDirection, // ray direction + tMax, // ray max range + 0 // payload (location = 0) + ); + } + vec3 incoming = prd.hitValue; + + // Apply the Rendering Equation here. + prd.hitValue = emittance + (BRDF * incoming * cos_theta / p); +} diff --git a/ray_tracing_gltf/shaders/pathtrace.rgen b/ray_tracing_gltf/shaders/pathtrace.rgen new file mode 100644 index 0000000..ad0c58e --- /dev/null +++ b/ray_tracing_gltf/shaders/pathtrace.rgen @@ -0,0 +1,93 @@ +#version 460 +#extension GL_EXT_ray_tracing : require +#extension GL_GOOGLE_include_directive : enable +#extension GL_ARB_shader_clock : enable + + +#include "binding.glsl" +#include "raycommon.glsl" +#include "sampling.glsl" + +layout(set = 0, binding = 0) uniform accelerationStructureEXT topLevelAS; +layout(set = 0, binding = 1, rgba32f) uniform image2D image; + +layout(location = 0) rayPayloadEXT hitPayload prd; + +layout(set = 1, binding = B_CAMERA) uniform CameraProperties +{ + mat4 view; + mat4 proj; + mat4 viewInverse; + mat4 projInverse; +} +cam; + +layout(push_constant) uniform Constants +{ + vec4 clearColor; + vec3 lightPosition; + float lightIntensity; + int lightType; + int frame; +} +pushC; + +void main() +{ + // Initialize the random number + uint seed = tea(gl_LaunchIDEXT.y * gl_LaunchSizeEXT.x + gl_LaunchIDEXT.x, int(clockARB())); + + const vec2 pixelCenter = vec2(gl_LaunchIDEXT.xy) + vec2(0.5); + const vec2 inUV = pixelCenter / vec2(gl_LaunchSizeEXT.xy); + vec2 d = inUV * 2.0 - 1.0; + + vec4 origin = cam.viewInverse * vec4(0, 0, 0, 1); + vec4 target = cam.projInverse * vec4(d.x, d.y, 1, 1); + vec4 direction = cam.viewInverse * vec4(normalize(target.xyz), 0); + + uint rayFlags = gl_RayFlagsOpaqueEXT; + float tMin = 0.001; + float tMax = 10000.0; + + prd.hitValue = vec3(0); + prd.seed = seed; + prd.depth = 0; + prd.rayOrigin = origin.xyz; + prd.rayDirection = direction.xyz; + prd.weight = vec3(0); + + vec3 curWeight = vec3(1); + vec3 hitValue = vec3(0); + + for(; prd.depth < 10; prd.depth++) + { + traceRayEXT(topLevelAS, // acceleration structure + rayFlags, // rayFlags + 0xFF, // cullMask + 0, // sbtRecordOffset + 0, // sbtRecordStride + 0, // missIndex + prd.rayOrigin, // ray origin + tMin, // ray min range + prd.rayDirection, // ray direction + tMax, // ray max range + 0 // payload (location = 0) + ); + + hitValue += prd.hitValue * curWeight; + curWeight *= prd.weight; + } + + // Do accumulation over time + if(pushC.frame > 0) + { + float a = 1.0f / float(pushC.frame + 1); + vec3 old_color = imageLoad(image, ivec2(gl_LaunchIDEXT.xy)).xyz; + imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(mix(old_color, hitValue, a), 1.f)); + } + else + { + // First frame, replace the value in the buffer + imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(hitValue, 1.f)); + } +} diff --git a/ray_tracing_gltf/shaders/pathtrace.rmiss b/ray_tracing_gltf/shaders/pathtrace.rmiss new file mode 100644 index 0000000..6bed091 --- /dev/null +++ b/ray_tracing_gltf/shaders/pathtrace.rmiss @@ -0,0 +1,20 @@ +#version 460 +#extension GL_EXT_ray_tracing : require +#extension GL_GOOGLE_include_directive : enable +#include "raycommon.glsl" + +layout(location = 0) rayPayloadInEXT hitPayload prd; + +layout(push_constant) uniform Constants +{ + vec4 clearColor; +}; + +void main() +{ + if(prd.depth == 0) + prd.hitValue = clearColor.xyz * 0.8; + else + prd.hitValue = vec3(0.01); // No contribution from environment + prd.depth = 100; // Ending trace +} diff --git a/ray_tracing_gltf/shaders/post.frag b/ray_tracing_gltf/shaders/post.frag new file mode 100644 index 0000000..b8f30f1 --- /dev/null +++ b/ray_tracing_gltf/shaders/post.frag @@ -0,0 +1,18 @@ +#version 450 +layout(location = 0) in vec2 outUV; +layout(location = 0) out vec4 fragColor; + +layout(set = 0, binding = 0) uniform sampler2D noisyTxt; + +layout(push_constant) uniform shaderInformation +{ + float aspectRatio; +} +pushc; + +void main() +{ + vec2 uv = outUV; + float gamma = 1. / 2.2; + fragColor = pow(texture(noisyTxt, uv).rgba, vec4(gamma)); +} diff --git a/ray_tracing_gltf/shaders/raycommon.glsl b/ray_tracing_gltf/shaders/raycommon.glsl new file mode 100644 index 0000000..2c6edf4 --- /dev/null +++ b/ray_tracing_gltf/shaders/raycommon.glsl @@ -0,0 +1,9 @@ +struct hitPayload +{ + vec3 hitValue; + uint seed; + uint depth; + vec3 rayOrigin; + vec3 rayDirection; + vec3 weight; +}; diff --git a/ray_tracing_gltf/shaders/raytrace.rchit b/ray_tracing_gltf/shaders/raytrace.rchit new file mode 100644 index 0000000..f2a2cbb --- /dev/null +++ b/ray_tracing_gltf/shaders/raytrace.rchit @@ -0,0 +1,172 @@ +#version 460 +#extension GL_EXT_ray_tracing : require +#extension GL_EXT_nonuniform_qualifier : enable +#extension GL_EXT_scalar_block_layout : enable +#extension GL_GOOGLE_include_directive : enable + +#include "binding.glsl" +#include "gltf.glsl" +#include "raycommon.glsl" + +hitAttributeEXT vec2 attribs; + +// clang-format off +layout(location = 0) rayPayloadInEXT hitPayload prd; +layout(location = 1) rayPayloadEXT bool isShadowed; + +layout(set = 0, binding = 0 ) uniform accelerationStructureEXT topLevelAS; +layout(set = 0, binding = 2) readonly buffer _InstanceInfo {PrimMeshInfo primInfo[];}; + +layout(set = 1, binding = B_VERTICES) readonly buffer _VertexBuf {float vertices[];}; +layout(set = 1, binding = B_INDICES) readonly buffer _Indices {uint indices[];}; +layout(set = 1, binding = B_NORMALS) readonly buffer _NormalBuf {float normals[];}; +layout(set = 1, binding = B_TEXCOORDS) readonly buffer _TexCoordBuf {float texcoord0[];}; +layout(set = 1, binding = B_MATERIALS) readonly buffer _MaterialBuffer {GltfMaterial materials[];}; +layout(set = 1, binding = B_TEXTURES) uniform sampler2D texturesMap[]; // all textures + + +// clang-format on + +layout(push_constant) uniform Constants +{ + vec4 clearColor; + vec3 lightPosition; + float lightIntensity; + int lightType; +} +pushC; + +// Return the vertex position +vec3 getVertex(uint index) +{ + vec3 vp; + vp.x = vertices[3 * index + 0]; + vp.y = vertices[3 * index + 1]; + vp.z = vertices[3 * index + 2]; + return vp; +} + +vec3 getNormal(uint index) +{ + vec3 vp; + vp.x = normals[3 * index + 0]; + vp.y = normals[3 * index + 1]; + vp.z = normals[3 * index + 2]; + return vp; +} + +vec2 getTexCoord(uint index) +{ + vec2 vp; + vp.x = texcoord0[2 * index + 0]; + vp.y = texcoord0[2 * index + 1]; + return vp; +} + + +void main() +{ + // Retrieve the Primitive mesh buffer information + PrimMeshInfo pinfo = primInfo[gl_InstanceCustomIndexEXT]; + + // Getting the 'first index' for this mesh (offset of the mesh + offset of the triangle) + uint indexOffset = pinfo.indexOffset + (3 * gl_PrimitiveID); + uint vertexOffset = pinfo.vertexOffset; // Vertex offset as defined in glTF + uint matIndex = max(0, pinfo.materialIndex); // material of primitive mesh + + // Getting the 3 indices of the triangle (local) + ivec3 triangleIndex = ivec3(indices[nonuniformEXT(indexOffset + 0)], // + indices[nonuniformEXT(indexOffset + 1)], // + indices[nonuniformEXT(indexOffset + 2)]); + triangleIndex += ivec3(vertexOffset); // (global) + + const vec3 barycentrics = vec3(1.0 - attribs.x - attribs.y, attribs.x, attribs.y); + + // Vertex of the triangle + const vec3 pos0 = getVertex(triangleIndex.x); + const vec3 pos1 = getVertex(triangleIndex.y); + const vec3 pos2 = getVertex(triangleIndex.z); + const vec3 position = pos0 * barycentrics.x + pos1 * barycentrics.y + pos2 * barycentrics.z; + const vec3 world_position = vec3(gl_ObjectToWorldEXT * vec4(position, 1.0)); + + // Normal + const vec3 nrm0 = getNormal(triangleIndex.x); + const vec3 nrm1 = getNormal(triangleIndex.y); + const vec3 nrm2 = getNormal(triangleIndex.z); + vec3 normal = normalize(nrm0 * barycentrics.x + nrm1 * barycentrics.y + nrm2 * barycentrics.z); + const vec3 world_normal = normalize(vec3(normal * gl_WorldToObjectEXT)); + const vec3 geom_normal = normalize(cross(pos1 - pos0, pos2 - pos0)); + + // TexCoord + const vec2 uv0 = getTexCoord(triangleIndex.x); + const vec2 uv1 = getTexCoord(triangleIndex.y); + const vec2 uv2 = getTexCoord(triangleIndex.z); + const vec2 texcoord0 = uv0 * barycentrics.x + uv1 * barycentrics.y + uv2 * barycentrics.z; + + // Vector toward the light + vec3 L; + float lightIntensity = pushC.lightIntensity; + float lightDistance = 100000.0; + // Point light + if(pushC.lightType == 0) + { + vec3 lDir = pushC.lightPosition - world_position; + lightDistance = length(lDir); + lightIntensity = pushC.lightIntensity / (lightDistance * lightDistance); + L = normalize(lDir); + } + else // Directional light + { + L = normalize(pushC.lightPosition - vec3(0)); + } + + // Material of the object + GltfMaterial mat = materials[nonuniformEXT(matIndex)]; + + // Diffuse + vec3 diffuse = computeDiffuse(mat, L, world_normal); + if(mat.pbrBaseColorTexture > -1) + { + uint txtId = mat.pbrBaseColorTexture; + diffuse *= texture(texturesMap[nonuniformEXT(txtId)], texcoord0).xyz; + } + + vec3 specular = vec3(0); + float attenuation = 1; + + // Tracing shadow ray only if the light is visible from the surface + if(dot(world_normal, L) > 0) + { + float tMin = 0.001; + float tMax = lightDistance; + vec3 origin = gl_WorldRayOriginEXT + gl_WorldRayDirectionEXT * gl_HitTEXT; + vec3 rayDir = L; + uint flags = gl_RayFlagsTerminateOnFirstHitEXT | gl_RayFlagsOpaqueEXT + | gl_RayFlagsSkipClosestHitShaderEXT; + isShadowed = true; + traceRayEXT(topLevelAS, // acceleration structure + flags, // rayFlags + 0xFF, // cullMask + 0, // sbtRecordOffset + 0, // sbtRecordStride + 1, // missIndex + origin, // ray origin + tMin, // ray min range + rayDir, // ray direction + tMax, // ray max range + 1 // payload (location = 1) + ); + + if(isShadowed) + { + attenuation = 0.3; + } + else + { + // Specular + specular = computeSpecular(mat, gl_WorldRayDirectionEXT, L, world_normal); + } + } + + prd.hitValue = vec3(lightIntensity * attenuation * (diffuse + specular)); +} diff --git a/ray_tracing_gltf/shaders/raytrace.rgen b/ray_tracing_gltf/shaders/raytrace.rgen new file mode 100644 index 0000000..e655f6d --- /dev/null +++ b/ray_tracing_gltf/shaders/raytrace.rgen @@ -0,0 +1,49 @@ +#version 460 +#extension GL_EXT_ray_tracing : require +#extension GL_GOOGLE_include_directive : enable +#include "binding.glsl" +#include "raycommon.glsl" + +layout(set = 0, binding = 0) uniform accelerationStructureEXT topLevelAS; +layout(set = 0, binding = 1, rgba32f) uniform image2D image; + +layout(location = 0) rayPayloadEXT hitPayload prd; + +layout(set = 1, binding = B_CAMERA) uniform CameraProperties +{ + mat4 view; + mat4 proj; + mat4 viewInverse; + mat4 projInverse; +} +cam; + +void main() +{ + const vec2 pixelCenter = vec2(gl_LaunchIDEXT.xy) + vec2(0.5); + const vec2 inUV = pixelCenter / vec2(gl_LaunchSizeEXT.xy); + vec2 d = inUV * 2.0 - 1.0; + + vec4 origin = cam.viewInverse * vec4(0, 0, 0, 1); + vec4 target = cam.projInverse * vec4(d.x, d.y, 1, 1); + vec4 direction = cam.viewInverse * vec4(normalize(target.xyz), 0); + + uint rayFlags = gl_RayFlagsOpaqueEXT; + float tMin = 0.001; + float tMax = 10000.0; + + traceRayEXT(topLevelAS, // acceleration structure + rayFlags, // rayFlags + 0xFF, // cullMask + 0, // sbtRecordOffset + 0, // sbtRecordStride + 0, // missIndex + origin.xyz, // ray origin + tMin, // ray min range + direction.xyz, // ray direction + tMax, // ray max range + 0 // payload (location = 0) + ); + + imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(prd.hitValue, 1.0)); +} diff --git a/ray_tracing_gltf/shaders/raytrace.rmiss b/ray_tracing_gltf/shaders/raytrace.rmiss new file mode 100644 index 0000000..774899a --- /dev/null +++ b/ray_tracing_gltf/shaders/raytrace.rmiss @@ -0,0 +1,16 @@ +#version 460 +#extension GL_EXT_ray_tracing : require +#extension GL_GOOGLE_include_directive : enable +#include "raycommon.glsl" + +layout(location = 0) rayPayloadInEXT hitPayload prd; + +layout(push_constant) uniform Constants +{ + vec4 clearColor; +}; + +void main() +{ + prd.hitValue = clearColor.xyz * 0.8; +} diff --git a/ray_tracing_gltf/shaders/raytraceShadow.rmiss b/ray_tracing_gltf/shaders/raytraceShadow.rmiss new file mode 100644 index 0000000..57be266 --- /dev/null +++ b/ray_tracing_gltf/shaders/raytraceShadow.rmiss @@ -0,0 +1,9 @@ +#version 460 +#extension GL_EXT_ray_tracing : require + +layout(location = 1) rayPayloadInEXT bool isShadowed; + +void main() +{ + isShadowed = false; +} diff --git a/ray_tracing_gltf/shaders/sampling.glsl b/ray_tracing_gltf/shaders/sampling.glsl new file mode 100644 index 0000000..be6a8f1 --- /dev/null +++ b/ray_tracing_gltf/shaders/sampling.glsl @@ -0,0 +1,64 @@ +// Generate a random unsigned int from two unsigned int values, using 16 pairs +// of rounds of the Tiny Encryption Algorithm. See Zafar, Olano, and Curtis, +// "GPU Random Numbers via the Tiny Encryption Algorithm" +uint tea(uint val0, uint val1) +{ + uint v0 = val0; + uint v1 = val1; + uint s0 = 0; + + for(uint n = 0; n < 16; n++) + { + s0 += 0x9e3779b9; + v0 += ((v1 << 4) + 0xa341316c) ^ (v1 + s0) ^ ((v1 >> 5) + 0xc8013ea4); + v1 += ((v0 << 4) + 0xad90777d) ^ (v0 + s0) ^ ((v0 >> 5) + 0x7e95761e); + } + + return v0; +} + +// Generate a random unsigned int in [0, 2^24) given the previous RNG state +// using the Numerical Recipes linear congruential generator +uint lcg(inout uint prev) +{ + uint LCG_A = 1664525u; + uint LCG_C = 1013904223u; + prev = (LCG_A * prev + LCG_C); + return prev & 0x00FFFFFF; +} + +// Generate a random float in [0, 1) given the previous RNG state +float rnd(inout uint prev) +{ + return (float(lcg(prev)) / float(0x01000000)); +} + + +//------------------------------------------------------------------------------------------------- +// Sampling +//------------------------------------------------------------------------------------------------- + +// Randomly sampling around +Z +vec3 samplingHemisphere(inout uint seed, in vec3 x, in vec3 y, in vec3 z) +{ +#define M_PI 3.141592 + + float r1 = rnd(seed); + float r2 = rnd(seed); + float sq = sqrt(1.0 - r2); + + vec3 direction = vec3(cos(2 * M_PI * r1) * sq, sin(2 * M_PI * r1) * sq, sqrt(r2)); + direction = direction.x * x + direction.y * y + direction.z * z; + + return direction; +} + +// Return the tangent and binormal from the incoming normal +void createCoordinateSystem(in vec3 N, out vec3 Nt, out vec3 Nb) +{ + if(abs(N.x) > abs(N.y)) + Nt = vec3(N.z, 0, -N.x) / sqrt(N.x * N.x + N.z * N.z); + else + Nt = vec3(0, -N.z, N.y) / sqrt(N.y * N.y + N.z * N.z); + Nb = cross(N, Nt); +} diff --git a/ray_tracing_gltf/shaders/vert_shader.vert b/ray_tracing_gltf/shaders/vert_shader.vert new file mode 100644 index 0000000..81916f4 --- /dev/null +++ b/ray_tracing_gltf/shaders/vert_shader.vert @@ -0,0 +1,61 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable +#extension GL_EXT_scalar_block_layout : enable +#extension GL_GOOGLE_include_directive : enable + +#include "binding.glsl" + +// clang-format off +layout( set = 0, binding = B_MATRICES) readonly buffer _Matrix { mat4 matrices[]; }; +// clang-format on + +layout(binding = 0) uniform UniformBufferObject +{ + mat4 view; + mat4 proj; + mat4 viewI; +} +ubo; + +layout(push_constant) uniform shaderInformation +{ + vec3 lightPosition; + uint instanceId; + float lightIntensity; + int lightType; + int materialId; +} +pushC; + +layout(location = 0) in vec3 inPosition; +layout(location = 1) in vec3 inNormal; +layout(location = 2) in vec2 inTexCoord; + + +//layout(location = 0) flat out int matIndex; +layout(location = 1) out vec2 fragTexCoord; +layout(location = 2) out vec3 fragNormal; +layout(location = 3) out vec3 viewDir; +layout(location = 4) out vec3 worldPos; + +out gl_PerVertex +{ + vec4 gl_Position; +}; + + +void main() +{ + mat4 objMatrix = matrices[pushC.instanceId]; + mat4 objMatrixIT = transpose(inverse(objMatrix)); + + vec3 origin = vec3(ubo.viewI * vec4(0, 0, 0, 1)); + + worldPos = vec3(objMatrix * vec4(inPosition, 1.0)); + viewDir = vec3(worldPos - origin); + fragTexCoord = inTexCoord; + fragNormal = vec3(objMatrixIT * vec4(inNormal, 0.0)); + // matIndex = inMatID; + + gl_Position = ubo.proj * ubo.view * vec4(worldPos, 1.0); +}