Add water in a slightly hacky way

This commit is contained in:
karl 2021-06-28 20:00:10 +02:00
parent 24ccf0ddbe
commit d8c60fbf41
9 changed files with 279 additions and 1 deletions

20
World.gd Normal file
View File

@ -0,0 +1,20 @@
extends Spatial
const ray_length = 2000
func _physics_process(delta):
if Input.is_mouse_button_pressed(1):
# If the left mouse button was pressed, cast a ray towards the mouse position
var mouse_pos = get_viewport().get_mouse_position()
var camera = $LerpedFollow/LerpedCamera
var from = camera.project_ray_origin(mouse_pos)
var to = from + camera.project_ray_normal(mouse_pos) * ray_length
var result = get_world().direct_space_state.intersect_ray(from, to)
if not result.empty():
# If there was a collision, set the depth of the water at that position
$Water.set_depth_at_position(result.position, 0.0)

View File

@ -1,4 +1,4 @@
[gd_scene load_steps=23 format=2] [gd_scene load_steps=25 format=2]
[ext_resource path="res://Player.tscn" type="PackedScene" id=1] [ext_resource path="res://Player.tscn" type="PackedScene" id=1]
[ext_resource path="res://Planets.gd" type="Script" id=2] [ext_resource path="res://Planets.gd" type="Script" id=2]
@ -11,6 +11,8 @@
[ext_resource path="res://Resources/gas_planet_flowmap.png" type="Texture" id=9] [ext_resource path="res://Resources/gas_planet_flowmap.png" type="Texture" id=9]
[ext_resource path="res://Resources/gas_planet_base.png" type="Texture" id=10] [ext_resource path="res://Resources/gas_planet_base.png" type="Texture" id=10]
[ext_resource path="res://LerpedFollow.gd" type="Script" id=11] [ext_resource path="res://LerpedFollow.gd" type="Script" id=11]
[ext_resource path="res://addons/interactive_water/Water.tscn" type="PackedScene" id=12]
[ext_resource path="res://World.gd" type="Script" id=13]
[sub_resource type="CubeMesh" id=1] [sub_resource type="CubeMesh" id=1]
size = Vector3( 10, 0.5, 10 ) size = Vector3( 10, 0.5, 10 )
@ -101,6 +103,7 @@ glow_strength = 1.1
glow_bloom = 0.1 glow_bloom = 0.1
[node name="World" type="Spatial"] [node name="World" type="Spatial"]
script = ExtResource( 13 )
[node name="MovingPlatformPivot" type="Position3D" parent="."] [node name="MovingPlatformPivot" type="Position3D" parent="."]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -125, 37, 60 ) transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -125, 37, 60 )
@ -204,3 +207,7 @@ target_paths = [ NodePath("../EnemyPos1"), NodePath("../EnemyPos2") ]
[node name="Player" parent="." instance=ExtResource( 1 )] [node name="Player" parent="." instance=ExtResource( 1 )]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 3, 0 ) transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 3, 0 )
[node name="Water" parent="." instance=ExtResource( 12 )]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 2, -66, -50.5288 )
plane_size = 200.0

View File

@ -0,0 +1,21 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://addons/interactive_water/WaterHeights.gd" type="Script" id=1]
[node name="PersistentShaderTexture" type="Node"]
script = ExtResource( 1 )
[node name="Viewport" type="Viewport" parent="."]
size = Vector2( 1, 1 )
transparent_bg = true
handle_input_locally = false
hdr = false
disable_3d = true
usage = 0
render_target_v_flip = true
render_target_update_mode = 3
[node name="Texture" type="ColorRect" parent="Viewport"]
margin_right = 128.0
margin_bottom = 128.0
rect_min_size = Vector2( 128, 128 )

View File

