Add real-time environment probes support

This commit is contained in:
Wojciech Figat
2022-07-18 14:50:41 +02:00
parent 3dbff3cab7
commit 60f868a08b
7 changed files with 124 additions and 50 deletions

View File

@@ -171,7 +171,6 @@ namespace FlaxEditor.Viewport.Previews
// //
EnvProbe = new EnvironmentProbe EnvProbe = new EnvironmentProbe
{ {
AutoUpdate = false,
CustomProbe = FlaxEngine.Content.LoadAsyncInternal<CubeTexture>(EditorAssets.DefaultSkyCubeTexture) CustomProbe = FlaxEngine.Content.LoadAsyncInternal<CubeTexture>(EditorAssets.DefaultSkyCubeTexture)
}; };
// //
@@ -222,7 +221,7 @@ namespace FlaxEditor.Viewport.Previews
} }
/// <inheritdoc /> /// <inheritdoc />
public override bool HasLoadedAssets => base.HasLoadedAssets && Sky.HasContentLoaded && EnvProbe.Probe.IsLoaded && PostFxVolume.HasContentLoaded; public override bool HasLoadedAssets => base.HasLoadedAssets && Sky.HasContentLoaded && EnvProbe.HasContentLoaded && PostFxVolume.HasContentLoaded;
/// <inheritdoc /> /// <inheritdoc />
public override void OnDestroy() public override void OnDestroy()

View File

