Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
b99ae6e
Fix small bug in GenericDataAccessor definition
Nov 11, 2025
b9537ea
First draft of Warpmap Generation workgroup implementation
Nov 11, 2025
a737173
Add warp concept
Nov 18, 2025
64349db
Add spherical warp
Nov 18, 2025
e44fcf4
Remove envmap accessors.hlsl
Nov 18, 2025
9b29dfd
Hierarchical image sampling implementation
Nov 18, 2025
06ae3e4
Merge branch 'master' into env_map_importance_sampling
Dec 19, 2025
16ecb52
Merge branch 'master' into env_map_importance_sampling
Dec 20, 2025
8d682b9
Remove envmap.hlsl
Dec 20, 2025
890f7c6
Move to sampling namespace and implement backward density
Dec 22, 2025
f99c63b
Remove private, public from hierarchical_image
Dec 22, 2025
3ff2791
Refactor hierarchical image to keep accessor and common data as member
Dec 22, 2025
76ef536
Refactor hierarchical image to separate binarySearch from Hierarchica…
Dec 26, 2025
ef773fd
Fix Spherical warp indentation
Dec 26, 2025
b9467fe
Add some comment why we add xi to the sample uvs
Dec 26, 2025
3682604
Merge branch 'master' into env_map_importance_sampling
Dec 30, 2025
ac1e2f3
WIP
Jan 6, 2026
baca1cf
Rename uv to coord for LuminanceAccessor concepts
Jan 9, 2026
f12b797
Fix hierarchical_image.hlsl
Jan 9, 2026
0957aed
Fix typo in spherical.hlsl
Jan 9, 2026
1b35d34
Implement gen_luma, gen_warpmap and measure_luma shaders
Jan 9, 2026
665bb8d
EnvmapImportanceSampling CMakeLists
Jan 9, 2026
b522b4f
Initial implementation of CEnvmapImportanceSampling
Jan 9, 2026
3e51c69
Initial implementation of CEnvmapImportanceSampling
Jan 12, 2026
c72d305
Small fixes
Jan 12, 2026
5ee2ce7
Initial implementation of computeWarpMap
Jan 20, 2026
867868c
Fix arithmetic config no const specifier for method
Jan 30, 2026
1a66157
Define config_t from outside
Jan 30, 2026
d4b8105
More fixes on computeWarpMap implementation
Jan 30, 2026
8853738
Fix chose second to be placed inside the loop
Jan 30, 2026
6bde489
LuminanceReadAccessor take ScalarT as template parameter
Jan 30, 2026
756fbb0
gen_warpmap to gen_warp
Jan 30, 2026
8d64a19
Move hierarchical_image concepts to sampling subdirectory
Feb 18, 2026
70d8423
Add some comment regarding corner sampling
Feb 18, 2026
2842d29
Parameterize spherical warp and make sure all literal is in the corre…
Feb 18, 2026
a51848c
Refactor CEnvmapImportanceSampling to block and calculate avgLuma
Feb 18, 2026
58c9c13
merge master, fix conflicts
keptsecret Feb 19, 2026
fa94ac2
Fix warp concept and add density type to warp concept
Feb 19, 2026
8494124
Rename luminanceScale to lumaRGBCoefficients
Feb 19, 2026
b273d87
Remove measure_luma.comp.hlsl
Feb 19, 2026
3bc0e57
Fix some bug in hierarchical_image.hlsl
Feb 19, 2026
1dadf92
Rename luminanceScales to lumaRGBCoefficients
Feb 19, 2026
f19cbe9
Move EnvmapImportanceSampling from ext to core
Feb 21, 2026
fde2bba
Fix binarySearch implementation. when last is 2x1 we should check for…
Feb 21, 2026
81cae21
Rename private member with underscore prefix
Feb 21, 2026
733a4ab
Merge branch 'master' into env_map_importance_sampling
Feb 22, 2026
ba6be93
Update submodule to follow master branch
Feb 22, 2026
f04d98b
Add missed EnvmapSampler.h and cpp
Feb 23, 2026
df2bfc3
Rename get and gather to texelFetch and texelGather
Feb 23, 2026
4930e25
Merge branch 'hlsl_path_tracer_example' into env_map_importance_sampling
Feb 23, 2026
05b862a
Include missing files into commit
Feb 23, 2026
d50b50f
Merge branch 'master' into env_map_importance_sampling
Feb 23, 2026
1498094
Update comment on sampleUvs
Mar 3, 2026
39da42b
Use bitfield for lumaMapResolution in push constant
Mar 3, 2026
47799ab
Remove NBL_BUILD_ENVMAP_IMPORTANCE_SAMPLING option
Mar 3, 2026
0f69171
Fix worgroup dim for gen_warp
Mar 3, 2026
43b88ef
Use constant workgroup dimension instead of WORKGROUP_DIM define
Mar 3, 2026
9aa3113
Remove passing WORKGROUP_DIM to shader
Mar 3, 2026
16c374e
Remove passing WORKGROUP_DIM for gen_luma
Mar 4, 2026
126aa21
Add upsscale parameter for EnvmapSampler
Mar 4, 2026
86a00f9
Pass warpMap width and height to luminanceSampler. Check For Out of B…
Mar 4, 2026
7509c83
Small fixes to gen_warp shader
Mar 4, 2026
833b388
gen_warp get image dimension from push constant instead of OpImageQue…
Mar 4, 2026
ac63441
Rename HierarchicalImage to WarpmapSampler
Mar 4, 2026
dacf493
Add todo comment for cube map
Mar 4, 2026
a2b57f9
Move EnvmapSampler from core/sampling to video/sampling
Mar 5, 2026
3343e64
Rename LuminanceSampler to HierarchicalLuminanceSampler
Mar 5, 2026
834163a
Fix corner sampling logic in gen_luma and hierarchical_image.hlsl
Mar 6, 2026
94d1f14
Fix previous commit
Mar 6, 2026
23a7e12
Add some todo comment for corner sampling flag
Mar 6, 2026
e8930ef
Add const modifier for binarySearch
Mar 7, 2026
de4807f
Add some temporary struct from pr #1001
Mar 7, 2026
a158057
Refactor hierarchical_image naming and concepts
Mar 10, 2026
4dd4e1f
Fix indentation of hierarchical_image.hlsl
Mar 10, 2026
fe14c93
Small fixes
Mar 10, 2026
bb41942
Remove superfluous member from WarpmapSampler
Mar 11, 2026
6b87e69
Merge branch 'master' into env_map_importance_sampling
Mar 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion 3rdparty/openexr
Submodule openexr updated 1 files
+2 −0 cmake/CMakeLists.txt
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ else()
message(STATUS "Vulkan SDK is not found")
endif()

