WorkInProgress/PlaneReflection.cs
using Sandbox.Rendering;
public sealed class PlaneReflection : Component, Component.ExecuteInEditor
{
Relationship<Renderer> _target;
[Property]
public Renderer TargetRenderer
{
get => _target.Value;
set => _target.Value = value;
}
[Property] public Vector3 PlaneNormal { get; set; } = Vector3.Up;
[Property] public CameraComponent ReflectionCamera { get; set; }
[Feature( "Reflection" )]
[Property] public bool IncludeReflection { get; set; } = true;
/// <summary>
/// Texture size divider. 0 = screen, 1 = screen/2, 2 = screen/4, 3 = screen/8
/// </summary>
[Range( 1, 8 ), Feature( "Reflection" )]
[Property] public int TextureResolution { get; set; } = 1;
[FeatureEnabled( "Offsetting" ), Property]
public bool OffsettingEnabled { get; set; }
[Property]
[Feature( "Offsetting" )]
public float ReflectionSurfaceOffset { get; set; } = 4;
[FeatureEnabled( "Refraction" ), Property]
public bool IncludeRefraction { get; set; }
[Feature( "Refraction" )]
[ToggleGroup( "RefractionFog" ), Property]
public bool RefractionFog { get; set; }
[Feature( "Refraction" )]
[ToggleGroup( "RefractionFog" ), Property]
public Color RefractionFogColor { get; set; } = Color.Black;
[Feature( "Refraction" )]
[ToggleGroup( "RefractionFog" ), Property, Range( -100, 100 )]
public float RefractionFogHeight { get; set; } = 0;
[Feature( "Refraction" )]
[ToggleGroup( "RefractionFog" ), Property, Range( 0, 1000 )]
public float RefractionFogDepth { get; set; } = 128;
[Feature( "Refraction" )]
[ToggleGroup( "RefractionFog" ), Property, Range( 0, 1000 )]
public float RefractionFogDistance { get; set; } = 0;
CommandList _drawReflection = new();
CommandList _clearReflection = new();
protected override void OnEnabled()
{
CreateCamera();
_target.Init( x => x.ExecuteBefore = _drawReflection, x => x.ExecuteBefore = null );
Tags.Add( "reflection" );
}
protected override void OnDisabled()
{
base.OnDisabled();
_target.Shutdown();
}
private void CreateCamera()
{
if ( ReflectionCamera.IsValid() )
return;
var go = new GameObject( GameObject, true, "Reflections Camera" )
{
Flags = /*GameObjectFlags.Hidden |*/ GameObjectFlags.Absolute
};
ReflectionCamera = go.AddComponent<CameraComponent>( true );
ReflectionCamera.Priority = -100;
ReflectionCamera.IsMainCamera = false;
ReflectionCamera.RenderExcludeTags.Add( "reflection" );
ReflectionCamera.RenderExcludeTags.Add( "debugoverlay" );
ReflectionCamera.Enabled = false;
}
protected override void OnPreRender()
{
base.OnPreRender();
CreateCamera();
if ( !TargetRenderer.IsValid() )
return;
//
// Create a command list that runs immediately before the sceneobject is rendered
//
_clearReflection.Reset();
_drawReflection.Reset();
// work out the reflection plane
var planeNormal = WorldTransform.NormalToWorld( PlaneNormal.Normal );
var reflectPlane = new Plane( WorldPosition, planeNormal );
ReflectionCamera.WorldTransform = Scene.Camera.WorldTransform;
// Refract
if ( IncludeRefraction )
{
var refractionSetup = new RefractionSetup();
refractionSetup.ClipOffset = 5; // This stops the refraction bleeding over the edges
if ( RefractionFog )
{
refractionSetup.ViewSetup.GradientFog = new GradientFogSetup
{
Enabled = true,
Color = RefractionFogColor,
StartDistance = -0.1f,
EndDistance = RefractionFogDistance,
StartHeight = WorldPosition.z + RefractionFogHeight - 0.1f - RefractionFogDepth,
EndHeight = WorldPosition.z + RefractionFogHeight,
MaximumOpacity = 1,
DistanceFalloffExponent = 1,
VerticalFalloffExponent = 1,
};
}
var renderTarget = _drawReflection.GetRenderTarget( "refract", ImageFormat.RGBA16161616F, 1, TextureResolution.Clamp( 1, 8 ) );
_drawReflection.DrawRefraction( ReflectionCamera, reflectPlane, renderTarget, refractionSetup );
_drawReflection.Attributes.Set( "HasRefractionTexture", true );
_drawReflection.Attributes.Set( "RefractionTexture", renderTarget.ColorTexture );
}
// Reflect
if ( IncludeReflection )
{
var reflectSetup = new ReflectionSetup();
reflectSetup.ClipOffset = ReflectionSurfaceOffset;
reflectSetup.FallbackColor = Color.White * 0.2f;
reflectSetup.ViewSetup.ZNear = 0.001f;
reflectSetup.ViewSetup.FlipX = true;
//reflectSetup.ViewSetup.ClipSpaceBounds = new Vector4( 1, -1, -1, 1 );
var renderTarget = _drawReflection.GetRenderTarget( "reflect", ImageFormat.RGBA16161616F, 1, TextureResolution.Clamp( 1, 8 ) );
_drawReflection.DrawReflection( ReflectionCamera, reflectPlane, renderTarget, reflectSetup );
_drawReflection.Attributes.Set( "HasReflectionTexture", true );
_drawReflection.Attributes.Set( "ReflectionTexture", renderTarget.ColorTexture );
_drawReflection.Attributes.Set( "ReflectionColorIndex", renderTarget.ColorIndex );
_clearReflection.Attributes.Set( "ReflectionColorIndex", -1 );
}
TargetRenderer.ExecuteBefore = _drawReflection;
TargetRenderer.ExecuteAfter = _clearReflection;
}
}
public struct Relationship<T>
{
private T _value;
Action<T> onStart;
Action<T> onEnd;
public T Value
{
get => _value;
set
{
if ( EqualityComparer<T>.Default.Equals( _value, value ) )
return;
if ( _value != null )
onEnd?.Invoke( _value );
_value = value;
if ( _value != null )
onStart?.Invoke( _value );
}
}
public void Clear()
{
Value = default;
}
public Relationship( Action<T> onStart, Action<T> onEnd )
{
this.onStart = onStart;
this.onEnd = onEnd;
if ( Value is not null )
{
this.onStart?.InvokeWithWarning( Value );
}
}
public void Init( Action<T> onStart, Action<T> onEnd )
{
this.onStart = onStart;
this.onEnd = onEnd;
if ( Value is not null )
{
this.onStart?.InvokeWithWarning( Value );
}
}
public void Shutdown()
{
if ( Value is not null )
{
onEnd?.InvokeWithWarning( Value );
}
}
}