Skip to content

Commit 34ca630

Browse files
bring back the rectangle area light
1 parent 5819d39 commit 34ca630

File tree

4 files changed

+62
-240
lines changed

4 files changed

+62
-240
lines changed

examples_tests/42.FragmentShaderPathTracer/common.glsl

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
// basic settings
66
#define MAX_DEPTH 15
7-
#define SAMPLES 64
7+
#define SAMPLES 32
88

99
// firefly and variance reduction techniques
1010
//#define KILL_DIFFUSE_SPECULAR_PATHS
@@ -242,14 +242,10 @@ vec3 BSDFNode_getReflectance(in BSDFNode node, in float VdotH)
242242
return albedoOrRealIoR;
243243
}
244244

245-
float BSDFNode_getNEESkipProb(in BSDFNode bsdf)
245+
float BSDFNode_getNEEProb(in BSDFNode bsdf)
246246
{
247-
if (BSDFNode_isNotDiffuse(bsdf))
248-
{
249-
const float alpha = BSDFNode_getRoughness(bsdf);
250-
return alpha*alpha;
251-
}
252-
return 0.0;
247+
const float alpha = BSDFNode_isNotDiffuse(bsdf) ? BSDFNode_getRoughness(bsdf):1.0;
248+
return min(8.0*alpha,1.0);
253249
}
254250