option(NBL_COMPILE_WITH_CUDA "Compile with CUDA interop?" OFF)
option(NBL_COMPILE_WITH_CUDA "Compile with CUDA interop?" ON)

if(NBL_COMPILE_WITH_CUDA)
find_package(CUDAToolkit REQUIRED)
Expand Down Expand Up @@ -195,7 +195,7 @@ endif()
option(NBL_BUILD_BULLET "Enable Bullet Physics building and integration?" OFF)
option(NBL_BUILD_DOCS "Enable building documentation?" OFF) # No one has doxygen installed, plus we dont know when was the last time we generated working doxy and we'll use SphinX in the future
option(NBL_ENABLE_PROJECT_JSON_CONFIG_VALIDATION "" ON)
option(NBL_EMBED_BUILTIN_RESOURCES "Embed built-in resources?" OFF)
option(NBL_EMBED_BUILTIN_RESOURCES "Embed built-in resources?" ON)
option(NBL_ENABLE_DOCKER_INTEGRATION "Enables docker integration, if client is not found Docker Desktop will be installed" OFF)

if (NBL_ENABLE_DOCKER_INTEGRATION)
Expand Down
2 changes: 1 addition & 1 deletion examples_tests
Submodule examples_tests updated 34 files
+0 −40 31_HLSLPathTracer/CMakeLists.txt
+0 −837 31_HLSLPathTracer/app_resources/glsl/common.glsl
+0 −182 31_HLSLPathTracer/app_resources/glsl/litByRectangle.comp
+0 −60 31_HLSLPathTracer/app_resources/glsl/litBySphere.comp
+0 −105 31_HLSLPathTracer/app_resources/glsl/litByTriangle.comp
+0 −742 31_HLSLPathTracer/app_resources/hlsl/example_common.hlsl
+0 −131 31_HLSLPathTracer/app_resources/hlsl/intersector.hlsl
+0 −286 31_HLSLPathTracer/app_resources/hlsl/material_system.hlsl
+0 −437 31_HLSLPathTracer/app_resources/hlsl/next_event_estimator.hlsl
+0 −19 31_HLSLPathTracer/app_resources/hlsl/present.frag.hlsl
+0 −40 31_HLSLPathTracer/app_resources/hlsl/rand_gen.hlsl
+0 −222 31_HLSLPathTracer/app_resources/hlsl/render.comp.hlsl
+0 −27 31_HLSLPathTracer/app_resources/hlsl/render_common.hlsl
+0 −13 31_HLSLPathTracer/app_resources/hlsl/render_rwmc_common.hlsl
+0 −65 31_HLSLPathTracer/app_resources/hlsl/resolve.comp.hlsl
+0 −15 31_HLSLPathTracer/app_resources/hlsl/resolve_common.hlsl
+0 −76 31_HLSLPathTracer/app_resources/hlsl/scene_base.hlsl
+0 −97 31_HLSLPathTracer/app_resources/hlsl/scene_rectangle_light.hlsl
+0 −100 31_HLSLPathTracer/app_resources/hlsl/scene_sphere_light.hlsl
+0 −97 31_HLSLPathTracer/app_resources/hlsl/scene_triangle_light.hlsl
+0 −17 31_HLSLPathTracer/include/nbl/this_example/common.hpp
+0 −167 31_HLSLPathTracer/include/nbl/this_example/transform.hpp
+0 −1,580 31_HLSLPathTracer/main.cpp
+0 −50 31_HLSLPathTracer/pipeline.groovy
+1 −1 40_PathTracer/include/renderer/shaders/session.hlsl
+9 −44 62_CAD/main.cpp
+72 −0 75_EnvmapImportanceSamplingTest/CMakeLists.txt
+38 −0 75_EnvmapImportanceSamplingTest/app_resources/common.hlsl
+24 −0 75_EnvmapImportanceSamplingTest/app_resources/present.frag.hlsl
+134 −0 75_EnvmapImportanceSamplingTest/app_resources/test.comp.hlsl
+0 −0 75_EnvmapImportanceSamplingTest/config.json.template
+7 −0 75_EnvmapImportanceSamplingTest/imagesTestList.txt
+485 −0 75_EnvmapImportanceSamplingTest/main.cpp
+1 −1 CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ NBL_CONCEPT_END(
#include <nbl/builtin/hlsl/concepts/__end.hlsl>

template<typename T, typename V, typename I=uint32_t>
NBL_BOOL_CONCEPT GenericDataAccessor = GenericWriteAccessor<T,V,I> && GenericWriteAccessor<T,V,I>;
NBL_BOOL_CONCEPT GenericDataAccessor = GenericReadAccessor<T,V,I> && GenericWriteAccessor<T,V,I>;

}
}
Expand Down
312 changes: 312 additions & 0 deletions include/nbl/builtin/hlsl/sampling/hierarchical_image.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,312 @@
// Copyright (C) 2018-2025 - DevSH Graphics Programming Sp. z O.O.
// This file is part of the "Nabla Engine".
// For conditions of distribution and use, see copyright notice in nabla.h

