1// Copyright 2009-2021 Intel Corporation
2// SPDX-License-Identifier: Apache-2.0
3
4#include "../../common/DistributedWorld.ih"
5#include "DistributedRenderer.ih"
6#include "common/Instance.ih"
7#include "common/World.ih"
8#include "math/random.ih"
9#include "math/sampling.ih"
10#include "openvkl/openvkl.isph"
11#include "render/materials/OBJ.ih"
12#include "render/scivis/surfaces.ih"
13#include "render/scivis/volumes.ih"
14#include "render/util.ih"
15#include "rkcommon/math/box.ih"
16
17struct DistributedRaycastRenderer
18{
19  DistributedRenderer super;
20  int aoSamples;
21  float aoRadius;
22  bool shadowsEnabled;
23  float volumeSamplingRate;
24};
25
26// The distributed raycast renderer uses its own volume interval integration
27// because we want to apply the jitter before offsetting our step size to stay
28// inside the region, not after.
29vec4f DRR_integrateVolumeInterval(const ScreenSample &sample,
30    const VolumeInterval &interval,
31    Ray &ray,
32    uniform float samplingRate)
33{
34  VolumetricModel *varying volModel = interval.volumetricModel;
35
36  Ray transformedRay = ray;
37  transformRay(transformedRay, interval.instance->rcp_xfm);
38
39  uniform unsigned int8 intervalIteratorBuffer[VKL_MAX_INTERVAL_ITERATOR_SIZE];
40
41  vec3f color = make_vec3f(0.f);
42  float alpha = 0.f;
43  foreach_unique (vm in volModel) {
44    Volume *uniform volume = vm->volume;
45    TransferFunction *uniform tf = vm->transferFunction;
46
47    float time = 0.5f;
48    VKLIntervalIterator intervalIterator =
49        vklInitIntervalIteratorV(vm->vklIntervalContext,
50            (varying vkl_vec3f *)&transformedRay.org,
51            (varying vkl_vec3f *)&transformedRay.dir,
52            (varying vkl_range1f *)&interval.interval,
53            &time,
54            intervalIteratorBuffer);
55
56    VKLInterval interval;
57    while (vklIterateIntervalV(intervalIterator, &interval) && alpha < 0.99f) {
58      const float nominalSamplingDt = interval.nominalDeltaT / samplingRate;
59
60      // initial sub interval, based on our renderer-defined sampling rate
61      // and the volume's nominal dt
62      box1f subInterval = make_box1f(interval.tRange.lower,
63          min(interval.tRange.lower + nominalSamplingDt,
64              interval.tRange.upper));
65
66      while (subInterval.upper - subInterval.lower > 0.f && alpha < 0.99f) {
67        transformedRay.t0 =
68            subInterval.lower + (subInterval.upper - subInterval.lower);
69        const float dt = subInterval.upper - subInterval.lower;
70
71        // Get volume sample
72        vec3f p = transformedRay.org + transformedRay.t0 * transformedRay.dir;
73        const float sample = vklComputeSampleV(
74            volume->vklSampler, (const varying vkl_vec3f *uniform) & p);
75
76        if (!isnan(sample)) {
77          vec4f sampleColorOpacity = tf->get(tf, sample);
78
79          sampleColorOpacity.w = 1.f
80              - pow(1.f - sampleColorOpacity.w * vm->densityScale,
81                  dt / interval.nominalDeltaT);
82          const float clampedOpacity = clamp(sampleColorOpacity.w);
83          color = color
84              + ((1.f - alpha) * clampedOpacity
85                  * make_vec3f(sampleColorOpacity));
86          alpha = alpha + ((1.f - alpha) * clampedOpacity);
87        }
88
89        // compute next sub interval
90        subInterval.lower = subInterval.upper;
91        subInterval.upper =
92            min(subInterval.lower + nominalSamplingDt, interval.tRange.upper);
93      }
94    }
95  }
96
97  return make_vec4f(color, alpha);
98}
99
100inline float computeAO(const DistributedRaycastRenderer *uniform self,
101    const FrameBuffer *uniform fb,
102    const World *uniform world,
103    const varying vec3i &sampleID,
104    const varying DifferentialGeometry &dg)
105{
106  const uniform int &sampleCnt = self->aoSamples;
107  const uniform int accumID = reduce_max(sampleID.z) * sampleCnt;
108
109  // init TEA RNG //
110  RandomTEA rng_state;
111  varying RandomTEA *const uniform rng = &rng_state;
112  RandomTEA__Constructor(rng, 0x290374, (fb->size.x * sampleID.y) + sampleID.x);
113  const vec2f rot = RandomTEA__getFloats(rng);
114
115  int hits = 0;
116
117  const linear3f localToWorld = frame(dg.Ns);
118
119  for (uniform int i = 0; i < sampleCnt; i++) {
120    const vec2f halton = HaltonSequence_get2D(accumID + i);
121    const vec2f r = CranleyPattersonRotation(halton, rot);
122    const vec3f local_ao_dir = cosineSampleHemisphere(r);
123    const vec3f ao_dir = localToWorld * local_ao_dir;
124
125    if (dot(ao_dir, dg.Ns) < 0.05f) { // check below surface
126      hits++;
127      continue;
128    }
129
130    Ray ao_ray;
131    setRay(ao_ray, dg.P, ao_dir, dg.epsilon, self->aoRadius);
132    if (isOccluded(world, ao_ray))
133      hits++;
134  }
135
136  // the cosTheta of cosineSampleHemispherePDF and dot(shadingNormal, ao_dir)
137  // cancel
138  return 1.0f - (hits / (float)sampleCnt);
139}
140
141vec4f DRR_shadeSurface(const DistributedRaycastRenderer *uniform self,
142    const FrameBuffer *uniform fb,
143    const DistributedWorld *uniform world,
144    const vec3i &sampleID,
145    const Ray &ray,
146    const DifferentialGeometry &dg)
147{
148  // TODO: DRR should have its own support for OBJ material and lighting model
149
150  vec3f surfaceColor = make_vec3f(dg.color);
151  float opacity = 1.f;
152  const OBJ *mat = (const OBJ *)dg.material;
153  if (mat) {
154    foreach_unique (m in mat) {
155      surfaceColor = m->Kd;
156      opacity = m->d;
157      if (valid(m->KdMap)) {
158        vec4f kdFromMap = get4f(m->KdMap, dg);
159        surfaceColor = surfaceColor * make_vec3f(kdFromMap);
160      }
161    }
162  }
163
164  // Just eyelight for now...
165  const float eyeLightIntensity = absf(dot(dg.Ns, ray.dir));
166  vec3f color = surfaceColor * eyeLightIntensity;
167  if (self->aoSamples > 0) {
168    float ao = computeAO(self, fb, &world->super, sampleID, dg);
169    color = color * ao;
170  }
171  return make_vec4f(color, opacity);
172
173#if 0
174  // TODO LIGHTS
175  // Calculate shading for all lights
176  for (uniform int i = 0; self->lights && i < self->numLights; i++) {
177    const uniform Light *uniform l = self->lights[i];
178    const vec2f s = make_vec2f(0.0f); // sample center of area lights
179    const Light_SampleRes light = l->sample(l, dg, s);
180
181    // any potential contribution?
182    if (reduce_max(light.weight) > 0.f) {
183      float cosNL = dot(light.dir, info.shadingNormal);
184      if (cosNL < 0.0f)
185        continue;
186
187      cosNL = abs(cosNL);
188
189      const vec3f H = normalize(light.dir - ray.dir);
190      const float cosNH = dot(H, info.shadingNormal);
191      const vec3f specular = info.Ks * powf(cosNH, info.Ns);
192      const vec3f diffuse = info.Kd * cosNL;
193      const vec3f light_contrib =
194        info.local_opacity * (diffuse + specular) * light.weight;
195
196      if (self->shadowsEnabled) {
197        const float max_contrib = reduce_max(light_contrib);
198        if (max_contrib > self->super.minContribution) {
199          vec3f P = dg.P;
200          if (dot(dg.Ng, light.dir) < 0.0f) {
201            P = P - (2.f * dg.epsilon) * dg.Ng;
202          }
203
204          Ray shadowRay;
205          setRay(shadowRay, P, light.dir, 0.0f, light.dist, 0.5f);
206          if (!isOccluded(model, shadowRay)
207              && !(ghostModel && isOccluded(ghostModel, shadowRay)))
208          {
209            color = color + light_contrib;
210          }
211        }
212      } else {
213        color = color + light_contrib;
214      }
215    }
216  }
217#endif
218  return color;
219}
220
221void DRR_renderRegionSample(DistributedRenderer *uniform _self,
222    FrameBuffer *uniform fb,
223    DistributedWorld *uniform world,
224    const box3f *uniform region,
225    const vec2f &regionInterval,
226    void *uniform perFrameData,
227    varying ScreenSample &sample)
228{
229  DistributedRaycastRenderer *uniform self =
230      (DistributedRaycastRenderer * uniform) _self;
231
232  Ray &geomRay = sample.ray;
233  Ray volumeRay = sample.ray;
234
235  VolumeInterval volumeInterval;
236
237  traceRay(&world->super, geomRay);
238  traceVolumeRay(&world->super, volumeRay, volumeInterval);
239  volumeInterval.interval.lower =
240      max(volumeInterval.interval.lower, regionInterval.x);
241  volumeInterval.interval.upper =
242      min(volumeInterval.interval.upper, regionInterval.y);
243
244  sample.z = min(geomRay.t, volumeInterval.interval.lower);
245
246  // TODO: Doesn't seem like much jittering happens with the volume integration
247  vec4f outputColor = make_vec4f(0.f);
248  while (outputColor.w < 0.99f) {
249    const bool haveGeometryHit = hadHit(geomRay)
250        && geomRay.t >= regionInterval.x && geomRay.t <= regionInterval.y;
251
252    const bool haveVolumeHit = hasInterval(volumeInterval);
253
254    const bool bothHit = haveGeometryHit && haveVolumeHit;
255    const bool eitherHit = haveGeometryHit || haveVolumeHit;
256
257    const bool volumeFirst = volumeInterval.interval.lower < geomRay.t;
258
259    if (!eitherHit) {
260      break;
261    }
262
263    vec4f volumeColor = make_vec4f(0.f);
264    vec4f surfaceColor = make_vec4f(0.f);
265    DifferentialGeometry dg;
266    if (haveGeometryHit) {
267      postIntersect(&world->super,
268          &self->super.super,
269          dg,
270          geomRay,
271          DG_NG | DG_NS | DG_NORMALIZE | DG_FACEFORWARD | DG_COLOR
272              | DG_TEXCOORD);
273      surfaceColor =
274          DRR_shadeSurface(self, fb, world, sample.sampleID, geomRay, dg);
275    }
276
277    // Always just integrate the volume when it comes in front of the geometry
278    if (haveVolumeHit && volumeFirst) {
279      volumeInterval.interval.upper =
280          min(geomRay.t, volumeInterval.interval.upper);
281
282      // Keep the interval within the region
283      volumeInterval.interval.lower =
284          max(volumeInterval.interval.lower, regionInterval.x);
285      volumeInterval.interval.upper =
286          min(volumeInterval.interval.upper, regionInterval.y);
287
288      // Offset the volume interval start to align to the same sampling steps
289      // across each rank, and apply the same jitter for each pixel on all ranks
290      float dt = 1.f /*was 'volModel->samplingStep*/ / self->volumeSamplingRate;
291      float t0 = volumeInterval.interval.lower;
292      volumeInterval.interval.lower = floor(t0 / dt) * dt;
293
294      float jitter = Halton_sample2(sample.sampleID.z);
295      int ix = sample.sampleID.x % 4;
296      int iy = sample.sampleID.y % 4;
297
298      int patternID = ix + 4 * iy;
299      jitter += Halton_sample3(patternID);
300      if (jitter > 1.f) {
301        jitter -= 1.f;
302      }
303      volumeInterval.interval.lower += jitter * dt;
304
305      if (volumeInterval.interval.lower < t0) {
306        volumeInterval.interval.lower += dt;
307      }
308
309      // TODO: At edges when coloring all bricks the same I do still see
310      // some edge/border artifacts
311
312      setRay(volumeRay,
313          volumeInterval.interval.lower,
314          volumeInterval.interval.upper);
315      volumeColor = DRR_integrateVolumeInterval(
316          sample, volumeInterval, volumeRay, self->volumeSamplingRate);
317    }
318
319    vec4f blendedColor = make_vec4f(0.f);
320    if (bothHit) {
321      blendedColor = volumeColor + (1.0f - volumeColor.w) * surfaceColor;
322    } else {
323      if (haveGeometryHit)
324        blendedColor = surfaceColor;
325      else
326        blendedColor = volumeColor;
327    }
328
329    outputColor = outputColor + (1.0f - outputColor.w) * blendedColor;
330
331    if (haveGeometryHit)
332      setRay(geomRay, geomRay.t + dg.epsilon, regionInterval.y + 0.0001);
333    else
334      setRay(geomRay, geomRay.t, geomRay.t0);
335
336    // Step the volume ray forwards as well
337    volumeRay = geomRay;
338
339    traceRay(&world->super, geomRay);
340    traceVolumeRay(&world->super, volumeRay, volumeInterval);
341  }
342  sample.rgb = make_vec3f(outputColor);
343  sample.alpha = outputColor.w;
344}
345
346// Exported functions /////////////////////////////////////////////////////////
347
348export void *uniform DistributedRaycastRenderer_create()
349{
350  DistributedRaycastRenderer *uniform self =
351      uniform new uniform DistributedRaycastRenderer;
352
353  DistributedRenderer_Constructor(&self->super);
354  self->super.renderRegionSample = DRR_renderRegionSample;
355  self->aoSamples = 0;
356  self->shadowsEnabled = false;
357
358  return self;
359}
360
361export void DistributedRaycastRenderer_set(void *uniform _self,
362    const uniform int aoSamples,
363    const uniform float aoRadius,
364    const uniform bool shadowsEnabled,
365    const uniform float volumeSamplingRate)
366{
367  DistributedRaycastRenderer *uniform self =
368      (DistributedRaycastRenderer * uniform) _self;
369
370  self->aoSamples = aoSamples;
371  self->aoRadius = aoRadius;
372  self->shadowsEnabled = shadowsEnabled;
373  self->volumeSamplingRate = volumeSamplingRate;
374}
375