diff --git a/README - Copie.md b/README - Copie.md new file mode 100644 index 0000000..28b6388 --- /dev/null +++ b/README - Copie.md @@ -0,0 +1,12 @@ +# Compute Shader Studio + A plugin for Godot to create compute shaders + +![logo](screenshots/compute_shader_studio_headline.png) + +**[Compute Shader Studio](https://virtulab.univ-brest.fr) is an addon for Godot Engine that enables you to write Compute Shaders in a minute:** + +![logo](screenshots/compute_shader_studio_ex2.png) + +**Read [the tutorial](doc/ComputeShaderStudio.pdf) or look at the [2 minutes video](https://www.youtube.com/watch?v=3bEgPawi7fQ) to make your first Compute Shader in Godot** + +**Several examples are available: test them to understand how they work and what can be done** diff --git a/examples/cells/example_cells.tscn b/examples/cells/example_cells.tscn index 6d35843..6959c31 100644 --- a/examples/cells/example_cells.tscn +++ b/examples/cells/example_cells.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=3 format=3 uid="uid://cutxgtalwsp6q"] -[ext_resource type="Script" uid="uid://bs2f5sxok3d3i" path="res://examples/cells/cells.gd" id="1_dw4h8"] +[ext_resource type="Script" path="res://examples/cells/cells.gd" id="1_dw4h8"] [ext_resource type="Texture2D" uid="uid://demftcowdd5c6" path="res://examples/icon.svg" id="2_oji6h"] [node name="Cells" type="Control" node_paths=PackedStringArray("data")] diff --git a/examples/circles/.vscode/settings.json b/examples/circles/.vscode/settings.json new file mode 100644 index 0000000..1d1a361 --- /dev/null +++ b/examples/circles/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "files.associations": { + "iostream": "cpp", + "vector": "cpp", + "cmath": "cpp" + } +} \ No newline at end of file diff --git a/examples/circles/circles.tscn b/examples/circles/circles.tscn index c66e724..6135dc6 100644 --- a/examples/circles/circles.tscn +++ b/examples/circles/circles.tscn @@ -3,7 +3,7 @@ [ext_resource type="Script" uid="uid://c8esqdv0y26yp" path="res://addons/compute_shader_studio/compute_shader_studio_2d.gd" id="1_amro2"] [ext_resource type="Texture2D" uid="uid://demftcowdd5c6" path="res://examples/icon.svg" id="2_ntl1q"] -[node name="Circles" type="Node2D"] +[node name="Circles2" type="Node2D"] [node name="ComputeShaderStudio2D" type="Node" parent="." node_paths=PackedStringArray("data")] script = ExtResource("1_amro2") @@ -27,6 +27,6 @@ horizontal_alignment = 1 vertical_alignment = 1 [node name="Icon" type="Sprite2D" parent="."] -position = Vector2(605.5, 411) +position = Vector2(592, 361) scale = Vector2(5.63281, 3.17969) texture = ExtResource("2_ntl1q") diff --git a/examples/cube2/cube.cpp b/examples/cube2/cube.cpp new file mode 100644 index 0000000..67a251d --- /dev/null +++ b/examples/cube2/cube.cpp @@ -0,0 +1,260 @@ +void main() +{ + int x = int(gl_GlobalInvocationID.x); + int y = int(gl_GlobalInvocationID.y); + int p = x + y * int(WSX); + + // Parameters for rotation + float A = step * 0.02; // Rotation X + float B = step * 0.02; // Rotation Y + float C = step * 0.005; // Rotation Z + + // Cube size + float cubeWidth = 45.0; + int width = int(WSX); + int height = int(WSY); + + // Convert pixel coordinates to normalized coordinates + float nx = (float(x) / float(width) - 0.5) * 2.0; + float ny = (float(y) / float(height) - 0.5) * 2.0; + + // Camera distance and scale factor + float distanceFromCam = 90.0; + float K1 = 70.0; + + // Drawing parameters + float incrementSpeed = 5; + float minDist = 999999.0; + int color = 0xFF000000; // Default color (black) + bool isOnCube = false; // Flag to track if the pixel is currently on the cube + + // For the first frame, initialize everything to black + if (step == 0) { + data_0[p] = 0xFF000000; + } + else { + // Fixed rotation to show 3 faces instead of using animated rotation + // This ensures we always see the 3 faces properly + float fixedA = 0.5; // Fixed angle to show 3 faces + float fixedB = 0.3; + float fixedC = 0.1; + + // Use a combination of fixed rotation and animated rotation + float rotA = fixedA + A * 0.2; + float rotB = fixedB + B * 0.2; + float rotC = fixedC + C * 0.2; + + float threshold = 0.6; + + // Calculate faces with consistent dimensions to ensure proper cube shape + for (float cubeX = -cubeWidth; cubeX < cubeWidth; cubeX += incrementSpeed) { + for (float cubeY = -cubeWidth; cubeY < cubeWidth; cubeY += incrementSpeed) { + // Face 1: Right face (X = cubeWidth) + { + float x1 = cubeWidth; + float y1 = cubeY; + float z1 = cubeX; + + // Apply rotation + float rotX1 = x1 * cos(rotB) * cos(rotC) + + z1 * sin(rotB) - + y1 * cos(rotB) * sin(rotC); + float rotY1 = x1 * (sin(rotA) * sin(rotB) * cos(rotC) + cos(rotA) * sin(rotC)) + + y1 * (cos(rotA) * cos(rotC) - sin(rotA) * sin(rotB) * sin(rotC)) - + z1 * sin(rotA) * cos(rotB); + float rotZ1 = x1 * (sin(rotA) * sin(rotC) - cos(rotA) * sin(rotB) * cos(rotC)) + + y1 * (cos(rotA) * sin(rotB) * sin(rotC) + sin(rotA) * cos(rotC)) + + z1 * cos(rotA) * cos(rotB); + + rotZ1 += distanceFromCam; + + if (rotZ1 > 0.0) { + float ooz1 = 1.0 / rotZ1; + float px1 = (width / 2.0) + K1 * ooz1 * rotX1; + float py1 = (height / 2.0) + K1 * ooz1 * rotY1; + + float dist1 = (px1 - float(x)) * (px1 - float(x)) + (py1 - float(y)) * (py1 - float(y)); + if (dist1 < threshold && dist1 < minDist) { + minDist = dist1; + color = 0xFF0000FF; // Face 1: Blue + isOnCube = true; + } + } + } + + // Face 2: Top face (Y = -cubeWidth) + { + float x2 = cubeX; + float y2 = -cubeWidth; + float z2 = cubeY; + + // Apply rotation + float rotX2 = x2 * cos(rotB) * cos(rotC) + + z2 * sin(rotB) - + y2 * cos(rotB) * sin(rotC); + float rotY2 = x2 * (sin(rotA) * sin(rotB) * cos(rotC) + cos(rotA) * sin(rotC)) + + y2 * (cos(rotA) * cos(rotC) - sin(rotA) * sin(rotB) * sin(rotC)) - + z2 * sin(rotA) * cos(rotB); + float rotZ2 = x2 * (sin(rotA) * sin(rotC) - cos(rotA) * sin(rotB) * cos(rotC)) + + y2 * (cos(rotA) * sin(rotB) * sin(rotC) + sin(rotA) * cos(rotC)) + + z2 * cos(rotA) * cos(rotB); + + rotZ2 += distanceFromCam; + + if (rotZ2 > 0.0) { + float ooz2 = 1.0 / rotZ2; + float px2 = (width / 2.0) + K1 * ooz2 * rotX2; + float py2 = (height / 2.0) + K1 * ooz2 * rotY2; + + float dist2 = (px2 - float(x)) * (px2 - float(x)) + (py2 - float(y)) * (py2 - float(y)); + if (dist2 < threshold && dist2 < minDist) { + minDist = dist2; + color = 0xFFFFFFFF; // Face 2: Green + isOnCube = true; + } + } + } + + // Face 3: Front face (Z = cubeWidth) + { + float x3 = cubeX; + float y3 = cubeY; + float z3 = cubeWidth; + + // Apply rotation + float rotX3 = x3 * cos(rotB) * cos(rotC) + + z3 * sin(rotB) - + y3 * cos(rotB) * sin(rotC); + float rotY3 = x3 * (sin(rotA) * sin(rotB) * cos(rotC) + cos(rotA) * sin(rotC)) + + y3 * (cos(rotA) * cos(rotC) - sin(rotA) * sin(rotB) * sin(rotC)) - + z3 * sin(rotA) * cos(rotB); + float rotZ3 = x3 * (sin(rotA) * sin(rotC) - cos(rotA) * sin(rotB) * cos(rotC)) + + y3 * (cos(rotA) * sin(rotB) * sin(rotC) + sin(rotA) * cos(rotC)) + + z3 * cos(rotA) * cos(rotB); + + rotZ3 += distanceFromCam; + + if (rotZ3 > 0.0) { + float ooz3 = 1.0 / rotZ3; + float px3 = (width / 2.0) + K1 * ooz3 * rotX3; + float py3 = (height / 2.0) + K1 * ooz3 * rotY3; + + float dist3 = (px3 - float(x)) * (px3 - float(x)) + (py3 - float(y)) * (py3 - float(y)); + if (dist3 < threshold && dist3 < minDist) { + minDist = dist3; + color = 0xFFFF8000; // Face 3: Orange + isOnCube = true; + } + } + } + + // Face 4: Right face (X = cubeWidth) + { + float x1 = -cubeWidth; + float y1 = cubeY; + float z1 = cubeX; + + // Apply rotation + float rotX1 = x1 * cos(rotB) * cos(rotC) + + z1 * sin(rotB) - + y1 * cos(rotB) * sin(rotC); + float rotY1 = x1 * (sin(rotA) * sin(rotB) * cos(rotC) + cos(rotA) * sin(rotC)) + + y1 * (cos(rotA) * cos(rotC) - sin(rotA) * sin(rotB) * sin(rotC)) - + z1 * sin(rotA) * cos(rotB); + float rotZ1 = x1 * (sin(rotA) * sin(rotC) - cos(rotA) * sin(rotB) * cos(rotC)) + + y1 * (cos(rotA) * sin(rotB) * sin(rotC) + sin(rotA) * cos(rotC)) + + z1 * cos(rotA) * cos(rotB); + + rotZ1 += distanceFromCam; + + if (rotZ1 > 0.0) { + float ooz1 = 1.0 / rotZ1; + float px1 = (width / 2.0) + K1 * ooz1 * rotX1; + float py1 = (height / 2.0) + K1 * ooz1 * rotY1; + + float dist1 = (px1 - float(x)) * (px1 - float(x)) + (py1 - float(y)) * (py1 - float(y)); + if (dist1 < threshold && dist1 < minDist) { + minDist = dist1; + color = 0xFF0000FF; // Face 1: Blue + isOnCube = true; + } + } + } + + // Face 5: Top face (Y = -cubeWidth) + { + float x2 = cubeX; + float y2 = cubeWidth; + float z2 = cubeY; + + // Apply rotation + float rotX2 = x2 * cos(rotB) * cos(rotC) + + z2 * sin(rotB) - + y2 * cos(rotB) * sin(rotC); + float rotY2 = x2 * (sin(rotA) * sin(rotB) * cos(rotC) + cos(rotA) * sin(rotC)) + + y2 * (cos(rotA) * cos(rotC) - sin(rotA) * sin(rotB) * sin(rotC)) - + z2 * sin(rotA) * cos(rotB); + float rotZ2 = x2 * (sin(rotA) * sin(rotC) - cos(rotA) * sin(rotB) * cos(rotC)) + + y2 * (cos(rotA) * sin(rotB) * sin(rotC) + sin(rotA) * cos(rotC)) + + z2 * cos(rotA) * cos(rotB); + + rotZ2 += distanceFromCam; + + if (rotZ2 > 0.0) { + float ooz2 = 1.0 / rotZ2; + float px2 = (width / 2.0) + K1 * ooz2 * rotX2; + float py2 = (height / 2.0) + K1 * ooz2 * rotY2; + + float dist2 = (px2 - float(x)) * (px2 - float(x)) + (py2 - float(y)) * (py2 - float(y)); + if (dist2 < threshold && dist2 < minDist) { + minDist = dist2; + color = 0xFFFFFFFF; // Face 2: Green + isOnCube = true; + } + } + } + + // Face 6: Front face (Z = cubeWidth) + { + float x3 = cubeX; + float y3 = cubeY; + float z3 = -cubeWidth; + + // Apply rotation + float rotX3 = x3 * cos(rotB) * cos(rotC) + + z3 * sin(rotB) - + y3 * cos(rotB) * sin(rotC); + float rotY3 = x3 * (sin(rotA) * sin(rotB) * cos(rotC) + cos(rotA) * sin(rotC)) + + y3 * (cos(rotA) * cos(rotC) - sin(rotA) * sin(rotB) * sin(rotC)) - + z3 * sin(rotA) * cos(rotB); + float rotZ3 = x3 * (sin(rotA) * sin(rotC) - cos(rotA) * sin(rotB) * cos(rotC)) + + y3 * (cos(rotA) * sin(rotB) * sin(rotC) + sin(rotA) * cos(rotC)) + + z3 * cos(rotA) * cos(rotB); + + rotZ3 += distanceFromCam; + + if (rotZ3 > 0.0) { + float ooz3 = 1.0 / rotZ3; + float px3 = (width / 2.0) + K1 * ooz3 * rotX3; + float py3 = (height / 2.0) + K1 * ooz3 * rotY3; + + float dist3 = (px3 - float(x)) * (px3 - float(x)) + (py3 - float(y)) * (py3 - float(y)); + if (dist3 < threshold && dist3 < minDist) { + minDist = dist3; + color = 0xFFFF8000; // Face 3: Orange + isOnCube = true; + } + } + } + } + } + + // No trailing effect + if (isOnCube) { + data_0[p] = color; + } else { + data_0[p] = 0xFF4C4C4C; + } + } +} + diff --git a/examples/cube2/cube.tscn b/examples/cube2/cube.tscn new file mode 100644 index 0000000..82301ba --- /dev/null +++ b/examples/cube2/cube.tscn @@ -0,0 +1,20 @@ +[gd_scene load_steps=3 format=3 uid="uid://bn86hxa8xefmh"] + +[ext_resource type="Script" uid="uid://c8esqdv0y26yp" path="res://addons/compute_shader_studio/compute_shader_studio_2d.gd" id="1_hegqh"] +[ext_resource type="Texture2D" uid="uid://demftcowdd5c6" path="res://examples/icon.svg" id="2_hegqh"] + +[node name="Cube" type="Node2D"] + +[node name="ComputeShaderStudio2D" type="Node" parent="." node_paths=PackedStringArray("data")] +script = ExtResource("1_hegqh") +WSX = 512 +WSY = 256 +glsl_file = "res://examples/cube2/cube.cpp" +GLSL_code = "" +data = [NodePath("../Icon")] +metadata/_custom_type_script = "uid://c8esqdv0y26yp" + +[node name="Icon" type="Sprite2D" parent="."] +position = Vector2(592, 361) +scale = Vector2(5.63281, 3.17969) +texture = ExtResource("2_hegqh") diff --git a/examples/cube3/cube.cpp b/examples/cube3/cube.cpp new file mode 100644 index 0000000..a000b51 --- /dev/null +++ b/examples/cube3/cube.cpp @@ -0,0 +1,315 @@ +void main() +{ + int x = int(gl_GlobalInvocationID.x); + int y = int(gl_GlobalInvocationID.y); + int p = x + y * int(WSX); + + // General parameters + int width = int(WSX); + int height = int(WSY); + float distanceFromCam = 150.0; + float K1 = 70.0; + + // Background color constant + int backgroundColor = 0xFF641E16; + + // For the first frame, initialize everything with background color + if (step == 0) { + data_0[p] = backgroundColor; + } + else { + // Number of cubes + const int numCubes = 3; + + float minDist = 999999.0; + int color = backgroundColor; // Default color (background) + bool isOnCube = false; // Indicator if pixel is on a cube + + // Individual parameters for each cube + float cubeWidth[numCubes] = float[](35.0, 35.0, 35.0); + + // Space cubes horizontally and add more lateral movement + vec3 cubePositions[numCubes] = vec3[]( + // Cube 1: rotated 90 up + vec3(-100.0 + sin(step * 0.010) * 50.0, + sin(step * 0.006) * 15.0, + cos(step * 0.015) * 40.0), + + // Cube 2: rotated 90 up + vec3(0.0 + sin(step * 0.011) * 60.0, + sin(step * 0.009) * 20.0, + cos(step * 0.007) * 45.0), + + // Cube 3: rotated 90 up + vec3(100.0 + sin(step * 0.008) * 55.0, + sin(step * 0.011) * 25.0, + cos(step * 0.014) * 35.0) + ); + + + + // Different rotations for each cube - reduced for smoother movement + vec3 cubeRotations[numCubes] = vec3[]( + vec3(step * 0.008, step * 0.009, step * 0.007), + vec3(step * 0.007, step * 0.010, step * 0.009), + vec3(step * 0.006, step * 0.011, step * 0.008) + ); + + int cubeColors[numCubes][6] = int[3][]( + int[](0xFF0000FF, 0xFF00FF00, 0xFFFF8000, 0xFF0080FF, 0xFFFF00FF, 0xFF80FF00), // Cube 1 + int[](0xFFFF0000, 0xFF00FFFF, 0xFFFF00FF, 0xFF8000FF, 0xFF00FF80, 0xFFFFFF00), // Cube 2 + int[](0xFFFFFF00, 0xFF8000FF, 0xFF00FF80, 0xFFFF0000, 0xFF00FFFF, 0xFFFF8000) // Cube 3 + ); + + // IMPROVED drawing parameters + float incrementSpeed = 5.0; // Much smaller for better density + float threshold = 0.3; // Larger for slightly overlapping points + + // Process each cube + for (int cubeIndex = 0; cubeIndex < numCubes; cubeIndex++) { + // Fixed + animated rotation + float fixedA = 0.5; // Fixed angle to show 3 faces + float fixedB = 0.3; + float fixedC = 0.1; + + vec3 rot = cubeRotations[cubeIndex]; + float rotA = fixedA + rot.x * 0.2; + float rotB = fixedB + rot.y * 0.2; + float rotC = fixedC + rot.z * 0.2; + + // Cube position + vec3 cubePos = cubePositions[cubeIndex]; + float currentCubeWidth = cubeWidth[cubeIndex]; + + // Calculate all 6 cube faces for complete representation + for (float cubeX = -currentCubeWidth; cubeX <= currentCubeWidth; cubeX += incrementSpeed) { + for (float cubeY = -currentCubeWidth; cubeY <= currentCubeWidth; cubeY += incrementSpeed) { + // Face 1: Right face (X = currentCubeWidth) + { + float x1 = currentCubeWidth; + float y1 = cubeY; + float z1 = cubeX; + + x1 += cubePos.x; + y1 += cubePos.y; + z1 += cubePos.z; + + // Apply rotation + float rotX1 = x1 * cos(rotB) * cos(rotC) + + z1 * sin(rotB) - + y1 * cos(rotB) * sin(rotC); + float rotY1 = x1 * (sin(rotA) * sin(rotB) * cos(rotC) + cos(rotA) * sin(rotC)) + + y1 * (cos(rotA) * cos(rotC) - sin(rotA) * sin(rotB) * sin(rotC)) - + z1 * sin(rotA) * cos(rotB); + float rotZ1 = x1 * (sin(rotA) * sin(rotC) - cos(rotA) * sin(rotB) * cos(rotC)) + + y1 * (cos(rotA) * sin(rotB) * sin(rotC) + sin(rotA) * cos(rotC)) + + z1 * cos(rotA) * cos(rotB); + + rotZ1 += distanceFromCam; + + if (rotZ1 > 0.0) { + float ooz1 = 1.0 / rotZ1; + float px1 = (width / 2.0) + K1 * ooz1 * rotX1; + float py1 = (height / 2.0) + K1 * ooz1 * rotY1; + + float dist1 = (px1 - float(x)) * (px1 - float(x)) + (py1 - float(y)) * (py1 - float(y)); + if (dist1 < threshold && dist1 < minDist) { + minDist = dist1; + color = cubeColors[cubeIndex][0]; // Face 1 color + isOnCube = true; + } + } + } + + // Face 2: Left face (X = -currentCubeWidth) + { + float x1 = -currentCubeWidth; + float y1 = cubeY; + float z1 = cubeX; + + x1 += cubePos.x; + y1 += cubePos.y; + z1 += cubePos.z; + + // Apply rotation + float rotX1 = x1 * cos(rotB) * cos(rotC) + + z1 * sin(rotB) - + y1 * cos(rotB) * sin(rotC); + float rotY1 = x1 * (sin(rotA) * sin(rotB) * cos(rotC) + cos(rotA) * sin(rotC)) + + y1 * (cos(rotA) * cos(rotC) - sin(rotA) * sin(rotB) * sin(rotC)) - + z1 * sin(rotA) * cos(rotB); + float rotZ1 = x1 * (sin(rotA) * sin(rotC) - cos(rotA) * sin(rotB) * cos(rotC)) + + y1 * (cos(rotA) * sin(rotB) * sin(rotC) + sin(rotA) * cos(rotC)) + + z1 * cos(rotA) * cos(rotB); + + rotZ1 += distanceFromCam; + + if (rotZ1 > 0.0) { + float ooz1 = 1.0 / rotZ1; + float px1 = (width / 2.0) + K1 * ooz1 * rotX1; + float py1 = (height / 2.0) + K1 * ooz1 * rotY1; + + float dist1 = (px1 - float(x)) * (px1 - float(x)) + (py1 - float(y)) * (py1 - float(y)); + if (dist1 < threshold && dist1 < minDist) { + minDist = dist1; + color = cubeColors[cubeIndex][1]; // Face 2 color + isOnCube = true; + } + } + } + + // Face 3: Top face (Y = -currentCubeWidth) + { + float x2 = cubeX; + float y2 = -currentCubeWidth; + float z2 = cubeY; + + x2 += cubePos.x; + y2 += cubePos.y; + z2 += cubePos.z; + + // Apply rotation + float rotX2 = x2 * cos(rotB) * cos(rotC) + + z2 * sin(rotB) - + y2 * cos(rotB) * sin(rotC); + float rotY2 = x2 * (sin(rotA) * sin(rotB) * cos(rotC) + cos(rotA) * sin(rotC)) + + y2 * (cos(rotA) * cos(rotC) - sin(rotA) * sin(rotB) * sin(rotC)) - + z2 * sin(rotA) * cos(rotB); + float rotZ2 = x2 * (sin(rotA) * sin(rotC) - cos(rotA) * sin(rotB) * cos(rotC)) + + y2 * (cos(rotA) * sin(rotB) * sin(rotC) + sin(rotA) * cos(rotC)) + + z2 * cos(rotA) * cos(rotB); + + rotZ2 += distanceFromCam; + + if (rotZ2 > 0.0) { + float ooz2 = 1.0 / rotZ2; + float px2 = (width / 2.0) + K1 * ooz2 * rotX2; + float py2 = (height / 2.0) + K1 * ooz2 * rotY2; + + float dist2 = (px2 - float(x)) * (px2 - float(x)) + (py2 - float(y)) * (py2 - float(y)); + if (dist2 < threshold && dist2 < minDist) { + minDist = dist2; + color = cubeColors[cubeIndex][2]; // Face 3 color + isOnCube = true; + } + } + } + + // Face 4: Bottom face (Y = currentCubeWidth) + { + float x2 = cubeX; + float y2 = currentCubeWidth; + float z2 = cubeY; + + x2 += cubePos.x; + y2 += cubePos.y; + z2 += cubePos.z; + + // Apply rotation + float rotX2 = x2 * cos(rotB) * cos(rotC) + + z2 * sin(rotB) - + y2 * cos(rotB) * sin(rotC); + float rotY2 = x2 * (sin(rotA) * sin(rotB) * cos(rotC) + cos(rotA) * sin(rotC)) + + y2 * (cos(rotA) * cos(rotC) - sin(rotA) * sin(rotB) * sin(rotC)) - + z2 * sin(rotA) * cos(rotB); + float rotZ2 = x2 * (sin(rotA) * sin(rotC) - cos(rotA) * sin(rotB) * cos(rotC)) + + y2 * (cos(rotA) * sin(rotB) * sin(rotC) + sin(rotA) * cos(rotC)) + + z2 * cos(rotA) * cos(rotB); + + rotZ2 += distanceFromCam; + + if (rotZ2 > 0.0) { + float ooz2 = 1.0 / rotZ2; + float px2 = (width / 2.0) + K1 * ooz2 * rotX2; + float py2 = (height / 2.0) + K1 * ooz2 * rotY2; + + float dist2 = (px2 - float(x)) * (px2 - float(x)) + (py2 - float(y)) * (py2 - float(y)); + if (dist2 < threshold && dist2 < minDist) { + minDist = dist2; + color = cubeColors[cubeIndex][3]; // Face 4 color + isOnCube = true; + } + } + } + + // Face 5: Front face (Z = currentCubeWidth) + { + float x3 = cubeX; + float y3 = cubeY; + float z3 = currentCubeWidth; + + x3 += cubePos.x; + y3 += cubePos.y; + z3 += cubePos.z; + + // Apply rotation + float rotX3 = x3 * cos(rotB) * cos(rotC) + + z3 * sin(rotB) - + y3 * cos(rotB) * sin(rotC); + float rotY3 = x3 * (sin(rotA) * sin(rotB) * cos(rotC) + cos(rotA) * sin(rotC)) + + y3 * (cos(rotA) * cos(rotC) - sin(rotA) * sin(rotB) * sin(rotC)) - + z3 * sin(rotA) * cos(rotB); + float rotZ3 = x3 * (sin(rotA) * sin(rotC) - cos(rotA) * sin(rotB) * cos(rotC)) + + y3 * (cos(rotA) * sin(rotB) * sin(rotC) + sin(rotA) * cos(rotC)) + + z3 * cos(rotA) * cos(rotB); + + rotZ3 += distanceFromCam; + + if (rotZ3 > 0.0) { + float ooz3 = 1.0 / rotZ3; + float px3 = (width / 2.0) + K1 * ooz3 * rotX3; + float py3 = (height / 2.0) + K1 * ooz3 * rotY3; + + float dist3 = (px3 - float(x)) * (px3 - float(x)) + (py3 - float(y)) * (py3 - float(y)); + if (dist3 < threshold && dist3 < minDist) { + minDist = dist3; + color = cubeColors[cubeIndex][4]; // Face 5 color + isOnCube = true; + } + } + } + + // Face 6: Back face (Z = -currentCubeWidth) + { + float x3 = cubeX; + float y3 = cubeY; + float z3 = -currentCubeWidth; + + x3 += cubePos.x; + y3 += cubePos.y; + z3 += cubePos.z; + + // Apply rotation + float rotX3 = x3 * cos(rotB) * cos(rotC) + + z3 * sin(rotB) - + y3 * cos(rotB) * sin(rotC); + float rotY3 = x3 * (sin(rotA) * sin(rotB) * cos(rotC) + cos(rotA) * sin(rotC)) + + y3 * (cos(rotA) * cos(rotC) - sin(rotA) * sin(rotB) * sin(rotC)) - + z3 * sin(rotA) * cos(rotB); + float rotZ3 = x3 * (sin(rotA) * sin(rotC) - cos(rotA) * sin(rotB) * cos(rotC)) + + y3 * (cos(rotA) * sin(rotB) * sin(rotC) + sin(rotA) * cos(rotC)) + + z3 * cos(rotA) * cos(rotB); + + rotZ3 += distanceFromCam; + + if (rotZ3 > 0.0) { + float ooz3 = 1.0 / rotZ3; + float px3 = (width / 2.0) + K1 * ooz3 * rotX3; + float py3 = (height / 2.0) + K1 * ooz3 * rotY3; + + float dist3 = (px3 - float(x)) * (px3 - float(x)) + (py3 - float(y)) * (py3 - float(y)); + if (dist3 < threshold && dist3 < minDist) { + minDist = dist3; + color = cubeColors[cubeIndex][5]; // Face 6 color + isOnCube = true; + } + } + } + } + } + } + + // Apply color to current pixel + data_0[p] = isOnCube ? color : backgroundColor; + } +} \ No newline at end of file diff --git a/examples/cube3/cube.tscn b/examples/cube3/cube.tscn new file mode 100644 index 0000000..456a329 --- /dev/null +++ b/examples/cube3/cube.tscn @@ -0,0 +1,32 @@ +[gd_scene load_steps=3 format=3 uid="uid://e5gixrwap30u"] + +[ext_resource type="Script" uid="uid://c8esqdv0y26yp" path="res://addons/compute_shader_studio/compute_shader_studio_2d.gd" id="1_t2x41"] +[ext_resource type="Texture2D" uid="uid://demftcowdd5c6" path="res://examples/icon.svg" id="2_woahi"] + +[node name="Circles" type="Node2D"] + +[node name="ComputeShaderStudio2D" type="Node" parent="." node_paths=PackedStringArray("data")] +script = ExtResource("1_t2x41") +WSX = 512 +WSY = 256 +glsl_file = "res://examples/cube3/cube.cpp" +GLSL_code = "" +data = [NodePath("../Icon")] +metadata/_custom_type_script = "uid://c8esqdv0y26yp" + +[node name="Label" type="Label" parent="."] +offset_left = 278.0 +offset_top = -1.0 +offset_right = 679.0 +offset_bottom = 101.0 +scale = Vector2(1.72, 1.72) +text = "Compute Shader Studio - Example Circles +- uses an external GLSL file +- and a Sprite2D" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="Icon" type="Sprite2D" parent="."] +position = Vector2(575, 323.5) +scale = Vector2(8.95313, 5.03906) +texture = ExtResource("2_woahi") diff --git a/examples/example_1.tscn b/examples/example_1.tscn index 4d271f5..9b4fac1 100644 --- a/examples/example_1.tscn +++ b/examples/example_1.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=5 format=3 uid="uid://ddr6qtwy1pesd"] -[ext_resource type="Script" uid="uid://c8esqdv0y26yp" path="res://addons/compute_shader_studio/compute_shader_studio_2d.gd" id="1_6846p"] +[ext_resource type="Script" path="res://addons/compute_shader_studio/compute_shader_studio_2d.gd" id="1_6846p"] [ext_resource type="Texture2D" uid="uid://demftcowdd5c6" path="res://examples/icon.svg" id="2_s3fct"] [sub_resource type="FastNoiseLite" id="FastNoiseLite_dmk8h"] diff --git a/examples/example_2.tscn b/examples/example_2.tscn index a843389..766e825 100644 --- a/examples/example_2.tscn +++ b/examples/example_2.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=3 format=3 uid="uid://cotswmubn7uvm"] -[ext_resource type="Script" uid="uid://c8esqdv0y26yp" path="res://addons/compute_shader_studio/compute_shader_studio_2d.gd" id="1_2cs40"] +[ext_resource type="Script" path="res://addons/compute_shader_studio/compute_shader_studio_2d.gd" id="1_2cs40"] [ext_resource type="Texture2D" uid="uid://demftcowdd5c6" path="res://examples/icon.svg" id="2_j6qu8"] [node name="Example2" type="Node2D"] diff --git a/examples/example_3.tscn b/examples/example_3.tscn index 8d0b838..25329f1 100644 --- a/examples/example_3.tscn +++ b/examples/example_3.tscn @@ -1,7 +1,7 @@ [gd_scene load_steps=4 format=3 uid="uid://cmucgsppcoo5e"] -[ext_resource type="Script" uid="uid://c8esqdv0y26yp" path="res://addons/compute_shader_studio/compute_shader_studio_2d.gd" id="1_eimw3"] -[ext_resource type="Script" uid="uid://jw3o2qu3a0pl" path="res://examples/LabelStepPass.gd" id="2_4aq4t"] +[ext_resource type="Script" path="res://addons/compute_shader_studio/compute_shader_studio_2d.gd" id="1_eimw3"] +[ext_resource type="Script" path="res://examples/LabelStepPass.gd" id="2_4aq4t"] [ext_resource type="Texture2D" uid="uid://demftcowdd5c6" path="res://examples/icon.svg" id="2_4upxj"] [node name="CompShadStudioEx3" type="Node2D"] diff --git a/examples/example_4.tscn b/examples/example_4.tscn index 1d7e26f..c777972 100644 --- a/examples/example_4.tscn +++ b/examples/example_4.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=3 format=3 uid="uid://b2ip5eftk3aij"] -[ext_resource type="Script" uid="uid://c8esqdv0y26yp" path="res://addons/compute_shader_studio/compute_shader_studio_2d.gd" id="1_ke3fj"] +[ext_resource type="Script" path="res://addons/compute_shader_studio/compute_shader_studio_2d.gd" id="1_ke3fj"] [ext_resource type="Texture2D" uid="uid://2behgeplwycn" path="res://examples/grid_512x512.png" id="2_flqlh"] [node name="CompShadStudioEx4" type="Node2D"] diff --git a/examples/example_5.tscn b/examples/example_5.tscn index b1d6e72..43aa420 100644 --- a/examples/example_5.tscn +++ b/examples/example_5.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=4 format=3 uid="uid://cets11mlsd8hb"] -[ext_resource type="Script" uid="uid://c8esqdv0y26yp" path="res://addons/compute_shader_studio/compute_shader_studio_2d.gd" id="1_cogap"] +[ext_resource type="Script" path="res://addons/compute_shader_studio/compute_shader_studio_2d.gd" id="1_cogap"] [sub_resource type="FastNoiseLite" id="FastNoiseLite_pm7bb"] diff --git a/examples/example_lenia_test.tscn b/examples/example_lenia_test.tscn index 118ec4d..6c2caef 100644 --- a/examples/example_lenia_test.tscn +++ b/examples/example_lenia_test.tscn @@ -1,7 +1,7 @@ [gd_scene load_steps=4 format=3 uid="uid://cljj33x767odp"] -[ext_resource type="Script" uid="uid://c8esqdv0y26yp" path="res://addons/compute_shader_studio/compute_shader_studio_2d.gd" id="1_wci4f"] -[ext_resource type="Script" uid="uid://jw3o2qu3a0pl" path="res://examples/LabelStepPass.gd" id="2_ndkd3"] +[ext_resource type="Script" path="res://addons/compute_shader_studio/compute_shader_studio_2d.gd" id="1_wci4f"] +[ext_resource type="Script" path="res://examples/LabelStepPass.gd" id="2_ndkd3"] [ext_resource type="Texture2D" uid="uid://demftcowdd5c6" path="res://examples/icon.svg" id="3_orsvs"] [node name="CompShadStudioEx3" type="Node2D"] diff --git a/examples/example_mandelbrot.gd b/examples/example_mandelbrot.gd new file mode 100644 index 0000000..506717b --- /dev/null +++ b/examples/example_mandelbrot.gd @@ -0,0 +1,39 @@ +extends Node + +var css : Node +var zoom : float = 1.0 +var pos : Vector2 = Vector2(0, 0) +var grab : Vector2 +var grabbing : bool = false + +func screen_to_world(screen : Vector2): + var pos_data0 : Vector2 = css.screen_to_data0(screen) + var wsize : Vector2 = Vector2(css.WSX, css.WSY); + return pos + (pos_data0 / wsize - Vector2(0.5, 0.5)) * 4.0 / zoom; + +func _ready(): + css = get_node("ComputeShaderStudio2D") + # Augmenter la performance + css.material.set_shader_parameter("num_iterations", 1) + +func _process(_delta): + # Pas besoin de passer des uniformes + pass + +func _input(event): + if event is InputEventMouseButton: + if event.button_index == MOUSE_BUTTON_WHEEL_UP: + zoom *= 1.1 + elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN: + zoom /= 1.1 + elif event.button_index == MOUSE_BUTTON_LEFT: + if event.pressed: # Start panning + grab = screen_to_world(event.position) + grabbing = true + else: # Stop panning + grabbing = false + + elif event is InputEventMouseMotion and grabbing: # Panning + var world_pos = screen_to_world(event.position) + var delta = world_pos - grab + pos -= delta diff --git a/examples/example_mandelbrot.gd.uid b/examples/example_mandelbrot.gd.uid new file mode 100644 index 0000000..d22223e --- /dev/null +++ b/examples/example_mandelbrot.gd.uid @@ -0,0 +1 @@ +uid://bib00xk6p8oo4 diff --git a/examples/mandelbrot/example_mandelbrot.tscn b/examples/mandelbrot/example_mandelbrot.tscn index 4f2951f..ca05987 100644 --- a/examples/mandelbrot/example_mandelbrot.tscn +++ b/examples/mandelbrot/example_mandelbrot.tscn @@ -1,5 +1,6 @@ [gd_scene load_steps=3 format=3 uid="uid://d1hhshnvrt86e"] +[ext_resource type="Script" uid="uid://bib00xk6p8oo4" path="res://examples/example_mandelbrot.gd" id="1_5gp20"] [ext_resource type="Script" uid="uid://c8esqdv0y26yp" path="res://addons/compute_shader_studio/compute_shader_studio_2d.gd" id="1_t5cx1"] [ext_resource type="Texture2D" uid="uid://demftcowdd5c6" path="res://examples/icon.svg" id="2_dyrad"] @@ -12,8 +13,7 @@ print_passes = true print_generated_code = true WSX = 512 WSY = 512 -glsl_file = "res://examples/mandelbrot/example_mandelbrot.cpp" -GLSL_code = "" +glsl_file = "res://examples/example_mandelbrot.cpp" data = [NodePath("../mandelbrot"), NodePath("../julia")] [node name="mandelbrot" type="Sprite2D" parent="."] diff --git a/examples/simple_circle/simple_circle.cpp b/examples/simple_circle/simple_circle.cpp index 40bea38..d971317 100644 --- a/examples/simple_circle/simple_circle.cpp +++ b/examples/simple_circle/simple_circle.cpp @@ -1,26 +1,172 @@ -void main() { - - uint x = gl_GlobalInvocationID.x; - uint y = gl_GlobalInvocationID.y; - uint p = x + y*WSX; - - float dx = float(mousex) - float(x) ; - float dy = float(mousey) - float(y) ; - float dist = sqrt( dx*dx + dy*dy ); - - if (dist < 10) { - if(mouse_button == 0) // No mouse button - data_0[p] = 0xFF00FF00; // Green circle - else if(mouse_button == 1) // Left mouse button - data_0[p] = 0xFFFFFF00; // Cyan circle - else if(mouse_button == 2) // Right mouse button - data_0[p] = 0xFF00FFFF; // Yellow circle - else if(mouse_button == 3) // Both mouse button - data_0[p] = 0xFFFFFFFF; // White circle - else if(mouse_button == 4) // Middle mouse button - data_0[p] = 0xFF000000; // Black circle - } else { - data_0[p] = int(0xFF0000FF - y) ; // Background +void main() +{ + int x = int(gl_GlobalInvocationID.x); + int y = int(gl_GlobalInvocationID.y); + int p = x + y * int(WSX); + + // Parameters for rotation + float A = step * 0.05; // Rotation X + float B = step * 0.05; // Rotation Y + float C = step * 0.01; // Rotation Z + + float cubeWidth = 20.0; + int width = int(WSX); + int height = int(WSY); + + // Convert pixel coordinates to normalized coordinates + float nx = (float(x) / float(width) - 0.5) * 2.0; + float ny = (float(y) / float(height) - 0.5) * 2.0; + + // Camera distance and scale factor + float distanceFromCam = 100.0; + float K1 = 40.0; + + // Drawing parameters + float incrementSpeed = 0.6; + float minDist = 999999.0; + int color = 0xFF000000; // Default color (black) + + // Initialize background on first step + if (step == 0) { + data_0[p] = 0xFF000000; + } + else { + // For each face of the cube, check if the current pixel is on this face + for (float cubeX = -cubeWidth; cubeX < cubeWidth; cubeX += incrementSpeed) { + for (float cubeY = -cubeWidth; cubeY < cubeWidth; cubeY += incrementSpeed) { + // Calculate 3D coordinates for different faces of the cube + // Face 1: Z = -cubeWidth + float x1 = cubeX * sin(A) * sin(B) * cos(C) - (-cubeWidth) * cos(A) * sin(B) * cos(C) + + cubeX * cos(A) * sin(C) + (-cubeWidth) * sin(A) * sin(C) + cubeY * cos(B) * cos(C); + float y1 = cubeX * cos(A) * cos(C) + (-cubeWidth) * sin(A) * cos(C) - + cubeX * sin(A) * sin(B) * sin(C) + (-cubeWidth) * cos(A) * sin(B) * sin(C) - + cubeY * cos(B) * sin(C); + float z1 = (-cubeWidth) * cos(A) * cos(B) - cubeX * sin(A) * cos(B) + cubeY * sin(B) + distanceFromCam; + + if (z1 > 0.0) { + float ooz1 = 1.0 / z1; + float px1 = (width / 2.0) + K1 * ooz1 * x1 * 2.0; + float py1 = (height / 2.0) + K1 * ooz1 * y1; + + float dist1 = (px1 - float(x)) * (px1 - float(x)) + (py1 - float(y)) * (py1 - float(y)); + if (dist1 < 1.0 && dist1 < minDist) { + minDist = dist1; + color = 0xFF0000FF; // Face 1: Red + } + } + + // Face 2: X = cubeWidth + float x2 = cubeY * sin(A) * sin(B) * cos(C) - cubeWidth * cos(A) * sin(B) * cos(C) + + cubeY * cos(A) * sin(C) + cubeWidth * sin(A) * sin(C) + cubeWidth * cos(B) * cos(C); + float y2 = cubeY * cos(A) * cos(C) + cubeWidth * sin(A) * cos(C) - + cubeY * sin(A) * sin(B) * sin(C) + cubeWidth * cos(A) * sin(B) * sin(C) - + cubeWidth * cos(B) * sin(C); + float z2 = cubeWidth * cos(A) * cos(B) - cubeY * sin(A) * cos(B) + cubeWidth * sin(B) + distanceFromCam; + + if (z2 > 0.0) { + float ooz2 = 1.0 / z2; + float px2 = (width / 2.0) + K1 * ooz2 * x2 * 2.0; + float py2 = (height / 2.0) + K1 * ooz2 * y2; + + float dist2 = (px2 - float(x)) * (px2 - float(x)) + (py2 - float(y)) * (py2 - float(y)); + if (dist2 < 1.0 && dist2 < minDist) { + minDist = dist2; + color = 0xFF00FF00; // Face 2: Green + } + } + + // Face 3: X = -cubeWidth + float x3 = cubeY * sin(A) * sin(B) * cos(C) - (-cubeWidth) * cos(A) * sin(B) * cos(C) + + cubeY * cos(A) * sin(C) + (-cubeWidth) * sin(A) * sin(C) + (-cubeWidth) * cos(B) * cos(C); + float y3 = cubeY * cos(A) * cos(C) + (-cubeWidth) * sin(A) * cos(C) - + cubeY * sin(A) * sin(B) * sin(C) + (-cubeWidth) * cos(A) * sin(B) * sin(C) - + (-cubeWidth) * cos(B) * sin(C); + float z3 = (-cubeWidth) * cos(A) * cos(B) - cubeY * sin(A) * cos(B) + (-cubeWidth) * sin(B) + distanceFromCam; + + if (z3 > 0.0) { + float ooz3 = 1.0 / z3; + float px3 = (width / 2.0) + K1 * ooz3 * x3 * 2.0; + float py3 = (height / 2.0) + K1 * ooz3 * y3; + + float dist3 = (px3 - float(x)) * (px3 - float(x)) + (py3 - float(y)) * (py3 - float(y)); + if (dist3 < 1.0 && dist3 < minDist) { + minDist = dist3; + color = 0xFF0080FF; // Face 3: Light blue + } + } + + // Face 4: Z = cubeWidth + float x4 = cubeX * sin(A) * sin(B) * cos(C) - cubeWidth * cos(A) * sin(B) * cos(C) + + cubeX * cos(A) * sin(C) + cubeWidth * sin(A) * sin(C) + cubeY * cos(B) * cos(C); + float y4 = cubeX * cos(A) * cos(C) + cubeWidth * sin(A) * cos(C) - + cubeX * sin(A) * sin(B) * sin(C) + cubeWidth * cos(A) * sin(B) * sin(C) - + cubeY * cos(B) * sin(C); + float z4 = cubeWidth * cos(A) * cos(B) - cubeX * sin(A) * cos(B) + cubeY * sin(B) + distanceFromCam; + + if (z4 > 0.0) { + float ooz4 = 1.0 / z4; + float px4 = (width / 2.0) + K1 * ooz4 * x4 * 2.0; + float py4 = (height / 2.0) + K1 * ooz4 * y4; + + float dist4 = (px4 - float(x)) * (px4 - float(x)) + (py4 - float(y)) * (py4 - float(y)); + if (dist4 < 1.0 && dist4 < minDist) { + minDist = dist4; + color = 0xFFFF00FF; // Face 4: Magenta + } + } + + // Face 5: Y = -cubeWidth + float x5 = cubeX * sin(A) * sin(B) * cos(C) - cubeY * cos(A) * sin(B) * cos(C) + + cubeX * cos(A) * sin(C) + cubeY * sin(A) * sin(C) + (-cubeWidth) * cos(B) * cos(C); + float y5 = cubeX * cos(A) * cos(C) + cubeY * sin(A) * cos(C) - + cubeX * sin(A) * sin(B) * sin(C) + cubeY * cos(A) * sin(B) * sin(C) - + (-cubeWidth) * cos(B) * sin(C); + float z5 = cubeY * cos(A) * cos(B) - cubeX * sin(A) * cos(B) + (-cubeWidth) * sin(B) + distanceFromCam; + + if (z5 > 0.0) { + float ooz5 = 1.0 / z5; + float px5 = (width / 2.0) + K1 * ooz5 * x5 * 2.0; + float py5 = (height / 2.0) + K1 * ooz5 * y5; + + float dist5 = (px5 - float(x)) * (px5 - float(x)) + (py5 - float(y)) * (py5 - float(y)); + if (dist5 < 1.0 && dist5 < minDist) { + minDist = dist5; + color = 0xFFFFFF00; // Face 5: Yellow + } + } + + // Face 6: Y = cubeWidth + float x6 = cubeX * sin(A) * sin(B) * cos(C) - cubeY * cos(A) * sin(B) * cos(C) + + cubeX * cos(A) * sin(C) + cubeY * sin(A) * sin(C) + cubeWidth * cos(B) * cos(C); + float y6 = cubeX * cos(A) * cos(C) + cubeY * sin(A) * cos(C) - + cubeX * sin(A) * sin(B) * sin(C) + cubeY * cos(A) * sin(B) * sin(C) - + cubeWidth * cos(B) * sin(C); + float z6 = cubeY * cos(A) * cos(B) - cubeX * sin(A) * cos(B) + cubeWidth * sin(B) + distanceFromCam; + + if (z6 > 0.0) { + float ooz6 = 1.0 / z6; + float px6 = (width / 2.0) + K1 * ooz6 * x6 * 2.0; + float py6 = (height / 2.0) + K1 * ooz6 * y6; + + float dist6 = (px6 - float(x)) * (px6 - float(x)) + (py6 - float(y)) * (py6 - float(y)); + if (dist6 < 1.0 && dist6 < minDist) { + minDist = dist6; + color = 0xFF00FFFF; // Face 6: Cyan + } + } + } + } + + // Update the pixel color if we found a close face + if (minDist < 999999.0) { + data_0[p] = color; + } else { + // Gradually darken the background + int pix = data_0[p]; + int col = pix & 0x00FFFFFF; + if (col > 0) { + data_0[p] = pix - 1; + } + } } - -} +} \ No newline at end of file diff --git a/examples/simple_circle/simple_circle.tscn b/examples/simple_circle/simple_circle.tscn index b5037a7..820945f 100644 --- a/examples/simple_circle/simple_circle.tscn +++ b/examples/simple_circle/simple_circle.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=3 format=3 uid="uid://c63rtjh0eurgm"] -[ext_resource type="Script" uid="uid://c8esqdv0y26yp" path="res://addons/compute_shader_studio/compute_shader_studio_2d.gd" id="1_rlapi"] +[ext_resource type="Script" path="res://addons/compute_shader_studio/compute_shader_studio_2d.gd" id="1_rlapi"] [ext_resource type="Texture2D" uid="uid://demftcowdd5c6" path="res://examples/icon.svg" id="2_y7slp"] [node name="SimpleCircle" type="Node2D"] @@ -10,7 +10,6 @@ script = ExtResource("1_rlapi") WSX = 256 glsl_file = "res://examples/simple_circle/simple_circle.cpp" data = [NodePath("../Icon")] -metadata/_custom_type_script = "uid://c8esqdv0y26yp" [node name="Icon" type="Sprite2D" parent="."] position = Vector2(622, 407) diff --git a/project.godot b/project.godot index 1617345..3cbff81 100644 --- a/project.godot +++ b/project.godot @@ -11,9 +11,10 @@ config_version=5 [application] config/name="compute_shader_studio" -run/main_scene="res://examples/example_1.tscn" +run/main_scene="uid://e5gixrwap30u" config/features=PackedStringArray("4.4", "Forward Plus") config/icon="res://addons/compute_shader_studio/icon.png" +<<<<<<