From 114491147f8d62bba7fc4638fb66449b22ec8076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miquel=20Barbar=C3=A0?= Date: Mon, 17 Aug 2026 15:54:12 +0200 Subject: [PATCH 1/4] Added Flow map first aproach --- Engine_Master_UPC/BinaryArchive.cpp | 5 + Engine_Master_UPC/BinaryArchive.h | 1 + Engine_Master_UPC/ComponentFactory.cpp | 6 +- Engine_Master_UPC/ComponentType.h | 3 +- Engine_Master_UPC/Engine.vcxproj | 29 ++- Engine_Master_UPC/FlowMapComponent.cpp | 111 ++++++++++++ Engine_Master_UPC/FlowMapComponent.h | 50 ++++++ Engine_Master_UPC/FlowMapGPUData.h | 15 ++ Engine_Master_UPC/FlowMapPS.hlsl | 98 +++++++++++ Engine_Master_UPC/FlowMapPass.cpp | 235 +++++++++++++++++++++++++ Engine_Master_UPC/FlowMapPass.h | 35 ++++ Engine_Master_UPC/FlowMapVS.hlsl | 52 ++++++ Engine_Master_UPC/IArchive.h | 1 + Engine_Master_UPC/JsonArchive.cpp | 40 +++++ Engine_Master_UPC/JsonArchive.h | 1 + Engine_Master_UPC/MeshRenderer.cpp | 2 +- Engine_Master_UPC/MeshRenderer.h | 5 +- Engine_Master_UPC/ModuleRender.cpp | 3 + 18 files changed, 686 insertions(+), 6 deletions(-) create mode 100644 Engine_Master_UPC/FlowMapComponent.cpp create mode 100644 Engine_Master_UPC/FlowMapComponent.h create mode 100644 Engine_Master_UPC/FlowMapGPUData.h create mode 100644 Engine_Master_UPC/FlowMapPS.hlsl create mode 100644 Engine_Master_UPC/FlowMapPass.cpp create mode 100644 Engine_Master_UPC/FlowMapPass.h create mode 100644 Engine_Master_UPC/FlowMapVS.hlsl diff --git a/Engine_Master_UPC/BinaryArchive.cpp b/Engine_Master_UPC/BinaryArchive.cpp index c1f63f6b..db2cdf89 100644 --- a/Engine_Master_UPC/BinaryArchive.cpp +++ b/Engine_Master_UPC/BinaryArchive.cpp @@ -105,6 +105,11 @@ void BinaryArchive::serialize(DirectX::SimpleMath::Vector3& val, const char* /*n serializeRaw(&val, sizeof(val)); } +void BinaryArchive::serialize(DirectX::SimpleMath::Vector2& val, const char* /*name*/) +{ + serializeRaw(&val, sizeof(val)); +} + void BinaryArchive::serialize(DirectX::SimpleMath::Quaternion& val, const char* /*name*/) { serializeRaw(&val, sizeof(val)); diff --git a/Engine_Master_UPC/BinaryArchive.h b/Engine_Master_UPC/BinaryArchive.h index 5e270eab..004cc920 100644 --- a/Engine_Master_UPC/BinaryArchive.h +++ b/Engine_Master_UPC/BinaryArchive.h @@ -22,6 +22,7 @@ class BinaryArchive : public IArchive void serializeRaw(void* data, size_t size, const char* name = "") override; void serialize(DirectX::SimpleMath::Vector3& val, const char* name = "") override; + void serialize(DirectX::SimpleMath::Vector2& val, const char* name = "") override; void serialize(DirectX::SimpleMath::Quaternion& val, const char* name = "") override; void serialize(DirectX::SimpleMath::Color& val, const char* name = "") override; void serialize(DirectX::SimpleMath::Matrix& val, const char* name = "") override; diff --git a/Engine_Master_UPC/ComponentFactory.cpp b/Engine_Master_UPC/ComponentFactory.cpp index ccc4cc11..a01650f8 100644 --- a/Engine_Master_UPC/ComponentFactory.cpp +++ b/Engine_Master_UPC/ComponentFactory.cpp @@ -20,6 +20,7 @@ #include "PlayerRenderBufferComponent.h" #include "DamageHighlightComponent.h" #include "DissolveComponent.h" +#include "FlowMapComponent.h" // Prefab #include "PrefabInstanceComponent.h" @@ -112,9 +113,12 @@ std::unique_ptr ComponentFactory::createWithUID(ComponentType type, U case ComponentType::DISSOLVE: return std::make_unique(id, owner); + case ComponentType::FLOW_MAP: + return std::make_unique(id, owner); + case ComponentType::TRANSFORM: case ComponentType::COUNT: default: return nullptr; } -} \ No newline at end of file +} diff --git a/Engine_Master_UPC/ComponentType.h b/Engine_Master_UPC/ComponentType.h index fb5d2776..ea442707 100644 --- a/Engine_Master_UPC/ComponentType.h +++ b/Engine_Master_UPC/ComponentType.h @@ -26,7 +26,8 @@ X(PREFAB_INSTANCE) \ X(PLAYER_RENDER_BUFFER) \ X(DAMAGE_HIGHLIGHT) \ - X(DISSOLVE) + X(DISSOLVE) \ + X(FLOW_MAP) #define COMP_ENUM(name) name, #define COMP_SWITCH(name) case ComponentType::name: return #name; diff --git a/Engine_Master_UPC/Engine.vcxproj b/Engine_Master_UPC/Engine.vcxproj index 75bce092..098b80b4 100644 --- a/Engine_Master_UPC/Engine.vcxproj +++ b/Engine_Master_UPC/Engine.vcxproj @@ -315,6 +315,9 @@ $(SolutionDir)3rdParty\RecastNavigation\DebugUtils\Include; + + + @@ -773,6 +776,8 @@ $(SolutionDir)3rdParty\RecastNavigation\DebugUtils\Include; + + @@ -1153,6 +1158,28 @@ $(SolutionDir)3rdParty\RecastNavigation\DebugUtils\Include; Pixel 6.0 + + Pixel + Pixel + Pixel + Pixel + Pixel + Pixel + 6.0 + 6.0 + 6.0 + + + Vertex + Vertex + Vertex + Vertex + Vertex + Vertex + 6.0 + 6.0 + 6.0 + Vertex 6.0 @@ -1335,4 +1362,4 @@ $(SolutionDir)3rdParty\RecastNavigation\DebugUtils\Include; - \ No newline at end of file + diff --git a/Engine_Master_UPC/FlowMapComponent.cpp b/Engine_Master_UPC/FlowMapComponent.cpp new file mode 100644 index 00000000..78f1584f --- /dev/null +++ b/Engine_Master_UPC/FlowMapComponent.cpp @@ -0,0 +1,111 @@ +#include "Globals.h" +#include "FlowMapComponent.h" + +#include "Application.h" +#include "JsonArchive.h" +#include "ModuleAssets.h" +#include "ModuleResources.h" +#include "ModuleTime.h" +#include "Texture.h" + +#include + +FlowMapComponent::FlowMapComponent(UID id, GameObject* owner) + : Component(id, ComponentType::FLOW_MAP, owner) +{ +} + +std::unique_ptr FlowMapComponent::clone(GameObject* newOwner) const +{ + auto result = std::make_unique(m_uuid, newOwner); + result->m_data = m_data; + result->m_textureAssetId = m_textureAssetId; + result->m_texture = m_texture; + result->m_textureAsset = m_textureAsset; + return result; +} + +void FlowMapComponent::update() +{ + if (!m_data.enabled || m_data.speed == 0.0f) + return; + + const float dt = app->getModuleTime()->deltaTime(); + m_data.offset += m_data.direction * (m_data.speed * dt); + m_data.offset.x -= std::floor(m_data.offset.x); + m_data.offset.y -= std::floor(m_data.offset.y); +} + +void FlowMapComponent::drawUi() +{ + if (!ImGui::CollapsingHeader("Flow Map Settings", ImGuiTreeNodeFlags_DefaultOpen)) + return; + + int source = static_cast(m_data.source); + const char* sources[] = { "Direction", "Texture" }; + if (ImGui::Combo("Source", &source, sources, 2)) + m_data.source = static_cast(source); + + ImGui::DragFloat2("Direction", &m_data.direction.x, 0.01f, -1.0f, 1.0f); + ImGui::DragFloat2("Tiling", &m_data.tiling.x, 0.01f, 0.001f, 100.0f); + ImGui::DragFloat("Speed", &m_data.speed, 0.01f, -100.0f, 100.0f); + ImGui::DragFloat("Strength", &m_data.strength, 0.01f, 0.0f, 10.0f); + bool enabled = m_data.enabled != 0; + if (ImGui::Checkbox("Enabled", &enabled)) + m_data.enabled = enabled ? 1u : 0u; + + if (getSource() == FlowMapSource::TEXTURE) + { + ImGui::Button("Drop Flow Texture Here"); + if (ImGui::BeginDragDropTarget()) + { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ASSET")) + { + UID* uid = static_cast(payload->Data); + AssetId* reference = app->getModuleAssets()->findReference(*uid); + if (reference) + setTextureAssetId(*reference); + } + ImGui::EndDragDropTarget(); + } + ImGui::SameLine(); + ImGui::Text("Loaded: %s", m_texture ? "YES" : "NO"); + } +} + +void FlowMapComponent::serialize(IArchive& archive) +{ + Component::serialize(archive); + archive.serialize(m_data.direction, "Direction"); + archive.serialize(m_data.tiling, "Tiling"); + archive.serialize(m_data.speed, "Speed"); + archive.serialize(m_data.strength, "Strength"); + archive.serialize(m_data.offset, "Offset"); + archive.serialize(m_data.source, "Source"); + archive.serialize(m_data.enabled, "Enabled"); + + archive.beginObject("TextureAssetId"); + m_textureAssetId.serialize(archive); + archive.endObject(); + + if (archive.mode() == ArchiveMode::Input) + loadTexture(); +} + +void FlowMapComponent::setTextureAssetId(const AssetId& id) +{ + m_textureAssetId = id; + loadTexture(); +} + +void FlowMapComponent::loadTexture() +{ + m_texture.reset(); + m_textureAsset.reset(); + if (!m_textureAssetId.isValid()) + return; + + m_textureAsset = app->getModuleAssets()->load(m_textureAssetId); + if (m_textureAsset) + m_texture = app->getModuleResources()->createTexture(*m_textureAsset, true); +} diff --git a/Engine_Master_UPC/FlowMapComponent.h b/Engine_Master_UPC/FlowMapComponent.h new file mode 100644 index 00000000..6259d4fc --- /dev/null +++ b/Engine_Master_UPC/FlowMapComponent.h @@ -0,0 +1,50 @@ +#pragma once + +#include "Component.h" +#include "TextureAsset.h" + +class Texture; + +enum class FlowMapSource : uint32_t +{ + DIRECTION = 0, + TEXTURE = 1 +}; + +struct FlowMapData +{ + Vector2 direction = Vector2(1.0f, 0.0f); + Vector2 tiling = Vector2::One; + float speed = 0.1f; + float strength = 0.1f; + Vector2 offset = Vector2::Zero; + uint32_t source = static_cast(FlowMapSource::DIRECTION); + uint32_t enabled = 1; +}; + +class FlowMapComponent : public Component +{ +public: + FlowMapComponent(UID id, GameObject* owner); + + std::unique_ptr clone(GameObject* newOwner) const override; + void update() override; + void drawUi() override; + void serialize(IArchive& archive) override; + void debugDraw() override {} + void onTransformChange() override {} + + FlowMapData getData() const { return m_data; } + FlowMapSource getSource() const { return static_cast(m_data.source); } + Texture* getTexture() const { return m_texture.get(); } + + void setTextureAssetId(const AssetId& id); + +private: + void loadTexture(); + + FlowMapData m_data{}; + AssetId m_textureAssetId{}; + std::shared_ptr m_texture; + std::shared_ptr m_textureAsset; +}; diff --git a/Engine_Master_UPC/FlowMapGPUData.h b/Engine_Master_UPC/FlowMapGPUData.h new file mode 100644 index 00000000..3d8e5b27 --- /dev/null +++ b/Engine_Master_UPC/FlowMapGPUData.h @@ -0,0 +1,15 @@ +#pragma once + +#include "SimpleMath.h" +#include + +struct FlowMapGPUData +{ + DirectX::SimpleMath::Vector2 direction = DirectX::SimpleMath::Vector2::Zero; + DirectX::SimpleMath::Vector2 tiling = DirectX::SimpleMath::Vector2::One; + DirectX::SimpleMath::Vector2 offset = DirectX::SimpleMath::Vector2::Zero; + float strength = 0.0f; + uint32_t source = 0; + uint32_t enabled = 0; + DirectX::SimpleMath::Vector2 padding = DirectX::SimpleMath::Vector2::Zero; +}; diff --git a/Engine_Master_UPC/FlowMapPS.hlsl b/Engine_Master_UPC/FlowMapPS.hlsl new file mode 100644 index 00000000..bc63e850 --- /dev/null +++ b/Engine_Master_UPC/FlowMapPS.hlsl @@ -0,0 +1,98 @@ +cbuffer ModelData : register(b2) +{ + float4x4 model; + float4x4 normalMat; + float3 diffuseColor; + bool hasDiffuseTex; + float metallicFactor; + float roughnessFactor; + bool hasMetallicRoughnessTex; + float normalFactor; + uint hasNormalTex; + float3 emissiveColor; + uint hasEmissiveTex; + float3 modelPadding; +}; + +cbuffer FlowMapData : register(b6) +{ + float2 flowDirection; + float2 flowTiling; + float2 flowOffset; + float flowStrength; + uint flowSource; + uint flowEnabled; + float2 flowPadding; +}; + +Texture2D diffuseTexture : register(t0); +Texture2D metallicRoughnessTexture : register(t1); +Texture2D normalTexture : register(t2); +Texture2D emissiveTexture : register(t3); +Texture2D flowTexture : register(t13); + +SamplerState linearWrapSampler : register(s0); + +struct PSOutput +{ + float4 diffuse : SV_Target0; + float4 metalRoughness : SV_Target1; + float4 normal : SV_Target2; + float4 position : SV_Target3; + float4 emissive : SV_Target4; +}; + +PSOutput main(float4 position : SV_POSITION, float3 worldPos : TEXCOORD0, + float2 coord : TEXCOORD1, float3 normal : TEXCOORD2, + float3 tangent : TEXCOORD3) +{ + PSOutput output; + float2 flowCoord = coord; + if (flowEnabled != 0 && flowSource == 1) + { + float2 flowVector = flowTexture.Sample(linearWrapSampler, coord).rg * 2.0f - 1.0f; + flowCoord += flowVector * flowStrength; + } + + float3 albedo = diffuseColor; + float metallic = metallicFactor; + float roughness = roughnessFactor; + float ao = 1.0f; + float3 finalNormal = normalize(normal); + float3 emissive = 0.0f; + + if (hasDiffuseTex != 0) + { + float4 sampleColor = diffuseTexture.Sample(linearWrapSampler, flowCoord); + if (sampleColor.a < 0.5f) + discard; + albedo *= sampleColor.rgb; + } + + if (hasMetallicRoughnessTex != 0) + { + float4 sampleM = metallicRoughnessTexture.Sample(linearWrapSampler, flowCoord); + ao = sampleM.r; + roughness = 1.0f - clamp(sampleM.g, 0.04f, 1.0f); + metallic = saturate(sampleM.b * metallicFactor); + } + + if (hasNormalTex != 0) + { + float3 tangentNormal = normalTexture.Sample(linearWrapSampler, flowCoord).rgb; + tangentNormal = normalize(tangentNormal * 2.0f - 1.0f); + float3 tangentVector = normalize(tangent); + float3 bitangentVector = normalize(cross(finalNormal, tangentVector)); + finalNormal = normalize(mul(tangentNormal, float3x3(tangentVector, bitangentVector, finalNormal))); + } + + if (hasEmissiveTex != 0) + emissive = emissiveTexture.Sample(linearWrapSampler, flowCoord).rgb * emissiveColor; + + output.diffuse = float4(albedo, 1.0f); + output.metalRoughness = float4(ao, roughness, metallic, 0.0f); + output.normal = float4(finalNormal, 0.0f); + output.position = float4(worldPos, 0.0f); + output.emissive = float4(emissive, 0.0f); + return output; +} diff --git a/Engine_Master_UPC/FlowMapPass.cpp b/Engine_Master_UPC/FlowMapPass.cpp new file mode 100644 index 00000000..f0caa01c --- /dev/null +++ b/Engine_Master_UPC/FlowMapPass.cpp @@ -0,0 +1,235 @@ +#include "Globals.h" +#include "FlowMapPass.h" + +#include "Application.h" +#include "BasicMaterial.h" +#include "BasicMesh.h" +#include "FlowMapComponent.h" +#include "GameObject.h" +#include "GeometryPass.h" +#include "IndexBuffer.h" +#include "MeshRenderer.h" +#include "ModuleDescriptors.h" +#include "ModuleResources.h" +#include "ModuleScene.h" +#include "ModuleRender.h" +#include "RenderContext.h" +#include "RenderSurface.h" +#include "Skin.h" +#include "Texture.h" +#include "Transform.h" +#include "VertexBuffer.h" + +#include +#include + +FlowMapPass::FlowMapPass(ComPtr device) + : m_device(device) +{ + createRootSignature(); + createPipelineState(); +} + +void FlowMapPass::createRootSignature() +{ + CD3DX12_ROOT_PARAMETER params[6]{}; + CD3DX12_DESCRIPTOR_RANGE materialRange, samplerRange, flowRange; + + materialRange.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, BasicMaterial::SLOT_COUNT, 0, 0); + samplerRange.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, ModuleDescriptors::SampleType::COUNT, 0); + flowRange.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 13, 0); + + params[0].InitAsConstantBufferView(0, 0, D3D12_SHADER_VISIBILITY_VERTEX); + params[1].InitAsConstantBufferView(2, 0, D3D12_SHADER_VISIBILITY_ALL); + params[2].InitAsDescriptorTable(1, &materialRange, D3D12_SHADER_VISIBILITY_PIXEL); + params[3].InitAsDescriptorTable(1, &samplerRange, D3D12_SHADER_VISIBILITY_PIXEL); + params[4].InitAsConstantBufferView(6, 0, D3D12_SHADER_VISIBILITY_ALL); + params[5].InitAsDescriptorTable(1, &flowRange, D3D12_SHADER_VISIBILITY_PIXEL); + + CD3DX12_ROOT_SIGNATURE_DESC desc; + desc.Init(_countof(params), params, 0, nullptr, + D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT); + + ComPtr signature; + ComPtr error; + DXCall(D3D12SerializeRootSignature(&desc, D3D_ROOT_SIGNATURE_VERSION_1, + &signature, &error)); + DXCall(m_device->CreateRootSignature(0, signature->GetBufferPointer(), + signature->GetBufferSize(), IID_PPV_ARGS(&m_rootSignature))); +} + +void FlowMapPass::createPipelineState() +{ + ComPtr vs; + ComPtr ps; + ThrowIfFailed(D3DReadFileToBlob(L"FlowMapVS.cso", &vs)); + ThrowIfFailed(D3DReadFileToBlob(L"FlowMapPS.cso", &ps)); + + D3D12_INPUT_ELEMENT_DESC input[] = + { + { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, + { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, + { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, + { "TANGENT", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 } + }; + + D3D12_DEPTH_STENCIL_DESC depth = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT); + depth.DepthEnable = TRUE; + depth.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO; + depth.DepthFunc = D3D12_COMPARISON_FUNC_EQUAL; + depth.StencilEnable = TRUE; + depth.StencilReadMask = 0xFF; + depth.StencilWriteMask = 0; + depth.FrontFace.StencilFunc = D3D12_COMPARISON_FUNC_EQUAL; + depth.FrontFace.StencilPassOp = D3D12_STENCIL_OP_KEEP; + depth.BackFace = depth.FrontFace; + + D3D12_GRAPHICS_PIPELINE_STATE_DESC desc{}; + desc.InputLayout = { input, _countof(input) }; + desc.pRootSignature = m_rootSignature.Get(); + desc.VS = CD3DX12_SHADER_BYTECODE(vs.Get()); + desc.PS = CD3DX12_SHADER_BYTECODE(ps.Get()); + desc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT); + desc.RasterizerState.FrontCounterClockwise = TRUE; + desc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT); + desc.DepthStencilState = depth; + desc.DSVFormat = DXGI_FORMAT_D32_FLOAT; + desc.SampleMask = UINT_MAX; + desc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; + desc.NumRenderTargets = GeometryPass::GBUFFER_COUNT; + for (UINT i = 0; i < GeometryPass::GBUFFER_COUNT; ++i) + desc.RTVFormats[i] = GeometryPass::GBUFFER_FORMATS[i]; + desc.SampleDesc = { 1, 0 }; + + DXCall(m_device->CreateGraphicsPipelineState(&desc, IID_PPV_ARGS(&m_pipelineState))); +} + +void FlowMapPass::prepare(const RenderContext& ctx) +{ + m_view = &ctx.view; + m_projection = &ctx.projection; + m_viewport = ctx.viewport; + m_scissorRect = ctx.scissorRect; + m_renderSurface = &ctx.renderSurface; + m_meshRenderers = app->getModuleScene()->getVisibleForwardMeshRenderers(RenderMode::FLOW_MAP); +} + +void FlowMapPass::transitionGBuffer(ID3D12GraphicsCommandList4* commandList, + D3D12_RESOURCE_STATES before, D3D12_RESOURCE_STATES after) const +{ + CD3DX12_RESOURCE_BARRIER barriers[GeometryPass::GBUFFER_COUNT]; + for (UINT i = 0; i < GeometryPass::GBUFFER_COUNT; ++i) + { + auto texture = m_renderSurface->getTexture(GeometryPass::kSlots[i]); + barriers[i] = CD3DX12_RESOURCE_BARRIER::Transition( + texture->getD3D12Resource().Get(), before, after); + } + commandList->ResourceBarrier(GeometryPass::GBUFFER_COUNT, barriers); +} + +void FlowMapPass::apply(ID3D12GraphicsCommandList4* commandList) +{ + if (!m_renderSurface || m_meshRenderers.empty()) + return; + + transitionGBuffer(commandList, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, + D3D12_RESOURCE_STATE_RENDER_TARGET); + + D3D12_CPU_DESCRIPTOR_HANDLE rtvs[GeometryPass::GBUFFER_COUNT]; + for (UINT i = 0; i < GeometryPass::GBUFFER_COUNT; ++i) + rtvs[i] = m_renderSurface->getTexture(GeometryPass::kSlots[i])->getRTV(0).cpu; + D3D12_CPU_DESCRIPTOR_HANDLE dsv = m_renderSurface->getTexture(RenderSurface::DEPTH_STENCIL)->getDSV().cpu; + commandList->OMSetRenderTargets(GeometryPass::GBUFFER_COUNT, rtvs, FALSE, &dsv); + commandList->RSSetViewports(1, &m_viewport); + commandList->RSSetScissorRects(1, &m_scissorRect); + commandList->OMSetStencilRef(static_cast(RenderMode::FLOW_MAP)); + commandList->SetPipelineState(m_pipelineState.Get()); + commandList->SetGraphicsRootSignature(m_rootSignature.Get()); + + ID3D12DescriptorHeap* heaps[] = { + app->getModuleDescriptors()->getHeap(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV).getHeap(), + app->getModuleDescriptors()->getHeap(D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER).getHeap() + }; + commandList->SetDescriptorHeaps(_countof(heaps), heaps); + commandList->SetGraphicsRootDescriptorTable(3, + app->getModuleDescriptors()->getHeap(D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER) + .getGPUHandle(ModuleDescriptors::SampleType::LINEAR_WRAP)); + commandList->SetGraphicsRootDescriptorTable(5, + app->getModuleResources()->getEnvironmentBrdfTexture()->getSRV().gpu); + + for (MeshRenderer* renderer : m_meshRenderers) + renderMeshRenderer(commandList, renderer); + + transitionGBuffer(commandList, D3D12_RESOURCE_STATE_RENDER_TARGET, + D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); +} + +void FlowMapPass::renderMeshRenderer(ID3D12GraphicsCommandList4* commandList, + MeshRenderer* renderer) +{ + GameObject* owner = renderer->getOwner(); + if (!owner || !owner->IsActiveInWindowHierarchy() || !renderer->isActive()) + return; + + auto* flow = owner->GetComponentAs(ComponentType::FLOW_MAP); + if (!flow || !flow->isActive()) + return; + + const auto& mesh = renderer->getMesh(); + if (!mesh) + return; + const auto& submeshes = mesh->getSubmeshes(); + const auto& materials = renderer->getMaterials(); + if (submeshes.size() != materials.size()) + return; + + const Skin* skin = renderer->getSkin(); + const VertexBuffer* gpuSkin = skin ? skin->getCurrentGpuSkinnedVertexBuffer() : nullptr; + const VertexBuffer* cpuSkin = skin && skin->isCpuSkinningFallbackEnabled() ? skin->getCpuSkinnedVertexBuffer() : nullptr; + const bool worldSpaceSkin = gpuSkin || cpuSkin; + const VertexBuffer* vb = gpuSkin ? gpuSkin : (cpuSkin ? cpuSkin : mesh->getVertexBuffer().get()); + if (!vb) + return; + + Transform* transform = renderer->getTransform(); + Matrix mvp = worldSpaceSkin ? (*m_view * *m_projection).Transpose() + : (transform->getGlobalMatrix() * *m_view * *m_projection).Transpose(); + commandList->SetGraphicsRootConstantBufferView(0, + app->getModuleRender()->allocateInRingBuffer(&mvp, sizeof(mvp))); + + FlowMapGPUData flowGpu{}; + const FlowMapData data = flow->getData(); + flowGpu.direction = data.direction; + flowGpu.tiling = data.tiling; + flowGpu.offset = data.offset; + flowGpu.strength = data.strength; + flowGpu.source = data.source; + flowGpu.enabled = data.enabled; + if (data.source == static_cast(FlowMapSource::TEXTURE) && flow->getTexture()) + commandList->SetGraphicsRootDescriptorTable(5, flow->getTexture()->getSRV().gpu); + else + flowGpu.source = static_cast(FlowMapSource::DIRECTION); + commandList->SetGraphicsRootConstantBufferView(4, + app->getModuleRender()->allocateInRingBuffer(&flowGpu, sizeof(flowGpu))); + + for (size_t i = 0; i < submeshes.size(); ++i) + { + ModelData model{}; + model.model = worldSpaceSkin ? Matrix::Identity.Transpose() : transform->getGlobalMatrix().Transpose(); + model.normalMat = worldSpaceSkin ? Matrix::Identity.Transpose() : transform->getNormalMatrix().Transpose(); + model.material = materials[i]->getMaterial(); + commandList->SetGraphicsRootConstantBufferView(1, + app->getModuleRender()->allocateInRingBuffer(&model, sizeof(model))); + commandList->SetGraphicsRootDescriptorTable(2, materials[i]->getTableGPUHandle()); + commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + D3D12_VERTEX_BUFFER_VIEW vbv = vb->getVertexBufferView(); + commandList->IASetVertexBuffers(0, 1, &vbv); + if (mesh->hasIndexBuffer()) + { + auto ibv = mesh->getIndexBuffer()->getIndexBufferView(); + commandList->IASetIndexBuffer(&ibv); + commandList->DrawIndexedInstanced(submeshes[i].indexCount, 1, + submeshes[i].indexStart, 0, 0); + } + } +} diff --git a/Engine_Master_UPC/FlowMapPass.h b/Engine_Master_UPC/FlowMapPass.h new file mode 100644 index 00000000..ca870fd0 --- /dev/null +++ b/Engine_Master_UPC/FlowMapPass.h @@ -0,0 +1,35 @@ +#pragma once + +#include "IRenderPass.h" +#include "FlowMapGPUData.h" + +#include + +class MeshRenderer; +class RenderSurface; + +class FlowMapPass : public IRenderPass +{ +public: + explicit FlowMapPass(ComPtr device); + + void prepare(const RenderContext& ctx) override; + void apply(ID3D12GraphicsCommandList4* commandList) override; + +private: + void createRootSignature(); + void createPipelineState(); + void transitionGBuffer(ID3D12GraphicsCommandList4* commandList, + D3D12_RESOURCE_STATES before, D3D12_RESOURCE_STATES after) const; + void renderMeshRenderer(ID3D12GraphicsCommandList4* commandList, MeshRenderer* renderer); + + ComPtr m_device; + ComPtr m_rootSignature; + ComPtr m_pipelineState; + std::vector m_meshRenderers; + RenderSurface* m_renderSurface = nullptr; + D3D12_VIEWPORT m_viewport{}; + D3D12_RECT m_scissorRect{}; + const Matrix* m_view = nullptr; + const Matrix* m_projection = nullptr; +}; diff --git a/Engine_Master_UPC/FlowMapVS.hlsl b/Engine_Master_UPC/FlowMapVS.hlsl new file mode 100644 index 00000000..ee230d88 --- /dev/null +++ b/Engine_Master_UPC/FlowMapVS.hlsl @@ -0,0 +1,52 @@ +cbuffer MVP : register(b0) +{ + float4x4 mvp; +}; + +cbuffer ModelData : register(b2) +{ + float4x4 model; + float4x4 normalMat; + float3 diffuseColor; + bool hasDiffuseTex; + float metallicFactor; + float roughnessFactor; + bool hasMetallicRoughnessTex; + float normalFactor; + uint hasNormalTex; + float3 emissiveColor; + uint hasEmissiveTex; + float3 modelPadding; +}; + +cbuffer FlowMapData : register(b6) +{ + float2 flowDirection; + float2 flowTiling; + float2 flowOffset; + float flowStrength; + uint flowSource; + uint flowEnabled; + float2 flowPadding; +}; + +struct VSOutput +{ + float4 position : SV_POSITION; + float3 worldPos : TEXCOORD0; + float2 coord : TEXCOORD1; + float3 normal : TEXCOORD2; + float3 tangent : TEXCOORD3; +}; + +VSOutput main(float3 position : POSITION, float2 coord : TEXCOORD, + float3 normal : NORMAL, float3 tangent : TANGENT) +{ + VSOutput output; + output.position = mul(float4(position, 1.0f), mvp); + output.worldPos = mul(float4(position, 1.0f), model).xyz; + output.coord = coord * flowTiling + flowOffset; + output.normal = normalize(mul(normal, (float3x3)normalMat)); + output.tangent = normalize(mul(tangent, (float3x3)normalMat)); + return output; +} diff --git a/Engine_Master_UPC/IArchive.h b/Engine_Master_UPC/IArchive.h index 38979490..fec7beba 100644 --- a/Engine_Master_UPC/IArchive.h +++ b/Engine_Master_UPC/IArchive.h @@ -22,6 +22,7 @@ class IArchive virtual void serializeRaw(void* data, size_t size, const char* name = "") = 0; virtual void serialize(DirectX::SimpleMath::Vector3& val, const char* name = "") = 0; + virtual void serialize(DirectX::SimpleMath::Vector2& val, const char* name = "") = 0; virtual void serialize(DirectX::SimpleMath::Quaternion& val, const char* name = "") = 0; virtual void serialize(DirectX::SimpleMath::Color& val, const char* name = "") = 0; virtual void serialize(DirectX::SimpleMath::Matrix& val, const char* name = "") = 0; diff --git a/Engine_Master_UPC/JsonArchive.cpp b/Engine_Master_UPC/JsonArchive.cpp index ee39585f..ca5b9205 100644 --- a/Engine_Master_UPC/JsonArchive.cpp +++ b/Engine_Master_UPC/JsonArchive.cpp @@ -374,6 +374,46 @@ void JsonArchive::serialize(DirectX::SimpleMath::Vector3& val, const char* name) } } +void JsonArchive::serialize(DirectX::SimpleMath::Vector2& val, const char* name) +{ + if (m_mode == ArchiveMode::Output) + { + if (m_valueStack.empty()) return; + rapidjson::Value arr(rapidjson::kArrayType); + arr.PushBack(val.x, m_doc.GetAllocator()); + arr.PushBack(val.y, m_doc.GetAllocator()); + if (m_typeStack.back() == Context::Object) + { + rapidjson::Value key(name, m_doc.GetAllocator()); + m_valueStack.back()->AddMember(key, arr, m_doc.GetAllocator()); + } + else if (m_typeStack.back() == Context::Array) + { + m_valueStack.back()->PushBack(arr, m_doc.GetAllocator()); + } + } + else + { + if (!m_currentInput) return; + const rapidjson::Value* v = nullptr; + if (m_currentInput->IsObject()) + { + if (!m_currentInput->HasMember(name)) return; + v = &(*m_currentInput)[name]; + } + else if (m_currentInput->IsArray()) + { + if (m_currentArrayIndex >= (int)m_currentInput->Size()) return; + v = &(*m_currentInput)[m_currentArrayIndex++]; + } + if (v && v->IsArray() && v->Size() >= 2) + { + val.x = (*v)[0].GetFloat(); + val.y = (*v)[1].GetFloat(); + } + } +} + void JsonArchive::serialize(DirectX::SimpleMath::Quaternion& val, const char* name) { if (m_mode == ArchiveMode::Output) diff --git a/Engine_Master_UPC/JsonArchive.h b/Engine_Master_UPC/JsonArchive.h index c88c3eab..2afbacb3 100644 --- a/Engine_Master_UPC/JsonArchive.h +++ b/Engine_Master_UPC/JsonArchive.h @@ -35,6 +35,7 @@ class JsonArchive : public IArchive void serializeRaw(void* data, size_t size, const char* name = "") override; void serialize(DirectX::SimpleMath::Vector3& val, const char* name = "") override; + void serialize(DirectX::SimpleMath::Vector2& val, const char* name = "") override; void serialize(DirectX::SimpleMath::Quaternion& val, const char* name = "") override; void serialize(DirectX::SimpleMath::Color& val, const char* name = "") override; void serialize(DirectX::SimpleMath::Matrix& val, const char* name = "") override; diff --git a/Engine_Master_UPC/MeshRenderer.cpp b/Engine_Master_UPC/MeshRenderer.cpp index e635d31f..56949420 100644 --- a/Engine_Master_UPC/MeshRenderer.cpp +++ b/Engine_Master_UPC/MeshRenderer.cpp @@ -84,7 +84,7 @@ void MeshRenderer::drawUi() ImGui::Text("Mesh: None"); } - static const char* RENDER_TYPES[(int)RenderMode::COUNT] = { "Default", "Player" }; + static const char* RENDER_TYPES[(int)RenderMode::COUNT] = { "Default", "Player", "Flow Map" }; int typeIndex = static_cast(m_renderMode); if (ImGui::Combo("Render Mode", &typeIndex, RENDER_TYPES, (int)RenderMode::COUNT)) { diff --git a/Engine_Master_UPC/MeshRenderer.h b/Engine_Master_UPC/MeshRenderer.h index aa603817..cccb1563 100644 --- a/Engine_Master_UPC/MeshRenderer.h +++ b/Engine_Master_UPC/MeshRenderer.h @@ -25,7 +25,8 @@ enum class RenderMode : UINT { DEFAULT = 0, PLAYER = 1, - COUNT = 2 + FLOW_MAP = 2, + COUNT = 3 }; @@ -100,4 +101,4 @@ class MeshRenderer : public Component bool m_isCulled = false; RenderMode m_renderMode = RenderMode::DEFAULT; -}; \ No newline at end of file +}; diff --git a/Engine_Master_UPC/ModuleRender.cpp b/Engine_Master_UPC/ModuleRender.cpp index f6d7dedf..a126b461 100644 --- a/Engine_Master_UPC/ModuleRender.cpp +++ b/Engine_Master_UPC/ModuleRender.cpp @@ -31,6 +31,7 @@ #include "GeometryPass.h" #include "DeferredShadingPass.h" #include "PlayerPass.h" +#include "FlowMapPass.h" #include "TrailPass.h" #include "DebugDrawPass.h" #include "UIImagePass.h" @@ -82,6 +83,8 @@ bool ModuleRender::init() m_geometryPass = new GeometryPass(device); m_renderPasses.push_back(std::unique_ptr(m_geometryPass)); + m_renderPasses.push_back(std::make_unique(device)); + m_meshRenderPass = new DeferredShadingPass(device); m_renderPasses.push_back(std::unique_ptr(m_meshRenderPass)); From 5ef8ffedb0b9cdd9af1436888bb9f119e67ef40a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miquel=20Barbar=C3=A0?= Date: Mon, 17 Aug 2026 16:05:25 +0200 Subject: [PATCH 2/4] Flow map working --- Engine_Master_UPC/FlowMapComponent.cpp | 10 ++++++++++ Engine_Master_UPC/FlowMapPass.cpp | 17 +++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/Engine_Master_UPC/FlowMapComponent.cpp b/Engine_Master_UPC/FlowMapComponent.cpp index 78f1584f..38bc697d 100644 --- a/Engine_Master_UPC/FlowMapComponent.cpp +++ b/Engine_Master_UPC/FlowMapComponent.cpp @@ -34,6 +34,16 @@ void FlowMapComponent::update() m_data.offset += m_data.direction * (m_data.speed * dt); m_data.offset.x -= std::floor(m_data.offset.x); m_data.offset.y -= std::floor(m_data.offset.y); + +#ifdef _DEBUG + const uint32_t frame = app->getModuleTime()->frameCount(); + if ((frame % 60u) == 0u) + { + DEBUG_LOG("[FlowMap] component=%llu offset=(%.4f, %.4f) speed=%.3f source=%u\n", + static_cast(m_uuid), + m_data.offset.x, m_data.offset.y, m_data.speed, m_data.source); + } +#endif } void FlowMapComponent::drawUi() diff --git a/Engine_Master_UPC/FlowMapPass.cpp b/Engine_Master_UPC/FlowMapPass.cpp index f0caa01c..e2f241e4 100644 --- a/Engine_Master_UPC/FlowMapPass.cpp +++ b/Engine_Master_UPC/FlowMapPass.cpp @@ -13,6 +13,7 @@ #include "ModuleResources.h" #include "ModuleScene.h" #include "ModuleRender.h" +#include "ModuleTime.h" #include "RenderContext.h" #include "RenderSurface.h" #include "Skin.h" @@ -112,6 +113,12 @@ void FlowMapPass::prepare(const RenderContext& ctx) m_scissorRect = ctx.scissorRect; m_renderSurface = &ctx.renderSurface; m_meshRenderers = app->getModuleScene()->getVisibleForwardMeshRenderers(RenderMode::FLOW_MAP); + +#ifdef _DEBUG + const uint32_t frame = app->getModuleTime()->frameCount(); + if ((frame % 60u) == 0u) + DirectX::DebugTrace("[FlowMap] pass renderers=%zu\n", m_meshRenderers.size()); +#endif } void FlowMapPass::transitionGBuffer(ID3D12GraphicsCommandList4* commandList, @@ -205,6 +212,16 @@ void FlowMapPass::renderMeshRenderer(ID3D12GraphicsCommandList4* commandList, flowGpu.strength = data.strength; flowGpu.source = data.source; flowGpu.enabled = data.enabled; + +#ifdef _DEBUG + const uint32_t frame = app->getModuleTime()->frameCount(); + if ((frame % 60u) == 0u) + { + DirectX::DebugTrace("[FlowMap] draw renderer=%p offset=(%.4f, %.4f) tiling=(%.3f, %.3f) enabled=%u\n", + static_cast(renderer), data.offset.x, data.offset.y, + data.tiling.x, data.tiling.y, data.enabled); + } +#endif if (data.source == static_cast(FlowMapSource::TEXTURE) && flow->getTexture()) commandList->SetGraphicsRootDescriptorTable(5, flow->getTexture()->getSRV().gpu); else From ddf993712c8f30ed549a12249243e8d4c1ba8a5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miquel=20Barbar=C3=A0?= Date: Sat, 22 Aug 2026 12:08:07 +0200 Subject: [PATCH 3/4] Fixed flow map implementation --- Engine_Master_UPC/FlowMapComponent.cpp | 40 +++++++++++--- Engine_Master_UPC/FlowMapComponent.h | 10 ++++ Engine_Master_UPC/FlowMapGPUData.h | 3 + Engine_Master_UPC/FlowMapPS.hlsl | 76 +++++++++++++++++++++----- Engine_Master_UPC/FlowMapPass.cpp | 14 ++++- Engine_Master_UPC/FlowMapVS.hlsl | 3 + 6 files changed, 124 insertions(+), 22 deletions(-) diff --git a/Engine_Master_UPC/FlowMapComponent.cpp b/Engine_Master_UPC/FlowMapComponent.cpp index 38bc697d..e8ec3ffb 100644 --- a/Engine_Master_UPC/FlowMapComponent.cpp +++ b/Engine_Master_UPC/FlowMapComponent.cpp @@ -22,6 +22,7 @@ std::unique_ptr FlowMapComponent::clone(GameObject* newOwner) const result->m_textureAssetId = m_textureAssetId; result->m_texture = m_texture; result->m_textureAsset = m_textureAsset; + result->m_phase = m_phase; return result; } @@ -31,9 +32,17 @@ void FlowMapComponent::update() return; const float dt = app->getModuleTime()->deltaTime(); - m_data.offset += m_data.direction * (m_data.speed * dt); - m_data.offset.x -= std::floor(m_data.offset.x); - m_data.offset.y -= std::floor(m_data.offset.y); + if (getTechnique() == FlowMapTechnique::FLOW_MAP_WATER) + { + m_phase += m_data.speed * dt; + m_phase -= std::floor(m_phase); + } + else + { + m_data.offset += m_data.direction * (m_data.speed * dt); + m_data.offset.x -= std::floor(m_data.offset.x); + m_data.offset.y -= std::floor(m_data.offset.y); + } #ifdef _DEBUG const uint32_t frame = app->getModuleTime()->frameCount(); @@ -51,10 +60,18 @@ void FlowMapComponent::drawUi() if (!ImGui::CollapsingHeader("Flow Map Settings", ImGuiTreeNodeFlags_DefaultOpen)) return; - int source = static_cast(m_data.source); - const char* sources[] = { "Direction", "Texture" }; - if (ImGui::Combo("Source", &source, sources, 2)) - m_data.source = static_cast(source); + int technique = static_cast(m_data.technique); + const char* techniques[] = { "Directional Scroll", "Flow Map Water" }; + if (ImGui::Combo("Technique", &technique, techniques, 2)) + m_data.technique = static_cast(technique); + + if (getTechnique() == FlowMapTechnique::FLOW_MAP_WATER) + { + int source = static_cast(m_data.source); + const char* sources[] = { "Direction", "Texture" }; + if (ImGui::Combo("Source", &source, sources, 2)) + m_data.source = static_cast(source); + } ImGui::DragFloat2("Direction", &m_data.direction.x, 0.01f, -1.0f, 1.0f); ImGui::DragFloat2("Tiling", &m_data.tiling.x, 0.01f, 0.001f, 100.0f); @@ -93,6 +110,15 @@ void FlowMapComponent::serialize(IArchive& archive) archive.serialize(m_data.offset, "Offset"); archive.serialize(m_data.source, "Source"); archive.serialize(m_data.enabled, "Enabled"); + archive.serialize(m_data.technique, "Technique"); + + if (archive.mode() == ArchiveMode::Input) + { + if (m_data.source > static_cast(FlowMapSource::TEXTURE)) + m_data.source = static_cast(FlowMapSource::DIRECTION); + if (m_data.technique > static_cast(FlowMapTechnique::FLOW_MAP_WATER)) + m_data.technique = static_cast(FlowMapTechnique::DIRECTIONAL_SCROLL); + } archive.beginObject("TextureAssetId"); m_textureAssetId.serialize(archive); diff --git a/Engine_Master_UPC/FlowMapComponent.h b/Engine_Master_UPC/FlowMapComponent.h index 6259d4fc..8896ad60 100644 --- a/Engine_Master_UPC/FlowMapComponent.h +++ b/Engine_Master_UPC/FlowMapComponent.h @@ -11,6 +11,12 @@ enum class FlowMapSource : uint32_t TEXTURE = 1 }; +enum class FlowMapTechnique : uint32_t +{ + DIRECTIONAL_SCROLL = 0, + FLOW_MAP_WATER = 1 +}; + struct FlowMapData { Vector2 direction = Vector2(1.0f, 0.0f); @@ -20,6 +26,7 @@ struct FlowMapData Vector2 offset = Vector2::Zero; uint32_t source = static_cast(FlowMapSource::DIRECTION); uint32_t enabled = 1; + uint32_t technique = static_cast(FlowMapTechnique::DIRECTIONAL_SCROLL); }; class FlowMapComponent : public Component @@ -36,7 +43,9 @@ class FlowMapComponent : public Component FlowMapData getData() const { return m_data; } FlowMapSource getSource() const { return static_cast(m_data.source); } + FlowMapTechnique getTechnique() const { return static_cast(m_data.technique); } Texture* getTexture() const { return m_texture.get(); } + float getPhase() const { return m_phase; } void setTextureAssetId(const AssetId& id); @@ -47,4 +56,5 @@ class FlowMapComponent : public Component AssetId m_textureAssetId{}; std::shared_ptr m_texture; std::shared_ptr m_textureAsset; + float m_phase = 0.0f; }; diff --git a/Engine_Master_UPC/FlowMapGPUData.h b/Engine_Master_UPC/FlowMapGPUData.h index 3d8e5b27..3e216412 100644 --- a/Engine_Master_UPC/FlowMapGPUData.h +++ b/Engine_Master_UPC/FlowMapGPUData.h @@ -11,5 +11,8 @@ struct FlowMapGPUData float strength = 0.0f; uint32_t source = 0; uint32_t enabled = 0; + uint32_t technique = 0; + float phase = 0.0f; + uint32_t paddingScalar = 0; DirectX::SimpleMath::Vector2 padding = DirectX::SimpleMath::Vector2::Zero; }; diff --git a/Engine_Master_UPC/FlowMapPS.hlsl b/Engine_Master_UPC/FlowMapPS.hlsl index bc63e850..db4efd1b 100644 --- a/Engine_Master_UPC/FlowMapPS.hlsl +++ b/Engine_Master_UPC/FlowMapPS.hlsl @@ -22,6 +22,9 @@ cbuffer FlowMapData : register(b6) float flowStrength; uint flowSource; uint flowEnabled; + uint flowTechnique; + float flowPhase; + uint flowPaddingScalar; float2 flowPadding; }; @@ -42,17 +45,36 @@ struct PSOutput float4 emissive : SV_Target4; }; +float3 decodeNormal(float4 sampleValue, float normalScale) +{ + float3 tangentNormal = normalize(sampleValue.rgb * 2.0f - 1.0f); + // Older material assets store an unspecified normal strength as zero. + // Treat that value as the standard full-strength normal map. + tangentNormal.xy *= normalScale > 0.0f ? normalScale : 1.0f; + return normalize(tangentNormal); +} + +float2 getFlowVector(float2 coord) +{ + if (flowSource == 1) + return flowTexture.Sample(linearWrapSampler, coord).rg * 2.0f - 1.0f; + + return flowDirection; +} + PSOutput main(float4 position : SV_POSITION, float3 worldPos : TEXCOORD0, float2 coord : TEXCOORD1, float3 normal : TEXCOORD2, float3 tangent : TEXCOORD3) { PSOutput output; - float2 flowCoord = coord; - if (flowEnabled != 0 && flowSource == 1) - { - float2 flowVector = flowTexture.Sample(linearWrapSampler, coord).rg * 2.0f - 1.0f; - flowCoord += flowVector * flowStrength; - } + const bool waterTechnique = flowTechnique == 1; + float2 materialCoord = coord; + float2 normalCoord = coord; + + // Directional scroll moves every material texture. Water mode keeps the + // material stable and only advects the normal map below. + if (!waterTechnique) + materialCoord = coord; float3 albedo = diffuseColor; float metallic = metallicFactor; @@ -63,7 +85,7 @@ PSOutput main(float4 position : SV_POSITION, float3 worldPos : TEXCOORD0, if (hasDiffuseTex != 0) { - float4 sampleColor = diffuseTexture.Sample(linearWrapSampler, flowCoord); + float4 sampleColor = diffuseTexture.Sample(linearWrapSampler, materialCoord); if (sampleColor.a < 0.5f) discard; albedo *= sampleColor.rgb; @@ -71,7 +93,7 @@ PSOutput main(float4 position : SV_POSITION, float3 worldPos : TEXCOORD0, if (hasMetallicRoughnessTex != 0) { - float4 sampleM = metallicRoughnessTexture.Sample(linearWrapSampler, flowCoord); + float4 sampleM = metallicRoughnessTexture.Sample(linearWrapSampler, materialCoord); ao = sampleM.r; roughness = 1.0f - clamp(sampleM.g, 0.04f, 1.0f); metallic = saturate(sampleM.b * metallicFactor); @@ -79,15 +101,41 @@ PSOutput main(float4 position : SV_POSITION, float3 worldPos : TEXCOORD0, if (hasNormalTex != 0) { - float3 tangentNormal = normalTexture.Sample(linearWrapSampler, flowCoord).rgb; - tangentNormal = normalize(tangentNormal * 2.0f - 1.0f); - float3 tangentVector = normalize(tangent); - float3 bitangentVector = normalize(cross(finalNormal, tangentVector)); - finalNormal = normalize(mul(tangentNormal, float3x3(tangentVector, bitangentVector, finalNormal))); + if (waterTechnique && flowEnabled != 0) + { + float2 flowVector = getFlowVector(coord); + float phase0 = frac(flowPhase); + float phase1 = frac(flowPhase + 0.5f); + + float3 normal0 = decodeNormal( + normalTexture.Sample(linearWrapSampler, coord + flowVector * phase0 * flowStrength), + normalFactor); + float3 normal1 = decodeNormal( + normalTexture.Sample(linearWrapSampler, coord + flowVector * phase1 * flowStrength), + normalFactor); + + // Each sample fades out when its phase wraps, hiding the UV jump. + float blend = abs(0.5f - phase0) / 0.5f; + float3 tangentNormal = normalize(lerp(normal0, normal1, blend)); + + float3 tangentVector = normalize(tangent); + float3 bitangentVector = normalize(cross(finalNormal, tangentVector)); + finalNormal = normalize(mul(tangentNormal, + float3x3(tangentVector, bitangentVector, finalNormal))); + } + else + { + float3 tangentNormal = decodeNormal( + normalTexture.Sample(linearWrapSampler, normalCoord), normalFactor); + float3 tangentVector = normalize(tangent); + float3 bitangentVector = normalize(cross(finalNormal, tangentVector)); + finalNormal = normalize(mul(tangentNormal, + float3x3(tangentVector, bitangentVector, finalNormal))); + } } if (hasEmissiveTex != 0) - emissive = emissiveTexture.Sample(linearWrapSampler, flowCoord).rgb * emissiveColor; + emissive = emissiveTexture.Sample(linearWrapSampler, materialCoord).rgb * emissiveColor; output.diffuse = float4(albedo, 1.0f); output.metalRoughness = float4(ao, roughness, metallic, 0.0f); diff --git a/Engine_Master_UPC/FlowMapPass.cpp b/Engine_Master_UPC/FlowMapPass.cpp index e2f241e4..bc4e811b 100644 --- a/Engine_Master_UPC/FlowMapPass.cpp +++ b/Engine_Master_UPC/FlowMapPass.cpp @@ -212,6 +212,17 @@ void FlowMapPass::renderMeshRenderer(ID3D12GraphicsCommandList4* commandList, flowGpu.strength = data.strength; flowGpu.source = data.source; flowGpu.enabled = data.enabled; + flowGpu.technique = data.technique; + flowGpu.phase = flow->getPhase(); + +#ifdef _DEBUG + if ((app->getModuleTime()->frameCount() % 60u) == 0u && + data.technique == static_cast(FlowMapTechnique::FLOW_MAP_WATER)) + { + DirectX::DebugTrace("[FlowMap] water phase=%.3f source=%u texture=%s\n", + flowGpu.phase, data.source, flow->getTexture() ? "YES" : "NO"); + } +#endif #ifdef _DEBUG const uint32_t frame = app->getModuleTime()->frameCount(); @@ -222,7 +233,8 @@ void FlowMapPass::renderMeshRenderer(ID3D12GraphicsCommandList4* commandList, data.tiling.x, data.tiling.y, data.enabled); } #endif - if (data.source == static_cast(FlowMapSource::TEXTURE) && flow->getTexture()) + if (data.technique == static_cast(FlowMapTechnique::FLOW_MAP_WATER) && + data.source == static_cast(FlowMapSource::TEXTURE) && flow->getTexture()) commandList->SetGraphicsRootDescriptorTable(5, flow->getTexture()->getSRV().gpu); else flowGpu.source = static_cast(FlowMapSource::DIRECTION); diff --git a/Engine_Master_UPC/FlowMapVS.hlsl b/Engine_Master_UPC/FlowMapVS.hlsl index ee230d88..4c38162f 100644 --- a/Engine_Master_UPC/FlowMapVS.hlsl +++ b/Engine_Master_UPC/FlowMapVS.hlsl @@ -27,6 +27,9 @@ cbuffer FlowMapData : register(b6) float flowStrength; uint flowSource; uint flowEnabled; + uint flowTechnique; + float flowPhase; + uint flowPaddingScalar; float2 flowPadding; }; From 54f7509c8b9012d42cebc05596ad77fb7b217b15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miquel=20Barbar=C3=A0?= Date: Sat, 22 Aug 2026 18:08:05 +0200 Subject: [PATCH 4/4] enabling the ForwardPass so FlowMapPass works again --- Engine_Master_UPC/ModuleRender.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Engine_Master_UPC/ModuleRender.cpp b/Engine_Master_UPC/ModuleRender.cpp index 5f04a8cb..c3f4979a 100644 --- a/Engine_Master_UPC/ModuleRender.cpp +++ b/Engine_Master_UPC/ModuleRender.cpp @@ -78,8 +78,8 @@ bool ModuleRender::init() m_renderPasses.push_back(std::make_unique(device)); - //m_forwardPrepass = new ForwardPrepass(device); - //m_renderPasses.push_back(std::unique_ptr(m_forwardPrepass)); + m_forwardPrepass = new ForwardPrepass(device); + m_renderPasses.push_back(std::unique_ptr(m_forwardPrepass)); m_geometryPass = new GeometryPass(device); m_renderPasses.push_back(std::unique_ptr(m_geometryPass)); @@ -104,6 +104,8 @@ bool ModuleRender::init() m_renderPasses.push_back(std::make_unique(device)); + + // Resolve the HDR scene into COMPOSITE (exposure, tone mapping, bloom, LUT, // outline, etc.) before the overlay passes draw on top. m_renderPasses.push_back(std::make_unique(device));