Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 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
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
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ option(NBL_BUILD_EXAMPLES "Enable building examples" ON)
option(NBL_BUILD_MITSUBA_LOADER "Enable nbl::ext::MitsubaLoader?" OFF) # TODO: once it compies turn this ON by default!
option(NBL_BUILD_IMGUI "Enable nbl::ext::ImGui?" ON)
option(NBL_BUILD_DEBUG_DRAW "Enable Nabla Debug Draw extension?" ON)
option(NBL_BUILD_ENVMAP_IMPORTANCE_SAMPLING "Enable Nabla Envmap Importance Sampling extension?" ON)

option(NBL_BUILD_OPTIX "Enable nbl::ext::OptiX?" OFF)
if(NBL_COMPILE_WITH_CUDA)
Expand Down
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#ifndef _NBL_BUILTIN_HLSL_CONCEPTS_ACCESSORS_HIERARCHICAL_IMAGE_INCLUDED_
#define _NBL_BUILTIN_HLSL_CONCEPTS_ACCESSORS_HIERARCHICAL_IMAGE_INCLUDED_

#include "nbl/builtin/hlsl/concepts/accessors/generic_shared_data.hlsl"

namespace nbl
{
namespace hlsl
{
namespace sampling
{
namespace hierarchical_image
{
// declare concept
#define NBL_CONCEPT_NAME LuminanceReadAccessor
#define NBL_CONCEPT_TPLT_PRM_KINDS (typename)
#define NBL_CONCEPT_TPLT_PRM_NAMES (U)
// not the greatest syntax but works
#define NBL_CONCEPT_PARAM_0 (a,U)
#define NBL_CONCEPT_PARAM_1 (coord,uint32_t2)
#define NBL_CONCEPT_PARAM_2 (level,uint32_t)
// start concept
NBL_CONCEPT_BEGIN(3)
// need to be defined AFTER the concept begins
#define a NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0
#define coord NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1
#define level NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2
NBL_CONCEPT_END(
((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((a.template get(coord,level)) , ::nbl::hlsl::is_same_v, float32_t))
((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((a.template gather(coord,level)) , ::nbl::hlsl::is_same_v, float32_t4))
);
#undef level
#undef coord
#undef a
#include <nbl/builtin/hlsl/concepts/__end.hlsl>

// declare concept
#define NBL_CONCEPT_NAME HierarchicalSampler
#define NBL_CONCEPT_TPLT_PRM_KINDS (typename)(typename)
#define NBL_CONCEPT_TPLT_PRM_NAMES (HierarchicalSamplerT)(ScalarT)
// not the greatest syntax but works
#define NBL_CONCEPT_PARAM_0 (sampler,HierarchicalSamplerT)
#define NBL_CONCEPT_PARAM_1 (coord,vector<ScalarT, 2>)
// start concept
NBL_CONCEPT_BEGIN(2)
// need to be defined AFTER the concept begins
#define sampler NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0
#define coord NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1
NBL_CONCEPT_END(
((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((sampler.template sampleUvs(coord)) , ::nbl::hlsl::is_same_v, matrix<ScalarT, 4, 2>))
);
#undef sampler
#undef coord
#include <nbl/builtin/hlsl/concepts/__end.hlsl>

}
}
}
}

#endif
166 changes: 166 additions & 0 deletions include/nbl/builtin/hlsl/sampling/hierarchical_image.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
// 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/concepts/accessors/hierarchical_image.hlsl>
#include <nbl/builtin/hlsl/cpp_compat/intrinsics.hlsl>

namespace nbl
{
namespace hlsl
{
namespace sampling
{

template <typename T, typename LuminanceAccessorT NBL_PRIMARY_REQUIRES(is_scalar_v<T> && hierarchical_image::LuminanceReadAccessor<LuminanceAccessorT>)
struct LuminanceMapSampler
{
using scalar_type = T;
using vector2_type = vector<scalar_type, 2>;
using vector4_type = vector<scalar_type, 4>;

LuminanceAccessorT _map;
uint32_t2 _mapSize;
uint32_t2 _lastWarpPixel;
bool _aspect2x1;

static LuminanceMapSampler<T, LuminanceAccessorT> create(NBL_CONST_REF_ARG(LuminanceAccessorT) lumaMap, uint32_t2 mapSize, bool aspect2x1, uint32_t2 warpSize)
{
LuminanceMapSampler<T, LuminanceAccessorT> result;
result._map = lumaMap;
result._mapSize = mapSize;
result._lastWarpPixel = warpSize - uint32_t2(1, 1);
result._aspect2x1 = aspect2x1;
return result;
}

static bool choseSecond(scalar_type first, scalar_type second, NBL_REF_ARG(float32_t) xi)
{
// numerical resilience against IEEE754
scalar_type dummy = 0.0f;
PartitionRandVariable<scalar_type> partition;
partition.leftProb = 1.0f / (1.0f + second/ first);
return partition(xi, dummy);
}

vector2_type binarySearch(const uint32_t2 coord)
{
float32_t2 xi = float32_t2(coord)/ _lastWarpPixel;
uint32_t2 p = uint32_t2(0, 0);
const uint32_t2 mip2x1 = findMSB(_mapSize.x) - 1;

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

for (uint32_t i = mip2x1; i != 0;)
{
--i;
p <<= 1;
const vector4_type values = _map.gather(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))
{
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))
p.x |= 1;
}

// If we don`t add xi, the sample will clump to the lowest corner of environment map texel. We add xi to simulate uniform distribution within a pixel and make the sample continuous. This is why we compute the pdf not from the normalized luminance of the texel, instead from the reciprocal of the Jacobian.
const vector2_type directionUV = (vector2_type(p.x, p.y) + xi) / vector2_type(_mapSize);
return directionUV;
}

matrix<scalar_type, 4, 2> sampleUvs(uint32_t2 sampleCoord) NBL_CONST_MEMBER_FUNC
{
const vector2_type dir0 = binarySearch(sampleCoord + vector2_type(0, 1));
const vector2_type dir1 = binarySearch(sampleCoord + vector2_type(1, 1));
const vector2_type dir2 = binarySearch(sampleCoord + vector2_type(1, 0));
const vector2_type dir3 = binarySearch(sampleCoord);
return matrix<scalar_type, 4, 2>(
dir0,
dir1,
dir2,
dir3
);
}
};

template <typename T, typename HierarchicalSamplerT, typename PostWarpT NBL_PRIMARY_REQUIRES(is_scalar_v<T> && hierarchical_image::HierarchicalSampler<HierarchicalSamplerT, T> && concepts::Warp<PostWarpT>)
struct HierarchicalImage
{
using scalar_type = T;
using vector2_type = vector<T, 2>;
using vector3_type = vector<T, 3>;
using vector4_type = vector<T, 4>;
HierarchicalSamplerT sampler;
uint32_t2 warpSize;
uint32_t2 lastWarpPixel;

static HierarchicalImage create(NBL_CONST_REF_ARG(HierarchicalSamplerT) sampler, uint32_t2 warpSize)
{
HierarchicalImage<T, HierarchicalSamplerT, PostWarpT> result;
result.sampler = sampler;
result.warpSize = warpSize;
result.lastWarpPixel = warpSize - uint32_t2(1, 1);
return result;
}


uint32_t2 generate(NBL_REF_ARG(scalar_type) rcpPdf, vector2_type xi) NBL_CONST_MEMBER_FUNC
{
const vector2_type texelCoord = xi * lastWarpPixel;
const vector2_type sampleCoord = (texelCoord + vector2_type(0.5f, 0.5f)) / vector2_type(warpSize.x, warpSize.y);

matrix<scalar_type, 4, 2> uvs = sampler.sampleUvs(sampleCoord);

const vector2_type interpolant = frac(texelCoord);

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];
const vector2_type uv = yDiff * interpolant.y + yVals[0];

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

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

rcpPdf = abs((detInterpolJacobian * scalar_type(lastWarpPixel.x * lastWarpPixel.y)) / warpResult.density);

return warpResult.dst;
}
};

}
}
}

