Skip to content

Commit 9b29dfd

Browse files
author
kevyuu
committed
Hierarchical image sampling implementation
1 parent e44fcf4 commit 9b29dfd

File tree

1 file changed

+136
-0
lines changed

1 file changed

+136
-0
lines changed
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// Copyright (C) 2018-2025 - DevSH Graphics Programming Sp. z O.O.
2+
// This file is part of the "Nabla Engine".
3+
// For conditions of distribution and use, see copyright notice in nabla.h
4+
5+
#ifndef _NBL_BUILTIN_HLSL_SAMPLING_HIERARCHICAL_IMAGE_INCLUDED_
6+
#define _NBL_BUILTIN_HLSL_SAMPLING_HIERARCHICAL_IMAGE_INCLUDED_
7+
8+
#include <nbl/builtin/hlsl/concepts/warp.hlsl>
9+
#include <nbl/builtin/hlsl/concepts/accessors/hierarchical_image.hlsl>
10+
11+
namespace nbl
12+
{
13+
namespace hlsl
14+
{
15+
namespace sampling
16+
{
17+
18+
class HierarchicalImage
19+
{
20+
private:
21+
22+
static float32_t3 calculateSampleAndPdf(float32_t4 dirsX, float32_t4 dirsY, float32_t2 unnormCoord, uint32_t2 lastWarpmapPixel, NBL_REF_ARG(float32_t) pdf)
23+
{
24+
const float32_t2 interpolant = frac(unnormCoord);
25+
const float32_t4x2 uvs = transpose(float32_t2x4(dirsX, dirsY));
26+
27+
const float32_t2 xDiffs[] = {
28+
uvs[2] - uvs[3],
29+
uvs[1] - uvs[0]
30+
};
31+
const float32_t2 yVals[] = {
32+
xDiffs[0] * interpolant.x + uvs[3],
33+
xDiffs[1] * interpolant.x + uvs[0]
34+
};
35+
const float32_t2 yDiff = yVals[1] - yVals[0];
36+
const float32_t2 uv = yDiff * interpolant.y + yVals[0];
37+
38+
// Note(kevinyu): sinTheta is calculated twice inside PostWarp::warp and PostWarp::forwardDensity
39+
const float32_t3 L = PostWarp::warp(uv);
40+
41+
const float detInterpolJacobian = determinant(float32_t2x2(
42+
lerp(xDiffs[0], xDiffs[1], interpolant.y), // first column dFdx
43+
yDiff // second column dFdy
44+
));
45+
46+
pdf = abs(PostWarp::forwardDensity(uv) / (detInterpolJacobian * float32_t(lastWarpmapPixel.x * lastWarpmapPixel.y));
47+
48+
return L;
49+
}
50+
51+
public:
52+
template <typename LuminanceAccessor NBL_FUNC_REQUIRES (hierarchical_image::LuminanceReadAccessor<LuminanceAccessor>)
53+
static float32_t2 binarySearch(NBL_CONST_REF_ARG(LuminanceAccessor) luminanceAccessor, const uint32_t2 lumaMapSize, const float32_t2 xi, const bool aspect2x1)
54+
{
55+
56+
uint32_t2 p = uint32_t2(0, 0);
57+
58+
if (aspect2x1) {
59+
// TODO(kevinyu): Implement findMSB
60+
const uint32_t2 mip2x1 = findMSB(lumaMapSize.x) - 1;
61+
62+
// do one split in the X axis first cause penultimate full mip would have been 2x1
63+
p.x = impl::choseSecond(luminanceAccessor.fetch(uint32_t2(0, 0), mip2x1), luminanceAccessor.fetch(uint32_t2(0, 1), mip2x1), xi.x) ? 1 : 0;
64+
}
65+
66+
for (uint32_t i = mip2x1; i != 0;)
67+
{
68+
--i;
69+
p <<= 1;
70+
const float32_t4 values = luminanceAccessor.gather(p, i);
71+
float32_t wx_0, wx_1;
72+
{
73+
const float32_t wy_0 = values[3] + values[2];
74+
const float32_t wy_1 = values[1] + values[0];
75+
if (impl::choseSecond(wy_0, wy_1, xi.y))
76+
{
77+
p.y |= 1;
78+
wx_0 = values[0];
79+
wx_1 = values[1];
80+
}
81+
else
82+
{
83+
wx_0 = values[3];
84+
wx_1 = values[2];
85+
}
86+
}
87+
88+
if (impl::choseSecond(wx_0, wx_1, xi.x))
89+
p.x |= 1;
90+
}
91+
92+
// TODO(kevinyu): Add some comment why we add xi.
93+
const float32_t2 directionUV = (float32_t2(p.x, p.y) + xi) / float32_t2(lumaMapSize);
94+
return directionUV;
95+
}
96+
97+
98+
template <typename WarpmapAccessor, typename PostWarp NBL_FUNC_REQUIRES(hierarchical_image::WarpmapReadAccessor<WarpmapAccessor>&& Warp<PostWarp, float32_t3>)
99+
static float32_t3 sampleWarpmap(NBL_CONST_REF_ARG(WarpmapAccessor) warpmap, const uint32_t2 warpmapSize, const float32_t2 xi, NBL_REF_ARG(float32_t) pdf) {
100+
101+
// TODO(kevinyu): Add some comment why we substract by 1
102+
const uint32_t3 lastWarpmapPixel = warpmapSize - uint32_t3(1, 1, 1);
103+
104+
const float32_t2 unnormCoord = xi * lastWarpmapPixel;
105+
const float32_t2 interpolant = frac(unnormCoord);
106+
const float32_t2 warpSampleCoord = (unnormCoord + float32_t2(0.5f, 0.5f)) / float32_t2(warpmapSize.x, warpmapSize.y);
107+
const float32_t4 dirsX = warpmap.gatherU(warpSampleCoord);
108+
const float32_t4 dirsY = warpmap.gatherV(warpSampleCoord);
109+
110+
return calculateSampleAndPdf(dirsX, dirsY, unnormCoord, lastWarpmapPixel, pdf);
111+
112+
}
113+
114+
template <typename LuminanceAccessor, typename PostWarp NBL_FUNC_REQUIRES(hierarchical_image::LuminanceReadAccessor<LuminanceAccessor>&& Warp<PostWarp, float32_t3>)
115+
static float32_t3 sample(NBL_CONST_REF_ARG(LuminanceReadAccessor) luminanceMap, const uint32_t2 lumaMapSize, const bool lumaAspect2x1, const uint32_t2 warpmapSize, const float32_t2 xi, NBL_REF_ARG(float32_t) pdf) {
116+
117+
const uint32_t3 lastWarpmapPixel = warpmapSize - uint32_t3(1, 1, 1);
118+
const float32_t2 unnormCoord = xi * lastWarpmapPixel;
119+
const float32_t2 warpSampleCoord = (unnormCoord + float32_t2(0.5f, 0.5f)) / float32_t2(warpmapSize.x, warpmapSize.y);
120+
const float32_t2 dir0 = binarySearch(luminanceMap, lumaMapSize, warpSampleCoord + float32_t2(0, 1), lumaAspect2x1);
121+
const float32_t2 dir1 = binarySearch(luminanceMap, lumaMapSize, warpSampleCoord + float32_t2(1, 1), lumaAspect2x1);
122+
const float32_t2 dir2 = binarySearch(luminanceMap, lumaMapSize, warpSampleCoord + float32_t2(1, 0), lumaAspect2x1);
123+
const float32_t2 dir3 = binarySearch(luminanceMap, lumaMapSize, warpSampleCoord, lumaAspect2x1);
124+
125+
const float32_t4 dirsX = float32_t4(dir0.x, dir1.x, dir2.x, dir3.x);
126+
const float32_t4 dirsY = float32_t4(dir1.y, dir1.y, dir2.y, dir3.y);
127+
128+
return calculateSampleAndPdf(dirsX, dirsY, unnormCoord, lastWarpmapPixel, pdf);
129+
130+
}
131+
};
132+
133+
}
134+
}
135+
136+
#endif

0 commit comments

Comments
 (0)