feat: Implement dynamic shadows

This commit is contained in:
Vsevolod Kremianskii 2020-11-25 01:41:52 +07:00
parent 4bb45a54c2
commit 47daa821a8
13 changed files with 260 additions and 32 deletions

View file

@ -37,22 +37,33 @@ Framebuffer::~Framebuffer() {
void Framebuffer::init() {
if (_inited) return;
_colorBuffers.resize(_colorBufferCount);
glGenTextures(_colorBufferCount, &_colorBuffers[0]);
if (_colorBufferCount > 0) {
_colorBuffers.resize(_colorBufferCount);
glGenTextures(_colorBufferCount, &_colorBuffers[0]);
for (int i = 0; i < _colorBufferCount; ++i) {
glBindTexture(GL_TEXTURE_2D, _colorBuffers[i]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _width, _height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);
for (int i = 0; i < _colorBufferCount; ++i) {
glBindTexture(GL_TEXTURE_2D, _colorBuffers[i]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _width, _height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);
}
}
glGenRenderbuffers(1, &_depthBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _depthBuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, _width, _height);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
glGenTextures(1, &_depthBuffer);
glBindTexture(GL_TEXTURE_2D, _depthBuffer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, _width, _height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
float borderColor[] = { 1.0f, 1.0f, 1.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
glBindTexture(GL_TEXTURE_2D, 0);
glGenFramebuffers(1, &_framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
@ -60,7 +71,7 @@ void Framebuffer::init() {
for (int i = 0; i < _colorBufferCount; ++i) {
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D, _colorBuffers[i], 0);
}
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, _depthBuffer);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, _depthBuffer, 0);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
throw runtime_error("Control: framebuffer is not complete");
@ -74,8 +85,11 @@ void Framebuffer::deinit() {
if (!_inited) return;
glDeleteFramebuffers(1, &_framebuffer);
glDeleteTextures(_colorBufferCount, &_colorBuffers[0]);
glDeleteRenderbuffers(1, &_depthBuffer);
if (_colorBufferCount > 0) {
glDeleteTextures(_colorBufferCount, &_colorBuffers[0]);
}
glDeleteTextures(1, &_depthBuffer);
_inited = false;
}
@ -92,10 +106,18 @@ void Framebuffer::bindColorBuffer(int n) const {
glBindTexture(GL_TEXTURE_2D, _colorBuffers[n]);
}
void Framebuffer::bindDepthBuffer() const {
glBindTexture(GL_TEXTURE_2D, _depthBuffer);
}
void Framebuffer::unbindColorBuffer() const {
glBindTexture(GL_TEXTURE_2D, 0);
}
void Framebuffer::unbindDepthBuffer() const {
glBindTexture(GL_TEXTURE_2D, 0);
}
int Framebuffer::width() const {
return _width;
}

View file

@ -34,7 +34,9 @@ public:
void bind() const;
void unbind() const;
void bindColorBuffer(int n) const;
void bindDepthBuffer() const;
void unbindColorBuffer() const;
void unbindDepthBuffer() const;
int width() const;
int height() const;

View file

@ -31,21 +31,32 @@ namespace reone {
namespace render {
static const int kShadowResolution = 2048;
WorldRenderPipeline::WorldRenderPipeline(IRenderable *scene, const GraphicsOptions &opts) :
_scene(scene),
_opts(opts),
_geometry(opts.width, opts.height, 2),
_verticalBlur(opts.width, opts.height),
_horizontalBlur(opts.width, opts.height) {
for (int i = 0; i < kMaxShadowLightCount; ++i) {
_shadows.push_back(make_unique<Framebuffer>(kShadowResolution, kShadowResolution, 0));
}
}
void WorldRenderPipeline::init() {
_geometry.init();
_verticalBlur.init();
_horizontalBlur.init();
for (auto &shadows : _shadows) {
shadows->init();
}
}
void WorldRenderPipeline::render() const {
drawShadows();
drawGeometry();
float w = static_cast<float>(_opts.width);
@ -61,6 +72,39 @@ void WorldRenderPipeline::render() const {
drawResult();
}
void WorldRenderPipeline::drawShadows() const {
const vector<ShadowLight> &lights = _scene->shadowLights();
int lightCount = static_cast<int>(lights.size());
if (lightCount == 0) return;
int viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
glViewport(0, 0, kShadowResolution, kShadowResolution);
GlobalUniforms globals;
for (int i = 0; i < lightCount; ++i) {
_shadows[i]->bind();
glDrawBuffer(GL_NONE);
glClear(GL_DEPTH_BUFFER_BIT);
globals.cameraPosition = lights[i].position;
globals.projection = lights[i].projection;
globals.view = lights[i].view;
Shaders::instance().setGlobalUniforms(globals);
withDepthTest([this]() { _scene->renderNoGlobalUniforms(); });
_shadows[i]->unbind();
}
glViewport(viewport[0], viewport[1], viewport[2], viewport[3]);
}
void WorldRenderPipeline::drawGeometry() const {
_geometry.bind();
@ -68,6 +112,13 @@ void WorldRenderPipeline::drawGeometry() const {
glDrawBuffers(2, buffers);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
int lightCount = static_cast<int>(_scene->shadowLights().size());
for (int i = 0; i < lightCount; ++i) {
glActiveTexture(GL_TEXTURE0 + TextureUniforms::shadowmap0 + i);
_shadows[i]->bindDepthBuffer();
}
withDepthTest([this]() { _scene->render(); });
_geometry.unbind();

View file

@ -17,6 +17,9 @@
#pragma once
#include <memory>
#include <vector>
#include "../framebuffer.h"
#include "../types.h"
@ -37,10 +40,12 @@ private:
Framebuffer _geometry;
Framebuffer _verticalBlur;
Framebuffer _horizontalBlur;
std::vector<std::unique_ptr<Framebuffer>> _shadows;
WorldRenderPipeline(const WorldRenderPipeline &) = delete;
WorldRenderPipeline &operator=(const WorldRenderPipeline &) = delete;
void drawShadows() const;
void drawGeometry() const;
void applyHorizontalBlur() const;
void applyVerticalBlur() const;

View file

@ -36,12 +36,14 @@ namespace render {
static const int kFeaturesBindingPointIndex = 1;
static const int kGeneralBindingPointIndex = 2;
static const int kLightingBindingPointIndex = 3;
static const int kSkeletalBindingPointIndex = 4;
static const int kShadowsBindingPointIndex = 4;
static const int kSkeletalBindingPointIndex = 5;
static const GLchar kCommonShaderHeader[] = R"END(
#version 330
const int MAX_LIGHTS = 8;
const int MAX_SHADOW_LIGHTS = 4;
const int MAX_BONES = 128;
struct Light {
@ -50,6 +52,12 @@ struct Light {
float radius;
};
struct ShadowLight {
vec4 position;
mat4 view;
mat4 projection;
};
layout(std140) uniform General {
bool uLightmapEnabled;
bool uEnvmapEnabled;
@ -61,6 +69,7 @@ layout(std140) uniform General {
bool uBlurEnabled;
bool uBloomEnabled;
bool uDiscardEnabled;
bool uShadowsEnabled;
uniform mat4 uModel;
uniform vec4 uColor;
@ -77,6 +86,11 @@ layout(std140) uniform Lighting {
Light uLights[MAX_LIGHTS];
};
layout(std140) uniform Shadows {
int uShadowLightCount;
ShadowLight uShadowLights[MAX_SHADOW_LIGHTS];
};
layout(std140) uniform Skeletal {
uniform mat4 uAbsTransform;
uniform mat4 uAbsTransformInv;
@ -102,6 +116,7 @@ void main() {
static const GLchar kModelVertexShader[] = R"END(
uniform mat4 uProjection;
uniform mat4 uView;
uniform mat4 uLightSpaceMatrix;
layout(location = 0) in vec3 aPosition;
layout(location = 1) in vec3 aNormal;
@ -187,6 +202,7 @@ uniform sampler2D uLightmap;
uniform sampler2D uBumpmap;
uniform samplerCube uEnvmap;
uniform samplerCube uBumpyShiny;
uniform sampler2D uShadowmaps[MAX_SHADOW_LIGHTS];
uniform vec3 uCameraPosition;
@ -221,18 +237,41 @@ void applyLighting(vec3 normal, inout vec3 color) {
vec3 lightDir = normalize(surfaceToLight);
vec3 surfaceToCamera = normalize(uCameraPosition - fragPosition);
float diffuseCoeff = max(dot(normal, lightDir), 0.0);
float specularCoeff = 0.0;
float diffuse = max(dot(normal, lightDir), 0.0);
float specular = 0.0;
if (diffuseCoeff > 0.0) {
specularCoeff = 0.25 * pow(max(0.0, dot(surfaceToCamera, reflect(-lightDir, normal))), 32);
if (diffuse > 0.0) {
specular = 0.25 * pow(max(0.0, dot(surfaceToCamera, reflect(-lightDir, normal))), 32);
}
float distToLight = length(surfaceToLight);
float attenuation = clamp(1.0 - distToLight / uLights[i].radius, 0.0, 1.0);
attenuation *= attenuation;
color += attenuation * (diffuseCoeff + specularCoeff) * uLights[i].color.rgb;
color += attenuation * (diffuse + specular) * uLights[i].color.rgb;
}
}
void applyShadows(vec3 normal, inout vec3 color) {
for (int i = 0; i < uShadowLightCount; ++i) {
vec4 lightSpacePos = uShadowLights[i].projection * uShadowLights[i].view * vec4(fragPosition, 1.0);
vec3 projCoords = lightSpacePos.xyz / lightSpacePos.w;
projCoords = projCoords * 0.5 + 0.5;
if (projCoords.z > 1.0) continue;
float distToCenter = distance(vec2(0.5), projCoords.xy); // between 0.0 and 0.5
float shadow = 0.5 * (1.0 - smoothstep(0.25, 0.5, distToCenter));
float closestDepth = texture(uShadowmaps[i], projCoords.xy).r;
float currentDepth = projCoords.z;
vec3 surfaceToLight = uShadowLights[i].position.xyz - fragPosition;
vec3 lightDir = normalize(surfaceToLight);
float bias = max(0.05 * (1.0 - dot(normal, lightDir)), 0.005);
if (currentDepth - bias > closestDepth) {
color *= 1.0 - shadow;
}
}
}
@ -256,6 +295,9 @@ void main() {
} else {
lightColor = vec3(1.0);
}
if (uShadowsEnabled) {
applyShadows(normal, lightColor);
}
float finalAlpha = uAlpha;
if (!uEnvmapEnabled && !uBumpyShinyEnabled && !uBumpmapEnabled) {
@ -336,6 +378,7 @@ void Shaders::initGL() {
glGenBuffers(1, &_generalUbo);
glGenBuffers(1, &_lightingUbo);
glGenBuffers(1, &_shadowsUbo);
glGenBuffers(1, &_skeletalUbo);
for (auto &program : _programs) {
@ -352,6 +395,10 @@ void Shaders::initGL() {
if (lightingBlockIdx != GL_INVALID_INDEX) {
glUniformBlockBinding(_activeOrdinal, lightingBlockIdx, kLightingBindingPointIndex);
}
uint32_t shadowsBlockIdx = glGetUniformBlockIndex(_activeOrdinal, "Shadows");
if (shadowsBlockIdx != GL_INVALID_INDEX) {
glUniformBlockBinding(_activeOrdinal, shadowsBlockIdx, kShadowsBindingPointIndex);
}
uint32_t skeletalBlockIdx = glGetUniformBlockIndex(_activeOrdinal, "Skeletal");
if (skeletalBlockIdx != GL_INVALID_INDEX) {
glUniformBlockBinding(_activeOrdinal, skeletalBlockIdx, kSkeletalBindingPointIndex);
@ -363,6 +410,11 @@ void Shaders::initGL() {
setUniform("uBumpmap", TextureUniforms::bumpmap);
setUniform("uBloom", TextureUniforms::bloom);
for (int i = 0; i < kMaxShadowLightCount; ++i) {
string name(str(boost::format("uShadowmaps[%d]") % i));
setUniform(name, TextureUniforms::shadowmap0 + i);
}
_activeOrdinal = 0;
glUseProgram(0);
}
@ -418,6 +470,10 @@ void Shaders::deinitGL() {
glDeleteBuffers(1, &_skeletalUbo);
_skeletalUbo = 0;
}
if (_shadowsUbo) {
glDeleteBuffers(1, &_shadowsUbo);
_shadowsUbo = 0;
}
if (_lightingUbo) {
glDeleteBuffers(1, &_lightingUbo);
_lightingUbo = 0;
@ -550,6 +606,9 @@ void Shaders::setGlobalUniforms(const GlobalUniforms &globals) {
setUniform("uProjection", globals.projection);
setUniform("uView", globals.view);
setUniform("uCameraPosition", globals.cameraPosition);
glBindBufferBase(GL_UNIFORM_BUFFER, kShadowsBindingPointIndex, _shadowsUbo);
glBufferData(GL_UNIFORM_BUFFER, sizeof(ShadowsUniforms), &globals.shadows, GL_STATIC_DRAW);
}
glUseProgram(ordinal);
_activeOrdinal = ordinal;

View file

@ -26,6 +26,8 @@
#include "glm/glm.hpp"
#include "types.h"
namespace reone {
namespace render {
@ -49,12 +51,20 @@ struct TextureUniforms {
static constexpr int bumpyShiny { 3 };
static constexpr int bumpmap { 4 };
static constexpr int bloom { 5 };
static constexpr int shadowmap0 { 6 };
};
struct ShadowsUniforms {
int shadowLightCount { 0 };
char padding[12];
ShadowLight shadowLights[kMaxShadowLightCount];
};
struct GlobalUniforms {
glm::mat4 projection { 1.0f };
glm::mat4 view { 1.0f };
glm::vec3 cameraPosition { 0.0f };
ShadowsUniforms shadows;
};
struct GeneralUniforms {
@ -68,7 +78,8 @@ struct GeneralUniforms {
int blurEnabled { false };
int bloomEnabled { false };
int discardEnabled { false };
char padding1[8];
int shadowsEnabled { false };
char padding1[4];
glm::mat4 model { 1.0f };
glm::vec4 color { 1.0f };
@ -142,6 +153,7 @@ private:
uint32_t _generalUbo { 0 };
uint32_t _lightingUbo { 0 };
uint32_t _shadowsUbo { 0 };
uint32_t _skeletalUbo { 0 };
// END Uniform buffer objects

View file

@ -22,12 +22,16 @@
#include "SDL2/SDL_events.h"
#include "glm/mat4x4.hpp"
#include "glm/vec3.hpp"
#include "glm/vec4.hpp"
namespace reone {
namespace render {
const int kMaxShadowLightCount = 4;
enum class TextureType {
Diffuse,
Lightmap,
@ -59,14 +63,24 @@ struct TextureFeatures {
std::vector<glm::vec3> lowerRightCoords;
};
struct ShadowLight {
glm::vec4 position { 0.0f };
glm::mat4 view { 1.0f };
glm::mat4 projection { 1.0f };
};
class IEventHandler {
public:
virtual bool handle(const SDL_Event &event) = 0;
};
// TODO: move render pipelines into libscene
class IRenderable {
public:
virtual void render() const = 0;
virtual void renderNoGlobalUniforms() const = 0;
virtual const std::vector<ShadowLight> &shadowLights() const = 0;
};
} // namespace render

View file

@ -23,11 +23,12 @@ namespace reone {
namespace scene {
LightSceneNode::LightSceneNode(SceneGraph *sceneGraph, int priority, const glm::vec3 &color, float radius, bool shadow) :
LightSceneNode::LightSceneNode(SceneGraph *sceneGraph, int priority, const glm::vec3 &color, float radius, float multiplier, bool shadow) :
SceneNode(sceneGraph),
_priority(priority),
_color(color),
_radius(radius),
_multiplier(multiplier),
_shadow(shadow) {
}
@ -43,6 +44,10 @@ float LightSceneNode::radius() const {
return _radius;
}
float LightSceneNode::multiplier() const {
return _multiplier;
}
bool LightSceneNode::shadow() const {
return _shadow;
}

View file

@ -25,17 +25,19 @@ namespace scene {
class LightSceneNode : public SceneNode {
public:
LightSceneNode(SceneGraph *sceneGraph, int priority, const glm::vec3 &color, float radius, bool shadow);
LightSceneNode(SceneGraph *sceneGraph, int priority, const glm::vec3 &color, float radius, float multiplier, bool shadow);
int priority() const;
const glm::vec3 &color() const;
float radius() const;
float multiplier() const;
bool shadow() const;
private:
int _priority { 0 };
glm::vec3 _color { 1.0f };
float _radius { 1.0f };
float _multiplier { 1.0f };
bool _shadow { false };
};

View file

@ -83,6 +83,9 @@ void ModelNodeSceneNode::renderSingle() const {
if (mesh->hasBumpmapTexture()) {
locals.general.bumpmapEnabled = true;
}
if (_modelSceneNode->model()->classification() == Model::Classification::Other) {
locals.general.shadowsEnabled = true;
}
if (skeletal) {
locals.general.skeletalEnabled = true;
locals.skeletal = Shaders::instance().skeletalUniforms();

View file

@ -67,7 +67,7 @@ void ModelSceneNode::initModelNodes() {
shared_ptr<ModelNode::Light> light(child->light());
if (light) {
shared_ptr<LightSceneNode> lightNode(new LightSceneNode(_sceneGraph, light->priority, child->color(), child->radius(), light->shadow));
shared_ptr<LightSceneNode> lightNode(new LightSceneNode(_sceneGraph, light->priority, child->color(), child->radius(), child->multiplier(), light->shadow));
childNode->addChild(lightNode);
}
}
@ -181,7 +181,7 @@ void ModelSceneNode::updateLighting() {
_lightsAffectedBy.clear();
glm::vec3 center(_absoluteTransform * glm::vec4(_model->aabb().center(), 1.0f));
_sceneGraph->getLightsAt(center, _lightsAffectedBy);
_sceneGraph->getLightsAt(center, kMaxLightCount, [](auto &) { return true; }, _lightsAffectedBy);
_lightingDirty = false;
for (auto &attached : _attachedModels) {

View file

@ -60,6 +60,7 @@ void SceneGraph::prepareFrame() {
if (!_activeCamera) return;
refreshMeshesAndLights();
refreshShadowLights();
for (auto &root : _roots) {
ModelSceneNode *modelNode = dynamic_cast<ModelSceneNode *>(root.get());
@ -128,6 +129,28 @@ void SceneGraph::refreshMeshesAndLights() {
}
}
void SceneGraph::refreshShadowLights() {
_shadowLights.clear();
if (!_activeCamera) return;
glm::vec3 cameraPosition(_activeCamera->absoluteTransform()[3]);
vector<LightSceneNode *> nodes;
getLightsAt(cameraPosition, kMaxShadowLightCount, [](auto &light) { return light.shadow(); }, nodes);
for (auto &node : nodes) {
glm::mat4 projection, view;
getLightProjectionView(*node, projection, view);
ShadowLight light;
light.position = node->absoluteTransform()[3];
light.view = view;
light.projection = projection;
_shadowLights.push_back(move(light));
}
}
void SceneGraph::render() const {
if (!_activeCamera) return;
@ -136,8 +159,19 @@ void SceneGraph::render() const {
globals.view = _activeCamera->view();
globals.cameraPosition = _activeCamera->absoluteTransform()[3];
int lightCount = static_cast<int>(_shadowLights.size());
globals.shadows.shadowLightCount = lightCount;
for (int i = 0; i < lightCount; ++i) {
ShadowLight &light = globals.shadows.shadowLights[i];
light = _shadowLights[i];
}
Shaders::instance().setGlobalUniforms(globals);
renderNoGlobalUniforms();
}
void SceneGraph::renderNoGlobalUniforms() const {
for (auto &root : _roots) {
root->render();
}
@ -149,11 +183,22 @@ void SceneGraph::render() const {
}
}
void SceneGraph::getLightsAt(const glm::vec3 &position, vector<LightSceneNode *> &lights) const {
const vector<ShadowLight> &SceneGraph::shadowLights() const {
return _shadowLights;
}
void SceneGraph::getLightProjectionView(const LightSceneNode &light, glm::mat4 &projection, glm::mat4 &view) const {
projection = glm::perspective(glm::radians(100.0f), 1.0f, 1.0f, light.radius());
view = light.absoluteTransformInverse();
}
void SceneGraph::getLightsAt(const glm::vec3 &position, int count, const function<bool(const LightSceneNode &)> &pred, vector<LightSceneNode *> &lights) const {
unordered_map<LightSceneNode *, float> distances;
lights.clear();
for (auto &light : _lights) {
if (!pred(*light)) continue;
float distance = light->distanceTo(position);
float radius = light->radius();
if (distance > radius) continue;
@ -175,8 +220,8 @@ void SceneGraph::getLightsAt(const glm::vec3 &position, vector<LightSceneNode *>
return leftDistance < rightDistance;
});
if (lights.size() > kMaxLightCount) {
lights.erase(lights.begin() + kMaxLightCount, lights.end());
if (lights.size() > count) {
lights.erase(lights.begin() + count, lights.end());
}
}

View file

@ -17,6 +17,7 @@
#pragma once
#include <functional>
#include <map>
#include <memory>
#include <vector>
@ -41,6 +42,7 @@ public:
SceneGraph(const render::GraphicsOptions &opts);
void render() const override;
void renderNoGlobalUniforms() const override;
void clear();
@ -54,7 +56,9 @@ public:
// Lights
void getLightsAt(const glm::vec3 &position, std::vector<LightSceneNode *> &lights) const;
const std::vector<render::ShadowLight> &shadowLights() const override;
void getLightsAt(const glm::vec3 &position, int count, const std::function<bool(const LightSceneNode &)> &pred, std::vector<LightSceneNode *> &lights) const;
const glm::vec3 &ambientLightColor() const;
void setAmbientLightColor(const glm::vec3 &color);
@ -71,11 +75,15 @@ private:
glm::vec3 _ambientLightColor { 0.5f };
uint32_t _textureId { 0 };
Octree _octree;
std::vector<render::ShadowLight> _shadowLights;
SceneGraph(const SceneGraph &) = delete;
SceneGraph &operator=(const SceneGraph &) = delete;
void refreshMeshesAndLights();
void refreshShadowLights();
void getLightProjectionView(const LightSceneNode &light, glm::mat4 &projection, glm::mat4 &view) const;
};
} // namespace scene