@ -0,0 +1,71 @@
extends Spatial
# Saves every frame as a PNG in the project directory. Use for debugging (with caution)
export var output_debug_textures := false
export var first_output_frame := 0
export var plane_size := 2.0
var _frame_number := 0
var _positions_to_set = []
func set_depth_at_position(pos: Vector3, depth: float):
# Transform the global position into 2D points on the water surface with values between 0 and 1
pos -= translation
var position_2d = Vector2(pos.x, pos.z)
position_2d += Vector2(plane_size, plane_size) / 2.0
position_2d /= plane_size
if position_2d.x < 1.0 and position_2d.y < 1.0 \
and position_2d.x >= 0.0 and position_2d.y >= 0.0:
_positions_to_set.append([position_2d, depth])
func _ready():
$WaterMesh.mesh.size = Vector2(plane_size, plane_size)
# Apply the scale and positioning to the collider
$WaterMesh/StaticBody.scale.x = plane_size / 2.0
$WaterMesh/StaticBody.scale.y = plane_size / 2.0
$WaterMesh/StaticBody.scale.z = plane_size / 2.0
$WaterMesh/StaticBody.translation.y = 0.6 * 40.0
func _physics_process(delta):
# Get result of previous frame
var result = $WaterHeights.get_texture()
# Set it as the data of the water mesh
$WaterMesh.material_override.set_shader_param("water_heights", result)
# Calculate a new frame. First, get the data of the last frame and modify it accordingly
var image_data = result.get_data()
# Set outstanding pixels
for position_and_depth in _positions_to_set:
var pos = position_and_depth[0]
var depth = position_and_depth[1]
image_data.lock()
image_data.set_pixel(floor(pos.x * 512), floor(pos.y * 512), Color(depth, 0.0, 0.0, 0.0))
image_data.unlock()
_positions_to_set.clear()
# Create an ImageTexture for this new frame
var previous_frame = ImageTexture.new()
previous_frame.create_from_image(image_data)
# Set the previous texture in the shader so that a new texture will be available next frame
$WaterHeights.set_previous_texture(previous_frame)
# Debug output if needed
if output_debug_textures and _frame_number > first_output_frame:
image_data.save_png("res://debugframes/frame%s.png" % [_frame_number])
_frame_number += 1

View File

@ -0,0 +1,18 @@
shader_type spatial;
uniform sampler2D water_heights;
float read_height(sampler2D tex, vec2 uv) {
return texture(tex, uv).r + texture(tex, uv).g / 255.0;
}
void fragment() {
ALBEDO = vec3(0.5, 0.7, 1.0) * (read_height(water_heights, UV) - 0.6) * 3.0;
}
void vertex() {
float vertex_distance = length(VERTEX) * 0.06;
VERTEX += NORMAL * (read_height(water_heights, UV) * 40.0 - pow(2, vertex_distance));
}

View File

@ -0,0 +1,38 @@
[gd_scene load_steps=9 format=2]
[ext_resource path="res://addons/interactive_water/Water.shader" type="Shader" id=1]
[ext_resource path="res://addons/interactive_water/Water.gd" type="Script" id=3]
[ext_resource path="res://addons/interactive_water/WaterUpdateMaterial.tres" type="Material" id=4]
[ext_resource path="res://addons/interactive_water/PersistentShaderTexture.tscn" type="PackedScene" id=5]
[sub_resource type="ViewportTexture" id=1]
viewport_path = NodePath("WaterHeightViewport")
[sub_resource type="ShaderMaterial" id=2]
resource_local_to_scene = true
shader = ExtResource( 1 )
shader_param/water_heights = SubResource( 1 )
[sub_resource type="PlaneMesh" id=3]
subdivide_width = 512
subdivide_depth = 512
[sub_resource type="PlaneShape" id=4]
[node name="Water" type="Spatial"]
script = ExtResource( 3 )
[node name="WaterMesh" type="MeshInstance" parent="."]
material_override = SubResource( 2 )
mesh = SubResource( 3 )
material/0 = null
[node name="StaticBody" type="StaticBody" parent="WaterMesh"]
transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.6, 0 )
[node name="CollisionShape" type="CollisionShape" parent="WaterMesh/StaticBody"]
shape = SubResource( 4 )
[node name="WaterHeights" parent="." instance=ExtResource( 5 )]
size = Vector2( 512, 512 )
shader_material = ExtResource( 4 )

