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
{
AutoUpdate = false,
CustomProbe = FlaxEngine.Content.LoadAsyncInternal<CubeTexture>(EditorAssets.DefaultSkyCubeTexture)
};
//
@@ -222,7 +221,7 @@ namespace FlaxEditor.Viewport.Previews
}
/// <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 />
public override void OnDestroy()

View File

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

View File

@@ -4,6 +4,7 @@
#include "Engine/Platform/FileSystem.h"
#include "Engine/Graphics/RenderView.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/Textures/GPUTexture.h"
#include "Engine/Graphics/Textures/TextureData.h"
#include "Engine/Renderer/RenderList.h"
#include "Engine/Renderer/ProbesRenderer.h"
@@ -13,18 +14,21 @@
#include "Engine/ContentImporters/AssetsImportingManager.h"
#include "Engine/Serialization/Serialization.h"
#include "Engine/Level/Scene/Scene.h"
#include "Engine/Graphics/Graphics.h"
EnvironmentProbe::EnvironmentProbe(const SpawnParams& params)
: Actor(params)
, _radius(3000.0f)
, _isUsingCustomProbe(false)
, _probe(nullptr)
{
_sphere = BoundingSphere(Vector3::Zero, _radius);
BoundingBox::FromSphere(_sphere, _box);
}
EnvironmentProbe::~EnvironmentProbe()
{
SAFE_DELETE_GPU_RESOURCE(_probeTexture);
}
float EnvironmentProbe::GetRadius() const
{
return _radius;
@@ -45,19 +49,9 @@ float EnvironmentProbe::GetScaledRadius() const
return _radius * _transform.Scale.MaxValue();
}
bool EnvironmentProbe::HasProbe() const
GPUTexture* EnvironmentProbe::GetProbe() const
{
return _probe != nullptr;
}
bool EnvironmentProbe::HasProbeLoaded() const
{
return _probe != nullptr && _probe->IsLoaded();
}
CubeTexture* EnvironmentProbe::GetProbe() const
{
return _probe;
return _probe ? _probe->GetTexture() : _probeTexture;
}
bool EnvironmentProbe::IsUsingCustomProbe() const
@@ -74,9 +68,7 @@ void EnvironmentProbe::SetupProbeData(const RenderContext& renderContext, ProbeD
CubeTexture* EnvironmentProbe::GetCustomProbe() const
{
if (IsUsingCustomProbe())
return GetProbe();
return nullptr;
return _isUsingCustomProbe ? _probe : nullptr;
}
void EnvironmentProbe::SetCustomProbe(CubeTexture* probe)
@@ -99,6 +91,34 @@ void EnvironmentProbe::Bake(float timeout)
#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)
{
// Remove custom probe (if used)
@@ -109,7 +129,6 @@ void EnvironmentProbe::SetProbeData(TextureData& data)
}
Guid id = Guid::New();
#if COMPILE_WITH_ASSETS_IMPORTER
// Create asset file
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!");
return;
}
// Remove probe texture
SAFE_DELETE_GPU_RESOURCE(_probeTexture);
#else
// TODO: create or reuse virtual texture and use it
LOG(Error, "Changing probes at runtime in game is not supported.");
@@ -151,10 +173,16 @@ void EnvironmentProbe::Draw(RenderContext& renderContext)
{
if (Brightness > ZeroTolerance &&
(renderContext.View.Flags & ViewFlags::Reflections) != 0 &&
renderContext.View.Pass & DrawPass::GBuffer &&
HasProbeLoaded())
renderContext.View.Pass & DrawPass::GBuffer)
{
renderContext.List->EnvironmentProbes.Add(this);
#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);
}
}
}
@@ -189,7 +217,7 @@ void EnvironmentProbe::Serialize(SerializeStream& stream, const void* otherObj)
SERIALIZE_MEMBER(Radius, _radius);
SERIALIZE(CubemapResolution);
SERIALIZE(Brightness);
SERIALIZE(AutoUpdate);
SERIALIZE(UpdateMode);
SERIALIZE(CaptureNearPlane);
SERIALIZE_MEMBER(IsCustomProbe, _isUsingCustomProbe);
SERIALIZE_MEMBER(ProbeID, _probe);
@@ -203,10 +231,18 @@ void EnvironmentProbe::Deserialize(DeserializeStream& stream, ISerializeModifier
DESERIALIZE_MEMBER(Radius, _radius);
DESERIALIZE(CubemapResolution);
DESERIALIZE(Brightness);
DESERIALIZE(AutoUpdate);
DESERIALIZE(UpdateMode);
DESERIALIZE(CaptureNearPlane);
DESERIALIZE_MEMBER(IsCustomProbe, _isUsingCustomProbe);
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
@@ -248,7 +284,7 @@ void EnvironmentProbe::OnTransformChanged()
UpdateBounds();
if (AutoUpdate && IsDuringPlay())
if (IsDuringPlay() && UpdateMode == ProbeUpdateMode::WhenMoved)
{
Bake(1.0f);
}

View File

@@ -13,11 +13,29 @@
API_CLASS() class FLAXENGINE_API EnvironmentProbe : public Actor
{
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:
float _radius;
bool _isUsingCustomProbe;
int32 _sceneRenderingKey = -1;
AssetReference<CubeTexture> _probe;
GPUTexture* _probeTexture = nullptr;
public:
~EnvironmentProbe();
public:
/// <summary>
@@ -33,10 +51,10 @@ public:
float Brightness = 1.0f;
/// <summary>
/// Value indicating if probe should be updated automatically on change.
/// The probe update mode.
/// </summary>
API_FIELD(Attributes="EditorOrder(30), EditorDisplay(\"Probe\")")
bool AutoUpdate = false;
ProbeUpdateMode UpdateMode = ProbeUpdateMode::Manual;
/// <summary>
/// The probe capture camera near plane distance.
@@ -61,20 +79,10 @@ public:
/// </summary>
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>
/// Gets the probe texture used during rendering (baked or custom one).
/// </summary>
API_PROPERTY() CubeTexture* GetProbe() const;
API_PROPERTY() GPUTexture* GetProbe() const;
/// <summary>
/// True if probe is using custom cube texture (not baked).
@@ -108,7 +116,14 @@ public:
API_FUNCTION() void Bake(float timeout = 0);
/// <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>
/// <param name="data">The new probe data.</param>
void SetProbeData(TextureData& data);

View File

@@ -157,7 +157,8 @@ void ProbesRenderer::Bake(EnvironmentProbe* probe, float timeout)
_probesToBake.Add(e);
// Fire event
OnRegisterBake(e);
if (e.UseTextureData())
OnRegisterBake(e);
}
void ProbesRenderer::Bake(SkyLight* probe, float timeout)
@@ -183,7 +184,21 @@ void ProbesRenderer::Bake(SkyLight* probe, float timeout)
_probesToBake.Add(e);
// Fire event
OnRegisterBake(e);
if (e.UseTextureData())
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
@@ -358,7 +373,7 @@ void ProbesRendererService::Update()
texture = _probe;
break;
}
ASSERT(texture);
ASSERT(texture && _current.UseTextureData());
auto taskB = New<DownloadProbeTask>(texture, _current);
auto taskA = texture->DownloadDataAsync(taskB->GetData());
if (taskA == nullptr)
@@ -462,7 +477,6 @@ void ProbesRenderer::onRender(RenderTask* task, GPUContext* context)
if (_current.Type == EntryType::EnvProbe)
{
auto envProbe = (EnvironmentProbe*)_current.Actor.Get();
LOG(Info, "Updating Env probe '{0}' (resolution: {1})...", envProbe->ToString(), probeResolution);
Vector3 position = envProbe->GetPosition();
float radius = envProbe->GetScaledRadius();
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)
{
auto skyLight = (SkyLight*)_current.Actor.Get();
LOG(Info, "Updating sky light '{0}' (resolution: {1})...", skyLight->ToString(), probeResolution);
Vector3 position = skyLight->GetPosition();
float nearPlane = 10.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
_updateFrameNumber = Engine::FrameCount;
_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

View File

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

View File

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