diff --git a/Source/Game-Lib/Game-Lib/ECS/Components/AABB.h b/Source/Game-Lib/Game-Lib/ECS/Components/AABB.h index e9ce95d..b9d06d5 100644 --- a/Source/Game-Lib/Game-Lib/ECS/Components/AABB.h +++ b/Source/Game-Lib/Game-Lib/ECS/Components/AABB.h @@ -16,4 +16,6 @@ namespace ECS::Components vec3 min; vec3 max; }; + + struct DirtyAABB {}; } \ No newline at end of file diff --git a/Source/Game-Lib/Game-Lib/ECS/Components/Decal.h b/Source/Game-Lib/Game-Lib/ECS/Components/Decal.h new file mode 100644 index 0000000..00e6c19 --- /dev/null +++ b/Source/Game-Lib/Game-Lib/ECS/Components/Decal.h @@ -0,0 +1,18 @@ +#pragma once +#include +#include + +namespace ECS::Components +{ + struct Decal + { + std::string texturePath; + Color colorMultiplier = Color::White; + hvec2 thresholdMinMax = hvec2(0.0f, 1.0f); + hvec2 minUV = hvec2(0.0f, 0.0f); + hvec2 maxUV = hvec2(1.0f, 1.0f); + u32 flags = 0; // DecalFlags + }; + + struct DirtyDecal {}; +} \ No newline at end of file diff --git a/Source/Game-Lib/Game-Lib/ECS/Systems/CalculateCameraMatrices.cpp b/Source/Game-Lib/Game-Lib/ECS/Systems/CalculateCameraMatrices.cpp index f017015..e407c71 100644 --- a/Source/Game-Lib/Game-Lib/ECS/Systems/CalculateCameraMatrices.cpp +++ b/Source/Game-Lib/Game-Lib/ECS/Systems/CalculateCameraMatrices.cpp @@ -83,8 +83,6 @@ namespace ECS::Systems if (CVAR_CameraLockCullingFrustum.Get() == 0) { - mat4x4 m = glm::transpose(camera.worldToClip); - glm::vec3 Front = glm::vec3(0, 0, 1); glm::vec3 Right = glm::vec3(1, 0, 0); glm::vec3 Up = glm::vec3(0, 1, 0); diff --git a/Source/Game-Lib/Game-Lib/ECS/Systems/UpdateAABBs.cpp b/Source/Game-Lib/Game-Lib/ECS/Systems/UpdateAABBs.cpp index 2577125..1e37e41 100644 --- a/Source/Game-Lib/Game-Lib/ECS/Systems/UpdateAABBs.cpp +++ b/Source/Game-Lib/Game-Lib/ECS/Systems/UpdateAABBs.cpp @@ -8,42 +8,55 @@ namespace ECS::Systems { + void UpdateWorldAABB(const Components::Transform& transform, const Components::AABB& aabb, Components::WorldAABB& worldAABB) + { + // Calculate the world AABB + glm::vec3 min = aabb.centerPos - aabb.extents; + glm::vec3 max = aabb.centerPos + aabb.extents; + + glm::vec3 corners[8] = { + glm::vec3(min.x, min.y, min.z), + glm::vec3(min.x, min.y, max.z), + glm::vec3(min.x, max.y, min.z), + glm::vec3(min.x, max.y, max.z), + glm::vec3(max.x, min.y, min.z), + glm::vec3(max.x, min.y, max.z), + glm::vec3(max.x, max.y, min.z), + glm::vec3(max.x, max.y, max.z) + }; + + const mat4x4 transformMatrix = transform.GetMatrix(); + for (i32 i = 0; i < 8; ++i) + { + corners[i] = transformMatrix * glm::vec4(corners[i], 1.0f); + } + + worldAABB.min = vec3(1000000000.0f); + worldAABB.max = vec3(-1000000000.0f); + + for (i32 i = 1; i < 8; ++i) + { + worldAABB.min = glm::min(worldAABB.min, corners[i]); + worldAABB.max = glm::max(worldAABB.max, corners[i]); + } + } + void UpdateAABBs::Update(entt::registry& registry, f32 deltaTime) { ZoneScopedN("ECS::UpdateAABBs"); - auto view = registry.view(); - view.each([&](entt::entity entity, Components::Transform& transform, Components::AABB& aabb, Components::WorldAABB& worldAABB, ECS::Components::DirtyTransform& dirtyTransform) + // Update AABBs for entities with dirty transforms + auto dirtyTransformView = registry.view(); + dirtyTransformView.each([&](entt::entity entity, Components::Transform& transform, Components::AABB& aabb, Components::WorldAABB& worldAABB, ECS::Components::DirtyTransform& dirtyTransform) + { + UpdateWorldAABB(transform, aabb, worldAABB); + }); + + // Update AABBs for entities with dirty AABBs + auto dirtyAABBView = registry.view(); + dirtyAABBView.each([&](entt::entity entity, Components::Transform& transform, Components::AABB& aabb, Components::WorldAABB& worldAABB) { - // Calculate the world AABB - glm::vec3 min = aabb.centerPos - aabb.extents; - glm::vec3 max = aabb.centerPos + aabb.extents; - - glm::vec3 corners[8] = { - glm::vec3(min.x, min.y, min.z), - glm::vec3(min.x, min.y, max.z), - glm::vec3(min.x, max.y, min.z), - glm::vec3(min.x, max.y, max.z), - glm::vec3(max.x, min.y, min.z), - glm::vec3(max.x, min.y, max.z), - glm::vec3(max.x, max.y, min.z), - glm::vec3(max.x, max.y, max.z) - }; - - const mat4x4 transformMatrix = transform.GetMatrix(); - for (i32 i = 0; i < 8; ++i) - { - corners[i] = transformMatrix * glm::vec4(corners[i], 1.0f); - } - - worldAABB.min = vec3(1000000000.0f); - worldAABB.max = vec3(-1000000000.0f); - - for (i32 i = 1; i < 8; ++i) - { - worldAABB.min = glm::min(worldAABB.min, corners[i]); - worldAABB.max = glm::max(worldAABB.max, corners[i]); - } + UpdateWorldAABB(transform, aabb, worldAABB); }); } } \ No newline at end of file diff --git a/Source/Game-Lib/Game-Lib/Editor/Inspector.cpp b/Source/Game-Lib/Game-Lib/Editor/Inspector.cpp index e43f501..08b2497 100644 --- a/Source/Game-Lib/Game-Lib/Editor/Inspector.cpp +++ b/Source/Game-Lib/Game-Lib/Editor/Inspector.cpp @@ -1,20 +1,12 @@ #include "Inspector.h" -#include -#include #include -#include -#include -#include -#include -#include -#include -#include #include #include #include #include #include +#include #include #include #include @@ -23,6 +15,15 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include @@ -322,8 +323,12 @@ namespace Editor InspectEntityTransform(entity); + InspectEntityAABB(entity); + InspectEntityModel(entity); + InspectEntityDecal(entity); + if (ECS::Components::Unit* unit = registry->try_get(entity)) { ImGui::Text("Body ID: %d", unit->bodyID); @@ -521,6 +526,119 @@ namespace Editor ImGui::PopStyleColor(); } + void Inspector::InspectEntityAABB(entt::entity entity) + { + entt::registry* registry = ServiceLocator::GetEnttRegistries()->gameRegistry; + + ECS::Components::AABB* aabb = registry->try_get(entity); + + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.4f, 0.4f, 0.4f, 1.0f)); + + if (aabb) + { + if (Util::Imgui::BeginGroupPanel("AABB")) + { + bool isDirty = false; + + // Center Pos + vec3 centerPos = aabb->centerPos; + if (ImGui::DragFloat3("center pos", ¢erPos.x)) + { + aabb->centerPos = centerPos; + isDirty = true; + } + + // Extents + vec3 extents = aabb->extents; + if (ImGui::DragFloat3("extents", &extents.x)) + { + aabb->extents = extents; + isDirty = true; + } + + if (isDirty) + { + registry->emplace_or_replace(entity); + } + } + Util::Imgui::EndGroupPanel(); + } + ImGui::PopStyleColor(); + } + + void Inspector::InspectEntityDecal(entt::entity entity) + { + entt::registry* registry = ServiceLocator::GetEnttRegistries()->gameRegistry; + + ECS::Components::Decal* decal = registry->try_get(entity); + + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.4f, 0.4f, 0.4f, 1.0f)); + + if (decal) + { + if (Util::Imgui::BeginGroupPanel("Decal")) + { + bool isDirty = false; + + std::string texturePath = decal->texturePath; + if (ImGui::InputText("Texture Path", &texturePath)) + { + decal->texturePath = texturePath; + isDirty = true; + } + + Color color = decal->colorMultiplier; + if (ImGui::ColorEdit3("Color Multiplier", &color.r)) + { + decal->colorMultiplier = color; + isDirty = true; + } + + f32 thresholdMin = f32(decal->thresholdMinMax.x); + if (ImGui::SliderFloat("Threshold Min", &thresholdMin, -1.0f, 1.0f)) + { + decal->thresholdMinMax.x = f16(thresholdMin); + isDirty = true; + } + + f32 thresholdMax = f32(decal->thresholdMinMax.y); + if (ImGui::SliderFloat("Threshold Max", &thresholdMax, -1.0f, 1.0f)) + { + decal->thresholdMinMax.y = f16(thresholdMax); + isDirty = true; + } + + vec2 minUV = vec2(decal->minUV); + if (ImGui::DragFloat2("Min UV", &minUV.x, 0.01f, 0.0f, 1.0f)) + { + decal->minUV = hvec2(minUV); + isDirty = true; + } + + vec2 maxUV = vec2(decal->maxUV); + if (ImGui::DragFloat2("Max UV", &maxUV.x, 0.01f, 0.0f, 1.0f)) + { + decal->maxUV = hvec2(maxUV); + isDirty = true; + } + + u32 flags = decal->flags; + if (ImGui::InputScalar("Flags", ImGuiDataType_U32, &flags, nullptr, nullptr, "%u", ImGuiInputTextFlags_CharsHexadecimal)) + { + decal->flags = flags; + isDirty = true; + } + + if (isDirty) + { + registry->emplace_or_replace(entity); + } + } + Util::Imgui::EndGroupPanel(); + } + ImGui::PopStyleColor(); + } + bool Inspector::DrawGizmo(entt::registry* registry, entt::entity entity, ECS::Components::Transform& transform) { entt::registry::context& ctx = registry->ctx(); diff --git a/Source/Game-Lib/Game-Lib/Editor/Inspector.h b/Source/Game-Lib/Game-Lib/Editor/Inspector.h index db4073d..7ac976f 100644 --- a/Source/Game-Lib/Game-Lib/Editor/Inspector.h +++ b/Source/Game-Lib/Game-Lib/Editor/Inspector.h @@ -51,6 +51,8 @@ namespace Editor void InspectEntity(entt::entity entity); void InspectEntityTransform(entt::entity entity); void InspectEntityModel(entt::entity entity); + void InspectEntityAABB(entt::entity entity); + void InspectEntityDecal(entt::entity entity); bool DrawGizmo(entt::registry* registry, entt::entity entity, ECS::Components::Transform& transform); void DrawGizmoControls(); diff --git a/Source/Game-Lib/Game-Lib/Rendering/GameRenderer.cpp b/Source/Game-Lib/Game-Lib/Rendering/GameRenderer.cpp index b9f7719..3be5a05 100644 --- a/Source/Game-Lib/Game-Lib/Rendering/GameRenderer.cpp +++ b/Source/Game-Lib/Game-Lib/Rendering/GameRenderer.cpp @@ -3,6 +3,7 @@ #include "UIRenderer.h" #include "Debug/DebugRenderer.h" #include "Debug/JoltDebugRenderer.h" +#include "Light/LightRenderer.h" #include "Terrain/TerrainRenderer.h" #include "Terrain/TerrainLoader.h" #include "Terrain/TerrainManipulator.h" @@ -145,7 +146,8 @@ GameRenderer::GameRenderer(InputManager* inputManager) _joltDebugRenderer = new JoltDebugRenderer(_renderer, _debugRenderer); _modelRenderer = new ModelRenderer(_renderer, _debugRenderer); - _modelLoader = new ModelLoader(_modelRenderer); + _lightRenderer = new LightRenderer(_renderer, _debugRenderer, _modelRenderer); + _modelLoader = new ModelLoader(_modelRenderer, _lightRenderer); _modelLoader->Init(); _liquidRenderer = new LiquidRenderer(_renderer, _debugRenderer); @@ -159,8 +161,8 @@ GameRenderer::GameRenderer(InputManager* inputManager) _modelLoader->SetTerrainLoader(_terrainLoader); _mapLoader = new MapLoader(_terrainLoader, _modelLoader, _liquidLoader); - - _materialRenderer = new MaterialRenderer(_renderer, _terrainRenderer, _modelRenderer); + + _materialRenderer = new MaterialRenderer(_renderer, _terrainRenderer, _modelRenderer, _lightRenderer); _skyboxRenderer = new SkyboxRenderer(_renderer, _debugRenderer); _editorRenderer = new EditorRenderer(_renderer, _debugRenderer); _canvasRenderer = new CanvasRenderer(_renderer, _debugRenderer); @@ -206,6 +208,7 @@ void GameRenderer::UpdateRenderers(f32 deltaTime) _materialRenderer->Update(deltaTime); _joltDebugRenderer->Update(deltaTime); _debugRenderer->Update(deltaTime); + _lightRenderer->Update(deltaTime); _pixelQuery->Update(deltaTime); _editorRenderer->Update(deltaTime); _canvasRenderer->Update(deltaTime); @@ -383,6 +386,8 @@ f32 GameRenderer::Render() _liquidRenderer->AddCullingPass(&renderGraph, _resources, _frameIndex); _liquidRenderer->AddGeometryPass(&renderGraph, _resources, _frameIndex); + _lightRenderer->AddClassificationPass(&renderGraph, _resources, _frameIndex); + _materialRenderer->AddPreEffectsPass(&renderGraph, _resources, _frameIndex); _effectRenderer->AddSSAOPass(&renderGraph, _resources, _frameIndex); @@ -396,6 +401,8 @@ f32 GameRenderer::Render() _canvasRenderer->AddCanvasPass(&renderGraph, _resources, _frameIndex); _debugRenderer->Add2DPass(&renderGraph, _resources, _frameIndex); + _lightRenderer->AddDebugPass(&renderGraph, _resources, _frameIndex); + Renderer::ImageID finalTarget = isEditorMode ? _resources.finalColor : _resources.sceneColor; _uiRenderer->AddImguiPass(&renderGraph, _resources, _frameIndex, finalTarget); @@ -532,6 +539,17 @@ void GameRenderer::CreatePermanentResources() sceneColorDesc.clearColor = Color(0.43f, 0.50f, 0.56f, 1.0f); // Slate gray _resources.finalColor = _renderer->CreateImage(sceneColorDesc); + // Debug rendertarget + Renderer::ImageDesc debugColorDesc; + debugColorDesc.debugName = "DebugColor"; + debugColorDesc.dimensions = vec2(1.0f, 1.0f); + debugColorDesc.dimensionType = Renderer::ImageDimensionType::DIMENSION_SCALE_RENDERSIZE; + debugColorDesc.format = _renderer->GetSwapChainImageFormat(); + debugColorDesc.sampleCount = Renderer::SampleCount::SAMPLE_COUNT_1; + debugColorDesc.clearColor = Color::Clear; + + _resources.debugColor = _renderer->CreateImage(debugColorDesc); + // SSAO Renderer::ImageDesc ssaoTargetDesc; ssaoTargetDesc.debugName = "SSAOTarget"; diff --git a/Source/Game-Lib/Game-Lib/Rendering/GameRenderer.h b/Source/Game-Lib/Game-Lib/Rendering/GameRenderer.h index a00b306..7f3e63f 100644 --- a/Source/Game-Lib/Game-Lib/Rendering/GameRenderer.h +++ b/Source/Game-Lib/Game-Lib/Rendering/GameRenderer.h @@ -32,6 +32,7 @@ class MaterialRenderer; class SkyboxRenderer; class EditorRenderer; class DebugRenderer; +class LightRenderer; class JoltDebugRenderer; class CanvasRenderer; class LiquidLoader; @@ -134,6 +135,7 @@ class GameRenderer MaterialRenderer* _materialRenderer = nullptr; SkyboxRenderer* _skyboxRenderer = nullptr; DebugRenderer* _debugRenderer = nullptr; + LightRenderer* _lightRenderer = nullptr; JoltDebugRenderer* _joltDebugRenderer = nullptr; EditorRenderer* _editorRenderer = nullptr; CanvasRenderer* _canvasRenderer = nullptr; diff --git a/Source/Game-Lib/Game-Lib/Rendering/Light/LightRenderer.cpp b/Source/Game-Lib/Game-Lib/Rendering/Light/LightRenderer.cpp new file mode 100644 index 0000000..0ce813d --- /dev/null +++ b/Source/Game-Lib/Game-Lib/Rendering/Light/LightRenderer.cpp @@ -0,0 +1,438 @@ +#include "LightRenderer.h" + +#include "Game-Lib/Application/EnttRegistries.h" +#include "Game-Lib/ECS/Components/AABB.h" +#include "Game-Lib/ECS/Components/Decal.h" +#include "Game-Lib/ECS/Components/Events.h" +#include "Game-Lib/ECS/Components/Name.h" +#include "Game-Lib/ECS/Util/EventUtil.h" +#include "Game-Lib/ECS/Util/Transforms.h" +#include "Game-Lib/Editor/EditorHandler.h" +#include "Game-Lib/Editor/TerrainTools.h" +#include "Game-Lib/Rendering/Debug/DebugRenderer.h" +#include "Game-Lib/Rendering/Model/ModelRenderer.h" +#include "Game-Lib/Rendering/RenderResources.h" +#include "Game-Lib/Rendering/RenderUtils.h" +#include "Game-Lib/Rendering/Terrain/TerrainRenderer.h" +#include "Game-Lib/Util/PhysicsUtil.h" +#include "Game-Lib/Util/ServiceLocator.h" + +#include + +#include +#include + +#include + +AutoCVar_ShowFlag CVAR_DebugLightTiles(CVarCategory::Client | CVarCategory::Rendering, "debugLightTiles", "Debug draw light tiles", ShowFlag::DISABLED); + +LightRenderer::LightRenderer(Renderer::Renderer* renderer, DebugRenderer* debugRenderer, ModelRenderer* modelRenderer) + : _renderer(renderer) + , _debugRenderer(debugRenderer) + , _modelRenderer(modelRenderer) +{ + CreatePermanentResources(); +} + +LightRenderer::~LightRenderer() +{ + +} + +void LightRenderer::Update(f32 deltaTime) +{ + ZoneScoped; + + entt::registry* registry = ServiceLocator::GetEnttRegistries()->gameRegistry; + + // Handle remove requests + u32 numDecalRemovals = static_cast(_decalRemoveRequests.try_dequeue_bulk(_decalRemoveWork.begin(), 64)); + if (numDecalRemovals > 0) + { + for (u32 i = 0; i < numDecalRemovals; i++) + { + DecalRemoveRequest& request = _decalRemoveWork[i]; + auto it = _entityToDecalID.find(request.entity); + if (it == _entityToDecalID.end()) + { + continue; + } + + u32 decalID = it->second; + _decals.Remove(decalID); + _decalIDToEntity.erase(decalID); + _entityToDecalID.erase(it); + } + } + + // Update existing decals if dirty transform + auto dirtyDecalTransformView = registry->view(); + dirtyDecalTransformView.each([this, ®istry](entt::entity entity, ECS::Components::Transform& transform, ECS::Components::AABB& aabb, ECS::Components::Decal& decalComp, ECS::Components::DirtyTransform& dirtyTransform) + { + auto it = _entityToDecalID.find(entity); + if (it == _entityToDecalID.end()) + { + // New decal we didn't know about, should we maybe just register it? For now it requires explicit AddDecal call + return; + } + + u32 decalID = it->second; + GPUDecal& decal = _decals[decalID]; + + vec3 position = transform.GetWorldPosition() + aabb.centerPos; + decal.positionAndTextureID = vec4(position, decal.positionAndTextureID.w); + decal.rotation = transform.GetWorldRotation(); + + _decals.SetDirtyElement(decalID); + }); + + // Update existing decals if dirty AABB + auto dirtyDecalAABBView = registry->view(); + dirtyDecalAABBView.each([this, ®istry](entt::entity entity, ECS::Components::Transform& transform, ECS::Components::AABB& aabb, ECS::Components::Decal& decalComp) + { + auto it = _entityToDecalID.find(entity); + if (it == _entityToDecalID.end()) + { + // New decal we didn't know about, should we maybe just register it? For now it requires explicit AddDecal call + return; + } + + u32 decalID = it->second; + GPUDecal& decal = _decals[decalID]; + + vec3 position = transform.GetWorldPosition() + aabb.centerPos; + decal.positionAndTextureID = vec4(position, decal.positionAndTextureID.w); + decal.extentsAndColor = vec4(aabb.extents, decal.extentsAndColor.w); + + _decals.SetDirtyElement(decalID); + }); + + // Update existing decals if dirty decal settings + auto dirtyDecalView = registry->view(); + dirtyDecalView.each([this, ®istry](entt::entity entity, ECS::Components::Transform& transform, ECS::Components::AABB& aabb, ECS::Components::Decal& decalComp) + { + auto it = _entityToDecalID.find(entity); + if (it == _entityToDecalID.end()) + { + // New decal we didn't know about, should we maybe just register it? For now it requires explicit AddDecal call + return; + } + + u32 decalID = it->second; + GPUDecal& decal = _decals[decalID]; + + // Load the texture into the model renderer (bigger chance for texture reuse than having our separate texture array) + _modelRenderer->LoadTexture(decalComp.texturePath, *reinterpret_cast(&decal.positionAndTextureID.w)); + + u32 colorInt = decalComp.colorMultiplier.ToABGR32(); + + decal.extentsAndColor.w = *reinterpret_cast(&colorInt); + decal.thresholdMinMax = decalComp.thresholdMinMax; + decal.minUV = decalComp.minUV; + decal.maxUV = decalComp.maxUV; + decal.flags = decalComp.flags; + + _decals.SetDirtyElement(decalID); + }); + registry->clear(); + + // Handle add requests + u32 numDecalAdds = static_cast(_decalAddRequests.try_dequeue_bulk(_decalAddWork.begin(), 64)); + if (numDecalAdds > 0) + { + for (u32 i = 0; i < numDecalAdds; i++) + { + DecalAddRequest& request = _decalAddWork[i]; + + ECS::Components::Transform& transform = registry->get(request.entity); + ECS::Components::AABB& aabb = registry->get(request.entity); + ECS::Components::Decal& decalComp = registry->get(request.entity); + + // Load the texture into the model renderer (bigger chance for texture reuse than having our separate texture array) + u32 textureID; + _modelRenderer->LoadTexture(decalComp.texturePath, textureID); + + // Add decal + vec3 position = transform.GetWorldPosition() + aabb.centerPos; + quat rotation = transform.GetWorldRotation(); + + u32 colorInt = decalComp.colorMultiplier.ToABGR32(); + + GPUDecal decal + { + .positionAndTextureID = vec4(position, *reinterpret_cast(&textureID)), + .rotation = rotation, + .extentsAndColor = vec4(aabb.extents, *reinterpret_cast(&colorInt)), + .thresholdMinMax = decalComp.thresholdMinMax, + .minUV = decalComp.minUV, + .maxUV = decalComp.maxUV, + .flags = decalComp.flags, + }; + + u32 decalID = _decals.Add(decal); + _decalIDToEntity[decalID] = request.entity; + _entityToDecalID[request.entity] = decalID; + } + } + + SyncToGPU(); + + // Debug draw decals as wireframes + if (CVAR_DebugLightTiles.Get() == ShowFlag::ENABLED) + { + for (u32 i = 0; i < _decals.Count(); i++) + { + const GPUDecal& decal = _decals[i]; + + vec3 min = vec3(decal.positionAndTextureID) - vec3(decal.extentsAndColor); + vec3 max = vec3(decal.positionAndTextureID) + vec3(decal.extentsAndColor); + + _debugRenderer->DrawOBB3D(vec3(decal.positionAndTextureID), vec3(decal.extentsAndColor), decal.rotation, Color::Cyan); + } + } + + /* + ECS::Util::EventUtil::OnEvent([&](const ECS::Components::MapLoadedEvent& event) + { + entt::registry* registry = ServiceLocator::GetEnttRegistries()->gameRegistry; + + entt::entity decalEntity = registry->create(); + + ECS::TransformSystem& ts = ECS::TransformSystem::Get(*registry); + + registry->emplace(decalEntity, "Test Decal"); + + registry->emplace(decalEntity); + ts.SetWorldPosition(decalEntity, vec3(-1414.81f, 343.78f, 1012.89f)); + ts.SetWorldRotation(decalEntity, quat(vec3(glm::radians(90.0f), 0.0f, 0.0f))); + registry->emplace(decalEntity, vec3(0.0f), vec3(5.0f, 5.0f, 10.0f)); + + auto& decal = registry->emplace(decalEntity); + decal.texturePath = "Data/Texture/interface/spellshadow/spell-shadow-acceptable.dds"; + + registry->emplace(decalEntity); + + AddDecal(decalEntity); + });*/ +} + +void LightRenderer::Clear() +{ + DecalAddRequest decalAddRequest; + while (_decalAddRequests.try_dequeue(decalAddRequest)) {} + + DecalRemoveRequest decalRemoveRequest; + while (_decalRemoveRequests.try_dequeue(decalRemoveRequest)) {} + + _decals.Clear(); + _decalIDToEntity.clear(); + _entityToDecalID.clear(); +} + +void LightRenderer::AddClassificationPass(Renderer::RenderGraph* renderGraph, RenderResources& resources, u8 frameIndex) +{ + struct ClassificationPassData + { + Renderer::BufferMutableResource entityTilesBuffer; + + Renderer::DepthImageResource depth; + + Renderer::ImageMutableResource debugColor; + + Renderer::DescriptorSetResource debugSet; + Renderer::DescriptorSetResource globalSet; + Renderer::DescriptorSetResource classifySet; + }; + + renderGraph->AddPass("Light Classification", + [this, &resources](ClassificationPassData& data, Renderer::RenderGraphBuilder& builder) // Setup + { + data.entityTilesBuffer = builder.Write(_entityTilesBuffer, Renderer::BufferPassUsage::COMPUTE | Renderer::BufferPassUsage::TRANSFER); + + data.depth = builder.Read(resources.depth, Renderer::PipelineType::COMPUTE); + + data.debugColor = builder.Write(resources.debugColor, Renderer::PipelineType::COMPUTE, Renderer::LoadMode::CLEAR); + + builder.Read(resources.cameras.GetBuffer(), Renderer::BufferPassUsage::COMPUTE); + builder.Read(_decals.GetBuffer(), Renderer::BufferPassUsage::COMPUTE); + + _debugRenderer->RegisterCullingPassBufferUsage(builder); + + data.debugSet = builder.Use(_debugRenderer->GetDebugDescriptorSet()); + data.globalSet = builder.Use(resources.globalDescriptorSet); + data.classifySet = builder.Use(_classifyPassDescriptorSet); + + return true; // Return true from setup to enable this pass, return false to disable it + }, + [this, &resources, frameIndex](ClassificationPassData& data, Renderer::RenderGraphResources& graphResources, Renderer::CommandList& commandList) // Execute + { + GPU_SCOPED_PROFILER_ZONE(commandList, LightClassificationPass); + + Renderer::ComputePipelineDesc pipelineDesc; + graphResources.InitializePipelineDesc(pipelineDesc); + + Renderer::ComputeShaderDesc shaderDesc; + shaderDesc.path = "Light/Classification.cs.hlsl"; + pipelineDesc.computeShader = _renderer->LoadShader(shaderDesc); + + Renderer::ComputePipelineID pipeline = _renderer->CreatePipeline(pipelineDesc); + commandList.BeginPipeline(pipeline); + + uvec2 outputSize = _renderer->GetImageDimensions(resources.sceneColor, 0); + u32 numTilesX = (outputSize.x + TILE_SIZE - 1) / TILE_SIZE; + u32 numTilesY = (outputSize.y + TILE_SIZE - 1) / TILE_SIZE; + uvec2 tileCount = uvec2(numTilesX, numTilesY); + + struct Constants + { + u32 maxEntitiesPerTile; + u32 numTotalDecals; + uvec2 tileCount; + vec2 screenSize; + }; + + Constants* constants = graphResources.FrameNew(); + constants->maxEntitiesPerTile = MAX_ENTITIES_PER_TILE; + constants->numTotalDecals = static_cast(_decals.Count()); + constants->tileCount = tileCount; + constants->screenSize = static_cast(outputSize); + + commandList.PushConstant(constants, 0, sizeof(Constants)); + + data.classifySet.Bind("_depthRT", data.depth); + data.classifySet.BindStorage("_debugTexture", data.debugColor); + + // Bind descriptorset + commandList.BindDescriptorSet(Renderer::DescriptorSetSlot::DEBUG, data.debugSet, frameIndex); + commandList.BindDescriptorSet(Renderer::DescriptorSetSlot::GLOBAL, data.globalSet, frameIndex); + commandList.BindDescriptorSet(Renderer::DescriptorSetSlot::PER_PASS, data.classifySet, frameIndex); + + commandList.Dispatch(numTilesX, numTilesY, 1); + + commandList.EndPipeline(pipeline); + }); +} + +void LightRenderer::AddDebugPass(Renderer::RenderGraph* renderGraph, RenderResources& resources, u8 frameIndex) +{ + if (CVAR_DebugLightTiles.Get() == ShowFlag::DISABLED) + return; + + struct DebugPassData + { + Renderer::ImageMutableResource color; + Renderer::ImageResource debug; + + Renderer::DescriptorSetResource globalSet; + Renderer::DescriptorSetResource debugSet; + }; + renderGraph->AddPass("Light Debug Overlay", + [this, &resources](DebugPassData& data, Renderer::RenderGraphBuilder& builder) // Setup + { + data.color = builder.Write(resources.sceneColor, Renderer::PipelineType::GRAPHICS, Renderer::LoadMode::LOAD); + data.debug = builder.Read(resources.debugColor, Renderer::PipelineType::GRAPHICS); + + builder.Read(resources.cameras.GetBuffer(), Renderer::BufferPassUsage::GRAPHICS); + + data.globalSet = builder.Use(resources.globalDescriptorSet); + data.debugSet = builder.Use(_debugPassDescriptorSet); + + return true;// Return true from setup to enable this pass, return false to disable it + }, + [this, frameIndex, &resources](DebugPassData& data, Renderer::RenderGraphResources& graphResources, Renderer::CommandList& commandList) // Execute + { + GPU_SCOPED_PROFILER_ZONE(commandList, DecalDebugOverlay); + + RenderUtils::OverlayParams overlayParams; + overlayParams.baseImage = data.color; + overlayParams.overlayImage = data.debug; + overlayParams.descriptorSet = data.debugSet; + + RenderUtils::Overlay(_renderer, graphResources, commandList, frameIndex, overlayParams); + }); +} + +void LightRenderer::AddDecal(entt::entity entity) +{ + // Make sure entity has required components + entt::registry* registry = ServiceLocator::GetEnttRegistries()->gameRegistry; + if (!registry->all_of(entity)) + { + return; + } + + DecalAddRequest request + { + .entity = entity, + }; + _decalAddRequests.enqueue(request); +} + +void LightRenderer::RemoveDecal(entt::entity entity) +{ + DecalRemoveRequest request + { + .entity = entity, + }; + _decalRemoveRequests.enqueue(request); +} + +void LightRenderer::CreatePermanentResources() +{ + _decals.SetDebugName("Decals"); + _decals.SetUsage(Renderer::BufferUsage::STORAGE_BUFFER); + + _renderer->AddOnRenderSizeChanged([this](const vec2& newSize) + { + RecreateBuffer(newSize); + }); + + _decalAddWork.resize(64); + _decalRemoveWork.resize(64); +} + +u32 LightRenderer::CalculateNumTiles(const vec2& size) +{ + uvec2 numTiles2D = CalculateNumTiles2D(size); + return numTiles2D.x * numTiles2D.y; +} + +uvec2 LightRenderer::CalculateNumTiles2D(const vec2& size) +{ + u32 width = static_cast(size.x); + u32 height = static_cast(size.y); + + u32 numTilesX = (width + TILE_SIZE - 1) / TILE_SIZE; + u32 numTilesY = (height + TILE_SIZE - 1) / TILE_SIZE; + return uvec2(numTilesX, numTilesY); +} + +void LightRenderer::RegisterMaterialPassBufferUsage(Renderer::RenderGraphBuilder& builder) +{ + builder.Read(_entityTilesBuffer, Renderer::BufferPassUsage::COMPUTE); + builder.Read(_decals.GetBuffer(), Renderer::BufferPassUsage::COMPUTE); +} + +void LightRenderer::RecreateBuffer(const vec2& size) +{ + u32 numTiles = CalculateNumTiles(size); + + Renderer::BufferDesc bufferDesc; + bufferDesc.name = "Entity Tiles"; + bufferDesc.size = numTiles * sizeof(u32) * MAX_ENTITIES_PER_TILE * 2; // *2: opaque and transparent arrays + bufferDesc.usage = Renderer::BufferUsage::STORAGE_BUFFER | Renderer::BufferUsage::TRANSFER_DESTINATION; + bufferDesc.cpuAccess = Renderer::BufferCPUAccess::AccessNone; + + _entityTilesBuffer = _renderer->CreateBuffer(_entityTilesBuffer, bufferDesc); + _classifyPassDescriptorSet.Bind("_entityTiles", _entityTilesBuffer); + _materialPassDescriptorSet.Bind("_entityTiles", _entityTilesBuffer); +} + +void LightRenderer::SyncToGPU() +{ + if (_decals.SyncToGPU(_renderer)) + { + _classifyPassDescriptorSet.Bind("_packedDecals", _decals.GetBuffer()); + _materialPassDescriptorSet.Bind("_packedDecals", _decals.GetBuffer()); + } +} \ No newline at end of file diff --git a/Source/Game-Lib/Game-Lib/Rendering/Light/LightRenderer.h b/Source/Game-Lib/Game-Lib/Rendering/Light/LightRenderer.h new file mode 100644 index 0000000..3bee256 --- /dev/null +++ b/Source/Game-Lib/Game-Lib/Rendering/Light/LightRenderer.h @@ -0,0 +1,107 @@ +#pragma once +#include + +#include +#include +#include + +#include + +#include +#include + +namespace Renderer +{ + class Renderer; + class RenderGraph; + +} + +class DebugRenderer; +class ModelRenderer; +struct RenderResources; + +class LightRenderer +{ + static constexpr u32 MAX_ENTITIES_PER_TILE = 8; + static constexpr u32 TILE_SIZE = 16; // in pixels, per axis (16 = 16x16 = 256 pixels per tile) + +public: + LightRenderer(Renderer::Renderer* renderer, DebugRenderer* debugRenderer, ModelRenderer* modelRenderer); + ~LightRenderer(); + + void Update(f32 deltaTime); + void Clear(); + + void AddClassificationPass(Renderer::RenderGraph* renderGraph, RenderResources& resources, u8 frameIndex); + void AddDebugPass(Renderer::RenderGraph* renderGraph, RenderResources& resources, u8 frameIndex); + + // Requires Transform, AABB and Decal components on entity + void AddDecal(entt::entity entity); + void RemoveDecal(entt::entity entity); + + inline u32 CalculateNumTiles(const vec2& size); + inline uvec2 CalculateNumTiles2D(const vec2& size); + + Renderer::DescriptorSet& GetTileDescriptorSet() { return _materialPassDescriptorSet; } + void RegisterMaterialPassBufferUsage(Renderer::RenderGraphBuilder& builder); + +private: + void CreatePermanentResources(); + + void RecreateBuffer(const vec2& size); + + void SyncToGPU(); + +public: + enum DecalFlags + { + DECAL_FLAG_TWOSIDED = 1 << 0, + }; + + struct DecalAddRequest + { + entt::entity entity; + }; + + struct DecalRemoveRequest + { + entt::entity entity; + }; + +private: + struct GPUDecal + { + vec4 positionAndTextureID; // xyz = position, w = texture index + quat rotation; + vec4 extentsAndColor; // xyz = extents, asuint(w) = uint color multiplier + hvec2 thresholdMinMax = hvec2(0.0f, 1.0f); + hvec2 minUV = hvec2(0.0f, 0.0f); + hvec2 maxUV = hvec2(1.0f, 1.0f); + u32 flags; + }; + +private: + Renderer::Renderer* _renderer; + + Renderer::DescriptorSet _classifyPassDescriptorSet; + Renderer::DescriptorSet _debugPassDescriptorSet; + Renderer::DescriptorSet _materialPassDescriptorSet; + + Renderer::GPUVector _decals; + robin_hood::unordered_map _decalIDToEntity; + robin_hood::unordered_map _entityToDecalID; + + Renderer::BufferID _entityTilesBuffer; + + Renderer::GraphicsPipelineID _debugPipeline; + + moodycamel::ConcurrentQueue _decalAddRequests; + std::vector _decalAddWork; + + moodycamel::ConcurrentQueue _decalRemoveRequests; + std::vector _decalRemoveWork; + + DebugRenderer* _debugRenderer = nullptr; + ModelRenderer* _modelRenderer = nullptr; +}; diff --git a/Source/Game-Lib/Game-Lib/Rendering/Material/MaterialRenderer.cpp b/Source/Game-Lib/Game-Lib/Rendering/Material/MaterialRenderer.cpp index 490a7f4..cf17529 100644 --- a/Source/Game-Lib/Game-Lib/Rendering/Material/MaterialRenderer.cpp +++ b/Source/Game-Lib/Game-Lib/Rendering/Material/MaterialRenderer.cpp @@ -4,6 +4,7 @@ #include "Game-Lib/Editor/EditorHandler.h" #include "Game-Lib/Editor/TerrainTools.h" #include "Game-Lib/Rendering/Debug/DebugRenderer.h" +#include "Game-Lib/Rendering/Light/LightRenderer.h" #include "Game-Lib/Rendering/Model/ModelRenderer.h" #include "Game-Lib/Rendering/RenderResources.h" #include "Game-Lib/Rendering/Terrain/TerrainRenderer.h" @@ -24,10 +25,11 @@ AutoCVar_VecFloat CVAR_FogColor(CVarCategory::Client | CVarCategory::Rendering, AutoCVar_Float CVAR_FogBeginDist(CVarCategory::Client | CVarCategory::Rendering, "fogBlendBegin", "Fog blending start distance", 200.0f, CVarFlags::EditFloatDrag); AutoCVar_Float CVAR_FogEndDist(CVarCategory::Client | CVarCategory::Rendering, "fogBlendEnd", "Fog blending end distance", 600.0f, CVarFlags::EditFloatDrag); -MaterialRenderer::MaterialRenderer(Renderer::Renderer* renderer, TerrainRenderer* terrainRenderer, ModelRenderer* modelRenderer) +MaterialRenderer::MaterialRenderer(Renderer::Renderer* renderer, TerrainRenderer* terrainRenderer, ModelRenderer* modelRenderer, LightRenderer* lightRenderer) : _renderer(renderer) , _terrainRenderer(terrainRenderer) , _modelRenderer(modelRenderer) + , _lightRenderer(lightRenderer) { CreatePermanentResources(); } @@ -132,7 +134,8 @@ void MaterialRenderer::AddMaterialPass(Renderer::RenderGraph* renderGraph, Rende Renderer::ImageResource ambientOcclusion; Renderer::DescriptorSetResource globalSet; - Renderer::DescriptorSetResource shadowSet; + Renderer::DescriptorSetResource tilesSet; + Renderer::DescriptorSetResource lightSet; Renderer::DescriptorSetResource materialSet; Renderer::DescriptorSetResource terrainSet; Renderer::DescriptorSetResource modelSet; @@ -155,17 +158,20 @@ void MaterialRenderer::AddMaterialPass(Renderer::RenderGraph* renderGraph, Rende builder.Read(resources.cameras.GetBuffer(), Renderer::BufferPassUsage::COMPUTE); builder.Read(_directionalLights.GetBuffer(), Renderer::BufferPassUsage::COMPUTE); + Renderer::DescriptorSet& tileDescriptorSet = _lightRenderer->GetTileDescriptorSet(); Renderer::DescriptorSet& terrainDescriptorSet = _terrainRenderer->GetMaterialPassDescriptorSet(); Renderer::DescriptorSet& modelDescriptorSet = _modelRenderer->GetMaterialPassDescriptorSet(); data.globalSet = builder.Use(resources.globalDescriptorSet); - data.shadowSet = builder.Use(resources.shadowDescriptorSet); + data.tilesSet = builder.Use(tileDescriptorSet); + data.lightSet = builder.Use(resources.lightDescriptorSet); data.materialSet = builder.Use(_materialPassDescriptorSet); data.terrainSet = builder.Use(terrainDescriptorSet); data.modelSet = builder.Use(modelDescriptorSet); _terrainRenderer->RegisterMaterialPassBufferUsage(builder); _modelRenderer->RegisterMaterialPassBufferUsage(builder); + _lightRenderer->RegisterMaterialPassBufferUsage(builder); return true; // Return true from setup to enable this pass, return false to disable it }, @@ -201,7 +207,8 @@ void MaterialRenderer::AddMaterialPass(Renderer::RenderGraph* renderGraph, Rende // Bind descriptorset commandList.BindDescriptorSet(Renderer::DescriptorSetSlot::GLOBAL, data.globalSet, frameIndex); - commandList.BindDescriptorSet(Renderer::DescriptorSetSlot::SHADOWS, data.shadowSet, frameIndex); + commandList.BindDescriptorSet(Renderer::DescriptorSetSlot::TILES, data.tilesSet, frameIndex); + commandList.BindDescriptorSet(Renderer::DescriptorSetSlot::LIGHT, data.lightSet, frameIndex); commandList.BindDescriptorSet(Renderer::DescriptorSetSlot::TERRAIN, data.terrainSet, frameIndex); commandList.BindDescriptorSet(Renderer::DescriptorSetSlot::MODEL, data.modelSet, frameIndex); commandList.BindDescriptorSet(Renderer::DescriptorSetSlot::PER_PASS, data.materialSet, frameIndex); @@ -217,6 +224,7 @@ void MaterialRenderer::AddMaterialPass(Renderer::RenderGraph* renderGraph, Rende { vec4 renderInfo; // x = Render Width, y = Render Height, z = 1/Width, w = 1/Height uvec4 lightInfo; // x = Directional Light Count, Y = Point Light Count, Z = Cascade Count, W = Shadows Enabled + uvec4 tileInfo; // xy = Num Tiles, zw = UNUSED vec4 fogColor; vec4 fogSettings; // x = Enabled, y = Begin Fog Blend Dist, z = End Fog Blend Dist, w = UNUSED vec4 mouseWorldPos; @@ -238,6 +246,8 @@ void MaterialRenderer::AddMaterialPass(Renderer::RenderGraph* renderGraph, Rende const u32 shadowEnabled = static_cast(*cvarSystem->GetIntCVar(CVarCategory::Client | CVarCategory::Rendering, "shadowEnabled")); constants->lightInfo = uvec4(static_cast(_directionalLights.Count()), 0, numCascades, shadowEnabled); + constants->tileInfo = uvec4(_lightRenderer->CalculateNumTiles2D(outputSize), 0, 0); + constants->fogColor = CVAR_FogColor.Get(); constants->fogSettings.x = CVAR_EnableFog.Get() == ShowFlag::ENABLED; constants->fogSettings.y = CVAR_FogBeginDist.GetFloat(); diff --git a/Source/Game-Lib/Game-Lib/Rendering/Material/MaterialRenderer.h b/Source/Game-Lib/Game-Lib/Rendering/Material/MaterialRenderer.h index 7c87583..20d5eb6 100644 --- a/Source/Game-Lib/Game-Lib/Rendering/Material/MaterialRenderer.h +++ b/Source/Game-Lib/Game-Lib/Rendering/Material/MaterialRenderer.h @@ -14,12 +14,13 @@ namespace Renderer class TerrainRenderer; class ModelRenderer; +class LightRenderer; struct RenderResources; class MaterialRenderer { public: - MaterialRenderer(Renderer::Renderer* renderer, TerrainRenderer* terrainRenderer, ModelRenderer* modelRenderer); + MaterialRenderer(Renderer::Renderer* renderer, TerrainRenderer* terrainRenderer, ModelRenderer* modelRenderer, LightRenderer* lightRenderer); ~MaterialRenderer(); void Update(f32 deltaTime); @@ -58,4 +59,5 @@ class MaterialRenderer TerrainRenderer* _terrainRenderer = nullptr; ModelRenderer* _modelRenderer = nullptr; + LightRenderer* _lightRenderer = nullptr; }; diff --git a/Source/Game-Lib/Game-Lib/Rendering/Model/ModelLoader.cpp b/Source/Game-Lib/Game-Lib/Rendering/Model/ModelLoader.cpp index 311d71f..bdf022d 100644 --- a/Source/Game-Lib/Game-Lib/Rendering/Model/ModelLoader.cpp +++ b/Source/Game-Lib/Game-Lib/Rendering/Model/ModelLoader.cpp @@ -16,6 +16,7 @@ #include "Game-Lib/Gameplay/MapLoader.h" #include "Game-Lib/Rendering/GameRenderer.h" #include "Game-Lib/Rendering/Debug/DebugRenderer.h" +#include "Game-Lib/Rendering/Light/LightRenderer.h" #include "Game-Lib/Rendering/Terrain/TerrainLoader.h" #include "Game-Lib/Util/JoltStream.h" #include "Game-Lib/Util/ServiceLocator.h" @@ -47,9 +48,10 @@ namespace fs = std::filesystem; static const fs::path dataPath = fs::path("Data/"); static const fs::path complexModelPath = dataPath / "ComplexModel"; -ModelLoader::ModelLoader(ModelRenderer* modelRenderer) +ModelLoader::ModelLoader(ModelRenderer* modelRenderer, LightRenderer* lightRenderer) : _terrainLoader(nullptr) , _modelRenderer(modelRenderer) + , _lightRenderer(lightRenderer) , _pendingLoadRequests(MAX_PENDING_LOADS_PER_FRAME) , _internalLoadRequests(MAX_INTERNAL_LOADS_PER_FRAME) , _discoveredModels() @@ -202,6 +204,7 @@ void ModelLoader::Clear() _numTerrainModelsToLoad = 0; _numTerrainModelsLoaded = 0; _modelRenderer->Clear(); + _lightRenderer->Clear(); auto& tSystem = ECS::TransformSystem::Get(*registry); tSystem.ProcessMovedEntities([](entt::entity entity) { }); diff --git a/Source/Game-Lib/Game-Lib/Rendering/Model/ModelLoader.h b/Source/Game-Lib/Game-Lib/Rendering/Model/ModelLoader.h index 35b3e7a..b9c19e3 100644 --- a/Source/Game-Lib/Game-Lib/Rendering/Model/ModelLoader.h +++ b/Source/Game-Lib/Game-Lib/Rendering/Model/ModelLoader.h @@ -27,6 +27,7 @@ namespace ECS::Components } class TerrainLoader; +class LightRenderer; class ModelRenderer; class ModelLoader { @@ -102,7 +103,7 @@ class ModelLoader }; public: - ModelLoader(ModelRenderer* modelRenderer); + ModelLoader(ModelRenderer* modelRenderer, LightRenderer* lightRenderer); void Init(); void Clear(); @@ -171,6 +172,7 @@ class ModelLoader private: TerrainLoader* _terrainLoader = nullptr; ModelRenderer* _modelRenderer = nullptr; + LightRenderer* _lightRenderer = nullptr; std::atomic _numDiscoveredModelsToLoad = 0; u32 _numDiscoveredModelsLoaded = 0; diff --git a/Source/Game-Lib/Game-Lib/Rendering/Model/ModelRenderer.cpp b/Source/Game-Lib/Game-Lib/Rendering/Model/ModelRenderer.cpp index 03c449b..10fe56a 100644 --- a/Source/Game-Lib/Game-Lib/Rendering/Model/ModelRenderer.cpp +++ b/Source/Game-Lib/Game-Lib/Rendering/Model/ModelRenderer.cpp @@ -1186,6 +1186,18 @@ void ModelRenderer::Reserve(const ReserveInfo& reserveInfo) _transparentSkyboxCullingResources.Reserve(reserveInfo.numTransparentDrawcalls); } +Renderer::TextureID ModelRenderer::LoadTexture(const std::string& path, u32& arrayIndex) +{ + ZoneScopedN("ModelRenderer::LoadTexture"); + + Renderer::TextureDesc textureDesc = + { + .path = path, + }; + + return _renderer->LoadTextureIntoArray(textureDesc, _textures, arrayIndex); +} + u32 ModelRenderer::LoadModel(const std::string& name, Model::ComplexModel& model) { ZoneScopedN("ModelRenderer::LoadModel"); diff --git a/Source/Game-Lib/Game-Lib/Rendering/Model/ModelRenderer.h b/Source/Game-Lib/Game-Lib/Rendering/Model/ModelRenderer.h index e8d5454..4ca2820 100644 --- a/Source/Game-Lib/Game-Lib/Rendering/Model/ModelRenderer.h +++ b/Source/Game-Lib/Game-Lib/Rendering/Model/ModelRenderer.h @@ -315,6 +315,8 @@ class ModelRenderer : CulledRenderer void Reserve(const ReserveInfo& reserveInfo); + Renderer::TextureID LoadTexture(const std::string& path, u32& arrayIndex); + u32 LoadModel(const std::string& name, Model::ComplexModel& model); u32 AddPlacementInstance(entt::entity entityID, u32 modelID, u32 modelHash, Model::ComplexModel* model, const vec3& position, const quat& rotation, f32 scale, u32 doodadSet, bool canUseDoodadSet); u32 AddInstance(entt::entity entityID, u32 modelID, Model::ComplexModel* model, const mat4x4& transformMatrix, u64 displayInfoPacked = std::numeric_limits().max()); diff --git a/Source/Game-Lib/Game-Lib/Rendering/RenderResources.h b/Source/Game-Lib/Game-Lib/Rendering/RenderResources.h index c6ea513..82e5bd0 100644 --- a/Source/Game-Lib/Game-Lib/Rendering/RenderResources.h +++ b/Source/Game-Lib/Game-Lib/Rendering/RenderResources.h @@ -22,6 +22,8 @@ struct RenderResources Renderer::ImageID sceneColor; Renderer::ImageID finalColor; + Renderer::ImageID debugColor; + Renderer::ImageID ssaoTarget; Renderer::ImageID transparency; @@ -37,7 +39,7 @@ struct RenderResources Renderer::DepthImageID debugRendererDepth; Renderer::DescriptorSet globalDescriptorSet; - Renderer::DescriptorSet shadowDescriptorSet; + Renderer::DescriptorSet lightDescriptorSet; Renderer::SemaphoreID sceneRenderedSemaphore; // This semaphore tells the present function when the scene is ready to be blitted and presented FrameResource frameSyncSemaphores; // This semaphore makes sure the GPU handles frames in order diff --git a/Source/Game-Lib/Game-Lib/Rendering/RenderUtils.h b/Source/Game-Lib/Game-Lib/Rendering/RenderUtils.h index 22be6fa..4c701df 100644 --- a/Source/Game-Lib/Game-Lib/Rendering/RenderUtils.h +++ b/Source/Game-Lib/Game-Lib/Rendering/RenderUtils.h @@ -22,9 +22,9 @@ class RenderUtils public: Renderer::ImageResource input; u32 inputMipLevel = 0; - vec4 colorMultiplier; - vec4 additiveColor; - ivec4 channelRedirectors; + vec4 colorMultiplier = vec4(1, 1, 1, 1); + vec4 additiveColor = vec4(0, 0, 0, 0); + ivec4 channelRedirectors = ivec4(0, 1, 2, 3); Renderer::ImageMutableResource output; @@ -36,9 +36,9 @@ class RenderUtils { public: Renderer::DepthImageResource input; - vec4 colorMultiplier; - vec4 additiveColor; - ivec4 channelRedirectors; + vec4 colorMultiplier = vec4(1, 1, 1, 1); + vec4 additiveColor = vec4(0, 0, 0, 0); + ivec4 channelRedirectors = ivec4(0, 1, 2, 3); Renderer::ImageMutableResource output; @@ -51,9 +51,9 @@ class RenderUtils public: Renderer::ImageResource overlayImage; u32 mipLevel = 0; - vec4 colorMultiplier; - vec4 additiveColor; - ivec4 channelRedirectors; + vec4 colorMultiplier = vec4(1,1,1,1); + vec4 additiveColor = vec4(0,0,0,0); + ivec4 channelRedirectors = ivec4(0, 1, 2, 3); Renderer::ImageMutableResource baseImage; @@ -65,9 +65,9 @@ class RenderUtils { public: Renderer::DepthImageResource overlayImage; - vec4 colorMultiplier; - vec4 additiveColor; - ivec4 channelRedirectors; + vec4 colorMultiplier = vec4(1, 1, 1, 1); + vec4 additiveColor = vec4(0, 0, 0, 0); + ivec4 channelRedirectors = ivec4(0, 1, 2, 3); Renderer::ImageMutableResource baseImage; @@ -80,9 +80,9 @@ class RenderUtils public: Renderer::ImageResource pipImage; u32 mipLevel = 0; - vec4 colorMultiplier; - vec4 additiveColor; - ivec4 channelRedirectors; + vec4 colorMultiplier = vec4(1, 1, 1, 1); + vec4 additiveColor = vec4(0, 0, 0, 0); + ivec4 channelRedirectors = ivec4(0, 1, 2, 3); Geometry::Box targetRegion; Renderer::ImageMutableResource baseImage; @@ -95,9 +95,9 @@ class RenderUtils { public: Renderer::DepthImageResource pipImage; - vec4 colorMultiplier; - vec4 additiveColor; - ivec4 channelRedirectors; + vec4 colorMultiplier = vec4(1, 1, 1, 1); + vec4 additiveColor = vec4(0, 0, 0, 0); + ivec4 channelRedirectors = ivec4(0, 1, 2, 3); Geometry::Box targetRegion; Renderer::ImageMutableResource baseImage; diff --git a/Source/Game-Lib/Game-Lib/Rendering/Shadow/ShadowRenderer.cpp b/Source/Game-Lib/Game-Lib/Rendering/Shadow/ShadowRenderer.cpp index d50b0ba..20e7eba 100644 --- a/Source/Game-Lib/Game-Lib/Rendering/Shadow/ShadowRenderer.cpp +++ b/Source/Game-Lib/Game-Lib/Rendering/Shadow/ShadowRenderer.cpp @@ -112,7 +112,7 @@ void ShadowRenderer::AddShadowPass(Renderer::RenderGraph* renderGraph, RenderRes data.shadowDepthCascades[i] = builder.Write(resources.shadowDepthCascades[i], Renderer::PipelineType::GRAPHICS, Renderer::LoadMode::CLEAR); } - data.shadowDescriptorSet = builder.Use(resources.shadowDescriptorSet); + data.shadowDescriptorSet = builder.Use(resources.lightDescriptorSet); return true; // Return true from setup to enable this pass, return false to disable it }, @@ -144,7 +144,7 @@ void ShadowRenderer::CreatePermanentResources(RenderResources& resources) samplerDesc.comparisonFunc = Renderer::ComparisonFunc::GREATER; _shadowCmpSampler = _renderer->CreateSampler(samplerDesc); - resources.shadowDescriptorSet.Bind("_shadowCmpSampler"_h, _shadowCmpSampler); + resources.lightDescriptorSet.Bind("_shadowCmpSampler"_h, _shadowCmpSampler); samplerDesc.filter = Renderer::SamplerFilter::MIN_MAG_MIP_POINT; samplerDesc.addressU = Renderer::TextureAddressMode::CLAMP; @@ -152,5 +152,5 @@ void ShadowRenderer::CreatePermanentResources(RenderResources& resources) samplerDesc.comparisonEnabled = false; _shadowPointClampSampler = _renderer->CreateSampler(samplerDesc); - resources.shadowDescriptorSet.Bind("_shadowPointClampSampler"_h, _shadowPointClampSampler); + resources.lightDescriptorSet.Bind("_shadowPointClampSampler"_h, _shadowPointClampSampler); } \ No newline at end of file diff --git a/Source/Shaders/Shaders/Include/Culling.inc.hlsl b/Source/Shaders/Shaders/Include/Culling.inc.hlsl index a9e785b..c54a137 100644 --- a/Source/Shaders/Shaders/Include/Culling.inc.hlsl +++ b/Source/Shaders/Shaders/Include/Culling.inc.hlsl @@ -1,7 +1,9 @@ #ifndef CULLING_INCLUDED #define CULLING_INCLUDED +#include "globalData.inc.hlsl" +#include "common.inc.hlsl" -struct AABB +struct AABBMinMax { float3 min; float3 max; @@ -10,4 +12,283 @@ struct AABB #define MAX_SHADOW_CASCADES 8 #define NUM_CULL_VIEWS 1 + MAX_SHADOW_CASCADES // Main view plus max number of cascades +// Based on the work of WickedEngine, thank you! https://github.com/turanszkij/WickedEngine/ +struct Plane +{ + float3 N; // Plane normal. + float d; // Distance to origin. +}; + +inline float PlaneEval(Plane p, float3 x) { return dot(p.N, x) - p.d; } + +// Flip plane so that `insidePoint` evaluates positive (inside = positive half-space) +inline Plane FaceInward(Plane p, float3 insidePoint) +{ + if (PlaneEval(p, insidePoint) < 0) { p.N *= -1.0f; p.d *= -1.0f; } + return p; +} + +// Compute a plane from 3 noncollinear points that form a triangle. +// This equation assumes a right-handed (counter-clockwise winding order) +// coordinate system to determine the direction of the plane normal. +Plane ComputePlane(float3 p0, float3 p1, float3 p2) +{ + Plane plane; + + float3 v0 = p1 - p0; + float3 v2 = p2 - p0; + + plane.N = normalize(cross(v0, v2)); + + // Compute the distance to the origin using p0. + plane.d = dot(plane.N, p0); + + return plane; +} + +// Force tile-frustum planes to face inward (+Z forward in view space). +inline Plane ForceInward(Plane p) +{ + // Flip if normal points opposite to +Z (we want camera at z=0, looking into +Z). + if (p.N.z < 0.0f) { p.N *= -1.0f; p.d *= -1.0f; } + return p; +} + +// Four planes of a view frustum (in view space). +// The planes are: +// * Left, +// * Right, +// * Top, +// * Bottom. +// The back and/or front planes can be computed from depth values in the +// light culling compute shader. +struct Frustum +{ + Plane planes[4]; // left, right, top, bottom frustum planes. +}; + +// Convert clip space coordinates to view space +float4 ClipToView(float4 clip) +{ + // View space position. + float4 view = mul(clip, _cameras[0].clipToView); + // Perspective projection. + view = view / view.w; + + return view; +} + +// Convert screen space coordinates to view space. +float4 ScreenToView(float4 screen, float2 dimRcp) +{ + // Convert to normalized texture coordinates + float2 texCoord = screen.xy * dimRcp; + + // Convert to clip space + float4 clip = float4(float2(texCoord.x, 1.0f - texCoord.y) * 2.0f - 1.0f, screen.z, screen.w); + + return ClipToView(clip); +} + +struct Sphere +{ + float3 c; // Center point. + float r; // Radius. +}; + +// Check to see if a sphere is fully behind (inside the negative halfspace of) a plane. +// Source: Real-time collision detection, Christer Ericson (2005) +bool SphereInsidePlane(Sphere sphere, Plane plane) +{ + return dot(plane.N, sphere.c) - plane.d < -sphere.r; +} + +// Check to see of a light is partially contained within the frustum. +bool SphereInsideFrustum(Sphere sphere, Frustum frustum, float zNear, float zFar) // this can only be used in view space +{ + bool result = true; + + //if (sphere.c.z + sphere.r < zNear || sphere.c.z - sphere.r > zFar) + //{ + // result = false; + //} + + //// Then check frustum planes + //for (int i = 0; i < 4 && result; i++) + //{ + // if (SphereInsidePlane(sphere, frustum.planes[i])) + // { + // result = false; + // } + //} + + // Better to just unroll: + result = ((sphere.c.z + sphere.r < zNear || sphere.c.z - sphere.r > zFar) ? false : result); + result = ((SphereInsidePlane(sphere, frustum.planes[0])) ? false : result); + result = ((SphereInsidePlane(sphere, frustum.planes[1])) ? false : result); + result = ((SphereInsidePlane(sphere, frustum.planes[2])) ? false : result); + result = ((SphereInsidePlane(sphere, frustum.planes[3])) ? false : result); + + return result; +} + +bool SphereInsideFrustum(Sphere sphere, Plane planes[6]) // this can be used in world space +{ + bool result = true; + + //for (int i = 0; i < 6 && result; i++) + //{ + // if (SphereInsidePlane(sphere, planes[i])) + // { + // result = false; + // } + //} + + result = (SphereInsidePlane(sphere, planes[0]) ? false : result); + result = (SphereInsidePlane(sphere, planes[1]) ? false : result); + result = (SphereInsidePlane(sphere, planes[2]) ? false : result); + result = (SphereInsidePlane(sphere, planes[3]) ? false : result); + result = (SphereInsidePlane(sphere, planes[4]) ? false : result); + result = (SphereInsidePlane(sphere, planes[5]) ? false : result); + + return result; +} + +// Check to see if a point is fully behind (inside the negative halfspace of) a plane. +bool PointInsidePlane(float3 p, Plane plane) +{ + return dot(plane.N, p) - plane.d < 0; +} + +struct Cone +{ + float3 T; // Cone tip. + float h; // Height of the cone. + float3 d; // Direction of the cone. + float r; // bottom radius of the cone. +}; + +// Check to see if a cone if fully behind (inside the negative halfspace of) a plane. +// Source: Real-time collision detection, Christer Ericson (2005) +bool ConeInsidePlane(Cone cone, Plane plane) +{ + // Compute the farthest point on the end of the cone to the positive space of the plane. + float3 m = cross(cross(plane.N, cone.d), cone.d); + float3 Q = cone.T + cone.d * cone.h - m * cone.r; + + // The cone is in the negative halfspace of the plane if both + // the tip of the cone and the farthest point on the end of the cone to the + // positive halfspace of the plane are both inside the negative halfspace + // of the plane. + return PointInsidePlane(cone.T, plane) && PointInsidePlane(Q, plane); +} + +bool ConeInsideFrustum(Cone cone, Frustum frustum, float zNear, float zFar) +{ + bool result = true; + + Plane nearPlane = { float3(0, 0, 1), zNear }; + Plane farPlane = { float3(0, 0, -1), -zFar }; + + // First check the near and far clipping planes. + if (ConeInsidePlane(cone, nearPlane) || ConeInsidePlane(cone, farPlane)) + { + result = false; + } + + // Then check frustum planes + for (int i = 0; i < 4 && result; i++) + { + if (ConeInsidePlane(cone, frustum.planes[i])) + { + result = false; + } + } + + return result; +} + +struct AABB +{ + float3 c; // center + float3 e; // half extents + + float3 getMin() { return c - e; } + float3 getMax() { return c + e; } +}; + +bool SphereIntersectsAABB(in Sphere sphere, in AABB aabb) +{ + float3 vDelta = max(0, abs(aabb.c - sphere.c) - aabb.e); + float fDistSq = dot(vDelta, vDelta); + return fDistSq <= sphere.r * sphere.r; +} + +bool IntersectAABB(AABB a, AABB b) +{ + if (abs(a.c[0] - b.c[0]) > (a.e[0] + b.e[0])) + return false; + if (abs(a.c[1] - b.c[1]) > (a.e[1] + b.e[1])) + return false; + if (abs(a.c[2] - b.c[2]) > (a.e[2] + b.e[2])) + return false; + + return true; +} + +void AABBfromMinMax(inout AABB aabb, float3 _min, float3 _max) +{ + aabb.c = (_min + _max) * 0.5f; + aabb.e = abs(_max - aabb.c); +} + +void AABBtransform(inout AABB aabb, float4x4 mat) +{ + float3 _min = aabb.getMin(); + float3 _max = aabb.getMax(); + float3 corners[8]; + corners[0] = _min; + corners[1] = float3(_min.x, _max.y, _min.z); + corners[2] = float3(_min.x, _max.y, _max.z); + corners[3] = float3(_min.x, _min.y, _max.z); + corners[4] = float3(_max.x, _min.y, _min.z); + corners[5] = float3(_max.x, _max.y, _min.z); + corners[6] = _max; + corners[7] = float3(_max.x, _min.y, _max.z); + _min = 1000000; + _max = -1000000; + + [unroll] + for (uint i = 0; i < 8; ++i) + { + corners[i] = mul(float4(corners[i], 1), mat).xyz; + _min = min(_min, corners[i]); + _max = max(_max, corners[i]); + } + + AABBfromMinMax(aabb, _min, _max); +} + +// AABB expected as center+extents: a.c (center), a.e (half-size) +// Fast box transform: e' = |R| * e, c' = R*c + t +inline void AABBTransformQuat(inout AABB a, float4 quat, float3 translation) +{ + float3x3 R = QuatToMat(quat); + + // center + a.c = mul(a.c, R) + translation; // row-vector convention + + // extents + float3 ex = abs(R[0]) * a.e.x; // R[0], R[1], R[2] are row vectors + float3 ey = abs(R[1]) * a.e.y; + float3 ez = abs(R[2]) * a.e.z; + a.e = ex + ey + ez; +} + +// Overload: pure rotation about origin +inline void AABBTransformQuat(inout AABB a, float4 quat) +{ + AABBTransformQuat(a, quat, 0); +} + #endif // CULLING_INCLUDED \ No newline at end of file diff --git a/Source/Shaders/Shaders/Include/Debug.inc.hlsl b/Source/Shaders/Shaders/Include/Debug.inc.hlsl index acfc59f..130cb73 100644 --- a/Source/Shaders/Shaders/Include/Debug.inc.hlsl +++ b/Source/Shaders/Shaders/Include/Debug.inc.hlsl @@ -162,7 +162,7 @@ void DrawPlane3D(float3 v0, float3 v1, float3 v2, float3 v3, uint color) DrawLine3D(context, v3, v0, color); } -void DrawAABB3D(inout DebugDrawContext3D context, AABB aabb, uint color) +void DrawAABB3D(inout DebugDrawContext3D context, AABBMinMax aabb, uint color) { // Bottom vertices float3 v0 = float3(aabb.min.x, aabb.min.y, aabb.min.z); @@ -195,12 +195,82 @@ void DrawAABB3D(inout DebugDrawContext3D context, AABB aabb, uint color) DrawLine3D(context, v3, v7, color); } -void DrawAABB3D(AABB aabb, uint color) +void DrawAABB3D(AABBMinMax aabb, uint color) { DebugDrawContext3D context = StartDraw3D(24); DrawAABB3D(context, aabb, color); } +void DrawOBB3D(inout DebugDrawContext3D context, float3 center, float3 extents, float4 quatXYZW, uint color) +{ + // Ensure unit quaternion + float4 q = normalize(quatXYZW); + + // Quaternion -> 3x3 rotation matrix (xyzw; w = scalar) + float xx = q.x * q.x, yy = q.y * q.y, zz = q.z * q.z; + float xy = q.x * q.y, xz = q.x * q.z, yz = q.y * q.z; + float wx = q.w * q.x, wy = q.w * q.y, wz = q.w * q.z; + + float3x3 R; + R[0][0] = 1.0f - 2.0f * (yy + zz); + R[0][1] = 2.0f * (xy + wz); + R[0][2] = 2.0f * (xz - wy); + + R[1][0] = 2.0f * (xy - wz); + R[1][1] = 1.0f - 2.0f * (xx + zz); + R[1][2] = 2.0f * (yz + wx); + + R[2][0] = 2.0f * (xz + wy); + R[2][1] = 2.0f * (yz - wx); + R[2][2] = 1.0f - 2.0f * (xx + yy); + + // Local corners in OBB space (using half-extents) + const float3 e = extents; + float3 c[8]; + c[0] = float3(-e.x, -e.y, -e.z); + c[1] = float3(-e.x, -e.y, +e.z); + c[2] = float3(+e.x, -e.y, +e.z); + c[3] = float3(+e.x, -e.y, -e.z); + c[4] = float3(-e.x, +e.y, -e.z); + c[5] = float3(-e.x, +e.y, +e.z); + c[6] = float3(+e.x, +e.y, +e.z); + c[7] = float3(+e.x, +e.y, -e.z); + + // Rotate + translate to world + float3 v[8]; + [unroll] for (uint i = 0; i < 8; ++i) + { + // Using row-vector convention: v_world = v_local * R + center + v[i] = mul(c[i], R) + center; + } + + // Edges (12 lines) + // Bottom face + DrawLine3D(context, v[0], v[1], color); + DrawLine3D(context, v[1], v[2], color); + DrawLine3D(context, v[2], v[3], color); + DrawLine3D(context, v[3], v[0], color); + + // Top face + DrawLine3D(context, v[4], v[5], color); + DrawLine3D(context, v[5], v[6], color); + DrawLine3D(context, v[6], v[7], color); + DrawLine3D(context, v[7], v[4], color); + + // Vertical edges + DrawLine3D(context, v[0], v[4], color); + DrawLine3D(context, v[1], v[5], color); + DrawLine3D(context, v[2], v[6], color); + DrawLine3D(context, v[3], v[7], color); +} + +// Convenience wrapper that allocates the vertex space (12 lines * 2 verts) +void DrawOBB3D(float3 center, float3 extents, float4 quatXYZW, uint color) +{ + DebugDrawContext3D ctx = StartDraw3D(24); + DrawOBB3D(ctx, center, extents, quatXYZW, color); +} + float3 Unproject(float3 p, float4x4 m) { float4 obj = mul(float4(p, 1.0f), m); diff --git a/Source/Shaders/Shaders/Include/Shadows.inc.hlsl b/Source/Shaders/Shaders/Include/Shadows.inc.hlsl index 5a9892f..a3295cb 100644 --- a/Source/Shaders/Shaders/Include/Shadows.inc.hlsl +++ b/Source/Shaders/Shaders/Include/Shadows.inc.hlsl @@ -5,9 +5,9 @@ #define MAX_SHADOW_CASCADES 8 // Has to be kept in sync with the one in RenderSettings.h -[[vk::binding(0, SHADOWS)]] SamplerComparisonState _shadowCmpSampler; -[[vk::binding(1, SHADOWS)]] SamplerState _shadowPointClampSampler; -[[vk::binding(2, SHADOWS)]] Texture2D _shadowCascadeRTs[MAX_SHADOW_CASCADES]; +[[vk::binding(0, LIGHT)]] SamplerComparisonState _shadowCmpSampler; +[[vk::binding(1, LIGHT)]] SamplerState _shadowPointClampSampler; +[[vk::binding(2, LIGHT)]] Texture2D _shadowCascadeRTs[MAX_SHADOW_CASCADES]; struct ShadowSettings { diff --git a/Source/Shaders/Shaders/Light/Classification.cs.hlsl b/Source/Shaders/Shaders/Light/Classification.cs.hlsl new file mode 100644 index 0000000..8eedb50 --- /dev/null +++ b/Source/Shaders/Shaders/Light/Classification.cs.hlsl @@ -0,0 +1,323 @@ +// Based on the work of WickedEngine, thank you! https://github.com/turanszkij/WickedEngine/ +// https://github.com/turanszkij/WickedEngine/blob/master/WickedEngine/shaders/lightCullingCS.hlsl + +#include "common.inc.hlsl" +#include "globalData.inc.hlsl" +#include "Include/Culling.inc.hlsl" +#include "Include/Debug.inc.hlsl" +#include "Light/LightShared.inc.hlsl" + +#define DEBUG_TILEDLIGHTCULLING +#define ADVANCED_CULLING + +struct Constants +{ + uint maxDecalsPerTile; + uint numTotalDecals; + uint2 tileCount; + float2 screenSize; // In pixels +}; + +[[vk::push_constant]] Constants _constants; + +[[vk::binding(0, PER_PASS)]] StructuredBuffer _packedDecals; // All decals in the world +[[vk::binding(1, PER_PASS)]] Texture2D _depthRT; + +[[vk::binding(2, PER_PASS)]] RWStructuredBuffer _entityTiles; + +// Group shared variables. +groupshared uint uMinDepth; +groupshared uint uMaxDepth; +groupshared uint uDepthMask; // Harada Siggraph 2012 2.5D culling +groupshared uint tile_opaque[SHADER_ENTITY_TILE_BUCKET_COUNT]; +groupshared uint tile_transparent[SHADER_ENTITY_TILE_BUCKET_COUNT]; +#ifdef DEBUG_TILEDLIGHTCULLING +groupshared uint entityCountDebug; +[[vk::binding(3, PER_PASS)]] RWTexture2D _debugTexture; +#endif // DEBUG_TILEDLIGHTCULLING + +void AppendEntity_Opaque(uint entityIndex) +{ + const uint bucket_index = entityIndex / 32; + const uint bucket_place = entityIndex % 32; + InterlockedOr(tile_opaque[bucket_index], 1u << bucket_place); + +#ifdef DEBUG_TILEDLIGHTCULLING + InterlockedAdd(entityCountDebug, 1); +#endif // DEBUG_TILEDLIGHTCULLING +} + +void AppendEntity_Transparent(uint entityIndex) +{ + const uint bucket_index = entityIndex / 32; + const uint bucket_place = entityIndex % 32; + InterlockedOr(tile_transparent[bucket_index], 1u << bucket_place); +} + +inline uint ConstructEntityMask(in float depthRangeMin, in float depthRangeRecip, in Sphere bounds) +{ + // We create a entity mask to decide if the entity is really touching something + // If we do an OR operation with the depth slices mask, we instantly get if the entity is contributing or not + // we do this in view space + + const float fMin = bounds.c.z - bounds.r; + const float fMax = bounds.c.z + bounds.r; + const uint __entitymaskcellindexSTART = clamp(floor((fMin - depthRangeMin) * depthRangeRecip), 0, 31); + const uint __entitymaskcellindexEND = clamp(floor((fMax - depthRangeMin) * depthRangeRecip), 0, 31); + + //// Unoptimized mask construction with loop: + //// Construct mask from START to END: + //// END START + //// 0000000000111111111110000000000 + //uint uLightMask = 0; + //for (uint c = __entitymaskcellindexSTART; c <= __entitymaskcellindexEND; ++c) + //{ + // uLightMask |= 1u << c; + //} + + // Optimized mask construction, without loop: + // - First, fill full mask: + // 1111111111111111111111111111111 + uint uLightMask = 0xFFFFFFFF; + // - Then Shift right with spare amount to keep mask only: + // 0000000000000000000011111111111 + uLightMask >>= 31u - (__entitymaskcellindexEND - __entitymaskcellindexSTART); + // - Last, shift left with START amount to correct mask position: + // 0000000000111111111110000000000 + uLightMask <<= __entitymaskcellindexSTART; + + return uLightMask; +} + +inline AABB WorldAABB_To_DecalSpace(AABB worldBox, float3 decalCenter, float4 decalQuat) { + float3 r, u, f; + BasisFromQuat(decalQuat, r, u, f); // 1) center: translate to decal origin, then project onto basis + + float3 cRel = worldBox.c - decalCenter; + AABB outB; + outB.c = float3(dot(cRel, r), dot(cRel, u), dot(cRel, f)); // 2) extents: e' = |M| * e, where M rows are (r,u,f) + + // i.e., abs(row) * ex + abs(row) * ey + abs(row) * ez + float3 row0 = abs(r); + float3 row1 = abs(u); + float3 row2 = abs(f); // multiply rows by source extents components and sum per row + outB.e = float3(dot(row0, worldBox.e), // X' + dot(row1, worldBox.e), // Y' + dot(row2, worldBox.e) // Z' + ); + return outB; +} + +[numthreads(TILED_CULLING_THREADSIZE, TILED_CULLING_THREADSIZE, 1)] +void main(uint3 Gid : SV_GroupID, uint3 DTid : SV_DispatchThreadID, uint3 GTid : SV_GroupThreadID, uint groupIndex : SV_GroupIndex) +{ + uint2 dim; + _depthRT.GetDimensions(dim.x, dim.y); + float2 dim_rcp = rcp(dim); + + // Each thread will zero out one bucket in the LDS: + for (uint i = groupIndex; i < SHADER_ENTITY_TILE_BUCKET_COUNT; i += TILED_CULLING_THREADSIZE * TILED_CULLING_THREADSIZE) + { + tile_opaque[i] = 0; + tile_transparent[i] = 0; + } + + // First thread zeroes out other LDS data: + if (groupIndex == 0) + { + uMinDepth = 0xffffffff; + uMaxDepth = 0; + uDepthMask = 0; + +#ifdef DEBUG_TILEDLIGHTCULLING + entityCountDebug = 0; +#endif // DEBUG_TILEDLIGHTCULLING + } + + // Calculate min & max depth in threadgroup / tile. + float depth[TILED_CULLING_GRANULARITY * TILED_CULLING_GRANULARITY]; + float depthMinUnrolled = 10000000; + float depthMaxUnrolled = -10000000; + + [unroll] + for (uint granularity = 0; granularity < TILED_CULLING_GRANULARITY * TILED_CULLING_GRANULARITY; ++granularity) + { + uint2 pixel = DTid.xy * uint2(TILED_CULLING_GRANULARITY, TILED_CULLING_GRANULARITY) + Unflatten2D(granularity, TILED_CULLING_GRANULARITY); + pixel = min(pixel, dim - 1); // avoid loading from outside the texture, it messes up the min-max depth! + depth[granularity] = _depthRT[pixel]; + depthMinUnrolled = min(depthMinUnrolled, depth[granularity]); + depthMaxUnrolled = max(depthMaxUnrolled, depth[granularity]); + } + + GroupMemoryBarrierWithGroupSync(); + + float wave_local_min = WaveActiveMin(depthMinUnrolled); + float wave_local_max = WaveActiveMax(depthMaxUnrolled); + if (WaveIsFirstLane()) + { + InterlockedMin(uMinDepth, asuint(wave_local_min)); + InterlockedMax(uMaxDepth, asuint(wave_local_max)); + } + + GroupMemoryBarrierWithGroupSync(); + + // reversed depth buffer! + float fMinDepth = asfloat(uMaxDepth); + float fMaxDepth = asfloat(uMinDepth); + + fMaxDepth = max(0.000001, fMaxDepth); // fix for AMD!!!!!!!!! + + Frustum GroupFrustum; + AABB GroupAABB; // frustum AABB around min-max depth in View Space + AABB GroupAABB_WS; // frustum AABB in world space + float3 viewSpace[8]; // View space frustum corners + { + // Top left point, near + viewSpace[0] = ScreenToView(float4(Gid.xy * TILED_CULLING_BLOCKSIZE, fMinDepth, 1.0f), dim_rcp).xyz; + // Top right point, near + viewSpace[1] = ScreenToView(float4(float2(Gid.x + 1, Gid.y) * TILED_CULLING_BLOCKSIZE, fMinDepth, 1.0f), dim_rcp).xyz; + // Bottom left point, near + viewSpace[2] = ScreenToView(float4(float2(Gid.x, Gid.y + 1) * TILED_CULLING_BLOCKSIZE, fMinDepth, 1.0f), dim_rcp).xyz; + // Bottom right point, near + viewSpace[3] = ScreenToView(float4(float2(Gid.x + 1, Gid.y + 1) * TILED_CULLING_BLOCKSIZE, fMinDepth, 1.0f), dim_rcp).xyz; + // Top left point, far + viewSpace[4] = ScreenToView(float4(Gid.xy * TILED_CULLING_BLOCKSIZE, fMaxDepth, 1.0f), dim_rcp).xyz; + // Top right point, far + viewSpace[5] = ScreenToView(float4(float2(Gid.x + 1, Gid.y) * TILED_CULLING_BLOCKSIZE, fMaxDepth, 1.0f), dim_rcp).xyz; + // Bottom left point, far + viewSpace[6] = ScreenToView(float4(float2(Gid.x, Gid.y + 1) * TILED_CULLING_BLOCKSIZE, fMaxDepth, 1.0f), dim_rcp).xyz; + // Bottom right point, far + viewSpace[7] = ScreenToView(float4(float2(Gid.x + 1, Gid.y + 1) * TILED_CULLING_BLOCKSIZE, fMaxDepth, 1.0f), dim_rcp).xyz; + + // Left plane + GroupFrustum.planes[0] = ComputePlane(viewSpace[2], viewSpace[0], viewSpace[4]); + // Right plane + GroupFrustum.planes[1] = ComputePlane(viewSpace[1], viewSpace[3], viewSpace[5]); + // Top plane + GroupFrustum.planes[2] = ComputePlane(viewSpace[0], viewSpace[1], viewSpace[4]); + // Bottom plane + GroupFrustum.planes[3] = ComputePlane(viewSpace[3], viewSpace[2], viewSpace[6]); + + // I construct an AABB around the minmax depth bounds to perform tighter culling: + // The frustum is asymmetric so we must consider all corners! + float3 minAABB = 10000000; + float3 maxAABB = -10000000; + [unroll] + for (uint i = 0; i < 8; ++i) + { + minAABB = min(minAABB, viewSpace[i]); + maxAABB = max(maxAABB, viewSpace[i]); + } + + AABBfromMinMax(GroupAABB, minAABB, maxAABB); + + // We can perform coarse AABB intersection tests with this: + GroupAABB_WS = GroupAABB; + AABBtransform(GroupAABB_WS, _cameras[0].viewToWorld);//AABBtransform(GroupAABB_WS, GetCamera().inverse_view); + } + + // Convert depth values to view space. + float minDepthVS = ScreenToView(float4(0, 0, fMinDepth, 1), dim_rcp).z; + float maxDepthVS = ScreenToView(float4(0, 0, fMaxDepth, 1), dim_rcp).z; + float nearClipVS = ScreenToView(float4(0, 0, 1, 1), dim_rcp).z; + +#ifdef ADVANCED_CULLING + // We divide the minmax depth bounds to 32 equal slices + // then we mark the occupied depth slices with atomic or from each thread + // we do all this in linear (view) space + const float __depthRangeRecip = 31.0f / (maxDepthVS - minDepthVS); + uint __depthmaskUnrolled = 0; + + [unroll] + for (uint granularity = 0; granularity < TILED_CULLING_GRANULARITY * TILED_CULLING_GRANULARITY; ++granularity) + { + float realDepthVS = ScreenToView(float4(0, 0, depth[granularity], 1), dim_rcp).z; + const uint __depthmaskcellindex = max(0, min(31, floor((realDepthVS - minDepthVS) * __depthRangeRecip))); + __depthmaskUnrolled |= 1u << __depthmaskcellindex; + } + + uint wave_depth_mask = WaveActiveBitOr(__depthmaskUnrolled); + if (WaveIsFirstLane()) + { + InterlockedOr(uDepthMask, wave_depth_mask); + } +#endif + + GroupMemoryBarrierWithGroupSync(); + + const uint depth_mask = uDepthMask; // take out from groupshared into register + + // Decals: + for (uint i = 0 + groupIndex; i < _constants.numTotalDecals; i += TILED_CULLING_THREADSIZE * TILED_CULLING_THREADSIZE) + { + PackedDecal packedDecal = _packedDecals[i]; + Decal decal = UnpackDecal(packedDecal); + + float3 positionVS = mul(float4(decal.position, 1), _cameras[0].worldToView).xyz; + const float radius = length(decal.extents.xyz); + Sphere sphere = { positionVS.xyz, radius }; + if (SphereInsideFrustum(sphere, GroupFrustum, nearClipVS, maxDepthVS)) + { + AppendEntity_Transparent(i); + + // unit AABB: + AABB a; + a.c = 0; + a.e = decal.extents.xyz; + + // frustum AABB in world space transformed into the space of the probe/decal OBB + AABB b = GroupAABB_WS; + b = WorldAABB_To_DecalSpace(b, decal.position, decal.rotationQuat); + + // TODO: More accurate culling? We see the decal getting classified into tiles that are outside the AABB, + // probably because we're doing this check in decal space and not viewspace, but this is good enough for now + + if (IntersectAABB(a, b)) + { +#ifdef ADVANCED_CULLING + if (depth_mask & ConstructEntityMask(minDepthVS, __depthRangeRecip, sphere)) +#endif + { + AppendEntity_Opaque(i); + } + } + } + } + + GroupMemoryBarrierWithGroupSync(); + + const uint flatTileIndex = Flatten2D(Gid.xy, _constants.tileCount.xy); + const uint tileBucketsAddress = flatTileIndex * SHADER_ENTITY_TILE_BUCKET_COUNT; + + const uint entityCullingTileBucketCountFlat = _constants.tileCount.x * _constants.tileCount.y * SHADER_ENTITY_TILE_BUCKET_COUNT; + + // Each thread will export one bucket from LDS to global memory: + for (uint i = groupIndex; i < SHADER_ENTITY_TILE_BUCKET_COUNT; i += TILED_CULLING_THREADSIZE * TILED_CULLING_THREADSIZE) + { + _entityTiles[tileBucketsAddress + i] = tile_opaque[i]; + _entityTiles[entityCullingTileBucketCountFlat + tileBucketsAddress + i] = tile_transparent[i]; + } + +#ifdef DEBUG_TILEDLIGHTCULLING + for (uint granularity = 0; granularity < TILED_CULLING_GRANULARITY * TILED_CULLING_GRANULARITY; ++granularity) + { + uint2 pixel = DTid.xy * uint2(TILED_CULLING_GRANULARITY, TILED_CULLING_GRANULARITY) + Unflatten2D(granularity, TILED_CULLING_GRANULARITY); + + const float3 mapTex[] = { + float3(0,0,0), + float3(0,0,1), + float3(0,1,1), + float3(0,1,0), + float3(1,1,0), + float3(1,0,0), + }; + const uint mapTexLen = 5; + const uint maxHeat = _constants.maxDecalsPerTile; + float l = saturate((float)entityCountDebug / maxHeat) * mapTexLen; + float3 a = mapTex[floor(l)]; + float3 b = mapTex[ceil(l)]; + float4 heatmap = float4(lerp(a, b, l - floor(l)), 0.8); + _debugTexture[pixel] = heatmap; + } +#endif // DEBUG_TILEDLIGHTCULLING +} \ No newline at end of file diff --git a/Source/Shaders/Shaders/Light/LightShared.inc.hlsl b/Source/Shaders/Shaders/Light/LightShared.inc.hlsl new file mode 100644 index 0000000..1063eb0 --- /dev/null +++ b/Source/Shaders/Shaders/Light/LightShared.inc.hlsl @@ -0,0 +1,130 @@ +#ifndef LIGHT_SHARED_INCLUDED +#define LIGHT_SHARED_INCLUDED +#include "common.inc.hlsl" + +static const uint TILED_CULLING_BLOCKSIZE = 16; +static const uint TILED_CULLING_THREADSIZE = 8; +static const uint TILED_CULLING_GRANULARITY = TILED_CULLING_BLOCKSIZE / TILED_CULLING_THREADSIZE; +static const uint SHADER_ENTITY_TILE_BUCKET_COUNT = 1; // Arbitrary limit, should be enough for most cases, just make sure this is equal or higher than maxDecalsPerTile + +struct PackedDecal +{ + float4 positionAndTextureID; // xyz = position, w = texture index + float4 rotationQuat; + float4 extentsAndColor; // xyz = extents, asuint(w) = uint color multiplier + float4 thresholds; // x = f16 min/max threshold, y = f16 min UV, z = f16 max UV, asuint(w) = flags +}; + +enum DecalFlags +{ + DECAL_FLAG_TWOSIDED = 1 << 0, +}; + +struct Decal +{ + float3 position; + float4 rotationQuat; + float3 extents; + float3 color; + float minThreshold; + float maxThreshold; + float2 minUV; + float2 maxUV; + uint textureID; + uint flags; + + bool IsTwoSided() { return (flags & DECAL_FLAG_TWOSIDED) != 0; } +}; + +float3 ApplyDecal(float3 pixelWS, float3 normalWS, Decal d, Texture2D decalTexture, SamplerState sampler) +{ + const float3 halfExtents = d.extents.xyz; + const float3 center = d.position.xyz; + const uint texId = d.textureID; + + // world -> local (inverse rotation via conjugate) + const float4 rotQuatInv = float4(-d.rotationQuat.xyz, d.rotationQuat.w); + const float3x3 rotMatInv = QuatToMat(rotQuatInv); + const float3 pixelLocal = mul(pixelWS - center, rotMatInv); // local coords + + // Select planar axes and signed depth along projector axis + float2 planar = pixelLocal.xy; + float2 extents2D = halfExtents.xy; + float depthS = pixelLocal.z; + float extentsDepth = halfExtents.z; + + // Full OBB thickness and footprint + if (depthS < -extentsDepth || depthS > extentsDepth) + return 0; + + if (any(abs(planar) > extents2D)) + return 0; + + if (!d.IsTwoSided()) + { + // Facing gate (reject only the underside) + const float3 localFwd = float3(0, 0, -1); + const float3x3 rotationMat = QuatToMat(d.rotationQuat); + const float3 decalProjDirWS = normalize(mul(normalize(localFwd), rotationMat)); + normalWS = normalize(normalWS); + + // If the surface normal points roughly the SAME way as the projector forward, it's the underside -> reject. + const float minSameDirThreshold = d.minThreshold; + const float maxSameDirThreshold = d.maxThreshold; + + float pixelDotDecalProjDir = dot(normalWS, decalProjDirWS); + if (pixelDotDecalProjDir < minSameDirThreshold) + return 0; + if (pixelDotDecalProjDir > maxSameDirThreshold) + return 0; + } + + // Map planar [-extents2D, +extents2D] -> [0,1] + const float2 uv01 = planar * (0.5f / extents2D) + 0.5f; + + // Map [0,1] -> [minUV,maxUV] + const float2 uvMin = d.minUV; + const float2 uvMax = d.maxUV; + const float2 uvSpan = uvMax - uvMin; + + // Gradients for compute shader sampling + float2 ddx01, ddy01; + ComputeGradients_Quad(uv01, ddx01, ddy01); + + // Scale gradients to keep mip selection correct + const float2 uv = uvMin + uv01 * uvSpan; + const float2 ddx = ddx01 * uvSpan; + const float2 ddy = ddy01 * uvSpan; + + float4 c = decalTexture.SampleGrad(sampler, uv, ddx, ddy); + c.rgb *= d.color; + + return c.rgb * c.a; +} + +Decal UnpackDecal(PackedDecal pd) +{ + Decal d; + d.position = pd.positionAndTextureID.xyz; + d.textureID = asuint(pd.positionAndTextureID.w); + + d.rotationQuat = pd.rotationQuat; + d.extents = pd.extentsAndColor.xyz; + + uint colorUint = asuint(pd.extentsAndColor.w); // ABGR + d.color = float3(((colorUint >> 24) & 0xFF) / 255.0f, ((colorUint >> 16) & 0xFF) / 255.0f, ((colorUint >> 8) & 0xFF) / 255.0f); + + // Unpack minThreshold and maxThreshold from 2xf16 packed into a f32 + d.minThreshold = f16tof32(asuint(pd.thresholds.x) & 0xFFFF); + d.maxThreshold = f16tof32((asuint(pd.thresholds.x) >> 16) & 0xFFFF); + + // Unpack minUV and maxUV from 4xf16 packed into 2xf32 + d.minUV = float2(f16tof32(asuint(pd.thresholds.y) & 0xFFFF), f16tof32((asuint(pd.thresholds.y) >> 16) & 0xFFFF)); + d.maxUV = float2(f16tof32(asuint(pd.thresholds.z) & 0xFFFF), f16tof32((asuint(pd.thresholds.z) >> 16) & 0xFFFF)); + + d.flags = asuint(pd.thresholds.w); + + return d; +} + +#endif // LIGHT_SHARED_INCLUDED \ No newline at end of file diff --git a/Source/Shaders/Shaders/MaterialPass.cs.hlsl b/Source/Shaders/Shaders/MaterialPass.cs.hlsl index 59b7bd8..5dae35a 100644 --- a/Source/Shaders/Shaders/MaterialPass.cs.hlsl +++ b/Source/Shaders/Shaders/MaterialPass.cs.hlsl @@ -10,11 +10,13 @@ permutation EDITOR_MODE = [0, 1]; // Off, Terrain #include "Include/Lighting.inc.hlsl" #include "Terrain/TerrainShared.inc.hlsl" #include "Model/ModelShared.inc.hlsl" +#include "Light/LightShared.inc.hlsl" struct Constants { float4 renderInfo; // x = Render Width, y = Render Height, z = 1/Width, w = 1/Height uint4 lightInfo; // x = Num Directional Lights, y = Num Point Lights, z = Num Cascades, w = Shadows Enabled + uint4 tileInfo; // xy = Num Tiles float4 fogColor; float4 fogSettings; // x = Enabled, y = Begin Fog Blend Dist, z = End Fog Blend Dist, w = UNUSED float4 mouseWorldPos; @@ -28,13 +30,65 @@ struct Constants [[vk::push_constant]] Constants _constants; +// Tiled culling +[[vk::binding(0, TILES)]] StructuredBuffer _entityTiles; + +// Lighting +[[vk::binding(0, LIGHT)]] StructuredBuffer _packedDecals; // All decals in the world + [[vk::binding(0, PER_PASS)]] SamplerState _sampler; [[vk::binding(3, PER_PASS)]] Texture2D _skyboxColor; [[vk::binding(4, PER_PASS)]] Texture2D _transparency; [[vk::binding(5, PER_PASS)]] Texture2D _transparencyWeights; [[vk::binding(6, PER_PASS)]] RWTexture2D _resolvedColor; -float4 ShadeTerrain(const uint2 pixelPos, const float2 screenUV, const VisibilityBuffer vBuffer, out float3 outPixelWorldPos) +float3 ProcessDecalBucket(uint buckedIndex, uint mask, float3 pixelWorldPos, float3 pixelWorldNormal) +{ + // Walk set bits in mask + float3 color = float3(0, 0, 0); + + while (mask) + { + uint bit = firstbitlow(mask); + uint decalIndex = buckedIndex * 32u + bit; // Global index into _decals + + PackedDecal packedDecal = _packedDecals[decalIndex]; + Decal decal = UnpackDecal(packedDecal); + + Texture2D decalTexture = _modelTextures[NonUniformResourceIndex(decal.textureID)]; // Decals share the same texture array as models + + color += ApplyDecal(pixelWorldPos, pixelWorldNormal, decal, decalTexture, _sampler); + + mask &= (mask - 1); // Clear the least significant bit set + } + + return color; +} + +float3 ApplyDecals(uint tileIndex, float3 pixelWorldPos, float3 pixelWorldNormal) +{ + // Layout reminder: + // [0 .. Tiles*Buckets-1] : Opaque bitmasks per tile, use these when applied to opaque objects + // [Tiles*Buckets .. 2*Tiles*Buckets-1] : Transparent bitmasks per tile, use these whne applied to transparent objects such as fog between camera and opaque (not actually used yet) + + uint numTiles = _constants.tileInfo.x * _constants.tileInfo.y; + uint bucketsPerLayer = SHADER_ENTITY_TILE_BUCKET_COUNT; + + uint opaqueBase = tileIndex * bucketsPerLayer; + uint transparentBase = numTiles * bucketsPerLayer + opaqueBase; // I don't think I need this but lets keep it so we know what to do in the future + + float3 color = float3(0, 0, 0); + for (uint bucketIndex = 0; bucketIndex < bucketsPerLayer; bucketIndex++) + { + uint mask = _entityTiles[opaqueBase + bucketIndex]; + if (mask != 0) + color += ProcessDecalBucket(bucketIndex, mask, pixelWorldPos, pixelWorldNormal); + } + + return saturate(color); +} + +float4 ShadeTerrain(const uint2 pixelPos, const float2 screenUV, const VisibilityBuffer vBuffer, uint tileIndex, out float3 outPixelWorldPos) { // Get the interpolated vertex data from the visibility buffer PixelVertexData pixelVertexData = GetPixelVertexDataTerrain(pixelPos, vBuffer, 0, _constants.renderInfo.xy); @@ -86,12 +140,6 @@ float4 ShadeTerrain(const uint2 pixelPos, const float2 screenUV, const Visibilit // Apply vertex color color.rgb *= pixelVertexData.color; - //if (_constants.numTextureDecals + _constants.numProceduralDecals > 0) - //{ - // // Apply decals - // ApplyDecals(pixelWorldPosition, color, pixelNormal, _constants.numTextureDecals, _constants.numProceduralDecals, _constants.mouseWorldPos.xyz); - //} - // TODO: Don't hardcode this ShadowSettings shadowSettings; shadowSettings.enableShadows = _constants.lightInfo.w == 1; @@ -101,6 +149,9 @@ float4 ShadeTerrain(const uint2 pixelPos, const float2 screenUV, const Visibilit // Apply lighting color.rgb = ApplyLighting(screenUV, color.rgb, pixelVertexData, _constants.lightInfo, shadowSettings); + // Apply decals + color.rgb += ApplyDecals(tileIndex, pixelVertexData.worldPos, pixelVertexData.worldNormal); + /*#if EDITOR_MODE == 1 // Get settings from push constants const float brushHardness = _constants.brushSettings.x; @@ -185,7 +236,7 @@ float4 ShadeTerrain(const uint2 pixelPos, const float2 screenUV, const Visibilit return saturate(color); } -float4 ShadeModel(const uint2 pixelPos, const float2 screenUV, const VisibilityBuffer vBuffer, out float3 outPixelWorldPos) +float4 ShadeModel(const uint2 pixelPos, const float2 screenUV, const VisibilityBuffer vBuffer, uint tileIndex, out float3 outPixelWorldPos) { // Get the interpolated vertex data from the visibility buffer PixelVertexData pixelVertexData = GetPixelVertexDataModel(pixelPos, vBuffer, 0, _constants.renderInfo.xy); @@ -247,6 +298,9 @@ float4 ShadeModel(const uint2 pixelPos, const float2 screenUV, const VisibilityB // Apply highlight color.rgb *= pixelVertexData.highlightIntensity; + // Apply decals + color.rgb += ApplyDecals(tileIndex, pixelVertexData.worldPos, pixelVertexData.worldNormal); + outPixelWorldPos = pixelVertexData.worldPos; return saturate(color); } @@ -293,6 +347,8 @@ void main(uint3 dispatchThreadId : SV_DispatchThreadID) #endif float2 pixelUV = pixelPos / _constants.renderInfo.xy; + uint2 tileCoord = PixelToTile(pixelPos, TILED_CULLING_BLOCKSIZE); + uint tileIndex = Flatten2D(tileCoord, _constants.tileInfo.xy); float4 color = float4(0, 0, 0, 1); @@ -308,11 +364,11 @@ void main(uint3 dispatchThreadId : SV_DispatchThreadID) } else if (vBuffer.typeID == ObjectType::Terrain) { - color = ShadeTerrain(pixelPos, pixelUV, vBuffer, pixelWorldPos); + color = ShadeTerrain(pixelPos, pixelUV, vBuffer, tileIndex, pixelWorldPos); } else if (vBuffer.typeID == ObjectType::ModelOpaque) // Transparent models are not rendered using visibility buffers { - color = ShadeModel(pixelPos, pixelUV, vBuffer, pixelWorldPos); + color = ShadeModel(pixelPos, pixelUV, vBuffer, tileIndex, pixelWorldPos); } else { diff --git a/Source/Shaders/Shaders/Terrain/Culling.cs.hlsl b/Source/Shaders/Shaders/Terrain/Culling.cs.hlsl index 25acb27..5620bfa 100644 --- a/Source/Shaders/Shaders/Terrain/Culling.cs.hlsl +++ b/Source/Shaders/Shaders/Terrain/Culling.cs.hlsl @@ -72,7 +72,7 @@ struct DrawInput CSInput csInput; InstanceData instance; float4 sphere; - AABB aabb; + AABBMinMax aabb; bool shouldOcclusionCull; bool isShadowPass; }; @@ -143,7 +143,7 @@ void main(CSInput input) const uint chunkID = instance.packedChunkCellID >> 16; const float2 heightRange = ReadHeightRange(instanceIndex); - AABB aabb = GetCellAABB(chunkID, cellID, heightRange); + AABBMinMax aabb = GetCellAABB(chunkID, cellID, heightRange); float4 sphere; sphere.xyz = (aabb.min + aabb.max) / 2.0f; diff --git a/Source/Shaders/Shaders/Terrain/TerrainShared.inc.hlsl b/Source/Shaders/Shaders/Terrain/TerrainShared.inc.hlsl index 82efe20..f7990dd 100644 --- a/Source/Shaders/Shaders/Terrain/TerrainShared.inc.hlsl +++ b/Source/Shaders/Shaders/Terrain/TerrainShared.inc.hlsl @@ -107,15 +107,15 @@ float2 GetGlobalVertexPosition(uint chunkID, uint cellID, uint vertexID) return float2(finalPos.x, -finalPos.y); } -AABB GetCellAABB(uint chunkID, uint cellID, float2 heightRange) +AABBMinMax GetCellAABB(uint chunkID, uint cellID, float2 heightRange) { float2 pos = GetCellPosition(chunkID, cellID); - float3 aabb_min = float3(pos.x, heightRange.x, pos.y); - float3 aabb_max = float3(pos.x - CELL_SIDE_SIZE, heightRange.y, pos.y - CELL_SIDE_SIZE); + float3 aabbMin = float3(pos.x, heightRange.x, pos.y); + float3 aabbMax = float3(pos.x - CELL_SIDE_SIZE, heightRange.y, pos.y - CELL_SIDE_SIZE); - AABB boundingBox; - boundingBox.min = max(aabb_min, aabb_max); - boundingBox.max = min(aabb_min, aabb_max); + AABBMinMax boundingBox; + boundingBox.min = max(aabbMin, aabbMax); + boundingBox.max = min(aabbMin, aabbMax); return boundingBox; } diff --git a/Source/Shaders/Shaders/Utils/Culling.cs.hlsl b/Source/Shaders/Shaders/Utils/Culling.cs.hlsl index e936457..a83044a 100644 --- a/Source/Shaders/Shaders/Utils/Culling.cs.hlsl +++ b/Source/Shaders/Shaders/Utils/Culling.cs.hlsl @@ -32,7 +32,7 @@ struct PackedCullingData struct CullingData { - AABB boundingBox; + AABBMinMax boundingBox; float sphereRadius; }; @@ -121,7 +121,7 @@ struct DrawInput CSInput csInput; IndexedDraw drawCall; float4 sphere; - AABB aabb; + AABBMinMax aabb; float4x4 instanceMatrix; bool shouldOcclusionCull; }; @@ -244,7 +244,7 @@ void main(CSInput input) const CullingData cullingData = LoadCullingData(modelID); - AABB aabb; + AABBMinMax aabb; float4x4 instanceMatrix; if (_constants.cullingDataIsWorldspace) { diff --git a/Source/Shaders/Shaders/Utils/CullingInstanced.cs.hlsl b/Source/Shaders/Shaders/Utils/CullingInstanced.cs.hlsl index e62e4ab..4df9f31 100644 --- a/Source/Shaders/Shaders/Utils/CullingInstanced.cs.hlsl +++ b/Source/Shaders/Shaders/Utils/CullingInstanced.cs.hlsl @@ -39,7 +39,7 @@ struct PackedCullingData struct CullingData { - AABB boundingBox; + AABBMinMax boundingBox; float sphereRadius; }; @@ -135,7 +135,7 @@ struct DrawInput CSInput csInput; uint instanceCount; float4 sphere; - AABB aabb; + AABBMinMax aabb; float4x4 instanceMatrix; bool shouldOcclusionCull; uint instanceRefID; @@ -264,7 +264,7 @@ void main(CSInput input) CullingData cullingData = LoadCullingData(modelID); // Get AABB from CullingData - AABB aabb = cullingData.boundingBox; + AABBMinMax aabb = cullingData.boundingBox; float4x4 instanceMatrix = { {1.0f, 0.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 0.0f, 1.0f} }; // Default to identity if (!_constants.cullingDataIsWorldspace) { diff --git a/Source/Shaders/Shaders/common.inc.hlsl b/Source/Shaders/Shaders/common.inc.hlsl index dc501dc..8b670c1 100644 --- a/Source/Shaders/Shaders/common.inc.hlsl +++ b/Source/Shaders/Shaders/common.inc.hlsl @@ -181,6 +181,63 @@ float4 PackedUnormsToFloat4(uint packed) return f; } +// Use quad ops (SM 6+), best match to pixel-shader ddx/ddy: +inline void ComputeGradients_Quad(float2 uv, out float2 ddx, out float2 ddy) +{ + // Across X + float2 uv_x = QuadReadAcrossX(uv); + ddx = ((WaveGetLaneIndex() & 1) == 0) ? (uv_x - uv) : (uv - uv_x); + + // Across Y + float2 uv_y = QuadReadAcrossY(uv); + ddy = ((WaveGetLaneIndex() & 2) == 0) ? (uv_y - uv) : (uv - uv_y); +} + +// Build orthonormal basis from quaternion (row-major) +inline void BasisFromQuat(float4 q, out float3 r, out float3 u, out float3 f) +{ + // q = (x,y,z,w), unit length + q = normalize(q); + float x = q.x, y = q.y, z = q.z, w = q.w; + float xx = x * x, yy = y * y, zz = z * z; + float xy = x * y, xz = x * z, yz = y * z; + float wx = w * x, wy = w * y, wz = w * z; + + r = float3(1 - 2 * (yy + zz), 2 * (xy + wz), 2 * (xz - wy)); + u = float3(2 * (xy - wz), 1 - 2 * (xx + zz), 2 * (yz + wx)); + f = float3(2 * (xz + wy), 2 * (yz - wx), 1 - 2 * (xx + yy)); +} + +inline uint2 PixelToTile(uint2 pixel, uint2 tileSize) +{ + // Integer division floors automatically: (0..15)->0, (16..31)->1, etc. + return pixel / tileSize; +} + +// 2D array index to flattened 1D array index +inline uint Flatten2D(uint2 coord, uint2 dim) +{ + return coord.x + coord.y * dim.x; +} + +// flattened array index to 2D array index +inline uint2 Unflatten2D(uint idx, uint2 dim) +{ + return uint2(idx % dim.x, idx / dim.x); +} + +inline float3x3 QuatToMat(float4 q) +{ + q = normalize(q); + float x = q.x, y = q.y, z = q.z, w = q.w; + float xx = x * x, yy = y * y, zz = z * z, xy = x * y, xz = x * z, yz = y * z, wx = w * x, wy = w * y, wz = w * z; + return float3x3( + 1 - 2 * (yy + zz), 2 * (xy + wz), 2 * (xz - wy), + 2 * (xy - wz), 1 - 2 * (xx + zz), 2 * (yz + wx), + 2 * (xz + wy), 2 * (yz - wx), 1 - 2 * (xx + yy) + ); +} + enum ObjectType : uint { Skybox = 0, // We clear to this color so we won't be writing it diff --git a/Submodules/Engine b/Submodules/Engine index 9ecb6d9..4f754a2 160000 --- a/Submodules/Engine +++ b/Submodules/Engine @@ -1 +1 @@ -Subproject commit 9ecb6d9ca756bcf853f1e5f720ff0784ca005762 +Subproject commit 4f754a25f8f359570291a3567f00947a5f6f0873