View File

@ -0,0 +1,30 @@
extends Node
# A texture which is continuously (statefully) updated by a shader.
#
# Typical usage: Retrieve the current result with `get_texture` and, at the end of the frame,
# re-insert that texture (updated if needed) with `set_previous_texture`.
#
# Note that the given shader needs to accept a `previous_frame` sampler2D. This represents the
# result of the last frame which is used for generating the new result.
export var size := Vector2(64, 64)
export var shader_material: ShaderMaterial
func _ready():
$Viewport/Texture.rect_min_size = size
$Viewport/Texture.rect_size = size
$Viewport.size = size
assert(shader_material)
$Viewport/Texture.material = shader_material
func get_texture():
return $Viewport.get_texture()
func set_previous_texture(texture):
$Viewport/Texture.material.set_shader_param("previous_frame", texture)

View File

@ -0,0 +1,63 @@
shader_type canvas_item;
render_mode unshaded, blend_disabled;
uniform sampler2D previous_frame;
uniform float water_height = 0.6;
uniform float height_damping = 0.13;
uniform float velocity_damping = 0.8;
uniform float spread = 0.94;
// Height and Velocity are encoded in two components each, so RG is height and BA is velocity.
// This is needed to get a workable accuracy.
float read_height(sampler2D tex, vec2 uv) {
return texture(tex, uv).r + texture(tex, uv).g / 255.0;
}
float read_velocity(sampler2D tex, vec2 uv) {
return texture(tex, uv).b + texture(tex, uv).a / 255.0;
}
float get_encoded_remainder(float num) {
return fract(num * 255.0);
}
void fragment() {
// Read values here
float height_here = read_height(previous_frame, UV);
float velocity_here = read_velocity(previous_frame, UV);
// Apply force towards the base height
// This follows from the damped harmonic oscillator equation F = -kx-bv
float force = -height_damping * (height_here - water_height) - velocity_here * velocity_damping;
float acceleration_here = force;
// In addition to each individual height behaving like a spring, neighbouring heights are
// "connected by springs" as well:
// Read more samples
float uv_mod = 1.0 / float(textureSize(previous_frame, 0).x);
float height_up = read_height(previous_frame, UV + vec2(0.0, uv_mod));
float height_down = read_height(previous_frame, UV + vec2(0.0, -uv_mod));
float height_left = read_height(previous_frame, UV + vec2(-uv_mod, 0.0));
float height_right = read_height(previous_frame, UV + vec2(uv_mod, 0.0));
// Calculate differences
float up_delta = spread * (height_up - height_here);
float down_delta = spread * (height_down - height_here);
float left_delta = spread * (height_left - height_here);
float right_delta = spread * (height_right - height_here);
// Use the biggest delta to apply to this height
float sum_delta = max(max(left_delta, right_delta), max(up_delta, down_delta));
// Apply velocity and height
velocity_here += sum_delta + acceleration_here;
height_here += velocity_here;
// Write to the texture
COLOR = vec4(
height_here, get_encoded_remainder(height_here),
velocity_here, get_encoded_remainder(velocity_here)
);
}

View File

@ -0,0 +1,10 @@
[gd_resource type="ShaderMaterial" load_steps=2 format=2]
[ext_resource path="res://addons/interactive_water/WaterUpdate.shader" type="Shader" id=1]
[resource]
shader = ExtResource( 1 )
shader_param/water_height = 0.6
shader_param/height_damping = 0.13
shader_param/velocity_damping = 0.8
shader_param/spread = 0.94