Moving forward

This commit is contained in:
samcake 2017-05-08 17:33:24 -07:00
parent d3724116d9
commit bbb513220b
5 changed files with 338 additions and 226 deletions

View file

@ -26,7 +26,64 @@
#include <LightPayload.h>
#include "DeferredLightingEffect.h"
class RenderableZoneEntityItemMeta {
public:
RenderableZoneEntityItemMeta(EntityItemPointer entity);
~RenderableZoneEntityItemMeta();
typedef render::Payload<RenderableZoneEntityItemMeta> Payload;
typedef Payload::DataPointer Pointer;
EntityItemPointer entity;
void render(RenderArgs* args);
void setVisible(bool visible) { _isVisible = visible; }
bool isVisible() const { return _isVisible; }
render::Item::Bound& editBound() { _needUpdate = true; return _bound; }
model::LightPointer editSunLight() { _needSunUpdate = true; return _sunLight; }
model::LightPointer editAmbientLight() { _needAmbientUpdate = true; return _ambientLight; }
model::SkyboxPointer editSkybox() { _needSkyboxUpdate = true; return _skybox; }
void setAmbientURL(const QString& ambientUrl);
void setSkyboxURL(const QString& skyboxUrl);
protected:
render::Item::Bound _bound;
model::LightPointer _sunLight;
model::LightPointer _ambientLight;
model::SkyboxPointer _skybox;
LightStagePointer _stage;
LightStage::Index _sunIndex { LightStage::INVALID_INDEX };
LightStage::Index _ambientIndex { LightStage::INVALID_INDEX };
bool _needUpdate { true };
bool _needSunUpdate { true };
bool _needAmbientUpdate { true };
bool _needSkyboxUpdate { true };
bool _isVisible { true };
void updateAmbientMap();
void updateSkyboxMap();
// More attributes used for rendering:
QString _ambientTextureURL;
NetworkTexturePointer _ambientTexture;
bool _pendingAmbientTexture { false };
bool _validAmbientTexture { false };
QString _skyboxTextureURL;
NetworkTexturePointer _skyboxTexture;
bool _pendingSkyboxTexture { false };
bool _validSkyboxTexture { false };
};
// Sphere entities should fit inside a cube entity of the same size, so a sphere that has dimensions 1x1x1
// is a half unit sphere. However, the geometry cache renders a UNIT sphere, so we need to scale down.
@ -123,52 +180,6 @@ void RenderableZoneEntityItem::updateGeometry() {
}
}
void RenderableZoneEntityItem::updateTextures() {
auto textureCache = DependencyManager::get<TextureCache>();
bool isAmbientSet = false;
if (_pendingAmbientTexture && !_ambientTexture) {
_ambientTexture = textureCache->getTexture(_ambientTextureURL, image::TextureUsage::CUBE_TEXTURE);
}
if (_ambientTexture && _ambientTexture->isLoaded()) {
_pendingAmbientTexture = false;
auto texture = _ambientTexture->getGPUTexture();
if (texture) {
isAmbientSet = true;
} else {
qCDebug(entitiesrenderer) << "Failed to load ambient texture:" << _ambientTexture->getURL();
}
}
if (_pendingSkyboxTexture &&
(!_skyboxTexture || (_skyboxTexture->getURL() != _skyboxTextureURL))) {
_skyboxTexture = textureCache->getTexture(_skyboxTextureURL, image::TextureUsage::CUBE_TEXTURE);
}
if (_skyboxTexture && _skyboxTexture->isLoaded()) {
_pendingSkyboxTexture = false;
auto texture = _skyboxTexture->getGPUTexture();
if (texture) {
// skybox->setCubemap(texture);
if (!isAmbientSet) {
// sceneKeyLight->setAmbientSphere(texture->getIrradiance());
// sceneKeyLight->setAmbientMap(texture);
isAmbientSet = true;
}
} else {
qCDebug(entitiesrenderer) << "Failed to load skybox texture:" << _skyboxTexture->getURL();
}
} else {
// skybox->setCubemap(nullptr);
}
if (!isAmbientSet) {
// sceneKeyLight->resetAmbientSphere();
// sceneKeyLight->setAmbientMap(nullptr);
}
}
void RenderableZoneEntityItem::render(RenderArgs* args) {
Q_ASSERT(getType() == EntityTypes::Zone);
@ -252,15 +263,6 @@ void RenderableZoneEntityItem::render(RenderArgs* args) {
sceneTime->setHour(this->getStageProperties().calculateHour());
sceneTime->setDay(this->getStageProperties().calculateDay());
}
// Set the ambient texture
_ambientTextureURL = this->getKeyLightProperties().getAmbientURL();
if (_ambientTextureURL.isEmpty()) {
_pendingAmbientTexture = false;
_ambientTexture.clear();
} else {
_pendingAmbientTexture = true;
}
}*/
}
@ -277,39 +279,143 @@ bool RenderableZoneEntityItem::contains(const glm::vec3& point) const {
return false;
}
class RenderableZoneEntityItemMeta {
public:
RenderableZoneEntityItemMeta(EntityItemPointer entity) : entity(entity), _light(std::make_shared<model::Light>()) { }
~RenderableZoneEntityItemMeta();
typedef render::Payload<RenderableZoneEntityItemMeta> Payload;
typedef Payload::DataPointer Pointer;
EntityItemPointer entity;
bool RenderableZoneEntityItem::addToScene(EntityItemPointer self, const render::ScenePointer& scene,
render::Transaction& transaction) {
_myMetaItem = scene->allocateID();
auto renderData = std::make_shared<RenderableZoneEntityItemMeta>(self);
auto renderPayload = std::make_shared<RenderableZoneEntityItemMeta::Payload>(renderData);
updateKeyZoneItemFromEntity((*renderData));
render::Item::Status::Getters statusGetters;
makeEntityItemStatusGetters(getThisPointer(), statusGetters);
renderPayload->addStatusGetters(statusGetters);
transaction.resetItem(_myMetaItem, renderPayload);
return true;
}
void RenderableZoneEntityItem::removeFromScene(EntityItemPointer self, const render::ScenePointer& scene,
render::Transaction& transaction) {
transaction.removeItem(_myMetaItem);
render::Item::clearID(_myMetaItem);
if (_model) {
_model->removeFromScene(scene, transaction);
}
}
void RenderableZoneEntityItem::notifyBoundChanged() {
if (!render::Item::isValidID(_myMetaItem)) {
return;
}
render::Transaction transaction;
render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();
if (scene) {
transaction.updateItem<RenderableZoneEntityItemMeta>(_myMetaItem, [](RenderableZoneEntityItemMeta& data) {});
scene->enqueueTransaction(transaction);
} else {
qCWarning(entitiesrenderer) << "RenderableZoneEntityItem::notifyBoundChanged(), Unexpected null scene, possibly during application shutdown";
}
}
void RenderableZoneEntityItem::updateKeySunFromEntity(RenderableZoneEntityItemMeta& keyZonePayload) {
auto sunLight = keyZonePayload.editSunLight();
sunLight->setType(model::Light::SUN);
sunLight->setPosition(this->getPosition());
sunLight->setOrientation(this->getRotation());
// Set the keylight
sunLight->setColor(ColorUtils::toVec3(this->getKeyLightProperties().getColor()));
sunLight->setIntensity(this->getKeyLightProperties().getIntensity());
sunLight->setDirection(this->getKeyLightProperties().getDirection());
}
void RenderableZoneEntityItem::updateKeyAmbientFromEntity(RenderableZoneEntityItemMeta& keyZonePayload) {
auto ambientLight = keyZonePayload.editAmbientLight();
ambientLight->setType(model::Light::AMBIENT);
ambientLight->setPosition(this->getPosition());
ambientLight->setOrientation(this->getRotation());
// Set the keylight
ambientLight->setColor(ColorUtils::toVec3(this->getKeyLightProperties().getColor()));
ambientLight->setAmbientIntensity(this->getKeyLightProperties().getAmbientIntensity());
// ambientLight->setIntensity(this->getKeyLightProperties().getIntensity());
ambientLight->setDirection(this->getKeyLightProperties().getDirection());
if (this->getKeyLightProperties().getAmbientURL().isEmpty()) {
keyZonePayload.setAmbientURL(this->getSkyboxProperties().getURL());
} else {
keyZonePayload.setAmbientURL(this->getKeyLightProperties().getAmbientURL());
}
}
void RenderableZoneEntityItem::updateKeyBackgroundFromEntity(RenderableZoneEntityItemMeta& keyZonePayload) {
auto skybox = keyZonePayload.editSkybox();
this->getBackgroundMode();
keyZonePayload.setSkyboxURL(this->getSkyboxProperties().getURL());
}
void RenderableZoneEntityItem::updateKeyZoneItemFromEntity(RenderableZoneEntityItemMeta& keyZonePayload) {
keyZonePayload.setVisible(this->getVisible());
bool success;
keyZonePayload.editBound() = this->getAABox(success);
if (!success) {
keyZonePayload.editBound() = render::Item::Bound();
}
updateKeySunFromEntity(keyZonePayload);
updateKeyAmbientFromEntity(keyZonePayload);
updateKeyBackgroundFromEntity(keyZonePayload);
}
void RenderableZoneEntityItem::sceneUpdateRenderItemFromEntity(render::Transaction& transaction) {
if (!render::Item::isValidID(_myMetaItem)) {
return;
}
transaction.updateItem<RenderableZoneEntityItemMeta>(_myMetaItem, [&](RenderableZoneEntityItemMeta& data) {
updateKeyZoneItemFromEntity(data);
});
}
void RenderableZoneEntityItem::notifyChangedRenderItem() {
if (!render::Item::isValidID(_myMetaItem)) {
return;
}
render::Transaction transaction;
render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();
sceneUpdateRenderItemFromEntity(transaction);
scene->enqueueTransaction(transaction);
}
void render(RenderArgs* args);
model::LightPointer editLight() { _needUpdate = true; return _light; }
render::Item::Bound& editBound() { _needUpdate = true; return _bound; }
void setVisible(bool visible) { _isVisible = visible; }
bool isVisible() const { return _isVisible; }
model::LightPointer _light;
render::Item::Bound _bound;
LightStagePointer _stage;
LightStage::Index _index { LightStage::INVALID_INDEX };
bool _needUpdate { true };
bool _isVisible { true };
};
namespace render {
template <> const ItemKey payloadGetKey(const RenderableZoneEntityItemMeta::Pointer& payload) {
return ItemKey::Builder().withTypeMeta().build();
}
template <> const Item::Bound payloadGetBound(const RenderableZoneEntityItemMeta::Pointer& payload) {
if (payload && payload->entity) {
bool success;
@ -326,142 +432,151 @@ namespace render {
}
}
bool RenderableZoneEntityItem::addToScene(EntityItemPointer self, const render::ScenePointer& scene,
render::Transaction& transaction) {
_myMetaItem = scene->allocateID();
auto renderData = std::make_shared<RenderableZoneEntityItemMeta>(self);
auto renderPayload = std::make_shared<RenderableZoneEntityItemMeta::Payload>(renderData);
updateKeyZoneItemFromEntity((*renderData));
RenderableZoneEntityItemMeta::RenderableZoneEntityItemMeta(EntityItemPointer entity) :
entity(entity),
_sunLight(std::make_shared<model::Light>()),
_ambientLight(std::make_shared<model::Light>()),
_skybox(std::make_shared<model::Skybox>())
{}
render::Item::Status::Getters statusGetters;
makeEntityItemStatusGetters(getThisPointer(), statusGetters);
renderPayload->addStatusGetters(statusGetters);
transaction.resetItem(_myMetaItem, renderPayload);
/* _myKeyLightItem = scene->allocateID();
auto keyLightPayload = std::make_shared<KeyLightPayload>();
updateKeyLightItemFromEntity((*keyLightPayload));
auto keyLightItem = std::make_shared<KeyLightPayload::Payload>(keyLightPayload);
transaction.resetItem(_myKeyLightItem, keyLightItem);
*/
return true;
}
void RenderableZoneEntityItem::removeFromScene(EntityItemPointer self, const render::ScenePointer& scene,
render::Transaction& transaction) {
// transaction.removeItem(_myKeyLightItem);
// render::Item::clearID(_myKeyLightItem);
transaction.removeItem(_myMetaItem);
render::Item::clearID(_myMetaItem);
if (_model) {
_model->removeFromScene(scene, transaction);
}
}
void RenderableZoneEntityItem::notifyBoundChanged() {
if (!render::Item::isValidID(_myMetaItem)) {
return;
}
render::Transaction transaction;
render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();
if (scene) {
transaction.updateItem<RenderableZoneEntityItemMeta>(_myMetaItem, [](RenderableZoneEntityItemMeta& data) {});
// transaction.updateItem<KeyLightPayload>(_myKeyLightItem, [](KeyLightPayload& data) {});
scene->enqueueTransaction(transaction);
} else {
qCWarning(entitiesrenderer) << "RenderableZoneEntityItem::notifyBoundChanged(), Unexpected null scene, possibly during application shutdown";
}
}
void RenderableZoneEntityItem::updateKeyZoneItemFromEntity(RenderableZoneEntityItemMeta& keyZonePayload) {
auto entity = this;
keyZonePayload.setVisible(entity->getVisible());
auto light = keyZonePayload.editLight();
light->setPosition(entity->getPosition());
light->setOrientation(entity->getRotation());
bool success;
keyZonePayload.editBound() = entity->getAABox(success);
if (!success) {
keyZonePayload.editBound() = render::Item::Bound();
}
// Set the keylight
light->setColor(ColorUtils::toVec3(this->getKeyLightProperties().getColor()));
light->setIntensity(this->getKeyLightProperties().getIntensity());
light->setAmbientIntensity(this->getKeyLightProperties().getAmbientIntensity());
light->setDirection(this->getKeyLightProperties().getDirection());
light->setType(model::Light::SUN);
}
void RenderableZoneEntityItem::updateKeyLightItemFromEntity(KeyLightPayload& keylightPayload) {
}
void RenderableZoneEntityItem::sceneUpdateRenderItemFromEntity(render::Transaction& transaction) {
if (!render::Item::isValidID(_myMetaItem)) {
return;
}
transaction.updateItem<KeyLightPayload>(_myMetaItem, [&](KeyLightPayload& data) {
updateKeyLightItemFromEntity(data);
});
}
void RenderableZoneEntityItem::notifyChangedRenderItem() {
if (!render::Item::isValidID(_myMetaItem)) {
return;
}
render::Transaction transaction;
render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();
sceneUpdateRenderItemFromEntity(transaction);
scene->enqueueTransaction(transaction);
}
RenderableZoneEntityItemMeta::~RenderableZoneEntityItemMeta() {
if (!LightStage::isIndexInvalid(_index)) {
if (_stage) {
_stage->removeLight(_index);
if (_stage) {
if (!LightStage::isIndexInvalid(_sunIndex)) {
_stage->removeLight(_sunIndex);
}
if (!LightStage::isIndexInvalid(_ambientIndex)) {
_stage->removeLight(_ambientIndex);
}
}
}
void RenderableZoneEntityItemMeta::render(RenderArgs* args) {
entity->render(args);
void RenderableZoneEntityItemMeta::setAmbientURL(const QString& ambientUrl) {
// nothing change if nothing change
if (_ambientTextureURL == ambientUrl) {
return;
}
_ambientTextureURL = ambientUrl;
if (_ambientTextureURL.isEmpty()) {
_validAmbientTexture = false;
_pendingAmbientTexture = false;
_ambientTexture.clear();
_ambientLight->setAmbientMap(nullptr);
_ambientLight->setAmbientSpherePreset(gpu::SphericalHarmonics::BREEZEWAY);
} else {
_pendingAmbientTexture = true;
auto textureCache = DependencyManager::get<TextureCache>();
_ambientTexture = textureCache->getTexture(_ambientTextureURL, image::TextureUsage::CUBE_TEXTURE);
// keep whatever is assigned on the ambient map/sphere until texture is loaded
}
}
void RenderableZoneEntityItemMeta::updateAmbientMap() {
if (_pendingAmbientTexture) {
if (_ambientTexture && _ambientTexture->isLoaded()) {
_pendingAmbientTexture = false;
auto texture = _ambientTexture->getGPUTexture();
if (texture) {
if (texture->getIrradiance()) {
_ambientLight->setAmbientSphere(*texture->getIrradiance());
} else {
_ambientLight->setAmbientSpherePreset(gpu::SphericalHarmonics::BREEZEWAY);
}
editAmbientLight()->setAmbientMap(texture);
_validAmbientTexture = true;
} else {
qCDebug(entitiesrenderer) << "Failed to load ambient texture:" << _ambientTexture->getURL();
}
}
}
}
void RenderableZoneEntityItemMeta::setSkyboxURL(const QString& skyboxUrl) {
// nothing change if nothing change
if (_skyboxTextureURL == skyboxUrl) {
return;
}
_skyboxTextureURL = skyboxUrl;
if (_skyboxTextureURL.isEmpty()) {
_validSkyboxTexture = false;
_pendingSkyboxTexture = false;
_skyboxTexture.clear();
editSkybox()->setCubemap(nullptr);
} else {
_pendingSkyboxTexture = true;
auto textureCache = DependencyManager::get<TextureCache>();
_skyboxTexture = textureCache->getTexture(_skyboxTextureURL, image::TextureUsage::CUBE_TEXTURE);
}
}
void RenderableZoneEntityItemMeta::updateSkyboxMap() {
if (_pendingSkyboxTexture) {
if (_skyboxTexture && _skyboxTexture->isLoaded()) {
_pendingSkyboxTexture = false;
auto texture = _skyboxTexture->getGPUTexture();
if (texture) {
editSkybox()->setCubemap(texture);
_validSkyboxTexture = true;
} else {
qCDebug(entitiesrenderer) << "Failed to load Skybox texture:" << _skyboxTexture->getURL();
}
}
}
}
void RenderableZoneEntityItemMeta::render(RenderArgs* args) {
// entity->render(args);
if (!_stage) {
_stage = DependencyManager::get<DeferredLightingEffect>()->getLightStage();
}
// Do we need to allocate the light in the stage ?
if (LightStage::isIndexInvalid(_index)) {
_index = _stage->addLight(_light);
_needUpdate = false;
{ // Sun
// Need an update ?
if (_needSunUpdate) {
// Do we need to allocate the light in the stage ?
if (LightStage::isIndexInvalid(_sunIndex)) {
_sunIndex = _stage->addLight(_sunLight);
} else {
_stage->updateLightArrayBuffer(_sunIndex);
}
_needSunUpdate = false;
}
}
// Need an update ?
if (_needUpdate) {
_stage->updateLightArrayBuffer(_index);
_needUpdate = false;
{ // Ambient
updateAmbientMap();
// Need an update ?
if (_needAmbientUpdate) {
// Do we need to allocate the light in the stage ?
if (LightStage::isIndexInvalid(_ambientIndex)) {
_ambientIndex = _stage->addLight(_ambientLight);
} else {
_stage->updateLightArrayBuffer(_ambientIndex);
}
_needAmbientUpdate = false;
}
}
{ // Skybox
updateSkyboxMap();
}
if (isVisible()) {
// FInally, push the light visible in the frame
_stage->_currentFrame.pushLight(_index, _light->getType());
_stage->_currentFrame.pushSunLight(_sunIndex);
_stage->_currentFrame.pushAmbientLight(_ambientIndex);
}
}

View file

@ -54,8 +54,6 @@ private:
Model* getModel();
void initialSimulation();
void updateGeometry();
void updateTextures();
template<typename Lambda>
void changeProperties(Lambda functor);
@ -63,25 +61,15 @@ private:
void notifyChangedRenderItem();
void sceneUpdateRenderItemFromEntity(render::Transaction& transaction);
void updateKeyZoneItemFromEntity(RenderableZoneEntityItemMeta& keyZonePayload);
void updateKeyLightItemFromEntity(KeyLightPayload& keyLightPayload);
void updateKeySunFromEntity(RenderableZoneEntityItemMeta& keyZonePayload);
void updateKeyAmbientFromEntity(RenderableZoneEntityItemMeta& keyZonePayload);
void updateKeyBackgroundFromEntity(RenderableZoneEntityItemMeta& keyZonePayload);
Model* _model;
bool _needsInitialSimulation;
render::ItemID _myMetaItem{ render::Item::INVALID_ITEM_ID };
render::ItemID _myKeyLightItem { render::Item::INVALID_ITEM_ID };
// More attributes used for rendering:
NetworkTexturePointer _ambientTexture;
NetworkTexturePointer _skyboxTexture;
QString _ambientTextureURL;
QString _skyboxTextureURL;
bool _pendingAmbientTexture { false };
bool _pendingSkyboxTexture { false };
bool _validAmbientTextureURL { false };
bool _validSkyboxTextureURL { false };
};
#endif // hifi_RenderableZoneEntityItem_h

View file

@ -68,7 +68,8 @@ public:
enum Type {
SUN = 0,
AMBIENT = 0,
SUN,
POINT,
SPOT,
@ -112,7 +113,7 @@ public:
void setIntensity(float intensity);
bool isRanged() const { return (getType() == POINT) || (getType() == SPOT); }
bool hasAmbient() const { return (getType() == SUN); }
bool hasAmbient() const { return (getType() == AMBIENT); }
// FalloffRradius is the physical radius of the light sphere through which energy shines,
// expressed in meters. It is used only to calculate the falloff curve of the light.

View file

@ -145,28 +145,33 @@ void DeferredLightingEffect::init() {
void DeferredLightingEffect::setupKeyLightBatch(gpu::Batch& batch, int lightBufferUnit, int ambientBufferUnit, int skyboxCubemapUnit) {
PerformanceTimer perfTimer("DLE->setupBatch()");
model::LightPointer keyLight;
model::LightPointer keySunLight;
if (_lightStage && _lightStage->_currentFrame._sunLights.size()) {
keyLight = _lightStage->getLight(_lightStage->_currentFrame._sunLights.front());
keySunLight = _lightStage->getLight(_lightStage->_currentFrame._sunLights.front());
} else {
keyLight = _allocatedLights[_globalLights.front()];
keySunLight = _allocatedLights[_globalLights.front()];
}
model::LightPointer keyAmbiLight;
if (_lightStage && _lightStage->_currentFrame._ambientLights.size()) {
keyAmbiLight = _lightStage->getLight(_lightStage->_currentFrame._ambientLights.front());
} else {
keyAmbiLight = _allocatedLights[_globalLights.front()];
}
if (lightBufferUnit >= 0) {
batch.setUniformBuffer(lightBufferUnit, keyLight->getLightSchemaBuffer());
batch.setUniformBuffer(lightBufferUnit, keySunLight->getLightSchemaBuffer());
}
if (keyLight->hasAmbient() && (ambientBufferUnit >= 0)) {
batch.setUniformBuffer(ambientBufferUnit, keyLight->getAmbientSchemaBuffer());
if (keyAmbiLight->hasAmbient() && (ambientBufferUnit >= 0)) {
batch.setUniformBuffer(ambientBufferUnit, keyAmbiLight->getAmbientSchemaBuffer());
}
if (keyLight->getAmbientMap() && (skyboxCubemapUnit >= 0)) {
batch.setResourceTexture(skyboxCubemapUnit, keyLight->getAmbientMap());
if (keyAmbiLight->getAmbientMap() && (skyboxCubemapUnit >= 0)) {
batch.setResourceTexture(skyboxCubemapUnit, keyAmbiLight->getAmbientMap());
}
}
void DeferredLightingEffect::unsetKeyLightBatch(gpu::Batch& batch, int lightBufferUnit, int ambientBufferUnit, int skyboxCubemapUnit) {
auto keyLight = _allocatedLights[_globalLights.front()];
if (lightBufferUnit >= 0) {
batch.setUniformBuffer(lightBufferUnit, nullptr);
}

View file

@ -118,22 +118,25 @@ public:
public:
Frame() {}
void clear() { _pointLights.clear(); _spotLights.clear(); _sunLights.clear(); }
void clear() { _pointLights.clear(); _spotLights.clear(); _sunLights.clear(); _ambientLights.clear(); }
void pushLight(LightStage::Index index, model::Light::Type type) {
switch (type) {
case model::Light::POINT: { pushPointLight(index); break; }
case model::Light::SPOT: { pushSpotLight(index); break; }
case model::Light::SUN: { pushSunLight(index); break; }
case model::Light::AMBIENT: { pushAmbientLight(index); break; }
default: { break; }
}
}
void pushPointLight(LightStage::Index index) { _pointLights.emplace_back(index); }
void pushSpotLight(LightStage::Index index) { _spotLights.emplace_back(index); }
void pushSunLight(LightStage::Index index) { _sunLights.emplace_back(index); }
void pushAmbientLight(LightStage::Index index) { _ambientLights.emplace_back(index); }
LightStage::LightIndices _pointLights;
LightStage::LightIndices _spotLights;
LightStage::LightIndices _sunLights;
LightStage::LightIndices _ambientLights;
};
Frame _currentFrame;