Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add background blur #5720

Open
wants to merge 56 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
8b872d0
Initial addition of blur
dligr Nov 29, 2024
9b31df7
Optimize the Viewport chain
dligr Nov 29, 2024
2072326
Rename some shader variables to camelCase, clean up scene hierarchy
dligr Nov 30, 2024
f8741e8
Add a clarification comment
dligr Nov 30, 2024
aad651b
Apply blur only when distortion is enabled, make viewports stretch to…
dligr Nov 30, 2024
f61f815
Remove a debug print
dligr Nov 30, 2024
aca64b6
Replace tabs with spaces
dligr Nov 30, 2024
4a2e3bc
Get better values for the blur shaders, fix style
dligr Nov 30, 2024
476ab02
Adjust background plane position
dligr Nov 30, 2024
9a7f3f5
Enable background distortion by default
dligr Nov 30, 2024
785b30c
Rename some variables, fix a bug with path disposing
dligr Nov 30, 2024
e087506
Fix the blur formula (backgrounds should be closer to original colors…
dligr Nov 30, 2024
d7bf936
Fix a nullable notation issue
dligr Nov 30, 2024
3364cbb
Merge branch 'master' of https://github.com/Revolutionary-Games/Thriv…
dligr Dec 3, 2024
8d806f0
Split all background logic into its own class
dligr Dec 4, 2024
32a1d9e
Disable the subviewport chain when blur is disabled
dligr Dec 4, 2024
de46cce
Fix null reference exceptions being thrown, fix some logic
dligr Dec 4, 2024
4b0bfbb
Add a blur strength option
dligr Dec 4, 2024
7dbb73d
Fix indentation
dligr Dec 4, 2024
183a356
Change class order, Remove some duplicate function calls and some nul…
dligr Dec 4, 2024
9fb7d8e
Make SubViewports only update when blur is on
dligr Dec 4, 2024
7e4624e
Remove blur toggle, change blur's slider position in the menu for a b…
dligr Dec 5, 2024
ef8633d
Switch from node path exports
dligr Dec 5, 2024
adf3799
Move world position setting to the BackgroundPlane class
dligr Dec 5, 2024
250ae91
Remove dispose from the camera class
dligr Dec 5, 2024
25d81a5
Save blurring state, change some obsolete debug text, change approach…
dligr Dec 5, 2024
81cc1fa
Change blur shader's name to include 'Gaussian'
dligr Dec 5, 2024
da80a2b
Re-add the accidentally deleted blur shaders, change variable orders …
dligr Dec 5, 2024
c42e895
Change variable order
dligr Dec 5, 2024
f8d66c4
Revert changes to blank.png.import
dligr Dec 5, 2024
ebbc183
Remove a null check of a non-nullable variable
dligr Dec 6, 2024
1ee7d7c
Remove an unused function, fix blur strength not changing in some cases
dligr Dec 6, 2024
558bee8
Remove an unused directive
dligr Dec 6, 2024
b6d16b2
Remove an unused {get;set;}, lower a setting's default value to be co…
dligr Dec 6, 2024
08fd6c7
Revert the background plane changing its own world position as it doe…
dligr Dec 6, 2024
4eefd74
Swap some method calls to change blur strength and scene hierarchy se…
dligr Dec 6, 2024
fb1a295
Add UTF-8 BOM
dligr Dec 6, 2024
8f263f3
Remove brackets around a single return
dligr Dec 6, 2024
edd8fad
Revert to BackgroundPlane changing plane's world position
dligr Dec 6, 2024
279b0fb
Simplify a {get;set;}
dligr Dec 6, 2024
d07857c
Make the default scene hierarchy fail-safe
dligr Dec 6, 2024
a921ee9
Not initialize a false variable
dligr Dec 7, 2024
175db9a
Correctly define subviewports' default update mode
dligr Dec 9, 2024
a9812a0
Reuse a function
dligr Dec 9, 2024
1e63466
Fix a typo, make changes to update the distortion json property's def…
dligr Dec 9, 2024
3d22c5e
Rename Subviewports and the BlurPlane on the Godot scene and in code
dligr Dec 9, 2024
05df472
Fill in BackgroundPlane's parameters to work with the new names
dligr Dec 9, 2024
3a04bdc
Remove some unnecessary checks
dligr Dec 10, 2024
d093fc6
Move some functions where they used to be for a cleaner diff
dligr Dec 10, 2024
c54034b
Fix lighting not being updated
dligr Dec 10, 2024
ad8fa84
Make a method private
dligr Dec 12, 2024
5b6c4c9
Update the localizations because of a layout change
dligr Dec 12, 2024
cd4f082
Only enable blur when distortion is on
dligr Dec 13, 2024
7ec3587
Merge 'origin/master' into blur
dligr Dec 13, 2024
95ea4f7
Re-arrange the options menu to place the blur slider under the distor…
dligr Dec 13, 2024
3ec4406
Allow blur without distortion
dligr Dec 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions shaders/Blur.gdshader
dligr marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
shader_type canvas_item;
render_mode unshaded;

uniform sampler2D textureAlbedo : source_color, filter_linear_mipmap, repeat_enable;

uniform vec2 blurStep = vec2(1.0f, 0.0f);

uniform bool applyBlur = true;

void fragment() {
if (applyBlur)
{
vec2 s = blurStep / vec2(textureSize(textureAlbedo, 0));
COLOR.rgb =
0.0016858046f * texture(textureAlbedo, UV - 6.4f * s).rgb +
0.015835322f * texture(textureAlbedo, UV - 4.8f * s).rgb +
0.07843286f * texture(textureAlbedo, UV - 3.2f * s).rgb +
0.20484284f * texture(textureAlbedo, UV - 1.6f * s).rgb +
0.28209478f * texture(textureAlbedo, UV).rgb +
0.20484288f * texture(textureAlbedo, UV + 1.6f * s).rgb +
0.07843288f * texture(textureAlbedo, UV + 3.2f * s).rgb +
0.01583533f * texture(textureAlbedo, UV + 4.8f * s).rgb +
0.0016858061f * texture(textureAlbedo, UV + 6.4f * s).rgb;
}
else
{
COLOR.rgb = texture(textureAlbedo, UV).rgb;
}

COLOR.a = 1.0f;
}
32 changes: 32 additions & 0 deletions shaders/BlurSpatial.gdshader
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
shader_type spatial;
render_mode unshaded, depth_draw_never;

uniform sampler2D textureAlbedo : source_color, filter_linear_mipmap, repeat_enable;

uniform vec2 blurStep = vec2(1.0f, 0.0f);

uniform bool applyBlur = true;

void fragment() {
if (applyBlur)
{
vec2 s = blurStep / vec2(textureSize(textureAlbedo, 0));

// Use SCREEN_UV instead of UV, because otherwise the textureAlbedo is stretched
// across the plane and only a small part of it is visible on the screen
ALBEDO =
0.0016858046f * texture(textureAlbedo, SCREEN_UV - 6.4f * s).rgb +
0.015835322f * texture(textureAlbedo, SCREEN_UV - 4.8f * s).rgb +
0.07843286f * texture(textureAlbedo, SCREEN_UV - 3.2f * s).rgb +
0.20484284f * texture(textureAlbedo, SCREEN_UV - 1.6f * s).rgb +
0.28209478f * texture(textureAlbedo, SCREEN_UV).rgb +
0.20484288f * texture(textureAlbedo, SCREEN_UV + 1.6f * s).rgb +
0.07843288f * texture(textureAlbedo, SCREEN_UV + 3.2f * s).rgb +
0.01583533f * texture(textureAlbedo, SCREEN_UV + 4.8f * s).rgb +
0.0016858061f * texture(textureAlbedo, SCREEN_UV + 6.4f * s).rgb;
}
else
{
ALBEDO = texture(textureAlbedo, SCREEN_UV).rgb;
}
}
5 changes: 3 additions & 2 deletions shaders/MicrobeBackground.gdshader
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ uniform float distortionSpeed : hint_range(0.0, 0.01, 0.0001) = 0.001f;

uniform float layerAnimateSpeed : hint_range(0.0, 2.0, 0.1) = 1.0f;

uniform vec2 worldPos;
hhyyrylainen marked this conversation as resolved.
Show resolved Hide resolved

const vec2 speed0 = vec2(3300.0f);
const vec2 speed1 = vec2(2550.0f);
const vec2 speed2 = vec2(1800.0f);
Expand Down Expand Up @@ -51,7 +53,6 @@ vec3 LightInfluence(float amount)

void vertex(){
vec2 offset = (repeats - 1.0f) / 2.0f;
vec2 worldPos = (INV_VIEW_MATRIX * vec4(0.0f, 0.0f, 0.0f, 1.0f)).xz;

UV = (UV + worldPos / (speed0 * repeats)) * repeats - offset;
UV2 = (0.12f + UV + worldPos / (speed1));
Expand All @@ -63,7 +64,7 @@ void fragment(){
// Application of noise textures on parallax layers, creating distortion.
// Subtracting time results in a flip of the direction of distortion, and multiplying by factor amplifies speed.
vec2 noiseUV1 = UV + TIME * distortionSpeed;
vec2 noise1 = vec2(texture(noiseTex1, noiseUV1).r, texture(noiseTex2, noiseUV1).r);
vec2 noise1 = vec2(texture(noiseTex1, noiseUV1).r, texture(noiseTex2, noiseUV1).r);
vec2 distortedUV0 = UV + noise1 * distortionFactor;

#ifdef DISTORT_ALL_LAYERS
Expand Down
2 changes: 1 addition & 1 deletion src/engine/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public enum StrainBarVisibility
/// enabled.
/// </summary>
[JsonProperty]
public SettingValue<float> MicrobeDistortionStrength { get; private set; } = new(0);
public SettingValue<float> MicrobeDistortionStrength { get; private set; } = new(0.6f);
dligr marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Type of controller button prompts to show
Expand Down
63 changes: 52 additions & 11 deletions src/microbe_stage/MicrobeCamera.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,20 @@ public partial class MicrobeCamera : Camera3D, IGodotEarlyNodeResolve, ISaveLoad
[Export]
public float LightLevelInterpolateSpeed = 4;

private readonly StringName applyBlurParameter = new("applyBlur");
private readonly StringName worldPositionParameter = new("worldPos");
private readonly StringName lightLevelParameter = new("lightLevel");
private readonly StringName distortionStrengthParameter = new("distortionFactor");

[Export]
private NodePath? blurPlanePath;

[Export]
private NodePath blurColorRectPath = null!;

[Export]
private NodePath backgroundPlanePath = null!;

#pragma warning disable CA2213

/// <summary>
Expand All @@ -77,7 +88,11 @@ public partial class MicrobeCamera : Camera3D, IGodotEarlyNodeResolve, ISaveLoad
[JsonIgnore]
private GpuParticles3D? backgroundParticles;

private ShaderMaterial? materialToUpdate;
private ShaderMaterial? spatialBlurMaterial;

private ShaderMaterial? canvasBlurMaterial;

private ShaderMaterial? backgroundMaterial;
#pragma warning restore CA2213

/// <summary>
Expand Down Expand Up @@ -164,14 +179,19 @@ public Vector3 CursorVisualWorldPos

public override void _Ready()
{
var material = GetNode<CsgMesh3D>("BackgroundPlane").Material;
if (material == null)
var material = GetNode<CsgMesh3D>(backgroundPlanePath).Material;
var planeBlurMaterial = GetNode<CsgMesh3D>(blurPlanePath).Material;
var colorRectBlurMaterial = GetNode<CanvasItem>(blurColorRectPath).Material;

if (material == null || planeBlurMaterial == null || colorRectBlurMaterial == null)
{
GD.PrintErr("MicrobeCamera didn't find material to update");
return;
}

materialToUpdate = (ShaderMaterial)material;
backgroundMaterial = (ShaderMaterial)material;
spatialBlurMaterial = (ShaderMaterial)planeBlurMaterial;
canvasBlurMaterial = (ShaderMaterial)colorRectBlurMaterial;

ResolveNodeReferences();

Expand All @@ -180,6 +200,7 @@ public override void _Ready()

UpdateBackgroundVisibility();
ApplyDistortionEffect();
ApplyBlurEffect();

ProcessMode = ProcessModeEnum.Always;
}
Expand All @@ -191,8 +212,8 @@ public void ResolveNodeReferences()

NodeReferencesResolved = true;

if (HasNode("BackgroundPlane"))
backgroundPlane = GetNode<Node3D>("BackgroundPlane");
if (HasNode(backgroundPlanePath))
backgroundPlane = GetNode<Node3D>(backgroundPlanePath);
}

public override void _EnterTree()
Expand All @@ -204,6 +225,7 @@ public override void _EnterTree()
Settings.Instance.MicrobeDistortionStrength.OnChanged += OnBackgroundDistortionChanged;

ApplyDistortionEffect();
ApplyBlurEffect();
}

public override void _ExitTree()
Expand All @@ -230,6 +252,8 @@ public override void _Process(double delta)
{
UpdateCameraPosition(delta, null);
}

backgroundMaterial?.SetShaderParameter(worldPositionParameter, new Vector2(Position.X, Position.Z));
}

