diff --git a/src/core/src/capsaicin/capsaicin_internal.h b/src/core/src/capsaicin/capsaicin_internal.h index 6793dbd1..ce21cf41 100644 --- a/src/core/src/capsaicin/capsaicin_internal.h +++ b/src/core/src/capsaicin/capsaicin_internal.h @@ -876,6 +876,17 @@ class CapsaicinInternal */ void resetRenderState() const noexcept; + void dumpBuffer(char const *file_path, GfxTexture dump_buffer); + void saveImage(GfxBuffer dump_buffer, uint32_t dump_buffer_width, uint32_t dump_buffer_height, + char const *file_path); + void saveEXR(GfxBuffer dump_buffer, uint32_t dump_buffer_width, uint32_t dump_buffer_height, + char const *exr_file_path); + void saveJPG(GfxBuffer dump_buffer, uint32_t dump_buffer_width, uint32_t dump_buffer_height, + char const *jpg_file_path); + void savePNG(GfxBuffer dump_buffer, uint32_t dump_buffer_width, + uint32_t dump_buffer_height, char const *png_file_path); + void dumpCamera(char const *file_path, CameraMatrices const &camera_matrices, float camera_jitter_x, + float camera_jitter_y); /** * Reset any internal events to their default state. */ @@ -969,6 +980,8 @@ class CapsaicinInternal uint32_t dumpBufferHeight, std::filesystem::path const &filePath); void saveJPG(GfxBuffer const &dumpBuffer, DXGI_FORMAT bufferFormat, uint32_t dumpBufferWidth, uint32_t dumpBufferHeight, std::filesystem::path const &filePath) const; + void savePNG(GfxBuffer const &dumpBuffer, DXGI_FORMAT bufferFormat, uint32_t dumpBufferWidth, + uint32_t dumpBufferHeight, std::filesystem::path const &filePath) const; void dumpCamera(CameraMatrices const &cameraMatrices, float cameraJitterX, float cameraJitterY, std::filesystem::path const &filePath) const; diff --git a/src/core/src/capsaicin/capsaicin_internal_dump.cpp b/src/core/src/capsaicin/capsaicin_internal_dump.cpp index 9cdf2d84..83b3ac0f 100644 --- a/src/core/src/capsaicin/capsaicin_internal_dump.cpp +++ b/src/core/src/capsaicin/capsaicin_internal_dump.cpp @@ -183,7 +183,7 @@ void CapsaicinInternal::dumpDebugView(std::filesystem::path const &filePath, std // instead of the raw AOV data auto extension = filePath.extension().string(); std::ranges::transform(extension, extension.begin(), tolower); - if (extension == ".jpg" || extension == ".jpeg") + if (extension == ".jpg" || extension == ".jpeg" || extension == ".png") { dump_buffer = currentView; } @@ -199,6 +199,7 @@ void CapsaicinInternal::dumpDebugView(std::filesystem::path const &filePath, std } } +// clang-format off void CapsaicinInternal::dumpCamera(std::filesystem::path const &filePath, bool const jittered) const { dumpCamera(camera_matrices_[jittered], jittered ? camera_jitter_.x : 0.F, @@ -236,6 +237,10 @@ void CapsaicinInternal::saveImage(GfxBuffer const &dumpBuffer, const DXGI_FORMAT { saveJPG(dumpBuffer, bufferFormat, dumpBufferWidth, dumpBufferHeight, filePath); } + else if (extension == ".png") + { + savePNG(dumpBuffer, bufferFormat, dumpBufferWidth, dumpBufferHeight, filePath); + } else if (extension == ".exr") { saveEXR(dumpBuffer, bufferFormat, dumpBufferWidth, dumpBufferHeight, filePath); @@ -455,6 +460,72 @@ void CapsaicinInternal::saveJPG(GfxBuffer const &dumpBuffer, const DXGI_FORMAT b } } +void CapsaicinInternal::savePNG(GfxBuffer const &dumpBuffer, const DXGI_FORMAT bufferFormat, uint32_t const dumpBufferWidth, + uint32_t const dumpBufferHeight, std::filesystem::path const &filePath) const +{ + // Image + void const *bufferData = gfxBufferGetData(gfx_, dumpBuffer); + uint32_t const imageWidth = dumpBufferWidth; + uint32_t const imageHeight = dumpBufferHeight; + uint32_t const imagePixelCount = dumpBufferWidth * dumpBufferHeight; + uint32_t const channelCount = GetNumChannels(bufferFormat); + uint32_t const bitsPerChannel = GetBitsPerPixel(bufferFormat) / channelCount; + + if (bitsPerChannel == 8 && channelCount == 3) + { + int ret = stbi_write_png(filePath.string().c_str(), static_cast(imageWidth), + static_cast(imageHeight), 3, bufferData, imageWidth * 3); + if (ret == 0) + { + GFX_PRINT_ERROR(kGfxResult_InternalError, "Can't save '%s'", filePath.string().c_str()); + } + } + else + { + std::vector imageData(static_cast(imageWidth) * imageHeight * 3); + auto quantize = [&](T const *dumpBufferData) { + for (size_t pixelIndex = 0; pixelIndex < imagePixelCount; ++pixelIndex) + { + imageData[3 * pixelIndex + 0] = + ConvertType(dumpBufferData[channelCount * pixelIndex + 0]); + imageData[3 * pixelIndex + 1] = + channelCount > 1 ? ConvertType(dumpBufferData[channelCount * pixelIndex + 1]) + : 0; + imageData[3 * pixelIndex + 2] = + channelCount > 2 ? ConvertType(dumpBufferData[channelCount * pixelIndex + 2]) + : 0; + } + }; + + if (bool const isFloatFormat = IsFormatFloat(bufferFormat); bitsPerChannel == 32 && isFloatFormat) + { + quantize(static_cast(bufferData)); + } + else if (bitsPerChannel == 32) + { + quantize(static_cast(bufferData)); + } + else if (bitsPerChannel == 16 && isFloatFormat) + { + quantize(static_cast(bufferData)); + } + else if (bitsPerChannel == 16) + { + quantize(static_cast(bufferData)); + } + else if (bitsPerChannel == 8) + { + quantize(static_cast(bufferData)); + } + + int ret = stbi_write_png(filePath.string().c_str(), static_cast(imageWidth), + static_cast(imageHeight), 3, imageData.data(), imageWidth * 3); + if (ret == 0) + { + GFX_PRINT_ERROR(kGfxResult_InternalError, "Can't save '%s'", filePath.string().c_str()); + } + } +} void CapsaicinInternal::dumpCamera(CameraMatrices const &cameraMatrices, float const cameraJitterX, float const cameraJitterY, std::filesystem::path const &filePath) const { diff --git a/src/core/src/geometry/path_tracing.hlsl b/src/core/src/geometry/path_tracing.hlsl index c4999434..0e616a3c 100644 --- a/src/core/src/geometry/path_tracing.hlsl +++ b/src/core/src/geometry/path_tracing.hlsl @@ -201,6 +201,38 @@ void shadeLightHit(RayDesc ray, MaterialBRDF material, float3 normal, float3 vie #endif // DISABLE_NON_NEE } +/** + * Calculate any radiance from a hit light. + * @param ray The traced ray that hit a surface. + * @param material Material data describing BRDF of surface. + * @param normal Shading normal vector at current position. + * @param viewDirection Outgoing ray view direction. + * @param throughput The current paths combined throughput. + * @param lightPDF The PDF of sampling the returned light direction. + * @param radianceLi The radiance visible along sampled light. + * @param selectedLight The light that was selected for sampling. + * @param [in,out] radiance The combined radiance. Any new radiance is added to the existing value and returned. + * @param firstHit True if this is the first hit + */ +void shadeLightHit(RayDesc ray, MaterialBRDF material, float3 normal, float3 viewDirection, float3 throughput, + float lightPDF, float3 radianceLi, Light selectedLight, inout float3 radiance, bool firstHit) +{ +#ifdef DISABLE_NON_NEE + float3 sampleReflectance = evaluateBRDF(material, normal, viewDirection, ray.Direction); + radiance += throughput * sampleReflectance * radianceLi / lightPDF.xxx; +#else + // Evaluate BRDF for new light direction and calculate combined PDF for current sample + float3 sampleReflectance; + float samplePDF = sampleBRDFPDFAndEvalute(material, normal, viewDirection, ray.Direction, sampleReflectance, firstHit); + if (samplePDF != 0.0f) + { + bool deltaLight = isDeltaLight(selectedLight); + float weight = (!deltaLight) ? heuristicMIS(lightPDF, samplePDF) : 1.0f; + radiance += throughput * sampleReflectance * radianceLi * (weight / lightPDF).xxx; + } +#endif // DISABLE_NON_NEE +} + /** * Calculates a new light ray direction from a surface by sampling the scenes lighting. * @tparam RNG The type of random number sampler to be used. @@ -303,6 +335,56 @@ void sampleLightsNEE(MaterialBRDF material, inout StratifiedSampler randomStrati } } +/** + * Calculates radiance from a new light ray direction from a surface by sampling the scenes lighting. + * @tparam RNG The type of random number sampler to be used. + * @param material Material data describing BRDF of surface. + * @param randomStratified Random number sampler used to sample light. + * @param lightSampler Light sampler. + * @param position Current position on surface. + * @param normal Shading normal vector at current position. + * @param geometryNormal Surface normal vector at current position. + * @param viewDirection Outgoing ray view direction. + * @param throughput The current paths combined throughput. + * @param [in,out] radiance The combined radiance. Any new radiance is added to the existing value and returned. + * @param firstHit True if this is the first hit. + */ +void sampleLightsNEEFirstHitInfo(MaterialBRDF material, inout StratifiedSampler randomStratified, LightSampler lightSampler, + float3 position, float3 normal, float3 geometryNormal, float3 viewDirection, float3 throughput, inout pathPayload radiance, bool firstHit) +{ + // Get sampled light direction + float lightPDF; + RayDesc ray; + float3 radianceLi; + Light selectedLight; + if (!sampleLightsNEEDirection(material, randomStratified, lightSampler, position, normal, geometryNormal, viewDirection, ray, lightPDF, radianceLi, selectedLight)) + { + return; + } + + // Trace shadow ray +#if USE_INLINE_RT + ShadowRayQuery rayShadowQuery = TraceRay < ShadowRayQuery > (ray); + bool hit = rayShadowQuery.CommittedStatus() == COMMITTED_NOTHING; +#else + ShadowRayPayload payload = {false}; + TraceRay(g_Scene, SHADOW_RAY_FLAGS, 0xFFu, 1, 0, 1, ray, payload); + bool hit = payload.visible; +#endif + + // If nothing was hit then we have hit the light + if (hit) + { + // Add lighting contribution +#ifdef USE_CUSTOM_HIT_FUNCTIONS + shadeLightHitCustom(ray, material, normal, viewDirection, throughput, lightPDF, radianceLi, selectedLight, radiance); +#else + shadeLightHit(ray, material, normal, viewDirection, throughput, lightPDF, radianceLi, selectedLight, radiance, firstHit); +#endif + } +} + + /** * Calculate the next segment along a path after a valid surface hit. * @param materialBRDF The material on the hit surface. @@ -323,10 +405,25 @@ bool pathNext(MaterialBRDF materialBRDF, inout StratifiedSampler randomStratifie inout LightSampler lightSampler, uint currentBounce, uint minBounces, uint maxBounces, float3 normal, float3 geometryNormal, float3 viewDirection, inout float3 throughput, out float3 rayDirection, out float samplePDF) { - // Sample BRDF to get next ray direction float3 sampleReflectance; - rayDirection = sampleBRDF(materialBRDF, randomStratified, normal, viewDirection, sampleReflectance, samplePDF); + bool specularSampled; + +#ifdef DEBUG_REFLECTIONS + if (currentBounce == 0) + { + materialBRDF.F0 = float3(1, 1, 1); + } +#endif + rayDirection = sampleBRDF(materialBRDF, randomStratified, normal, viewDirection, sampleReflectance, samplePDF, specularSampled); +#ifdef DEBUG_REFLECTIONS + // If we decide to bounce diffusely on the first bounce, we terminate the ray + if (!specularSampled && currentBounce == 0) + { + return false; + } +#endif + // Prevent tracing directions below the surface if (dot(geometryNormal, rayDirection) <= 0.0f || samplePDF == 0.0f) { @@ -382,6 +479,16 @@ bool pathHit(inout RayDesc ray, HitInfo hitData, IntersectData iData, inout Stra return false; } +#ifdef DEBUG_REFLECTIONS + // If the first hit is too rough, then we won't get a reflection, so we terminate the ray + if (currentBounce == 0) + { + MaterialEvaluated materialEvaluated = MakeMaterialEvaluated(iData.material, iData.uv); + if (materialEvaluated.roughness > 0.6f) + return false; + } +#endif + float3 viewDirection = -ray.Direction; // Stop if surface normal places ray behind surface (note surface normal != geometric normal) // Currently disabled due to incorrect normals generated by normal mapping when not using displacement/parallax @@ -391,6 +498,8 @@ bool pathHit(inout RayDesc ray, HitInfo hitData, IntersectData iData, inout Stra //} MaterialBRDF materialBRDF = MakeMaterialBRDF(iData.material, iData.uv); + + #ifdef DISABLE_ALBEDO_MATERIAL // Disable material albedo if requested if (currentBounce == 0) @@ -409,8 +518,13 @@ bool pathHit(inout RayDesc ray, HitInfo hitData, IntersectData iData, inout Stra # endif // DISABLE_DIRECT_LIGHTING { // Sample a single light - sampleLightsNEE(materialBRDF, randomStratified, lightSampler, iData.position, - iData.normal, iData.geometryNormal, viewDirection, throughput, radiance); + bool firstHit; + if (currentBounce == 0) + firstHit = true; + else + firstHit = false; + sampleLightsNEEFirstHitInfo(materialBRDF, randomStratified, lightSampler, iData.position, + iData.normal, iData.geometryNormal, viewDirection, throughput, radiance, firstHit); } #endif // DISABLE_NEE @@ -465,6 +579,12 @@ void tracePath(RayDesc ray, inout StratifiedSampler randomStratified, inout Ligh // Check for valid intersection if (rayQuery.CommittedStatus() == COMMITTED_NOTHING) { + #ifdef DEBUG_REFLECTIONS + //No sky light for reflection debug + if (bounce == 0) + return; + #endif + # ifdef USE_CUSTOM_HIT_FUNCTIONS shadePathMissCustom(ray, bounce, lightSampler, normal, samplePDF, throughput, radiance); # else diff --git a/src/core/src/lights/lights_shared.h b/src/core/src/lights/lights_shared.h index 8ae3b719..d77e7b56 100644 --- a/src/core/src/lights/lights_shared.h +++ b/src/core/src/lights/lights_shared.h @@ -23,6 +23,9 @@ THE SOFTWARE. #ifndef LIGHTS_SHARED_H #define LIGHTS_SHARED_H +#ifdef _WIN32 +#include +#endif #include "../gpu_shared.h" enum LightType diff --git a/src/core/src/materials/material_evaluation.hlsl b/src/core/src/materials/material_evaluation.hlsl index d7c31ceb..a615398a 100644 --- a/src/core/src/materials/material_evaluation.hlsl +++ b/src/core/src/materials/material_evaluation.hlsl @@ -203,11 +203,30 @@ float3 evaluateBRDFDiffuse(MaterialBRDF material, float dotHV, float dotNL) float3 diffuse = evaluateLambert(material.albedo); // Add the weight of the diffuse compensation term to prevent excessive brightness compared to specular - diffuse *= diffuseCompensation(fresnel(0.04f.xxx, dotHV), dotHV); + diffuse *= diffuseCompensationTerm(fresnel(0.04f.xxx, dotHV), dotHV); float3 brdf = diffuse * saturate(dotNL); return brdf; } +/** + * Evaluate the diffuse component of the BRDF. + * @param material Material data describing BRDF. + * @param normal The normal of the surface + * @param viewDirection The direction from the hit point towards the view point + * @param lightDirection The direction from the hit point towards the light + * @return The calculated reflectance. + */ +float3 evaluateBRDFDiffuse(MaterialBRDF material, float3 normal, float3 viewDirection, float3 lightDirection) +{ + // Calculate shading angles + float dotNL = clamp(dot(normal, lightDirection), -1.0f, 1.0f); + // Calculate half vector + float3 halfVector = normalize(viewDirection + lightDirection); + float dotHV = saturate(dot(halfVector, viewDirection)); + + return evaluateBRDFDiffuse(material, dotHV, dotNL); +} + #ifndef DISABLE_SPECULAR_MATERIALS /** * Evaluate the specular component of the BRDF. diff --git a/src/core/src/materials/material_sampling.hlsl b/src/core/src/materials/material_sampling.hlsl index ce1e78cd..281acaef 100644 --- a/src/core/src/materials/material_sampling.hlsl +++ b/src/core/src/materials/material_sampling.hlsl @@ -418,6 +418,51 @@ float sampleBRDFPDFAndEvalute(MaterialBRDF material, float3 normal, float3 viewD return samplePDF; } +/** + * Calculate the PDF and evaluate radiance for given values for the combined BRDF. + * @param material Material data describing BRDF. + * @param normal Shading normal vector at current position. + * @param viewDirection Outgoing ray view direction. + * @param lightDirection Incoming ray light direction. + * @param reflectance (Out) Evaluated reflectance associated with the sampled ray direction. + * @param firsthit True if this was the first hit, for the debug reflection view. + * @return The calculated PDF. + */ +float sampleBRDFPDFAndEvalute(MaterialBRDF material, float3 normal, float3 viewDirection, + float3 lightDirection, out float3 reflectance, bool firstHit) +{ + // Evaluate BRDF for new light direction + float dotNL = clamp(dot(normal, lightDirection), -1.0f, 1.0f); + // Calculate half vector + float3 halfVector = normalize(viewDirection + lightDirection); + // Calculate shading angles + float dotHV = saturate(dot(halfVector, viewDirection)); + float dotNH = clamp(dot(normal, halfVector), -1.0f, 1.0f); + float dotNV = clamp(dot(normal, viewDirection), -1.0f, 1.0f); +#ifdef DEBUG_REFLECTIONS + if (firstHit) + { + reflectance = evaluateBRDFSpecular(material, dotHV, dotNH, dotNL, dotNV); + } + else + { + reflectance = evaluateBRDF(material, dotHV, dotNH, dotNL, dotNV); + } + +#else + reflectance = evaluateBRDF(material, dotHV, dotNH, dotNL, dotNV); +#endif + + // Transform the view direction into the surfaces tangent coordinate space (oriented so that z axis is aligned to normal) + Quaternion localRotation = QuaternionRotationZ(normal); + float3 localView = localRotation.transform(viewDirection); + + // Calculate combined PDF for current sample + // Note: has some duplicated calculations in evaluateBRDF and sampleBRDFPDF + float samplePDF = sampleBRDFPDF(material, dotNH, dotNL, dotHV, dotNV, localView); + return samplePDF; +} + /** * Calculate the PDF and evaluate radiance for given values for the diffuse and specular BRDF components separately. * @param material Material data describing BRDF. @@ -544,6 +589,25 @@ float3 sampleBRDF(MaterialBRDF material, inout RNG randomNG, float3 normal, floa return sampleBRDFType(material, randomNG, normal, viewDirection, reflectance, pdf, unused); } +/** + * Calculates a reflected ray direction from a surface by sampling its BRDF. + * @tparam RNG The type of random number sampler to be used. + * @param material Material data describing BRDF of surface. + * @param randomNG Random number sampler used to sample BRDF. + * @param normal Shading normal vector at current position. + * @param viewDirection Outgoing ray view direction. + * @param reflectance (Out) Evaluated reflectance associated with the sampled ray direction. + * @param pdf (Out) PDF weight associated with the sampled ray direction. + * @param specularSampled (Out) True if the specular component was sampled. + * @return The new outgoing light ray direction. + */ +template +float3 sampleBRDF(MaterialBRDF material, inout RNG randomNG, float3 normal, float3 viewDirection, + out float3 reflectance, out float pdf, out bool specularSampled) +{ + return sampleBRDFType(material, randomNG, normal, viewDirection, reflectance, pdf, specularSampled); +} + /** * Calculates a reflected ray direction from a surface by sampling its BRDFs diffuse component. * @tparam RNG The type of random number sampler to be used. diff --git a/src/core/src/render_techniques/gi1/gi1.comp b/src/core/src/render_techniques/gi1/gi1.comp index 4e1a559b..cee32529 100644 --- a/src/core/src/render_techniques/gi1/gi1.comp +++ b/src/core/src/render_techniques/gi1/gi1.comp @@ -38,6 +38,12 @@ float3 g_PreviousEye; int2 g_BlurDirection; uint2 g_BufferDimensions; uint g_UseDirectLighting; +uint g_UseTemporalFeedback; +uint g_UseTemporalMultibounceFeedback; +uint g_UseScreenSpaceReflections; +uint g_UseAlbedoReflections; +uint g_UseMultibounce; +uint g_UseBypassCache; StructuredBuffer g_Exposure; Texture2D g_DepthBuffer; @@ -132,9 +138,13 @@ struct PopulateScreenProbesPayload float hit_dist; }; +struct PopulateMultibounceCellsPayload +{ + float hit_dist; +}; + struct PopulateCellsPayload { - uint query_index; float3 world; float3 normal; float3 lighting; @@ -166,8 +176,11 @@ void ClearCounters() g_HashGridCache_PackedTileCountBuffer[0] = 0; g_HashGridCache_UpdateTileCountBuffer[0] = 0; - g_HashGridCache_VisibilityCountBuffer[0] = 0; + g_HashGridCache_VisibilityCountBuffer0[0] = 0; + g_HashGridCache_VisibilityCountBuffer1[0] = 0; g_HashGridCache_VisibilityRayCountBuffer[0] = 0; + g_HashGridCache_MultibounceCountBuffer[0] = 0; + g_HashGridCache_ResolveCountBuffer[0] = 0; g_GlossyReflections_RtSampleCountBuffer[0] = 0; } @@ -995,11 +1008,11 @@ void PopulateScreenProbesHandleHit(uint did, inout PopulateScreenProbesPayload p // We update the cell index for later passes uint visibility_index; - InterlockedAdd(g_HashGridCache_VisibilityCountBuffer[0], 1, visibility_index); + InterlockedAdd(g_HashGridCache_VisibilityCountBuffer0[0], 1, visibility_index); g_HashGridCache_VisibilityBuffer[visibility_index] = HashGridCache_PackVisibility(visibility); g_HashGridCache_VisibilityCellBuffer[visibility_index] = cell_index; g_HashGridCache_VisibilityQueryBuffer[visibility_index] = did; - + // Write out bounds of visibility requestLightSampleLocation(data.hit_position); @@ -1015,6 +1028,7 @@ void PopulateScreenProbesHandleHit(uint did, inout PopulateScreenProbesPayload p { uint cell_index = HashGridCache_CellIndex(cell_offset, tile_index); g_HashGridCache_ValueBuffer[cell_index] = uint2(0, 0); + g_HashGridCache_ValueIndirectBuffer[cell_index] = uint2(0, 0); } } @@ -1047,9 +1061,10 @@ void PopulateScreenProbesHandleHit(uint did, inout PopulateScreenProbesPayload p uint debug_cell_index = HashGridCache_PackDebugCell(data, tile_index, packed_debug_cell); // BE CAREFUL: writing to g_HashGridCache_DebugCellBuffer isn't atomic and several writings could occur - uint previous_cell_decay; - InterlockedExchange(g_HashGridCache_DecayCellBuffer[debug_cell_index], g_FrameIndex, previous_cell_decay); - if (previous_cell_decay != g_FrameIndex) + // debug_cell_index can be different from cell_index + uint previous_debug_cell_decay; + InterlockedExchange(g_HashGridCache_DebugDecayCellBuffer[debug_cell_index], g_FrameIndex, previous_debug_cell_decay); + if (previous_debug_cell_decay != g_FrameIndex) { g_HashGridCache_DebugCellBuffer[debug_cell_index] = packed_debug_cell; } @@ -1325,6 +1340,7 @@ void BlendScreenProbes(in uint did : SV_DispatchThreadID, in uint local_id : SV_ } g_ScreenProbes_ProbeBuffer[pos] = radiance; + } [numthreads(64, 1, 1)] @@ -1652,9 +1668,9 @@ void InterpolateScreenProbes(in uint2 did : SV_DispatchThreadID) return; // raytrace low roughness pixels at half resolution } else if (roughness > g_GlossyReflectionsConstants.high_roughness_threshold) + { g_ReflectionBuffer[did] = float4(irradiance * INV_PI, denoiser_hint); - return; // fall back to diffuse on high roughness surfaces } @@ -1687,7 +1703,7 @@ void InterpolateScreenProbes(in uint2 did : SV_DispatchThreadID) } radiance.xyz /= (1.0f + radiance.xyz); - + if (g_GlossyReflectionsConstants.half_res && GlossyReflections_QueueSample(did)) { g_GlossyReflections_SpecularBuffer[did >> 1] = float4(radiance, denoiser_hint); @@ -1739,7 +1755,7 @@ void PurgeTiles(in uint did : SV_DispatchThreadID) for (int cell_offset = 0; cell_offset < g_HashGridCacheConstants.num_cells_per_tile; ++cell_offset) { uint cell_index = HashGridCache_CellIndex(cell_offset, tile_index); - g_HashGridCache_DecayCellBuffer[cell_index] = 0xFFFFFFFFu; + g_HashGridCache_DebugDecayCellBuffer[cell_index] = 0xFFFFFFFFu; } #endif // DEBUG_HASH_CELLS @@ -1751,6 +1767,259 @@ void PurgeTiles(in uint did : SV_DispatchThreadID) g_HashGridCache_PackedTileIndexBuffer[packed_tile_index] = tile_index; } +void PopulateMultibounceCellsHandleHit(uint did, inout PopulateMultibounceCellsPayload payload, RayDesc ray, HitInfo hit_info, float3 albedo, float pdf) +{ + HashGridCache_Data data; + data.eye_position = g_Eye; + data.hit_position = ray.Origin + payload.hit_dist * ray.Direction; + data.direction = ray.Direction; + data.hit_distance = payload.hit_dist; + + uint tile_index; + bool is_new_tile; + uint cell_index = HashGridCache_InsertCell(data, tile_index, is_new_tile); + + if (cell_index != kGI1_InvalidId) + { + + // We update the cell index for later passes + uint multibounce_index; + InterlockedAdd(g_HashGridCache_MultibounceCountBuffer[0], 1, multibounce_index); + g_HashGridCache_MultibounceCellBuffer[multibounce_index] = cell_index; + g_HashGridCache_MultibounceQueryBuffer[multibounce_index] = g_HashGridCache_VisibilityCellBuffer[did]; // BE CAREFUL: also a cell + g_HashGridCache_MultibounceInfoBuffer[multibounce_index] = float4(albedo, pdf); + + + // Bump the cell's decay to the max. now that it's been 'touched' + uint previous_tile_decay; + InterlockedExchange(g_HashGridCache_DecayTileBuffer[tile_index], g_FrameIndex, previous_tile_decay); + + HashGridCache_Visibility visibility; + visibility.is_front_face = hit_info.frontFace; + visibility.instance_index = hit_info.instanceIndex; + visibility.geometry_index = hit_info.geometryIndex; + visibility.primitive_index = hit_info.primitiveIndex; + visibility.barycentrics = hit_info.barycentrics; + + uint visibility_index; + InterlockedAdd(g_HashGridCache_VisibilityCountBuffer1[0], 1, visibility_index); + visibility_index += g_HashGridCache_VisibilityCountBuffer0[0]; + g_HashGridCache_VisibilityBuffer[visibility_index] = HashGridCache_PackVisibility(visibility); + g_HashGridCache_VisibilityCellBuffer[visibility_index] = cell_index; + g_HashGridCache_VisibilityQueryBuffer[visibility_index] = did; // BE CAREFUL: visibility index + + // Write out bounds of visibility + requestLightSampleLocation(data.hit_position); + + // If this cell is inside a new tile, we need to add the tile to the packed storage and clear its cells. + if (is_new_tile) + { + uint packed_tile_index; + InterlockedAdd(g_HashGridCache_PackedTileCountBuffer[0], 1, packed_tile_index); + g_HashGridCache_PackedTileIndexBuffer[packed_tile_index] = tile_index; + + // Clear mip0 cells (others will be reset anyways by UpdateTiles) + for (int cell_offset = 0; cell_offset < g_HashGridCacheConstants.num_cells_per_tile_mip0; ++cell_offset) + { + uint cell_index = HashGridCache_CellIndex(cell_offset, tile_index); + g_HashGridCache_ValueBuffer[cell_index] = uint2(0, 0); + g_HashGridCache_ValueIndirectBuffer[cell_index] = uint2(0, 0); + } + } + + // If we're the 1st invocation touching this cell (this frame), we want to clear the + // scratch storage that'll be used for atomically updating the radiance. + // The accumulation will be resolved in the 'UpdateTiles()' kernel to + // avoid integer overflow. + if (is_new_tile || previous_tile_decay != g_FrameIndex) + { + int update_tile_index; + InterlockedAdd(g_HashGridCache_UpdateTileCountBuffer[0], 1, update_tile_index); + g_HashGridCache_UpdateTileBuffer[update_tile_index] = tile_index; + } + +#ifdef DEBUG_HASH_CELLS + // For debugging purposes, we need to be able to retrieve the position + // & orientation of cells as we iterate the content of the cache. + // So, write the packed cell descriptor out to memory in this case. + if (is_new_tile) + { + // Clear debug cells (all mips) + for (int cell_offset = 0; cell_offset < g_HashGridCacheConstants.num_cells_per_tile; ++cell_offset) + { + uint cell_index = HashGridCache_CellIndex(cell_offset, tile_index); + g_HashGridCache_DebugCellBuffer[cell_index] = HashGridCache_ClearDebugCell(); + } + } + + float4 packed_debug_cell; + uint debug_cell_index = HashGridCache_PackDebugCell(data, tile_index, packed_debug_cell); + + // BE CAREFUL: writing to g_HashGridCache_DebugCellBuffer isn't atomic and several writings could occur + // debug_cell_index can be different from cell_index + uint previous_debug_cell_decay; + InterlockedExchange(g_HashGridCache_DebugDecayCellBuffer[debug_cell_index], g_FrameIndex, previous_debug_cell_decay); + if (previous_debug_cell_decay != g_FrameIndex) + { + g_HashGridCache_DebugCellBuffer[debug_cell_index] = packed_debug_cell; + } +#endif // DEBUG_HASH_CELLS + } +} + +void PopulateMultibounceCellsHandleMiss(inout PopulateMultibounceCellsPayload payload, RayDesc ray) +{ + +} + +void PopulateMultibounceCellsTraceRayInline(uint did, inout PopulateMultibounceCellsPayload payload, RayDesc ray, float3 albedo, float pdf) +{ + ClosestRayQuery ray_query = TraceRay(ray); + + // If we hit some geometry, we append a new world-space hash-grid cache query + if (ray_query.CommittedStatus() == COMMITTED_NOTHING) + { + payload.hit_dist = ray_query.CommittedRayT(); + PopulateMultibounceCellsHandleMiss(payload, ray); + } + else + { + payload.hit_dist = ray_query.CommittedRayT(); + PopulateMultibounceCellsHandleHit(did, payload, ray, GetHitInfoRtInlineCommitted(ray_query), albedo, pdf); + } +} + +void PopulateMultibounceCellsTraceRayRt(uint did, inout PopulateMultibounceCellsPayload payload, RayDesc ray) +{ + TraceRay(g_Scene, RAY_FLAG_NONE, 0xFFu, 0, 0, 0, ray, payload); +} + +void PopulateMultibounceCellsTraceRay(uint did, inout PopulateMultibounceCellsPayload payload, RayDesc ray, float3 albedo, float pdf) +{ +#if USE_INLINE_RT + return PopulateMultibounceCellsTraceRayInline(did, payload, ray, albedo, pdf); +#else + return PopulateMultibounceCellsTraceRayRt(did, payload, ray); +#endif +} + +void PopulateMultibounceCells(uint did) +{ + if (did >= g_HashGridCache_VisibilityCountBuffer0[0]) + { + return; // out of bounds + } + + // Random sample + Random random = MakeRandom(did, g_FrameIndex); + float3 s = random.rand3(); + + if (s.z <= g_HashGridCacheConstants.discard_multibounce_ray_probability) + { + return; + } + + // Load our visibility sample + float4 packed_visibility = g_HashGridCache_VisibilityBuffer[did]; + HashGridCache_Visibility visibility = HashGridCache_UnpackVisibility(packed_visibility); + + // Reconstruct world-space position and normal + Instance instance = g_InstanceBuffer[visibility.instance_index]; + float3x4 transform = g_TransformBuffer[instance.transform_index]; + + TriangleNormUV vertices = fetchVerticesNormUV(instance, visibility.primitive_index); + + vertices.v0 = transformPoint(vertices.v0, transform); + vertices.v1 = transformPoint(vertices.v1, transform); + vertices.v2 = transformPoint(vertices.v2, transform); + + vertices.n0 = transformNormal(vertices.n0, transform); + vertices.n1 = transformNormal(vertices.n1, transform); + vertices.n2 = transformNormal(vertices.n2, transform); + + float3 world = interpolate(vertices.v0, vertices.v1, vertices.v2, visibility.barycentrics); + float3 normal = (visibility.is_front_face ? 1.0f : -1.0f) * normalize(interpolate(vertices.n0, vertices.n1, vertices.n2, visibility.barycentrics)); + + Material material = g_MaterialBuffer[instance.material_index]; + float2 mesh_uv = interpolate(vertices.uv0, vertices.uv1, vertices.uv2, visibility.barycentrics); + + MaterialEvaluated materialEvaluated = MakeMaterialEvaluated(material, mesh_uv); + +#ifndef DISABLE_SPECULAR_MATERIALS + // Force diffuse lighting on secondary bounce to reduce noise + materialEvaluated.metallicity = 0.0f; + materialEvaluated.roughness = 1.0f; +#endif // DISABLE_SPECULAR_MATERIALS + + MaterialBRDF materialBRDF = MakeMaterialBRDF(materialEvaluated); + + // Sample diffuse direction + Quaternion local_rotation = QuaternionRotationZ(normal); + float3 local_direction = sampleLambert(0.f, s.xy); + float pdf = local_direction.z * INV_PI; + float3 direction = normalize(local_rotation.inverse().transform(local_direction)); + + // Trace + RayDesc ray_desc; + ray_desc.Direction = direction; + ray_desc.Origin = offsetPosition(world, normal); + ray_desc.TMin = 0.0f; + ray_desc.TMax = MAX_HIT_DISTANCE; + + // Recover the ray origin + float3 origin, unused; + Reservoir_UnpackIndirectSample(g_Reservoir_IndirectSampleBuffer[did], origin, unused); + + float3 brdf = evaluateBRDF(materialBRDF, normal, normalize(origin - world), direction); + + PopulateMultibounceCellsPayload payload; + PopulateMultibounceCellsTraceRay(did, payload, ray_desc, brdf, pdf); +} + +[numthreads(32, 1, 1)] +void PopulateMultibounceCellsMain(in uint did : SV_DispatchThreadID) +{ + PopulateMultibounceCells(did); +} + +void UpdateMultibounceCells(uint did) +{ + if (did >= g_HashGridCache_MultibounceCountBuffer[0]) + { + return; // out of bounds + } + + uint cell_index = g_HashGridCache_MultibounceCellBuffer[did]; + uint query_index = g_HashGridCache_MultibounceQueryBuffer[did]; // BE CAREFUL: also a cell + float4 query_info = g_HashGridCache_MultibounceInfoBuffer[did]; + float3 brdf = query_info.xyz; + float pdf = query_info.w; + // BE CAREFUL: g_HashGridCache_ValueBuffer contains last frame + float4 direct_radiance; + HashGridCache_FilteredRadianceDirect(cell_index, false, direct_radiance); + + float3 radiance = (direct_radiance.rgb / max(direct_radiance.w, 1.0f)); + radiance.rgb = (radiance.rgb * brdf) / pdf; + radiance.rgb /= 1.0 - g_HashGridCacheConstants.discard_multibounce_ray_probability; + uint4 quantized_radiance = HashGridCache_QuantizeRadiance(radiance.rgb); + + if (dot(radiance, radiance) > 0.0f) + { + InterlockedAdd(g_HashGridCache_UpdateCellValueIndirectBuffer[4 * query_index + 0], quantized_radiance.x); + InterlockedAdd(g_HashGridCache_UpdateCellValueIndirectBuffer[4 * query_index + 1], quantized_radiance.y); + InterlockedAdd(g_HashGridCache_UpdateCellValueIndirectBuffer[4 * query_index + 2], quantized_radiance.z); + } + InterlockedAdd(g_HashGridCache_UpdateCellValueIndirectBuffer[4 * query_index + 3], quantized_radiance.w); + + // BE CAREFUL: we found no leak with close cells, consider adding bypass if multibounce leaks appear +} + +[numthreads(32, 1, 1)] +void UpdateMultibounceCellsMain(in uint did : SV_DispatchThreadID) +{ + UpdateMultibounceCells(did); +} + void PopulateCellsHandleHit(uint did, inout PopulateCellsPayload payload, RayDesc ray) { payload.lighting = float3(0.0f, 0.0f, 0.0f); @@ -1768,14 +2037,12 @@ void PopulateCellsHandleMiss(uint did, inout PopulateCellsPayload payload, RayDe { float3 light_radiance = payload.lighting; float light_weight = payload.reservoir.W; - uint probe_index = ScreenProbes_GetCellAndProbeIndex(payload.query_index).y; - uint2 seed = ScreenProbes_UnpackSeed(g_ScreenProbes_ProbeSpawnBuffer[probe_index]); + MaterialBRDF material = unpackMaterial(g_Reservoir_IndirectSampleMaterialBuffer[did]); // Recover the ray origin - float depth = g_DepthBuffer.Load(int3(seed, 0)).x; - float2 uv = (seed + 0.5f) / g_BufferDimensions; - float3 origin = transformPointProjection(uv, depth, g_ViewProjectionInverse); + float3 origin, unused; + Reservoir_UnpackIndirectSample(g_Reservoir_IndirectSampleBuffer[did], origin, unused); // And evaluate our lighting payload.lighting = @@ -1821,7 +2088,6 @@ void PopulateCells(uint did) uint visibility_index = (g_HashGridCache_VisibilityRayBuffer[did] & ~0x80000000u); HashGridCache_Visibility visibility = HashGridCache_UnpackVisibility(g_HashGridCache_VisibilityBuffer[visibility_index]); - uint query_index = g_HashGridCache_VisibilityQueryBuffer[visibility_index]; // Reconstruct world-space position and normal Instance instance = g_InstanceBuffer[visibility.instance_index]; @@ -1856,17 +2122,19 @@ void PopulateCells(uint did) ray_desc.TMax = hasLightPosition(selected_light) ? 1.0f - SHADOW_RAY_EPSILON : FLT_MAX; PopulateCellsPayload payload; - payload.query_index = query_index; payload.world = world; payload.normal = normal; payload.lighting = light_radiance; payload.reservoir = reservoir; + PopulateCellsTraceRay(did, payload, ray_desc); - + + uint4 quantized_radiance; + // And update the hash-grid cell payload - uint cell_index = g_HashGridCache_VisibilityCellBuffer[visibility_index]; - uint4 quantized_radiance = HashGridCache_QuantizeRadiance(payload.lighting); - + quantized_radiance = HashGridCache_QuantizeRadiance(payload.lighting); + + uint cell_index = g_HashGridCache_VisibilityCellBuffer[visibility_index]; if (dot(payload.lighting, payload.lighting) > 0.0f) { InterlockedAdd(g_HashGridCache_UpdateCellValueBuffer[4 * cell_index + 0], quantized_radiance.x); @@ -1878,10 +2146,11 @@ void PopulateCells(uint did) // In some cases, we want to bypass the cache to avoid light leaks; // so we simply patch the screen probes directly and invalidate the // cache connection. - bool is_bypass_cache = ((g_HashGridCache_VisibilityRayBuffer[did] >> 31) != 0); + bool bypass_cache = ((g_HashGridCache_VisibilityRayBuffer[did] >> 31) != 0); - if (is_bypass_cache) + if (bypass_cache) { + uint query_index = g_HashGridCache_VisibilityQueryBuffer[visibility_index]; ScreenProbes_AccumulateRadiance(query_index, GIDenoiser_RemoveNaNs(payload.lighting)); } } @@ -1901,6 +2170,7 @@ void PopulateCellsMain(in uint did : SV_DispatchThreadID) #define UPDATE_TILES_GROUP_SIZE (UPDATE_TILES_GROUP_X * UPDATE_TILES_GROUP_Y) groupshared uint2 lds_UpdateTiles_ValueBuffer[UPDATE_TILES_GROUP_X][UPDATE_TILES_GROUP_Y]; +groupshared uint2 lds_UpdateTiles_ValueIndirectBuffer[UPDATE_TILES_GROUP_X][UPDATE_TILES_GROUP_Y]; [numthreads(1, 1, 1)] void GenerateUpdateTilesDispatch() @@ -1938,38 +2208,66 @@ void UpdateTiles(in uint group_id : SV_GroupID, in uint2 group_thread_id : SV_Gr uint cell_index = HashGridCache_CellIndex(subgroup_thread_id, tile_index, 0); // Temporal accumulation - float4 radiance = HashGridCache_UnpackRadiance(g_HashGridCache_ValueBuffer[cell_index]); - float4 new_radiance = HashGridCache_RecoverRadiance(uint4(g_HashGridCache_UpdateCellValueBuffer[4 * cell_index + 0], - g_HashGridCache_UpdateCellValueBuffer[4 * cell_index + 1], - g_HashGridCache_UpdateCellValueBuffer[4 * cell_index + 2], - g_HashGridCache_UpdateCellValueBuffer[4 * cell_index + 3])); - - float sample_count = min(radiance.w + new_radiance.w, g_HashGridCacheConstants.max_sample_count); - - radiance /= max(radiance.w, 1.0f); - new_radiance /= max(new_radiance.w, 1.0f); - - if (radiance.w <= 0.0f) + float4 direct_radiance = HashGridCache_UnpackRadiance(g_HashGridCache_ValueBuffer[cell_index]); + float4 indirect_radiance = HashGridCache_UnpackRadiance(g_HashGridCache_ValueIndirectBuffer[cell_index]); + float4 new_direct_radiance = HashGridCache_RecoverRadiance(uint4(g_HashGridCache_UpdateCellValueBuffer[4 * cell_index + 0], + g_HashGridCache_UpdateCellValueBuffer[4 * cell_index + 1], + g_HashGridCache_UpdateCellValueBuffer[4 * cell_index + 2], + g_HashGridCache_UpdateCellValueBuffer[4 * cell_index + 3])); + + float4 new_indirect_radiance = HashGridCache_RecoverRadiance(uint4(g_HashGridCache_UpdateCellValueIndirectBuffer[4 * cell_index + 0], + g_HashGridCache_UpdateCellValueIndirectBuffer[4 * cell_index + 1], + g_HashGridCache_UpdateCellValueIndirectBuffer[4 * cell_index + 2], + g_HashGridCache_UpdateCellValueIndirectBuffer[4 * cell_index + 3])); + + float direct_sample_count = min(direct_radiance.w + new_direct_radiance.w, g_HashGridCacheConstants.max_sample_count); + float indirect_sample_count = min(indirect_radiance.w + new_indirect_radiance.w, g_HashGridCacheConstants.max_multibounce_sample_count); + + direct_radiance /= max(direct_radiance.w, 1.0f); + new_direct_radiance /= max(new_direct_radiance.w, 1.0f); + indirect_radiance /= max(indirect_radiance.w, 1.0f); + new_indirect_radiance /= max(new_indirect_radiance.w, 1.0f); + + if (direct_radiance.w <= 0.0f) + { + direct_radiance = new_direct_radiance; + } + else + { + direct_radiance = lerp(direct_radiance, new_direct_radiance, 1.0f / direct_sample_count); + } + + if (indirect_radiance.w <= 0.0f) { - radiance = new_radiance; + indirect_radiance = new_indirect_radiance; } else { - radiance = lerp(radiance, new_radiance, 1.0f / sample_count); + indirect_radiance = lerp(indirect_radiance, new_indirect_radiance, 1.0f / indirect_sample_count); } - radiance *= sample_count; // sample count is used as a hint for picking prefiltering amount + direct_radiance *= direct_sample_count; // sample count is used as a hint for picking prefiltering amount + indirect_radiance *= indirect_sample_count; // sample count is used as a hint for picking prefiltering amount // Pack - uint2 packed_radiance = HashGridCache_PackRadiance(radiance); - lds_UpdateTiles_ValueBuffer[group_thread_id.x][group_thread_id.y] = packed_radiance; - g_HashGridCache_ValueBuffer[cell_index] = packed_radiance; - + uint2 packed_direct_radiance = HashGridCache_PackRadiance(direct_radiance); + lds_UpdateTiles_ValueBuffer[group_thread_id.x][group_thread_id.y] = packed_direct_radiance; + g_HashGridCache_ValueBuffer[cell_index] = packed_direct_radiance; + + uint2 packed_indirect_radiance = HashGridCache_PackRadiance(indirect_radiance); + lds_UpdateTiles_ValueIndirectBuffer[group_thread_id.x][group_thread_id.y] = packed_indirect_radiance; + g_HashGridCache_ValueIndirectBuffer[cell_index] = packed_indirect_radiance; + // Clear scratch g_HashGridCache_UpdateCellValueBuffer[4 * cell_index + 0] = 0; g_HashGridCache_UpdateCellValueBuffer[4 * cell_index + 1] = 0; g_HashGridCache_UpdateCellValueBuffer[4 * cell_index + 2] = 0; g_HashGridCache_UpdateCellValueBuffer[4 * cell_index + 3] = 0; + + g_HashGridCache_UpdateCellValueIndirectBuffer[4 * cell_index + 0] = 0; + g_HashGridCache_UpdateCellValueIndirectBuffer[4 * cell_index + 1] = 0; + g_HashGridCache_UpdateCellValueIndirectBuffer[4 * cell_index + 2] = 0; + g_HashGridCache_UpdateCellValueIndirectBuffer[4 * cell_index + 3] = 0; } // MIP 1 @@ -1995,6 +2293,24 @@ void UpdateTiles(in uint group_id : SV_GroupID, in uint2 group_thread_id : SV_Gr uint2 packed_radiance = HashGridCache_PackRadiance(radiance); lds_UpdateTiles_ValueBuffer[group_thread_id.x][group_thread_id.y] = packed_radiance; g_HashGridCache_ValueBuffer[cell_index] = packed_radiance; + + // Indirect + // Box filter + uint2 packed_radiance_indirect00 = lds_UpdateTiles_ValueIndirectBuffer[group_thread_id.x + 0][group_thread_id.y + 0]; + uint2 packed_radiance_indirect10 = lds_UpdateTiles_ValueIndirectBuffer[group_thread_id.x + 1][group_thread_id.y + 0]; + uint2 packed_radiance_indirect01 = lds_UpdateTiles_ValueIndirectBuffer[group_thread_id.x + 0][group_thread_id.y + 1]; + uint2 packed_radiance_indirect11 = lds_UpdateTiles_ValueIndirectBuffer[group_thread_id.x + 1][group_thread_id.y + 1]; + + float4 indirect_radiance = float4(0.f, 0.f, 0.f, 0.f); + indirect_radiance += HashGridCache_UnpackRadiance(packed_radiance_indirect00); + indirect_radiance += HashGridCache_UnpackRadiance(packed_radiance_indirect10); + indirect_radiance += HashGridCache_UnpackRadiance(packed_radiance_indirect01); + indirect_radiance += HashGridCache_UnpackRadiance(packed_radiance_indirect11); + + // Pack + uint2 packed_radiance_indirect = HashGridCache_PackRadiance(indirect_radiance); + lds_UpdateTiles_ValueIndirectBuffer[group_thread_id.x][group_thread_id.y] = packed_radiance_indirect; + g_HashGridCache_ValueIndirectBuffer[cell_index] = packed_radiance_indirect; } // MIP 2 @@ -2020,6 +2336,23 @@ void UpdateTiles(in uint group_id : SV_GroupID, in uint2 group_thread_id : SV_Gr uint2 packed_radiance = HashGridCache_PackRadiance(radiance); lds_UpdateTiles_ValueBuffer[group_thread_id.x][group_thread_id.y] = packed_radiance; g_HashGridCache_ValueBuffer[cell_index] = packed_radiance; + + // Box filter + uint2 packed_radiance_indirect00 = lds_UpdateTiles_ValueIndirectBuffer[group_thread_id.x + 0][group_thread_id.y + 0]; + uint2 packed_radiance_indirect20 = lds_UpdateTiles_ValueIndirectBuffer[group_thread_id.x + 2][group_thread_id.y + 0]; + uint2 packed_radiance_indirect02 = lds_UpdateTiles_ValueIndirectBuffer[group_thread_id.x + 0][group_thread_id.y + 2]; + uint2 packed_radiance_indirect22 = lds_UpdateTiles_ValueIndirectBuffer[group_thread_id.x + 2][group_thread_id.y + 2]; + + float4 radiance_indirect = float4(0.f, 0.f, 0.f, 0.f); + radiance_indirect += HashGridCache_UnpackRadiance(packed_radiance_indirect00); + radiance_indirect += HashGridCache_UnpackRadiance(packed_radiance_indirect20); + radiance_indirect += HashGridCache_UnpackRadiance(packed_radiance_indirect02); + radiance_indirect += HashGridCache_UnpackRadiance(packed_radiance_indirect22); + + // Pack + uint2 packed_radiance_indirect = HashGridCache_PackRadiance(radiance_indirect); + lds_UpdateTiles_ValueIndirectBuffer[group_thread_id.x][group_thread_id.y] = packed_radiance_indirect; + g_HashGridCache_ValueIndirectBuffer[cell_index] = packed_radiance_indirect; } // MIP 3 @@ -2043,20 +2376,36 @@ void UpdateTiles(in uint group_id : SV_GroupID, in uint2 group_thread_id : SV_Gr // Pack g_HashGridCache_ValueBuffer[cell_index] = HashGridCache_PackRadiance(radiance); + + // Box filter + uint2 packed_radiance_indirect00 = lds_UpdateTiles_ValueIndirectBuffer[group_thread_id.x + 0][group_thread_id.y + 0]; + uint2 packed_radiance_indirect40 = lds_UpdateTiles_ValueIndirectBuffer[group_thread_id.x + 4][group_thread_id.y + 0]; + uint2 packed_radiance_indirect04 = lds_UpdateTiles_ValueIndirectBuffer[group_thread_id.x + 0][group_thread_id.y + 4]; + uint2 packed_radiance_indirect44 = lds_UpdateTiles_ValueIndirectBuffer[group_thread_id.x + 4][group_thread_id.y + 4]; + + float4 radiance_indirect = float4(0.f, 0.f, 0.f, 0.f); + radiance_indirect += HashGridCache_UnpackRadiance(packed_radiance_indirect00); + radiance_indirect += HashGridCache_UnpackRadiance(packed_radiance_indirect40); + radiance_indirect += HashGridCache_UnpackRadiance(packed_radiance_indirect04); + radiance_indirect += HashGridCache_UnpackRadiance(packed_radiance_indirect44); + + // Pack + g_HashGridCache_ValueIndirectBuffer[cell_index] = HashGridCache_PackRadiance(radiance_indirect); } } [numthreads(64, 1, 1)] void ResolveCells(in uint did : SV_DispatchThreadID) { - if (did >= g_HashGridCache_VisibilityRayCountBuffer[0]) + if (did >= g_HashGridCache_ResolveCountBuffer[0]) { return; // out of bounds } - uint visibility_index = g_HashGridCache_VisibilityRayBuffer[did]; + uint visibility_index = g_HashGridCache_ResolveBuffer[did]; - if ((visibility_index >> 31) != 0) + bool bypass_cache = (visibility_index >> 31) != 0; + if (bypass_cache) { return; // do not use filtered radiance } @@ -2064,9 +2413,12 @@ void ResolveCells(in uint did : SV_DispatchThreadID) uint cell_index = g_HashGridCache_VisibilityCellBuffer[visibility_index]; uint query_index = g_HashGridCache_VisibilityQueryBuffer[visibility_index]; - float4 radiance = HashGridCache_FilteredRadiance(cell_index, false); + float4 direct_radiance, indirect_radiance; + HashGridCache_FilteredRadianceDirect(cell_index, false, direct_radiance); + HashGridCache_FilteredRadianceIndirect(cell_index, false, indirect_radiance); - ScreenProbes_AccumulateRadiance(query_index, radiance.xyz / max(radiance.w, 1.0f)); + float3 radiance = (direct_radiance.rgb / max(direct_radiance.w, 1.0f)) + (indirect_radiance.rgb / max(indirect_radiance.w, 1.0f)); + ScreenProbes_AccumulateRadiance(query_index, radiance); } //! @@ -2083,7 +2435,7 @@ void ClearReservoirs(in uint did : SV_DispatchThreadID) [numthreads(64, 1, 1)] void GenerateReservoirs(in uint did : SV_DispatchThreadID) { - if (did >= g_HashGridCache_VisibilityCountBuffer[0]) + if (did >= g_HashGridCache_VisibilityCountBuffer0[0]) { return; // out of bounds } @@ -2139,7 +2491,7 @@ void GenerateReservoirs(in uint did : SV_DispatchThreadID) // evaluated. // If successful, we inject the reprojected radiance into the cache so it can be re-used // by neighbor vertices but bypass the filtered readback as the sample is already denoised. - if (g_UseDirectLighting != 0) + if (g_UseDirectLighting != 0 && g_UseTemporalFeedback == 1) { float3 homogeneous = transformPointProjection(world, g_ViewProjection); @@ -2196,16 +2548,10 @@ void GenerateReservoirs(in uint did : SV_DispatchThreadID) LightSampler lightSampler = MakeLightSampler(random); Reservoir reservoir = lightSampler.sampleLightListCone(world, normal, view_direction, solid_angle, materialBRDF); - if (!reservoir.isValid()) - { - return; - } - - // Append our resampled shadow ray - uint ray_index; - InterlockedAdd(g_HashGridCache_VisibilityRayCountBuffer[0], 1, ray_index); - - g_HashGridCache_VisibilityRayBuffer[ray_index] = did; // compact the surviving shadow rays + // Resolve cells (no reservoir only discards nee, not second bounce contrib we need to resolve into screen probes) + uint resolve_index; + InterlockedAdd(g_HashGridCache_ResolveCountBuffer[0], 1, resolve_index); + g_HashGridCache_ResolveBuffer[resolve_index] = did; // Here, we check that the ray has traversed at least a full hash cell // before hitting something. @@ -2232,10 +2578,26 @@ void GenerateReservoirs(in uint did : SV_DispatchThreadID) // Such a scenario mostly occurs if the length of the ray connecting // the 2nd vertex is less than the size of the hash cell being used. // So, we detect this case and flag a bypass of the filtered readback. - float cell_size = HashGridCache_GetCellSize(world); - float ray_length = distance(origin, world); - if (ray_length < cell_size) + float cell_size = HashGridCache_GetCellSize(world); + float ray_length = distance(origin, world); + bool bypass_cache = ray_length < cell_size && g_UseBypassCache; + if (bypass_cache) + { + g_HashGridCache_ResolveBuffer[resolve_index] |= 0x80000000u; + } + + if (!reservoir.isValid()) + { + return; + } + + // Append our resampled shadow ray + uint ray_index; + InterlockedAdd(g_HashGridCache_VisibilityRayCountBuffer[0], 1, ray_index); + + g_HashGridCache_VisibilityRayBuffer[ray_index] = did; // compact the surviving shadow rays + if (bypass_cache) { g_HashGridCache_VisibilityRayBuffer[ray_index] |= 0x80000000u; } @@ -2248,11 +2610,142 @@ void GenerateReservoirs(in uint did : SV_DispatchThreadID) jitter *= Reservoir_GetCellSize(world) * kReservoir_SpatialJitter; Reservoir_InsertEntry(ray_index, world + jitter.x * b1 + jitter.y * b2); - g_Reservoir_IndirectSampleBuffer[ray_index] = Reservoir_PackIndirectSample(origin, world); g_Reservoir_IndirectSampleNormalBuffer[ray_index] = packNormal(normal); #endif // USE_RESAMPLING // Write the results out to memory + g_Reservoir_IndirectSampleBuffer[ray_index] = Reservoir_PackIndirectSample(origin, world); + g_Reservoir_IndirectSampleMaterialBuffer[ray_index] = packMaterial(material2); // cannot afford to re-fetch the full material each time, so we need to approximate it... + g_Reservoir_IndirectSampleReservoirBuffer[ray_index] = packReservoir(reservoir); +} + +[numthreads(64, 1, 1)] +void GenerateMultibounceReservoirs(in uint did : SV_DispatchThreadID) +{ + if (did >= g_HashGridCache_VisibilityCountBuffer1[0]) + { + return; // out of bounds + } + + did += g_HashGridCache_VisibilityCountBuffer0[0]; + + // Load our visibility sample + float4 packed_visibility = g_HashGridCache_VisibilityBuffer[did]; + HashGridCache_Visibility visibility = HashGridCache_UnpackVisibility(packed_visibility); + + // Reconstruct world-space position and normal + Instance instance = g_InstanceBuffer[visibility.instance_index]; + float3x4 transform = g_TransformBuffer[instance.transform_index]; + + TriangleNormUV vertices = fetchVerticesNormUV(instance, visibility.primitive_index); + + vertices.v0 = transformPoint(vertices.v0, transform); + vertices.v1 = transformPoint(vertices.v1, transform); + vertices.v2 = transformPoint(vertices.v2, transform); + float3 world = interpolate(vertices.v0, vertices.v1, vertices.v2, visibility.barycentrics); + float3 normal = (visibility.is_front_face ? 1.0f : -1.0f) * normalize(interpolate(vertices.n0, vertices.n1, vertices.n2, visibility.barycentrics)); + Material material = g_MaterialBuffer[instance.material_index]; + float2 mesh_uv = interpolate(vertices.uv0, vertices.uv1, vertices.uv2, visibility.barycentrics); + + // Recover the ray origin + uint query_index = g_HashGridCache_VisibilityQueryBuffer[did]; + float4 query_packed_visibility = g_HashGridCache_VisibilityBuffer[query_index]; + HashGridCache_Visibility query_visibility = HashGridCache_UnpackVisibility(query_packed_visibility); + + Instance query_instance = g_InstanceBuffer[query_visibility.instance_index]; + float3x4 query_transform = g_TransformBuffer[query_instance.transform_index]; + + TriangleNorm query_vertices = fetchVerticesNorm(query_instance, query_visibility.primitive_index); + + query_vertices.v0 = transformPoint(query_vertices.v0, query_transform); + query_vertices.v1 = transformPoint(query_vertices.v1, query_transform); + query_vertices.v2 = transformPoint(query_vertices.v2, query_transform); + + float3 origin = interpolate(query_vertices.v0, query_vertices.v1, query_vertices.v2, query_visibility.barycentrics); + + // We can perform some temporal radiance feedback from last frame if direct lighting was + // evaluated. + // If successful, we inject the reprojected radiance into the cache so it can be re-used + // by neighbor vertices but bypass the filtered readback as the sample is already denoised. + if (g_UseDirectLighting != 0 && g_UseTemporalMultibounceFeedback == 1) + { + float3 homogeneous = transformPointProjection(world, g_ViewProjection); + float2 uv = 0.5f * float2(homogeneous.x, -homogeneous.y) + 0.5f; + float depth = homogeneous.z; + + if (all(uv > 0.0f) && all(uv < 1.0f) && depth > 0.0f && depth < 1.0f) + { + float2 previous_uv = uv - g_VelocityBuffer.SampleLevel(g_NearestSampler, uv, 0.0f).xy; + + if (all(previous_uv > 0.0f) && all(previous_uv < 1.0f)) + { + float3 homogeneous2 = transformPointProjection(uv, depth, g_Reprojection); + homogeneous2.z = toLinearDepth(homogeneous2.z, g_NearFar); + + float previous_depth = toLinearDepth(g_PreviousDepthBuffer.SampleLevel(g_NearestSampler, previous_uv, 0.0f).x, g_NearFar); + float3 previous_normal = normalize(2.0f * g_PreviousNormalBuffer.SampleLevel(g_NearestSampler, previous_uv, 0.0f).xyz - 1.0f); + + if (dot(previous_normal, normal) > 5e-1f && abs(previous_depth - homogeneous2.z) / homogeneous2.z < 5e-2f) + { + float3 previous_lighting = g_PrevCombinedIlluminationBuffer.SampleLevel(g_NearestSampler, previous_uv, 0.0f).xyz; + + uint cell_index = g_HashGridCache_VisibilityCellBuffer[did]; + uint4 quantized_radiance = HashGridCache_QuantizeRadiance(previous_lighting); + + ScreenProbes_AccumulateRadiance(query_index, previous_lighting); + + if (dot(previous_lighting, previous_lighting) > 0.0f) + { + InterlockedAdd(g_HashGridCache_UpdateCellValueBuffer[4 * cell_index + 0], quantized_radiance.x); + InterlockedAdd(g_HashGridCache_UpdateCellValueBuffer[4 * cell_index + 1], quantized_radiance.y); + InterlockedAdd(g_HashGridCache_UpdateCellValueBuffer[4 * cell_index + 2], quantized_radiance.z); + } + InterlockedAdd(g_HashGridCache_UpdateCellValueBuffer[4 * cell_index + 3], quantized_radiance.w); + + return; // we can skip the shadow ray for this sample :) + } + } + } + } + + float3 view_direction = normalize(origin - world); + + // Sample new lights + const float solid_angle = FOUR_PI / (kReservoir_SampleCount * 1.5f); + MaterialEvaluated material2 = MakeMaterialEvaluated(material, mesh_uv); +#ifndef DISABLE_SPECULAR_MATERIALS + // Force diffuse lighting on secondary bounce to reduce noise + // TODO: This is technically not purely diffuse as still has fresnel + material2.metallicity = 0.0f; + material2.roughness = 1.0f; +#endif // DISABLE_SPECULAR_MATERIALS + MaterialBRDF materialBRDF = MakeMaterialBRDF(material2); + LightSampler lightSampler = MakeLightSampler(MakeRandom(did, g_FrameIndex)); + Reservoir reservoir = lightSampler.sampleLightListCone(world, normal, view_direction, solid_angle, materialBRDF); + + if (!reservoir.isValid()) + { + return; + } + + // Append our resampled shadow ray + uint ray_index; + InterlockedAdd(g_HashGridCache_VisibilityRayCountBuffer[0], 1, ray_index); + g_HashGridCache_VisibilityRayBuffer[ray_index] = did; // compact the surviving shadow rays + + // Insert the reservoir into the hash table +#ifdef USE_RESAMPLING + float3 b1, b2; + GetOrthoVectors(normal, b1, b2); + float2 jitter = 2.0f * random.rand2() - 1.0f; + jitter *= Reservoir_GetCellSize(world) * kReservoir_SpatialJitter; + Reservoir_InsertEntry(ray_index, world + jitter.x * b1 + jitter.y * b2); + + g_Reservoir_IndirectSampleNormalBuffer[ray_index] = packNormal(normal); +#endif // USE_RESAMPLING + + // Write the results out to memory + g_Reservoir_IndirectSampleBuffer[ray_index] = Reservoir_PackIndirectSample(origin, world); g_Reservoir_IndirectSampleMaterialBuffer[ray_index] = packMaterial(material2); // cannot afford to re-fetch the full material each time, so we need to approximate it... g_Reservoir_IndirectSampleReservoirBuffer[ray_index] = packReservoir(reservoir); } @@ -2402,23 +2895,27 @@ void TraceReflectionsHandleHit(uint did, inout TraceReflectionsPayload payload, { // Use previous frame lighting when available bool previous_frame_available = false; - if (all(uv > 0.0f) && all(uv < 1.0f) && depth > 0.0f) + + if (g_UseScreenSpaceReflections) { - float2 previous_uv = uv - g_VelocityBuffer.SampleLevel(g_NearestSampler, uv, 0.0f).xy; - if (all(previous_uv > 0.0f) && all(previous_uv < 1.0f)) + if (all(uv > 0.0f) && all(uv < 1.0f) && depth > 0.0f) { - float previous_depth = toLinearDepth(g_PreviousDepthBuffer.SampleLevel(g_NearestSampler, previous_uv, 0.0f).x, g_NearFar); - float3 previous_normal = normalize(2.0f * g_PreviousNormalBuffer.SampleLevel(g_NearestSampler, previous_uv, 0.0f).xyz - 1.0f); + float2 previous_uv = uv - g_VelocityBuffer.SampleLevel(g_NearestSampler, uv, 0.0f).xy; + if (all(previous_uv > 0.0f) && all(previous_uv < 1.0f)) + { + float previous_depth = toLinearDepth(g_PreviousDepthBuffer.SampleLevel(g_NearestSampler, previous_uv, 0.0f).x, g_NearFar); + float3 previous_normal = normalize(2.0f * g_PreviousNormalBuffer.SampleLevel(g_NearestSampler, previous_uv, 0.0f).xyz - 1.0f); - float3 homogeneous2 = transformPointProjection(uv, depth, g_Reprojection); - homogeneous2.z = toLinearDepth(homogeneous2.z, g_NearFar); + float3 homogeneous2 = transformPointProjection(uv, depth, g_Reprojection); + homogeneous2.z = toLinearDepth(homogeneous2.z, g_NearFar); - if (abs(homogeneous2.z - previous_depth) / homogeneous2.z < 1e-2f && dot(hit_normal, previous_normal) > 0.95f) - { - float3 previous_lighting = g_PrevCombinedIlluminationBuffer.SampleLevel(g_NearestSampler, previous_uv, 0.0f).xyz; - payload.hit_distance = hit_distance; - payload.radiance = previous_lighting; - previous_frame_available = true; + if (abs(homogeneous2.z - previous_depth) / homogeneous2.z < 1e-2f && dot(hit_normal, previous_normal) > 0.95f) + { + float3 previous_lighting = g_PrevCombinedIlluminationBuffer.SampleLevel(g_NearestSampler, previous_uv, 0.0f).xyz; + payload.hit_distance = hit_distance; + payload.radiance = previous_lighting; + previous_frame_available = true; + } } } } @@ -2448,8 +2945,11 @@ void TraceReflectionsHandleHit(uint did, inout TraceReflectionsPayload payload, uint previous_tile_decay; InterlockedExchange(g_HashGridCache_DecayTileBuffer[tile_index], g_FrameIndex, previous_tile_decay); - float4 li = HashGridCache_FilteredRadiance(cell_index, false); - payload.radiance = (li.xyz / max(li.w, 1.0f)); + float4 direct_radiance, indirect_radiance; + HashGridCache_FilteredRadianceDirect(cell_index, false, direct_radiance); + HashGridCache_FilteredRadianceIndirect(cell_index, false, indirect_radiance); + float3 radiance = (direct_radiance.rgb / max(direct_radiance.w, 1.0f)) + (indirect_radiance.rgb / max(indirect_radiance.w, 1.0f)); + payload.radiance = radiance; payload.hit_distance = hit_distance; } } @@ -2576,6 +3076,7 @@ void TraceReflections(in uint did) payload.radiance /= (1.0f + payload.radiance); int2 half_pos = GlossyReflections_FullToHalfRes(full_pos); + g_GlossyReflections_SpecularBuffer[half_pos] = float4(payload.radiance, payload.hit_distance); g_GlossyReflections_DirectionBuffer[half_pos] = payload.hit_distance > 0.f && payload.hit_distance < 100.f ? float4(world + payload.hit_distance * direction - g_Eye, 1.f) diff --git a/src/core/src/render_techniques/gi1/gi1.cpp b/src/core/src/render_techniques/gi1/gi1.cpp index b2186ea6..60a003fd 100644 --- a/src/core/src/render_techniques/gi1/gi1.cpp +++ b/src/core/src/render_techniques/gi1/gi1.cpp @@ -35,6 +35,12 @@ char const *kPopulateScreenProbesAnyHitShaderName = "PopulateScreenProbesAny char const *kPopulateScreenProbesClosestHitShaderName = "PopulateScreenProbesClosestHit"; char const *kPopulateScreenProbesHitGroupName = "PopulateScreenProbesHitGroup"; +char const *kPopulateMultibounceCellsRaygenShaderName = "PopulateMultibounceCellsRaygen"; +char const *kPopulateMultibounceCellsMissShaderName = "PopulateMultibounceCellsMiss"; +char const *kPopulateMultibounceCellsAnyHitShaderName = "PopulateMultibounceCellsAnyHit"; +char const *kPopulateMultibounceCellsClosestHitShaderName = "PopulateMultibounceCellsClosestHit"; +char const *kPopulateMultibounceCellsHitGroupName = "PopulateMultibounceCellsHitGroup"; + char const *kPopulateCellsRaygenShaderName = "PopulateCellsRaygen"; char const *kPopulateCellsMissShaderName = "PopulateCellsMiss"; char const *kPopulateCellsAnyHitShaderName = "PopulateCellsAnyHit"; @@ -366,17 +372,21 @@ GI1::HashGridCache::HashGridCache(GI1 &gi1) , first_cell_offset_tile_mip3_(0) , radiance_cache_hash_buffer_ping_pong_(0) , radiance_cache_hash_buffer_(radiance_cache_hash_buffer_uint_[HASHGRIDCACHE_HASHBUFFER]) - , radiance_cache_decay_cell_buffer_(radiance_cache_hash_buffer_uint_[HASHGRIDCACHE_DECAYCELLBUFFER]) , radiance_cache_decay_tile_buffer_(radiance_cache_hash_buffer_uint_[HASHGRIDCACHE_DECAYTILEBUFFER]) , radiance_cache_value_buffer_(radiance_cache_hash_buffer_uint2_[HASHGRIDCACHE_VALUEBUFFER]) + , radiance_cache_value_indirect_buffer_(radiance_cache_hash_buffer_uint2_[HASHGRIDCACHE_VALUEINDIRECTBUFFER]) , radiance_cache_update_tile_buffer_(radiance_cache_hash_buffer_uint_[HASHGRIDCACHE_UPDATETILEBUFFER]) , radiance_cache_update_tile_count_buffer_( radiance_cache_hash_buffer_uint_[HASHGRIDCACHE_UPDATETILECOUNTBUFFER]) , radiance_cache_update_cell_value_buffer_( radiance_cache_hash_buffer_uint_[HASHGRIDCACHE_UPDATECELLVALUEBUFFER]) + , radiance_cache_update_cell_value_indirect_buffer_( + radiance_cache_hash_buffer_uint_[HASHGRIDCACHE_UPDATECELLVALUEINDIRECTBUFFER]) , radiance_cache_visibility_buffer_(radiance_cache_hash_buffer_float4_[HASHGRIDCACHE_VISIBILITYBUFFER]) - , radiance_cache_visibility_count_buffer_( - radiance_cache_hash_buffer_uint_[HASHGRIDCACHE_VISIBILITYCOUNTBUFFER]) + , radiance_cache_visibility_count_buffer0_( + radiance_cache_hash_buffer_uint_[HASHGRIDCACHE_VISIBILITYCOUNTBUFFER0]) + , radiance_cache_visibility_count_buffer1_( + radiance_cache_hash_buffer_uint_[HASHGRIDCACHE_VISIBILITYCOUNTBUFFER1]) , radiance_cache_visibility_cell_buffer_( radiance_cache_hash_buffer_uint_[HASHGRIDCACHE_VISIBILITYCELLBUFFER]) , radiance_cache_visibility_query_buffer_( @@ -385,6 +395,16 @@ GI1::HashGridCache::HashGridCache(GI1 &gi1) radiance_cache_hash_buffer_uint_[HASHGRIDCACHE_VISIBILITYRAYBUFFER]) , radiance_cache_visibility_ray_count_buffer_( radiance_cache_hash_buffer_uint_[HASHGRIDCACHE_VISIBILITYRAYCOUNTBUFFER]) + , radiance_cache_multibounce_count_buffer_( + radiance_cache_hash_buffer_uint_[HASHGRIDCACHE_MULTIBOUNCECOUNTBUFFER]) + , radiance_cache_multibounce_cell_buffer_( + radiance_cache_hash_buffer_uint_[HASHGRIDCACHE_MULTIBOUNCECELLBUFFER]) + , radiance_cache_multibounce_query_buffer_( + radiance_cache_hash_buffer_uint_[HASHGRIDCACHE_MULTIBOUNCEQUERYBUFFER]) + , radiance_cache_multibounce_info_buffer_( + radiance_cache_hash_buffer_float4_[HASHGRIDCACHE_MULTIBOUNCEINFOBUFFER]) + , radiance_cache_resolve_count_buffer_(radiance_cache_hash_buffer_uint_[HASHGRIDCACHE_RESOLVECOUNTBUFFER]) + , radiance_cache_resolve_buffer_(radiance_cache_hash_buffer_uint_[HASHGRIDCACHE_RESOLVEBUFFER]) , radiance_cache_packed_tile_count_buffer0_( radiance_cache_hash_buffer_uint_[HASHGRIDCACHE_PACKEDTILECOUNTBUFFER0]) , radiance_cache_packed_tile_count_buffer1_( @@ -393,6 +413,8 @@ GI1::HashGridCache::HashGridCache(GI1 &gi1) radiance_cache_hash_buffer_uint_[HASHGRIDCACHE_PACKEDTILEINDEXBUFFER0]) , radiance_cache_packed_tile_index_buffer1_( radiance_cache_hash_buffer_uint_[HASHGRIDCACHE_PACKEDTILEINDEXBUFFER1]) + , radiance_cache_debug_decay_cell_buffer_( + radiance_cache_hash_buffer_uint_[HASHGRIDCACHE_DEBUGDECAYCELLBUFFER]) , radiance_cache_debug_cell_buffer_(radiance_cache_hash_buffer_float4_[HASHGRIDCACHE_DEBUGCELLBUFFER]) , radiance_cache_debug_bucket_occupancy_buffer_( radiance_cache_hash_buffer_uint_[HASHGRIDCACHE_BUCKETOCCUPANCYBUFFER]) @@ -487,13 +509,34 @@ void GI1::HashGridCache::ensureMemoryIsAllocated([[maybe_unused]] CapsaicinInter if (!radiance_cache_value_buffer_ || num_cells != num_cells_) { gfxDestroyBuffer(gfx_, radiance_cache_value_buffer_); - + radiance_cache_value_buffer_ = gfxCreateBuffer(gfx_, num_cells); radiance_cache_value_buffer_.setName("Capsaicin_RadianceCache_ValueBuffer"); } debug_total_memory_size_in_bytes += radiance_cache_value_buffer_.getSize(); + if (!radiance_cache_multibounce_info_buffer_ || num_cells != num_cells_) + { + gfxDestroyBuffer(gfx_, radiance_cache_multibounce_info_buffer_); + radiance_cache_multibounce_info_buffer_ = gfxCreateBuffer(gfx_, num_cells); + radiance_cache_multibounce_info_buffer_.setName("Capsaicin_RadianceCache_MultibounceInfoBuffer"); + + gfxCommandClearBuffer(gfx_, radiance_cache_multibounce_info_buffer_); + } + + debug_total_memory_size_in_bytes += radiance_cache_multibounce_info_buffer_.getSize(); + + if (!radiance_cache_value_indirect_buffer_ || num_cells != num_cells_) + { + gfxDestroyBuffer(gfx_, radiance_cache_value_indirect_buffer_); + + radiance_cache_value_indirect_buffer_ = gfxCreateBuffer(gfx_, num_cells); + radiance_cache_value_indirect_buffer_.setName("Capsaicin_RadianceCache_ValueIndirectBuffer"); + } + + debug_total_memory_size_in_bytes += radiance_cache_value_indirect_buffer_.getSize(); + if (!radiance_cache_update_tile_count_buffer_) { radiance_cache_update_tile_count_buffer_ = gfxCreateBuffer(gfx_, 1); @@ -514,15 +557,29 @@ void GI1::HashGridCache::ensureMemoryIsAllocated([[maybe_unused]] CapsaicinInter debug_total_memory_size_in_bytes += radiance_cache_update_cell_value_buffer_.getSize(); - if (!radiance_cache_visibility_count_buffer_) + if (!radiance_cache_update_cell_value_buffer_ || num_cells != num_cells_) { - gfxDestroyBuffer(gfx_, radiance_cache_visibility_count_buffer_); + gfxDestroyBuffer(gfx_, radiance_cache_update_cell_value_indirect_buffer_); + radiance_cache_update_cell_value_indirect_buffer_ = gfxCreateBuffer(gfx_, num_cells << 2); + radiance_cache_update_cell_value_indirect_buffer_.setName( + "Capsaicin_RadianceCache_UpdateCellValueIndirectBuffer"); + + gfxCommandClearBuffer(gfx_, radiance_cache_update_cell_value_indirect_buffer_); + } + debug_total_memory_size_in_bytes += radiance_cache_update_cell_value_indirect_buffer_.getSize(); + + if (!radiance_cache_visibility_count_buffer0_) + { + gfxDestroyBuffer(gfx_, radiance_cache_visibility_count_buffer0_); + gfxDestroyBuffer(gfx_, radiance_cache_visibility_count_buffer1_); gfxDestroyBuffer(gfx_, radiance_cache_visibility_ray_count_buffer_); gfxDestroyBuffer(gfx_, radiance_cache_packed_tile_count_buffer0_); gfxDestroyBuffer(gfx_, radiance_cache_packed_tile_count_buffer1_); - radiance_cache_visibility_count_buffer_ = gfxCreateBuffer(gfx_, 1); - radiance_cache_visibility_count_buffer_.setName("Capsaicin_RadianceCache_VisibilityCountBuffer"); + radiance_cache_visibility_count_buffer0_ = gfxCreateBuffer(gfx_, 1); + radiance_cache_visibility_count_buffer0_.setName("Capsaicin_RadianceCache_VisibilityCountBuffer0"); + radiance_cache_visibility_count_buffer1_ = gfxCreateBuffer(gfx_, 1); + radiance_cache_visibility_count_buffer1_.setName("Capsaicin_RadianceCache_VisibilityCountBuffer1"); radiance_cache_visibility_ray_count_buffer_ = gfxCreateBuffer(gfx_, 1); radiance_cache_visibility_ray_count_buffer_.setName( @@ -535,7 +592,8 @@ void GI1::HashGridCache::ensureMemoryIsAllocated([[maybe_unused]] CapsaicinInter radiance_cache_packed_tile_count_buffer1_.setName("Capsaicin_RadianceCache_PackedTileCountBuffer1"); } - debug_total_memory_size_in_bytes += radiance_cache_visibility_count_buffer_.getSize(); + debug_total_memory_size_in_bytes += radiance_cache_visibility_count_buffer0_.getSize(); + debug_total_memory_size_in_bytes += radiance_cache_visibility_count_buffer1_.getSize(); debug_total_memory_size_in_bytes += radiance_cache_visibility_ray_count_buffer_.getSize(); debug_total_memory_size_in_bytes += radiance_cache_packed_tile_count_buffer0_.getSize(); debug_total_memory_size_in_bytes += radiance_cache_packed_tile_count_buffer1_.getSize(); @@ -566,29 +624,28 @@ void GI1::HashGridCache::ensureMemoryIsAllocated([[maybe_unused]] CapsaicinInter { if (!radiance_cache_debug_cell_buffer_ || num_cells != num_cells_) { + gfxDestroyBuffer(gfx_, radiance_cache_debug_decay_cell_buffer_); gfxDestroyBuffer(gfx_, radiance_cache_debug_cell_buffer_); - gfxDestroyBuffer(gfx_, radiance_cache_decay_cell_buffer_); + radiance_cache_debug_decay_cell_buffer_ = gfxCreateBuffer(gfx_, num_cells); + radiance_cache_debug_decay_cell_buffer_.setName("Capsaicin_RadianceCache_DebugDecayCellBuffer"); radiance_cache_debug_cell_buffer_ = gfxCreateBuffer(gfx_, num_cells); radiance_cache_debug_cell_buffer_.setName("Capsaicin_RadianceCache_DebugCellBuffer"); - radiance_cache_decay_cell_buffer_ = gfxCreateBuffer(gfx_, num_cells); - radiance_cache_decay_cell_buffer_.setName("Capsaicin_RadianceCache_DecayCellBuffer"); - - gfxCommandClearBuffer(gfx_, radiance_cache_decay_cell_buffer_, 0xFFFFFFFFU); + gfxCommandClearBuffer(gfx_, radiance_cache_debug_decay_cell_buffer_, 0xFFFFFFFFu); } } else { + gfxDestroyBuffer(gfx_, radiance_cache_debug_decay_cell_buffer_); gfxDestroyBuffer(gfx_, radiance_cache_debug_cell_buffer_); - gfxDestroyBuffer(gfx_, radiance_cache_decay_cell_buffer_); - radiance_cache_debug_cell_buffer_ = {}; - radiance_cache_decay_cell_buffer_ = {}; + radiance_cache_debug_decay_cell_buffer_ = {}; + radiance_cache_debug_cell_buffer_ = {}; } + debug_total_memory_size_in_bytes += radiance_cache_debug_decay_cell_buffer_.getSize(); debug_total_memory_size_in_bytes += radiance_cache_debug_cell_buffer_.getSize(); - debug_total_memory_size_in_bytes += radiance_cache_decay_cell_buffer_.getSize(); if (!radiance_cache_update_tile_buffer_ || max_ray_count != max_ray_count_ || num_cells != num_cells_) { @@ -599,19 +656,20 @@ void GI1::HashGridCache::ensureMemoryIsAllocated([[maybe_unused]] CapsaicinInter gfxDestroyBuffer(gfx_, radiance_cache_visibility_ray_buffer_); radiance_cache_update_tile_buffer_ = - gfxCreateBuffer(gfx_, GFX_MIN(max_ray_count, num_cells)); + gfxCreateBuffer(gfx_, GFX_MIN(2 * max_ray_count, num_cells)); radiance_cache_update_tile_buffer_.setName("Capsaicin_RadianceCache_UpdateTileBuffer"); - radiance_cache_visibility_buffer_ = gfxCreateBuffer(gfx_, max_ray_count); + radiance_cache_visibility_buffer_ = + gfxCreateBuffer(gfx_, 2 * max_ray_count); // BE CAREFUL: only bounce 0 and 1 radiance_cache_visibility_buffer_.setName("Capsaicin_RadianceCache_VisibilityBuffer"); - radiance_cache_visibility_cell_buffer_ = gfxCreateBuffer(gfx_, max_ray_count); + radiance_cache_visibility_cell_buffer_ = gfxCreateBuffer(gfx_, 2 * max_ray_count); radiance_cache_visibility_cell_buffer_.setName("Capsaicin_RadianceCache_VisibilityCellBuffer"); - radiance_cache_visibility_query_buffer_ = gfxCreateBuffer(gfx_, max_ray_count); + radiance_cache_visibility_query_buffer_ = gfxCreateBuffer(gfx_, 2 * max_ray_count); radiance_cache_visibility_query_buffer_.setName("Capsaicin_RadianceCache_VisibilityQueryBuffer"); - radiance_cache_visibility_ray_buffer_ = gfxCreateBuffer(gfx_, max_ray_count); + radiance_cache_visibility_ray_buffer_ = gfxCreateBuffer(gfx_, 2 * max_ray_count); radiance_cache_visibility_ray_buffer_.setName("Capsaicin_RadianceCache_VisibilityRayBuffer"); } @@ -621,6 +679,44 @@ void GI1::HashGridCache::ensureMemoryIsAllocated([[maybe_unused]] CapsaicinInter debug_total_memory_size_in_bytes += radiance_cache_visibility_query_buffer_.getSize(); debug_total_memory_size_in_bytes += radiance_cache_visibility_ray_buffer_.getSize(); + if (!radiance_cache_multibounce_count_buffer_) + { + radiance_cache_multibounce_count_buffer_ = gfxCreateBuffer(gfx_, 1); + radiance_cache_multibounce_count_buffer_.setName("Capsaicin_RadianceCache_MultibounceCountBuffer"); + } + + debug_total_memory_size_in_bytes += radiance_cache_multibounce_count_buffer_.getSize(); + + if (!radiance_cache_multibounce_cell_buffer_ || max_ray_count != max_ray_count_) + { + radiance_cache_multibounce_cell_buffer_ = + gfxCreateBuffer(gfx_, max_ray_count); + radiance_cache_multibounce_cell_buffer_.setName("Capsaicin_RadianceCache_MultibounceCellBuffer"); + radiance_cache_multibounce_query_buffer_ = + gfxCreateBuffer(gfx_, max_ray_count); + radiance_cache_multibounce_query_buffer_.setName("Capsaicin_RadianceCache_MultibounceQueryBuffer"); + } + + debug_total_memory_size_in_bytes += radiance_cache_multibounce_cell_buffer_.getSize(); + debug_total_memory_size_in_bytes += radiance_cache_multibounce_query_buffer_.getSize(); + + if (!radiance_cache_resolve_count_buffer_) + { + radiance_cache_resolve_count_buffer_ = gfxCreateBuffer(gfx_, 1); + radiance_cache_resolve_count_buffer_.setName("Capsaicin_RadianceCache_ResolveCountBuffer"); + } + + debug_total_memory_size_in_bytes += radiance_cache_resolve_count_buffer_.getSize(); + + if (!radiance_cache_resolve_buffer_ || max_ray_count != max_ray_count_) + { + radiance_cache_resolve_buffer_ = + gfxCreateBuffer(gfx_, max_ray_count); // BE CAREFUL: we only resolve first bounce cells + radiance_cache_resolve_buffer_.setName("Capsaicin_RadianceCache_ResolveBuffer"); + } + + debug_total_memory_size_in_bytes += radiance_cache_resolve_buffer_.getSize(); + if (!radiance_cache_debug_free_bucket_buffer_) { gfxDestroyBuffer(gfx_, radiance_cache_debug_free_bucket_buffer_); @@ -838,7 +934,7 @@ void GI1::WorldSpaceReSTIR::ensureMemoryIsAllocated([[maybe_unused]] CapsaicinIn reservoir_hash_list_count_buffer_.setName("Capsaicin_Reservoir_HashListCountBuffer"); } - if (reservoir_indirect_sample_buffer_.getCount() < max_ray_count) + if (reservoir_indirect_sample_buffer_.getCount() < 2 * max_ray_count) { gfxDestroyBuffer(gfx_, reservoir_indirect_sample_buffer_); for (GfxBuffer const &reservoir_indirect_sample_normal_buffer : @@ -853,7 +949,7 @@ void GI1::WorldSpaceReSTIR::ensureMemoryIsAllocated([[maybe_unused]] CapsaicinIn gfxDestroyBuffer(gfx_, reservoir_indirect_sample_reservoir_buffer); } - reservoir_indirect_sample_buffer_ = gfxCreateBuffer(gfx_, max_ray_count); + reservoir_indirect_sample_buffer_ = gfxCreateBuffer(gfx_, 2 * max_ray_count); reservoir_indirect_sample_buffer_.setName("Capsaicin_Reservoir_IndirectSampleBuffer"); for (uint32_t i = 0; i < ARRAYSIZE(reservoir_indirect_sample_normal_buffers_); ++i) @@ -861,11 +957,11 @@ void GI1::WorldSpaceReSTIR::ensureMemoryIsAllocated([[maybe_unused]] CapsaicinIn char buffer[64]; GFX_SNPRINTF(buffer, sizeof(buffer), "Capsaicin_Reservoir_IndirectSampleNormalBuffer%u", i); - reservoir_indirect_sample_normal_buffers_[i] = gfxCreateBuffer(gfx_, max_ray_count); + reservoir_indirect_sample_normal_buffers_[i] = gfxCreateBuffer(gfx_, 2 * max_ray_count); reservoir_indirect_sample_normal_buffers_[i].setName(buffer); } - reservoir_indirect_sample_material_buffer_ = gfxCreateBuffer(gfx_, max_ray_count); + reservoir_indirect_sample_material_buffer_ = gfxCreateBuffer(gfx_, 2 * max_ray_count); reservoir_indirect_sample_material_buffer_.setName( "Capsaicin_Reservoir_IndirectSamplerMaterialBuffer"); @@ -874,7 +970,7 @@ void GI1::WorldSpaceReSTIR::ensureMemoryIsAllocated([[maybe_unused]] CapsaicinIn char buffer[64]; GFX_SNPRINTF(buffer, sizeof(buffer), "Capsaicin_Reservoir_IndirectSampleReservoirBuffer%u", i); - reservoir_indirect_sample_reservoir_buffers_[i] = gfxCreateBuffer(gfx_, max_ray_count); + reservoir_indirect_sample_reservoir_buffers_[i] = gfxCreateBuffer(gfx_, 2 * max_ray_count); reservoir_indirect_sample_reservoir_buffers_[i].setName(buffer); } } @@ -1153,6 +1249,11 @@ RenderOptionList GI1::getRenderOptions() noexcept newOptions.emplace(RENDER_OPTION_MAKE(gi1_use_dxr10, options_)); newOptions.emplace(RENDER_OPTION_MAKE(gi1_use_resampling, options_)); newOptions.emplace(RENDER_OPTION_MAKE(gi1_use_direct_lighting, options_)); + newOptions.emplace(RENDER_OPTION_MAKE(gi1_use_temporal_feedback, options_)); + newOptions.emplace(RENDER_OPTION_MAKE(gi1_use_temporal_multibounce_feedback, options_)); + newOptions.emplace(RENDER_OPTION_MAKE(gi1_use_screen_space_reflections, options_)); + newOptions.emplace(RENDER_OPTION_MAKE(gi1_use_bypass_cache, options_)); + newOptions.emplace(RENDER_OPTION_MAKE(gi1_use_multibounce, options_)); newOptions.emplace(RENDER_OPTION_MAKE(gi1_disable_albedo_textures, options_)); newOptions.emplace(RENDER_OPTION_MAKE(gi1_disable_specular_materials, options_)); newOptions.emplace(RENDER_OPTION_MAKE(gi1_hash_grid_cache_cell_size, options_)); @@ -1161,6 +1262,9 @@ RenderOptionList GI1::getRenderOptions() noexcept newOptions.emplace(RENDER_OPTION_MAKE(gi1_hash_grid_cache_num_buckets, options_)); newOptions.emplace(RENDER_OPTION_MAKE(gi1_hash_grid_cache_num_tiles_per_bucket, options_)); newOptions.emplace(RENDER_OPTION_MAKE(gi1_hash_grid_cache_max_sample_count, options_)); + newOptions.emplace( + RENDER_OPTION_MAKE(gi1_hash_grid_cache_discard_multibounce_ray_probability, options_)); + newOptions.emplace(RENDER_OPTION_MAKE(gi1_hash_grid_cache_max_multibounce_sample_count, options_)); newOptions.emplace(RENDER_OPTION_MAKE(gi1_hash_grid_cache_debug_mip_level, options_)); newOptions.emplace(RENDER_OPTION_MAKE(gi1_hash_grid_cache_debug_propagate, options_)); newOptions.emplace(RENDER_OPTION_MAKE(gi1_hash_grid_cache_debug_max_cell_decay, options_)); @@ -1193,11 +1297,17 @@ RenderOptionList GI1::getRenderOptions() noexcept GI1::RenderOptions GI1::convertOptions(RenderOptionList const &options) noexcept { RenderOptions newOptions; + RENDER_OPTION_GET(gi1_use_dxr10, newOptions, options) RENDER_OPTION_GET(gi1_use_resampling, newOptions, options) newOptions.gi1_disable_alpha_testing = *std::get_if( &options.at("visibility_buffer_disable_alpha_testing")); // Map the option from visibility buffer RENDER_OPTION_GET(gi1_use_direct_lighting, newOptions, options) + RENDER_OPTION_GET(gi1_use_temporal_feedback, newOptions, options) + RENDER_OPTION_GET(gi1_use_temporal_multibounce_feedback, newOptions, options) + RENDER_OPTION_GET(gi1_use_screen_space_reflections, newOptions, options) + RENDER_OPTION_GET(gi1_use_bypass_cache, newOptions, options) + RENDER_OPTION_GET(gi1_use_multibounce, newOptions, options) RENDER_OPTION_GET(gi1_disable_albedo_textures, newOptions, options) RENDER_OPTION_GET(gi1_disable_specular_materials, newOptions, options) RENDER_OPTION_GET(gi1_hash_grid_cache_cell_size, newOptions, options) @@ -1206,6 +1316,8 @@ GI1::RenderOptions GI1::convertOptions(RenderOptionList const &options) noexcept RENDER_OPTION_GET(gi1_hash_grid_cache_num_buckets, newOptions, options) RENDER_OPTION_GET(gi1_hash_grid_cache_num_tiles_per_bucket, newOptions, options) RENDER_OPTION_GET(gi1_hash_grid_cache_max_sample_count, newOptions, options) + RENDER_OPTION_GET(gi1_hash_grid_cache_discard_multibounce_ray_probability, newOptions, options) + RENDER_OPTION_GET(gi1_hash_grid_cache_max_multibounce_sample_count, newOptions, options) RENDER_OPTION_GET(gi1_hash_grid_cache_debug_mip_level, newOptions, options) RENDER_OPTION_GET(gi1_hash_grid_cache_debug_propagate, newOptions, options) RENDER_OPTION_GET(gi1_hash_grid_cache_debug_max_cell_decay, newOptions, options) @@ -1307,6 +1419,7 @@ bool GI1::init(CapsaicinInternal const &capsaicin) noexcept { base_defines.push_back(i.c_str()); } + if (options_.gi1_disable_alpha_testing) { base_defines.push_back("DISABLE_ALPHA_TESTING"); @@ -1339,6 +1452,7 @@ bool GI1::init(CapsaicinInternal const &capsaicin) noexcept } auto const debug_hash_cells_define_count = static_cast(debug_hash_cells_defines.size()); + // We need to clear the radiance cache when toggling the hash cells debug mode; // that is because we only populate the 'packedCell' buffer when visualizing the // cells as this is required to recover the position & orientation of a given cell. @@ -1415,6 +1529,19 @@ bool GI1::init(CapsaicinInternal const &capsaicin) noexcept static_cast(populate_screen_probes_subobjects.size()), debug_hash_cells_defines.data(), debug_hash_cells_define_count); + std::vector populate_multibounce_cells_exports; + populate_multibounce_cells_exports.push_back(kPopulateMultibounceCellsRaygenShaderName); + populate_multibounce_cells_exports.push_back(kPopulateMultibounceCellsMissShaderName); + populate_multibounce_cells_exports.push_back(kPopulateMultibounceCellsAnyHitShaderName); + populate_multibounce_cells_exports.push_back(kPopulateMultibounceCellsClosestHitShaderName); + std::vector populate_multibounce_cells_subobjects = base_subobjects; + populate_multibounce_cells_subobjects.push_back(kPopulateMultibounceCellsHitGroupName); + populate_multibounce_cells_kernel_ = gfxCreateRaytracingKernel(gfx_, gi1_program_, nullptr, 0, + populate_multibounce_cells_exports.data(), (uint32_t)populate_multibounce_cells_exports.size(), + populate_multibounce_cells_subobjects.data(), + (uint32_t)populate_multibounce_cells_subobjects.size(), debug_hash_cells_defines.data(), + debug_hash_cells_define_count); + std::vector populate_cells_kernel_exports; populate_cells_kernel_exports.push_back(kPopulateCellsRaygenShaderName); populate_cells_kernel_exports.push_back(kPopulateCellsMissShaderName); @@ -1448,18 +1575,22 @@ bool GI1::init(CapsaicinInternal const &capsaicin) noexcept gfxSceneGetInstanceCount(capsaicin.getScene()) * capsaicin.getSbtStrideInEntries(kGfxShaderGroupType_Hit), capsaicin.getSbtStrideInEntries(kGfxShaderGroupType_Callable)}; - GfxKernel sbt_kernels[] { - populate_screen_probes_kernel_, populate_cells_kernel_, trace_reflections_kernel_}; + GfxKernel sbt_kernels[] {populate_screen_probes_kernel_, populate_multibounce_cells_kernel_, + populate_cells_kernel_, trace_reflections_kernel_}; sbt_ = gfxCreateSbt(gfx_, sbt_kernels, ARRAYSIZE(sbt_kernels), entry_count); } else { - populate_screen_probes_kernel_ = gfxCreateComputeKernel(gfx_, gi1_program_, - "PopulateScreenProbesMain", debug_hash_cells_defines.data(), debug_hash_cells_define_count); - populate_cells_kernel_ = gfxCreateComputeKernel( + populate_screen_probes_kernel_ = gfxCreateComputeKernel(gfx_, gi1_program_, + "PopulateScreenProbesMain", debug_hash_cells_defines.data(), debug_hash_cells_define_count); + populate_multibounce_cells_kernel_ = gfxCreateComputeKernel(gfx_, gi1_program_, + "PopulateMultibounceCellsMain", debug_hash_cells_defines.data(), debug_hash_cells_define_count); + populate_cells_kernel_ = gfxCreateComputeKernel( gfx_, gi1_program_, "PopulateCellsMain", resampling_defines.data(), resampling_define_count); trace_reflections_kernel_ = gfxCreateComputeKernel(gfx_, gi1_program_, "TraceReflectionsMain"); } + update_multibounce_cells_kernel_ = gfxCreateComputeKernel( + gfx_, gi1_program_, "UpdateMultibounceCellsMain", debug_hash_cells_defines.data()); blend_screen_probes_kernel_ = gfxCreateComputeKernel(gfx_, gi1_program_, "BlendScreenProbes"); reorder_screen_probes_kernel_ = gfxCreateComputeKernel(gfx_, gi1_program_, "ReorderScreenProbes"); filter_screen_probes_kernel_ = gfxCreateComputeKernel(gfx_, gi1_program_, "FilterScreenProbes"); @@ -1486,6 +1617,8 @@ bool GI1::init(CapsaicinInternal const &capsaicin) noexcept clear_reservoirs_kernel_ = gfxCreateComputeKernel(gfx_, gi1_program_, "ClearReservoirs"); generate_reservoirs_kernel_ = gfxCreateComputeKernel( gfx_, gi1_program_, "GenerateReservoirs", resampling_defines.data(), resampling_define_count); + generate_multibounce_reservoirs_kernel_ = gfxCreateComputeKernel(gfx_, gi1_program_, + "GenerateMultibounceReservoirs", resampling_defines.data(), resampling_define_count); compact_reservoirs_kernel_ = gfxCreateComputeKernel(gfx_, gi1_program_, "CompactReservoirs"); resample_reservoirs_kernel_ = gfxCreateComputeKernel( gfx_, gi1_program_, "ResampleReservoirs", base_defines.data(), base_define_count); @@ -1521,7 +1654,7 @@ bool GI1::init(CapsaicinInternal const &capsaicin) noexcept irradiance_buffer_ = capsaicin.createRenderTexture(DXGI_FORMAT_R16G16B16A16_FLOAT, "GI_IrradianceBuffer"); // Reserve position values with light bounds sampler - light_sampler->reserveBoundsValues(screen_probes_.max_ray_count, this); + light_sampler->reserveBoundsValues(screen_probes_.max_ray_count * 2, this); return !!filter_gi_kernel_; } @@ -1693,6 +1826,9 @@ void GI1::render(CapsaicinInternal &capsaicin) noexcept hash_grid_cache_constant_data.first_cell_offset_tile_mip3 = hash_grid_cache_.first_cell_offset_tile_mip3_; hash_grid_cache_constant_data.buffer_ping_pong = hash_grid_cache_.radiance_cache_hash_buffer_ping_pong_; hash_grid_cache_constant_data.max_sample_count = options_.gi1_hash_grid_cache_max_sample_count; + hash_grid_cache_constant_data.discard_multibounce_ray_probability = + options_.gi1_hash_grid_cache_discard_multibounce_ray_probability; + hash_grid_cache_constant_data.max_multibounce_sample_count = options_.gi1_hash_grid_cache_max_multibounce_sample_count; hash_grid_cache_constant_data.debug_mip_level = static_cast(options_.gi1_hash_grid_cache_debug_mip_level); hash_grid_cache_constant_data.debug_propagate = @@ -1794,9 +1930,14 @@ void GI1::render(CapsaicinInternal &capsaicin) noexcept gfxProgramSetParameter(gfx_, gi1_program_, "g_BufferDimensions", capsaicin.getRenderDimensions()); gfxProgramSetParameter( gfx_, gi1_program_, "g_UseDirectLighting", options_.gi1_use_direct_lighting ? 1 : 0); + gfxProgramSetParameter( + gfx_, gi1_program_, "g_UseTemporalFeedback", options_.gi1_use_temporal_feedback ? 1 : 0); + gfxProgramSetParameter(gfx_, gi1_program_, "g_UseTemporalMultibounceFeedback", options_.gi1_use_temporal_multibounce_feedback ? 1 : 0); + gfxProgramSetParameter(gfx_, gi1_program_, "g_UseScreenSpaceReflections", options_.gi1_use_screen_space_reflections ? 1 : 0); + gfxProgramSetParameter(gfx_, gi1_program_, "g_UseMultibounce", options_.gi1_use_multibounce ? 1 : 0); + gfxProgramSetParameter(gfx_, gi1_program_, "g_UseBypassCache", options_.gi1_use_bypass_cache ? 1 : 0); gfxProgramSetParameter( gfx_, gi1_program_, "g_DisableAlbedoTextures", options_.gi1_disable_albedo_textures ? 1 : 0); - gfxProgramSetParameter( gfx_, gi1_program_, "g_DepthBuffer", capsaicin.getSharedTexture("VisibilityDepth")); gfxProgramSetParameter( @@ -2166,6 +2307,41 @@ void GI1::render(CapsaicinInternal &capsaicin) noexcept gfxCommandDispatch(gfx_, num_groups_x, 1, 1); } + // Discover indirect cells we need for multibounce + if (options.gi1_use_multibounce) + { + if (options.gi1_use_dxr10) + { + TimedSection const timed_section(*this, "PopulateMultibounceCells"); + + gfxSbtSetShaderGroup( + gfx_, sbt_, kGfxShaderGroupType_Raygen, 0, kPopulateMultibounceCellsRaygenShaderName); + gfxSbtSetShaderGroup( + gfx_, sbt_, kGfxShaderGroupType_Miss, 0, kPopulateMultibounceCellsMissShaderName); + for (uint32_t i = 0; i < capsaicin.getRaytracingPrimitiveCount(); i++) + { + gfxSbtSetShaderGroup(gfx_, sbt_, kGfxShaderGroupType_Hit, + i * capsaicin.getSbtStrideInEntries(kGfxShaderGroupType_Hit), + kPopulateMultibounceCellsHitGroupName); + } + + generateDispatchRays(hash_grid_cache_.radiance_cache_visibility_count_buffer0_); + + gfxCommandBindKernel(gfx_, populate_multibounce_cells_kernel_); + gfxCommandDispatchRaysIndirect(gfx_, sbt_, dispatch_command_buffer_); + } + else + { + TimedSection const timed_section(*this, "PopulateMultibounceCells"); + + uint32_t const *num_threads = gfxKernelGetNumThreads(gfx_, populate_multibounce_cells_kernel_); + generateDispatch(hash_grid_cache_.radiance_cache_visibility_count_buffer0_, num_threads[0]); + + gfxCommandBindKernel(gfx_, populate_multibounce_cells_kernel_); + gfxCommandDispatchIndirect(gfx_, dispatch_command_buffer_); + } + } + // Update light sampling data structure { light_sampler->update(capsaicin, this); @@ -2189,12 +2365,23 @@ void GI1::render(CapsaicinInternal &capsaicin) noexcept TimedSection const timed_section(*this, "GenerateReservoirs"); uint32_t const *num_threads = gfxKernelGetNumThreads(gfx_, generate_reservoirs_kernel_); - generateDispatch(hash_grid_cache_.radiance_cache_visibility_count_buffer_, num_threads[0]); + generateDispatch(hash_grid_cache_.radiance_cache_visibility_count_buffer0_, num_threads[0]); gfxCommandBindKernel(gfx_, generate_reservoirs_kernel_); gfxCommandDispatchIndirect(gfx_, dispatch_command_buffer_); } + if (options.gi1_use_multibounce) + { + TimedSection const timed_section(*this, "GenerateMultibounceReservoirs"); + + uint32_t const *num_threads = gfxKernelGetNumThreads(gfx_, generate_multibounce_reservoirs_kernel_); + generateDispatch(hash_grid_cache_.radiance_cache_visibility_count_buffer1_, num_threads[0]); + + gfxCommandBindKernel(gfx_, generate_multibounce_reservoirs_kernel_); + gfxCommandDispatchIndirect(gfx_, dispatch_command_buffer_); + } + // Compact the reservoir caching structure if (options_.gi1_use_resampling) { @@ -2227,7 +2414,7 @@ void GI1::render(CapsaicinInternal &capsaicin) noexcept // Populate the cells of our world-space hash-grid radiance cache if (options.gi1_use_dxr10) { - TimedSection const timed_section(*this, "PopulateRadianceCache"); + TimedSection const timed_section(*this, "PopulateCells"); gfxSbtSetShaderGroup(gfx_, sbt_, kGfxShaderGroupType_Raygen, 0, kPopulateCellsRaygenShaderName); gfxSbtSetShaderGroup(gfx_, sbt_, kGfxShaderGroupType_Miss, 0, kPopulateCellsMissShaderName); @@ -2244,7 +2431,7 @@ void GI1::render(CapsaicinInternal &capsaicin) noexcept } else { - TimedSection const timed_section(*this, "PopulateRadianceCache"); + TimedSection const timed_section(*this, "PopulateCells"); uint32_t const *num_threads = gfxKernelGetNumThreads(gfx_, populate_cells_kernel_); generateDispatch(hash_grid_cache_.radiance_cache_visibility_ray_count_buffer_, num_threads[0]); @@ -2253,9 +2440,9 @@ void GI1::render(CapsaicinInternal &capsaicin) noexcept gfxCommandDispatchIndirect(gfx_, dispatch_command_buffer_); } - // Update our tiles using the result of the raytracing + // Filter tiles (include first and second bounces cells) { - TimedSection const timed_section(*this, "UpdateRadianceCache"); + TimedSection const timed_section(*this, "UpdateTiles"); gfxCommandBindKernel(gfx_, generate_update_tiles_dispatch_kernel_); gfxCommandDispatch(gfx_, 1, 1, 1); @@ -2264,12 +2451,25 @@ void GI1::render(CapsaicinInternal &capsaicin) noexcept gfxCommandDispatchIndirect(gfx_, dispatch_command_buffer_); } - // Resolve our cells into the per-query storage + + // Resolve bounce 1 cells into first bounce cells using last frame + if (options.gi1_use_multibounce) { - TimedSection const timed_section(*this, "ResolveRadianceCache"); + TimedSection const timed_section(*this, "UpdateMultibounceCells"); + + uint32_t const *num_threads = gfxKernelGetNumThreads(gfx_, update_multibounce_cells_kernel_); + generateDispatch(hash_grid_cache_.radiance_cache_multibounce_count_buffer_, num_threads[0]); + + gfxCommandBindKernel(gfx_, update_multibounce_cells_kernel_); + gfxCommandDispatchIndirect(gfx_, dispatch_command_buffer_); + } + + // Resolve first bounce cells into screen probes + { + TimedSection const timed_section(*this, "ResolveCells"); uint32_t const *num_threads = gfxKernelGetNumThreads(gfx_, resolve_cells_kernel_); - generateDispatch(hash_grid_cache_.radiance_cache_visibility_ray_count_buffer_, num_threads[0]); + generateDispatch(hash_grid_cache_.radiance_cache_resolve_count_buffer_, num_threads[0]); gfxCommandBindKernel(gfx_, resolve_cells_kernel_); gfxCommandDispatchIndirect(gfx_, dispatch_command_buffer_); @@ -2686,6 +2886,7 @@ void GI1::render(CapsaicinInternal &capsaicin) noexcept // Finally, we can resolve the filtered lighting with the per-pixel material details { + gfxProgramSetParameter(gfx_, gi1_program_, "g_TextureSampler", capsaicin.getAnisotropicSampler()); TimedSection const timed_section(*this, "ResolveGI1"); @@ -2898,6 +3099,8 @@ void GI1::terminate() noexcept gfxDestroyKernel(gfx_, interpolate_screen_probes_kernel_); gfxDestroyKernel(gfx_, purge_tiles_kernel_); + gfxDestroyKernel(gfx_, populate_multibounce_cells_kernel_); + gfxDestroyKernel(gfx_, update_multibounce_cells_kernel_); gfxDestroyKernel(gfx_, populate_cells_kernel_); gfxDestroyKernel(gfx_, update_tiles_kernel_); gfxDestroyKernel(gfx_, resolve_cells_kernel_); @@ -2910,6 +3113,7 @@ void GI1::terminate() noexcept gfxDestroyKernel(gfx_, clear_reservoirs_kernel_); gfxDestroyKernel(gfx_, generate_reservoirs_kernel_); + gfxDestroyKernel(gfx_, generate_multibounce_reservoirs_kernel_); gfxDestroyKernel(gfx_, compact_reservoirs_kernel_); gfxDestroyKernel(gfx_, resample_reservoirs_kernel_); diff --git a/src/core/src/render_techniques/gi1/gi1.frag b/src/core/src/render_techniques/gi1/gi1.frag index f88c896d..a1e0420d 100644 --- a/src/core/src/render_techniques/gi1/gi1.frag +++ b/src/core/src/render_techniques/gi1/gi1.frag @@ -47,6 +47,7 @@ StructuredBuffer g_MaterialBuffer; RWTexture2D g_IrradianceBuffer; RWTexture2D g_ReflectionBuffer; + Texture2D g_TextureMaps[] : register(space99); SamplerState g_NearestSampler; SamplerState g_LinearSampler; @@ -117,7 +118,7 @@ PS_OUTPUT ResolveGI1(in float4 pos : SV_Position) #ifndef DISABLE_SPECULAR_MATERIALS materialBRDF.F0 = 0.0f.xxx; #endif // DISABLE_SPECULAR_MATERIALS - } + } PS_OUTPUT output; @@ -133,6 +134,7 @@ PS_OUTPUT ResolveGI1(in float4 pos : SV_Position) float3 specular_dominant_direction = calculateGGXSpecularDirection(normal, view_direction, sqrt(materialBRDF.roughnessAlpha)); float3 specular_dominant_half_vector = normalize(view_direction + specular_dominant_direction); float dotHV = saturate(dot(view_direction, specular_dominant_half_vector)); + float3 diffuse_compensation = diffuseCompensation(materialBRDF.F0, dotHV); // diffuse term @@ -142,10 +144,11 @@ PS_OUTPUT ResolveGI1(in float4 pos : SV_Position) // compute specular term with split-sum approximation float4 radiance_sum = (material_evaluated.roughness > g_GlossyReflectionsConstants.high_roughness_threshold ? float4(irradiance, PI) : g_ReflectionBuffer[did]); // fall back to filtered irradiance past threshold + float2 lut = g_LutBuffer.SampleLevel(g_LinearSampler, float2(dotNV, material_evaluated.roughness), 0.0f).xy; float3 directional_albedo = saturate(materialBRDF.F0 * lut.x + (1.0f - materialBRDF.F0) * lut.y); float3 specular = directional_albedo * (radiance_sum.xyz / max(radiance_sum.w, 1.0f)); - output.lighting = float4(emissiveRadiance + diffuse + specular, 1.0f); + output.lighting = float4(emissiveRadiance + diffuse + specular, 1.0f); #endif // DISABLE_SPECULAR_MATERIALS diff --git a/src/core/src/render_techniques/gi1/gi1.h b/src/core/src/render_techniques/gi1/gi1.h index c723c905..aed18483 100644 --- a/src/core/src/render_techniques/gi1/gi1.h +++ b/src/core/src/render_techniques/gi1/gi1.h @@ -47,25 +47,32 @@ class GI1 final : public RenderTechnique struct RenderOptions { - bool gi1_use_dxr10 = false; - bool gi1_use_resampling = false; - bool gi1_disable_alpha_testing = false; - bool gi1_use_direct_lighting = true; - bool gi1_disable_albedo_textures = false; - bool gi1_disable_specular_materials = false; - float gi1_hash_grid_cache_cell_size = 32.0F; - float gi1_hash_grid_cache_min_cell_size = 1e-1F; - int gi1_hash_grid_cache_tile_cell_ratio = 8; // 8x8 = 64 - int gi1_hash_grid_cache_num_buckets = 12; // 1 << 12 = 4096 - int gi1_hash_grid_cache_num_tiles_per_bucket = 4; // 1 << 4 = 16 total : 4194304 - float gi1_hash_grid_cache_max_sample_count = 16.F; // - int gi1_hash_grid_cache_debug_mip_level = 0; - bool gi1_hash_grid_cache_debug_propagate = false; - int gi1_hash_grid_cache_debug_max_cell_decay = 0; // Debug cells touched this frame - bool gi1_hash_grid_cache_debug_stats = false; - int gi1_hash_grid_cache_debug_max_bucket_overflow = 64; - float gi1_reservoir_cache_cell_size = 16.0F; - + bool gi1_use_dxr10 = false; + bool gi1_use_resampling = false; + bool gi1_disable_alpha_testing = false; + bool gi1_use_direct_lighting = true; + bool gi1_use_temporal_feedback = false; + bool gi1_use_temporal_multibounce_feedback = false; + bool gi1_use_screen_space_reflections = true; + bool gi1_use_bypass_cache = true; + bool gi1_use_multibounce = true; + bool gi1_disable_albedo_textures = false; + bool gi1_disable_specular_materials = false; + float gi1_hash_grid_cache_cell_size = 32.0f; + float gi1_hash_grid_cache_min_cell_size = 1e-1f; + int gi1_hash_grid_cache_tile_cell_ratio = 8; // 8x8 + int gi1_hash_grid_cache_num_buckets = 14; // 1 << 14 = 4096 + int gi1_hash_grid_cache_num_tiles_per_bucket = 4; // 1 << 4 + float gi1_hash_grid_cache_max_sample_count = 16.f; // + float gi1_hash_grid_cache_discard_multibounce_ray_probability = 0.7f; + float gi1_hash_grid_cache_max_multibounce_sample_count = 16.f; + int gi1_hash_grid_cache_debug_mip_level = 0; + bool gi1_hash_grid_cache_debug_propagate = false; + int gi1_hash_grid_cache_debug_max_cell_decay = 0; // Debug cells touched this frame + bool gi1_hash_grid_cache_debug_stats = false; + int gi1_hash_grid_cache_debug_max_bucket_overflow = 64; + float gi1_reservoir_cache_cell_size = 16.0f; + bool gi1_glossy_reflections_halfres = true; int gi1_glossy_reflections_denoiser_mode = 1; // Atrous Ratio Estimator bool gi1_glossy_reflections_cleanup_fireflies = true; @@ -267,22 +274,31 @@ class GI1 final : public RenderTechnique GfxBuffer radiance_cache_hash_buffer_float4_[HASHGRID_FLOAT4_BUFFER_COUNT]; uint32_t radiance_cache_hash_buffer_ping_pong_; GfxBuffer &radiance_cache_hash_buffer_; - GfxBuffer &radiance_cache_decay_cell_buffer_; GfxBuffer &radiance_cache_decay_tile_buffer_; GfxBuffer &radiance_cache_value_buffer_; + GfxBuffer &radiance_cache_value_indirect_buffer_; GfxBuffer &radiance_cache_update_tile_buffer_; GfxBuffer &radiance_cache_update_tile_count_buffer_; GfxBuffer &radiance_cache_update_cell_value_buffer_; + GfxBuffer &radiance_cache_update_cell_value_indirect_buffer_; GfxBuffer &radiance_cache_visibility_buffer_; - GfxBuffer &radiance_cache_visibility_count_buffer_; + GfxBuffer &radiance_cache_visibility_count_buffer0_; + GfxBuffer &radiance_cache_visibility_count_buffer1_; GfxBuffer &radiance_cache_visibility_cell_buffer_; GfxBuffer &radiance_cache_visibility_query_buffer_; GfxBuffer &radiance_cache_visibility_ray_buffer_; GfxBuffer &radiance_cache_visibility_ray_count_buffer_; + GfxBuffer &radiance_cache_multibounce_count_buffer_; + GfxBuffer &radiance_cache_multibounce_cell_buffer_; + GfxBuffer &radiance_cache_multibounce_query_buffer_; + GfxBuffer &radiance_cache_multibounce_info_buffer_; + GfxBuffer &radiance_cache_resolve_count_buffer_; + GfxBuffer &radiance_cache_resolve_buffer_; GfxBuffer &radiance_cache_packed_tile_count_buffer0_; GfxBuffer &radiance_cache_packed_tile_count_buffer1_; GfxBuffer &radiance_cache_packed_tile_index_buffer0_; GfxBuffer &radiance_cache_packed_tile_index_buffer1_; + GfxBuffer &radiance_cache_debug_decay_cell_buffer_; GfxBuffer &radiance_cache_debug_cell_buffer_; GfxBuffer &radiance_cache_debug_bucket_occupancy_buffer_; GfxBuffer &radiance_cache_debug_bucket_overflow_count_buffer_; @@ -430,7 +446,9 @@ class GI1 final : public RenderTechnique // Hash grid cache kernels: GfxKernel purge_tiles_kernel_; + GfxKernel populate_multibounce_cells_kernel_; GfxKernel populate_cells_kernel_; + GfxKernel update_multibounce_cells_kernel_; GfxKernel update_tiles_kernel_; GfxKernel resolve_cells_kernel_; GfxKernel clear_bucket_overflow_count_kernel_; @@ -443,6 +461,7 @@ class GI1 final : public RenderTechnique // World-space ReSTIR kernels: GfxKernel clear_reservoirs_kernel_; GfxKernel generate_reservoirs_kernel_; + GfxKernel generate_multibounce_reservoirs_kernel_; GfxKernel compact_reservoirs_kernel_; GfxKernel resample_reservoirs_kernel_; diff --git a/src/core/src/render_techniques/gi1/gi1.rt b/src/core/src/render_techniques/gi1/gi1.rt index 446b2c39..4a5a8261 100644 --- a/src/core/src/render_techniques/gi1/gi1.rt +++ b/src/core/src/render_techniques/gi1/gi1.rt @@ -31,6 +31,12 @@ TriangleHitGroup PopulateScreenProbesHitGroup = "PopulateScreenProbesClosestHit", // ClosestHit }; +TriangleHitGroup PopulateMultibounceCellsHitGroup = +{ + "PopulateMultibounceCellsAnyHit", // AnyHit + "PopulateMultibounceCellsClosestHit", // ClosestHit +}; + TriangleHitGroup PopulateCellsHitGroup = { "PopulateCellsAnyHit", // AnyHit @@ -83,6 +89,35 @@ void PopulateScreenProbesMiss(inout PopulateScreenProbesPayload payload) PopulateScreenProbesHandleMiss(payload, GetRayDescRt()); } +[shader("raygeneration")] +void PopulateMultibounceCellsRaygen() +{ + PopulateMultibounceCells(DispatchRaysIndex().x); +} + +[shader("anyhit")] +void PopulateMultibounceCellsAnyHit(inout PopulateMultibounceCellsPayload payload, in BuiltInTriangleIntersectionAttributes attr) +{ + if (!AlphaTest(GetHitInfoRt(attr))) + { + IgnoreHit(); + } +} + +[shader("closesthit")] +void PopulateMultibounceCellsClosestHit(inout PopulateMultibounceCellsPayload payload, in BuiltInTriangleIntersectionAttributes attr) +{ + payload.hit_dist = RayTCurrent(); + PopulateMultibounceCellsHandleHit(DispatchRaysIndex().x, payload, GetRayDescRt(), GetHitInfoRt(attr)); +} + +[shader("miss")] +void PopulateMultibounceCellsMiss(inout PopulateMultibounceCellsPayload payload) +{ + payload.hit_dist = MAX_HIT_DISTANCE; + PopulateMultibounceCellsHandleMiss(payload, GetRayDescRt()); +} + [shader("raygeneration")] void PopulateCellsRaygen() { diff --git a/src/core/src/render_techniques/gi1/gi1.vert b/src/core/src/render_techniques/gi1/gi1.vert index aba536ad..2b0ea859 100644 --- a/src/core/src/render_techniques/gi1/gi1.vert +++ b/src/core/src/render_techniques/gi1/gi1.vert @@ -78,22 +78,37 @@ float3 NormalizeRadiance(float4 radiance) float4 CellRadiance(uint cell_index) { - float4 radiance = HashGridCache_UnpackRadiance(g_HashGridCache_ValueBuffer[cell_index]); - return float4(NormalizeRadiance(radiance), float(radiance.w > 0.f)); + float4 direct = HashGridCache_UnpackRadiance(g_HashGridCache_ValueBuffer[cell_index]); + float4 indirect = HashGridCache_UnpackRadiance(g_HashGridCache_ValueIndirectBuffer[cell_index]); + float3 radiance = (direct.rgb / max(direct.w, 1.0f)) + (indirect.rgb / max(indirect.w, 1.0f)); + return float4(radiance, float(direct.w + indirect.w > 0.f)); } float4 CellFilteredRadiance(uint entry_cell_mip0) { - float4 radiance = HashGridCache_FilteredRadiance(entry_cell_mip0, false); + float4 direct, indirect; + HashGridCache_FilteredRadianceDirect(entry_cell_mip0, false, direct); + HashGridCache_FilteredRadianceIndirect(entry_cell_mip0, false, indirect); + float3 radiance = (direct.rgb / max(direct.w, 1.0f)) + (indirect.rgb / max(indirect.w, 1.0f)); + return float4(radiance, float(direct.w + indirect.w > 0.f)); +} + +float4 CellFilteredRadianceIndirect(uint entry_cell_mip0) +{ + float4 radiance; + HashGridCache_FilteredRadianceIndirect(entry_cell_mip0, false, radiance); return float4(NormalizeRadiance(radiance), float(radiance.w > 0.f)); } float4 CellFilteringGain(uint entry_cell_mip0) { - float4 filtered_radiance = HashGridCache_FilteredRadiance(entry_cell_mip0, false); - float4 base_radiance = HashGridCache_UnpackRadiance(g_HashGridCache_ValueBuffer[entry_cell_mip0]); - float3 diff_radiance = max(0.f, NormalizeRadiance(filtered_radiance) - NormalizeRadiance(base_radiance)); - return float4(diff_radiance, float(filtered_radiance.w > 0.f)); + float4 filtered_direct_radiance, filtered_indirect_radiance; + HashGridCache_FilteredRadianceDirect(entry_cell_mip0, false, filtered_direct_radiance); + HashGridCache_FilteredRadianceIndirect(entry_cell_mip0, false, filtered_indirect_radiance); + float4 direct_radiance = HashGridCache_UnpackRadiance(g_HashGridCache_ValueBuffer[entry_cell_mip0]); + float4 indirect_radiance = HashGridCache_UnpackRadiance(g_HashGridCache_ValueIndirectBuffer[entry_cell_mip0]); + float3 diff_radiance = max(0.f, NormalizeRadiance(filtered_direct_radiance) + NormalizeRadiance(filtered_indirect_radiance) - NormalizeRadiance(direct_radiance) + NormalizeRadiance(indirect_radiance)); + return float4(diff_radiance, float((filtered_direct_radiance.w + filtered_indirect_radiance.w) > 0.f)); } float4 HeatSampleCount(float4 radiance) @@ -110,19 +125,20 @@ float4 CellRadianceSampleCount(uint cell_index) if (all(packed_radiance == uint2(0, 0))) return float4(0.f, 0.f, 0.f, 0.f); + float4 radiance = HashGridCache_UnpackRadiance(packed_radiance); return HeatSampleCount(radiance); } float4 CellFilteredSampleCount(uint entry_cell_mip0) { - float4 radiance = HashGridCache_FilteredRadiance(entry_cell_mip0, true); + float4 radiance = HashGridCache_FilteredRadiance(entry_cell_mip0, g_HashGridCacheConstants.max_sample_count, true); return HeatSampleCount(radiance); } float4 CellFilteredMipLevel(uint entry_cell_mip0) { - float4 radiance = HashGridCache_FilteredRadiance(entry_cell_mip0, true); + float4 radiance = HashGridCache_FilteredRadiance(entry_cell_mip0, g_HashGridCacheConstants.max_sample_count, true); return float4(radiance.xyz, 1.f); } @@ -181,7 +197,7 @@ DebugHashGridCells_Params DebugHashGridCells(in uint idx : SV_VertexID) float4 packed_cell = g_HashGridCache_DebugCellBuffer[debug_cell_index]; uint decay_tile = WrapDecay(g_HashGridCache_DecayTileBuffer[tile_index]); - uint decay_cell = WrapDecay(g_HashGridCache_DecayCellBuffer[debug_cell_index]); + uint decay_cell = WrapDecay(g_HashGridCache_DebugDecayCellBuffer[debug_cell_index]); float cell_size; float3 cell_center, direction; diff --git a/src/core/src/render_techniques/gi1/gi1_shared.h b/src/core/src/render_techniques/gi1/gi1_shared.h index 7f2b51fc..52d3af72 100644 --- a/src/core/src/render_techniques/gi1/gi1_shared.h +++ b/src/core/src/render_techniques/gi1/gi1_shared.h @@ -81,6 +81,8 @@ struct HashGridCacheConstants uint first_cell_offset_tile_mip3; uint buffer_ping_pong; float max_sample_count; + float discard_multibounce_ray_probability; + float max_multibounce_sample_count; uint debug_mip_level; uint debug_propagate; uint debug_max_cell_decay; @@ -98,16 +100,23 @@ enum HashGridBufferNamesFloat enum HashGridBufferNamesUint { HASHGRIDCACHE_HASHBUFFER = 0, - HASHGRIDCACHE_DECAYCELLBUFFER, + HASHGRIDCACHE_DEBUGDECAYCELLBUFFER, HASHGRIDCACHE_DECAYTILEBUFFER, HASHGRIDCACHE_UPDATETILEBUFFER, HASHGRIDCACHE_UPDATETILECOUNTBUFFER, HASHGRIDCACHE_UPDATECELLVALUEBUFFER, - HASHGRIDCACHE_VISIBILITYCOUNTBUFFER, + HASHGRIDCACHE_UPDATECELLVALUEINDIRECTBUFFER, + HASHGRIDCACHE_VISIBILITYCOUNTBUFFER0, + HASHGRIDCACHE_VISIBILITYCOUNTBUFFER1, HASHGRIDCACHE_VISIBILITYCELLBUFFER, HASHGRIDCACHE_VISIBILITYQUERYBUFFER, HASHGRIDCACHE_VISIBILITYRAYBUFFER, HASHGRIDCACHE_VISIBILITYRAYCOUNTBUFFER, + HASHGRIDCACHE_MULTIBOUNCECOUNTBUFFER, + HASHGRIDCACHE_MULTIBOUNCECELLBUFFER, + HASHGRIDCACHE_MULTIBOUNCEQUERYBUFFER, + HASHGRIDCACHE_RESOLVECOUNTBUFFER, + HASHGRIDCACHE_RESOLVEBUFFER, HASHGRIDCACHE_PACKEDTILECOUNTBUFFER0, HASHGRIDCACHE_PACKEDTILECOUNTBUFFER1, HASHGRIDCACHE_PACKEDTILEINDEXBUFFER0, @@ -117,18 +126,20 @@ enum HashGridBufferNamesUint HASHGRIDCACHE_BUCKETOVERFLOWBUFFER, HASHGRIDCACHE_FREEBUCKETBUFFER, HASHGRIDCACHE_USEDBUCKETBUFFER, - HASHGRID_UINT_BUFFER_COUNT + HASHGRID_UINT_BUFFER_COUNT, }; enum HashGridBufferNamesUint2 { HASHGRIDCACHE_VALUEBUFFER = 0, + HASHGRIDCACHE_VALUEINDIRECTBUFFER, HASHGRID_UINT2_BUFFER_COUNT }; enum HashGridBufferNamesFloat4 { HASHGRIDCACHE_VISIBILITYBUFFER = 0, + HASHGRIDCACHE_MULTIBOUNCEINFOBUFFER, HASHGRIDCACHE_DEBUGCELLBUFFER, HASHGRID_FLOAT4_BUFFER_COUNT }; diff --git a/src/core/src/render_techniques/gi1/hash_grid_cache.hlsl b/src/core/src/render_techniques/gi1/hash_grid_cache.hlsl index 2c99aa95..1901628a 100644 --- a/src/core/src/render_techniques/gi1/hash_grid_cache.hlsl +++ b/src/core/src/render_techniques/gi1/hash_grid_cache.hlsl @@ -53,22 +53,31 @@ RWStructuredBuffer g_HashGridCache_BuffersUint2[] : register(space97); RWStructuredBuffer g_HashGridCache_BuffersFloat4[] : register(space98); #define g_HashGridCache_HashBuffer g_HashGridCache_BuffersUint [HASHGRIDCACHE_HASHBUFFER] -#define g_HashGridCache_DecayCellBuffer g_HashGridCache_BuffersUint [HASHGRIDCACHE_DECAYCELLBUFFER] #define g_HashGridCache_DecayTileBuffer g_HashGridCache_BuffersUint [HASHGRIDCACHE_DECAYTILEBUFFER] #define g_HashGridCache_ValueBuffer g_HashGridCache_BuffersUint2 [HASHGRIDCACHE_VALUEBUFFER] +#define g_HashGridCache_ValueIndirectBuffer g_HashGridCache_BuffersUint2 [HASHGRIDCACHE_VALUEINDIRECTBUFFER] #define g_HashGridCache_UpdateTileBuffer g_HashGridCache_BuffersUint [HASHGRIDCACHE_UPDATETILEBUFFER] #define g_HashGridCache_UpdateTileCountBuffer g_HashGridCache_BuffersUint [HASHGRIDCACHE_UPDATETILECOUNTBUFFER] #define g_HashGridCache_UpdateCellValueBuffer g_HashGridCache_BuffersUint [HASHGRIDCACHE_UPDATECELLVALUEBUFFER] +#define g_HashGridCache_UpdateCellValueIndirectBuffer g_HashGridCache_BuffersUint [HASHGRIDCACHE_UPDATECELLVALUEINDIRECTBUFFER] #define g_HashGridCache_VisibilityBuffer g_HashGridCache_BuffersFloat4[HASHGRIDCACHE_VISIBILITYBUFFER] -#define g_HashGridCache_VisibilityCountBuffer g_HashGridCache_BuffersUint [HASHGRIDCACHE_VISIBILITYCOUNTBUFFER] +#define g_HashGridCache_VisibilityCountBuffer0 g_HashGridCache_BuffersUint [HASHGRIDCACHE_VISIBILITYCOUNTBUFFER0] +#define g_HashGridCache_VisibilityCountBuffer1 g_HashGridCache_BuffersUint [HASHGRIDCACHE_VISIBILITYCOUNTBUFFER1] #define g_HashGridCache_VisibilityCellBuffer g_HashGridCache_BuffersUint [HASHGRIDCACHE_VISIBILITYCELLBUFFER] #define g_HashGridCache_VisibilityQueryBuffer g_HashGridCache_BuffersUint [HASHGRIDCACHE_VISIBILITYQUERYBUFFER] #define g_HashGridCache_VisibilityRayBuffer g_HashGridCache_BuffersUint [HASHGRIDCACHE_VISIBILITYRAYBUFFER] #define g_HashGridCache_VisibilityRayCountBuffer g_HashGridCache_BuffersUint [HASHGRIDCACHE_VISIBILITYRAYCOUNTBUFFER] +#define g_HashGridCache_MultibounceCountBuffer g_HashGridCache_BuffersUint [HASHGRIDCACHE_MULTIBOUNCECOUNTBUFFER] +#define g_HashGridCache_MultibounceCellBuffer g_HashGridCache_BuffersUint [HASHGRIDCACHE_MULTIBOUNCECELLBUFFER] +#define g_HashGridCache_MultibounceQueryBuffer g_HashGridCache_BuffersUint [HASHGRIDCACHE_MULTIBOUNCEQUERYBUFFER] +#define g_HashGridCache_MultibounceInfoBuffer g_HashGridCache_BuffersFloat4[HASHGRIDCACHE_MULTIBOUNCEINFOBUFFER] +#define g_HashGridCache_ResolveCountBuffer g_HashGridCache_BuffersUint [HASHGRIDCACHE_RESOLVECOUNTBUFFER] +#define g_HashGridCache_ResolveBuffer g_HashGridCache_BuffersUint [HASHGRIDCACHE_RESOLVEBUFFER] #define g_HashGridCache_PackedTileCountBuffer g_HashGridCache_BuffersUint [HASHGRIDCACHE_PACKEDTILECOUNTBUFFER0 + g_HashGridCacheConstants.buffer_ping_pong] #define g_HashGridCache_PreviousPackedTileCountBuffer g_HashGridCache_BuffersUint [HASHGRIDCACHE_PACKEDTILECOUNTBUFFER1 - g_HashGridCacheConstants.buffer_ping_pong] #define g_HashGridCache_PackedTileIndexBuffer g_HashGridCache_BuffersUint [HASHGRIDCACHE_PACKEDTILEINDEXBUFFER0 + g_HashGridCacheConstants.buffer_ping_pong] #define g_HashGridCache_PreviousPackedTileIndexBuffer g_HashGridCache_BuffersUint [HASHGRIDCACHE_PACKEDTILEINDEXBUFFER1 - g_HashGridCacheConstants.buffer_ping_pong] +#define g_HashGridCache_DebugDecayCellBuffer g_HashGridCache_BuffersUint [HASHGRIDCACHE_DEBUGDECAYCELLBUFFER] #define g_HashGridCache_DebugCellBuffer g_HashGridCache_BuffersFloat4[HASHGRIDCACHE_DEBUGCELLBUFFER] #define g_HashGridCache_BucketOccupancyBuffer g_HashGridCache_BuffersUint [HASHGRIDCACHE_BUCKETOCCUPANCYBUFFER] #define g_HashGridCache_BucketOverflowCountBuffer g_HashGridCache_BuffersUint [HASHGRIDCACHE_BUCKETOVERFLOWCOUNTBUFFER] @@ -390,6 +399,18 @@ HashGridCache_Visibility HashGridCache_UnpackVisibility(in float4 packed_visibil return visibility; } +uint HashGridCache_PackColor(float3 albedo) +{ + uint3 packed_albedo = uint3(round(albedo * 255.0f)); + return (packed_albedo.x << 16) | (packed_albedo.y << 8) | packed_albedo.z; +} + +float3 HashGridCache_UnpackColor(uint packed) +{ + uint3 albedo = uint3(packed >> 16, packed >> 8, packed) & 0xFFu; + return float3(float(albedo.x) / 255.0f, float(albedo.y) / 255.0f, float(albedo.z) / 255.0f); +} + float3 HashGridCache_HeatColor(float heatValue) { // 0 -> Red, 0.5 -> Blue, 1.0 -> Green @@ -403,7 +424,7 @@ float3 HashGridCache_HeatColor(float heatValue) return heatColor; } -float4 HashGridCache_FilteredRadiance(uint cell_index_mip0, bool debug_mip_level) +float4 HashGridCache_FilteredRadiance(uint cell_index_mip0, float max_sample_count, bool debug_mip_level) { uint2 cell_offset_mip0; uint tile_index = HashGridCache_CellOffsetMip0(cell_index_mip0, cell_offset_mip0); @@ -420,17 +441,17 @@ float4 HashGridCache_FilteredRadiance(uint cell_index_mip0, bool debug_mip_level radiance = debug_mip_level ? float4(HashGridCache_HeatColor(1.000f), radiance.w) : radiance; // Mip 1 - use_mip = radiance.w < g_HashGridCacheConstants.max_sample_count && cell_index_mip1 != kGI1_InvalidId; + use_mip = radiance.w < max_sample_count && cell_index_mip1 != kGI1_InvalidId; radiance = use_mip ? HashGridCache_UnpackRadiance(g_HashGridCache_ValueBuffer[cell_index_mip1]) : radiance; radiance = debug_mip_level && use_mip ? float4(HashGridCache_HeatColor(0.666f), radiance.w) : radiance; // Mip 2 - use_mip = radiance.w < g_HashGridCacheConstants.max_sample_count && cell_index_mip2 != kGI1_InvalidId; + use_mip = radiance.w < max_sample_count && cell_index_mip2 != kGI1_InvalidId; radiance = use_mip ? HashGridCache_UnpackRadiance(g_HashGridCache_ValueBuffer[cell_index_mip2]) : radiance; radiance = debug_mip_level && use_mip ? float4(HashGridCache_HeatColor(0.333f), radiance.w) : radiance; // Mip 3 - use_mip = radiance.w < g_HashGridCacheConstants.max_sample_count && cell_index_mip3 != kGI1_InvalidId; + use_mip = radiance.w < max_sample_count && cell_index_mip3 != kGI1_InvalidId; radiance = use_mip ? HashGridCache_UnpackRadiance(g_HashGridCache_ValueBuffer[cell_index_mip3]) : radiance; radiance = debug_mip_level && use_mip ? float4(HashGridCache_HeatColor(0.000f), radiance.w) : radiance; @@ -438,4 +459,69 @@ float4 HashGridCache_FilteredRadiance(uint cell_index_mip0, bool debug_mip_level return radiance; } +void HashGridCache_FilteredRadianceIndirect(uint cell_index_mip0, bool debug_mip_level, out float4 indirect_radiance) +{ + uint2 cell_offset_mip0; + uint tile_index = HashGridCache_CellOffsetMip0(cell_index_mip0, cell_offset_mip0); + uint cell_index_mip1 = g_HashGridCacheConstants.size_tile_mip1 > 0 ? HashGridCache_CellIndex(cell_offset_mip0, tile_index, 1) : kGI1_InvalidId; + uint cell_index_mip2 = g_HashGridCacheConstants.size_tile_mip2 > 0 ? HashGridCache_CellIndex(cell_offset_mip0, tile_index, 2) : kGI1_InvalidId; + uint cell_index_mip3 = g_HashGridCacheConstants.size_tile_mip3 > 0 ? HashGridCache_CellIndex(cell_offset_mip0, tile_index, 3) : kGI1_InvalidId; + + // Select best mip + bool use_mip; + + // Mip 0 + indirect_radiance = HashGridCache_UnpackRadiance(g_HashGridCache_ValueIndirectBuffer[cell_index_mip0]); + indirect_radiance = debug_mip_level ? float4(HashGridCache_HeatColor(1.000f), indirect_radiance.w) : indirect_radiance; + + // Mip 1 + use_mip = indirect_radiance.w < g_HashGridCacheConstants.max_multibounce_sample_count && cell_index_mip1 != kGI1_InvalidId; + indirect_radiance = use_mip ? HashGridCache_UnpackRadiance(g_HashGridCache_ValueIndirectBuffer[cell_index_mip1]) : indirect_radiance; + indirect_radiance = debug_mip_level && use_mip ? float4(HashGridCache_HeatColor(0.666f), indirect_radiance.w) : indirect_radiance; + + // Mip 2 + use_mip = indirect_radiance.w < g_HashGridCacheConstants.max_multibounce_sample_count && cell_index_mip2 != kGI1_InvalidId; + indirect_radiance = use_mip ? HashGridCache_UnpackRadiance(g_HashGridCache_ValueIndirectBuffer[cell_index_mip2]) : indirect_radiance; + indirect_radiance = debug_mip_level && use_mip ? float4(HashGridCache_HeatColor(0.333f), indirect_radiance.w) : indirect_radiance; + + // Mip 3 + use_mip = indirect_radiance.w < g_HashGridCacheConstants.max_multibounce_sample_count && cell_index_mip3 != kGI1_InvalidId; + indirect_radiance = use_mip ? HashGridCache_UnpackRadiance(g_HashGridCache_ValueIndirectBuffer[cell_index_mip3]) : indirect_radiance; + indirect_radiance = debug_mip_level && use_mip ? float4(HashGridCache_HeatColor(0.000f), indirect_radiance.w) : indirect_radiance; + + return; +} + +void HashGridCache_FilteredRadianceDirect(uint cell_index_mip0, bool debug_mip_level, out float4 direct_radiance) +{ + uint2 cell_offset_mip0; + uint tile_index = HashGridCache_CellOffsetMip0(cell_index_mip0, cell_offset_mip0); + uint cell_index_mip1 = g_HashGridCacheConstants.size_tile_mip1 > 0 ? HashGridCache_CellIndex(cell_offset_mip0, tile_index, 1) : kGI1_InvalidId; + uint cell_index_mip2 = g_HashGridCacheConstants.size_tile_mip2 > 0 ? HashGridCache_CellIndex(cell_offset_mip0, tile_index, 2) : kGI1_InvalidId; + uint cell_index_mip3 = g_HashGridCacheConstants.size_tile_mip3 > 0 ? HashGridCache_CellIndex(cell_offset_mip0, tile_index, 3) : kGI1_InvalidId; + + // Select best mip + bool use_mip; + + // Mip 0 + direct_radiance = HashGridCache_UnpackRadiance(g_HashGridCache_ValueBuffer[cell_index_mip0]); + direct_radiance = debug_mip_level ? float4(HashGridCache_HeatColor(1.000f), direct_radiance.w) : direct_radiance; + + // Mip 1 + use_mip = direct_radiance.w < g_HashGridCacheConstants.max_sample_count && cell_index_mip1 != kGI1_InvalidId; + direct_radiance = use_mip ? HashGridCache_UnpackRadiance(g_HashGridCache_ValueBuffer[cell_index_mip1]) : direct_radiance; + direct_radiance = debug_mip_level && use_mip ? float4(HashGridCache_HeatColor(0.666f), direct_radiance.w) : direct_radiance; + + // Mip 2 + use_mip = direct_radiance.w < g_HashGridCacheConstants.max_sample_count && cell_index_mip2 != kGI1_InvalidId; + direct_radiance = use_mip ? HashGridCache_UnpackRadiance(g_HashGridCache_ValueBuffer[cell_index_mip2]) : direct_radiance; + direct_radiance = debug_mip_level && use_mip ? float4(HashGridCache_HeatColor(0.333f), direct_radiance.w) : direct_radiance; + + // Mip 3 + use_mip = direct_radiance.w < g_HashGridCacheConstants.max_sample_count && cell_index_mip3 != kGI1_InvalidId; + direct_radiance = use_mip ? HashGridCache_UnpackRadiance(g_HashGridCache_ValueBuffer[cell_index_mip3]) : direct_radiance; + direct_radiance = debug_mip_level && use_mip ? float4(HashGridCache_HeatColor(0.000f), direct_radiance.w) : direct_radiance; + + return; +} #endif // HASH_GRID_CACHE_HLSL diff --git a/src/core/src/render_techniques/reference_path_tracer/reference_path_tracer.cpp b/src/core/src/render_techniques/reference_path_tracer/reference_path_tracer.cpp index 09543c8a..84f5205c 100644 --- a/src/core/src/render_techniques/reference_path_tracer/reference_path_tracer.cpp +++ b/src/core/src/render_techniques/reference_path_tracer/reference_path_tracer.cpp @@ -60,7 +60,11 @@ RenderOptionList ReferencePT::getRenderOptions() noexcept newOptions.emplace(RENDER_OPTION_MAKE(reference_pt_nee_only, options)); newOptions.emplace(RENDER_OPTION_MAKE(reference_pt_disable_nee, options)); newOptions.emplace(RENDER_OPTION_MAKE(reference_pt_use_dxr10, options)); + + // Debug reflections + newOptions.emplace(RENDER_OPTION_MAKE(reference_pt_debug_reflections, options)); newOptions.emplace(RENDER_OPTION_MAKE(reference_pt_accumulate, options)); + return newOptions; } @@ -77,6 +81,8 @@ ReferencePT::RenderOptions ReferencePT::convertOptions(RenderOptionList const &o RENDER_OPTION_GET(reference_pt_nee_only, newOptions, options) RENDER_OPTION_GET(reference_pt_disable_nee, newOptions, options) RENDER_OPTION_GET(reference_pt_use_dxr10, newOptions, options) + + RENDER_OPTION_GET(reference_pt_debug_reflections, newOptions, options) RENDER_OPTION_GET(reference_pt_accumulate, newOptions, options) return newOptions; } @@ -240,9 +246,11 @@ void ReferencePT::renderGUI(CapsaicinInternal &capsaicin) const noexcept ImGui::Checkbox("Disable NEE", &capsaicin.getOption("reference_pt_disable_nee")); ImGui::Checkbox( "Disable Specular Materials", &capsaicin.getOption("reference_pt_disable_specular_materials")); + ImGui::Checkbox( + "Debug reflections", &capsaicin.getOption("reference_pt_debug_reflections")); ImGui::Checkbox( "Disable Alpha Testing", &capsaicin.getOption("reference_pt_disable_alpha_testing")); - ImGui::Checkbox("Enable Accumulation", &capsaicin.getOption("reference_pt_accumulate")); + ImGui::Checkbox("Enable Accumulation", &capsaicin.getOption("reference_pt_accumulate")); } bool ReferencePT::initKernels(CapsaicinInternal const &capsaicin) noexcept @@ -280,6 +288,12 @@ bool ReferencePT::initKernels(CapsaicinInternal const &capsaicin) noexcept { defines.push_back("DISABLE_NEE"); } + + if (options.reference_pt_debug_reflections) + { + defines.push_back("DEBUG_REFLECTIONS"); + } + if (options.reference_pt_use_dxr10) { std::vector exports; @@ -345,7 +359,9 @@ bool ReferencePT::needsRecompile( || options.reference_pt_disable_alpha_testing != newOptions.reference_pt_disable_alpha_testing || options.reference_pt_nee_only != newOptions.reference_pt_nee_only || options.reference_pt_disable_nee != newOptions.reference_pt_disable_nee - || options.reference_pt_use_dxr10 != newOptions.reference_pt_use_dxr10; + || options.reference_pt_use_dxr10 != newOptions.reference_pt_use_dxr10 + || options.reference_pt_debug_reflections != newOptions.reference_pt_debug_reflections; + return recompile; } diff --git a/src/core/src/render_techniques/reference_path_tracer/reference_path_tracer.h b/src/core/src/render_techniques/reference_path_tracer/reference_path_tracer.h index 2f115633..67b8884b 100644 --- a/src/core/src/render_techniques/reference_path_tracer/reference_path_tracer.h +++ b/src/core/src/render_techniques/reference_path_tracer/reference_path_tracer.h @@ -61,8 +61,9 @@ class ReferencePT : public RenderTechnique false; /**< Disable testing material alpha - treats all surfaces as opaque */ bool reference_pt_nee_only = false; /**< Disable light contributions from source other than NEE */ bool reference_pt_disable_nee = false; /**< Disable light contributions from Next Event Estimation */ - bool reference_pt_use_dxr10 = false; /**< Use dxr 1.0 ray-tracing pipelines instead of inline rt */ + bool reference_pt_use_dxr10 = false; /**< Use dxr 1.0 ray-tracing pipelines instead of inline rt */ bool reference_pt_accumulate = true; /**< Enable accumulation of frames */ + bool reference_pt_debug_reflections = false; }; /** diff --git a/src/scene_viewer/main_shared.cpp b/src/scene_viewer/main_shared.cpp index ae5dc456..db894bd8 100644 --- a/src/scene_viewer/main_shared.cpp +++ b/src/scene_viewer/main_shared.cpp @@ -82,6 +82,9 @@ vector const CapsaicinMain::scenes = { { .name = "Sponza", .fileName = {"assets/CapsaicinTestMedia/sponza/Sponza.gltf"}, .useEnvironmentMap = true}, + { .name = "Cornell Box", + .fileName = {"assets/CapsaicinTestMedia/cornell_box_2/scene.gltf"}, + .useEnvironmentMap = false}, }; vector> const CapsaicinMain::sceneEnvironmentMaps = { @@ -1066,7 +1069,7 @@ bool CapsaicinMain::renderFrame() noexcept if (saveImage) { // Disable performing tone mapping as we output in HDR - if (!saveAsJPEG && Capsaicin::hasOption("tonemap_enable")) + if (!saveAsJPEG && !saveAsPNG && Capsaicin::hasOption("tonemap_enable")) { reenableToneMap = Capsaicin::getOption("tonemap_enable"); Capsaicin::setOption("tonemap_enable", false); @@ -1587,7 +1590,11 @@ bool CapsaicinMain::renderGUIDetails() saveImage = true; } ImGui::SameLine(); - ImGui::Checkbox("Save as JPEG", &saveAsJPEG); + if (!saveAsPNG) + ImGui::Checkbox("Save as JPEG", &saveAsJPEG); + ImGui::SameLine(); + if (!saveAsJPEG) + ImGui::Checkbox("Save as PNG", &saveAsPNG); } return true; } @@ -1626,18 +1633,23 @@ void CapsaicinMain::saveFrame() noexcept { savePath += view; } + if (saveAsJPEG) { - savePath += ".jpeg"; + savePath += ".jpeg"sv; + } + else if (saveAsPNG) + { + savePath += ".png"sv; } else { - savePath += ".exr"; + savePath += ".exr"sv; } - // Save the requested buffer to disk Capsaicin::DumpDebugView(savePath, dumpView); } + catch (exception const &e) { printString(e.what(), MessageLevel::Error); @@ -1664,14 +1676,44 @@ filesystem::path CapsaicinMain::getSaveName() { currentEM = "None"; } + + auto renderer = Capsaicin::GetCurrentRenderer(); + auto view = Capsaicin::GetCurrentDebugView(); + // reference path tracer + if (renderer.ends_with('r')) + { + savePath += "ref/"s; + if (Capsaicin::getOption("reference_pt_debug_reflections")) + { + view = "Reflection"; + } + } + else if (renderer.ends_with("1")) + { + savePath += "test/"s; + } + savePath += renderer; + + savePath += '_'; + + if (Capsaicin::getOption("gi1_use_multibounce")) + { + savePath += "Multibounce_"; + } + else if (renderer.ends_with("1") && !Capsaicin::getOption("gi1_use_multibounce")) + { + savePath += "SingleBounce_"; + } savePath += currentSceneName; savePath += '_'; savePath += currentEM; savePath += '_'; savePath += Capsaicin::GetSceneCurrentCamera(); savePath += '_'; - savePath += Capsaicin::GetCurrentRenderer(); + savePath += view; + savePath += '_'; + auto filename = savePath.filename().string(); erase_if(filename, [](unsigned char const c) { return isspace(c); }); savePath.replace_filename(filename); diff --git a/src/scene_viewer/main_shared.h b/src/scene_viewer/main_shared.h index 59781103..0dc5016c 100644 --- a/src/scene_viewer/main_shared.h +++ b/src/scene_viewer/main_shared.h @@ -196,6 +196,7 @@ class CapsaicinMain benchmark mode (default is just the last frame) */ std::string benchmarkModeSuffix; /**< String appended to any saved files */ bool saveAsJPEG = false; /**< File type selector for dump frame */ + bool saveAsPNG = false; bool saveImage = false; /**< Used to buffer save image requests */ bool reDisableRender = false; /**< Use to render only a single frame at a time */