From 34c6c4cb2bb21feee5cc164bffd176228cec275e Mon Sep 17 00:00:00 2001 From: danij Date: Thu, 1 Aug 2013 06:17:19 +0100 Subject: [PATCH] BiasSurface: Further revised the internal design of this component In this iteration vertex illumination is fully decoupled from the "surface", whose main job now is tracking changes and routing through update notifications from sources to vertexes. The next step is separating the illumination form the surface so it can again be managed with an external buffer (the difference being that now these are thinking VertexIllum instances). --- doomsday/client/include/render/biassurface.h | 29 +- doomsday/client/src/render/biassurface.cpp | 493 +++++++++++-------- doomsday/client/src/render/rend_main.cpp | 18 +- doomsday/client/src/world/bspleaf.cpp | 5 +- doomsday/client/src/world/segment.cpp | 4 +- 5 files changed, 321 insertions(+), 228 deletions(-) diff --git a/doomsday/client/include/render/biassurface.h b/doomsday/client/include/render/biassurface.h index 27e1269f54..ec07940406 100644 --- a/doomsday/client/include/render/biassurface.h +++ b/doomsday/client/include/render/biassurface.h @@ -20,6 +20,7 @@ #ifndef DENG_RENDER_SHADOWBIAS_SURFACE_H #define DENG_RENDER_SHADOWBIAS_SURFACE_H +#include #include #include "MapElement" @@ -33,6 +34,10 @@ class BiasTracker; */ class BiasSurface { +public: + /// An unknown light contributor was referenced @ingroup errors + DENG2_ERROR(UnknownContributorError); + public: /** * Construct a new surface. @@ -58,16 +63,30 @@ class BiasSurface void updateAfterMove(); + /** + * Returns a light source contributor by @a index. + */ + BiasSource &contributor(int index) const; + + /** + * Determine the earliest time in milliseconds that an affecting source + * was changed/deleted. + */ + uint timeOfLatestContributorUpdate() const; + /** * Perform lighting for the supplied geometry. It is assumed that this * geometry has the @em same number of vertices as the bias surface. * - * @param vertCount Number of vertices to be lit. - * @param positions World coordinates for each vertex. - * @param colors Final lighting values will be written here. + * @param surfaceNormal Normal of the surface being lit. + * @param biasTime Current time in milliseconds for bias. + * @param vertCount Number of vertices to be lit. + * @param positions World coordinates for each vertex. + * @param colors Final lighting values will be written here. */ - void lightPoly(de::Vector3f const &surfaceNormal, int vertCount, - struct rvertex_s const *positions, struct ColorRawf_s *colors); + void lightPoly(de::Vector3f const &surfaceNormal, uint biasTime, + int vertCount, struct rvertex_s const *positions, + struct ColorRawf_s *colors); private: DENG2_PRIVATE(d) diff --git a/doomsday/client/src/render/biassurface.cpp b/doomsday/client/src/render/biassurface.cpp index 553cb0cd68..a4797ef301 100644 --- a/doomsday/client/src/render/biassurface.cpp +++ b/doomsday/client/src/render/biassurface.cpp @@ -19,6 +19,7 @@ #include +#include #include #include "de_base.h" @@ -44,260 +45,269 @@ static int lightSpeed = 130; //cvar static int devUpdateAffected = true; //cvar static int devUseSightCheck = true; //cvar -struct Contributor +static byte activeContributors; +static byte changedContributions; + +class VertexIllum { - BiasSource *source; - float influence; -}; + BiasSurface &surface; ///< The owning surface. + Vector3f color; ///< Current light color. + Vector3f dest; ///< Destination light color (interpolated to). + uint updateTime; ///< When the value was calculated. + bool interpolating; ///< Set to @c true during interpolation. -typedef Contributor Contributors[MAX_CONTRIBUTORS]; + /** + * Cast lighting contributions from each source that affects the map point. + * Order is the same as that in the affected surface. + */ + Vector3f casted[MAX_CONTRIBUTORS]; -/** - * evalLighting uses these -- they must be set before it is called. - */ -static uint biasTime; -static MapElement const *bspRoot; -static Vector3f const *mapSurfaceNormal; +public: + VertexIllum(BiasSurface &surface) + : surface(surface), + updateTime(0), + interpolating(false) + {} -/// @todo defer allocation of most data -- adopt a 'fly-weight' approach. -DENG2_PIMPL_NOREF(BiasSurface), -DENG2_OBSERVES(BiasSource, Deletion) -{ /** - * Per-vertex illumination data. + * (Re-)Evaluate lighting for this world point. + * + * @param color Final color will be written here. + * @param point Point in the map to evaluate. Assumed not to have + * moved since the last call unless the light source + * contributions have since been updated. + * @param normalAtPoint Surface normal at @a point. Also assumed not to + * have changed since the last call. + * @param biasTime Time in milliseconds of the last bias frame update. */ - struct VertexIllum + void evaluate(Vector3f &color, Vector3d const &point, + Vector3f const &normalAtPoint, uint biasTime) { - Vector3f color; ///< Current light color at the vertex. - Vector3f dest; ///< Destination light color at the vertex (interpolated to). - uint updateTime; ///< When the value was calculated. - bool interpolating; ///< Set to @c true during interpolation. - - /** - * Light contributions from each source affecting the vertex. The order - * of which being the same as that in the affected surface. - */ - Vector3f casted[MAX_CONTRIBUTORS]; - - VertexIllum() : updateTime(0), interpolating(false) - {} - - /** - * Interpolate between current and destination. - */ - void lerp(Vector3f &result, uint currentTime) + // Does the surface have any lighting changes to apply? + if(changedContributions) { - if(!interpolating) + if(activeContributors & changedContributions) { - // Use the current color. - result = color; - return; + /* + * Recalculate the contribution for each light. + * We can reuse the previously calculated value for a source + * if it hasn't changed. + */ + for(int i = 0; i < MAX_CONTRIBUTORS; ++i) + { + if(activeContributors & changedContributions & (1 << i)) + { + updateContribution(i, point, normalAtPoint); + } + } } - float inter = (currentTime - updateTime) / float( lightSpeed ); - - if(inter > 1) - { - interpolating = false; - color = dest; - - result = color; - } - else - { - result = color + (dest - color) * inter; - } + applyLightingChanges(activeContributors, biasTime); } - /** - * @return Light contribution by the specified contributor. - */ - Vector3f &contribution(int contributorIndex) - { - DENG_ASSERT(contributorIndex >= 0 && contributorIndex < MAX_CONTRIBUTORS); - return casted[contributorIndex]; - } + // Factor in the current color (and perform interpolation if needed). + lerp(color, biasTime); + } - void updateContribution(int contributorIndex, BiasSource *source, - Vector3d const &surfacePoint, Vector3f const &surfaceNormal, - MapElement const &bspRoot) - { - DENG_ASSERT(source != 0); + /// @copydoc evaluate() + void evaluate(ColorRawf &color, Vector3d const &point, + Vector3f const &normalAtPoint, uint biasTime) + { + Vector3f tmp; + evaluate(tmp, point, normalAtPoint, biasTime); + for(int c = 0; c < 3; ++c) + color.rgba[c] += tmp[c]; + } - Vector3f &casted = contribution(contributorIndex); +private: + /** + * Returns a previous light contribution by unique contributor index. + */ + inline Vector3f &contribution(int index) + { + DENG_ASSERT(index >= 0 && index < MAX_CONTRIBUTORS); + return casted[index]; + } - /// @todo LineSightTest should (optionally) perform this test. - Sector *sector = &source->bspLeafAtOrigin().sector(); - if((!sector->floor().surface().hasSkyMaskedMaterial() && - source->origin().z < sector->floor().visHeight()) || - (!sector->ceiling().surface().hasSkyMaskedMaterial() && - source->origin().z > sector->ceiling().visHeight())) - { - // This affecting source does not contribute any light. - casted = Vector3f(); - return; - } + /** + * Update any changed lighting contributions. + * + * @param surfacePoint Position of the vertex in the map coordinate space. + */ + void applyLightingChanges(byte activeContributors, uint biasTime) + { +#define COLOR_CHANGE_THRESHOLD 0.1f // Ignore small variations for perf - Vector3d sourceToSurface = source->origin() - surfacePoint; + // Determine the new color (initially, black). + Vector3f newColor; - if(devUseSightCheck && - !LineSightTest(source->origin(), - surfacePoint + sourceToSurface / 100).trace(bspRoot)) + // Do we need to re-accumulate light contributions? + if(activeContributors) + { + /// Maximum accumulated color strength. + static Vector3f const saturated(1, 1, 1); + + for(int i = 0; i < MAX_CONTRIBUTORS; ++i) { - // LOS fail. - // This affecting source does not contribute any light. - casted = Vector3f(); - return; + if(activeContributors & (1 << i)) + { + newColor += contribution(i); + + // Stop once fully saturated. + if(newColor >= saturated) + break; + } } - double distance = sourceToSurface.length(); - double dot = sourceToSurface.normalize().dot(surfaceNormal); + // Clamp. + newColor = newColor.min(saturated); + } - // The surface faces away from the light? - if(dot < 0) + // Is there a new destination? + if(!activeContributors || + (!de::fequal(dest.x, newColor.x, COLOR_CHANGE_THRESHOLD) || + !de::fequal(dest.y, newColor.y, COLOR_CHANGE_THRESHOLD) || + !de::fequal(dest.z, newColor.z, COLOR_CHANGE_THRESHOLD))) + { + if(interpolating) { - casted = Vector3f(); - return; + // Must not lose the half-way interpolation. + Vector3f mid; lerp(mid, biasTime); + + // This is current color at this very moment. + color = mid; } - // Apply light casted from this source. - float strength = dot * source->evaluateIntensity() / distance; - casted = source->color() * de::clamp(0.f, strength, 1.f); + // This is what we will be interpolating to. + dest = newColor; + interpolating = true; + updateTime = surface.timeOfLatestContributorUpdate(); } - }; - QVector illums; /// @todo use std::vector instead? - Contributors contributors; - byte activeContributors; - byte changedContributions; - - uint lastUpdateOnFrame; - uint lastSourceDeletion; // Milliseconds. - - Instance(int size) - : illums(size), - activeContributors(0), - changedContributions(0), - lastUpdateOnFrame(0), - lastSourceDeletion(0) - { - zap(contributors); +#undef COLOR_CHANGE_THRESHOLD } /** - * Perform lighting calculations for a vertex. + * Update the lighting contribution for the specified contributor @a index. * - * @param vi Illumination data for the vertex to be evaluated. - * @param surfacePoint Position of the vertex in the map coordinate space. + * @param index Unique index of the contributor. + * @param point Point in the map to evaluate. + * @param normalAtPoint Surface normal at @a point. */ - Vector3f evalLighting(VertexIllum &vi, Vector3d const &surfacePoint) + void updateContribution(int index, Vector3d const &point, + Vector3f const &normalAtPoint) { -#define COLOR_CHANGE_THRESHOLD 0.1f // Ignore small variations for perf + BiasSource const &source = surface.contributor(index); - static Vector3f const saturated(1, 1, 1); + Vector3f &casted = contribution(index); - // Do we have any lighting changes to apply? - if(changedContributions) + /// @todo LineSightTest should (optionally) perform this test. + Sector *sector = &source.bspLeafAtOrigin().sector(); + if((!sector->floor().surface().hasSkyMaskedMaterial() && + source.origin().z < sector->floor().visHeight()) || + (!sector->ceiling().surface().hasSkyMaskedMaterial() && + source.origin().z > sector->ceiling().visHeight())) { - uint latestSourceUpdate = 0; + // This affecting source does not contribute any light. + casted = Vector3f(); + return; + } - Contributor *ctbr = contributors; - for(int i = 0; i < MAX_CONTRIBUTORS; ++i, ctbr++) - { - if(!(changedContributions & (1 << i))) - continue; + Vector3d sourceToSurface = source.origin() - point; - // Determine the earliest time an affecting source changed. - if(!ctbr->source && !(activeContributors & (1 << i))) - { - // The source of the contribution was deleted. - if(latestSourceUpdate < lastSourceDeletion) - latestSourceUpdate = lastSourceDeletion; - } - else if(latestSourceUpdate < ctbr->source->lastUpdateTime()) - { - latestSourceUpdate = ctbr->source->lastUpdateTime(); - } - } + /// @todo Do not assume the current map. + MapElement &bspRoot = App_World().map().bspRoot(); - if(activeContributors & changedContributions) - { - /* - * Recalculate the contribution for each light. - * We can reuse the previously calculated value for a source - * if it hasn't changed. - */ - ctbr = contributors; - for(int i = 0; i < MAX_CONTRIBUTORS; ++i, ctbr++) - { - if(activeContributors & changedContributions & (1 << i)) - { - vi.updateContribution(i, ctbr->source, surfacePoint, - *mapSurfaceNormal, *bspRoot); - } - } - } + if(devUseSightCheck && + !LineSightTest(source.origin(), + point + sourceToSurface / 100).trace(bspRoot)) + { + // LOS fail. + // This affecting source does not contribute any light. + casted = Vector3f(); + return; + } - // Determine the new color (initially, black). - Vector3f newColor; + double distance = sourceToSurface.length(); + double dot = sourceToSurface.normalize().dot(normalAtPoint); - // Do we need to accumulate light contributions? - if(activeContributors) - { - ctbr = contributors; - for(int i = 0; i < MAX_CONTRIBUTORS; ++i, ctbr++) - { - if(activeContributors & (1 << i)) - { - newColor += vi.contribution(i); + // The surface faces away from the light? + if(dot < 0) + { + casted = Vector3f(); + return; + } - // Stop once fully saturated. - if(newColor >= saturated) - break; - } - } + // Apply light casted from this source. + float strength = dot * source.evaluateIntensity() / distance; + casted = source.color() * de::clamp(0.f, strength, 1.f); + } - // Clamp. - newColor = newColor.min(saturated); - } + /** + * Interpolate between current and destination. + */ + void lerp(Vector3f &result, uint currentTime) + { + if(!interpolating) + { + // Use the current color. + result = color; + return; + } - // Is there a new destination? - if(!activeContributors || - (!de::fequal(vi.dest.x, newColor.x, COLOR_CHANGE_THRESHOLD) || - !de::fequal(vi.dest.y, newColor.y, COLOR_CHANGE_THRESHOLD) || - !de::fequal(vi.dest.z, newColor.z, COLOR_CHANGE_THRESHOLD))) - { - if(vi.interpolating) - { - // Must not lose the half-way interpolation. - Vector3f mid; vi.lerp(mid, biasTime); + float inter = (currentTime - updateTime) / float( lightSpeed ); - // This is current color at this very moment. - vi.color = mid; - } + if(inter > 1) + { + interpolating = false; + color = dest; - // This is what we will be interpolating to. - vi.dest = newColor; - vi.interpolating = true; - vi.updateTime = latestSourceUpdate; - } + result = color; } - - // Finalize lighting (i.e., perform interpolation if needed). - Vector3f color; - vi.lerp(color, biasTime); - - // Apply an ambient light term? - Map &map = bspRoot->map(); - if(map.hasLightGrid()) + else { - color = (color + map.lightGrid().evaluate(surfacePoint)) - .min(saturated); // clamp + result = color + (dest - color) * inter; } + } +}; - return color; +struct Contributor +{ + BiasSource *source; + float influence; +}; -#undef COLOR_CHANGE_THRESHOLD +/** + * @todo Defer allocation of most data -- adopt a 'fly-weight' approach. + * + * @todo Do not observe source deletion. A better solution would represent any + * source deletions within the change tracker. + */ +DENG2_PIMPL_NOREF(BiasSurface), +DENG2_OBSERVES(BiasSource, Deletion) +{ + QVector illums; /// @todo use an external allocator. + + Contributor contributors[MAX_CONTRIBUTORS]; + byte activeContributors; + byte changedContributions; + + uint lastUpdateOnFrame; + uint lastSourceDeletion; // Milliseconds. + + Instance(int size) + : illums(size), + activeContributors(0), + changedContributions(0), + lastUpdateOnFrame(0), + lastSourceDeletion(0) + { + zap(contributors); + } + + ~Instance() + { + qDeleteAll(illums); } /// Observes BiasSource Deletion @@ -313,9 +323,7 @@ DENG2_OBSERVES(BiasSource, Deletion) changedContributions |= 1 << i; // Remember the current time (used for interpolation). - /// @todo Do not assume the 'current' map. A better solution - /// would represent source deletion in the per-frame change - /// notifications. + /// @todo Do not assume the 'current' map. lastSourceDeletion = App_World().map().biasCurrentTime(); break; } @@ -460,25 +468,72 @@ void BiasSurface::updateAfterMove() } } -void BiasSurface::lightPoly(Vector3f const &surfaceNormal, int vertCount, - rvertex_t const *positions, ColorRawf *colors) +BiasSource &BiasSurface::contributor(int index) const +{ + if(index >= 0 && index < MAX_CONTRIBUTORS && + (d->activeContributors & (1 << index))) + { + DENG_ASSERT(d->contributors[index].source != 0); + return *d->contributors[index].source; + } + /// @throw UnknownContributorError An invalid contributor index was specified. + throw UnknownContributorError("BiasSurface::lightContributor", QString("Index %1 invalid/out of range").arg(index)); +} + +uint BiasSurface::timeOfLatestContributorUpdate() const +{ + uint latest = 0; + + if(d->changedContributions) + { + Contributor const *ctbr = d->contributors; + for(int i = 0; i < MAX_CONTRIBUTORS; ++i, ctbr++) + { + if(!(d->changedContributions & (1 << i))) + continue; + + if(!ctbr->source && !(d->activeContributors & (1 << i))) + { + // The source of the contribution was deleted. + if(latest < d->lastSourceDeletion) + latest = d->lastSourceDeletion; + } + else if(latest < ctbr->source->lastUpdateTime()) + { + latest = ctbr->source->lastUpdateTime(); + } + } + } + + return latest; +} + +void BiasSurface::lightPoly(Vector3f const &surfaceNormal, uint biasTime, + int vertCount, rvertex_t const *positions, ColorRawf *colors) { DENG_ASSERT(vertCount == d->illums.count()); // sanity check DENG_ASSERT(positions != 0 && colors != 0); - // Configure global arguments for evalLighting(), for perf - bspRoot = &App_World().map().bspRoot(); - biasTime = bspRoot->map().biasCurrentTime(); - mapSurfaceNormal = &surfaceNormal; - - int i; rvertex_t const *vtx = positions; - for(i = 0; i < vertCount; ++i, vtx++) + // Time to allocate the illumination data? + if(!d->illums[0]) { - Vector3d origin(vtx->pos[VX], vtx->pos[VY], vtx->pos[VZ]); - Vector3f light = d->evalLighting(d->illums[i], origin); + for(int i = 0; i < d->illums.count(); ++i) + { + d->illums[i] = new VertexIllum(*this); + } + } - for(int c = 0; c < 3; ++c) - colors[i].rgba[c] = light[c]; + // Configure global arguments for BiasVertex::evalLighting(), for perf + /// @todo refactor away. + activeContributors = d->activeContributors; + changedContributions = d->changedContributions; + + rvertex_t const *vtx = positions; + ColorRawf *color = colors; + for(int i = 0; i < vertCount; ++i, vtx++, color++) + { + Vector3d surfacePoint(vtx->pos[VX], vtx->pos[VY], vtx->pos[VZ]); + d->illums[i]->evaluate(*color, surfacePoint, surfaceNormal, biasTime); } // Any changes from contributors will have now been applied. diff --git a/doomsday/client/src/render/rend_main.cpp b/doomsday/client/src/render/rend_main.cpp index e3790f825f..548f757aef 100644 --- a/doomsday/client/src/render/rend_main.cpp +++ b/doomsday/client/src/render/rend_main.cpp @@ -914,7 +914,23 @@ static bool renderWorldPoly(rvertex_t *rvertices, uint numVertices, // Non-uniform color. if(useBias) { - // Do BIAS lighting for this poly. + // Apply the ambient light term from the grid (if available). + Map &map = p.elem->map(); + if(map.hasLightGrid()) + { + rvertex_t const *vtx = rvertices; + ColorRawf *color = rcolors; + for(uint i = 0; i < numVertices; ++i, vtx++, color++) + { + Vector3d surfacePoint(vtx->pos[VX], vtx->pos[VY], vtx->pos[VZ]); + Vector3f ambientLight = map.lightGrid().evaluate(surfacePoint); + + for(int c = 0; c < 3; ++c) + color->rgba[c] = ambientLight[c]; + } + } + + // Apply shadow bias contributions. if(p.elem->type() == DMU_BSPLEAF) { p.elem->as()-> diff --git a/doomsday/client/src/world/bspleaf.cpp b/doomsday/client/src/world/bspleaf.cpp index e6b1edc08f..fa151284b3 100644 --- a/doomsday/client/src/world/bspleaf.cpp +++ b/doomsday/client/src/world/bspleaf.cpp @@ -568,8 +568,9 @@ void BspLeaf::lightPoly(int group, int vertCount, rvertex_t const *positions, d->updateAffected(bsuf, group); } - bsuf.lightPoly(d->sector->plane(group).surface().normal(), vertCount, - positions, colors); + Surface &surface = d->sector->plane(group).surface(); + bsuf.lightPoly(surface.normal(), map().biasCurrentTime(), + vertCount, positions, colors); } ShadowLink *BspLeaf::firstShadowLink() const diff --git a/doomsday/client/src/world/segment.cpp b/doomsday/client/src/world/segment.cpp index cdcaeaf26b..572438530a 100644 --- a/doomsday/client/src/world/segment.cpp +++ b/doomsday/client/src/world/segment.cpp @@ -278,7 +278,9 @@ void Segment::lightPoly(int group, int vertCount, rvertex_t const *positions, d->updateAffected(bsuf, group); } - bsuf.lightPoly(d->lineSide->middle().normal(), vertCount, positions, colors); + Surface &surface = d->lineSide->middle(); + bsuf.lightPoly(surface.normal(), map().biasCurrentTime(), + vertCount, positions, colors); } #endif // __CLIENT__