浏览代码

Add a 3D procedural materials demo

Hugo Locurcio 2 年之前
父节点
当前提交
6860a1e814
共有 31 个文件被更改,包括 1076 次插入0 次删除
  1. 37 0
      3d/procedural_materials/README.md
  2. 二进制
      3d/procedural_materials/checker.png
  3. 35 0
      3d/procedural_materials/checker.png.import
  4. 二进制
      3d/procedural_materials/icon.webp
  5. 34 0
      3d/procedural_materials/icon.webp.import
  6. 13 0
      3d/procedural_materials/loading.gd
  7. 30 0
      3d/procedural_materials/loading.tscn
  8. 10 0
      3d/procedural_materials/materials/grass.tres
  9. 42 0
      3d/procedural_materials/materials/ice.tres
  10. 57 0
      3d/procedural_materials/materials/lava.tres
  11. 30 0
      3d/procedural_materials/materials/marble.tres
  12. 4 0
      3d/procedural_materials/materials/pixel_art.tres
  13. 10 0
      3d/procedural_materials/materials/sand.tres
  14. 20 0
      3d/procedural_materials/materials/textures/grass_albedo.tres
  15. 19 0
      3d/procedural_materials/materials/textures/grass_normal.tres
  16. 19 0
      3d/procedural_materials/materials/textures/sand_albedo.tres
  17. 13 0
      3d/procedural_materials/materials/textures/sand_normal.tres
  18. 19 0
      3d/procedural_materials/materials/textures/trypophobia_albedo.tres
  19. 21 0
      3d/procedural_materials/materials/textures/trypophobia_normal.tres
  20. 19 0
      3d/procedural_materials/materials/textures/trypophobia_roughness.tres
  21. 16 0
      3d/procedural_materials/materials/textures/wet_concrete_albedo.tres
  22. 13 0
      3d/procedural_materials/materials/textures/wet_concrete_orm.tres
  23. 12 0
      3d/procedural_materials/materials/trypophobia.tres
  24. 9 0
      3d/procedural_materials/materials/wet_concrete.tres
  25. 27 0
      3d/procedural_materials/project.godot
  26. 0 0
      3d/procedural_materials/screenshots/.gdignore
  27. 二进制
      3d/procedural_materials/screenshots/procedural_materials.webp
  28. 37 0
      3d/procedural_materials/scripts/grid.gd
  29. 12 0
      3d/procedural_materials/shaders/plasma.gdshader
  30. 448 0
      3d/procedural_materials/test.tscn
  31. 70 0
      3d/procedural_materials/tester.gd

+ 37 - 0
3d/procedural_materials/README.md

