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