1// Copyright 2009-2021 Intel Corporation
2// SPDX-License-Identifier: Apache-2.0
3
4#include "Light.ih"
5#include "common/Instance.ih"
6#include "math/Distribution2D.ih"
7#include "math/LinearSpace.ih"
8#include "math/sampling.ih"
9#include "texture/Texture2D.ih"
10
11struct HDRILight
12{
13  Light super;
14
15  linear3f light2world;
16  linear3f world2light;
17  const Texture2D *uniform
18      map; // Environment map in latitude / longitude format
19  const Distribution2D *uniform
20      distribution; // The 2D distribution used to importance sample
21  vec2f rcpSize; // precomputed 1/map.size
22  vec3f radianceScale; // scaling factor of emmitted RGB radiance
23};
24
25// Implementation
26//////////////////////////////////////////////////////////////////////////////
27
28// sample function used when no environment map is given: black
29Light_SampleRes HDRILight_sample_dummy(const Light *uniform,
30    const DifferentialGeometry &,
31    const vec2f &,
32    const float)
33{
34  Light_SampleRes res;
35  memset(&res, 0, sizeof(Light_SampleRes));
36  return res;
37}
38
39inline Light_SampleRes Sample(const HDRILight *uniform self,
40    const uniform linear3f &light2world,
41    const vec2f &s)
42{
43  Light_SampleRes res;
44
45  Sample2D sample2d = Distribution2D_sample(self->distribution, s);
46  // Distribution2D samples within bin i as (i, i+1), whereas we provided
47  // average importance for (i-0.5, i+0.5), thus shift by 0.5
48  sample2d.uv = sample2d.uv - self->map->halfTexel;
49
50  const float phi = two_pi * sample2d.uv.x;
51  const float theta = M_PI * sample2d.uv.y;
52
53  float sinTheta, cosTheta;
54  sincos(theta, &sinTheta, &cosTheta);
55  const vec3f localDir = cartesian(phi, sinTheta, cosTheta);
56
57  res.dir = light2world * localDir;
58
59  res.pdf = sample2d.pdf * one_over_two_pi_sqr * rcp(sinTheta);
60
61  res.dist = inf;
62
63  // clamp2edge for theta for tex lookup, to prevent light leaks at the poles
64  sample2d.uv.y = clamp(
65      sample2d.uv.y, self->map->halfTexel.y, 1.0f - self->map->halfTexel.y);
66  DifferentialGeometry lookup;
67  initDgFromTexCoord(lookup, sample2d.uv);
68  res.weight = get3f(self->map, lookup) * self->radianceScale / res.pdf;
69
70  return res;
71}
72
73Light_SampleRes HDRILight_sample(const Light *uniform super,
74    const DifferentialGeometry &,
75    const vec2f &s,
76    const float)
77{
78  const HDRILight *uniform self = (HDRILight * uniform) super;
79  assert(self);
80  return Sample(self, self->light2world, s);
81}
82
83Light_SampleRes HDRILight_sample_instanced(const Light *uniform super,
84    const DifferentialGeometry &,
85    const vec2f &s,
86    const float time)
87{
88  const HDRILight *uniform self = (HDRILight * uniform) super;
89  assert(self);
90
91  const Instance *uniform instance = self->super.instance;
92  assert(instance);
93
94  Light_SampleRes res;
95  foreach_unique (utime in time) {
96    const uniform affine3f xfm = Instance_getTransform(instance, utime);
97    res = Sample(self, xfm.l * self->light2world, s);
98  }
99  return res;
100}
101
102inline Light_EvalRes Eval(HDRILight *uniform self,
103    const uniform linear3f &world2light,
104    const vec3f &dir,
105    const float maxDist)
106{
107  Light_EvalRes res;
108  res.radiance = make_vec3f(0.f);
109
110  if (inf > maxDist)
111    return res;
112
113  const vec3f localDir = world2light * dir;
114
115  const float u = atan2(localDir.y, localDir.x) * one_over_two_pi;
116  const float v = acos(localDir.z) * one_over_pi;
117  const vec2f uv = make_vec2f(u, v);
118
119  // clamp2edge for theta for tex lookup, to prevent light leaks at the poles
120  const vec2f uvc = make_vec2f(
121      u, clamp(v, self->map->halfTexel.y, 1.0f - self->map->halfTexel.y));
122  DifferentialGeometry lookup;
123  initDgFromTexCoord(lookup, uvc);
124  res.radiance = get3f(self->map, lookup) * self->radianceScale;
125
126  // domain of Distribution2D is shifted by half a texel compared to texture
127  // atan2 can get negative, shift can lead to values > 1.f: reproject to [0..1)
128  const vec2f uvd = frac(uv + self->map->halfTexel);
129  res.pdf = Distribution2D_pdf(self->distribution, uvd);
130  res.pdf *= one_over_two_pi_sqr * rsqrt(1.f - sqr(localDir.z));
131
132  return res;
133}
134
135Light_EvalRes HDRILight_eval(const Light *uniform super,
136    const DifferentialGeometry &,
137    const vec3f &dir,
138    const float,
139    const float maxDist,
140    const float)
141{
142  const HDRILight *uniform self = (HDRILight * uniform) super;
143  assert(self);
144  return Eval(self, self->world2light, dir, maxDist);
145}
146
147Light_EvalRes HDRILight_eval_instanced(const Light *uniform super,
148    const DifferentialGeometry &,
149    const vec3f &dir,
150    const float,
151    const float maxDist,
152    const float time)
153{
154  const HDRILight *uniform self = (HDRILight * uniform) super;
155  assert(self);
156
157  const Instance *uniform instance = self->super.instance;
158  assert(instance);
159
160  Light_EvalRes res;
161  foreach_unique (utime in time) {
162    const uniform affine3f xfm = Instance_getTransform(instance, utime);
163    res = Eval(self, self->world2light * rcp(xfm.l), dir, maxDist);
164  }
165  return res;
166}
167
168// bin i represents the average contribution of (i-0.5, i+0.5) when we sample
169// the texture bilinearly at i
170// for i==0 we have a wrap-around, which is wanted for x (phi), but actually
171// not for y (theta), because then light (importance) from the south-pole is
172// leaking to the north-pole
173// however, sin(theta) is zero then, thus we will never sample there
174task unmasked void HDRILight_calcRowImportance(
175    const Texture2D *uniform const map,
176    float *uniform const importance,
177    float *uniform const row_importance)
178{
179  const uniform int y = taskIndex;
180  const uniform vec2f rcpSize = 1.f / map->sizef;
181  const uniform float fy = y * rcpSize.y;
182  const uniform int width = map->size.x;
183  const uniform float sinTheta = sin(fy * M_PI);
184  foreach (x = 0 ... width) {
185    const vec2f coord = make_vec2f(x * rcpSize.x, fy);
186    // using bilinear filtering is indeed what we want
187    DifferentialGeometry lookup;
188    initDgFromTexCoord(lookup, coord);
189    const vec3f col = get3f(map, lookup);
190    importance[y * width + x] = sinTheta * luminance(col);
191  }
192  row_importance[y] = Distribution1D_create(width, importance + y * width);
193}
194
195// Exports (called from C++)
196//////////////////////////////////////////////////////////////////////////////
197
198//! Set the parameters of an ispc-side HDRILight object
199export void HDRILight_set(void *uniform super,
200    const uniform vec3f &radianceScale,
201    const uniform linear3f &light2world,
202    const void *uniform map,
203    const void *uniform distribution)
204{
205  HDRILight *uniform self = (HDRILight * uniform) super;
206  self->radianceScale = radianceScale;
207
208  if (map) {
209    self->map = (uniform Texture2D * uniform) map;
210    self->distribution = (const uniform Distribution2D *uniform)distribution;
211
212    self->rcpSize = 1.f / self->map->sizef;
213    self->light2world = light2world;
214    self->super.sample = HDRILight_sample;
215    self->super.eval = HDRILight_eval;
216
217    // Enable dynamic runtime instancing or apply static transformation
218    const Instance *uniform instance = self->super.instance;
219    if (instance) {
220      if (instance->motionBlur) {
221        self->super.sample = HDRILight_sample_instanced;
222        self->super.eval = HDRILight_eval_instanced;
223      } else {
224        self->light2world = instance->xfm.l * self->light2world;
225      }
226    }
227    self->world2light = rcp(self->light2world);
228  } else {
229    self->super.sample = HDRILight_sample_dummy;
230    self->super.eval = Light_eval;
231  }
232}
233
234//! Create an ispc-side HDRILight object
235export void *uniform HDRILight_create()
236{
237  HDRILight *uniform self = uniform new HDRILight;
238
239  Light_Constructor(&self->super);
240  self->super.sample = HDRILight_sample_dummy;
241  HDRILight_set(
242      self, make_vec3f(1.0f), make_LinearSpace3f_identity(), NULL, NULL);
243  return self;
244}
245
246export void *uniform HDRILight_createDistribution(const void *uniform map)
247{
248  // calculate importance in parallel
249  const Texture2D *uniform m = (const Texture2D *uniform)map;
250  const uniform int height = m->size.y;
251  float *uniform cdf_x = uniform new float[m->size.x * height];
252  float *uniform row_importance = uniform new float[height];
253  launch[height] HDRILight_calcRowImportance(m, cdf_x, row_importance);
254  sync;
255
256  // create distribution
257  return Distribution2D_create(m->size, cdf_x, row_importance);
258  // no delete[] (row_)importance: ownership was transferred to Distribution2D
259}
260
261export void HDRILight_destroyDistribution(void *uniform distribution)
262{
263  Distribution2D_destroy((Distribution2D * uniform) distribution);
264}
265