Skip to content

Commit 09a3072

Browse files
committed
better sampling & Light alias table
1 parent a6046e3 commit 09a3072

8 files changed

Lines changed: 203 additions & 111 deletions

File tree

src/app/main.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ void runCli(const CliOptions& options)
114114
session.scene.getRenderSettings().samples = options.samplesPerPixel;
115115
session.scene.getRenderSettings().optixDenoiserEnabled = options.denoiserEnabled;
116116
LOG_INFO("Rendering @ " << options.samplesPerPixel << " spp");
117-
raytracer.renderFrame(PushData{.frame = 0});
117+
raytracer.renderFrame(0, 0);
118118
raytracer.getOutputColor().save(options.outputPath);
119119
raytracer.harvestKernelStats();
120120
LOG_INFO("Saved: " << options.outputPath);

src/libnoorray/Kernels/GaussianHit.cu

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <optix_device.h>
44

55
#include "Raytracing/SceneData.h"
6+
#include "Samplers/OwenSobolSampler.h"
67
#include "Samplers/RandomSampler.h"
78

89
extern "C"
@@ -64,16 +65,11 @@ extern "C" __global__ void __anyhit__gaussian()
6465
// 2: accepted gaussianId (init to InvalidIndex)
6566
const uint32_t sampleIndex = optixGetPayload_0();
6667

67-
// Counter-based PCG hashing keeps every decision deterministic while
68-
// decorrelating Gaussian ID, path, accumulated sample, and bounce. The
69-
// previous shifted-XOR key discarded much of the Gaussian and frame IDs.
70-
const uint32_t sampleCounter = hashCombine32(
71-
params.frame.totalAccumulated, params.depth);
72-
const uint32_t pathKey = hashCombine32(sampleIndex, sampleCounter);
73-
const uint32_t bits = hashCombine32(globalGaussianId, pathKey);
74-
const float xi = (static_cast<float>(bits >> 8u) + 0.5f)
75-
* (1.0f / 16777216.0f);
76-
68+
const uint32_t pathSeed = hashCombine32(sampleIndex, params.depth);
69+
const uint32_t gaussianSeed = hashCombine32(globalGaussianId, 0u);
70+
const OwenSobolSampler sampler({
71+
params.frame.totalAccumulated, hashCombine32(pathSeed, gaussianSeed)});
72+
const float xi = sampler.sample1D(SampleDimension::Opacity);
7773
// alpha <= opacity, so low-opacity rejections avoid evaluating the
7874
// exponential.
7975
if (xi >= opacity)

src/libnoorray/Kernels/Shade.cu

Lines changed: 55 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "Raytracing/Geometry.h"
44
#include "Raytracing/GaussianShading.h"
5+
#include "Raytracing/LightSampling.h"
56
#include "Raytracing/MisHeuristic.h"
67
#include "Raytracing/Queues.h"
78
#include "Raytracing/RgbToSpectrum.h"
@@ -50,66 +51,31 @@ NR_GPU inline glm::vec3 shadeBsdfLobe(
5051
state.packedCounters += 1u << CounterDiffuseShift;
5152
}
5253
state.throughput *= bsdfSample.weight;
53-
state.lastBsdfPdfBits = __float_as_uint(bsdfSample.pdf);
54+
state.lastBsdfPdfBits = __float_as_uint(
55+
bsdf.transmission <= 0.0f ? bsdfSample.pdf : 0.0f);
5456

