Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 devices/rtx/device/gpu/gpu_objects.h
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,7 @@ struct InteractiveRendererGPUData
vec3 aoColor;
float aoIntensity;
float inverseVolumeSamplingRateShadows;
int maxSampledLights; // NEE shadow-ray budget per hit; <= 0 samples all lights
};

union RendererParametersGPUData
Expand Down
4 changes: 4 additions & 0 deletions devices/rtx/device/renderer/Interactive.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ void Interactive::commitParameters()
m_aoSamples = std::clamp(getParam<int>("ambientSamples", 1), 0, 256);
m_volumeSamplingRateShadows = std::clamp(
getParam<float>("volumeSamplingRateShadows", 0.0125f), 1e-4f, 10.f);
// <= 0 is the "sample all lights" sentinel; clamp negatives to 0 so the kernel
// sees a single unambiguous unlimited value.
m_maxSampledLights = std::max(getParam<int>("maxSampledLights", 8), 0);
}

void Interactive::populateFrameData(FrameGPUData &fd) const
Expand All @@ -65,6 +68,7 @@ void Interactive::populateFrameData(FrameGPUData &fd) const
interactive.aoIntensity = m_aoIntensity;
interactive.inverseVolumeSamplingRateShadows =
1.f / m_volumeSamplingRateShadows;
interactive.maxSampledLights = m_maxSampledLights;
}

OptixModule Interactive::optixModule() const
Expand Down
1 change: 1 addition & 0 deletions devices/rtx/device/renderer/Interactive.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ struct Interactive : public Renderer
vec3 m_aoColor{1.f};
float m_aoIntensity{1.f};
float m_volumeSamplingRateShadows{0.0125f};
int m_maxSampledLights{8};
};

} // namespace visrtx
53 changes: 47 additions & 6 deletions devices/rtx/device/renderer/Interactive_ptx.cu
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,11 @@ struct InteractiveShadingPolicy
* rendererParams.ambientIntensity * materialEvaluateTint(shadingState);

const vec3 shadowOrigin = shadingHitpoint(hit) + hit.Ng * hit.epsilon;
for (size_t i = 0; i < world.numLightInstances; i++) {

// One light instance's NEE contribution, scaled by `weight`. `weight` is 1
// when every light is sampled and 1/(K*pPick) when a stochastic subset is
// drawn, so the accumulated image converges to the full deterministic sum.
auto addLightContribution = [&](size_t i, float weight) {
const auto &light = world.lightInstances[i];
const auto lightSample = sampleLight(ss,
shadowOrigin,
Expand All @@ -109,7 +113,7 @@ struct InteractiveShadingPolicy
light.surfaceInstanceIndex);

if (lightSample.pdf == 0.0f)
continue;
return;

const LightType lightType =
frameData.registry.lights[light.lightIndex].type;
Expand All @@ -134,24 +138,61 @@ struct InteractiveShadingPolicy

if (glm::all(
glm::lessThanEqual(attenuation, vec3(MIN_CONTRIBUTION_EPSILON))))
continue;
return;

vec3 thisLightContrib =
materialShadeSurface(shadingState, hit, lightSample, -ray.dir);

// Environment MIS (balance heuristic): the HDRI is the only light the
// indirect bounce's escape can also reach, so combine the NEE and escape
// estimators instead of summing them (which double-counted the env).
// Interactive loops all lights with no pick, so pLight = envPdf (NO
// 1/numLights). Non-env lights keep wNee = 1 (behaviour unchanged).
// pLight = envPdf independent of the pick, and the 1/(K*pPick) reweight
// keeps E[stochastic] == the all-lights sum, so this stays MIS-consistent
// whether we sample all lights or a subset. Non-env lights keep wNee = 1.
if (lightType == LightType::HDRI) {
const float pLight = envPdf(frameData, lightSample.dir);
const float pBsdf =
materialEvalPdf(shadingState, -ray.dir, lightSample.dir);
thisLightContrib *= pLight / (pLight + pBsdf);
}

contrib += thisLightContrib * attenuation;
contrib += weight * thisLightContrib * attenuation;
};

// `maxSampledLights` caps the shadow-ray budget per hit. When the scene has
// more light instances than the cap, importance-sample that many via the
// world's Pick-Power CDF instead of looping all of them; otherwise keep the
// exact all-lights sum (pixel-identical to an unlimited budget).
const size_t numLights = world.numLightInstances;
const int maxSampled = interactiveParams.maxSampledLights;
const bool sampleAllLights =
maxSampled <= 0 || numLights <= size_t(maxSampled);

if (sampleAllLights) {
for (size_t i = 0; i < numLights; i++)
addLightContribution(i, 1.0f);
} else {
const int numPicks = maxSampled;
// A zero total Pick Power (every light dark) leaves the CDF unnormalized;
// fall back to a uniform pick to avoid a divide-by-zero, matching Quality.
const bool haveCdf = world.totalLightPower > 0.0f;
for (int s = 0; s < numPicks; s++) {
const float u = pcg_uniform(&ss.rs);
size_t idx;
float pPick;
if (haveCdf) {
idx = glm::min(size_t(detail::inverseSampleCDF(
world.lightPickCdf, int(numLights), u)),
numLights - 1);
const float lo = idx > 0 ? world.lightPickCdf[idx - 1] : 0.0f;
pPick = world.lightPickCdf[idx] - lo;
} else {
idx = glm::min(size_t(u * float(numLights)), numLights - 1);
pPick = 1.0f / float(numLights);
}
if (pPick > 0.0f)
addLightContribution(idx, 1.0f / (float(numPicks) * pPick));
}
}

// Single indirect bounce — REFLECTION only. Transmission/refraction is
Expand Down
10 changes: 10 additions & 0 deletions devices/rtx/device/visrtx_renderer_interactive.json
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,16 @@
"minimum": 0,
"description": "ambient occlusion distance"
},
{
"name": "maxSampledLights",
"types": [
"ANARI_INT32"
],
"tags": [],
"default": 8,
"minimum": 0,
"description": "max light instances sampled per hit via the Pick-Power CDF; 0 samples all lights (slow with many lights)"
},
{
"name": "lightFalloff",
"types": [
Expand Down
Loading