Add real-time environment probes support
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -46,6 +46,7 @@ public:
|
||||
Timeout = other.Timeout;
|
||||
}
|
||||
|
||||
bool UseTextureData() const;
|
||||
int32 GetResolution() const;
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user