@@ -89,8 +89,7 @@ void ForwardShadingFeature::Bind(MaterialShader::BindParameters& params, Span<by
if (probe && probe->GetProbe()) if (probe && probe->GetProbe())
{ {
probe->SetupProbeData(params.RenderContext, &data.EnvironmentProbe); probe->SetupProbeData(params.RenderContext, &data.EnvironmentProbe);
const auto texture = probe->GetProbe()->GetTexture(); context->BindSR(envProbeShaderRegisterIndex, probe->GetProbe());
context->BindSR(envProbeShaderRegisterIndex, GET_TEXTURE_VIEW_SAFE(texture));
} }
else else
{ {

View File

@@ -4,6 +4,7 @@
#include "Engine/Platform/FileSystem.h" #include "Engine/Platform/FileSystem.h"
#include "Engine/Graphics/RenderView.h" #include "Engine/Graphics/RenderView.h"
#include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/Textures/GPUTexture.h"
#include "Engine/Graphics/Textures/TextureData.h" #include "Engine/Graphics/Textures/TextureData.h"
#include "Engine/Renderer/RenderList.h" #include "Engine/Renderer/RenderList.h"
#include "Engine/Renderer/ProbesRenderer.h" #include "Engine/Renderer/ProbesRenderer.h"
@@ -13,18 +14,21 @@
#include "Engine/ContentImporters/AssetsImportingManager.h" #include "Engine/ContentImporters/AssetsImportingManager.h"
#include "Engine/Serialization/Serialization.h" #include "Engine/Serialization/Serialization.h"
#include "Engine/Level/Scene/Scene.h" #include "Engine/Level/Scene/Scene.h"
#include "Engine/Graphics/Graphics.h"
EnvironmentProbe::EnvironmentProbe(const SpawnParams& params) EnvironmentProbe::EnvironmentProbe(const SpawnParams& params)
: Actor(params) : Actor(params)
, _radius(3000.0f) , _radius(3000.0f)
, _isUsingCustomProbe(false) , _isUsingCustomProbe(false)
, _probe(nullptr)
{ {
_sphere = BoundingSphere(Vector3::Zero, _radius); _sphere = BoundingSphere(Vector3::Zero, _radius);
BoundingBox::FromSphere(_sphere, _box); BoundingBox::FromSphere(_sphere, _box);
} }
EnvironmentProbe::~EnvironmentProbe()
{
SAFE_DELETE_GPU_RESOURCE(_probeTexture);
}
float EnvironmentProbe::GetRadius() const float EnvironmentProbe::GetRadius() const
{ {
return _radius; return _radius;
@@ -45,19 +49,9 @@ float EnvironmentProbe::GetScaledRadius() const
return _radius * _transform.Scale.MaxValue(); return _radius * _transform.Scale.MaxValue();
} }
bool EnvironmentProbe::HasProbe() const GPUTexture* EnvironmentProbe::GetProbe() const
{ {
return _probe != nullptr; return _probe ? _probe->GetTexture() : _probeTexture;
}
bool EnvironmentProbe::HasProbeLoaded() const
{
return _probe != nullptr && _probe->IsLoaded();
}
CubeTexture* EnvironmentProbe::GetProbe() const
{
return _probe;
} }
bool EnvironmentProbe::IsUsingCustomProbe() const bool EnvironmentProbe::IsUsingCustomProbe() const
@@ -74,9 +68,7 @@ void EnvironmentProbe::SetupProbeData(const RenderContext& renderContext, ProbeD
CubeTexture* EnvironmentProbe::GetCustomProbe() const CubeTexture* EnvironmentProbe::GetCustomProbe() const
{ {
if (IsUsingCustomProbe()) return _isUsingCustomProbe ? _probe : nullptr;
return GetProbe();
return nullptr;
} }
void EnvironmentProbe::SetCustomProbe(CubeTexture* probe) void EnvironmentProbe::SetCustomProbe(CubeTexture* probe)
@@ -99,6 +91,34 @@ void EnvironmentProbe::Bake(float timeout)
#endif #endif
} }
void EnvironmentProbe::SetProbeData(GPUContext* context, GPUTexture* data)
{
// Remove any probe asset
_isUsingCustomProbe = false;
_probe = nullptr;
// Allocate probe texture manually
if (!_probeTexture)
{
_probeTexture = GPUTexture::New();
#if !BUILD_RELEASE
_probeTexture->SetName(GetNamePath());
#endif
}
if (_probeTexture->Width() != data->Width() || _probeTexture->Format() != data->Format())
{
auto desc = data->GetDescription();
desc.Usage = GPUResourceUsage::Default;
desc.Flags = GPUTextureFlags::ShaderResource;
if (_probeTexture->Init(desc))
return;
_probeTexture->SetResidentMipLevels(_probeTexture->MipLevels());
}
// Copy probe texture data
context->CopyResource(_probeTexture, data);
}
void EnvironmentProbe::SetProbeData(TextureData& data) void EnvironmentProbe::SetProbeData(TextureData& data)
{ {
// Remove custom probe (if used) // Remove custom probe (if used)
@@ -109,7 +129,6 @@ void EnvironmentProbe::SetProbeData(TextureData& data)
} }
Guid id = Guid::New(); Guid id = Guid::New();
#if COMPILE_WITH_ASSETS_IMPORTER #if COMPILE_WITH_ASSETS_IMPORTER
// Create asset file // Create asset file
const String path = GetScene()->GetDataFolderPath() / TEXT("EnvProbes/") / GetID().ToString(Guid::FormatType::N) + ASSET_FILES_EXTENSION_WITH_DOT; const String path = GetScene()->GetDataFolderPath() / TEXT("EnvProbes/") / GetID().ToString(Guid::FormatType::N) + ASSET_FILES_EXTENSION_WITH_DOT;
@@ -121,6 +140,9 @@ void EnvironmentProbe::SetProbeData(TextureData& data)
LOG(Error, "Cannot import generated env probe!"); LOG(Error, "Cannot import generated env probe!");
return; return;
} }
// Remove probe texture
SAFE_DELETE_GPU_RESOURCE(_probeTexture);
#else #else
// TODO: create or reuse virtual texture and use it // TODO: create or reuse virtual texture and use it
LOG(Error, "Changing probes at runtime in game is not supported."); LOG(Error, "Changing probes at runtime in game is not supported.");
@@ -151,11 +173,17 @@ void EnvironmentProbe::Draw(RenderContext& renderContext)
{ {
if (Brightness > ZeroTolerance && if (Brightness > ZeroTolerance &&
(renderContext.View.Flags & ViewFlags::Reflections) != 0 && (renderContext.View.Flags & ViewFlags::Reflections) != 0 &&
renderContext.View.Pass & DrawPass::GBuffer && renderContext.View.Pass & DrawPass::GBuffer)
HasProbeLoaded()) {
#if COMPILE_WITH_PROBES_BAKING
if (UpdateMode == ProbeUpdateMode::Realtime)
ProbesRenderer::Bake(this, 0.0f);
#endif
if ((_probe != nullptr && _probe->IsLoaded()) || _probeTexture != nullptr)
{ {
renderContext.List->EnvironmentProbes.Add(this); renderContext.List->EnvironmentProbes.Add(this);
} }
}
} }
#if USE_EDITOR #if USE_EDITOR
@@ -189,7 +217,7 @@ void EnvironmentProbe::Serialize(SerializeStream& stream, const void* otherObj)
SERIALIZE_MEMBER(Radius, _radius); SERIALIZE_MEMBER(Radius, _radius);
SERIALIZE(CubemapResolution); SERIALIZE(CubemapResolution);
SERIALIZE(Brightness); SERIALIZE(Brightness);
SERIALIZE(AutoUpdate); SERIALIZE(UpdateMode);
SERIALIZE(CaptureNearPlane); SERIALIZE(CaptureNearPlane);
SERIALIZE_MEMBER(IsCustomProbe, _isUsingCustomProbe); SERIALIZE_MEMBER(IsCustomProbe, _isUsingCustomProbe);
SERIALIZE_MEMBER(ProbeID, _probe); SERIALIZE_MEMBER(ProbeID, _probe);
@@ -203,10 +231,18 @@ void EnvironmentProbe::Deserialize(DeserializeStream& stream, ISerializeModifier
DESERIALIZE_MEMBER(Radius, _radius); DESERIALIZE_MEMBER(Radius, _radius);
DESERIALIZE(CubemapResolution); DESERIALIZE(CubemapResolution);
DESERIALIZE(Brightness); DESERIALIZE(Brightness);
DESERIALIZE(AutoUpdate); DESERIALIZE(UpdateMode);
DESERIALIZE(CaptureNearPlane); DESERIALIZE(CaptureNearPlane);
DESERIALIZE_MEMBER(IsCustomProbe, _isUsingCustomProbe); DESERIALIZE_MEMBER(IsCustomProbe, _isUsingCustomProbe);
DESERIALIZE_MEMBER(ProbeID, _probe); DESERIALIZE_MEMBER(ProbeID, _probe);
// [Deprecated on 18.07.2022, expires on 18.07.2022]
if (modifier->EngineBuild <= 6332)
{
const auto member = stream.FindMember("AutoUpdate");
if (member != stream.MemberEnd() && member->value.IsBool() && member->value.GetBool())
UpdateMode = ProbeUpdateMode::WhenMoved;
}
} }
bool EnvironmentProbe::HasContentLoaded() const bool EnvironmentProbe::HasContentLoaded() const
@@ -248,7 +284,7 @@ void EnvironmentProbe::OnTransformChanged()
UpdateBounds(); UpdateBounds();
if (AutoUpdate && IsDuringPlay()) if (IsDuringPlay() && UpdateMode == ProbeUpdateMode::WhenMoved)
{ {
Bake(1.0f); Bake(1.0f);
} }

