Files
kraken/kraken/nodes/KRLight.cpp
Kearwood Gilbert fd2c213aec
Some checks failed
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, clang, clang++, macos-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Has been cancelled
Added VertexBufferLayout and associated structs, preparing to support independent layouts for each mesh primitive.
2026-06-11 01:08:06 -07:00

588 lines
20 KiB
C++
Executable File

//
// KRLight.cpp
// Kraken Engine
//
// Copyright 2026 Kearwood Gilbert. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other materials
// provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY KEARWOOD GILBERT ''AS IS'' AND ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KEARWOOD GILBERT OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// The views and conclusions contained in the software and documentation are those of the
// authors and should not be interpreted as representing official policies, either expressed
// or implied, of Kearwood Gilbert.
//
#include "KREngine-common.h"
#include "KRLight.h"
#include "KRNode.h"
#include "KRCamera.h"
#include "KRContext.h"
#include "KRPipelineManager.h"
#include "KRPipeline.h"
#include "KRDirectionalLight.h"
#include "KRSpotLight.h"
#include "KRPointLight.h"
#include "KRRenderPass.h"
using namespace hydra;
/* static */
void KRLight::InitNodeInfo(KrNodeInfo* nodeInfo)
{
KRNode::InitNodeInfo(nodeInfo);
nodeInfo->light.casts_shadow = decltype(m_casts_shadow)::defaultVal;
nodeInfo->light.color = decltype(m_color)::defaultVal;
nodeInfo->light.decay_start = decltype(m_decayStart)::defaultVal;
nodeInfo->light.dust_particle_density = decltype(m_dust_particle_density)::defaultVal;
nodeInfo->light.dust_particle_intensity = decltype(m_dust_particle_density)::defaultVal;
nodeInfo->light.dust_particle_size = decltype(m_dust_particle_size)::defaultVal;
nodeInfo->light.flare_occlusion_size = decltype(m_flareOcclusionSize)::defaultVal;
nodeInfo->light.flare_size = decltype(m_flareSize)::defaultVal;
nodeInfo->light.flare_texture = -1;
nodeInfo->light.intensity = decltype(m_intensity)::defaultVal;
nodeInfo->light.light_shafts = decltype(m_light_shafts)::defaultVal;
}
KRLight::KRLight(KRScene& scene, std::string name)
: KRNode(scene, name)
{
m_occlusionQuery = 0;
// Initialize shadow buffers
m_cShadowBuffers = 0;
for (int iBuffer = 0; iBuffer < KRENGINE_MAX_SHADOW_BUFFERS; iBuffer++) {
shadowFramebuffer[iBuffer] = 0;
shadowDepthTexture[iBuffer] = 0;
shadowValid[iBuffer] = false;
}
}
KRLight::~KRLight()
{
if (m_occlusionQuery) {
GLDEBUG(glDeleteQueriesEXT(1, &m_occlusionQuery));
m_occlusionQuery = 0;
}
allocateShadowBuffers(0);
}
tinyxml2::XMLElement* KRLight::saveXML(tinyxml2::XMLNode* parent)
{
tinyxml2::XMLElement* e = KRNode::saveXML(parent);
m_intensity.save(e);
m_color.save(e);
m_decayStart.save(e);
m_flareSize.save(e);
m_flareOcclusionSize.save(e);
m_casts_shadow.save(e);
m_light_shafts.save(e);
m_dust_particle_density.save(e);
m_dust_particle_size.save(e);
m_dust_particle_intensity.save(e);
m_flareTexture.save(e);
return e;
}
void KRLight::loadXML(tinyxml2::XMLElement* e)
{
KRNode::loadXML(e);
m_color.load(e);
m_intensity.load(e);
m_decayStart.load(e);
m_flareSize.load(e);
m_flareOcclusionSize.load(e);
m_casts_shadow.load(e);
m_light_shafts.load(e);
m_dust_particle_density.load(e);
m_dust_particle_size.load(e);
m_dust_particle_intensity.load(e);
m_flareTexture.load(e);
}
void KRLight::setFlareTexture(std::string flare_texture)
{
m_flareTexture.val.set(flare_texture);
}
void KRLight::setFlareSize(float flare_size)
{
m_flareSize = flare_size;
}
void KRLight::setFlareOcclusionSize(float occlusion_size)
{
m_flareOcclusionSize = occlusion_size;
}
void KRLight::setIntensity(float intensity)
{
m_intensity = intensity;
}
float KRLight::getIntensity() const
{
return m_intensity;
}
const Vector3& KRLight::getColor()
{
return m_color;
}
void KRLight::setColor(const Vector3& color)
{
m_color = color;
}
void KRLight::setDecayStart(float decayStart)
{
m_decayStart = decayStart;
}
float KRLight::getDecayStart() const
{
return m_decayStart;
}
void KRLight::getResourceBindings(std::list<KRResourceBinding*>& bindings)
{
KRNode::getResourceBindings(bindings);
bindings.push_back(&m_flareTexture.val);
}
Matrix4 KRLight::getParticleModelMatrix(const KRViewport& viewport) const
{
Matrix4 particleModelMatrix;
particleModelMatrix.scale(KRLIGHT_PARTICLE_RANGE); // Scale the box symetrically to ensure that we don't have an uneven distribution of particles for different angles of the view frustrum
particleModelMatrix.translate(viewport.getCameraPosition());
return particleModelMatrix;
}
int KRLight::getParticleCount() const
{
int particle_count = (int)(m_dust_particle_density * pow(KRLIGHT_PARTICLE_RANGE, 3));
if (particle_count > KRMeshManager::KRENGINE_MAX_RANDOM_PARTICLES) {
particle_count = KRMeshManager::KRENGINE_MAX_RANDOM_PARTICLES;
}
return particle_count;
}
int KRLight::getSliceCount(const KRCamera* camera) const
{
int slice_count = (int)(camera->getSettings()->volumetric_environment_quality * 495.0) + 5;
return slice_count;
}
void KRLight::render(RenderInfo& ri)
{
KRNode::render(ri);
ri.reflectedObjects.push_back(this);
if (ri.renderPass->getType() == RenderPassType::RENDER_PASS_SHADOWMAP && (ri.camera->settings.volumetric_environment_enable || ri.camera->settings.dust_particle_enable || (ri.camera->settings.m_cShadowBuffers > 0 && m_casts_shadow))) {
allocateShadowBuffers(configureShadowBufferViewports(*ri.viewport));
renderShadowBuffers(ri);
}
if (ri.renderPass->getType() == RenderPassType::RENDER_PASS_ADDITIVE_PARTICLES && ri.camera->settings.dust_particle_enable) {
// Render brownian particles for dust floating in air
if (m_cShadowBuffers >= 1 && shadowValid[0] && m_dust_particle_density > 0.0f && m_dust_particle_size > 0.0f && m_dust_particle_intensity > 0.0f) {
if (ri.viewport->visible(getBounds()) || true) { // FINDME, HACK need to remove "|| true"?
std::vector<KRDirectionalLight*> this_directional_light;
std::vector<KRSpotLight*> this_spot_light;
std::vector<KRPointLight*> this_point_light;
KRDirectionalLight* directional_light = dynamic_cast<KRDirectionalLight*>(this);
KRSpotLight* spot_light = dynamic_cast<KRSpotLight*>(this);
KRPointLight* point_light = dynamic_cast<KRPointLight*>(this);
if (directional_light) {
this_directional_light.push_back(directional_light);
}
if (spot_light) {
this_spot_light.push_back(spot_light);
}
if (point_light) {
this_point_light.push_back(point_light);
}
PipelineInfo info{};
std::string shader_name("dust_particle");
info.shader_name = &shader_name;
info.pCamera = ri.camera;
info.point_lights = &this_point_light;
info.directional_lights = &this_directional_light;
info.spot_lights = &this_spot_light;
info.renderPass = ri.renderPass;
info.rasterMode = RasterMode::kAdditive;
info.cullMode = CullMode::kCullNone;
info.layout = m_pContext->getMeshManager()->KRENGINE_VBO_DATA_RANDOM_PARTICLES.getLayout();
KRPipeline* pParticleShader = m_pContext->getPipelineManager()->getPipeline(*ri.surface, info);
if (pParticleShader && pParticleShader->bind(ri, getParticleModelMatrix(*ri.viewport))) { // TODO: Pass light index to shader
m_pContext->getMeshManager()->bindVBO(ri.commandBuffer, &m_pContext->getMeshManager()->KRENGINE_VBO_DATA_RANDOM_PARTICLES, 1.0f);
vkCmdDraw(ri.commandBuffer, getParticleCount() *3, 1, 0, 0);
}
}
}
}
if (ri.renderPass->getType() == RenderPassType::RENDER_PASS_VOLUMETRIC_EFFECTS_ADDITIVE && ri.camera->settings.volumetric_environment_enable && m_light_shafts) {
std::string shader_name = ri.camera->settings.volumetric_environment_downsample != 0 ? "volumetric_fog_downsampled" : "volumetric_fog";
std::vector<KRDirectionalLight*> this_directional_light;
std::vector<KRSpotLight*> this_spot_light;
std::vector<KRPointLight*> this_point_light;
KRDirectionalLight* directional_light = dynamic_cast<KRDirectionalLight*>(this);
KRSpotLight* spot_light = dynamic_cast<KRSpotLight*>(this);
KRPointLight* point_light = dynamic_cast<KRPointLight*>(this);
if (directional_light) {
this_directional_light.push_back(directional_light);
}
if (spot_light) {
this_spot_light.push_back(spot_light);
}
if (point_light) {
this_point_light.push_back(point_light);
}
PipelineInfo info{};
info.shader_name = &shader_name;
info.pCamera = ri.camera;
info.point_lights = &this_point_light;
info.directional_lights = &this_directional_light;
info.spot_lights = &this_spot_light;
info.renderPass = ri.renderPass;
info.rasterMode = RasterMode::kAdditive;
info.cullMode = CullMode::kCullNone;
info.layout = m_pContext->getMeshManager()->KRENGINE_VBO_DATA_VOLUMETRIC_LIGHTING.getLayout();
KRPipeline* pFogShader = m_pContext->getPipelineManager()->getPipeline(*ri.surface, info);
if (pFogShader) {
if (pFogShader->bind(ri, Matrix4())) { // TODO: Pass indexes of lights to shader
m_pContext->getMeshManager()->bindVBO(ri.commandBuffer, &m_pContext->getMeshManager()->KRENGINE_VBO_DATA_VOLUMETRIC_LIGHTING, 1.0f);
vkCmdDraw(ri.commandBuffer, getSliceCount(ri.camera) * 6, 1, 0, 0);
}
}
}
if (ri.renderPass->getType() == RenderPassType::RENDER_PASS_PARTICLE_OCCLUSION) {
if (m_flareTexture.val.isBound() && m_flareSize > 0.0f) {
KRMesh* sphereModel = getContext().getMeshManager()->getMesh("__sphere");
if (sphereModel) {
Matrix4 occlusion_test_sphere_matrix = Matrix4();
occlusion_test_sphere_matrix.scale(m_localScale.val * m_flareOcclusionSize);
occlusion_test_sphere_matrix.translate(m_localTranslation);
if (m_parentNode) {
occlusion_test_sphere_matrix *= m_parentNode->getModelMatrix();
}
PipelineInfo info{};
std::string shader_name("occlusion_test");
info.shader_name = &shader_name;
info.pCamera = ri.camera;
info.point_lights = &ri.point_lights;
info.directional_lights = &ri.directional_lights;
info.spot_lights = &ri.spot_lights;
info.renderPass = ri.renderPass;
info.rasterMode = RasterMode::kAdditive;
info.cullMode = CullMode::kCullNone;
info.layout = sphereModel->getLayout(0);
KRPipeline* pPipeline = getContext().getPipelineManager()->getPipeline(*ri.surface, info);
if (pPipeline && pPipeline->bind(ri, occlusion_test_sphere_matrix)) {
GLDEBUG(glGenQueriesEXT(1, &m_occlusionQuery));
#if TARGET_OS_IPHONE || defined(ANDROID)
GLDEBUG(glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, m_occlusionQuery));
#else
GLDEBUG(glBeginQuery(GL_SAMPLES_PASSED, m_occlusionQuery));
#endif
sphereModel->renderNoMaterials(ri.commandBuffer, ri.renderPass, getName(), "occlusion_test", 1.0f);
#if TARGET_OS_IPHONE || defined(ANDROID)
GLDEBUG(glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT));
#else
GLDEBUG(glEndQuery(GL_SAMPLES_PASSED));
#endif
}
}
}
ri.reflectedObjects.pop_back();
}
if (ri.renderPass->getType() == RenderPassType::RENDER_PASS_ADDITIVE_PARTICLES) {
if (m_flareTexture.val.isBound() && m_flareSize > 0.0f) {
if (m_occlusionQuery) {
int params = 0;
GLDEBUG(glGetQueryObjectuivEXT(m_occlusionQuery, GL_QUERY_RESULT_EXT, &params));
GLDEBUG(glDeleteQueriesEXT(1, &m_occlusionQuery));
if (params) {
KRMeshManager::KRVBOData& vertices = getContext().getMeshManager()->KRENGINE_VBO_DATA_2D_SQUARE_VERTICES;
// Render light flare on transparency pass
PipelineInfo info{};
std::string shader_name("flare");
info.shader_name = &shader_name;
info.pCamera = ri.camera;
info.point_lights = &ri.point_lights;
info.directional_lights = &ri.directional_lights;
info.spot_lights = &ri.spot_lights;
info.renderPass = ri.renderPass;
info.rasterMode = RasterMode::kAdditiveNoTest;
info.cullMode = CullMode::kCullNone;
info.layout = vertices.getLayout();
KRPipeline* pShader = getContext().getPipelineManager()->getPipeline(*ri.surface, info);
if (pShader && pShader->bind(ri, getModelMatrix())) {
m_pContext->getMeshManager()->bindVBO(ri.commandBuffer, &vertices, 1.0f);
vkCmdDraw(ri.commandBuffer, 4, 1, 0, 0);
}
}
}
}
}
}
void KRLight::allocateShadowBuffers(int cBuffers)
{
// First deallocate buffers no longer needed
for (int iShadow = cBuffers; iShadow < KRENGINE_MAX_SHADOW_BUFFERS; iShadow++) {
if (shadowDepthTexture[iShadow]) {
GLDEBUG(glDeleteTextures(1, shadowDepthTexture + iShadow));
shadowDepthTexture[iShadow] = 0;
}
if (shadowFramebuffer[iShadow]) {
GLDEBUG(glDeleteFramebuffers(1, shadowFramebuffer + iShadow));
shadowFramebuffer[iShadow] = 0;
}
}
// Allocate newly required buffers
for (int iShadow = 0; iShadow < cBuffers; iShadow++) {
Vector2 viewportSize = m_shadowViewports[iShadow].getSize();
if (!shadowDepthTexture[iShadow]) {
shadowValid[iShadow] = false;
GLDEBUG(glGenFramebuffers(1, shadowFramebuffer + iShadow));
GLDEBUG(glGenTextures(1, shadowDepthTexture + iShadow));
// ===== Create offscreen shadow framebuffer object =====
GLDEBUG(glBindFramebuffer(GL_FRAMEBUFFER, shadowFramebuffer[iShadow]));
// ----- Create Depth Texture for shadowFramebuffer -----
// TODO - Vulkan Refactoring. Note: shadowDepthTexture Sampler needs clamp-to-edge and linear filtering
GLDEBUG(glBindTexture(GL_TEXTURE_2D, shadowDepthTexture[iShadow]));
GLDEBUG(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
GLDEBUG(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
// m_pContext->getTextureManager()->_setWrapModeS(shadowDepthTexture[iShadow], GL_CLAMP_TO_EDGE);
// m_pContext->getTextureManager()->_setWrapModeT(shadowDepthTexture[iShadow], GL_CLAMP_TO_EDGE);
#if GL_EXT_shadow_samplers
GLDEBUG(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_EXT, GL_COMPARE_REF_TO_TEXTURE_EXT)); // TODO - Detect GL_EXT_shadow_samplers and only activate if available
GLDEBUG(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC_EXT, GL_LEQUAL)); // TODO - Detect GL_EXT_shadow_samplers and only activate if available
#endif
GLDEBUG(glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, (int)viewportSize.x, (int)viewportSize.y, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL));
GLDEBUG(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, shadowDepthTexture[iShadow], 0));
}
}
m_cShadowBuffers = cBuffers;
}
void KRLight::deleteBuffers()
{
// Called when this light wasn't used in the last frame, so we can free the resources for use by other lights
allocateShadowBuffers(0);
}
void KRLight::invalidateShadowBuffers()
{
for (int iShadow = 0; iShadow < m_cShadowBuffers; iShadow++) {
shadowValid[iShadow] = false;
}
}
int KRLight::configureShadowBufferViewports(const KRViewport& viewport)
{
return 0;
}
void KRLight::renderShadowBuffers(RenderInfo& ri)
{
const KRViewport* prevViewport = ri.viewport;
for (int iShadow = 0; iShadow < m_cShadowBuffers; iShadow++) {
if (!shadowValid[iShadow]) {
shadowValid[iShadow] = true;
GLDEBUG(glBindFramebuffer(GL_FRAMEBUFFER, shadowFramebuffer[iShadow]));
GLDEBUG(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, shadowDepthTexture[iShadow], 0));
GLDEBUG(glViewport(0, 0, (int)m_shadowViewports[iShadow].getSize().x, (int)m_shadowViewports[iShadow].getSize().y));
GLDEBUG(glClearDepthf(0.0f));
GLDEBUG(glClear(GL_DEPTH_BUFFER_BIT));
GLDEBUG(glViewport(1, 1, (int)m_shadowViewports[iShadow].getSize().x - 2, (int)m_shadowViewports[iShadow].getSize().y - 2));
GLDEBUG(glClearDepthf(1.0f));
GLDEBUG(glClear(GL_DEPTH_BUFFER_BIT));
GLDEBUG(glDisable(GL_DITHER));
ri.viewport = &m_shadowViewports[iShadow];
// Use shader program
PipelineInfo info{};
std::string shader_name("ShadowShader");
info.shader_name = &shader_name;
info.pCamera = ri.camera;
info.renderPass = ri.renderPass;
info.rasterMode = RasterMode::kOpaqueLessTest; // TODO - This is sub-optimal. Evaluate increasing depth buffer resolution instead of disabling depth test.
info.cullMode = CullMode::kCullNone; // Disabling culling, which eliminates some self-cast shadow artifacts
KRPipeline* shadowShader = m_pContext->getPipelineManager()->getPipeline(*ri.surface, info);
if (shadowShader && shadowShader->bind(ri, Matrix4())) {
getScene().render(ri);
}
}
}
ri.viewport = prevViewport;
}
int KRLight::getShadowBufferCount()
{
int cBuffers = 0;
for (int iBuffer = 0; iBuffer < m_cShadowBuffers; iBuffer++) {
if (shadowValid[iBuffer]) {
cBuffers++;
} else {
break;
}
}
return cBuffers;
}
int* KRLight::getShadowTextures()
{
return shadowDepthTexture;
}
KRViewport* KRLight::getShadowViewports()
{
return m_shadowViewports;
}
bool KRLight::getShaderValue(const KRCamera* camera, ShaderValue value, float* output) const
{
switch (value) {
case ShaderValue::light_intensity:
*output = m_intensity;
return true;
case ShaderValue::light_decay_start:
*output = getDecayStart();
return true;
case ShaderValue::light_cutoff:
*output = KRLIGHT_MIN_INFLUENCE;
return true;
case ShaderValue::flare_size:
*output = m_flareSize;
return true;
case ShaderValue::dust_particle_size:
*output = m_dust_particle_size;
return true;
default:
return KRNode::getShaderValue(camera, value, output);
}
}
bool KRLight::getShaderValue(const KRCamera* camera, ShaderValue value, hydra::Vector2* output) const
{
if (camera) {
switch (value) {
case ShaderValue::slice_depth_scale:
{
int slice_count = getSliceCount(camera);
float slice_near = -camera->getSettings()->getPerspectiveNearZ();
float slice_far = -camera->getSettings()->volumetric_environment_max_distance;
float slice_spacing = (slice_far - slice_near) / slice_count;
*output = Vector2::Create(slice_near, slice_spacing);
return true;
}
break;
default:
break;
}
}
return KRNode::getShaderValue(camera, value, output);
}
bool KRLight::getShaderValue(const KRCamera* camera, ShaderValue value, hydra::Vector3* output) const
{
switch (value) {
case ShaderValue::light_position:
*output = m_localTranslation;
return true;
case ShaderValue::light_color:
*output = m_color;
return true;
case ShaderValue::particle_origin:
*output = Matrix4::DotWDiv(Matrix4::Invert(getParticleModelMatrix(*camera->getViewport())), Vector3::Zero());
return true;
case ShaderValue::dust_particle_color:
*output = m_color.val * m_dust_particle_intensity * m_intensity * camera->settings.dust_particle_intensity;
return true;
default:
return KRNode::getShaderValue(camera, value, output);
}
}
bool KRLight::getImageBinding(const std::string& name, const KRTextureBinding** binding, KRSampler** sample) const
{
if (name == "flareTexture") {
*binding = &m_flareTexture.val;
*sample = getContext().getSamplerManager()->DEFAULT_CLAMPED_SAMPLER;
return true;
} else {
return KRNode::getImageBinding(name, binding, sample);
}
}