#endif
49 changes: 49 additions & 0 deletions include/nbl/builtin/hlsl/sampling/warp.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#ifndef _NBL_BUILTIN_HLSL_SAMPLING_CONCEPTS_WARP_INCLUDED_
#define _NBL_BUILTIN_HLSL_SAMPLING_CONCEPTS_WARP_INCLUDED_


namespace nbl
{
namespace hlsl
{
namespace sampling
{

template <typename CodomainT>
struct WarpResult
{
CodomainT dst;
float32_t density;
};
}

namespace concepts
{

// declare concept
#define NBL_CONCEPT_NAME Warp
#define NBL_CONCEPT_TPLT_PRM_KINDS (typename)
#define NBL_CONCEPT_TPLT_PRM_NAMES (U)
// not the greatest syntax but works
#define NBL_CONCEPT_PARAM_0 (warper,U)
#define NBL_CONCEPT_PARAM_1 (xi,typename U::domain_type)
#define NBL_CONCEPT_PARAM_2 (dst,typename U::codomain_type)
// start concept
NBL_CONCEPT_BEGIN(3)
#define warper NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0
#define xi NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1
#define dst NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2
NBL_CONCEPT_END(
((NBL_CONCEPT_REQ_TYPE)(U::domain_type))
);
#undef dst
#undef xi
#undef warper
#include <nbl/builtin/hlsl/concepts/__end.hlsl>

}

}
}

