+ // when static, we skip styled lights because they tend to change...
+ if (rtlight->style > 0 && r_shadow_bouncegrid.integer != 2)
+ continue;
+ }
+ else if (r_shadow_debuglight.integer >= 0 && (int)lightindex != r_shadow_debuglight.integer)
+ continue;
+ }
+ else
+ {
+ rtlight = r_refdef.scene.lights[lightindex - range];
+ VectorClear(rtlight->bouncegrid_photoncolor);
+ rtlight->bouncegrid_photons = 0;
+ rtlight->bouncegrid_hits = 0;
+ rtlight->bouncegrid_traces = 0;
+ rtlight->bouncegrid_effectiveradius = 0;
+ }
+ // draw only visible lights (major speedup)
+ radius = rtlight->radius * settings->lightradiusscale;
+ cullmins[0] = rtlight->shadoworigin[0] - radius;
+ cullmins[1] = rtlight->shadoworigin[1] - radius;
+ cullmins[2] = rtlight->shadoworigin[2] - radius;
+ cullmaxs[0] = rtlight->shadoworigin[0] + radius;
+ cullmaxs[1] = rtlight->shadoworigin[1] + radius;
+ cullmaxs[2] = rtlight->shadoworigin[2] + radius;
+ w = r_shadow_lightintensityscale.value * (rtlight->ambientscale + rtlight->diffusescale + rtlight->specularscale);
+ if (!settings->staticmode)
+ {
+ // skip if the expanded light box does not touch any visible leafs
+ if (r_refdef.scene.worldmodel
+ && r_refdef.scene.worldmodel->brush.BoxTouchingVisibleLeafs
+ && !r_refdef.scene.worldmodel->brush.BoxTouchingVisibleLeafs(r_refdef.scene.worldmodel, r_refdef.viewcache.world_leafvisible, cullmins, cullmaxs))
+ continue;
+ // skip if the expanded light box is not visible to traceline
+ // note that PrepareLight already did this check but for a smaller box, so we
+ // end up casting more traces per frame per light when using bouncegrid, which
+ // is probably fine (and they use the same timer)
+ if (r_shadow_culllights_trace.integer)
+ {
+ if (rtlight->trace_timer != realtime && R_CanSeeBox(rtlight->trace_timer == 0 ? r_shadow_culllights_trace_tempsamples.integer : r_shadow_culllights_trace_samples.integer, r_shadow_culllights_trace_eyejitter.value, r_shadow_culllights_trace_enlarge.value, r_refdef.view.origin, rtlight->cullmins, rtlight->cullmaxs))
+ rtlight->trace_timer = realtime;
+ if (realtime - rtlight->trace_timer > r_shadow_culllights_trace_delay.value)
+ return;
+ }
+ // skip if expanded light box is offscreen
+ if (R_CullBox(cullmins, cullmaxs))
+ continue;
+ // skip if overall light intensity is zero
+ if (w * VectorLength2(rtlight->color) == 0.0f)
+ continue;
+ }
+ // a light that does not emit any light before style is applied, can be
+ // skipped entirely (it may just be a corona)
+ if (rtlight->radius == 0.0f || VectorLength2(rtlight->color) == 0.0f)
+ continue;
+ w *= ((rtlight->style >= 0 && rtlight->style < MAX_LIGHTSTYLES) ? r_refdef.scene.rtlightstylevalue[rtlight->style] : 1);
+ VectorScale(rtlight->color, w, rtlight->bouncegrid_photoncolor);
+ // skip lights that will emit no photons
+ if (!VectorLength2(rtlight->bouncegrid_photoncolor))
+ continue;
+ // shoot particles from this light
+ // use a calculation for the number of particles that will not
+ // vary with lightstyle, otherwise we get randomized particle
+ // distribution, the seeded random is only consistent for a
+ // consistent number of particles on this light...
+ s = rtlight->radius;
+ lightintensity = VectorLength(rtlight->color) * (rtlight->ambientscale + rtlight->diffusescale + rtlight->specularscale);
+ if (lightindex >= range)
+ lightintensity *= settings->dlightparticlemultiplier;
+ rtlight->bouncegrid_photons = lightintensity * s * s * normalphotonscaling;
+ photoncount += rtlight->bouncegrid_photons;
+ VectorScale(rtlight->bouncegrid_photoncolor, settings->particleintensity * settings->energyperphoton, rtlight->bouncegrid_photoncolor);
+ // if the lightstyle happens to be off right now, we can skip actually
+ // firing the photons, but we did have to count them in the total.
+ //if (VectorLength2(rtlight->photoncolor) == 0.0f)
+ // rtlight->bouncegrid_photons = 0;
+ }
+ // the user provided an energyperphoton value which we try to use
+ // if that results in too many photons to shoot this frame, then we cap it
+ // which causes photons to appear/disappear from frame to frame, so we don't
+ // like doing that in the typical case
+ photonscaling = 1.0f;
+ photonintensity = 1.0f;
+ if (photoncount > settings->maxphotons)
+ {
+ photonscaling = settings->maxphotons / photoncount;
+ photonintensity = 1.0f / photonscaling;
+ }
+
+ // modify the lights to reflect our computed scaling
+ for (lightindex = 0; lightindex < range2; lightindex++)
+ {
+ if (lightindex < range)
+ {
+ light = (dlight_t *)Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex);
+ if (!light)
+ continue;
+ rtlight = &light->rtlight;
+ }
+ else
+ rtlight = r_refdef.scene.lights[lightindex - range];
+ rtlight->bouncegrid_photons *= photonscaling;
+ VectorScale(rtlight->bouncegrid_photoncolor, photonintensity, rtlight->bouncegrid_photoncolor);
+ }
+}
+
+static int R_Shadow_BounceGrid_SplatPathCompare(const void *pa, const void *pb)
+{
+ r_shadow_bouncegrid_splatpath_t *a = (r_shadow_bouncegrid_splatpath_t *)pa;
+ r_shadow_bouncegrid_splatpath_t *b = (r_shadow_bouncegrid_splatpath_t *)pb;
+ // we only really care about sorting by Z
+ if (a->point[2] < b->point[2])
+ return -1;
+ if (a->point[2] > b->point[2])
+ return 1;
+ return 0;
+}
+
+static void R_Shadow_BounceGrid_ClearPixels(void)
+{
+ // clear the highpixels array we'll be accumulating into
+ if (r_shadow_bouncegrid_state.blurpixels[0] == NULL)
+ r_shadow_bouncegrid_state.blurpixels[0] = (float *)Mem_Alloc(r_main_mempool, r_shadow_bouncegrid_state.numpixels * sizeof(float[4]));
+ if (r_shadow_bouncegrid_state.settings.blur && r_shadow_bouncegrid_state.blurpixels[1] == NULL)
+ r_shadow_bouncegrid_state.blurpixels[1] = (float *)Mem_Alloc(r_main_mempool, r_shadow_bouncegrid_state.numpixels * sizeof(float[4]));
+ r_shadow_bouncegrid_state.highpixels_index = 0;
+ r_shadow_bouncegrid_state.highpixels = r_shadow_bouncegrid_state.blurpixels[r_shadow_bouncegrid_state.highpixels_index];
+ memset(r_shadow_bouncegrid_state.highpixels, 0, r_shadow_bouncegrid_state.numpixels * sizeof(float[4]));
+}
+
+static void R_Shadow_BounceGrid_PerformSplats(void)
+{
+ r_shadow_bouncegrid_splatpath_t *splatpaths = r_shadow_bouncegrid_state.splatpaths;
+ r_shadow_bouncegrid_splatpath_t *splatpath;
+ float *highpixels = r_shadow_bouncegrid_state.highpixels;
+ int numsplatpaths = r_shadow_bouncegrid_state.numsplatpaths;
+ int splatindex;
+ vec3_t steppos;
+ vec3_t stepdelta;
+ vec3_t dir;
+ vec_t lightpathsize_current;
+ vec_t lightpathsize_perstep;
+ float splatcolor[32];
+ int resolution[3];
+ int pixelsperband = r_shadow_bouncegrid_state.pixelsperband;
+ int pixelbands = r_shadow_bouncegrid_state.pixelbands;
+ int numsteps;
+ int step;
+
+ // hush warnings about uninitialized data - pixelbands doesn't change but...
+ memset(splatcolor, 0, sizeof(splatcolor));
+
+ // we use this a lot, so get a local copy
+ VectorCopy(r_shadow_bouncegrid_state.resolution, resolution);
+
+ // sort the splats before we execute them, to reduce cache misses
+ if (r_shadow_bouncegrid_sortlightpaths.integer)
+ qsort(splatpaths, numsplatpaths, sizeof(*splatpaths), R_Shadow_BounceGrid_SplatPathCompare);
+
+ splatpath = splatpaths;
+ for (splatindex = 0;splatindex < numsplatpaths;splatindex++, splatpath++)
+ {
+ // calculate second order spherical harmonics values (average, slopeX, slopeY, slopeZ)
+ // accumulate average shotcolor
+ VectorCopy(splatpath->splatdir, dir);
+ splatcolor[ 0] = splatpath->splatcolor[0];
+ splatcolor[ 1] = splatpath->splatcolor[1];
+ splatcolor[ 2] = splatpath->splatcolor[2];
+ splatcolor[ 3] = 0.0f;
+ if (pixelbands > 1)
+ {
+ // store bentnormal in case the shader has a use for it,
+ // bentnormal is an intensity-weighted average of the directions,
+ // and will be normalized on conversion to texture pixels.
+ splatcolor[ 4] = dir[0] * splatpath->splatintensity;
+ splatcolor[ 5] = dir[1] * splatpath->splatintensity;
+ splatcolor[ 6] = dir[2] * splatpath->splatintensity;
+ splatcolor[ 7] = splatpath->splatintensity;
+ // for each color component (R, G, B) calculate the amount that a
+ // direction contributes
+ splatcolor[ 8] = splatcolor[0] * max(0.0f, dir[0]);
+ splatcolor[ 9] = splatcolor[0] * max(0.0f, dir[1]);
+ splatcolor[10] = splatcolor[0] * max(0.0f, dir[2]);
+ splatcolor[11] = 0.0f;
+ splatcolor[12] = splatcolor[1] * max(0.0f, dir[0]);
+ splatcolor[13] = splatcolor[1] * max(0.0f, dir[1]);
+ splatcolor[14] = splatcolor[1] * max(0.0f, dir[2]);
+ splatcolor[15] = 0.0f;
+ splatcolor[16] = splatcolor[2] * max(0.0f, dir[0]);
+ splatcolor[17] = splatcolor[2] * max(0.0f, dir[1]);
+ splatcolor[18] = splatcolor[2] * max(0.0f, dir[2]);
+ splatcolor[19] = 0.0f;
+ // and do the same for negative directions
+ splatcolor[20] = splatcolor[0] * max(0.0f, -dir[0]);
+ splatcolor[21] = splatcolor[0] * max(0.0f, -dir[1]);
+ splatcolor[22] = splatcolor[0] * max(0.0f, -dir[2]);
+ splatcolor[23] = 0.0f;
+ splatcolor[24] = splatcolor[1] * max(0.0f, -dir[0]);
+ splatcolor[25] = splatcolor[1] * max(0.0f, -dir[1]);
+ splatcolor[26] = splatcolor[1] * max(0.0f, -dir[2]);
+ splatcolor[27] = 0.0f;
+ splatcolor[28] = splatcolor[2] * max(0.0f, -dir[0]);
+ splatcolor[29] = splatcolor[2] * max(0.0f, -dir[1]);
+ splatcolor[30] = splatcolor[2] * max(0.0f, -dir[2]);
+ splatcolor[31] = 0.0f;
+ }
+ // calculate the number of steps we need to traverse this distance
+ VectorCopy(splatpath->point, steppos);
+ VectorCopy(splatpath->step, stepdelta);
+ numsteps = splatpath->remainingsplats;
+ lightpathsize_current = splatpath->splatsize_current + 1.0f; // add 1.0 for the gradient fade around the sphere
+ lightpathsize_perstep = splatpath->splatsize_perstep;
+ for (step = 0;step < numsteps;step++)
+ {
+ // the middle row/column/layer of each splat are full intensity
+ float splatmins[3];
+ float splatmaxs[3];
+ if (lightpathsize_current > MAXBOUNCEGRIDSPLATSIZE)
+ lightpathsize_current = MAXBOUNCEGRIDSPLATSIZE;
+ splatmins[0] = max(1.0f, steppos[0] - lightpathsize_current * 0.5f);
+ splatmins[1] = max(1.0f, steppos[1] - lightpathsize_current * 0.5f);
+ splatmins[2] = max(1.0f, steppos[2] - lightpathsize_current * 0.5f);
+ splatmaxs[0] = min(steppos[0] + lightpathsize_current * 0.5f, resolution[0] - 1.0f);
+ splatmaxs[1] = min(steppos[1] + lightpathsize_current * 0.5f, resolution[1] - 1.0f);
+ splatmaxs[2] = min(steppos[2] + lightpathsize_current * 0.5f, resolution[2] - 1.0f);
+ if (splatmaxs[0] > splatmins[0] && splatmaxs[1] > splatmins[1] && splatmaxs[2] > splatmins[2])
+ {
+ // it is within bounds... do the real work now
+ int xi, yi, zi, band, row;
+ float pixelpos[3];
+ float w;
+ float *p;
+ float colorscale = 1.0f / lightpathsize_current;
+ r_refdef.stats[r_stat_bouncegrid_splats]++;
+ // accumulate light onto the pixels
+ for (zi = (int)floor(splatmins[2]);zi < splatmaxs[2];zi++)
+ {
+ pixelpos[2] = zi + 0.5f;
+ for (yi = (int)floor(splatmins[1]); yi < splatmaxs[1]; yi++)
+ {
+ pixelpos[1] = yi + 0.5f;
+ row = (zi*resolution[1] + yi)*resolution[0];
+ for (xi = (int)floor(splatmins[0]); xi < splatmaxs[0]; xi++)
+ {
+ pixelpos[0] = xi + 0.5f;
+ // simple radial antialiased sphere - linear gradient fade over 1 pixel from the edge
+ w = lightpathsize_current - VectorDistance(pixelpos, steppos);
+ if (w > 0.0f)
+ {
+ if (w > 1.0f)
+ w = 1.0f;
+ w *= colorscale;
+ p = highpixels + 4 * (row + xi);
+ for (band = 0; band < pixelbands; band++, p += pixelsperband * 4)
+ {
+ // add to the pixel color
+ p[0] += splatcolor[band * 4 + 0] * w;
+ p[1] += splatcolor[band * 4 + 1] * w;
+ p[2] += splatcolor[band * 4 + 2] * w;
+ p[3] += splatcolor[band * 4 + 3] * w;
+ }
+ }
+ }
+ }
+ }
+ }
+ VectorAdd(steppos, stepdelta, steppos);
+ lightpathsize_current += lightpathsize_perstep;
+ }
+ }
+}
+
+static void R_Shadow_BounceGrid_BlurPixelsInDirection(const float *inpixels, float *outpixels, int off)
+{
+ const float *inpixel;
+ float *outpixel;
+ int pixelbands = r_shadow_bouncegrid_state.pixelbands;
+ int pixelband;
+ unsigned int index;
+ unsigned int x, y, z;
+ unsigned int resolution[3];
+ VectorCopy(r_shadow_bouncegrid_state.resolution, resolution);
+ for (pixelband = 0;pixelband < pixelbands;pixelband++)
+ {
+ for (z = 1;z < resolution[2]-1;z++)
+ {
+ for (y = 1;y < resolution[1]-1;y++)
+ {
+ x = 1;
+ index = ((pixelband*resolution[2]+z)*resolution[1]+y)*resolution[0]+x;
+ inpixel = inpixels + 4*index;
+ outpixel = outpixels + 4*index;
+ for (;x < resolution[0]-1;x++, inpixel += 4, outpixel += 4)
+ {
+ outpixel[0] = (inpixel[0] + inpixel[ off] + inpixel[0-off]) * (1.0f / 3.0);
+ outpixel[1] = (inpixel[1] + inpixel[1+off] + inpixel[1-off]) * (1.0f / 3.0);
+ outpixel[2] = (inpixel[2] + inpixel[2+off] + inpixel[2-off]) * (1.0f / 3.0);
+ outpixel[3] = (inpixel[3] + inpixel[3+off] + inpixel[3-off]) * (1.0f / 3.0);
+ }
+ }
+ }
+ }
+}
+
+static void R_Shadow_BounceGrid_BlurPixels(void)
+{
+ float *pixels[4];
+ unsigned int resolution[3];
+
+ if (!r_shadow_bouncegrid_state.settings.blur)
+ return;
+
+ VectorCopy(r_shadow_bouncegrid_state.resolution, resolution);
+
+ pixels[0] = r_shadow_bouncegrid_state.blurpixels[r_shadow_bouncegrid_state.highpixels_index];
+ pixels[1] = r_shadow_bouncegrid_state.blurpixels[r_shadow_bouncegrid_state.highpixels_index ^ 1];
+ pixels[2] = r_shadow_bouncegrid_state.blurpixels[r_shadow_bouncegrid_state.highpixels_index];
+ pixels[3] = r_shadow_bouncegrid_state.blurpixels[r_shadow_bouncegrid_state.highpixels_index ^ 1];
+
+ // blur on X
+ R_Shadow_BounceGrid_BlurPixelsInDirection(pixels[0], pixels[1], 4);
+ // blur on Y
+ R_Shadow_BounceGrid_BlurPixelsInDirection(pixels[1], pixels[2], resolution[0] * 4);
+ // blur on Z
+ R_Shadow_BounceGrid_BlurPixelsInDirection(pixels[2], pixels[3], resolution[0] * resolution[1] * 4);
+
+ // toggle the state, highpixels now points to pixels[3] result
+ r_shadow_bouncegrid_state.highpixels_index ^= 1;
+ r_shadow_bouncegrid_state.highpixels = r_shadow_bouncegrid_state.blurpixels[r_shadow_bouncegrid_state.highpixels_index];
+}
+
+static void R_Shadow_BounceGrid_ConvertPixelsAndUpload(void)
+{
+ int floatcolors = r_shadow_bouncegrid_state.settings.floatcolors;
+ unsigned char *pixelsbgra8 = NULL;
+ unsigned char *pixelbgra8;
+ unsigned short *pixelsrgba16f = NULL;
+ unsigned short *pixelrgba16f;
+ float *pixelsrgba32f = NULL;
+ float *highpixels = r_shadow_bouncegrid_state.highpixels;
+ float *highpixel;
+ float *bandpixel;
+ unsigned int pixelsperband = r_shadow_bouncegrid_state.pixelsperband;
+ unsigned int pixelbands = r_shadow_bouncegrid_state.pixelbands;
+ unsigned int pixelband;
+ unsigned int x, y, z;
+ unsigned int index, bandindex;
+ unsigned int resolution[3];
+ int c[4];
+ VectorCopy(r_shadow_bouncegrid_state.resolution, resolution);
+
+ if (r_shadow_bouncegrid_state.createtexture && r_shadow_bouncegrid_state.texture)
+ {
+ R_FreeTexture(r_shadow_bouncegrid_state.texture);
+ r_shadow_bouncegrid_state.texture = NULL;
+ }
+
+ // if bentnormals exist, we need to normalize and bias them for the shader
+ if (pixelbands > 1)
+ {
+ pixelband = 1;
+ for (z = 0;z < resolution[2]-1;z++)
+ {
+ for (y = 0;y < resolution[1]-1;y++)
+ {
+ x = 1;
+ index = ((pixelband*resolution[2]+z)*resolution[1]+y)*resolution[0]+x;
+ highpixel = highpixels + 4*index;
+ for (;x < resolution[0]-1;x++, index++, highpixel += 4)
+ {
+ // only convert pixels that were hit by photons
+ if (highpixel[3] != 0.0f)
+ VectorNormalize(highpixel);
+ VectorSet(highpixel, highpixel[0] * 0.5f + 0.5f, highpixel[1] * 0.5f + 0.5f, highpixel[2] * 0.5f + 0.5f);
+ highpixel[pixelsperband * 4 + 3] = 1.0f;
+ }
+ }
+ }
+ }
+
+ // start by clearing the pixels array - we won't be writing to all of it
+ //
+ // then process only the pixels that have at least some color, skipping
+ // the higher bands for speed on pixels that are black
+ switch (floatcolors)
+ {
+ case 0:
+ if (r_shadow_bouncegrid_state.u8pixels == NULL)
+ r_shadow_bouncegrid_state.u8pixels = (unsigned char *)Mem_Alloc(r_main_mempool, r_shadow_bouncegrid_state.numpixels * sizeof(unsigned char[4]));
+ pixelsbgra8 = r_shadow_bouncegrid_state.u8pixels;
+ for (pixelband = 0;pixelband < pixelbands;pixelband++)
+ {
+ if (pixelband == 1)
+ memset(pixelsbgra8 + pixelband * r_shadow_bouncegrid_state.bytesperband, 128, r_shadow_bouncegrid_state.bytesperband);
+ else
+ memset(pixelsbgra8 + pixelband * r_shadow_bouncegrid_state.bytesperband, 0, r_shadow_bouncegrid_state.bytesperband);
+ }
+ for (z = 1;z < resolution[2]-1;z++)
+ {
+ for (y = 1;y < resolution[1]-1;y++)
+ {
+ x = 1;
+ pixelband = 0;
+ index = ((pixelband*resolution[2]+z)*resolution[1]+y)*resolution[0]+x;
+ highpixel = highpixels + 4*index;
+ for (;x < resolution[0]-1;x++, index++, highpixel += 4)
+ {
+ // only convert pixels that were hit by photons
+ if (VectorLength2(highpixel))
+ {
+ // normalize the bentnormal now
+ if (pixelbands > 1)
+ {
+ VectorNormalize(highpixel + pixelsperband * 4);
+ highpixel[pixelsperband * 4 + 3] = 1.0f;
+ }
+ // process all of the pixelbands for this pixel
+ for (pixelband = 0, bandindex = index;pixelband < pixelbands;pixelband++, bandindex += pixelsperband)
+ {
+ pixelbgra8 = pixelsbgra8 + 4*bandindex;
+ bandpixel = highpixels + 4*bandindex;
+ c[0] = (int)(bandpixel[0]*256.0f);
+ c[1] = (int)(bandpixel[1]*256.0f);
+ c[2] = (int)(bandpixel[2]*256.0f);
+ c[3] = (int)(bandpixel[3]*256.0f);
+ pixelbgra8[2] = (unsigned char)bound(0, c[0], 255);
+ pixelbgra8[1] = (unsigned char)bound(0, c[1], 255);
+ pixelbgra8[0] = (unsigned char)bound(0, c[2], 255);
+ pixelbgra8[3] = (unsigned char)bound(0, c[3], 255);
+ }
+ }
+ }
+ }
+ }
+
+ if (!r_shadow_bouncegrid_state.createtexture)
+ R_UpdateTexture(r_shadow_bouncegrid_state.texture, pixelsbgra8, 0, 0, 0, resolution[0], resolution[1], resolution[2]*pixelbands);
+ else
+ r_shadow_bouncegrid_state.texture = R_LoadTexture3D(r_shadow_texturepool, "bouncegrid", resolution[0], resolution[1], resolution[2]*pixelbands, pixelsbgra8, TEXTYPE_BGRA, TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCELINEAR, 0, NULL);
+ break;
+ case 1:
+ if (r_shadow_bouncegrid_state.fp16pixels == NULL)
+ r_shadow_bouncegrid_state.fp16pixels = (unsigned short *)Mem_Alloc(r_main_mempool, r_shadow_bouncegrid_state.numpixels * sizeof(unsigned short[4]));
+ pixelsrgba16f = r_shadow_bouncegrid_state.fp16pixels;
+ memset(pixelsrgba16f, 0, r_shadow_bouncegrid_state.numpixels * sizeof(unsigned short[4]));
+ for (z = 1;z < resolution[2]-1;z++)
+ {
+ for (y = 1;y < resolution[1]-1;y++)
+ {
+ x = 1;
+ pixelband = 0;
+ index = ((pixelband*resolution[2]+z)*resolution[1]+y)*resolution[0]+x;
+ highpixel = highpixels + 4*index;
+ for (;x < resolution[0]-1;x++, index++, highpixel += 4)
+ {
+ // only convert pixels that were hit by photons
+ if (VectorLength2(highpixel))
+ {
+ // process all of the pixelbands for this pixel
+ for (pixelband = 0, bandindex = index;pixelband < pixelbands;pixelband++, bandindex += pixelsperband)
+ {
+ // time to have fun with IEEE 754 bit hacking...
+ union {
+ float f[4];
+ unsigned int raw[4];
+ } u;
+ pixelrgba16f = pixelsrgba16f + 4*bandindex;
+ bandpixel = highpixels + 4*bandindex;
+ VectorCopy4(bandpixel, u.f);
+ VectorCopy4(u.raw, c);
+ // this math supports negative numbers, snaps denormals to zero
+ //pixelrgba16f[0] = (unsigned short)(((c[0] & 0x7FFFFFFF) < 0x38000000) ? 0 : (((c[0] - 0x38000000) >> 13) & 0x7FFF) | ((c[0] >> 16) & 0x8000));
+ //pixelrgba16f[1] = (unsigned short)(((c[1] & 0x7FFFFFFF) < 0x38000000) ? 0 : (((c[1] - 0x38000000) >> 13) & 0x7FFF) | ((c[1] >> 16) & 0x8000));
+ //pixelrgba16f[2] = (unsigned short)(((c[2] & 0x7FFFFFFF) < 0x38000000) ? 0 : (((c[2] - 0x38000000) >> 13) & 0x7FFF) | ((c[2] >> 16) & 0x8000));
+ //pixelrgba16f[3] = (unsigned short)(((c[3] & 0x7FFFFFFF) < 0x38000000) ? 0 : (((c[3] - 0x38000000) >> 13) & 0x7FFF) | ((c[3] >> 16) & 0x8000));
+ // this math does not support negative
+ pixelrgba16f[0] = (unsigned short)((c[0] < 0x38000000) ? 0 : ((c[0] - 0x38000000) >> 13));
+ pixelrgba16f[1] = (unsigned short)((c[1] < 0x38000000) ? 0 : ((c[1] - 0x38000000) >> 13));
+ pixelrgba16f[2] = (unsigned short)((c[2] < 0x38000000) ? 0 : ((c[2] - 0x38000000) >> 13));
+ pixelrgba16f[3] = (unsigned short)((c[3] < 0x38000000) ? 0 : ((c[3] - 0x38000000) >> 13));
+ }
+ }
+ }
+ }
+ }
+
+ if (!r_shadow_bouncegrid_state.createtexture)
+ R_UpdateTexture(r_shadow_bouncegrid_state.texture, (const unsigned char *)pixelsrgba16f, 0, 0, 0, resolution[0], resolution[1], resolution[2]*pixelbands);
+ else
+ r_shadow_bouncegrid_state.texture = R_LoadTexture3D(r_shadow_texturepool, "bouncegrid", resolution[0], resolution[1], resolution[2]*pixelbands, (const unsigned char *)pixelsrgba16f, TEXTYPE_COLORBUFFER16F, TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCELINEAR, 0, NULL);
+ break;
+ case 2:
+ // our native format happens to match, so this is easy.
+ pixelsrgba32f = highpixels;
+
+ if (!r_shadow_bouncegrid_state.createtexture)
+ R_UpdateTexture(r_shadow_bouncegrid_state.texture, (const unsigned char *)pixelsrgba32f, 0, 0, 0, resolution[0], resolution[1], resolution[2]*pixelbands);
+ else
+ r_shadow_bouncegrid_state.texture = R_LoadTexture3D(r_shadow_texturepool, "bouncegrid", resolution[0], resolution[1], resolution[2]*pixelbands, (const unsigned char *)pixelsrgba32f, TEXTYPE_COLORBUFFER32F, TEXF_CLAMP | TEXF_ALPHA | TEXF_FORCELINEAR, 0, NULL);
+ break;
+ }
+
+ r_shadow_bouncegrid_state.lastupdatetime = realtime;
+}
+
+static void R_Shadow_BounceGrid_TracePhotons(r_shadow_bouncegrid_settings_t settings, unsigned int range, unsigned int range1, unsigned int range2, int flag)
+{
+ vec3_t bouncerandom[10];
+ dlight_t *light;
+ int bouncecount;
+ int hitsupercontentsmask;
+ int skipsupercontentsmask;
+ int maxbounce;
+ int shootparticles;
+ int shotparticles;
+ float bounceminimumintensity2;
+ trace_t cliptrace;
+ //trace_t cliptrace2;
+ //trace_t cliptrace3;
+ unsigned int lightindex;
+ unsigned int seed;
+ randomseed_t randomseed;
+ vec3_t shotcolor;
+ vec3_t baseshotcolor;
+ vec3_t surfcolor;
+ vec3_t clipend;
+ vec3_t clipstart;
+ vec3_t clipdiff;
+ vec_t radius;
+ vec_t distancetraveled;
+ vec_t s;
+ rtlight_t *rtlight;
+
+ // compute a seed for the unstable random modes
+ Math_RandomSeed_FromInts(&randomseed, 0, 0, 0, realtime * 1000.0);
+ seed = realtime * 1000.0;
+
+ r_shadow_bouncegrid_state.numsplatpaths = 0;
+
+ // figure out what we want to interact with
+ if (settings.hitmodels)
+ hitsupercontentsmask = SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY;// | SUPERCONTENTS_LIQUIDSMASK;
+ else
+ hitsupercontentsmask = SUPERCONTENTS_SOLID;// | SUPERCONTENTS_LIQUIDSMASK;
+ skipsupercontentsmask = SUPERCONTENTS_SKY; // this allows the e1m5 sky shadow to work by ignoring the sky surfaces
+ maxbounce = settings.maxbounce;
+
+ for (lightindex = 0;lightindex < range2;lightindex++)
+ {
+ if (lightindex < range)
+ {
+ light = (dlight_t *) Mem_ExpandableArray_RecordAtIndex(&r_shadow_worldlightsarray, lightindex);
+ if (!light)
+ continue;
+ rtlight = &light->rtlight;
+ }
+ else
+ rtlight = r_refdef.scene.lights[lightindex - range];
+ // note that this code used to keep track of residual photons and
+ // distribute them evenly to achieve exactly a desired photon count,
+ // but that caused unwanted flickering in dynamic mode
+ shootparticles = (int)floor(rtlight->bouncegrid_photons);
+ // skip if we won't be shooting any photons
+ if (!shootparticles)
+ continue;
+ radius = rtlight->radius * settings.lightradiusscale;
+ //s = settings.particleintensity / shootparticles;
+ //VectorScale(rtlight->bouncegrid_photoncolor, s, baseshotcolor);
+ VectorCopy(rtlight->bouncegrid_photoncolor, baseshotcolor);
+ if (VectorLength2(baseshotcolor) <= 0.0f)
+ continue;
+ r_refdef.stats[r_stat_bouncegrid_lights]++;
+ r_refdef.stats[r_stat_bouncegrid_particles] += shootparticles;
+ // we stop caring about bounces once the brightness goes below this fraction of the original intensity
+ bounceminimumintensity2 = VectorLength(baseshotcolor) * settings.bounceminimumintensity2;
+
+ // for seeded random we start the RNG with the position of the light
+ if (settings.rng_seed >= 0)
+ {
+ union
+ {
+ unsigned int i[4];
+ float f[4];
+ }
+ u;
+ u.f[0] = rtlight->shadoworigin[0];
+ u.f[1] = rtlight->shadoworigin[1];
+ u.f[2] = rtlight->shadoworigin[2];
+ u.f[3] = 1;
+ switch (settings.rng_type)
+ {
+ default:
+ case 0:
+ // we have to shift the seed provided by the user because the result must be odd
+ Math_RandomSeed_FromInts(&randomseed, u.i[0], u.i[1], u.i[2], u.i[3] ^ (settings.rng_seed << 1));
+ break;
+ case 1:
+ seed = u.i[0] ^ u.i[1] ^ u.i[2] ^ u.i[3] ^ settings.rng_seed;
+ break;
+ }
+ }
+
+ for (shotparticles = 0;shotparticles < shootparticles;shotparticles++)
+ {
+ VectorCopy(baseshotcolor, shotcolor);
+ VectorCopy(rtlight->shadoworigin, clipstart);
+ switch (settings.rng_type)
+ {
+ default:
+ case 0:
+ VectorLehmerRandom(&randomseed, clipend);
+ if (settings.bounceanglediffuse)
+ {
+ // we want random to be stable, so we still have to do all the random we would have done
+ for (bouncecount = 0; bouncecount < maxbounce; bouncecount++)
+ VectorLehmerRandom(&randomseed, bouncerandom[bouncecount]);
+ }
+ break;
+ case 1:
+ VectorCheeseRandom(seed, clipend);
+ if (settings.bounceanglediffuse)
+ {
+ // we want random to be stable, so we still have to do all the random we would have done
+ for (bouncecount = 0; bouncecount < maxbounce; bouncecount++)
+ VectorCheeseRandom(seed, bouncerandom[bouncecount]);
+ }
+ break;
+ }
+
+ // we want a uniform distribution spherically, not merely within the sphere
+ if (settings.normalizevectors)
+ VectorNormalize(clipend);
+
+ VectorMA(clipstart, radius, clipend, clipend);
+ distancetraveled = 0.0f;
+ for (bouncecount = 0;;bouncecount++)
+ {
+ r_refdef.stats[r_stat_bouncegrid_traces]++;
+ rtlight->bouncegrid_traces++;
+ //r_refdef.scene.worldmodel->TraceLineAgainstSurfaces(r_refdef.scene.worldmodel, NULL, NULL, &cliptrace, clipstart, clipend, hitsupercontentsmask);
+ //r_refdef.scene.worldmodel->TraceLine(r_refdef.scene.worldmodel, NULL, NULL, &cliptrace2, clipstart, clipend, hitsupercontentsmask);
+ if (settings.staticmode || settings.rng_seed < 0)
+ {
+ // static mode fires a LOT of rays but none of them are identical, so they are not cached
+ // non-stable random in dynamic mode also never reuses a direction, so there's no reason to cache it
+ cliptrace = CL_TraceLine(clipstart, clipend, settings.staticmode ? MOVE_WORLDONLY : (settings.hitmodels ? MOVE_HITMODEL : MOVE_NOMONSTERS), NULL, hitsupercontentsmask, skipsupercontentsmask, collision_extendmovelength.value, true, false, NULL, true, true);
+ }
+ else
+ {
+ // dynamic mode fires many rays and most will match the cache from the previous frame
+ cliptrace = CL_Cache_TraceLineSurfaces(clipstart, clipend, settings.staticmode ? MOVE_WORLDONLY : (settings.hitmodels ? MOVE_HITMODEL : MOVE_NOMONSTERS), hitsupercontentsmask, skipsupercontentsmask);
+ }
+ if (bouncecount > 0 || settings.includedirectlighting)
+ {
+ vec3_t hitpos;
+ VectorCopy(cliptrace.endpos, hitpos);
+ R_Shadow_BounceGrid_AddSplatPath(clipstart, hitpos, shotcolor, distancetraveled);
+ }
+ distancetraveled += VectorDistance(clipstart, cliptrace.endpos);
+ s = VectorDistance(rtlight->shadoworigin, cliptrace.endpos);
+ if (rtlight->bouncegrid_effectiveradius < s)
+ rtlight->bouncegrid_effectiveradius = s;
+ if (cliptrace.fraction >= 1.0f)
+ break;
+ r_refdef.stats[r_stat_bouncegrid_hits]++;
+ rtlight->bouncegrid_hits++;
+ if (bouncecount >= maxbounce)
+ break;
+ // scale down shot color by bounce intensity and texture color (or 50% if no texture reported)
+ // also clamp the resulting color to never add energy, even if the user requests extreme values
+ if (cliptrace.hittexture && cliptrace.hittexture->currentskinframe)
+ VectorCopy(cliptrace.hittexture->currentskinframe->avgcolor, surfcolor);
+ else
+ VectorSet(surfcolor, 0.5f, 0.5f, 0.5f);
+ VectorScale(surfcolor, settings.particlebounceintensity, surfcolor);
+ surfcolor[0] = min(surfcolor[0], 1.0f);
+ surfcolor[1] = min(surfcolor[1], 1.0f);
+ surfcolor[2] = min(surfcolor[2], 1.0f);
+ VectorMultiply(shotcolor, surfcolor, shotcolor);
+ if (VectorLength2(shotcolor) <= bounceminimumintensity2)
+ break;
+ r_refdef.stats[r_stat_bouncegrid_bounces]++;
+ if (settings.bounceanglediffuse)
+ {
+ // random direction, primarily along plane normal
+ s = VectorDistance(cliptrace.endpos, clipend);
+ VectorMA(cliptrace.plane.normal, 0.95f, bouncerandom[bouncecount], clipend);
+ VectorNormalize(clipend);
+ VectorScale(clipend, s, clipend);
+ }
+ else
+ {
+ // reflect the remaining portion of the line across plane normal
+ VectorSubtract(clipend, cliptrace.endpos, clipdiff);
+ VectorReflect(clipdiff, 1.0, cliptrace.plane.normal, clipend);
+ }
+ // calculate the new line start and end
+ VectorCopy(cliptrace.endpos, clipstart);
+ VectorAdd(clipstart, clipend, clipend);
+ }
+ }
+ }
+}
+
+void R_Shadow_UpdateBounceGridTexture(void)
+{
+ int flag = r_refdef.scene.rtworld ? LIGHTFLAG_REALTIMEMODE : LIGHTFLAG_NORMALMODE;
+ r_shadow_bouncegrid_settings_t settings;
+ qboolean enable = false;
+ qboolean settingschanged;
+ unsigned int range; // number of world lights
+ unsigned int range1; // number of dynamic lights (or zero if disabled)
+ unsigned int range2; // range+range1
+
+ enable = R_Shadow_BounceGrid_CheckEnable(flag);
+
+ R_Shadow_BounceGrid_GenerateSettings(&settings);
+
+ // changing intensity does not require an update
+ r_shadow_bouncegrid_state.intensity = r_shadow_bouncegrid_intensity.value;
+
+ settingschanged = memcmp(&r_shadow_bouncegrid_state.settings, &settings, sizeof(settings)) != 0;
+
+ // when settings change, we free everything as it is just simpler that way.
+ if (settingschanged || !enable)
+ {
+ // not enabled, make sure we free anything we don't need anymore.
+ if (r_shadow_bouncegrid_state.texture)
+ {
+ R_FreeTexture(r_shadow_bouncegrid_state.texture);
+ r_shadow_bouncegrid_state.texture = NULL;
+ }
+ r_shadow_bouncegrid_state.highpixels = NULL;
+ if (r_shadow_bouncegrid_state.blurpixels[0]) Mem_Free(r_shadow_bouncegrid_state.blurpixels[0]); r_shadow_bouncegrid_state.blurpixels[0] = NULL;
+ if (r_shadow_bouncegrid_state.blurpixels[1]) Mem_Free(r_shadow_bouncegrid_state.blurpixels[1]); r_shadow_bouncegrid_state.blurpixels[1] = NULL;
+ if (r_shadow_bouncegrid_state.u8pixels) Mem_Free(r_shadow_bouncegrid_state.u8pixels); r_shadow_bouncegrid_state.u8pixels = NULL;
+ if (r_shadow_bouncegrid_state.fp16pixels) Mem_Free(r_shadow_bouncegrid_state.fp16pixels); r_shadow_bouncegrid_state.fp16pixels = NULL;
+ if (r_shadow_bouncegrid_state.splatpaths) Mem_Free(r_shadow_bouncegrid_state.splatpaths); r_shadow_bouncegrid_state.splatpaths = NULL;
+ r_shadow_bouncegrid_state.maxsplatpaths = 0;
+ r_shadow_bouncegrid_state.numpixels = 0;
+ r_shadow_bouncegrid_state.directional = false;
+
+ if (!enable)
+ return;
+ }
+
+ // if all the settings seem identical to the previous update, return
+ if (r_shadow_bouncegrid_state.texture && (settings.staticmode || realtime < r_shadow_bouncegrid_state.lastupdatetime + r_shadow_bouncegrid_dynamic_updateinterval.value) && !settingschanged)
+ return;
+
+ // store the new settings
+ r_shadow_bouncegrid_state.settings = settings;
+
+ R_Shadow_BounceGrid_UpdateSpacing();
+
+ // get the range of light numbers we'll be looping over:
+ // range = static lights
+ // range1 = dynamic lights (optional)
+ // range2 = range + range1
+ range = (unsigned int)Mem_ExpandableArray_IndexRange(&r_shadow_worldlightsarray); // checked
+ range1 = settings.staticmode ? 0 : r_refdef.scene.numlights;
+ range2 = range + range1;
+
+ // calculate weighting factors for distributing photons among the lights
+ R_Shadow_BounceGrid_AssignPhotons(&settings, range, range1, range2, flag);
+
+ // trace the photons from lights and accumulate illumination
+ R_Shadow_BounceGrid_TracePhotons(settings, range, range1, range2, flag);
+
+ // clear the texture
+ R_Shadow_BounceGrid_ClearPixels();
+
+ // accumulate the light splatting into texture
+ R_Shadow_BounceGrid_PerformSplats();
+
+ // apply a mild blur filter to the texture
+ R_Shadow_BounceGrid_BlurPixels();
+
+ // convert the pixels to lower precision and upload the texture
+ R_Shadow_BounceGrid_ConvertPixelsAndUpload();
+
+ // after we compute the static lighting we don't need to keep the highpixels array around
+ if (settings.staticmode)
+ {
+ r_shadow_bouncegrid_state.highpixels = NULL;
+ if (r_shadow_bouncegrid_state.blurpixels[0]) Mem_Free(r_shadow_bouncegrid_state.blurpixels[0]); r_shadow_bouncegrid_state.blurpixels[0] = NULL;
+ if (r_shadow_bouncegrid_state.blurpixels[1]) Mem_Free(r_shadow_bouncegrid_state.blurpixels[1]); r_shadow_bouncegrid_state.blurpixels[1] = NULL;
+ if (r_shadow_bouncegrid_state.u8pixels) Mem_Free(r_shadow_bouncegrid_state.u8pixels); r_shadow_bouncegrid_state.u8pixels = NULL;
+ if (r_shadow_bouncegrid_state.fp16pixels) Mem_Free(r_shadow_bouncegrid_state.fp16pixels); r_shadow_bouncegrid_state.fp16pixels = NULL;
+ if (r_shadow_bouncegrid_state.splatpaths) Mem_Free(r_shadow_bouncegrid_state.splatpaths); r_shadow_bouncegrid_state.splatpaths = NULL;
+ r_shadow_bouncegrid_state.maxsplatpaths = 0;
+ }
+}
+
+void R_Shadow_RenderMode_VisibleShadowVolumes(void)
+{
+ R_Shadow_RenderMode_Reset();
+ GL_BlendFunc(GL_ONE, GL_ONE);
+ GL_DepthRange(0, 1);
+ GL_DepthTest(r_showshadowvolumes.integer < 2);
+ GL_Color(0.0, 0.0125 * r_refdef.view.colorscale, 0.1 * r_refdef.view.colorscale, 1);
+ GL_PolygonOffset(r_refdef.shadowpolygonfactor, r_refdef.shadowpolygonoffset);CHECKGLERROR
+ GL_CullFace(GL_NONE);
+ r_shadow_rendermode = R_SHADOW_RENDERMODE_VISIBLEVOLUMES;
+}
+
+void R_Shadow_RenderMode_VisibleLighting(qboolean stenciltest, qboolean transparent)
+{
+ R_Shadow_RenderMode_Reset();
+ GL_BlendFunc(GL_ONE, GL_ONE);
+ GL_DepthRange(0, 1);
+ GL_DepthTest(r_showlighting.integer < 2);
+ GL_Color(0.1 * r_refdef.view.colorscale, 0.0125 * r_refdef.view.colorscale, 0, 1);
+ if (!transparent)
+ GL_DepthFunc(GL_EQUAL);
+ R_SetStencil(stenciltest, 255, GL_KEEP, GL_KEEP, GL_KEEP, GL_EQUAL, 128, 255);
+ r_shadow_rendermode = R_SHADOW_RENDERMODE_VISIBLELIGHTING;
+}
+
+void R_Shadow_RenderMode_End(void)
+{
+ R_Shadow_RenderMode_Reset();
+ R_Shadow_RenderMode_ActiveLight(NULL);
+ GL_DepthMask(true);
+ GL_Scissor(r_refdef.view.viewport.x, r_refdef.view.viewport.y, r_refdef.view.viewport.width, r_refdef.view.viewport.height);
+ r_shadow_rendermode = R_SHADOW_RENDERMODE_NONE;
+}
+
+int bboxedges[12][2] =
+{
+ // top
+ {0, 1}, // +X
+ {0, 2}, // +Y
+ {1, 3}, // Y, +X
+ {2, 3}, // X, +Y
+ // bottom
+ {4, 5}, // +X
+ {4, 6}, // +Y
+ {5, 7}, // Y, +X
+ {6, 7}, // X, +Y
+ // verticals
+ {0, 4}, // +Z
+ {1, 5}, // X, +Z
+ {2, 6}, // Y, +Z
+ {3, 7}, // XY, +Z
+};
+
+qboolean R_Shadow_ScissorForBBox(const float *mins, const float *maxs)
+{
+ if (!r_shadow_scissor.integer || r_shadow_usingdeferredprepass || r_trippy.integer)
+ {
+ r_shadow_lightscissor[0] = r_refdef.view.viewport.x;
+ r_shadow_lightscissor[1] = r_refdef.view.viewport.y;
+ r_shadow_lightscissor[2] = r_refdef.view.viewport.width;
+ r_shadow_lightscissor[3] = r_refdef.view.viewport.height;
+ return false;
+ }
+ if(R_ScissorForBBox(mins, maxs, r_shadow_lightscissor))
+ return true; // invisible
+ if(r_shadow_lightscissor[0] != r_refdef.view.viewport.x
+ || r_shadow_lightscissor[1] != r_refdef.view.viewport.y
+ || r_shadow_lightscissor[2] != r_refdef.view.viewport.width
+ || r_shadow_lightscissor[3] != r_refdef.view.viewport.height)
+ r_refdef.stats[r_stat_lights_scissored]++;
+ return false;
+}
+
+static void R_Shadow_RenderLighting_Light_Vertex_Shading(int firstvertex, int numverts, const float *diffusecolor, const float *ambientcolor)
+{
+ int i;
+ const float *vertex3f;
+ const float *normal3f;
+ float *color4f;
+ float dist, dot, distintensity, shadeintensity, v[3], n[3];
+ switch (r_shadow_rendermode)
+ {
+ case R_SHADOW_RENDERMODE_LIGHT_VERTEX3DATTEN:
+ case R_SHADOW_RENDERMODE_LIGHT_VERTEX2D1DATTEN:
+ if (VectorLength2(diffusecolor) > 0)
+ {
+ for (i = 0, vertex3f = rsurface.batchvertex3f + 3*firstvertex, normal3f = rsurface.batchnormal3f + 3*firstvertex, color4f = rsurface.passcolor4f + 4 * firstvertex;i < numverts;i++, vertex3f += 3, normal3f += 3, color4f += 4)
+ {
+ Matrix4x4_Transform(&rsurface.entitytolight, vertex3f, v);
+ Matrix4x4_Transform3x3(&rsurface.entitytolight, normal3f, n);
+ if ((dot = DotProduct(n, v)) < 0)