Compute-based pixel counting and basic ui
This commit is contained in:
+56
-17
@@ -1,13 +1,19 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://rd48gl8k5x34"]
|
||||
[gd_scene load_steps=8 format=3 uid="uid://rd48gl8k5x34"]
|
||||
|
||||
[ext_resource type="Script" path="res://Game.gd" id="1_7jqda"]
|
||||
[ext_resource type="PackedScene" uid="uid://b1nm5h3yccr16" path="res://scenes/World.tscn" id="2_voimb"]
|
||||
[ext_resource type="FontFile" uid="uid://bf6q51e8x2s1h" path="res://assets/fonts/Bangers-Regular/Bangers-Regular.ttf" id="3_52dp0"]
|
||||
[ext_resource type="Script" path="res://scenes/GameUI.gd" id="4_2tdl1"]
|
||||
|
||||
[sub_resource type="World3D" id="World3D_5g233"]
|
||||
|
||||
[sub_resource type="ViewportTexture" id="ViewportTexture_2lmj8"]
|
||||
viewport_path = NodePath("Viewport")
|
||||
|
||||
[sub_resource type="Theme" id="Theme_qtmht"]
|
||||
default_font = ExtResource("3_52dp0")
|
||||
default_font_size = 30
|
||||
|
||||
[node name="Game" type="Node"]
|
||||
script = ExtResource("1_7jqda")
|
||||
|
||||
@@ -39,28 +45,61 @@ size_flags_vertical = 4
|
||||
texture = SubResource("ViewportTexture_2lmj8")
|
||||
stretch_mode = 6
|
||||
|
||||
[node name="HSplitContainer" type="HSplitContainer" parent="."]
|
||||
[node name="TopBar" type="Panel" parent="."]
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(0, 100)
|
||||
anchors_preset = 10
|
||||
anchor_right = 1.0
|
||||
grow_horizontal = 2
|
||||
size_flags_horizontal = 4
|
||||
size_flags_vertical = 4
|
||||
|
||||
[node name="Panel2" type="Panel" parent="."]
|
||||
custom_minimum_size = Vector2(0, 80)
|
||||
anchors_preset = 10
|
||||
anchor_right = 1.0
|
||||
grow_horizontal = 2
|
||||
|
||||
[node name="GameUI" type="Control" parent="."]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_horizontal = 4
|
||||
size_flags_vertical = 4
|
||||
theme = SubResource("Theme_qtmht")
|
||||
script = ExtResource("4_2tdl1")
|
||||
world_path = NodePath("../Viewport/World")
|
||||
|
||||
[node name="WorldView" type="TextureRect" parent="HSplitContainer"]
|
||||
texture_filter = 1
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
size_flags_vertical = 4
|
||||
texture = SubResource("ViewportTexture_2lmj8")
|
||||
stretch_mode = 6
|
||||
[node name="Player1Widgets" type="VBoxContainer" parent="GameUI"]
|
||||
layout_mode = 1
|
||||
offset_right = 49.0
|
||||
offset_bottom = 77.0
|
||||
alignment = 1
|
||||
|
||||
[node name="WorldView2" type="TextureRect" parent="HSplitContainer"]
|
||||
texture_filter = 1
|
||||
[node name="NameLabel" type="Label" parent="GameUI/Player1Widgets"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
size_flags_vertical = 4
|
||||
texture = SubResource("ViewportTexture_2lmj8")
|
||||
stretch_mode = 6
|
||||
text = "Red"
|
||||
|
||||
[node name="ScoreLabel" type="Label" parent="GameUI/Player1Widgets"]
|
||||
layout_mode = 2
|
||||
text = "0
|
||||
"
|
||||
|
||||
[node name="Player2Widgets" type="VBoxContainer" parent="GameUI"]
|
||||
layout_direction = 3
|
||||
layout_mode = 0
|
||||
offset_left = 7.0
|
||||
offset_right = 56.0
|
||||
offset_bottom = 77.0
|
||||
size_flags_horizontal = 8
|
||||
alignment = 1
|
||||
|
||||
[node name="NameLabel" type="Label" parent="GameUI/Player2Widgets"]
|
||||
layout_mode = 2
|
||||
text = "Blue"
|
||||
|
||||
[node name="ScoreLabel" type="Label" parent="GameUI/Player2Widgets"]
|
||||
layout_mode = 2
|
||||
text = "0
|
||||
"
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
extends Control
|
||||
|
||||
@export var world_path : NodePath = NodePath()
|
||||
|
||||
@onready var player1_score_label : Label = $'Player1Widgets/ScoreLabel'
|
||||
@onready var player2_score_label : Label = $'Player2Widgets/ScoreLabel'
|
||||
@onready var player2_widgets : Control = $'Player2Widgets'
|
||||
|
||||
var world : World = null
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready():
|
||||
world = get_node(world_path)
|
||||
|
||||
if world.players.size() == 1:
|
||||
player2_widgets.visible = false
|
||||
|
||||
func _process(_delta):
|
||||
player1_score_label.text = str(world.players[0].score)
|
||||
|
||||
if player2_widgets.visible:
|
||||
player2_score_label.text = str(world.players[1].score)
|
||||
@@ -0,0 +1,156 @@
|
||||
# based on:
|
||||
# - https://www.youtube.com/watch?v=5CKvGYqagyI
|
||||
# - https://pastebin.com/wXCqk1nQ
|
||||
|
||||
class_name PixelCountComputeShader extends Node
|
||||
|
||||
@export var paint_viewport_path: NodePath = NodePath()
|
||||
|
||||
var red_score := 0
|
||||
var blue_score := 0
|
||||
var pink_score := 0
|
||||
|
||||
var thread: Thread
|
||||
var semaphore: Semaphore
|
||||
var mutex: Mutex
|
||||
var exit := false
|
||||
|
||||
var paint_texture: ViewportTexture
|
||||
var paint_viewport: Viewport
|
||||
|
||||
var rd: RenderingDevice
|
||||
var shader
|
||||
var pipeline
|
||||
|
||||
var pixel_count_result_buffer
|
||||
var pixel_count_uniform
|
||||
|
||||
var v_tex
|
||||
var samp
|
||||
var tex_uniform
|
||||
|
||||
var uniform_set
|
||||
|
||||
func _ready():
|
||||
paint_viewport = get_node(paint_viewport_path)
|
||||
paint_texture = paint_viewport.get_texture()
|
||||
|
||||
mutex = Mutex.new()
|
||||
semaphore = Semaphore.new()
|
||||
thread = Thread.new()
|
||||
thread.start(_thread_calculate_score)
|
||||
|
||||
# We will be using our own RenderingDevice to handle the compute commands
|
||||
rd = RenderingServer.create_local_rendering_device()
|
||||
|
||||
# Create shader and pipeline
|
||||
var shader_file = load("res://materials/shader/PixelCountCompute.glsl")
|
||||
var shader_spirv = shader_file.get_spirv()
|
||||
shader = rd.shader_create_from_spirv(shader_spirv)
|
||||
pipeline = rd.compute_pipeline_create(shader)
|
||||
|
||||
var pb = PackedInt32Array([0,0,0])
|
||||
var pbb = pb.to_byte_array()
|
||||
|
||||
pixel_count_result_buffer = rd.storage_buffer_create(pbb.size(), pbb)
|
||||
|
||||
pixel_count_uniform = RDUniform.new()
|
||||
pixel_count_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
|
||||
pixel_count_uniform.binding = 0
|
||||
pixel_count_uniform.add_id(pixel_count_result_buffer)
|
||||
|
||||
var img = paint_texture.get_image()
|
||||
var ta = img.get_data()
|
||||
|
||||
var fmt = RDTextureFormat.new()
|
||||
fmt.width = paint_texture.get_width()
|
||||
fmt.height = paint_texture.get_height()
|
||||
fmt.usage_bits = RenderingDevice.TEXTURE_USAGE_CAN_UPDATE_BIT | RenderingDevice.TEXTURE_USAGE_SAMPLING_BIT
|
||||
fmt.format = RenderingDevice.DATA_FORMAT_R8G8B8A8_SRGB
|
||||
|
||||
v_tex = rd.texture_create(fmt, RDTextureView.new(), [ta])
|
||||
var samp_state = RDSamplerState.new()
|
||||
samp_state.unnormalized_uvw = true
|
||||
samp = rd.sampler_create(samp_state)
|
||||
|
||||
tex_uniform = RDUniform.new()
|
||||
tex_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_SAMPLER_WITH_TEXTURE
|
||||
tex_uniform.binding = 1
|
||||
tex_uniform.add_id(samp)
|
||||
tex_uniform.add_id(v_tex)
|
||||
|
||||
uniform_set = rd.uniform_set_create([pixel_count_uniform, tex_uniform], shader, 0)
|
||||
|
||||
func recalculate_score():
|
||||
await RenderingServer.frame_post_draw
|
||||
semaphore.post()
|
||||
|
||||
func get_blue_score() -> int:
|
||||
var result := blue_score
|
||||
|
||||
return result
|
||||
|
||||
func get_red_score() -> int:
|
||||
var result := red_score
|
||||
|
||||
return result
|
||||
|
||||
func _thread_calculate_score():
|
||||
while true:
|
||||
semaphore.wait()
|
||||
|
||||
mutex.lock()
|
||||
var should_exit := exit
|
||||
mutex.unlock()
|
||||
|
||||
if should_exit:
|
||||
break
|
||||
|
||||
mutex.lock()
|
||||
var image := paint_texture.get_image()
|
||||
#var image := paint_viewport.get_texture()
|
||||
mutex.unlock()
|
||||
|
||||
var size = image.get_size()
|
||||
var ta = image.get_data()
|
||||
|
||||
rd.texture_update(v_tex, 0, ta)
|
||||
|
||||
# Start compute list to start recording our compute commands
|
||||
var compute_list = rd.compute_list_begin()
|
||||
# Bind the pipeline, this tells the GPU what shader to use
|
||||
rd.compute_list_bind_compute_pipeline(compute_list, pipeline)
|
||||
# Binds the uniform set with the data we want to give our shader
|
||||
rd.compute_list_bind_uniform_set(compute_list, uniform_set, 0)
|
||||
# Dispatch 1x1x1 (XxYxZ) work groups
|
||||
rd.compute_list_dispatch(compute_list, 1024/4, 1024/4, 1)
|
||||
#rd.compute_list_add_barrier(compute_list)
|
||||
# Tell the GPU we are done with this compute task
|
||||
rd.compute_list_end()
|
||||
|
||||
# Force the GPU to start our commands
|
||||
rd.submit()
|
||||
# Force the CPU to wait for the GPU to finish with the recorded commands
|
||||
rd.sync()
|
||||
|
||||
mutex.lock()
|
||||
# Now we can grab our data from the storage buffer
|
||||
var byte_data = rd.buffer_get_data(pixel_count_result_buffer)
|
||||
var output := byte_data.to_int32_array()
|
||||
|
||||
red_score = floor(output[0]/100)
|
||||
blue_score = floor(output[1]/100)
|
||||
pink_score = floor(output[2]/100)
|
||||
|
||||
var pb = PackedInt32Array([0,0,0])
|
||||
var pbb = pb.to_byte_array()
|
||||
rd.buffer_update(pixel_count_result_buffer, 0, pbb.size(), pbb)
|
||||
mutex.unlock()
|
||||
|
||||
func _exit_tree():
|
||||
mutex.lock()
|
||||
exit = true
|
||||
mutex.unlock()
|
||||
|
||||
semaphore.post()
|
||||
thread.wait_to_finish()
|
||||
@@ -0,0 +1,7 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://bqgleq3w0o5wj"]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/PixelCountComputeShader.gd" id="1_g7ep2"]
|
||||
|
||||
[node name="PixelCountComputeShader" type="Node"]
|
||||
script = ExtResource("1_g7ep2")
|
||||
paint_viewport_path = NodePath("../DrawViewport")
|
||||
+50
-8
@@ -1,27 +1,61 @@
|
||||
extends Node3D
|
||||
class_name World extends Node3D
|
||||
|
||||
@export var players: Array
|
||||
|
||||
@onready var score_update_timer: Timer = $ScoreUpdateTimer
|
||||
@onready var pixel_count_compute_shader: PixelCountComputeShader = $PixelCountComputeShader
|
||||
|
||||
@onready var camera: Camera3D = $Camera3D
|
||||
@onready var level: Node3D = $Level
|
||||
@onready var world_coloring_viewport: SubViewport = $WorldColoringViewport
|
||||
|
||||
@onready var player1: CharacterBody3D = $Player1
|
||||
@onready var player2: CharacterBody3D = $Player2
|
||||
|
||||
var camera_offset: Vector3;
|
||||
var camera_position: Vector3;
|
||||
var world_coloring_material: ShaderMaterial
|
||||
var world_plane: Plane = Plane(Vector3.UP, Vector3.ZERO)
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready():
|
||||
camera_offset = camera.global_position - player1.global_position
|
||||
|
||||
world_coloring_material = load("res://materials/WorldColoringMaterialPass.tres")
|
||||
world_coloring_material.set_shader_parameter("world_color_texture", world_coloring_viewport.get_texture())
|
||||
|
||||
var player1: CharacterBody3D = $Player1
|
||||
var player2: CharacterBody3D = $Player2
|
||||
|
||||
players.append(player1)
|
||||
players.append(player2)
|
||||
|
||||
camera_position = camera.global_position
|
||||
|
||||
score_update_timer.connect("timeout", update_score)
|
||||
|
||||
apply_world_coloring_recursive(level)
|
||||
|
||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
func _process(_delta):
|
||||
camera.global_position = player1.global_position + camera_offset
|
||||
var player1 : Player = players[0]
|
||||
var player_center = Vector3.ZERO
|
||||
var player_coords_rect : Rect2 = Rect2(
|
||||
player1.global_position.x,
|
||||
player1.global_position.z,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
|
||||
for player: CharacterBody3D in players:
|
||||
player_center += player.global_position
|
||||
player_coords_rect = player_coords_rect.expand(Vector2(player.global_position.x, player.global_position.z))
|
||||
|
||||
player_center = player_center / players.size()
|
||||
player_center.y = 0
|
||||
|
||||
var view_width = player_coords_rect.size.x + 10
|
||||
var view_height = player_coords_rect.size.y + 15
|
||||
var camera_dist = max (view_width, view_height) * 0.5 / sin(deg_to_rad(camera.fov) * 0.5)
|
||||
|
||||
var camera_direction : Vector3 = -camera.basis.z
|
||||
camera_position = player_center - camera_direction * camera_dist
|
||||
|
||||
camera.global_position = camera_position
|
||||
pass
|
||||
|
||||
func _physics_process(_delta):
|
||||
@@ -41,3 +75,11 @@ func assign_world_coloring_pass(mesh_instance: MeshInstance3D) -> void:
|
||||
return
|
||||
|
||||
material.next_pass = world_coloring_material
|
||||
|
||||
func update_score():
|
||||
pixel_count_compute_shader.recalculate_score()
|
||||
|
||||
players[0].score = pixel_count_compute_shader.red_score
|
||||
|
||||
if players.size() >= 1:
|
||||
players[1].score = pixel_count_compute_shader.blue_score
|
||||
|
||||
+30
-5
@@ -1,14 +1,22 @@
|
||||
[gd_scene load_steps=15 format=3 uid="uid://b1nm5h3yccr16"]
|
||||
[gd_scene load_steps=18 format=3 uid="uid://b1nm5h3yccr16"]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/World.gd" id="1_gtcjp"]
|
||||
[ext_resource type="PackedScene" uid="uid://bfyjtfdko3l7o" path="res://entities/Player.tscn" id="2_a343a"]
|
||||
[ext_resource type="Texture2D" uid="uid://bnsrnuuq28p4d" path="res://assets/textures/player_draw_mask.png" id="4_dipd5"]
|
||||
[ext_resource type="Material" uid="uid://bg3dawo5kkosg" path="res://materials/GrassNoiseMaterial.tres" id="4_pseda"]
|
||||
[ext_resource type="Texture2D" uid="uid://vcebfpqo2ko7" path="res://assets/textures/player_bomb_mask.png" id="5_17c1g"]
|
||||
[ext_resource type="PackedScene" path="res://scenes/PixelCountComputeShader.tscn" id="6_75vqy"]
|
||||
|
||||
[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_5gauv"]
|
||||
sky_energy_multiplier = 5.0
|
||||
|
||||
[sub_resource type="Sky" id="Sky_qfldx"]
|
||||
sky_material = SubResource("ProceduralSkyMaterial_5gauv")
|
||||
|
||||
[sub_resource type="Environment" id="Environment_f3jci"]
|
||||
background_mode = 2
|
||||
sky = SubResource("Sky_qfldx")
|
||||
ambient_light_source = 3
|
||||
ambient_light_color = Color(0.521569, 0.521569, 0.521569, 1)
|
||||
ambient_light_energy = 2.64
|
||||
|
||||
[sub_resource type="World3D" id="World3D_s7kgt"]
|
||||
@@ -37,13 +45,22 @@ size = Vector3(4, 1, 0.4)
|
||||
[node name="World" type="Node3D"]
|
||||
script = ExtResource("1_gtcjp")
|
||||
|
||||
[node name="ScoreUpdateTimer" type="Timer" parent="."]
|
||||
wait_time = 0.33
|
||||
autostart = true
|
||||
|
||||
[node name="PixelCountComputeShader" parent="." instance=ExtResource("6_75vqy")]
|
||||
paint_viewport_path = NodePath("../WorldColoringViewport")
|
||||
|
||||
[node name="Player1" parent="." instance=ExtResource("2_a343a")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.121184, 0)
|
||||
color = Color(1, 0, 0, 1)
|
||||
coloring_sprite_path = NodePath("../WorldColoringViewport/Player1Sprite")
|
||||
coloring_bomb_sprite_path = NodePath("../WorldColoringViewport/Player1BombSprite")
|
||||
|
||||
[node name="Player2" parent="." instance=ExtResource("2_a343a")]
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.63193, 0, 0)
|
||||
color = Color(0.878431, 0, 0.980392, 1)
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 3, -0.121184, 0)
|
||||
color = Color(0, 0, 1, 1)
|
||||
move_right_action = "move_right_p2"
|
||||
move_left_action = "move_left_p2"
|
||||
move_down_action = "move_down_p2"
|
||||
@@ -72,22 +89,30 @@ own_world_3d = true
|
||||
world_3d = SubResource("World3D_s7kgt")
|
||||
transparent_bg = true
|
||||
handle_input_locally = false
|
||||
size = Vector2i(1000, 1000)
|
||||
size = Vector2i(1024, 1024)
|
||||
render_target_clear_mode = 1
|
||||
render_target_update_mode = 4
|
||||
|
||||
[node name="Player1Sprite" type="Sprite2D" parent="WorldColoringViewport"]
|
||||
modulate = Color(0.117647, 0.564706, 1, 1)
|
||||
position = Vector2(500, 500)
|
||||
texture = ExtResource("4_dipd5")
|
||||
|
||||
[node name="Player1BombSprite" type="Sprite2D" parent="WorldColoringViewport"]
|
||||
visible = false
|
||||
modulate = Color(0.117647, 0.564706, 1, 1)
|
||||
position = Vector2(500, 500)
|
||||
texture = ExtResource("5_17c1g")
|
||||
|
||||
[node name="Player2Sprite" type="Sprite2D" parent="WorldColoringViewport"]
|
||||
modulate = Color(0.878431, 0, 0.980392, 1)
|
||||
position = Vector2(530, 500)
|
||||
texture = ExtResource("4_dipd5")
|
||||
|
||||
[node name="Player2BombSprite" type="Sprite2D" parent="WorldColoringViewport"]
|
||||
visible = false
|
||||
modulate = Color(0.878431, 0, 0.980392, 1)
|
||||
position = Vector2(530, 500)
|
||||
texture = ExtResource("5_17c1g")
|
||||
|
||||
[node name="Level" type="Node3D" parent="."]
|
||||
|
||||
Reference in New Issue
Block a user