#endif
73 changes: 73 additions & 0 deletions include/nbl/builtin/hlsl/sampling/warps/spherical.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#ifndef _NBL_BUILTIN_HLSL_SAMPLING_WARP_SPHERICAL_INCLUDED_
#define _NBL_BUILTIN_HLSL_SAMPLING_WARP_SPHERICAL_INCLUDED_

#include <nbl/builtin/hlsl/numbers.hlsl>
#include <nbl/builtin/hlsl/tgmath.hlsl>
#include <nbl/builtin/hlsl/sampling/warp.hlsl>

namespace nbl
{
namespace hlsl
{
namespace sampling
{
namespace warp
{
Comment on lines +14 to +15

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sampling namespace, anything to do with PDFs is sampling


struct Spherical
{
using domain_type = float32_t2;
using codomain_type = float32_t3;

template <typename DomainT NBL_FUNC_REQUIRES(is_same_v<DomainT, domain_type>)
static WarpResult<codomain_type> warp(const DomainT uv)
{
const float32_t phi = 2 * uv.x * numbers::pi<float32_t>;
const float32_t theta = uv.y * numbers::pi<float32_t>;
float32_t3 dir;
dir.x = cos(uv.x * 2.f * numbers::pi<float32_t>);
dir.y = sqrt(1.f - dir.x * dir.x);
if (uv.x > 0.5f) dir.y = -dir.y;
const float32_t cosTheta = cos(theta);
float32_t sinTheta = (1.0 - cosTheta * cosTheta);
dir.xy *= sinTheta;
dir.z = cosTheta;
WarpResult<codomain_type> warpResult;
warpResult.dst = dir;
warpResult.density = 1 / (sinTheta * numbers::pi<float32_t> * numbers::pi<float32_t>);
return warpResult;
}

template <typename CodomainT NBL_FUNC_REQUIRES(is_same_v<CodomainT, codomain_type>)
static domain_type inverseWarp(const CodomainT v)
{
float32_t2 uv = float32_t2(atan(v.y, v.x), acos(v.z));
uv.x *= (numbers::inv_pi<float32_t> * 0.5);
if (v.y < 0.0f)
uv.x += 1.0f;
uv.y *= numbers::inv_pi<float32_t>;
return uv;
}


template <typename DomainT NBL_FUNC_REQUIRES(is_same_v<DomainT, domain_type>)
static float32_t forwardDensity(const DomainT uv)
{
const float32_t theta = uv.y * numbers::pi<float32_t>;
return 1.0f / (sin(theta) * 2 * numbers::pi<float32_t> * numbers::pi<float32_t>);

}

template <typename CodomainT NBL_FUNC_REQUIRES(is_same_v<CodomainT, codomain_type>)
static float32_t backwardDensity(const CodomainT dst)
{
return 1.0f / (sqrt(1.0f - dst.z * dst.z) * 2 * numbers::pi<float32_t> * numbers::pi<float32_t>);
}
};

}
}
}
}

#endif
Loading
Loading