mirror of
https://github.com/godotengine/godot-demo-projects.git
synced 2025-12-16 13:30:07 +01:00
Add compute texture demo (#938)
This commit is contained in:
38
compute/texture/README.md
Normal file
38
compute/texture/README.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Compute texture
|
||||
|
||||
This demo shows how to use compute shaders to populate a texture that is used as an input for a material shader.
|
||||
|
||||
When the mouse cursor isn't hovering above the plane random "drops" of water are added that drive the ripple effect.
|
||||
When the mouse cursor is above the plane you can "draw" on the plane to drive the ripple effect.
|
||||
|
||||
Language: GDScript
|
||||
|
||||
Renderer: Forward Plus
|
||||
|
||||
> Note: this demo requires Godot 4.2 or later
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||
|
||||
## Technical description
|
||||
|
||||
The texture populated by the compute shader contains height data that is used in the material shader to create a rain drops/water ripple effect. It's a well known technique that has been around since the mid 90ies, adapted to a compute shader.
|
||||
|
||||
Three textures are created directly on the rendering device:
|
||||
- One texture is used to write the heightmap to and used in the material shader.
|
||||
- One texture is read from and contains the previous frames data.
|
||||
- One texture is read from and contains data from the frame before that.
|
||||
|
||||
Instead of copying data from texture to texture to create this history, we simply cycle the RIDs.
|
||||
|
||||
Note that in this demo we are using the main rendering device to ensure we execute our compute shader before our normal rendering.
|
||||
|
||||
To use the texture with the latest height data we use a `Texture2DRD` resource, this is a special texture resource node that is able to use a texture directly created on the rendering device and expose it to material shaders.
|
||||
|
||||
The material shader uses a standard gradient approach by sampling the height map and calculating tangent and bi-normal vectors and adjust the normal accordingly.
|
||||
|
||||
## Licenses
|
||||
|
||||
Files in the `polyhaven/` folder are downloaded from <https://polyhaven.com/a/industrial_sunset_puresky>
|
||||
and are licensed under CC0 1.0 Universal.
|
||||
Binary file not shown.
@@ -0,0 +1,34 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://d051ugdf65it1"
|
||||
path="res://.godot/imported/industrial_sunset_puresky_2k.hdr-2273dddf6859dd4da64c4a85b4589512.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://assets/polyhaven/industrial_sunset_puresky_2k.hdr"
|
||||
dest_files=["res://.godot/imported/industrial_sunset_puresky_2k.hdr-2273dddf6859dd4da64c4a85b4589512.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=3
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=0
|
||||
1
compute/texture/icon.svg
Normal file
1
compute/texture/icon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg height="128" width="128" xmlns="http://www.w3.org/2000/svg"><rect x="2" y="2" width="124" height="124" rx="14" fill="#363d52" stroke="#212532" stroke-width="4"/><g transform="scale(.101) translate(122 122)"><g fill="#fff"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 813 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H447l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c3 34 55 34 58 0v-86c-3-34-55-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 950 B |
37
compute/texture/icon.svg.import
Normal file
37
compute/texture/icon.svg.import
Normal file
@@ -0,0 +1,37 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bonkdv3wikslq"
|
||||
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://icon.svg"
|
||||
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
||||
27
compute/texture/main.gd
Normal file
27
compute/texture/main.gd
Normal file
@@ -0,0 +1,27 @@
|
||||
extends Node3D
|
||||
|
||||
# Note, the code here just adds some control to our effects.
|
||||
# Check res://water_plane/water_plane.gd for the real implementation.
|
||||
|
||||
var y = 0.0
|
||||
|
||||
@onready var water_plane = $WaterPlane
|
||||
|
||||
func _ready():
|
||||
$Container/RainSize/HSlider.value = $WaterPlane.rain_size
|
||||
$Container/MouseSize/HSlider.value = $WaterPlane.mouse_size
|
||||
|
||||
|
||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
func _process(delta):
|
||||
if $Container/Rotate.button_pressed:
|
||||
y += delta
|
||||
water_plane.basis = Basis(Vector3.UP, y)
|
||||
|
||||
|
||||
func _on_rain_size_changed(value):
|
||||
$WaterPlane.rain_size = value
|
||||
|
||||
|
||||
func _on_mouse_size_changed(value):
|
||||
$WaterPlane.mouse_size = value
|
||||
76
compute/texture/main.tscn
Normal file
76
compute/texture/main.tscn
Normal file
@@ -0,0 +1,76 @@
|
||||
[gd_scene load_steps=7 format=3 uid="uid://c7nfvt1chslyh"]
|
||||
|
||||
[ext_resource type="Script" path="res://main.gd" id="1_yvrvl"]
|
||||
[ext_resource type="Texture2D" uid="uid://d051ugdf65it1" path="res://assets/polyhaven/industrial_sunset_puresky_2k.hdr" id="2_g2q6b"]
|
||||
[ext_resource type="PackedScene" uid="uid://b2a5bjsxw63wr" path="res://water_plane/water_plane.tscn" id="2_k1nfp"]
|
||||
|
||||
[sub_resource type="PanoramaSkyMaterial" id="PanoramaSkyMaterial_obhcg"]
|
||||
panorama = ExtResource("2_g2q6b")
|
||||
|
||||
[sub_resource type="Sky" id="Sky_s1sgk"]
|
||||
sky_material = SubResource("PanoramaSkyMaterial_obhcg")
|
||||
|
||||
[sub_resource type="Environment" id="Environment_5dv8s"]
|
||||
background_mode = 2
|
||||
sky = SubResource("Sky_s1sgk")
|
||||
tonemap_mode = 2
|
||||
tonemap_white = 4.56
|
||||
|
||||
[node name="Main" type="Node3D"]
|
||||
script = ExtResource("1_yvrvl")
|
||||
|
||||
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
|
||||
transform = Transform3D(0.5, -0.75, 0.433013, 2.78059e-08, 0.5, 0.866026, -0.866025, -0.433013, 0.25, 0, 1, 0)
|
||||
shadow_enabled = true
|
||||
|
||||
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
|
||||
environment = SubResource("Environment_5dv8s")
|
||||
|
||||
[node name="WaterPlane" parent="." instance=ExtResource("2_k1nfp")]
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="."]
|
||||
transform = Transform3D(0.900266, -0.142464, 0.41137, -0.113954, 0.834877, 0.538512, -0.420162, -0.531681, 0.735377, 1.55343, 1.1434, 2.431)
|
||||
|
||||
[node name="Container" type="VBoxContainer" parent="."]
|
||||
offset_right = 40.0
|
||||
offset_bottom = 40.0
|
||||
|
||||
[node name="Rotate" type="CheckBox" parent="Container"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0, 0, 0, 1)
|
||||
text = "Rotate"
|
||||
|
||||
[node name="RainSize" type="HBoxContainer" parent="Container"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HSlider" type="HSlider" parent="Container/RainSize"]
|
||||
custom_minimum_size = Vector2(250, 0)
|
||||
layout_mode = 2
|
||||
min_value = 1.0
|
||||
max_value = 10.0
|
||||
step = 0.1
|
||||
value = 1.0
|
||||
|
||||
[node name="Label" type="Label" parent="Container/RainSize"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0, 0, 0, 1)
|
||||
text = "Rain size"
|
||||
|
||||
[node name="MouseSize" type="HBoxContainer" parent="Container"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HSlider" type="HSlider" parent="Container/MouseSize"]
|
||||
custom_minimum_size = Vector2(250, 0)
|
||||
layout_mode = 2
|
||||
min_value = 1.0
|
||||
max_value = 10.0
|
||||
step = 0.1
|
||||
value = 1.1
|
||||
|
||||
[node name="Label" type="Label" parent="Container/MouseSize"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0, 0, 0, 1)
|
||||
text = "Mouse size"
|
||||
|
||||
[connection signal="value_changed" from="Container/RainSize/HSlider" to="." method="_on_rain_size_changed"]
|
||||
[connection signal="value_changed" from="Container/MouseSize/HSlider" to="." method="_on_mouse_size_changed"]
|
||||
20
compute/texture/project.godot
Normal file
20
compute/texture/project.godot
Normal file
@@ -0,0 +1,20 @@
|
||||
; Engine configuration file.
|
||||
; It's best edited using the editor UI and not directly,
|
||||
; since the parameters that go here are not all obvious.
|
||||
;
|
||||
; Format:
|
||||
; [section] ; section goes between []
|
||||
; param=value ; assign values to parameters
|
||||
|
||||
config_version=5
|
||||
|
||||
[application]
|
||||
|
||||
config/name="TestCustomTextures"
|
||||
run/main_scene="res://main.tscn"
|
||||
config/features=PackedStringArray("4.2", "Forward Plus")
|
||||
config/icon="res://icon.svg"
|
||||
|
||||
[rendering]
|
||||
|
||||
driver/threads/thread_model=2
|
||||
0
compute/texture/screenshots/.gdignore
Normal file
0
compute/texture/screenshots/.gdignore
Normal file
BIN
compute/texture/screenshots/compute_texture.webp
Normal file
BIN
compute/texture/screenshots/compute_texture.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 465 KiB |
52
compute/texture/water_plane/water_compute.glsl
Normal file
52
compute/texture/water_plane/water_compute.glsl
Normal file
@@ -0,0 +1,52 @@
|
||||
#[compute]
|
||||
#version 450
|
||||
|
||||
// Invocations in the (x, y, z) dimension.
|
||||
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
|
||||
|
||||
// Our textures.
|
||||
layout(r32f, set = 0, binding = 0) uniform restrict readonly image2D current_image;
|
||||
layout(r32f, set = 1, binding = 0) uniform restrict readonly image2D previous_image;
|
||||
layout(r32f, set = 2, binding = 0) uniform restrict writeonly image2D output_image;
|
||||
|
||||
// Our push PushConstant.
|
||||
layout(push_constant, std430) uniform Params {
|
||||
vec4 add_wave_point;
|
||||
vec2 texture_size;
|
||||
float damp;
|
||||
float res2;
|
||||
} params;
|
||||
|
||||
// The code we want to execute in each invocation.
|
||||
void main() {
|
||||
ivec2 tl = ivec2(0, 0);
|
||||
ivec2 size = ivec2(params.texture_size.x - 1, params.texture_size.y - 1);
|
||||
|
||||
ivec2 uv = ivec2(gl_GlobalInvocationID.xy);
|
||||
|
||||
// Just in case the texture size is not divisable by 8.
|
||||
if ((uv.x > size.x) || (uv.y > size.y)) {
|
||||
return;
|
||||
}
|
||||
|
||||
float current_v = imageLoad(current_image, uv).r;
|
||||
float up_v = imageLoad(current_image, clamp(uv - ivec2(0, 1), tl, size)).r;
|
||||
float down_v = imageLoad(current_image, clamp(uv + ivec2(0, 1), tl, size)).r;
|
||||
float left_v = imageLoad(current_image, clamp(uv - ivec2(1, 0), tl, size)).r;
|
||||
float right_v = imageLoad(current_image, clamp(uv + ivec2(1, 0), tl, size)).r;
|
||||
float previous_v = imageLoad(previous_image, uv).r;
|
||||
|
||||
float new_v = 2.0 * current_v - previous_v + 0.25 * (up_v + down_v + left_v + right_v - 4.0 * current_v);
|
||||
new_v = new_v - (params.damp * new_v * 0.001);
|
||||
|
||||
if (params.add_wave_point.z > 0.0 && uv.x == floor(params.add_wave_point.x) && uv.y == floor(params.add_wave_point.y)) {
|
||||
new_v = params.add_wave_point.z;
|
||||
}
|
||||
|
||||
if (new_v < 0.0) {
|
||||
new_v = 0.0;
|
||||
}
|
||||
vec4 result = vec4(new_v, new_v, new_v, 1.0);
|
||||
|
||||
imageStore(output_image, uv, result);
|
||||
}
|
||||
14
compute/texture/water_plane/water_compute.glsl.import
Normal file
14
compute/texture/water_plane/water_compute.glsl.import
Normal file
@@ -0,0 +1,14 @@
|
||||
[remap]
|
||||
|
||||
importer="glsl"
|
||||
type="RDShaderFile"
|
||||
uid="uid://b6pdquh2n2jvn"
|
||||
path="res://.godot/imported/water_compute.glsl-c7fe8f11197ba28412c4cdf6f7a9a21b.res"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://water_plane/water_compute.glsl"
|
||||
dest_files=["res://.godot/imported/water_compute.glsl-c7fe8f11197ba28412c4cdf6f7a9a21b.res"]
|
||||
|
||||
[params]
|
||||
|
||||
237
compute/texture/water_plane/water_plane.gd
Normal file
237
compute/texture/water_plane/water_plane.gd
Normal file
@@ -0,0 +1,237 @@
|
||||
@tool
|
||||
extends Area3D
|
||||
|
||||
############################################################################
|
||||
# Water ripple effect shader - Bastiaan Olij
|
||||
#
|
||||
# This is an example of how to implement a more complex compute shader
|
||||
# in Godot and making use of the new Custom Texture RD API added to
|
||||
# the RenderingServer.
|
||||
#
|
||||
# If thread model is set to Multi-Threaded the code related to compute will
|
||||
# run on the render thread. This is needed as we want to add our logic to
|
||||
# the normal rendering pipeline for this thread.
|
||||
#
|
||||
# The effect itself is an implementation of the classic ripple effect
|
||||
# that has been around since the 90ies but in a compute shader.
|
||||
# If someone knows if the original author ever published a paper I could
|
||||
# quote, please let me know :)
|
||||
|
||||
@export var rain_size : float = 3.0
|
||||
@export var mouse_size : float = 5.0
|
||||
@export var texture_size : Vector2i = Vector2i(512, 512)
|
||||
@export_range(1.0, 10.0, 0.1) var damp : float = 1.0
|
||||
|
||||
var t = 0.0
|
||||
var max_t = 0.1
|
||||
|
||||
var texture : Texture2DRD
|
||||
var next_texture : int = 0
|
||||
|
||||
var add_wave_point : Vector4
|
||||
var mouse_pos : Vector2
|
||||
var mouse_pressed : bool = false
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready():
|
||||
# In case we're running stuff on the rendering thread
|
||||
# we need to do our initialisation on that thread.
|
||||
RenderingServer.call_on_render_thread(_initialize_compute_code.bind(texture_size))
|
||||
|
||||
# Get our texture from our material so we set our RID.
|
||||
var material : ShaderMaterial = $MeshInstance3D.material_override
|
||||
if material:
|
||||
material.set_shader_parameter("effect_texture_size", texture_size)
|
||||
|
||||
# Get our texture object.
|
||||
texture = material.get_shader_parameter("effect_texture")
|
||||
|
||||
|
||||
func _exit_tree():
|
||||
# Make sure we clean up!
|
||||
if texture:
|
||||
texture.texture_rd_rid = RID()
|
||||
|
||||
RenderingServer.call_on_render_thread(_free_compute_resources)
|
||||
|
||||
|
||||
func _unhandled_input(event):
|
||||
# If tool enabled, we don't want to handle our input in the editor.
|
||||
if Engine.is_editor_hint():
|
||||
return
|
||||
|
||||
if event is InputEventMouseMotion or event is InputEventMouseButton:
|
||||
mouse_pos = event.global_position
|
||||
|
||||
if event is InputEventMouseButton and event.button_index == MouseButton.MOUSE_BUTTON_LEFT:
|
||||
mouse_pressed = event.pressed
|
||||
|
||||
|
||||
func _check_mouse_pos():
|
||||
# This is a mouse event, do a raycast.
|
||||
var camera = get_viewport().get_camera_3d()
|
||||
|
||||
var parameters = PhysicsRayQueryParameters3D.new()
|
||||
parameters.from = camera.project_ray_origin(mouse_pos)
|
||||
parameters.to = parameters.from + camera.project_ray_normal(mouse_pos) * 100.0
|
||||
parameters.collision_mask = 1
|
||||
parameters.collide_with_bodies = false
|
||||
parameters.collide_with_areas = true
|
||||
|
||||
var result = get_world_3d().direct_space_state.intersect_ray(parameters)
|
||||
if result.size() > 0:
|
||||
# Transform our intersection point.
|
||||
var pos = global_transform.affine_inverse() * result.position
|
||||
add_wave_point.x = clamp(pos.x / 5.0, -0.5, 0.5) * texture_size.x + 0.5 * texture_size.x
|
||||
add_wave_point.y = clamp(pos.z / 5.0, -0.5, 0.5) * texture_size.y + 0.5 * texture_size.y
|
||||
add_wave_point.w = 1.0 # We have w left over so we use it to indicate mouse is over our water plane.
|
||||
else:
|
||||
add_wave_point.x = 0.0
|
||||
add_wave_point.y = 0.0
|
||||
add_wave_point.w = 0.0
|
||||
|
||||
|
||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
func _process(delta):
|
||||
# If tool is enabled, ignore mouse input.
|
||||
if Engine.is_editor_hint():
|
||||
add_wave_point.w = 0.0
|
||||
else:
|
||||
# Check where our mouse intersects our area, can change if things move.
|
||||
_check_mouse_pos()
|
||||
|
||||
# If we're not using the mouse, animate water drops, we (ab)used our W for this.
|
||||
if add_wave_point.w == 0.0:
|
||||
t += delta
|
||||
if t > max_t:
|
||||
t = 0
|
||||
add_wave_point.x = randi_range(0, texture_size.x)
|
||||
add_wave_point.y = randi_range(0, texture_size.y)
|
||||
add_wave_point.z = rain_size
|
||||
else:
|
||||
add_wave_point.z = 0.0
|
||||
else:
|
||||
add_wave_point.z = mouse_size if mouse_pressed else 0.0
|
||||
|
||||
# Increase our next texture index.
|
||||
next_texture = (next_texture + 1) % 3
|
||||
|
||||
# Update our texture to show our next result (we are about to create).
|
||||
# Note that `_initialize_compute_code` may not have run yet so the first
|
||||
# frame this my be an empty RID.
|
||||
if texture:
|
||||
texture.texture_rd_rid = texture_rds[next_texture]
|
||||
|
||||
# While our render_process may run on the render thread it will run before our texture
|
||||
# is used and thus our next_rd will be populated with our next result.
|
||||
# It's probably overkill to sent texture_size and damp as parameters as these are static
|
||||
# but we sent add_wave_point as it may be modified while process runs in parallel.
|
||||
RenderingServer.call_on_render_thread(_render_process.bind(next_texture, add_wave_point, texture_size, damp))
|
||||
|
||||
###############################################################################
|
||||
# Everything after this point is designed to run on our rendering thread.
|
||||
|
||||
var rd : RenderingDevice
|
||||
|
||||
var shader : RID
|
||||
var pipeline : RID
|
||||
|
||||
# We use 3 textures:
|
||||
# - One to render into
|
||||
# - One that contains the last frame rendered
|
||||
# - One for the frame before that
|
||||
var texture_rds : Array = [ RID(), RID(), RID() ]
|
||||
var texture_sets : Array = [ RID(), RID(), RID() ]
|
||||
|
||||
func _create_uniform_set(texture_rd : RID) -> RID:
|
||||
var uniform := RDUniform.new()
|
||||
uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_IMAGE
|
||||
uniform.binding = 0
|
||||
uniform.add_id(texture_rd)
|
||||
# Even though we're using 3 sets, they are identical, so we're kinda cheating.
|
||||
return rd.uniform_set_create([uniform], shader, 0)
|
||||
|
||||
|
||||
func _initialize_compute_code(init_with_texture_size):
|
||||
# As this becomes part of our normal frame rendering,
|
||||
# we use our main rendering device here.
|
||||
rd = RenderingServer.get_rendering_device()
|
||||
|
||||
# Create our shader.
|
||||
var shader_file = load("res://water_plane/water_compute.glsl")
|
||||
var shader_spirv: RDShaderSPIRV = shader_file.get_spirv()
|
||||
shader = rd.shader_create_from_spirv(shader_spirv)
|
||||
pipeline = rd.compute_pipeline_create(shader)
|
||||
|
||||
# Create our textures to manage our wave.
|
||||
var tf : RDTextureFormat = RDTextureFormat.new()
|
||||
tf.format = RenderingDevice.DATA_FORMAT_R32_SFLOAT
|
||||
tf.texture_type = RenderingDevice.TEXTURE_TYPE_2D
|
||||
tf.width = init_with_texture_size.x
|
||||
tf.height = init_with_texture_size.y
|
||||
tf.depth = 1
|
||||
tf.array_layers = 1
|
||||
tf.mipmaps = 1
|
||||
tf.usage_bits = RenderingDevice.TEXTURE_USAGE_SAMPLING_BIT + RenderingDevice.TEXTURE_USAGE_COLOR_ATTACHMENT_BIT + RenderingDevice.TEXTURE_USAGE_STORAGE_BIT + RenderingDevice.TEXTURE_USAGE_CAN_UPDATE_BIT + RenderingDevice.TEXTURE_USAGE_CAN_COPY_TO_BIT
|
||||
|
||||
for i in range(3):
|
||||
# Create our texture.
|
||||
texture_rds[i] = rd.texture_create(tf, RDTextureView.new(), [])
|
||||
|
||||
# Make sure our textures are cleared.
|
||||
rd.texture_clear(texture_rds[i], Color(0, 0, 0, 0), 0, 1, 0, 1)
|
||||
|
||||
# Now create our uniform set so we can use these textures in our shader.
|
||||
texture_sets[i] = _create_uniform_set(texture_rds[i])
|
||||
|
||||
|
||||
func _render_process(with_next_texture, wave_point, tex_size, damp):
|
||||
# We don't have structures (yet) so we need to build our push constant
|
||||
# "the hard way"...
|
||||
var push_constant : PackedFloat32Array = PackedFloat32Array()
|
||||
push_constant.push_back(wave_point.x)
|
||||
push_constant.push_back(wave_point.y)
|
||||
push_constant.push_back(wave_point.z)
|
||||
push_constant.push_back(wave_point.w)
|
||||
|
||||
push_constant.push_back(tex_size.x)
|
||||
push_constant.push_back(tex_size.y)
|
||||
push_constant.push_back(damp)
|
||||
push_constant.push_back(0.0)
|
||||
|
||||
# Calculate our dispatch group size.
|
||||
# We do `n - 1 / 8 + 1` in case our texture size is not nicely
|
||||
# divisible by 8.
|
||||
# In combination with a discard check in the shader this ensures
|
||||
# we cover the entire texture.
|
||||
var x_groups = (tex_size.x - 1) / 8 + 1
|
||||
var y_groups = (tex_size.y - 1) / 8 + 1
|
||||
|
||||
var next_set = texture_sets[with_next_texture]
|
||||
var current_set = texture_sets[(with_next_texture - 1) % 3]
|
||||
var previous_set = texture_sets[(with_next_texture - 2) % 3]
|
||||
|
||||
# Run our compute shader.
|
||||
var compute_list := rd.compute_list_begin()
|
||||
rd.compute_list_bind_compute_pipeline(compute_list, pipeline)
|
||||
rd.compute_list_bind_uniform_set(compute_list, current_set, 0)
|
||||
rd.compute_list_bind_uniform_set(compute_list, previous_set, 1)
|
||||
rd.compute_list_bind_uniform_set(compute_list, next_set, 2)
|
||||
rd.compute_list_set_push_constant(compute_list, push_constant.to_byte_array(), push_constant.size() * 4)
|
||||
rd.compute_list_dispatch(compute_list, x_groups, y_groups, 1)
|
||||
rd.compute_list_end()
|
||||
|
||||
# We don't need to sync up here, Godots default barriers will do the trick.
|
||||
# If you want the output of a compute shader to be used as input of
|
||||
# another computer shader you'll need to add a barrier:
|
||||
#rd.barrier(RenderingDevice.BARRIER_MASK_COMPUTE)
|
||||
|
||||
|
||||
func _free_compute_resources():
|
||||
# Note that our sets and pipeline are cleaned up automatically as they are dependencies :P
|
||||
for i in range(3):
|
||||
if texture_rds[i]:
|
||||
rd.free_rid(texture_rds[i])
|
||||
|
||||
if shader:
|
||||
rd.free_rid(shader)
|
||||
51
compute/texture/water_plane/water_plane.tscn
Normal file
51
compute/texture/water_plane/water_plane.tscn
Normal file
@@ -0,0 +1,51 @@
|
||||
[gd_scene load_steps=11 format=3 uid="uid://b2a5bjsxw63wr"]
|
||||
|
||||
[ext_resource type="Script" path="res://water_plane/water_plane.gd" id="1_ltm8k"]
|
||||
[ext_resource type="Shader" path="res://water_plane/water_shader.gdshader" id="1_rujqj"]
|
||||
[ext_resource type="Texture2D" uid="uid://d051ugdf65it1" path="res://assets/polyhaven/industrial_sunset_puresky_2k.hdr" id="3_fdqn0"]
|
||||
|
||||
[sub_resource type="Texture2DRD" id="Texture2DRD_gbeoi"]
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_qy6ln"]
|
||||
resource_local_to_scene = true
|
||||
render_priority = 0
|
||||
shader = ExtResource("1_rujqj")
|
||||
shader_parameter/albedo = Color(5.19812e-06, 0.748295, 0.942472, 1)
|
||||
shader_parameter/metalic = 1.0
|
||||
shader_parameter/roughness = 0.0
|
||||
shader_parameter/effect_texture_size = null
|
||||
shader_parameter/effect_texture = SubResource("Texture2DRD_gbeoi")
|
||||
|
||||
[sub_resource type="PlaneMesh" id="PlaneMesh_wl5mm"]
|
||||
size = Vector2(5, 5)
|
||||
|
||||
[sub_resource type="BoxShape3D" id="BoxShape3D_gvcbg"]
|
||||
size = Vector3(5, 0.01, 5)
|
||||
|
||||
[sub_resource type="PanoramaSkyMaterial" id="PanoramaSkyMaterial_xm1lt"]
|
||||
panorama = ExtResource("3_fdqn0")
|
||||
|
||||
[sub_resource type="Sky" id="Sky_ng08w"]
|
||||
sky_material = SubResource("PanoramaSkyMaterial_xm1lt")
|
||||
|
||||
[sub_resource type="Environment" id="Environment_iw7ig"]
|
||||
background_mode = 2
|
||||
sky = SubResource("Sky_ng08w")
|
||||
|
||||
[node name="WaterPlane" type="Area3D"]
|
||||
script = ExtResource("1_ltm8k")
|
||||
damp = 2.0
|
||||
|
||||
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
|
||||
material_override = SubResource("ShaderMaterial_qy6ln")
|
||||
mesh = SubResource("PlaneMesh_wl5mm")
|
||||
skeleton = NodePath("../..")
|
||||
|
||||
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
|
||||
shape = SubResource("BoxShape3D_gvcbg")
|
||||
|
||||
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
|
||||
transform = Transform3D(0.983217, 5.59419e-08, 0.182442, -0.178298, 0.211922, 0.960885, -0.0386633, -0.977287, 0.208365, 0, 1.12002, 0)
|
||||
|
||||
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
|
||||
environment = SubResource("Environment_iw7ig")
|
||||
33
compute/texture/water_plane/water_shader.gdshader
Normal file
33
compute/texture/water_plane/water_shader.gdshader
Normal file
@@ -0,0 +1,33 @@
|
||||
shader_type spatial;
|
||||
render_mode blend_mix, depth_draw_opaque, cull_back, diffuse_burley, specular_schlick_ggx;
|
||||
|
||||
uniform vec3 albedo : source_color;
|
||||
uniform float metalic : hint_range(0.0, 1.0, 0.1) = 0.8;
|
||||
uniform float roughness : hint_range(0.0, 1.0, 0.1) = 0.2;
|
||||
uniform sampler2D effect_texture;
|
||||
uniform vec2 effect_texture_size;
|
||||
|
||||
varying vec2 uv_tangent;
|
||||
varying vec2 uv_binormal;
|
||||
|
||||
void vertex() {
|
||||
vec2 pixel_size = vec2(1.0, 1.0) / effect_texture_size;
|
||||
|
||||
uv_tangent = UV + vec2(pixel_size.x, 0.0);
|
||||
uv_binormal = UV + vec2(0.0, pixel_size.y);
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
float f1 = texture(effect_texture, UV).r;
|
||||
float f2 = texture(effect_texture, uv_tangent).r;
|
||||
float f3 = texture(effect_texture, uv_binormal).r;
|
||||
|
||||
vec3 tangent = normalize(vec3(1.0, 0.0, f2 - f1));
|
||||
vec3 binormal = normalize(vec3(0.0, 1.0, f3 - f1));
|
||||
NORMAL_MAP = normalize(cross(binormal, tangent)) * 0.5 + 0.5;
|
||||
|
||||
ALBEDO = albedo.rgb;
|
||||
METALLIC = metalic;
|
||||
ROUGHNESS = roughness;
|
||||
SPECULAR = 0.5;
|
||||
}
|
||||
Reference in New Issue
Block a user