#ifndef _NBL_BUILTIN_HLSL_SAMPLING_HIERARCHICAL_IMAGE_INCLUDED_
#define _NBL_BUILTIN_HLSL_SAMPLING_HIERARCHICAL_IMAGE_INCLUDED_

#include <nbl/builtin/hlsl/sampling/basic.hlsl>
#include <nbl/builtin/hlsl/sampling/warp.hlsl>
#include <nbl/builtin/hlsl/sampling/hierarchical_image/accessors.hlsl>
#include <nbl/builtin/hlsl/cpp_compat/intrinsics.hlsl>

namespace nbl
{
namespace hlsl
{
namespace sampling
{

// TODO(kevinyu): Temporary struct before PR #1001 merged to master
template<typename V, typename P>
struct value_and_rcpPdf
{
using this_t = value_and_rcpPdf<V, P>;

static this_t create(const V _value, const P _rcpPdf)
{
this_t retval;
retval._value = _value;
retval._rcpPdf = _rcpPdf;
return retval;
}

V value() { return _value; }
P rcpPdf() { return _rcpPdf; }

V _value;
P _rcpPdf;
};

template<typename V, typename P>
struct value_and_pdf
{
using this_t = value_and_pdf<V, P>;

static this_t create(const V _value, const P _pdf)
{
this_t retval;
retval._value = _value;
retval._pdf = _pdf;
return retval;
}

V value() { return _value; }
P pdf() { return _pdf; }

V _value;
P _pdf;
};

// TODO: Add an option for corner sampling or centered sampling as boolean parameter
template <typename ScalarT, typename LuminanceAccessorT
NBL_PRIMARY_REQUIRES(
is_scalar_v<ScalarT> &&
hierarchical_image::LuminanceReadAccessor<LuminanceAccessorT, ScalarT>
)
struct HierarchicalWarpGenerator
{
using scalar_type = ScalarT;
using vector2_type = vector<scalar_type, 2>;
using vector4_type = vector<scalar_type, 4>;
using domain_type = vector2_type;
using codomain_type = vector2_type;
using sample_type = value_and_pdf<codomain_type, scalar_type>;
using density_type = scalar_type;

LuminanceAccessorT _map;
float32_t _rcpAvgLuma;
float32_t2 _rcpWarpSize;
uint16_t2 _mapSize;
uint16_t _mip2x1 : 15;
uint16_t _aspect2x1 : 1;

static HierarchicalWarpGenerator<ScalarT, LuminanceAccessorT> create(NBL_CONST_REF_ARG(LuminanceAccessorT) lumaMap, uint32_t2 mapSize, bool aspect2x1)
{
HierarchicalWarpGenerator<ScalarT, LuminanceAccessorT> result;
result._map = lumaMap;
result._mapSize = mapSize;
// Note: We use mapSize.y here because the currently the map aspect ratio can only be 1x1 or 2x1
result._mip2x1 = _static_cast<uint16_t>(findMSB(mapSize.y));
result._aspect2x1 = aspect2x1;
return result;
}

static bool __choseSecond(scalar_type first, scalar_type second, NBL_REF_ARG(scalar_type) xi, NBL_REF_ARG(scalar_type) rcpPmf) NBL_CONST_MEMBER_FUNC
{
// numerical resilience against IEEE754
scalar_type rcpChoiceProb = scalar_type(0);
PartitionRandVariable<scalar_type> partition;
partition.leftProb = scalar_type(1) / (scalar_type(1) + (second / first));
bool choseSecond = partition(xi, rcpChoiceProb);
rcpPmf *= rcpChoiceProb;
return choseSecond;
}

// Cannot use textureGather since we need to pass the mipLevel
vector4_type __texelGather(uint32_t2 coord, uint32_t level) NBL_CONST_MEMBER_FUNC
{
assert(coord.x < _mapSize.x - 1 && coord.y < _mapSize.y - 1);
const scalar_type v0, v1, v2, v3;

return float32_t4(
_map.load(uint32_t3(coord, level), uint32_t2(0, 1)),
_map.load(uint32_t3(coord, level), uint32_t2(1, 1)),
_map.load(uint32_t3(coord, level), uint32_t2(1, 0)),
_map.load(uint32_t3(coord, level), uint32_t2(0, 0))
);
}

sample_type generate(vector2_type xi) NBL_CONST_MEMBER_FUNC
{
uint32_t2 p = uint32_t2(0, 0);

scalar_type rcpPmf = 1;
if (_aspect2x1) {
// do one split in the X axis first cause penultimate full mip would have been 2x1
p.x = __choseSecond(_map.load(uint32_t2(0, 0), _mip2x1), _map.load(uint32_t2(1, 0), _mip2x1), xi.x, rcpPmf) ? 1 : 0;
}

for (int i = _mip2x1 - 1; i >= 0; i--)
{
p <<= 1;
const vector4_type values = __texelGather(p, i);
scalar_type wx_0, wx_1;
{
const scalar_type wy_0 = values[3] + values[2];
const scalar_type wy_1 = values[1] + values[0];
if (__choseSecond(wy_0, wy_1, xi.y, rcpPmf))
{
p.y |= 1;
wx_0 = values[0];
wx_1 = values[1];
}
else
{
wx_0 = values[3];
wx_1 = values[2];
}
}
if (__choseSecond(wx_0, wx_1, xi.x, rcpPmf))
p.x |= 1;
}


// If we don`t add xi, the sample will clump to the lowest corner of environment map texel. Each time we call PartitionRandVariable(), the output xi is the new xi that determines how left and right(or top and bottom for y axis) to choose the child partition. It means that if for some input xi, the output xi = 0, then the input xi is the edge of choosing this partition and the previous partition, and vice versa, if output xi = 1, then the input xi is the edge of choosing this partition and the next partition. Hence, by adding xi to the lower corner of the texel, we create a gradual transition from one pixel to another. Without adding output xi, the calculation of jacobian using the difference of sample value would not work.
// Since we want to do corner sampling. We have to handle edge texels as corner cases. Remember, in corner sampling we map uv [0,1] to [center of first texel, center of last texel]. So when p is an edge texel, we have to remap xi. [0.5, 1] when p == 0, and [0.5, 1] when p == length - 1.
if (p.x == 0)
xi.x = xi.x * scalar_type(0.5) + scalar_type(0.5);
if (p.y == 0)
xi.y = xi.y * scalar_type(0.5) + scalar_type(0.5);
if (p.x == _mapSize.x - 1)
xi.x = xi.x * scalar_type(0.5);
if (p.y == _mapSize.y - 1)
xi.y = xi.y * scalar_type(0.5);

const vector2_type directionUV = (vector2_type(p.x, p.y) + xi) / _mapSize;
return sample_type::create(directionUV, (_mapSize.x * _mapSize.y) / rcpPmf);
}

density_type forwardPdf(domain_type xi) NBL_CONST_MEMBER_FUNC
{
return generate(xi).pdf();
}

// Doesn't comply with sampler concept. This class is extracted so can be used on warpmap generation without passing in unnecessary information like avgLuma. So, need to pass in avgLuma when calculating backwardPdf.
density_type backwardPdf(codomain_type codomainVal, scalar_type rcpAvgLuma) NBL_CONST_MEMBER_FUNC
{
return _map.load(codomainVal) * rcpAvgLuma;
}

};

template <typename ScalarT, typename LuminanceAccessorT, typename PostWarpT
NBL_PRIMARY_REQUIRES(
is_scalar_v<ScalarT> &&
hierarchical_image::LuminanceReadAccessor<LuminanceAccessorT, ScalarT> &&
concepts::Warp<PostWarpT>
)
struct HierarchicalWarpSampler
{
using warp_generator_type = HierarchicalWarpGenerator<ScalarT, LuminanceAccessorT>;
using warp_sample_type = typename warp_generator_type::sample_type;
using scalar_type = ScalarT;
using density_type = scalar_type;
using vector2_type = vector<scalar_type, 2>;
using vector3_type = vector<scalar_type, 3>;
using vector4_type = vector<scalar_type, 4>;
using domain_type = vector2_type;
using codomain_type = vector3_type;
using sample_type = value_and_pdf<codomain_type, density_type>;

warp_generator_type _warpGenerator;
scalar_type _rcpAvgLuma;

static HierarchicalWarpSampler<ScalarT, LuminanceAccessorT, PostWarpT> create(NBL_CONST_REF_ARG(LuminanceAccessorT) lumaMap, scalar_type avgLuma, uint32_t2 mapSize, bool aspect2x1)
{
HierarchicalWarpSampler result;
result._warpGenerator = warp_generator_type::create(lumaMap, mapSize, aspect2x1);
result._rcpAvgLuma = scalar_type(1.0) / avgLuma;
return result;
}

sample_type generate(domain_type xi) NBL_CONST_MEMBER_FUNC
{
const warp_sample_type warpSample = _warpGenerator.generate(xi);
const WarpResult<codomain_type> postWarpResult = PostWarpT::warp(warpSample.value());
return sample_type::create(postWarpResult.dst, postWarpResult.density * warpSample.pdf());
}

density_type forwardPdf(domain_type xi) NBL_CONST_MEMBER_FUNC
{
const warp_sample_type warpSample = _warpGenerator.generate(xi);
return PostWarpT::forwardDensity(warpSample.value()) * warpSample.pdf();
}

density_type backwardPdf(codomain_type codomainVal) NBL_CONST_MEMBER_FUNC
{
return PostWarpT::backwardPdf(codomainVal, _rcpAvgLuma) * _warpGenerator.backwardPdf(codomainVal);
}

};


template <typename ScalarT, typename LuminanceAccessorT, typename HierarchicalSamplerT, typename PostWarpT
NBL_PRIMARY_REQUIRES(is_scalar_v<ScalarT> &&
concepts::accessors::GenericReadAccessor<LuminanceAccessorT, ScalarT, float32_t2> &&
hierarchical_image::WarpAccessor<HierarchicalSamplerT, ScalarT> &&
concepts::Warp<PostWarpT>)
struct WarpmapSampler
{
using scalar_type = ScalarT;
using vector2_type = vector<ScalarT, 2>;
using vector3_type = vector<ScalarT, 3>;
using vector4_type = vector<ScalarT, 4>;
using domain_type = vector2_type;
using codomain_type = vector3_type;
using weight_type = scalar_type;
using sample_type = value_and_pdf<codomain_type, weight_type>;

LuminanceAccessorT _lumaMap;
HierarchicalSamplerT _warpMap;
uint32_t _effectiveWarpArea;
scalar_type _rcpAvgLuma;

static WarpmapSampler create(NBL_CONST_REF_ARG(LuminanceAccessorT) lumaMap, NBL_CONST_REF_ARG(HierarchicalSamplerT) warpMap, uint32_t2 warpSize, scalar_type avgLuma)
{
WarpmapSampler<ScalarT, LuminanceAccessorT, HierarchicalSamplerT, PostWarpT> result;
result._lumaMap = lumaMap;
result._warpMap = warpMap;
result._effectiveWarpArea = (warpSize.x - 1) * (warpSize.y - 1);
result._rcpAvgLuma = ScalarT(1.0) / avgLuma;
return result;
}

weight_type forwardWeight(domain_type xi) NBL_CONST_MEMBER_FUNC
{
return generate(xi).value();
}

weight_type backwardWeight(codomain_type direction) NBL_CONST_MEMBER_FUNC
{
vector2_type envmapUv = PostWarpT::inverseWarp(direction);
scalar_type luma;
_lumaMap.get(envmapUv, luma);
return luma * _rcpAvgLuma * PostWarpT::backwardDensity(direction);
}

sample_type generate(vector2_type xi) NBL_CONST_MEMBER_FUNC
{
const vector2_type interpolant;
matrix<scalar_type, 4, 2> uvs;
_warpMap.gatherUv(xi, uvs, interpolant);

const vector2_type xDiffs[] = {
uvs[2] - uvs[3],
uvs[1] - uvs[0]
};
const vector2_type yVals[] = {
xDiffs[0] * interpolant.x + uvs[3],
xDiffs[1] * interpolant.x + uvs[0]
};
const vector2_type yDiff = yVals[1] - yVals[0];
vector2_type uv = yDiff * interpolant.y + yVals[0];

const WarpResult<vector3_type> warpResult = PostWarpT::warp(uv);

const scalar_type detInterpolJacobian = determinant(matrix<scalar_type, 2, 2>(
lerp(xDiffs[0], xDiffs[1], interpolant.y), // first column dFdx
yDiff // second column dFdy
)) * _effectiveWarpArea;

const scalar_type pdf = abs(warpResult.density / detInterpolJacobian);

return sample_type::create(warpResult.dst, pdf);
}
};

}
}
}

#endif
Loading
Loading