public void UpdateCameraPosition(double delta, Vector3? followedObject)
Expand Down Expand Up @@ -332,13 +356,13 @@ public void SetBackground(Background background)
{
// TODO: skip duplicate background changes

if (materialToUpdate == null)
if (backgroundMaterial == null)
throw new InvalidOperationException("Camera not initialized yet");

for (int i = 0; i < 4; ++i)
{
// TODO: switch this loop away to reuse StringName instances if this causes significant allocations
materialToUpdate.SetShaderParameter($"layer{i:n0}", GD.Load<Texture2D>(background.Textures[i]));
backgroundMaterial.SetShaderParameter($"layer{i:n0}", GD.Load<Texture2D>(background.Textures[i]));
}

backgroundParticles?.DetachAndQueueFree();
Expand All @@ -356,8 +380,17 @@ protected override void Dispose(bool disposing)
{
if (disposing)
{
applyBlurParameter.Dispose();
lightLevelParameter.Dispose();
distortionStrengthParameter.Dispose();
worldPositionParameter.Dispose();

if (blurPlanePath != null)
{
blurPlanePath.Dispose();
blurColorRectPath.Dispose();
backgroundPlanePath.Dispose();
}
}

base.Dispose(disposing);
Expand Down Expand Up @@ -461,7 +494,7 @@ private void UpdateBackgroundVisibility()

private void UpdateLightLevel(float delta)
{
if (materialToUpdate == null)
if (backgroundMaterial == null)
{
GD.PrintErr($"{nameof(UpdateLightLevel)} called too early, material not ready");
return;
Expand All @@ -486,17 +519,25 @@ private void UpdateLightLevel(float delta)
lastSetLightLevel = lightLevel;
}

materialToUpdate.SetShaderParameter(lightLevelParameter, lastSetLightLevel);
backgroundMaterial.SetShaderParameter(lightLevelParameter, lastSetLightLevel);
}

private void OnBackgroundDistortionChanged(float value)
{
ApplyDistortionEffect();
ApplyBlurEffect();
}

private void ApplyDistortionEffect()
{
materialToUpdate?.SetShaderParameter(distortionStrengthParameter,
backgroundMaterial?.SetShaderParameter(distortionStrengthParameter,
Settings.Instance.MicrobeDistortionStrength.Value);
}

private void ApplyBlurEffect()
{
bool enabled = Settings.Instance.MicrobeDistortionStrength.Value > 0.0f;
canvasBlurMaterial?.SetShaderParameter(applyBlurParameter, enabled);
spatialBlurMaterial?.SetShaderParameter(applyBlurParameter, enabled);
}
}
73 changes: 68 additions & 5 deletions src/microbe_stage/MicrobeCamera.tscn
Original file line number Diff line number Diff line change
@@ -1,15 +1,38 @@
[gd_scene load_steps=13 format=3 uid="uid://bako5jivv1dji"]
[gd_scene load_steps=19 format=3 uid="uid://bako5jivv1dji"]

[ext_resource type="Texture2D" uid="uid://d3msr0kfa34w2" path="res://assets/textures/background/Thrive_vent1.png" id="1"]
[ext_resource type="Texture2D" uid="uid://bddntbgykd7uq" path="res://assets/textures/background/Thrive_vent2.png" id="2"]
[ext_resource type="Shader" path="res://shaders/BlurSpatial.gdshader" id="2_70a3p"]
[ext_resource type="Script" path="res://src/microbe_stage/MicrobeCamera.cs" id="3"]
[ext_resource type="Texture2D" uid="uid://bsvls5moner1g" path="res://assets/textures/background/Thrive_vent3.png" id="4"]
[ext_resource type="Shader" path="res://shaders/MicrobeBackground.gdshader" id="5"]
[ext_resource type="Texture2D" uid="uid://c3i2n4piw6ln0" path="res://assets/textures/background/Thrive_vent0.png" id="6"]
[ext_resource type="Shader" path="res://shaders/Blur.gdshader" id="8_qrk01"]

[sub_resource type="PlaneMesh" id="1"]
size = Vector2(800, 400)

[sub_resource type="ViewportTexture" id="ViewportTexture_duokn"]
viewport_path = NodePath("SubViewport2")

[sub_resource type="ShaderMaterial" id="ShaderMaterial_1sr76"]
resource_local_to_scene = true
render_priority = -100
shader = ExtResource("2_70a3p")
shader_parameter/blurStep = Vector2(0, 1)
shader_parameter/applyBlur = true
shader_parameter/textureAlbedo = SubResource("ViewportTexture_duokn")

[sub_resource type="ViewportTexture" id="ViewportTexture_vbioy"]
viewport_path = NodePath("SubViewport")

[sub_resource type="ShaderMaterial" id="ShaderMaterial_tdrj5"]
resource_local_to_scene = true
shader = ExtResource("8_qrk01")
shader_parameter/blurStep = Vector2(1, 0)
shader_parameter/applyBlur = true
shader_parameter/textureAlbedo = SubResource("ViewportTexture_vbioy")

[sub_resource type="FastNoiseLite" id="FastNoiseLite_dor76"]
frequency = 0.0035

Expand All @@ -32,8 +55,9 @@ shader = ExtResource("5")
shader_parameter/lightLevel = 1.0
shader_parameter/repeats = Vector2(2, 1)
shader_parameter/distortionFactor = 0.0
shader_parameter/distortionSpeed = 0.001
shader_parameter/distortionSpeed = 0.0025
shader_parameter/layerAnimateSpeed = 0.4
shader_parameter/worldPos = Vector2(0, 0)
shader_parameter/layer0 = ExtResource("6")
shader_parameter/layer1 = ExtResource("1")
shader_parameter/layer2 = ExtResource("2")
Expand All @@ -48,12 +72,51 @@ current = true
fov = 90.0
script = ExtResource("3")
DefaultCameraHeight = 30.0
blurPlanePath = NodePath("BackgroundPlane")
blurColorRectPath = NodePath("SubViewport2/ColorRect")
backgroundPlanePath = NodePath("SubViewport/BackgroundPlane")

[node name="AudioListener3D" type="AudioListener3D" parent="."]
current = true

[node name="BackgroundPlane" type="CSGMesh3D" parent="."]
transform = Transform3D(1, 0, 0, 0, -1.62921e-07, -1, 0, 1, -1.62921e-07, 0, 5.96247e-08, -15)
transform = Transform3D(1, 0, 0, 0, -1.62921e-07, -1, 0, 1, -1.62921e-07, 0, 0, -20)
cast_shadow = 0
mesh = SubResource("1")
material = SubResource("ShaderMaterial_1sr76")

[node name="SubViewport2" type="SubViewport" parent="."]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be implemented as a second render pass or directly baked into the background shader? I ask because we'll hit a rendering performance problem at some point if we just throw subviewports at any problem.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, it seems possible to re-implement this without viewports. I've just found out that it's possible for a shader to get it's texture from a CanvasLayer instead of a viewport (the docs), which should be more performant.

Copy link
Member

@hhyyrylainen hhyyrylainen Nov 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case that's not usable, I think putting the blur code directly into the background shader would be the right way to go. That would also quite neatly I think expose how expensive in terms of texture reads it is actually to perform a blur.

Edit: and as an added benefit the blur could be only applied to the layer that has the distortion on it (so only layer0).

Copy link
Member Author

@dligr dligr Nov 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Putting the blur code into the background shader would be bad for performance, because it would then need n*n texture reads (for blur diameter of n), and for each of those reads it would also need to calculate distortion.

The current implementation does blur in 2 passes (vertical and horizontal), and because of that only needs 2*n reads. This difference in calculation costs should probably compensate for viewports' performance.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still it will now blur the moving bubble layers that are separate from the furthest background layer as well, which is probably not the best. Also it could maybe be possible to use a cheaper blur method combined into the distortion math to do a reasonable enough blur with high performance at the same time as applying the distortion math?

I guess I'll let you investigate first what is actually possible to do before dumping more ideas.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently the way of making multi-pass shaders with CanvasLayers requires reading the screen texture, so there still needs to be at least one viewport.
The background plane can also be optimized into a ColorRect (which needs a scale argument passed to it, but it's probably still more performant than rendering 3D geometry).

This is what the resulting scene hierarchy looks like:
image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After thinking about the situation more: I guess the most optimal way to implement this feature is to use the subviewports, but I think that to balance that out we should add a graphics option to control the blur amount and if the blur is zero the subviewports should be skipped entirely so that the game is no slower than it currently is on computers where rendering to texture ends up harming performance a lot.

disable_3d = true
handle_input_locally = false
size = Vector2i(1280, 720)
render_target_update_mode = 4

[node name="ColorRect" type="ColorRect" parent="SubViewport2"]
material = SubResource("ShaderMaterial_tdrj5")
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -1000.0
offset_top = -500.0
offset_right = 1000.0
offset_bottom = 500.0
grow_horizontal = 2
grow_vertical = 2

[node name="SubViewport" type="SubViewport" parent="."]
own_world_3d = true
handle_input_locally = false
size = Vector2i(1280, 720)
render_target_update_mode = 4

[node name="BackgroundPlane" type="CSGMesh3D" parent="SubViewport"]
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, -1, 0, 1, -4.37114e-08, 0, 0, -15)
cast_shadow = 0
mesh = SubResource("1")
material = SubResource("2")

[node name="AudioListener3D" type="AudioListener3D" parent="."]
current = true
[node name="Camera3D" type="Camera3D" parent="SubViewport"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 10)
fov = 90.0