5557
// Direct light sampling.
5658
{
5759
LightSample lightSample{};
58-
const uint32_t pl = params.scene.pointLightCount;
59-
const uint32_t sl = params.scene.spotLightCount;
60-
const uint32_t rl = params.scene.rectLightCount;
61-
const uint32_t dl = params.scene.directionalLightCount;
6260
const float analyticWeight = analyticLightSelectionWeight(params.scene);
6361
const float environmentWeight = bsdf.transmission <= 0.0f
6462
? fmaxf(params.scene.environment->importanceWeight, 0.0f) : 0.0f;
6563
const float totalWeight = analyticWeight + environmentWeight;
6664
if (totalWeight > 0.0f)
6765
{
68-
float target = randomFloat(lightRng) * totalWeight;
66+
const float target = randomFloat(lightRng) * totalWeight;
6967
float selectedWeight = 0.0f;
7068
bool environmentSelected = false;
7169
float sampledEnvironmentPdf = 0.0f;
72-
for (uint32_t i = 0; i < pl && selectedWeight == 0.0f; ++i) {
73-
const float w = params.scene.pointLights[i].selectionWeight();
74-
if (target < w) {
75-
selectedWeight = w;
76-
lightSample = params.scene.pointLights[i].sampleLi(
77-
position, lightRng, wl,
78-
params.scene.spectrumTableScale, params.scene.spectrumTableCoeffs,
79-
params.scene.d65);
80-
} else target -= w;
81-
}
82-
for (uint32_t i = 0; i < sl && selectedWeight == 0.0f; ++i) {
83-
const float w = params.scene.spotLights[i].selectionWeight();
84-
if (target < w) {
85-
selectedWeight = w;
86-
lightSample = params.scene.spotLights[i].sampleLi(
87-
position, lightRng, wl,
88-
params.scene.spectrumTableScale, params.scene.spectrumTableCoeffs,
89-
params.scene.d65);
90-
} else target -= w;
91-
}
92-
for (uint32_t i = 0; i < rl && selectedWeight == 0.0f; ++i) {
93-
const float w = params.scene.rectLights[i].selectionWeight();
94-
if (target < w) {
95-
selectedWeight = w;
96-
lightSample = params.scene.rectLights[i].sampleLi(
97-
position, lightRng, wl,
98-
params.scene.spectrumTableScale, params.scene.spectrumTableCoeffs,
99-
params.scene.d65);
100-
} else target -= w;
101-
}
102-
for (uint32_t i = 0; i < dl && selectedWeight == 0.0f; ++i) {
103-
const float w = params.scene.directionalLights[i].selectionWeight();
104-
if (target < w) {
105-
selectedWeight = w;
106-
lightSample = params.scene.directionalLights[i].sampleLi(
107-
position, lightRng, wl,
108-
params.scene.spectrumTableScale, params.scene.spectrumTableCoeffs,
109-
params.scene.d65);
110-
} else target -= w;
70+
if (target < analyticWeight && analyticWeight > 0.0f)
71+
{
72+
const AnalyticLightSample analytic = sampleAnalyticLight(
73+
params.scene, position, lightRng, wl);
74+
selectedWeight = analytic.selectionPdf * analyticWeight;
75+
lightSample = analytic.light;
11176
}
112-
if (selectedWeight == 0.0f && environmentWeight > 0.0f) {
77+
else if (environmentWeight > 0.0f)
78+
{
11379
const EnvironmentSample environmentSample =
11480
params.scene.environment->sampleDirection(lightRng);
11581
if (environmentSample.pdf > 0.0f) {
@@ -166,8 +132,8 @@ NR_GPU_KERNEL void shadeKernel(const KernelParams params)
166132
params.scene.renderSettings.gaussianShadingMode == GaussianShadingMode::DirectColor;
167133
const bool mayWriteShadowQueue = params.scene.meshInstanceCount > 0 ||
168134
(!gaussianDirectColor &&
169-
(params.scene.pointLightCount > 0 || params.scene.spotLightCount > 0 ||
170-
params.scene.rectLightCount > 0 || params.scene.directionalLightCount > 0));
135+
(params.scene.analyticLightAliasCount > 0
136+
|| params.scene.environment->importanceWeight > 0.0f));
171137
bool continuePath = false;
172138
PathRayWorkItem continuation{};
173139
if (inRange)
@@ -246,63 +212,61 @@ NR_GPU_KERNEL void shadeKernel(const KernelParams params)
246212
// ── NEE: sample analytic lights ────────────────────────
247213
{
248214
LightSample lightSample{};
249-
const uint32_t pl = params.scene.pointLightCount;
250-
const uint32_t sl = params.scene.spotLightCount;
251-
const uint32_t rl = params.scene.rectLightCount;
252-
const uint32_t dl = params.scene.directionalLightCount;
253215
const float analyticWeight = analyticLightSelectionWeight(params.scene);
254-
const float environmentWeight = 0.0f; // env sampled by scattered ray
216+
const float environmentWeight = fmaxf(
217+
params.scene.environment->importanceWeight, 0.0f);
255218
const float totalWeight = analyticWeight + environmentWeight;
256219
if (totalWeight > 0.0f)
257220
{
258-
float target = randomFloat(lightRng) * totalWeight;
221+
const float target = randomFloat(lightRng) * totalWeight;
259222
float selectedWeight = 0.0f;
260-
for (uint32_t i = 0; i < pl && selectedWeight == 0.0f; ++i) {
261-
const float w = params.scene.pointLights[i].selectionWeight();
262-
if (target < w) { selectedWeight = w;
263-
lightSample = params.scene.pointLights[i].sampleLi(
264-
gaussianPos, lightRng, wl,
265-
params.scene.spectrumTableScale, params.scene.spectrumTableCoeffs,
266-
params.scene.d65); } else target -= w;
267-
}
268-
for (uint32_t i = 0; i < sl && selectedWeight == 0.0f; ++i) {
269-
const float w = params.scene.spotLights[i].selectionWeight();
270-
if (target < w) { selectedWeight = w;
271-
lightSample = params.scene.spotLights[i].sampleLi(
272-
gaussianPos, lightRng, wl,
273-
params.scene.spectrumTableScale, params.scene.spectrumTableCoeffs,
274-
params.scene.d65); } else target -= w;
275-
}
276-
for (uint32_t i = 0; i < rl && selectedWeight == 0.0f; ++i) {
277-
const float w = params.scene.rectLights[i].selectionWeight();
278-
if (target < w) { selectedWeight = w;
279-
lightSample = params.scene.rectLights[i].sampleLi(
280-
gaussianPos, lightRng, wl,
281-
params.scene.spectrumTableScale, params.scene.spectrumTableCoeffs,
282-
params.scene.d65); } else target -= w;
223+
bool environmentSelected = false;
224+
float sampledEnvironmentPdf = 0.0f;
225+
if (target < analyticWeight && analyticWeight > 0.0f)
226+
{
227+
const AnalyticLightSample analytic = sampleAnalyticLight(
228+
params.scene, gaussianPos, lightRng, wl);
229+
selectedWeight = analytic.selectionPdf * analyticWeight;
230+
lightSample = analytic.light;
283231
}
284-
for (uint32_t i = 0; i < dl && selectedWeight == 0.0f; ++i) {
285-
const float w = params.scene.directionalLights[i].selectionWeight();
286-
if (target < w) { selectedWeight = w;
287-
lightSample = params.scene.directionalLights[i].sampleLi(
288-
gaussianPos, lightRng, wl,
289-
params.scene.spectrumTableScale, params.scene.spectrumTableCoeffs,
290-
params.scene.d65); } else target -= w;
232+
else if (environmentWeight > 0.0f)
233+
{
234+
const EnvironmentSample environmentSample =
235+
params.scene.environment->sampleDirection(lightRng);
236+
if (environmentSample.pdf > 0.0f)
237+
{
238+
selectedWeight = environmentWeight;
239+
environmentSelected = true;
240+
sampledEnvironmentPdf = environmentSample.pdf;
241+
lightSample.direction = environmentSample.direction;
242+
lightSample.distance = 1e16f;
243+
lightSample.radiance = params.scene.environment->radiance(
244+
params.scene.textures, params.scene.textureCount,
245+
environmentSample.direction, false, wl,
246+
params.scene.spectrumTableScale,
247+
params.scene.spectrumTableCoeffs, params.scene.d65);
248+
}
291249
}
292250
if (selectedWeight > 0.0f && lightSample.radiance.maxComponent() > 0.0f)
293251
{
294252
const float selectionPdf = selectedWeight / totalWeight;
295253
const SampledSpectrum brdf = albedo * inv4Pi;
254+
if (environmentSelected)
255+
{
256+
const float lightPdf = selectionPdf * sampledEnvironmentPdf;
257+
lightSample.radiance *= powerHeuristic(lightPdf, inv4Pi)
258+
/ fmaxf(lightPdf, 1e-20f);
259+
}
260+
else
261+
{
262+
lightSample.radiance *= 1.0f / selectionPdf;
263+
}
296264
shadow.origin = gaussianPos + lightSample.direction * 0.001f;
297265
shadow.direction = lightSample.direction;
298266
shadow.tMin = 0.001f;
299267
shadow.tMax = lightSample.distance - 0.002f;
300-
// Analytic lights are only reachable through NEE: they are
301-
// not emissive geometry that the scattered ray can hit.
302-
// Therefore there is no competing sampling technique and
303-
// no MIS term. Compensate only for selecting one light.
304-
shadow.contribution = state.throughput * brdf * lightSample.radiance
305-
/ fmaxf(selectionPdf, 1e-20f);
268+
shadow.contribution =
269+
state.throughput * brdf * lightSample.radiance;
306270
shadow.rngState = shadowRng;
307271
shadow.sampleIndex = hit.sampleIndex;
308272
params.queues.shadowQueue[index] = shadow;
@@ -311,9 +275,7 @@ NR_GPU_KERNEL void shadeKernel(const KernelParams params)
311275
}
312276

313277
state.throughput *= albedo;
314-
// Gaussian scattering does not perform environment NEE. A zero PDF
315-
// tells the miss path not to MIS-downweight its environment sample.
316-
state.lastBsdfPdfBits = __float_as_uint(0.0f);
278+
state.lastBsdfPdfBits = __float_as_uint(inv4Pi);
317279

318280
state.depth++;
319281
continuePath = true;
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#pragma once
2+
3+
#include <cstdint>
4+
5+
#include "Raytracing/SceneData.h"
6+
#include "Samplers/RandomSampler.h"
7+
8+
struct AnalyticLightAliasEntry
9+
{
10+
float threshold{};
11+
uint32_t alias{};
12+
float selectionPdf{};
13+
};
14+
15+
struct AnalyticLightSample
16+
{
17+
LightSample light{};
18+
float selectionPdf{};
19+
};
20+
21+
#if defined(NR_GPU_CODE)
22+
NR_GPU inline AnalyticLightSample sampleAnalyticLight(
23+
const GpuSceneData& scene,
24+
const glm::vec3 position,
25+
RandomState& rng,
26+
const SampledWavelengths& wl)
27+
{
28+
AnalyticLightSample result{};
29+
if (scene.analyticLightAliasCount == 0 || scene.analyticLightAliases == nullptr)
30+
return result;
31+
32+
const float tableSample = randomFloat(rng) * scene.analyticLightAliasCount;
33+
const uint32_t column = min(
34+
static_cast<uint32_t>(tableSample), scene.analyticLightAliasCount - 1);
35+
const AnalyticLightAliasEntry columnEntry = scene.analyticLightAliases[column];
36+
const uint32_t selected = tableSample - static_cast<float>(column)
37+
< columnEntry.threshold
38+
? column
39+
: columnEntry.alias;
40+
const AnalyticLightAliasEntry selectedEntry = scene.analyticLightAliases[selected];
41+
result.selectionPdf = selectedEntry.selectionPdf;
42+
43+
uint32_t localIndex = selected;
44+
if (localIndex < scene.pointLightCount)
45+
{
46+
result.light = scene.pointLights[localIndex].sampleLi(
47+
position, rng, wl, scene.spectrumTableScale,
48+
scene.spectrumTableCoeffs, scene.d65);
49+
return result;
50+
}
51+
localIndex -= scene.pointLightCount;
52+
if (localIndex < scene.spotLightCount)
53+
{
54+
result.light = scene.spotLights[localIndex].sampleLi(
55+
position, rng, wl, scene.spectrumTableScale,
56+
scene.spectrumTableCoeffs, scene.d65);
57+
return result;
58+
}
59+
localIndex -= scene.spotLightCount;
60+
if (localIndex < scene.rectLightCount)
61+
{
62+
result.light = scene.rectLights[localIndex].sampleLi(
63+
position, rng, wl, scene.spectrumTableScale,
64+
scene.spectrumTableCoeffs, scene.d65);
65+
return result;
66+
}
67+
localIndex -= scene.rectLightCount;
68+
if (localIndex < scene.directionalLightCount)
69+
{
70+
result.light = scene.directionalLights[localIndex].sampleLi(
71+
position, rng, wl, scene.spectrumTableScale,
72+
scene.spectrumTableCoeffs, scene.d65);
73+
}
74+
return result;
75+
}
76+
#endif

0 commit comments

Comments
 (0)