@@ -0,0 +1,37 @@
+# Procedural Materials
+
+This demo includes procedurally generated materials with 3 different techniques:
+
+- **[NoiseTexture2D](https://docs.godotengine.org/en/stable/classes/class_noisetexture2d.html):**
+  Built-in class that generates images on the CPU based on
+  noise patterns (such as Simplex or Cellular). This is suited for static
+  textures only. Texture generation is done asynchronously and is faster than
+  using scripting, since the noise algorithms are implemented in C++ in the
+  engine.
+
+- **Scripting:** Uses the
+  [Image](https://docs.godotengine.org/en/stable/classes/class_image.html) class
+  to procedurally generate an
+  [ImageTexture](https://docs.godotengine.org/en/stable/classes/class_imagetexture.html)
+  on the CPU. This is suited for static textures only. This approach is more
+  flexible than NoiseTexture2D, but is slower to generate textures. Once the
+  texture is generated, rendering performance is identical to NoiseTexture2D.
+
+- **Shaders:** Uses a 2D shader on a
+  [ColorRect](https://docs.godotengine.org/en/stable/classes/class_colorrect.html)
+  node that matches a
+  [Viewport](https://docs.godotengine.org/en/stable/classes/class_viewport.html)'s
+  size with the resulting
+  [ViewportTexture](https://docs.godotengine.org/en/stable/classes/class_viewporttexture.html)
+  applied to a material. This is updated on the GPU in real-time, and is most
+  suited for animated textures. This approach can also be used for static
+  textures, with a lower performance overhead since the texture doesn't need to
+  be updated every frame.
+
+Language: GDScript
+
+Renderer: Forward Plus
+
+## Screenshots
+
+![Screenshot](screenshots/procedural_materials.webp)

二进制
3d/procedural_materials/checker.png


+ 35 - 0
3d/procedural_materials/checker.png.import

@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://chjqieyps5n5r"
+path.s3tc="res://.godot/imported/checker.png-6bb199bedbd039461e4248c1d0b9691d.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://checker.png"
+dest_files=["res://.godot/imported/checker.png-6bb199bedbd039461e4248c1d0b9691d.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=true
+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

二进制
3d/procedural_materials/icon.webp


+ 34 - 0
3d/procedural_materials/icon.webp.import

@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ny54l3w66vw3"
+path="res://.godot/imported/icon.webp-e94f9a68b0f625a567a797079e4d325f.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://icon.webp"
+dest_files=["res://.godot/imported/icon.webp-e94f9a68b0f625a567a797079e4d325f.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

+ 13 - 0
3d/procedural_materials/loading.gd

@@ -0,0 +1,13 @@
+# This acts as a staging scene shown until the main scene is fully loaded.
+extends Control
+
+
+func _ready():
+	for i in 2:
+		# Wait 2 frames before starting to change to the main scene,
+		# so that the loading text can be shown instead of the splash screen.
+		await get_tree().process_frame
+
+	# Do not use `preload()` to avoid incurring the loading time before the
+	# loading text can be shown.
+	get_tree().change_scene_to_packed(load("res://test.tscn"))

+ 30 - 0
3d/procedural_materials/loading.tscn

@@ -0,0 +1,30 @@
+[gd_scene load_steps=2 format=3 uid="uid://b1odr115brm8m"]
+
+[ext_resource type="Script" path="res://loading.gd" id="1_anktk"]
+
+[node name="Loading" type="ColorRect"]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+color = Color(0.494118, 0.580392, 0.686275, 1)
+script = ExtResource("1_anktk")
+
+[node name="Label" type="Label" parent="."]
+layout_mode = 1
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+offset_left = -74.5
+offset_top = -24.0
+offset_right = 74.5
+offset_bottom = 24.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
+theme_override_constants/outline_size = 6
+theme_override_font_sizes/font_size = 32
+text = "Generating textures, please wait…"

+ 10 - 0
3d/procedural_materials/materials/grass.tres

@@ -0,0 +1,10 @@
+[gd_resource type="StandardMaterial3D" load_steps=3 format=3 uid="uid://chsncadr63hoc"]
+
+[ext_resource type="Texture2D" uid="uid://1adlmerrq6tb" path="res://materials/textures/grass_albedo.tres" id="1_m5akc"]
+[ext_resource type="Texture2D" uid="uid://b7c28bnw0u27e" path="res://materials/textures/grass_normal.tres" id="2_spkki"]
+
+[resource]
+albedo_texture = ExtResource("1_m5akc")
+normal_enabled = true
+normal_texture = ExtResource("2_spkki")
+texture_filter = 5

+ 42 - 0
3d/procedural_materials/materials/ice.tres

@@ -0,0 +1,42 @@
+[gd_resource type="StandardMaterial3D" load_steps=6 format=3 uid="uid://cbijbaq5qol7n"]
+
+[sub_resource type="Gradient" id="Gradient_16ij7"]
+colors = PackedColorArray(0.6, 0.8, 1, 1, 1, 1, 1, 1)
+
+[sub_resource type="FastNoiseLite" id="FastNoiseLite_qbhty"]
+frequency = 0.003
+fractal_type = 2
+fractal_lacunarity = 2.5
+
+[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_m8iqd"]
+width = 1024
+height = 1024
+seamless = true
+color_ramp = SubResource("Gradient_16ij7")
+noise = SubResource("FastNoiseLite_qbhty")
+
+[sub_resource type="FastNoiseLite" id="FastNoiseLite_5tmlw"]
+frequency = 0.003
+fractal_type = 2
+fractal_octaves = 10
+fractal_lacunarity = 1.342
+fractal_gain = 0.776
+fractal_weighted_strength = 0.04
+
+[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_gyhec"]
+width = 1024
+height = 1024
+seamless = true
+as_normal_map = true
+bump_strength = 4.0
+noise = SubResource("FastNoiseLite_5tmlw")
+
+[resource]
+albedo_texture = SubResource("NoiseTexture2D_m8iqd")
+metallic_specular = 1.0
+roughness = 0.0
+normal_enabled = true
+normal_texture = SubResource("NoiseTexture2D_gyhec")
+backlight_enabled = true
+backlight = Color(0.3, 0.4, 0.5, 1)
+texture_filter = 5

+ 57 - 0
3d/procedural_materials/materials/lava.tres

@@ -0,0 +1,57 @@
+[gd_resource type="StandardMaterial3D" load_steps=7 format=3 uid="uid://b74tdcluvrao6"]
+
+[sub_resource type="Gradient" id="Gradient_f6rvr"]
+offsets = PackedFloat32Array(0.00813008, 0.28628, 0.358839, 0.53562, 0.699187, 0.766491, 1)
+colors = PackedColorArray(0, 0, 0, 1, 0.436461, 0.0783241, 0.09101, 1, 0.601977, 0, 0.175375, 1, 1, 0, 0.25, 1, 1, 0.716667, 0, 1, 0.91, 0.761973, 0.3549, 1, 1, 1, 1, 1)
+
+[sub_resource type="FastNoiseLite" id="FastNoiseLite_cl4ii"]
+noise_type = 2
+frequency = 0.005
+fractal_type = 3
+fractal_octaves = 1
+cellular_return_type = 6
+domain_warp_enabled = true
+domain_warp_type = 2
+domain_warp_amplitude = 100.0
+domain_warp_frequency = 0.01
+domain_warp_fractal_octaves = 10
+domain_warp_fractal_lacunarity = 2.204
+domain_warp_fractal_gain = 0.6
+
+[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_itinc"]
+width = 1024
+height = 1024
+seamless = true
+color_ramp = SubResource("Gradient_f6rvr")
+noise = SubResource("FastNoiseLite_cl4ii")
+
+[sub_resource type="Gradient" id="Gradient_t5fqa"]
+offsets = PackedFloat32Array(0.444591, 0.502639, 0.699187, 0.766491, 1)
+colors = PackedColorArray(0, 0, 0, 1, 1, 0, 0.25, 1, 1, 0.716667, 0, 1, 0.91, 0.761973, 0.3549, 1, 1, 1, 1, 1)
+
+[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_fuepi"]
+width = 1024
+height = 1024
+seamless = true
+color_ramp = SubResource("Gradient_t5fqa")
+noise = SubResource("FastNoiseLite_cl4ii")
+
+[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_w1lhv"]
+width = 1024
+height = 1024
+seamless = true
+color_ramp = SubResource("Gradient_f6rvr")
+noise = SubResource("FastNoiseLite_cl4ii")
+
+[resource]
+albedo_texture = SubResource("NoiseTexture2D_itinc")
+emission_enabled = true
+emission_texture = SubResource("NoiseTexture2D_fuepi")
+heightmap_enabled = true
+heightmap_scale = 2.0
+heightmap_deep_parallax = true
+heightmap_min_layers = 8
+heightmap_max_layers = 32
+heightmap_texture = SubResource("NoiseTexture2D_w1lhv")
+heightmap_flip_texture = true
+texture_filter = 5

+ 30 - 0
3d/procedural_materials/materials/marble.tres

@@ -0,0 +1,30 @@
+[gd_resource type="StandardMaterial3D" load_steps=4 format=3 uid="uid://c5wve1c3hypfo"]
+
+[sub_resource type="Gradient" id="Gradient_57yod"]
+offsets = PackedFloat32Array(0, 0.51049, 0.671329, 1)
+colors = PackedColorArray(0.0235294, 0.0235294, 0.0392157, 1, 0.113725, 0.113725, 0.164706, 1, 0.128, 0.145067, 0.16, 1, 1, 1, 1, 1)
+
+[sub_resource type="FastNoiseLite" id="FastNoiseLite_vdicb"]
+frequency = 0.006
+fractal_type = 2
+fractal_octaves = 10
+fractal_lacunarity = 3.0
+fractal_gain = 0.6
+domain_warp_amplitude = 15.0
+domain_warp_frequency = 0.03
+domain_warp_fractal_octaves = 10
+
+[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_oc5fq"]
+width = 1024
+height = 1024
+seamless = true
+color_ramp = SubResource("Gradient_57yod")
+noise = SubResource("FastNoiseLite_vdicb")
+
+[resource]
+albedo_texture = SubResource("NoiseTexture2D_oc5fq")
+roughness = 0.0
+rim_enabled = true
+rim_tint = 1.0
+clearcoat_roughness = 0.0
+texture_filter = 5

+ 4 - 0
3d/procedural_materials/materials/pixel_art.tres

@@ -0,0 +1,4 @@
+[gd_resource type="StandardMaterial3D" format=3 uid="uid://d0yum6cvd2cyb"]
+
+[resource]
+texture_filter = 4

+ 10 - 0
3d/procedural_materials/materials/sand.tres

@@ -0,0 +1,10 @@
+[gd_resource type="StandardMaterial3D" load_steps=3 format=3 uid="uid://3odjrwml2nes"]
+
+[ext_resource type="Texture2D" uid="uid://caf47v17px0e" path="res://materials/textures/sand_albedo.tres" id="1_kw214"]
+[ext_resource type="Texture2D" uid="uid://2u8c47a4cd5" path="res://materials/textures/sand_normal.tres" id="2_52v72"]
+
+[resource]
+albedo_texture = ExtResource("1_kw214")
+normal_enabled = true
+normal_texture = ExtResource("2_52v72")
+texture_filter = 5

+ 20 - 0
3d/procedural_materials/materials/textures/grass_albedo.tres

@@ -0,0 +1,20 @@
+[gd_resource type="NoiseTexture2D" load_steps=3 format=3 uid="uid://1adlmerrq6tb"]
+
+[sub_resource type="Gradient" id="Gradient_8sl6j"]
+colors = PackedColorArray(0, 0.21, 0, 1, 0.5418, 0.72, 0.324, 1)
+
+[sub_resource type="FastNoiseLite" id="FastNoiseLite_ryabi"]
+noise_type = 4
+frequency = 0.005
+fractal_type = 3
+fractal_octaves = 9
+fractal_lacunarity = 10.896
+fractal_gain = 1.0
+fractal_ping_pong_strength = 2.1
+
+[resource]
+width = 1024
+height = 1024
+seamless = true
+color_ramp = SubResource("Gradient_8sl6j")
+noise = SubResource("FastNoiseLite_ryabi")

+ 19 - 0
3d/procedural_materials/materials/textures/grass_normal.tres

@@ -0,0 +1,19 @@
+[gd_resource type="NoiseTexture2D" load_steps=3 format=3 uid="uid://b7c28bnw0u27e"]
+
+[sub_resource type="Gradient" id="Gradient_8sl6j"]
+colors = PackedColorArray(0, 0.243137, 0, 1, 0.486275, 0.756863, 0.14902, 1)
+
+[sub_resource type="FastNoiseLite" id="FastNoiseLite_7ucal"]
+noise_type = 4
+frequency = 0.005
+fractal_type = 2
+fractal_lacunarity = 2.416
+fractal_ping_pong_strength = 2.1
+
+[resource]
+width = 1024
+height = 1024
+seamless = true
+as_normal_map = true
+color_ramp = SubResource("Gradient_8sl6j")
+noise = SubResource("FastNoiseLite_7ucal")

+ 19 - 0
3d/procedural_materials/materials/textures/sand_albedo.tres

@@ -0,0 +1,19 @@
+[gd_resource type="NoiseTexture2D" load_steps=3 format=3 uid="uid://caf47v17px0e"]
+
+[sub_resource type="Gradient" id="Gradient_pheaa"]
+offsets = PackedFloat32Array(0.0243902, 1)
+colors = PackedColorArray(1, 0.743333, 0.45, 1, 1, 0.874, 0.73, 1)
+
+[sub_resource type="FastNoiseLite" id="FastNoiseLite_nwsqr"]
+noise_type = 3
+fractal_octaves = 8
+domain_warp_enabled = true
+domain_warp_type = 1
+domain_warp_fractal_gain = 1.0
+
+[resource]
+width = 1024
+height = 1024
+seamless = true
+color_ramp = SubResource("Gradient_pheaa")
+noise = SubResource("FastNoiseLite_nwsqr")

+ 13 - 0
3d/procedural_materials/materials/textures/sand_normal.tres

@@ -0,0 +1,13 @@
+[gd_resource type="NoiseTexture2D" load_steps=2 format=3 uid="uid://2u8c47a4cd5"]
+
+[sub_resource type="FastNoiseLite" id="FastNoiseLite_qi0cj"]
+fractal_octaves = 3
+fractal_lacunarity = 1.612
+
+[resource]
+width = 1024
+height = 1024
+seamless = true
+as_normal_map = true
+bump_strength = 16.0
+noise = SubResource("FastNoiseLite_qi0cj")

+ 19 - 0
3d/procedural_materials/materials/textures/trypophobia_albedo.tres

@@ -0,0 +1,19 @@
+[gd_resource type="NoiseTexture2D" load_steps=3 format=3 uid="uid://dw4mslgkir1bd"]
+
+[sub_resource type="Gradient" id="Gradient_d5vv8"]
+offsets = PackedFloat32Array(0.00813008, 0.341463, 0.674797, 1)
+colors = PackedColorArray(0.05, 0.05, 0.05, 1, 0.510312, 0.415082, 0.239094, 1, 0.95, 0.799267, 0.627, 1, 1, 1, 1, 1)
+
+[sub_resource type="FastNoiseLite" id="FastNoiseLite_65rwj"]
+noise_type = 2
+frequency = 0.005
+fractal_type = 3
+fractal_octaves = 9
+fractal_ping_pong_strength = 5.84
+
+[resource]
+width = 1024
+height = 1024
+seamless = true
+color_ramp = SubResource("Gradient_d5vv8")
+noise = SubResource("FastNoiseLite_65rwj")

+ 21 - 0
3d/procedural_materials/materials/textures/trypophobia_normal.tres

@@ -0,0 +1,21 @@
+[gd_resource type="NoiseTexture2D" load_steps=3 format=3 uid="uid://dd5i28wnuc2cs"]
+
+[sub_resource type="Gradient" id="Gradient_3xmi8"]
+offsets = PackedFloat32Array(0.00813008, 0.341463, 0.674797, 1)
+colors = PackedColorArray(0.05, 0.05, 0.05, 1, 0.43, 0.43, 0.43, 1, 0.89, 0.89, 0.89, 1, 1, 1, 1, 1)
+
+[sub_resource type="FastNoiseLite" id="FastNoiseLite_6p36u"]
+noise_type = 2
+frequency = 0.005
+fractal_type = 3
+fractal_octaves = 9
+fractal_ping_pong_strength = 5.84
+
+[resource]
+width = 1024
+height = 1024
+seamless = true
+as_normal_map = true
+bump_strength = 3.0
+color_ramp = SubResource("Gradient_3xmi8")
+noise = SubResource("FastNoiseLite_6p36u")

+ 19 - 0
3d/procedural_materials/materials/textures/trypophobia_roughness.tres

@@ -0,0 +1,19 @@
+[gd_resource type="NoiseTexture2D" load_steps=3 format=3 uid="uid://muqmqaqefsrm"]
+
+[sub_resource type="Gradient" id="Gradient_2rffn"]
+offsets = PackedFloat32Array(0.00813008, 0.341463, 0.674797, 1)
+colors = PackedColorArray(0, 0, 0, 1, 0, 0, 0, 1, 0.89, 0.89, 0.89, 1, 1, 1, 1, 1)
+
+[sub_resource type="FastNoiseLite" id="FastNoiseLite_65rwj"]
+noise_type = 2
+frequency = 0.005
+fractal_type = 3
+fractal_octaves = 9
+fractal_ping_pong_strength = 5.84
+
+[resource]
+width = 1024
+height = 1024
+seamless = true
+color_ramp = SubResource("Gradient_2rffn")
+noise = SubResource("FastNoiseLite_65rwj")

+ 16 - 0
3d/procedural_materials/materials/textures/wet_concrete_albedo.tres

@@ -0,0 +1,16 @@
+[gd_resource type="NoiseTexture2D" load_steps=3 format=3 uid="uid://ch81ib0h0gfqg"]
+
+[sub_resource type="Gradient" id="Gradient_8ut3h"]
+colors = PackedColorArray(0.55, 0.55, 0.55, 1, 0.683077, 0.683077, 0.683077, 1)
+
+[sub_resource type="FastNoiseLite" id="FastNoiseLite_bgo3j"]
+noise_type = 0
+frequency = 0.08
+domain_warp_enabled = true
+
+[resource]
+width = 1024
+height = 1024
+seamless = true
+color_ramp = SubResource("Gradient_8ut3h")
+noise = SubResource("FastNoiseLite_bgo3j")

+ 13 - 0
3d/procedural_materials/materials/textures/wet_concrete_orm.tres

@@ -0,0 +1,13 @@
+[gd_resource type="NoiseTexture2D" load_steps=3 format=3 uid="uid://bm0om4im5p1da"]
+
+[sub_resource type="Gradient" id="Gradient_ac24k"]
+colors = PackedColorArray(0, 0.258824, 0, 1, 1, 1, 1, 1)
+
+[sub_resource type="FastNoiseLite" id="FastNoiseLite_t6fbr"]
+
+[resource]
+width = 1024
+height = 1024
+seamless = true
+color_ramp = SubResource("Gradient_ac24k")
+noise = SubResource("FastNoiseLite_t6fbr")

+ 12 - 0
3d/procedural_materials/materials/trypophobia.tres

@@ -0,0 +1,12 @@
+[gd_resource type="StandardMaterial3D" load_steps=4 format=3 uid="uid://b2588u4jkfiog"]
+
+[ext_resource type="Texture2D" uid="uid://dw4mslgkir1bd" path="res://materials/textures/trypophobia_albedo.tres" id="1_1gqoj"]
+[ext_resource type="Texture2D" uid="uid://dd5i28wnuc2cs" path="res://materials/textures/trypophobia_normal.tres" id="2_0h02x"]
+[ext_resource type="Texture2D" uid="uid://muqmqaqefsrm" path="res://materials/textures/trypophobia_roughness.tres" id="2_2fvnl"]
+
+[resource]
+albedo_texture = ExtResource("1_1gqoj")
+roughness_texture = ExtResource("2_2fvnl")
+normal_enabled = true
+normal_texture = ExtResource("2_0h02x")
+texture_filter = 5

+ 9 - 0
3d/procedural_materials/materials/wet_concrete.tres

@@ -0,0 +1,9 @@
+[gd_resource type="ORMMaterial3D" load_steps=3 format=3 uid="uid://cw85kxowmkj37"]
+
+[ext_resource type="Texture2D" uid="uid://ch81ib0h0gfqg" path="res://materials/textures/wet_concrete_albedo.tres" id="1_or44f"]
+[ext_resource type="Texture2D" uid="uid://bm0om4im5p1da" path="res://materials/textures/wet_concrete_orm.tres" id="2_k25x3"]
+
+[resource]
+albedo_texture = ExtResource("1_or44f")
+orm_texture = ExtResource("2_k25x3")
+ao_enabled = true

+ 27 - 0
3d/procedural_materials/project.godot

@@ -0,0 +1,27 @@
+; 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="Procedural Materials"
+run/main_scene="res://loading.tscn"
+config/features=PackedStringArray("4.0")
+config/icon="res://icon.webp"
+
+[display]
+
+window/stretch/mode="canvas_items"
+window/stretch/aspect="expand"
+
+[rendering]
+
+textures/default_filters/anisotropic_filtering_level=4
+environment/defaults/default_clear_color=Color(0.301961, 0.301961, 0.301961, 1)
+anti_aliasing/quality/msaa_3d=2

+ 0 - 0
3d/procedural_materials/screenshots/.gdignore


二进制
3d/procedural_materials/screenshots/procedural_materials.webp


+ 37 - 0
3d/procedural_materials/scripts/grid.gd

@@ -0,0 +1,37 @@
+# This script creates the ImageTexture and assigns it to an existing material at runtime.
+# By not having `@tool`, this avoids saving the raw image data in the scene file,
+# which would make it much larger.
+extends MeshInstance3D
+
+const TEXTURE_SIZE = Vector2i(512, 512)
+const GRID_SIZE = 32
+const GRID_THICKNESS = 4
+
+func _ready() -> void:
+	var image := Image.create(TEXTURE_SIZE.x, TEXTURE_SIZE.y, false, Image.FORMAT_RGB8)
+	# Use 1-dimensional loop as it's faster than a nested loop.
+	for i in TEXTURE_SIZE.x * TEXTURE_SIZE.y:
+		var x := i % TEXTURE_SIZE.y
+		var y := i / TEXTURE_SIZE.y
+		var color := Color()
+
+		# Draw a grid with more contrasted points where X and Y lines meet.
+		# Center the grid's lines so that lines are visible on all the texture's edges.
+		if (x + GRID_THICKNESS / 2) % GRID_SIZE < GRID_THICKNESS and (y + GRID_THICKNESS / 2) % GRID_SIZE < GRID_THICKNESS:
+			color.g = 0.8
+		elif (x + GRID_THICKNESS / 2) % GRID_SIZE < GRID_THICKNESS or (y + GRID_THICKNESS / 2) % GRID_SIZE < GRID_THICKNESS:
+			color.g = 0.25
+
+		# Add some random noise for detail.
+		color += Color(randf(), randf(), randf()) * 0.1
+
+		image.set_pixel(x, y, color)
+
+	image.generate_mipmaps()
+	var image_texture := ImageTexture.create_from_image(image)
+	get_surface_override_material(0).albedo_texture = image_texture
+
+	image.bump_map_to_normal_map(5.0)
+	image.generate_mipmaps()
+	var image_texture_normal := ImageTexture.create_from_image(image)
+	get_surface_override_material(0).normal_texture = image_texture_normal

+ 12 - 0
3d/procedural_materials/shaders/plasma.gdshader

@@ -0,0 +1,12 @@
+shader_type canvas_item;
+
+uniform sampler2D noise1 : repeat_enable;
+uniform sampler2D noise2 : repeat_enable;
+
+const float SCROLL_SPEED = 0.04;
+
+void fragment() {
+	vec2 noise1_uv = UV + vec2(SCROLL_SPEED) * TIME;
+	COLOR = texture(noise1, noise1_uv);
+	COLOR.rgb += 0.3 * texture(noise2, UV * 4.0 - vec2(texture(noise1, noise1_uv * 0.3).xy * 0.015) * TIME * 2.0).r;
+}

+ 448 - 0
3d/procedural_materials/test.tscn

@@ -0,0 +1,448 @@
+[gd_scene load_steps=40 format=3 uid="uid://c4i1xdk0nc7s0"]
+
+[ext_resource type="Material" uid="uid://b2588u4jkfiog" path="res://materials/trypophobia.tres" id="3_322sv"]
+[ext_resource type="Material" uid="uid://chsncadr63hoc" path="res://materials/grass.tres" id="4_gplko"]
+[ext_resource type="Material" uid="uid://3odjrwml2nes" path="res://materials/sand.tres" id="5_12jug"]
+[ext_resource type="Material" uid="uid://cw85kxowmkj37" path="res://materials/wet_concrete.tres" id="6_q1c2f"]
+[ext_resource type="Material" uid="uid://c5wve1c3hypfo" path="res://materials/marble.tres" id="7_ebvqo"]
+[ext_resource type="Material" uid="uid://cbijbaq5qol7n" path="res://materials/ice.tres" id="7_k1q1v"]
+[ext_resource type="Material" uid="uid://b74tdcluvrao6" path="res://materials/lava.tres" id="8_jphcw"]
+[ext_resource type="Script" path="res://scripts/grid.gd" id="10_wwlcf"]
+[ext_resource type="Shader" path="res://shaders/plasma.gdshader" id="11_aoush"]
+[ext_resource type="Texture2D" uid="uid://chjqieyps5n5r" path="res://checker.png" id="14"]
+[ext_resource type="Script" path="res://tester.gd" id="18"]
+
+[sub_resource type="ProceduralSkyMaterial" id="9"]
+
+[sub_resource type="Sky" id="10"]
+sky_material = SubResource("9")
+
+[sub_resource type="Environment" id="11"]
+background_mode = 2
+sky = SubResource("10")
+tonemap_mode = 3
+tonemap_white = 6.0
+
+[sub_resource type="StandardMaterial3D" id="13"]
+diffuse_mode = 1
+albedo_texture = ExtResource("14")
+uv1_scale = Vector3(32, 32, 1)
+texture_filter = 5
+
+[sub_resource type="PlaneMesh" id="14"]
+material = SubResource("13")
+size = Vector2(128, 128)
+
+[sub_resource type="Animation" id="Animation_2qwoj"]
+length = 0.001
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Testers/NoiseTextureLava/MeshInstance3D:surface_material_override/0:uv1_offset")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0),
+"transitions": PackedFloat32Array(1),
+"update": 0,
+"values": [Vector3(0, 0, 0)]
+}
+
+[sub_resource type="Animation" id="Animation_nbv0n"]
+resource_name = "animate_textures"
+length = 300.0
+loop_mode = 1
+tracks/0/type = "value"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Testers/NoiseTextureLava/MeshInstance3D:surface_material_override/0:uv1_offset")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 300),
+"transitions": PackedFloat32Array(1, 1),
+"update": 0,
+"values": [Vector3(0, 0, 0), Vector3(4, 1, 0)]
+}
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_sin37"]
+_data = {
+"RESET": SubResource("Animation_2qwoj"),
+"animate_textures": SubResource("Animation_nbv0n")
+}
+
+[sub_resource type="SphereMesh" id="SphereMesh_be5u0"]
+radius = 0.75
+height = 1.5
+
+[sub_resource type="BoxMesh" id="BoxMesh_opf1g"]
+size = Vector3(3, 1, 4)
+
+[sub_resource type="Gradient" id="Gradient_iy4y2"]
+offsets = PackedFloat32Array(0, 0.0211082, 0.0435356, 0.0633245, 1)
+colors = PackedColorArray(0, 0, 0, 1, 0.733005, 0.733005, 0.733005, 1, 0.0461741, 0.0461741, 0.0461741, 1, 0.72, 0.69192, 0.6264, 1, 1, 1, 1, 1)
+
+[sub_resource type="FastNoiseLite" id="FastNoiseLite_mc7b4"]
+noise_type = 2
+frequency = 0.042
+fractal_type = 0
+fractal_octaves = 3
+fractal_gain = 1.596
+fractal_weighted_strength = 0.44
+cellular_jitter = 0.03
+cellular_return_type = 4
+domain_warp_enabled = true
+domain_warp_type = 2
+domain_warp_amplitude = 2.0
+domain_warp_fractal_type = 2
+
+[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_e1o5w"]
+width = 1024
+height = 1024
+seamless = true
+seamless_blend_skirt = 0.0
+color_ramp = SubResource("Gradient_iy4y2")
+noise = SubResource("FastNoiseLite_mc7b4")
+
+[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_71qgk"]
+width = 1024
+height = 1024
+seamless = true
+seamless_blend_skirt = 0.065
+as_normal_map = true
+bump_strength = 2.0
+color_ramp = SubResource("Gradient_iy4y2")
+noise = SubResource("FastNoiseLite_mc7b4")
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_aq2a1"]
+albedo_texture = SubResource("NoiseTexture2D_e1o5w")
+roughness = 0.5
+roughness_texture = SubResource("NoiseTexture2D_e1o5w")
+normal_enabled = true
+normal_texture = SubResource("NoiseTexture2D_71qgk")
+texture_filter = 5
+
+[sub_resource type="Gradient" id="Gradient_5l3re"]
+interpolation_mode = 2
+offsets = PackedFloat32Array(0, 0.135884, 0.329815, 0.341689, 0.544855, 0.581794, 0.770449, 0.943272)
+colors = PackedColorArray(0, 0, 0, 1, 0.111922, 0.139008, 0.140144, 1, 0.398808, 0.12884, 0.147843, 1, 0.421438, 0.289437, 0.227878, 1, 0.65045, 0.360142, 0.181692, 1, 0.739192, 0.503788, 0.301212, 1, 1, 0.672262, 0.366251, 1, 1, 1, 1, 1)
+
+[sub_resource type="FastNoiseLite" id="FastNoiseLite_10olv"]
+frequency = 0.05
+fractal_weighted_strength = 1.0
+domain_warp_enabled = true
+
+[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_e0bku"]
+width = 64
+height = 64
+seamless = true
+color_ramp = SubResource("Gradient_5l3re")
+noise = SubResource("FastNoiseLite_10olv")
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_k7qv4"]
+albedo_texture = SubResource("NoiseTexture2D_e0bku")
+texture_filter = 4
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_msbte"]
+roughness = 0.75
+normal_enabled = true
+texture_filter = 5
+
+[sub_resource type="ViewportTexture" id="ViewportTexture_ykq6a"]
+viewport_path = NodePath("Testers/ShaderPlasma/SubViewport")
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_4ovjj"]
+resource_local_to_scene = true
+shading_mode = 0
+albedo_texture = SubResource("ViewportTexture_ykq6a")
+heightmap_enabled = true
+heightmap_scale = 9.0
+heightmap_deep_parallax = true
+heightmap_min_layers = 8
+heightmap_max_layers = 32
+heightmap_texture = SubResource("ViewportTexture_ykq6a")
+heightmap_flip_texture = true
+texture_filter = 5
+
+[sub_resource type="Gradient" id="Gradient_hsp0x"]
+offsets = PackedFloat32Array(0, 0.385224, 0.655673, 0.886544, 1)
+colors = PackedColorArray(1.95531e-08, 0.0213273, 0.0973903, 1, 0.294665, 0.332091, 0.73046, 1, 0.22273, 0.723553, 0.777314, 1, 0.877091, 0.64938, 0.963435, 1, 0.932381, 0.719091, 0.921475, 1)
+
+[sub_resource type="FastNoiseLite" id="FastNoiseLite_r8ncc"]
+frequency = 0.002
+fractal_type = 2
+fractal_octaves = 4
+fractal_lacunarity = 2.901
+fractal_gain = 0.353
+
+[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_n04ac"]
+width = 1024
+height = 1024
+seamless = true
+color_ramp = SubResource("Gradient_hsp0x")
+noise = SubResource("FastNoiseLite_r8ncc")
+
+[sub_resource type="FastNoiseLite" id="FastNoiseLite_25kcu"]
+seed = 60607
+fractal_gain = 0.695
+domain_warp_enabled = true
+
+[sub_resource type="NoiseTexture2D" id="NoiseTexture2D_pn44s"]
+width = 1024
+height = 1024
+seamless = true
+noise = SubResource("FastNoiseLite_25kcu")
+
+[sub_resource type="ShaderMaterial" id="ShaderMaterial_2p45w"]
+shader = ExtResource("11_aoush")
+shader_parameter/noise1 = SubResource("NoiseTexture2D_n04ac")
+shader_parameter/noise2 = SubResource("NoiseTexture2D_pn44s")
+
+[node name="WorldEnvironment" type="WorldEnvironment"]
+environment = SubResource("11")
+script = ExtResource("18")
+
+[node name="Plane" type="MeshInstance3D" parent="."]
+layers = 2
+mesh = SubResource("14")
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
+transform = Transform3D(0.909487, -0.23874, 0.340349, 0, 0.818672, 0.574262, -0.415733, -0.522284, 0.744571, 3.9506, 3.39961, 3.54442)
+shadow_enabled = true
+shadow_bias = 0.04
+directional_shadow_mode = 0
+directional_shadow_fade_start = 1.0
+directional_shadow_max_distance = 24.0
+
+[node name="ReflectionProbe" type="ReflectionProbe" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 8)
+max_distance = 100.0
+size = Vector3(100, 100, 100)
+box_projection = true
+enable_shadows = true
+ambient_mode = 0
+
+[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
+autoplay = "animate_textures"
+libraries = {
+"": SubResource("AnimationLibrary_sin37")
+}
+
+[node name="CameraHolder" type="Node3D" parent="."]
+transform = Transform3D(-4.37114e-08, 0, 1, 0, 1, 0, -1, 0, -4.37114e-08, 0, 0.125, 26)
+
+[node name="RotationX" type="Node3D" parent="CameraHolder"]
+
+[node name="Camera3D" type="Camera3D" parent="CameraHolder/RotationX"]
+fov = 70.0
+
+[node name="Testers" type="Node3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 4)
+
+[node name="NoiseTextureGrass" type="Node3D" parent="Testers"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.025, 22)
+
+[node name="MeshInstance3D" type="MeshInstance3D" parent="Testers/NoiseTextureGrass"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.25, 0)
+mesh = SubResource("SphereMesh_be5u0")
+skeleton = NodePath("../../..")
+surface_material_override/0 = ExtResource("4_gplko")
+
+[node name="MeshInstance3D2" type="MeshInstance3D" parent="Testers/NoiseTextureGrass"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1.5, 0)
+mesh = SubResource("BoxMesh_opf1g")
+surface_material_override/0 = ExtResource("4_gplko")
+
+[node name="NoiseTextureSand" type="Node3D" parent="Testers"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.025, 18)
+
+[node name="MeshInstance3D" type="MeshInstance3D" parent="Testers/NoiseTextureSand"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.25, 0)
+mesh = SubResource("SphereMesh_be5u0")
+skeleton = NodePath("../../..")
+surface_material_override/0 = ExtResource("5_12jug")
+
+[node name="MeshInstance3D2" type="MeshInstance3D" parent="Testers/NoiseTextureSand"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1.5, 0)
+mesh = SubResource("BoxMesh_opf1g")
+surface_material_override/0 = ExtResource("5_12jug")
+
+[node name="NoiseTextureWetConcrete" type="Node3D" parent="Testers"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.025, 14)
+
+[node name="MeshInstance3D" type="MeshInstance3D" parent="Testers/NoiseTextureWetConcrete"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.25, 0)
+mesh = SubResource("SphereMesh_be5u0")
+skeleton = NodePath("../../..")
+surface_material_override/0 = ExtResource("6_q1c2f")
+
+[node name="MeshInstance3D2" type="MeshInstance3D" parent="Testers/NoiseTextureWetConcrete"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1.5, 0)
+mesh = SubResource("BoxMesh_opf1g")
+surface_material_override/0 = ExtResource("6_q1c2f")
+
+[node name="NoiseTextureBathroomFloorTile" type="Node3D" parent="Testers"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.025, 10)
+
+[node name="MeshInstance3D" type="MeshInstance3D" parent="Testers/NoiseTextureBathroomFloorTile"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.25, 0)
+mesh = SubResource("SphereMesh_be5u0")
+skeleton = NodePath("../../..")
+surface_material_override/0 = SubResource("StandardMaterial3D_aq2a1")
+
+[node name="MeshInstance3D2" type="MeshInstance3D" parent="Testers/NoiseTextureBathroomFloorTile"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1.5, 0)
+mesh = SubResource("BoxMesh_opf1g")
+surface_material_override/0 = SubResource("StandardMaterial3D_aq2a1")
+
+[node name="NoiseTextureIce" type="Node3D" parent="Testers"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.025, 6)
+
+[node name="MeshInstance3D" type="MeshInstance3D" parent="Testers/NoiseTextureIce"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.25, 0)
+mesh = SubResource("SphereMesh_be5u0")
+skeleton = NodePath("../../..")
+surface_material_override/0 = ExtResource("7_k1q1v")
+
+[node name="MeshInstance3D2" type="MeshInstance3D" parent="Testers/NoiseTextureIce"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1.5, 0)
+mesh = SubResource("BoxMesh_opf1g")
+surface_material_override/0 = ExtResource("7_k1q1v")
+
+[node name="NoiseTextureMarble" type="Node3D" parent="Testers"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.025, 2)
+
+[node name="MeshInstance3D" type="MeshInstance3D" parent="Testers/NoiseTextureMarble"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.25, 0)
+mesh = SubResource("SphereMesh_be5u0")
+skeleton = NodePath("../../..")
+surface_material_override/0 = ExtResource("7_ebvqo")
+
+[node name="MeshInstance3D2" type="MeshInstance3D" parent="Testers/NoiseTextureMarble"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1.5, 0)
+mesh = SubResource("BoxMesh_opf1g")
+surface_material_override/0 = ExtResource("7_ebvqo")
+
+[node name="NoiseTextureLava" type="Node3D" parent="Testers"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.025, -2)
+
+[node name="MeshInstance3D" type="MeshInstance3D" parent="Testers/NoiseTextureLava"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.25, 0)
+mesh = SubResource("SphereMesh_be5u0")
+skeleton = NodePath("../../..")
+surface_material_override/0 = ExtResource("8_jphcw")
+
+[node name="MeshInstance3D2" type="MeshInstance3D" parent="Testers/NoiseTextureLava"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1.5, 0)
+mesh = SubResource("BoxMesh_opf1g")
+surface_material_override/0 = ExtResource("8_jphcw")
+
+[node name="NoiseTextureTrypophobia" type="Node3D" parent="Testers"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.025, -6)
+
+[node name="MeshInstance3D" type="MeshInstance3D" parent="Testers/NoiseTextureTrypophobia"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.00275182, -0.244153, -0.000101089)
+mesh = SubResource("SphereMesh_be5u0")
+skeleton = NodePath("../../..")
+surface_material_override/0 = ExtResource("3_322sv")
+
+[node name="MeshInstance3D2" type="MeshInstance3D" parent="Testers/NoiseTextureTrypophobia"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1.5, 0)
+mesh = SubResource("BoxMesh_opf1g")
+surface_material_override/0 = ExtResource("3_322sv")
+
+[node name="NoiseTexturePixelArt" type="Node3D" parent="Testers"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.025, -10)
+
+[node name="MeshInstance3D" type="MeshInstance3D" parent="Testers/NoiseTexturePixelArt"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.00275182, -0.244153, -0.000101089)
+mesh = SubResource("SphereMesh_be5u0")
+skeleton = NodePath("../../..")
+surface_material_override/0 = SubResource("StandardMaterial3D_k7qv4")
+
+[node name="MeshInstance3D2" type="MeshInstance3D" parent="Testers/NoiseTexturePixelArt"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1.5, 0)
+mesh = SubResource("BoxMesh_opf1g")
+surface_material_override/0 = SubResource("StandardMaterial3D_k7qv4")
+
+[node name="ScriptGrid" type="Node3D" parent="Testers"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.025, -14)
+
+[node name="MeshInstance3D" type="MeshInstance3D" parent="Testers/ScriptGrid"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.00275182, -0.244153, -0.000101089)
+mesh = SubResource("SphereMesh_be5u0")
+skeleton = NodePath("../../..")
+surface_material_override/0 = SubResource("StandardMaterial3D_msbte")
+script = ExtResource("10_wwlcf")
+
+[node name="MeshInstance3D2" type="MeshInstance3D" parent="Testers/ScriptGrid"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1.5, 0)
+mesh = SubResource("BoxMesh_opf1g")
+surface_material_override/0 = SubResource("StandardMaterial3D_msbte")
+
+[node name="ShaderPlasma" type="Node3D" parent="Testers"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.025, -18)
+
+[node name="MeshInstance3D" type="MeshInstance3D" parent="Testers/ShaderPlasma"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.00275182, -0.244153, -0.000101089)
+mesh = SubResource("SphereMesh_be5u0")
+skeleton = NodePath("../../..")
+surface_material_override/0 = SubResource("StandardMaterial3D_4ovjj")
+
+[node name="MeshInstance3D2" type="MeshInstance3D" parent="Testers/ShaderPlasma"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1.5, 0)
+mesh = SubResource("BoxMesh_opf1g")
+surface_material_override/0 = SubResource("StandardMaterial3D_4ovjj")
+
+[node name="SubViewport" type="SubViewport" parent="Testers/ShaderPlasma"]
+size = Vector2i(1024, 1024)
+
+[node name="ColorRect" type="ColorRect" parent="Testers/ShaderPlasma/SubViewport"]
+material = SubResource("ShaderMaterial_2p45w")
+offset_right = 1024.0
+offset_bottom = 1024.0
+
+[node name="TestName" type="Label" parent="."]
+anchors_preset = 7
+anchor_left = 0.5
+anchor_top = 1.0
+anchor_right = 0.5
+anchor_bottom = 1.0
+offset_left = -192.0
+offset_top = -58.0
+offset_right = 192.0
+offset_bottom = -24.0
+grow_horizontal = 2
+grow_vertical = 0
+theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
+theme_override_constants/outline_size = 5
+theme_override_font_sizes/font_size = 24
+horizontal_alignment = 1
+
+[node name="Previous" type="Button" parent="."]
+anchors_preset = 2
+anchor_top = 1.0
+anchor_bottom = 1.0
+offset_left = 24.0
+offset_top = -55.0
+offset_right = 135.0
+offset_bottom = -24.0
+grow_vertical = 0
+text = "«  Previous"
+
+[node name="Next" type="Button" parent="."]
+anchors_preset = 3
+anchor_left = 1.0
+anchor_top = 1.0
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -107.0
+offset_top = -55.0
+offset_right = -24.0
+offset_bottom = -24.0
+grow_horizontal = 0
+grow_vertical = 0
+text = "Next  »"
+
+[connection signal="pressed" from="Previous" to="." method="_on_previous_pressed"]
+[connection signal="pressed" from="Next" to="." method="_on_next_pressed"]

+ 70 - 0
3d/procedural_materials/tester.gd

@@ -0,0 +1,70 @@
+extends WorldEnvironment
+
+const ROT_SPEED = 0.003
+const ZOOM_SPEED = 0.125
+const MAIN_BUTTONS = MOUSE_BUTTON_MASK_LEFT | MOUSE_BUTTON_MASK_RIGHT | MOUSE_BUTTON_MASK_MIDDLE
+
+var tester_index = 0
+var rot_x = deg_to_rad(-22.5)  # This must be kept in sync with RotationX.
+var rot_y = deg_to_rad(90)  # This must be kept in sync with CameraHolder.
+var zoom = 2.5
+var base_height = ProjectSettings.get_setting("display/window/size/viewport_height")
+
+@onready var testers = $Testers
+@onready var camera_holder = $CameraHolder # Has a position and rotates on Y.
+@onready var rotation_x = $CameraHolder/RotationX
+@onready var camera = $CameraHolder/RotationX/Camera3D
+
+
+func _ready():
+	camera_holder.transform.basis = Basis.from_euler(Vector3(0, rot_y, 0))
+	rotation_x.transform.basis = Basis.from_euler(Vector3(rot_x, 0, 0))
+	update_gui()
+
+
+func _unhandled_input(event):
+	if event.is_action_pressed("ui_left"):
+		_on_previous_pressed()
+	if event.is_action_pressed("ui_right"):
+		_on_next_pressed()
+
+	if event is InputEventMouseButton:
+		if event.button_index == MOUSE_BUTTON_WHEEL_UP:
+			zoom -= ZOOM_SPEED
+		if event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
+			zoom += ZOOM_SPEED
+		zoom = clamp(zoom, 1.5, 4)
+
+	if event is InputEventMouseMotion and event.button_mask & MAIN_BUTTONS:
+		# Compensate motion speed to be resolution-independent (based on the window height).
+		var relative_motion = event.relative * DisplayServer.window_get_size().y / base_height
+		rot_y -= relative_motion.x * ROT_SPEED
+		rot_x -= relative_motion.y * ROT_SPEED
+		rot_x = clamp(rot_x, deg_to_rad(-90), 0)
+		camera_holder.transform.basis = Basis.from_euler(Vector3(0, rot_y, 0))
+		rotation_x.transform.basis = Basis.from_euler(Vector3(rot_x, 0, 0))
+
+
+func _process(delta):
+	var current_tester = testers.get_child(tester_index)
+	# This code assumes CameraHolder's X and Y coordinates are already correct.
+	var current_position = camera_holder.global_transform.origin.z
+	var target_position = current_tester.global_transform.origin.z
+	camera_holder.global_transform.origin.z = lerpf(current_position, target_position, 3 * delta)
+	camera.position.z = lerpf(camera.position.z, zoom, 10 * delta)
+
+
+func _on_previous_pressed():
+	tester_index = max(0, tester_index - 1)
+	update_gui()
+
+
+func _on_next_pressed():
+	tester_index = min(tester_index + 1, testers.get_child_count() - 1)
+	update_gui()
+
+
+func update_gui():
+	$TestName.text = str(testers.get_child(tester_index).name).capitalize()
+	$Previous.disabled = tester_index == 0
+	$Next.disabled = tester_index == testers.get_child_count() - 1