View File

@@ -13,11 +13,29 @@
API_CLASS() class FLAXENGINE_API EnvironmentProbe : public Actor API_CLASS() class FLAXENGINE_API EnvironmentProbe : public Actor
{ {
DECLARE_SCENE_OBJECT(EnvironmentProbe); DECLARE_SCENE_OBJECT(EnvironmentProbe);
public:
/// <summary>
/// The environment probe update mode.
/// </summary>
API_ENUM() enum class ProbeUpdateMode
{
// Probe can be updated manually (eg. in Editor or from script).
Manual = 0,
// Probe will be automatically updated when is moved.
WhenMoved = 1,
// Probe will be automatically updated in real-time (only if in view and frequency depending on distance to the camera).
Realtime = 2,
};
private: private:
float _radius; float _radius;
bool _isUsingCustomProbe; bool _isUsingCustomProbe;
int32 _sceneRenderingKey = -1; int32 _sceneRenderingKey = -1;
AssetReference<CubeTexture> _probe; AssetReference<CubeTexture> _probe;
GPUTexture* _probeTexture = nullptr;
public:
~EnvironmentProbe();
public: public:
/// <summary> /// <summary>
@@ -33,10 +51,10 @@ public:
float Brightness = 1.0f; float Brightness = 1.0f;
/// <summary> /// <summary>
/// Value indicating if probe should be updated automatically on change. /// The probe update mode.
/// </summary> /// </summary>
API_FIELD(Attributes="EditorOrder(30), EditorDisplay(\"Probe\")") API_FIELD(Attributes="EditorOrder(30), EditorDisplay(\"Probe\")")
bool AutoUpdate = false; ProbeUpdateMode UpdateMode = ProbeUpdateMode::Manual;
/// <summary> /// <summary>
/// The probe capture camera near plane distance. /// The probe capture camera near plane distance.
@@ -61,20 +79,10 @@ public:
/// </summary> /// </summary>
API_PROPERTY() float GetScaledRadius() const; API_PROPERTY() float GetScaledRadius() const;
/// <summary>
/// Returns true if env probe has cube texture assigned.
/// </summary>
API_PROPERTY() bool HasProbe() const;
/// <summary>
/// Returns true if env probe has cube texture assigned.
/// </summary>
API_PROPERTY() bool HasProbeLoaded() const;
/// <summary> /// <summary>
/// Gets the probe texture used during rendering (baked or custom one). /// Gets the probe texture used during rendering (baked or custom one).
/// </summary> /// </summary>
API_PROPERTY() CubeTexture* GetProbe() const; API_PROPERTY() GPUTexture* GetProbe() const;
/// <summary> /// <summary>
/// True if probe is using custom cube texture (not baked). /// True if probe is using custom cube texture (not baked).
@@ -108,7 +116,14 @@ public:
API_FUNCTION() void Bake(float timeout = 0); API_FUNCTION() void Bake(float timeout = 0);
/// <summary> /// <summary>
/// Action fired when probe has been baked. /// Action fired when probe has been baked. Copies data to the texture memory (GPU-only for real-time probes).
/// </summary>
/// <param name="context">The GPU context to use for probe data copying.</param>
/// <param name="data">The new probe data (GPU texture).</param>
void SetProbeData(GPUContext* context, GPUTexture* data);
/// <summary>
/// Action fired when probe has been baked. Imports data to the texture asset (virtual if running in game).
/// </summary> /// </summary>
/// <param name="data">The new probe data.</param> /// <param name="data">The new probe data.</param>
void SetProbeData(TextureData& data); void SetProbeData(TextureData& data);

View File

@@ -157,6 +157,7 @@ void ProbesRenderer::Bake(EnvironmentProbe* probe, float timeout)
_probesToBake.Add(e); _probesToBake.Add(e);
// Fire event // Fire event
if (e.UseTextureData())
OnRegisterBake(e); OnRegisterBake(e);
} }
@@ -183,9 +184,23 @@ void ProbesRenderer::Bake(SkyLight* probe, float timeout)
_probesToBake.Add(e); _probesToBake.Add(e);
// Fire event // Fire event
if (e.UseTextureData())
OnRegisterBake(e); OnRegisterBake(e);
} }
bool ProbesRenderer::Entry::UseTextureData() const
{
if (Type == EntryType::EnvProbe && Actor)
{
switch (Actor.As<EnvironmentProbe>()->UpdateMode)
{
case EnvironmentProbe::ProbeUpdateMode::Realtime:
return false;
}
}
return true;
}
int32 ProbesRenderer::Entry::GetResolution() const int32 ProbesRenderer::Entry::GetResolution() const
{ {
auto resolution = ProbeCubemapResolution::UseGraphicsSettings; auto resolution = ProbeCubemapResolution::UseGraphicsSettings;
@@ -358,7 +373,7 @@ void ProbesRendererService::Update()
texture = _probe; texture = _probe;
break; break;
} }
ASSERT(texture); ASSERT(texture && _current.UseTextureData());
auto taskB = New<DownloadProbeTask>(texture, _current); auto taskB = New<DownloadProbeTask>(texture, _current);
auto taskA = texture->DownloadDataAsync(taskB->GetData()); auto taskA = texture->DownloadDataAsync(taskB->GetData());
if (taskA == nullptr) if (taskA == nullptr)
@@ -462,7 +477,6 @@ void ProbesRenderer::onRender(RenderTask* task, GPUContext* context)
if (_current.Type == EntryType::EnvProbe) if (_current.Type == EntryType::EnvProbe)
{ {
auto envProbe = (EnvironmentProbe*)_current.Actor.Get(); auto envProbe = (EnvironmentProbe*)_current.Actor.Get();
LOG(Info, "Updating Env probe '{0}' (resolution: {1})...", envProbe->ToString(), probeResolution);
Vector3 position = envProbe->GetPosition(); Vector3 position = envProbe->GetPosition();
float radius = envProbe->GetScaledRadius(); float radius = envProbe->GetScaledRadius();
float nearPlane = Math::Max(0.1f, envProbe->CaptureNearPlane); float nearPlane = Math::Max(0.1f, envProbe->CaptureNearPlane);
@@ -479,7 +493,6 @@ void ProbesRenderer::onRender(RenderTask* task, GPUContext* context)
else if (_current.Type == EntryType::SkyLight) else if (_current.Type == EntryType::SkyLight)
{ {
auto skyLight = (SkyLight*)_current.Actor.Get(); auto skyLight = (SkyLight*)_current.Actor.Get();
LOG(Info, "Updating sky light '{0}' (resolution: {1})...", skyLight->ToString(), probeResolution);
Vector3 position = skyLight->GetPosition(); Vector3 position = skyLight->GetPosition();
float nearPlane = 10.0f; float nearPlane = 10.0f;
float farPlane = Math::Max(nearPlane + 1000.0f, skyLight->SkyDistanceThreshold * 2.0f); float farPlane = Math::Max(nearPlane + 1000.0f, skyLight->SkyDistanceThreshold * 2.0f);
@@ -575,6 +588,19 @@ void ProbesRenderer::onRender(RenderTask* task, GPUContext* context)
// Mark as rendered // Mark as rendered
_updateFrameNumber = Engine::FrameCount; _updateFrameNumber = Engine::FrameCount;
_task->Enabled = false; _task->Enabled = false;
// Real-time probes don't use TextureData (for streaming) but copy generated probe directly to GPU memory
if (!_current.UseTextureData())
{
if (_current.Type == EntryType::EnvProbe && _current.Actor)
{
_current.Actor.As<EnvironmentProbe>()->SetProbeData(context, _probe);
}
// Clear flag
_updateFrameNumber = 0;
_current.Type = EntryType::Invalid;
}
} }
#endif #endif

View File

@@ -46,6 +46,7 @@ public:
Timeout = other.Timeout; Timeout = other.Timeout;
} }
bool UseTextureData() const;
int32 GetResolution() const; int32 GetResolution() const;
}; };

View File

@@ -412,8 +412,6 @@ void ReflectionsPass::Render(RenderContext& renderContext, GPUTextureView* light
{ {
// Cache data // Cache data
auto probe = renderContext.List->EnvironmentProbes[probeIndex]; auto probe = renderContext.List->EnvironmentProbes[probeIndex];
if (!probe->HasProbeLoaded())
continue;
float probeRadius = probe->GetScaledRadius(); float probeRadius = probe->GetScaledRadius();
Float3 probePosition = probe->GetPosition() - renderContext.View.Origin; Float3 probePosition = probe->GetPosition() - renderContext.View.Origin;
@@ -436,7 +434,7 @@ void ReflectionsPass::Render(RenderContext& renderContext, GPUTextureView* light
// Render reflections // Render reflections
context->UpdateCB(cb, &data); context->UpdateCB(cb, &data);
context->BindCB(0, cb); context->BindCB(0, cb);
context->BindSR(4, probe->GetProbe()->GetTexture()); context->BindSR(4, probe->GetProbe());
context->SetState(isViewInside ? _psProbeInverted : _psProbeNormal); context->SetState(isViewInside ? _psProbeInverted : _psProbeNormal);
_sphereModel->Render(context); _sphereModel->Render(context);