Skip to content

Commit bf39946

Browse files
Optimized Spherical Triangle sampling works
1 parent 7a78531 commit bf39946

File tree

2 files changed

+94
-5
lines changed

2 files changed

+94
-5
lines changed

examples_tests/42.FragmentShaderPathTracer/common.glsl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ float Sphere_getSolidAngle(in Sphere sphere, in vec3 origin)
8181
return Sphere_getSolidAngle_impl(cosThetaMax);
8282
}
8383

84-
84+
#define TRIANGLE_METHOD 1 // 0 area sampling, 1 solid angle sampling, 2 approximate projected solid angle sampling
8585
struct Triangle
8686
{
8787
vec3 vertex0;

examples_tests/42.FragmentShaderPathTracer/litByTriangle.frag

Lines changed: 93 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ Triangle triangles[TRIANGLE_COUNT] = {
2323

2424
#define LIGHT_COUNT 1
2525
Light lights[LIGHT_COUNT] = {
26-
//{vec3(30.0,25.0,15.0)*0.01,0u}
27-
{vec3(30.0,25.0,15.0),0u}
26+
//{vec3(30.0,25.0,15.0),0u}
27+
{vec3(30.0,25.0,15.0)*0.01,0u}
2828
};
2929

3030

@@ -69,11 +69,86 @@ bool traceRay(in ImmutableRay_t _immutable)
6969
vec3 irr_glsl_light_deferred_eval_and_prob(out float pdf, in float intersectionT, in irr_glsl_AnisotropicViewSurfaceInteraction interaction, in Light light)
7070
{
7171
// 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
72-
pdf = scene_getLightChoicePdf(light)*intersectionT*intersectionT/abs(dot(Triangle_getNormalTimesArea(triangles[Light_getObjectID(light)]),interaction.isotropic.V.dir));
72+
pdf = scene_getLightChoicePdf(light);
73+
74+
Triangle tri = triangles[Light_getObjectID(light)];
75+
#if TRIANGLE_METHOD==0
76+
pdf *= intersectionT*intersectionT/abs(dot(Triangle_getNormalTimesArea(tri),interaction.isotropic.V.dir));
77+
#elif TRIANGLE_METHOD==1
78+
//pdf /= Triangle_getSolidAngle(tri),origin);
79+
#elif TRIANGLE_METHOD==2
80+
pdf /= Triangle_getApproxProjSolidAngle(tri,origin,interaction.isotropic.V.dir);
81+
#endif
7382
return Light_getRadiance(light);
7483
}
7584

7685

86+
vec3 faster_slerp(in vec3 start, in vec3 preScaledWaypoint, float cosAngleFromStart)
87+
{
88+
vec3 planeNormal = cross(start,preScaledWaypoint);
89+
90+
cosAngleFromStart *= 0.5;
91+
const float sinAngle = sqrt(0.5-cosAngleFromStart);
92+
const float cosAngle = sqrt(0.5+cosAngleFromStart);
93+
94+
planeNormal *= sinAngle;
95+
const vec3 precompPart = cross(planeNormal,start)*2.0;
96+
97+
return start+precompPart*cosAngle+cross(planeNormal,precompPart);
98+
}
99+
100+
// WARNING: can and will return NAN if one or three of the triangle edges are near zero length
101+
vec3 sampleSphericalTriangle(out float rcpPdf, in mat3 vertices, in vec3 origin, in vec2 u)
102+
{
103+
mat3 localVertices = mat3(vertices[0]-origin,vertices[1]-origin,vertices[2]-origin);
104+
105+
const vec3 A = normalize(localVertices[0]);
106+
const vec3 B = normalize(localVertices[1]);
107+
const vec3 C = normalize(localVertices[2]);
108+
109+
// The sides are denoted by lower-case letters a, b, and c. On the unit sphere their lengths are numerically equal to the radian measure of the angles that the great circle arcs subtend at the centre. The sides of proper spherical triangles are (by convention) less than PI
110+
const vec3 cos_sides = vec3(dot(B,C),dot(C,A),dot(A,B));
111+
const vec3 csc_sides = inversesqrt(vec3(1.0)-cos_sides*cos_sides);
112+
113+
// Both vertices and angles at the vertices are denoted by the same upper case letters A, B, and C. The angles A, B, C of the triangle are equal to the angles between the planes that intersect the surface of the sphere or, equivalently, the angles between the tangent vectors of the great circle arcs where they meet at the vertices. Angles are in radians. The angles of proper spherical triangles are (by convention) less than PI
114+
const vec3 cos_vertices = (cos_sides-cos_sides.yzx*cos_sides.zxy)*csc_sides.yzx*csc_sides.zxy; // using Spherical Law of Cosines
115+
const vec3 sin_vertices = sqrt(vec3(1.0)-cos_vertices*cos_vertices);
116+
117+
// get solid angle, which is also the reciprocal of the probability
118+
{
119+
// sorry about the naming of `something` I just can't seem to be able to give good name to the variables that is consistent with semantics
120+
bool something0 = cos_vertices[0]<-cos_vertices[1];
121+
float cosSumAB = cos_vertices[0]*cos_vertices[1]-sin_vertices[0]*sin_vertices[1];
122+
bool something1 = cosSumAB<(-cos_vertices[2]);
123+
bool something2 = cosSumAB<cos_vertices[2];
124+
// apply triple angle formula
125+
float absArccosSumABC = acos(cosSumAB*cos_vertices[2]-(cos_vertices[0]*sin_vertices[1]+sin_vertices[0]*cos_vertices[1])*sin_vertices[2]);
126+
rcpPdf = (something0 ? something2:something1) ? (-absArccosSumABC):absArccosSumABC;
127+
rcpPdf += something0||something1 ? irr_glsl_PI:(-irr_glsl_PI);
128+
}
129+
130+
// this part literally cannot be optimized further
131+
float negSinSubSolidAngle,negCosSubSolidAngle;
132+
irr_glsl_sincos(rcpPdf*u.x-irr_glsl_PI,negSinSubSolidAngle,negCosSubSolidAngle);
133+
134+
// TODO: we could optimize everything up and including to the first slerp, because precision here is just godawful
135+
const float p = negCosSubSolidAngle*sin_vertices[0]-negSinSubSolidAngle*cos_vertices[0];
136+
const float q = -negSinSubSolidAngle*sin_vertices[0]-negCosSubSolidAngle*cos_vertices[0];
137+
138+
float u_ = q - cos_vertices[0];
139+
float v_ = p + sin_vertices[0]*cos_sides[2];
140+
141+
const float cosAngleAlongAC = clamp(((v_*q - u_*p)*cos_vertices[0] - v_) / ((v_*p + u_*q)*sin_vertices[0]), -1.0, 1.0); // TODO: get rid of this clamp (by improving the precision here)
142+
143+
vec3 C_s = faster_slerp(A, C*csc_sides[1], cosAngleAlongAC);
144+
145+
const float cosBC_s = dot(C_s,B);
146+
const float cosAngleAlongBC_s = cosBC_s*u.y - u.y + 1.0;
147+
148+
return faster_slerp(B, C_s*inversesqrt(1.0-cosBC_s*cosBC_s), cosAngleAlongBC_s);
149+
}
150+
151+
77152
irr_glsl_LightSample irr_glsl_light_generate_and_remainder_and_pdf(out vec3 remainder, out float pdf, out float newRayMaxT, in vec3 origin, in irr_glsl_AnisotropicViewSurfaceInteraction interaction, in vec3 u, in int depth)
78153
{
79154
// normally we'd pick from set of lights, using `u.z`
@@ -82,19 +157,33 @@ irr_glsl_LightSample irr_glsl_light_generate_and_remainder_and_pdf(out vec3 rema
82157

83158
const Triangle tri = triangles[Light_getObjectID(light)];
84159

160+
#if TRIANGLE_METHOD==0
85161
const vec3 edges[2] = vec3[2](tri.vertex1-tri.vertex0,tri.vertex2-tri.vertex0);
86162
vec3 point = tri.vertex0+edges[0]*(1.0-u.y)+edges[1]*u.y*sqrt(u.x);
87163
vec3 L = point-origin;
88164

89165
const float distanceSq = dot(L,L);
90166
const float rcpDistance = inversesqrt(distanceSq);
91167
L *= rcpDistance;
168+
169+
const float dist = 1.0/rcpDistance;
92170

93171
const float rcpPdf = abs(dot(Triangle_getNormalTimesArea(tri),L))/(distanceSq*choicePdf);
172+
#elif TRIANGLE_METHOD==1
173+
float rcpPdf;
174+
const vec3 L = sampleSphericalTriangle(rcpPdf,mat3(tri.vertex0,tri.vertex1,tri.vertex2),origin,u.xy);
175+
rcpPdf = isnan(rcpPdf) ? 0.0:rcpPdf;
176+
177+
const vec3 N = Triangle_getNormalTimesArea(tri);
178+
const float dist = dot(N,tri.vertex0-origin)/dot(N,L);
179+
#elif TRIANGLE_METHOD==2
180+
const float rcpPdf = Triangle_getApproxProjSolidAngle(tri,origin,interaction.isotropic.V.dir);
181+
#endif
182+
94183
remainder = Light_getRadiance(light)*rcpPdf;
95184
pdf = 1.0/rcpPdf;
96185

97-
newRayMaxT = getEndTolerance(depth)/rcpDistance;
186+
newRayMaxT = getEndTolerance(depth)*dist;
98187

99188
return irr_glsl_createLightSample(L,interaction);
100189
}

0 commit comments

Comments
 (0)