255251
#include <nbl/builtin/glsl/colorspace/EOTF.glsl>
@@ -446,9 +442,10 @@ vec3 nbl_glsl_bsdf_cos_remainder_and_pdf(out float pdf, in nbl_glsl_LightSample
446442
const float VdotL = dot(interaction.isotropic.V.dir,_sample.L);
447443

448444
//
449-
const float a = max(BSDFNode_getRoughness(bsdf),0.01); // TODO: @Crisspl 0-roughness still doesn't work! Also Beckmann has a weird dark rim instead as fresnel!?
445+
const float a = max(BSDFNode_getRoughness(bsdf),0.0001); // TODO: @Crisspl 0-roughness still doesn't work! Also Beckmann has a weird dark rim instead as fresnel!?
450446
const float a2 = a*a;
451447

448+
// TODO: refactor into Material Compiler-esque thing
452449
switch (BSDFNode_getType(bsdf))
453450
{
454451
case DIFFUSE_OP:
@@ -594,9 +591,9 @@ bool closestHitProgram(in uint depth, in uint _sample, inout Ray_t ray, inout nb
594591
const float monochromeEta = dot(throughputCIE_Y,BSDFNode_getEta(bsdf)[0])/(throughputCIE_Y.r+throughputCIE_Y.g+throughputCIE_Y.b);
595592

596593
// do NEE
597-
const float neeSkipProbability = BSDFNode_getNEESkipProb(bsdf);
594+
const float neeProbability = BSDFNode_getNEEProb(bsdf);
598595
float rcpChoiceProb;
599-
if (nbl_glsl_partitionRandVariable(neeSkipProbability, epsilon[0].z, rcpChoiceProb))
596+
if (!nbl_glsl_partitionRandVariable(neeProbability,epsilon[0].z,rcpChoiceProb))
600597
{
601598
vec3 neeContrib; float lightPdf, t;
602599
nbl_glsl_LightSample nee_sample = nbl_glsl_light_generate_and_remainder_and_pdf(
@@ -636,7 +633,7 @@ bool closestHitProgram(in uint depth, in uint _sample, inout Ray_t ray, inout nb
636633
if (bsdfPdf>bsdfPdfThreshold && getLuma(throughput)>lumaThroughputThreshold)
637634
{
638635
ray._payload.throughput = throughput;
639-
ray._payload.otherTechniqueHeuristic = (1.0-neeSkipProbability)/bsdfPdf; // numerically stable, don't touch
636+
ray._payload.otherTechniqueHeuristic = neeProbability/bsdfPdf; // numerically stable, don't touch
640637
ray._payload.otherTechniqueHeuristic *= ray._payload.otherTechniqueHeuristic;
641638

642639
// trace new ray
Lines changed: 51 additions & 226 deletions
Original file line numberDiff line numberDiff line change
@@ -1,280 +1,105 @@
1+
// Copyright (C) 2018-2020 - 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+
15
#version 430 core
26
#extension GL_GOOGLE_include_directive : require
37

48
#define SPHERE_COUNT 8
5-
#define RECTANGLE_METHOD 0 // 0 area sampling, 1 solid angle sampling, 2 approximate projected solid angle sampling
9+
#define POLYGON_METHOD 0 // 0 area sampling, 1 solid angle sampling, 2 approximate projected solid angle sampling
610
#include "common.glsl"
711

12+
813
#define RECTANGLE_COUNT 1
14+
const vec3 edge0 = normalize(vec3(2,0,-1));
15+
const vec3 edge1 = normalize(vec3(2,-5,4));
916
Rectangle rectangles[RECTANGLE_COUNT] = {
10-
Rectangle_Rectangle(vec3(-1.8,0.35,0.3),vec3(0.6,0.0,-0.3),vec3(0.3,0.45,-0.6),INVALID_ID_16BIT,0u)
17+
Rectangle_Rectangle(vec3(-3.8,0.35,1.3),edge0*7.0,edge1*0.1,INVALID_ID_16BIT,0u)
1118
};
1219

1320

14-
bool traceRay(in ImmutableRay_t _immutable)
21+
void traceRay_extraShape(inout int objectID, inout float intersectionT, in vec3 origin, in vec3 direction)
1522
{
16-
const bool anyHit = bitfieldExtract(_immutable.typeDepthSampleIx,31,1)!=0;
17-
18-
int objectID = -1;
19-
float intersectionT = _immutable.maxT;
20-
for (int i=0; i<SPHERE_COUNT; i++)
21-
{
22-
float t = Sphere_intersect(spheres[i],_immutable.origin,_immutable.direction);
23-
bool closerIntersection = t>0.0 && t<intersectionT;
24-
25-
objectID = closerIntersection ? i:objectID;
26-
intersectionT = closerIntersection ? t:intersectionT;
27-
28-
// allowing early out results in a performance regression, WTF!?
29-
//if (anyHit && closerIntersection && anyHitProgram(_immutable))
30-
//break;
31-
}
3223
for (int i=0; i<RECTANGLE_COUNT; i++)
3324
{
34-
float t = Rectangle_intersect(rectangles[i],_immutable.origin,_immutable.direction);
25+
float t = Rectangle_intersect(rectangles[i],origin,direction);
3526
bool closerIntersection = t>0.0 && t<intersectionT;
3627

3728
objectID = closerIntersection ? (i+SPHERE_COUNT):objectID;
3829
intersectionT = closerIntersection ? t:intersectionT;
39-
40-
// allowing early out results in a performance regression, WTF!?
41-
//if (anyHit && closerIntersection && anyHitProgram(_immutable))
42-
//break;
4330
}
44-
rayStack[stackPtr]._mutable.objectID = objectID;
45-
rayStack[stackPtr]._mutable.intersectionT = intersectionT;
46-
// hit
47-
return anyHit;
4831
}
4932

50-
5133
/// #include <nbl/builtin/glsl/sampling/projected_spherical_rectangle.glsl>
52-
53-
54-
// the interaction here is the interaction at the illuminator-end of the ray, not the receiver
55-
vec3 nbl_glsl_light_deferred_eval_and_prob(
56-
out float pdf, in Light light, in vec3 L
57-
#if RECTANGLE_METHOD==0
58-
,in float intersectionT
59-
#else
60-
,in vec3 origin
61-
#if RECTANGLE_METHOD==2
62-
,in vec3 normalAtOrigin, in bool wasBSDFAtOrigin
63-
#endif
64-
#endif
65-
)
34+
float nbl_glsl_light_deferred_pdf(in Light light, in Ray_t ray)
6635
{
67-
// we don't have to worry about solid angle of the light w.r.t. surface of the light because this function only ever gets called from closestHit routine, so such ray cannot be produced
68-
pdf = scene_getLightChoicePdf(light);
36+
const Rectangle rect = rectangles[Light_getObjectID(light)];
6937

70-
Rectangle rect = rectangles[Light_getObjectID(light)];
71-
#if RECTANGLE_METHOD==0
72-
pdf *= intersectionT*intersectionT/abs(dot(Rectangle_getNormalTimesArea(rect),L));
38+
const vec3 L = ray._immutable.direction;
39+
#if POLYGON_METHOD==0
40+
const float dist = ray._mutable.intersectionT;
41+
return dist*dist/abs(dot(Rectangle_getNormalTimesArea(rect),L));
7342
#else
74-
const mat3 sphericalVertices = nbl_glsl_shapes_getSphericalTriangle(mat3(tri.vertex0,tri.vertex1,tri.vertex2),origin);
75-
Triangle tmpTri = Triangle_Triangle(mat3(tri.vertex0,tri.vertex1,tri.vertex2),0u,0u);
76-
#if RECTANGLE_METHOD==1
77-
float rcpProb = nbl_glsl_shapes_SolidAngleOfTriangle(sphericalVertices);
43+
const ImmutableRay_t _immutable = ray._immutable;
44+
const mat3 sphericalVertices = nbl_glsl_shapes_getSphericalTriangle(mat3(tri.vertex0,tri.vertex1,tri.vertex2),_immutable.origin);
45+
#if POLYGON_METHOD==1
46+
const float rcpProb = nbl_glsl_shapes_SolidAngleOfTriangle(sphericalVertices);
7847
// if `rcpProb` is NAN then the triangle's solid angle was close to 0.0
79-
pdf = rcpProb>FLT_MIN ? (pdf/rcpProb):FLT_MAX;
80-
#elif RECTANGLE_METHOD==2
81-
pdf *= nbl_glsl_sampling_probProjectedSphericalTriangleSample(sphericalVertices,normalAtOrigin,wasBSDFAtOrigin,L);
48+
return rcpProb>FLT_MIN ? (1.0/rcpProb):FLT_MAX;
49+
#elif POLYGON_METHOD==2
50+
const float pdf = nbl_glsl_sampling_probProjectedSphericalTriangleSample(sphericalVertices,_immutable.normalAtOrigin,_immutable.wasBSDFAtOrigin,L);
8251
// if `pdf` is NAN then the triangle's projected solid angle was close to 0.0, if its close to INF then the triangle was very small
83-
pdf = pdf<FLT_MAX ? pdf:0.0;
52+
return pdf<FLT_MAX ? pdf:0.0;
8453
#endif
8554
#endif
86-
return Light_getRadiance(light);
8755
}
8856

89-
90-
nbl_glsl_LightSample nbl_glsl_light_generate_and_remainder_and_pdf(out vec3 remainder, out float pdf, out float newRayMaxT, in vec3 origin, in nbl_glsl_AnisotropicViewSurfaceInteraction interaction, in bool isBSDF, in vec3 u, in int depth)
57+
vec3 nbl_glsl_light_generate_and_pdf(out float pdf, out float newRayMaxT, in vec3 origin, in nbl_glsl_AnisotropicViewSurfaceInteraction interaction, in bool isBSDF, in vec3 xi, in uint objectID)
9158
{
92-
// normally we'd pick from set of lights, using `u.z`
93-
const Light light = lights[0];
94-
const float choicePdf = scene_getLightChoicePdf(light);
95-
96-
const Rectangle rect = rectangles[Light_getObjectID(light)];
59+
const Rectangle rect = rectangles[objectID];
9760

98-
#if RECTANGLE_METHOD==0
99-
const vec3 point = rect.offset+rect.edge0*u.x+rect.edge1*u.y;
100-
vec3 L = point-origin;
61+
#if POLYGON_METHOD==0
62+
const vec3 point = rect.offset+rect.edge0*xi.x+rect.edge1*xi.y; // TODO: refactor
63+
const vec3 L = point-origin;
10164

10265
const float distanceSq = dot(L,L);
10366
const float rcpDistance = inversesqrt(distanceSq);
104-
L *= rcpDistance;
105-
106-
const float dist = 1.0/rcpDistance;
10767

108-
const float rcpPdf = abs(dot(Rectangle_getNormalTimesArea(rect),L))/(distanceSq*choicePdf);
68+
pdf = distanceSq/abs(dot(Rectangle_getNormalTimesArea(rect),L));
69+
newRayMaxT = 1.0/rcpDistance;
70+
return L*rcpDistance;
10971
#else
11072
float rcpPdf;
11173

11274
const mat3 sphericalVertices = nbl_glsl_shapes_getSphericalTriangle(mat3(tri.vertex0,tri.vertex1,tri.vertex2),origin);
113-
#if RECTANGLE_METHOD==1
114-
const vec3 L = nbl_glsl_sampling_generateSphericalTriangleSample(rcpPdf,sphericalVertices,u.xy);
115-
#elif RECTANGLE_METHOD==2
116-
const vec3 L = nbl_glsl_sampling_generateProjectedSphericalTriangleSample(rcpPdf,sphericalVertices,interaction.isotropic.N,isBSDF,u.xy);
75+
#if POLYGON_METHOD==1
76+
const vec3 L = nbl_glsl_sampling_generateSphericalTriangleSample(rcpPdf,sphericalVertices,xi.xy);
77+
#elif POLYGON_METHOD==2
78+
const vec3 L = nbl_glsl_sampling_generateProjectedSphericalTriangleSample(rcpPdf,sphericalVertices,interaction.isotropic.N,isBSDF,xi.xy);
11779
#endif
80+
11881
// if `rcpProb` is NAN or negative then the triangle's solidAngle or projectedSolidAngle was close to 0.0
119-
rcpPdf = rcpPdf>FLT_MIN ? rcpPdf:0.0;
82+
pdf = rcpPdf>FLT_MIN ? (1.0/rcpPdf):0.0;
12083

12184
const vec3 N = Triangle_getNormalTimesArea(tri);
122-
const float dist = dot(N,tri.vertex0-origin)/dot(N,L);
85+
newRayMaxT = dot(N,tri.vertex0-origin)/dot(N,L);
86+
return L;
12387
#endif
124-
125-
remainder = Light_getRadiance(light)*rcpPdf;
126-
pdf = 1.0/rcpPdf;
127-
128-
newRayMaxT = getEndTolerance(depth)*dist;
129-
130-
return nbl_glsl_createLightSample(L,interaction);
13188
}
13289

133-
void closestHitProgram(in ImmutableRay_t _immutable, inout nbl_glsl_xoroshiro64star_state_t scramble_state)
134-
{
135-
const MutableRay_t mutable = rayStack[stackPtr]._mutable;
136-
137-
vec3 intersection = _immutable.origin+_immutable.direction*mutable.intersectionT;
138-
const uint objectID = mutable.objectID;
139-
140-
uint bsdfLightIDs;
141-
nbl_glsl_AnisotropicViewSurfaceInteraction interaction;
142-
{
143-
nbl_glsl_IsotropicViewSurfaceInteraction isotropic;
14490

145-
isotropic.V.dir = -_immutable.direction;
146-
//isotropic.V.dPosdScreen = screw that
147-
if (objectID<SPHERE_COUNT)
148-
{
149-
Sphere sphere = spheres[objectID];
150-
isotropic.N = Sphere_getNormal(sphere,intersection);
151-
bsdfLightIDs = sphere.bsdfLightIDs;
152-
}
153-
else
154-
{
155-
Rectangle rect = rectangles[objectID-SPHERE_COUNT];
156-
isotropic.N = normalize(Rectangle_getNormalTimesArea(rect));
157-
bsdfLightIDs = rect.bsdfLightIDs;
158-
}
159-
isotropic.NdotV = dot(isotropic.V.dir,isotropic.N);
160-
isotropic.NdotV_squared = isotropic.NdotV*isotropic.NdotV;
161-
162-
interaction = nbl_glsl_calcAnisotropicInteraction(isotropic);
163-
}
164-
165-
const uint lightID = bitfieldExtract(bsdfLightIDs,16,16);
166-
167-
vec3 throughput = rayStack[stackPtr]._payload.throughput;
168-
// finish MIS
169-
if (lightID!=INVALID_ID_16BIT) // has emissive
91+
uint getBSDFLightIDAndDetermineNormal(out vec3 normal, in uint objectID, in vec3 intersection)
92+
{
93+
if (objectID<SPHERE_COUNT)
17094
{
171-
float lightPdf;
172-
vec3 lightVal = nbl_glsl_light_deferred_eval_and_prob(
173-
lightPdf,lights[lightID],_immutable.direction
174-
#if RECTANGLE_METHOD==0
175-
,mutable.intersectionT
176-
#else
177-
,_immutable.origin
178-
#if RECTANGLE_METHOD==2
179-
,_immutable.normalAtOrigin,_immutable.wasBSDFAtOrigin
180-
#endif
181-
#endif
182-
);
183-
rayStack[stackPtr]._payload.accumulation += throughput*lightVal/(1.0+lightPdf*lightPdf*rayStack[stackPtr]._payload.otherTechniqueHeuristic);
95+
Sphere sphere = spheres[objectID];
96+
normal = Sphere_getNormal(sphere,intersection);
97+
return sphere.bsdfLightIDs;
18498
}
185-
186-
const int sampleIx = bitfieldExtract(_immutable.typeDepthSampleIx,0,DEPTH_BITS_OFFSET);
187-
const int depth = bitfieldExtract(_immutable.typeDepthSampleIx,DEPTH_BITS_OFFSET,DEPTH_BITS_COUNT);
188-
189-
// check if we even have a BSDF at all
190-
uint bsdfID = bitfieldExtract(bsdfLightIDs,0,16);
191-
if (depth<MAX_DEPTH && bsdfID!=INVALID_ID_16BIT)
99+
else
192100
{
193-
// common preload
194-
BSDFNode bsdf = bsdfs[bsdfID];
195-
uint opType = BSDFNode_getType(bsdf);
196-
197-
#ifdef KILL_DIFFUSE_SPECULAR_PATHS
198-
if (BSDFNode_isNotDiffuse(bsdf))
199-
{
200-
if (rayStack[stackPtr]._payload.hasDiffuse)
201-
return;
202-
}
203-
else
204-
rayStack[stackPtr]._payload.hasDiffuse = true;
205-
#endif
206-
207-
208-
const float bsdfGeneratorProbability = BSDFNode_getMISWeight(bsdf);
209-
mat2x3 epsilon = rand3d(depth,sampleIx,scramble_state);
210-
211-
float rcpChoiceProb;
212-
const bool doNEE = nbl_glsl_partitionRandVariable(bsdfGeneratorProbability,epsilon[0].z,rcpChoiceProb);
213-
214-
215-
float maxT;
216-
// the probability of generating a sample w.r.t. the light generator only possible and used when it was generated with it!
217-
float lightPdf;
218-
nbl_glsl_LightSample _sample;
219-
nbl_glsl_AnisotropicMicrofacetCache _cache;
220-
const bool isBSDF = BSDFNode_isBSDF(bsdf);
221-
if (doNEE)
222-
{
223-
vec3 lightRemainder;
224-
_sample = nbl_glsl_light_generate_and_remainder_and_pdf(
225-
lightRemainder,lightPdf,maxT,
226-
intersection,interaction,
227-
isBSDF,epsilon[0],depth
228-
);
229-
throughput *= lightRemainder;
230-
}
231-
232-
bool validPath = true;
233-
const vec3 throughputCIE_Y = transpose(nbl_glsl_sRGBtoXYZ)[1]*throughput;
234-
const float monochromeEta = dot(throughputCIE_Y,BSDFNode_getEta(bsdf)[0])/(throughputCIE_Y.r+throughputCIE_Y.g+throughputCIE_Y.b);
235-
if (doNEE)
236-
{
237-
// if we allowed non-watertight transmitters (single water surface), it would make sense just to apply this line.
238-
validPath = nbl_glsl_calcAnisotropicMicrofacetCache(_cache,interaction,_sample,monochromeEta);
239-
// but we don't allow non watertight transmitters in this renderer
240-
validPath = validPath && _sample.NdotL>0.0;
241-
}
242-
else
243-
{
244-
maxT = FLT_MAX;
245-
_sample = nbl_glsl_bsdf_cos_generate(interaction,epsilon[0],bsdf,monochromeEta,_cache);
246-
}
247-
248-
// do a cool trick and always compute the bsdf parts this way! (no divergence)
249-
float bsdfPdf;
250-
// the value of the bsdf divided by the probability of the sample being generated
251-
if (validPath)
252-
throughput *= nbl_glsl_bsdf_cos_remainder_and_pdf(bsdfPdf,_sample,interaction,bsdf,monochromeEta,_cache);
253-
else
254-
throughput = vec3(0.0);
255-
256-
// OETF smallest perceptible value
257-
const float bsdfPdfThreshold = getLuma(nbl_glsl_eotf_sRGB(vec3(1.0)/255.0));
258-
const float lumaThroughputThreshold = bsdfPdfThreshold;
259-
if (bsdfPdf>bsdfPdfThreshold && getLuma(throughput)>lumaThroughputThreshold)
260-
{
261-
rayStack[stackPtr]._payload.throughput = throughput*rcpChoiceProb;
262-
263-
float heuristicFactor = rcpChoiceProb-1.0; // weightNonGenerator/weightGenerator
264-
heuristicFactor /= doNEE ? lightPdf:bsdfPdf; // weightNonGenerator/(weightGenerator*probGenerated)
265-
heuristicFactor *= heuristicFactor; // (weightNonGenerator/(weightGenerator*probGenerated))^2
266-
if (doNEE)
267-
heuristicFactor = 1.0/(1.0/bsdfPdf+heuristicFactor*bsdfPdf); // numerically stable, don't touch
268-
rayStack[stackPtr]._payload.otherTechniqueHeuristic = heuristicFactor;
269-
270-
// trace new ray
271-
rayStack[stackPtr]._immutable.origin = intersection+_sample.L*(doNEE ? maxT:1.0/*kSceneSize*/)*getStartTolerance(depth);
272-
rayStack[stackPtr]._immutable.maxT = maxT;
273-
rayStack[stackPtr]._immutable.direction = _sample.L;
274-
rayStack[stackPtr]._immutable.typeDepthSampleIx = bitfieldInsert(sampleIx,depth+2,DEPTH_BITS_OFFSET,DEPTH_BITS_COUNT)|(doNEE ? ANY_HIT_FLAG:0);
275-
rayStack[stackPtr]._immutable.normalAtOrigin = interaction.isotropic.N;
276-
rayStack[stackPtr]._immutable.wasBSDFAtOrigin = isBSDF;
277-
stackPtr++;
278-
}
101+
Rectangle rect = rectangles[objectID-SPHERE_COUNT];
102+
normal = normalize(Rectangle_getNormalTimesArea(rect));
103+
return rect.bsdfLightIDs;
279104
}
280105
}

0 commit comments

Comments
 (0)