@modastar/godot-skills 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/plugins/marketplace.json +13 -0
- package/.claude-plugin/marketplace.json +16 -0
- package/.claude-plugin/plugin.json +8 -0
- package/.codex-plugin/plugin.json +12 -0
- package/README.md +75 -0
- package/agents/godot-ui-designer.md +39 -0
- package/package.json +29 -0
- package/skills/godot-character-body/SKILL.md +35 -0
- package/skills/godot-docs-lookup/SKILL.md +47 -0
- package/skills/godot-gdscript/SKILL.md +49 -0
- package/skills/godot-gdshader/SKILL.md +40 -0
- package/skills/godot-input/SKILL.md +28 -0
- package/skills/godot-localization/SKILL.md +40 -0
- package/skills/godot-mesh-lod-3d/SKILL.md +51 -0
- package/skills/godot-multimesh-3d/SKILL.md +58 -0
- package/skills/godot-navigation/SKILL.md +29 -0
- package/skills/godot-occlusion-culling-3d/SKILL.md +70 -0
- package/skills/godot-pipeline-compilation/SKILL.md +42 -0
- package/skills/godot-tween/SKILL.md +63 -0
- package/skills/godot-ui/SKILL.md +27 -0
- package/skills/godot-visibility-ranges-3d/SKILL.md +62 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "godot-skills",
|
|
3
|
+
"interface": { "displayName": "godot-skills" },
|
|
4
|
+
"plugins": [
|
|
5
|
+
{
|
|
6
|
+
"name": "godot-skills",
|
|
7
|
+
"source": { "source": "url", "path": "./" },
|
|
8
|
+
"description": "Agent Skills for Godot 4.7+.",
|
|
9
|
+
"policy": { "installation": "AVAILABLE", "authentication": "ON_INSTALL" },
|
|
10
|
+
"category": "Development"
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://www.schemastore.org/claude-code-marketplace.json",
|
|
3
|
+
"name": "godot-skills",
|
|
4
|
+
"owner": {
|
|
5
|
+
"name": "lanstar",
|
|
6
|
+
"email": "danny95624268@gmail.com"
|
|
7
|
+
},
|
|
8
|
+
"plugins": [
|
|
9
|
+
{
|
|
10
|
+
"name": "godot-skills",
|
|
11
|
+
"source": "./",
|
|
12
|
+
"description": "Agent Skills for Godot 4.7+.",
|
|
13
|
+
"category": "Development"
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
|
+
"name": "godot-skills",
|
|
4
|
+
"displayName": "godot-skills",
|
|
5
|
+
"version": "0.1.3",
|
|
6
|
+
"description": "Agent Skills for Godot 4.7+.",
|
|
7
|
+
"keywords": ["godot", "gdscript", "gdshader", "agent-skills"]
|
|
8
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "godot-skills",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "Agent Skills for Godot 4.7+.",
|
|
5
|
+
"keywords": ["godot", "gdscript", "gdshader", "agent-skills"],
|
|
6
|
+
"skills": "./skills/",
|
|
7
|
+
"interface": {
|
|
8
|
+
"displayName": "godot-skills",
|
|
9
|
+
"shortDescription": "Godot 4.7+ skills",
|
|
10
|
+
"category": "Development"
|
|
11
|
+
}
|
|
12
|
+
}
|
package/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# godot-skills
|
|
2
|
+
|
|
3
|
+
A efficient and lightweight Agent Skills plugin for Godot 4.7+. Works with Claude Code, Codex, and Pi Coding Agent.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
### Claude Code
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
/plugin marketplace add xlanstar/godot-skills
|
|
11
|
+
/plugin install godot-skills@godot-skills
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
If the install summary says `Run /reload-plugins to activate.`, run that command.
|
|
15
|
+
|
|
16
|
+
### Codex
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
codex plugin marketplace add https://github.com/xlanstar/godot-skills
|
|
20
|
+
codex plugin add godot-skills@godot-skills
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Pi Coding Agent
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
pi install npm:@modastar/godot-skills
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Local development
|
|
30
|
+
|
|
31
|
+
Load from a working copy without installing:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
claude --plugin-dir /absolute/path/to/godot-skills # session only
|
|
35
|
+
codex plugin marketplace add /absolute/path/to/godot-skills
|
|
36
|
+
pi install /absolute/path/to/godot-skills
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
All hosts discover `skills/*/SKILL.md` automatically.
|
|
40
|
+
|
|
41
|
+
## Design philosophy
|
|
42
|
+
|
|
43
|
+
We assume modern frontier LLMs already have sufficient reasoning ability and need minimal guidance. Skills should cover only information the model is unlikely to know or recall accurately, or topics where it may confuse details or be unsure of the correct approach, such as new or easily confused syntax, version-specific APIs, similarly named methods or nodes, or tasks with non-obvious implementation choices.
|
|
44
|
+
|
|
45
|
+
## Architecture
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
.claude-plugin/plugin.json Claude Code metadata
|
|
49
|
+
.codex-plugin/plugin.json Codex plugin metadata
|
|
50
|
+
.agents/plugins/marketplace.json Codex local marketplace entry
|
|
51
|
+
package.json Pi package metadata
|
|
52
|
+
AGENTS.md Shared instructions (CLAUDE.md symlinks here)
|
|
53
|
+
skills/*/SKILL.md Portable Agent Skills
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The plugin intentionally has no hooks, MCP server, runtime code, or dependencies.
|
|
57
|
+
|
|
58
|
+
## Release
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
npm version patch # or minor / major
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`npm version` bumps `package.json`, runs `scripts/sync-version.sh` to mirror the
|
|
65
|
+
version into `.claude-plugin/plugin.json` and `.codex-plugin/plugin.json`,
|
|
66
|
+
commits, tags `vX.Y.Z`, and pushes with the tag. The pushed tag triggers
|
|
67
|
+
`.github/workflows/release.yml`, which verifies the tag matches `package.json`,
|
|
68
|
+
creates a GitHub Release with generated notes, and publishes to npm (for `pi
|
|
69
|
+
install`) when the `NPM_TOKEN` repo secret exists.
|
|
70
|
+
|
|
71
|
+
Claude Code and Codex install straight from the Git repo, so nothing else is
|
|
72
|
+
published; the tag is the release.
|
|
73
|
+
|
|
74
|
+
Versioning: **patch** = wording or fixes inside a skill, **minor** = new skill
|
|
75
|
+
or agent, **major** = removed or renamed skill (breaks anyone invoking it).
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: godot-ui-designer
|
|
3
|
+
description: "Build, debug, or review Godot 4.7+ UI — Control layouts, themes, focus navigation, UI animation, localized text. Use for HUDs, menus, and dialogs."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Godot UI Designer
|
|
7
|
+
|
|
8
|
+
Design and implement Godot 4.7+ interfaces in GDScript.
|
|
9
|
+
|
|
10
|
+
## Scope
|
|
11
|
+
|
|
12
|
+
In scope: scene structure of `Control` nodes, containers and sizing, anchors, themes and type variations, focus and UI navigation, pointer/mouse filtering, UI tweens, translated and RTL-safe text.
|
|
13
|
+
|
|
14
|
+
Out of scope: gameplay systems, physics, shaders beyond UI materials, editor plugins. Hand those back to the caller.
|
|
15
|
+
|
|
16
|
+
## Skills
|
|
17
|
+
|
|
18
|
+
Load before working:
|
|
19
|
+
|
|
20
|
+
- `godot-ui` — always.
|
|
21
|
+
- `godot-input` — focus chains, `ui_*` actions, mouse filter, input propagation.
|
|
22
|
+
- `godot-tween` — transitions, fades, panel slides.
|
|
23
|
+
- `godot-localization` — any user-visible string.
|
|
24
|
+
- `godot-gdscript` — whenever writing or editing script.
|
|
25
|
+
|
|
26
|
+
Do not restate skill content; follow it.
|
|
27
|
+
|
|
28
|
+
## Workflow
|
|
29
|
+
|
|
30
|
+
1. Read the existing scene/script and reuse the project's themes, autoloads, and naming before adding anything.
|
|
31
|
+
2. Pick the layout mechanism first (container vs. anchored non-container parent), then fill in nodes.
|
|
32
|
+
3. Wire behaviour with signals; keep node lookups via `@onready` + `%UniqueName` or exported `NodePath`.
|
|
33
|
+
4. Verify: layout at a small and a large window size, keyboard/gamepad focus reaches every interactive control, and no user-visible literal bypasses `tr()`/`atr()`.
|
|
34
|
+
|
|
35
|
+
## Output
|
|
36
|
+
|
|
37
|
+
- The scene tree as an indented list with node types, plus what changed and why in a few lines.
|
|
38
|
+
- `.gd` scripts complete and runnable; `.tscn` edits described as node/property changes unless the caller asks for raw scene text.
|
|
39
|
+
- Flag any assumption about the project's theme, resolution, or input map instead of silently inventing one.
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@modastar/godot-skills",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "Agent Skills for Godot 4.7+.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"pi-package",
|
|
7
|
+
"agent-skills",
|
|
8
|
+
"godot",
|
|
9
|
+
"gdscript",
|
|
10
|
+
"gdshader"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"version": "sh scripts/sync-version.sh",
|
|
14
|
+
"postversion": "git push --follow-tags"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
".claude-plugin",
|
|
18
|
+
".codex-plugin",
|
|
19
|
+
".agents",
|
|
20
|
+
"agents",
|
|
21
|
+
"skills",
|
|
22
|
+
"README.md"
|
|
23
|
+
],
|
|
24
|
+
"pi": {
|
|
25
|
+
"skills": [
|
|
26
|
+
"./skills"
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: godot-character-body
|
|
3
|
+
description: "Write, edit, or review CharacterBody2D or CharacterBody3D."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Character Body
|
|
7
|
+
|
|
8
|
+
Target Godot 4.7+.
|
|
9
|
+
|
|
10
|
+
## Moving
|
|
11
|
+
|
|
12
|
+
- Move only with `move_and_slide()` or `move_and_collide()`, called from `_physics_process()`; never assign `position`/`global_position` to move a body, and never move from `_process()`.
|
|
13
|
+
- `move_and_slide()` moves from `velocity` and applies the physics `delta` itself. `move_and_collide(motion)` takes an absolute motion vector, so it needs `velocity * delta`. Mixing these up is the common bug.
|
|
14
|
+
- Keep `velocity` in units per second (pixels in 2D, meters in 3D): `velocity = direction * speed`, not `direction * speed * delta`. Apply acceleration and gravity with `delta`.
|
|
15
|
+
- Gravity: `velocity += get_gravity() * delta`, which honors project gravity and `Area2D`/`Area3D` overrides at the body's position; do not hardcode a constant.
|
|
16
|
+
|
|
17
|
+
## Collision response
|
|
18
|
+
|
|
19
|
+
- `move_and_slide() -> bool` reports whether a collision occurred; never assign its return value to `velocity`. It rewrites `velocity` on collision (for example zeroing the vertical component on landing), so save the pre-collision value when later logic needs it.
|
|
20
|
+
- `move_and_collide(motion)` stops dead at the first collision and returns a `KinematicCollision2D`/`3D` or `null`. It never touches `velocity`; write the response yourself:
|
|
21
|
+
```gdscript
|
|
22
|
+
var collision = move_and_collide(velocity * delta)
|
|
23
|
+
if collision:
|
|
24
|
+
velocity = velocity.slide(collision.get_normal()) # or .bounce() to reflect
|
|
25
|
+
```
|
|
26
|
+
- Use `move_and_collide(motion, true)` (`test_only`) to probe a move without applying it.
|
|
27
|
+
- Iterate slide collisions with `for i in get_slide_collision_count(): get_slide_collision(i)`. The count only includes collisions that redirected the body, not every touching body — use an `Area` or shape query for contact detection.
|
|
28
|
+
- `is_on_floor()`, `is_on_wall()`, collision counts, and collision getters describe the last `move_and_slide()` call. Before moving they are from the previous physics tick; after moving they describe the current call.
|
|
29
|
+
|
|
30
|
+
## Surface classification
|
|
31
|
+
|
|
32
|
+
- Floor/wall/ceiling classification requires `MOTION_MODE_GROUNDED` and uses nonzero `up_direction` plus `floor_max_angle` (default 45°); `MOTION_MODE_FLOATING` classifies every collision as a wall and disables slope handling.
|
|
33
|
+
- `floor_stop_on_slope` (default `true`) keeps a still body from sliding down slopes. `wall_min_slide_angle` (default 15°) is the minimum angle before the body slides along a wall instead of stopping.
|
|
34
|
+
- `floor_snap_length` does not snap while velocity points along `up_direction` (such as during a jump). `apply_floor_snap()` ignores velocity, but does nothing while already on the floor.
|
|
35
|
+
- Moving-platform velocity is added automatically. By default, `platform_on_leave` also carries that velocity after departure; do not add either manually unless changing the platform behavior.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: godot-docs-lookup
|
|
3
|
+
description: "Look up official Godot API or tutorial documentation to verify a signature, parameter, enum, or version-specific behavior."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Documentation lookup
|
|
7
|
+
|
|
8
|
+
Target Godot 4.7+. Fetch the raw reStructuredText behind docs.godotengine.org instead of the HTML page — same content, ~30x smaller (43 KB vs 1.4 MB for `CharacterBody3D`), and greppable.
|
|
9
|
+
|
|
10
|
+
## URL shape
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
https://docs.godotengine.org/en/<version>/_sources/<page path>.rst.txt
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
- `<version>`: pin `4.7` (also valid: `4.6`, `stable`, `latest`). Pin when the answer is version-sensitive.
|
|
17
|
+
- Class reference: `classes/class_<lowercased class name>.rst.txt` — `CharacterBody3D` → `class_characterbody3d`, `RenderingServer` → `class_renderingserver`.
|
|
18
|
+
- Tutorials: mirror the HTML path — `https://docs.godotengine.org/en/4.7/tutorials/physics/using_character_body_2d.html` → `_sources/tutorials/physics/using_character_body_2d.rst.txt`.
|
|
19
|
+
|
|
20
|
+
## Extract one member, not the whole class
|
|
21
|
+
|
|
22
|
+
Class pages run 20–60 KB. Anchors are stable, so slice out only the member in question:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# anchor forms: _class_<Class>_method_<name>: _property_<name>: _signal_<name>: _constant_<NAME>:
|
|
26
|
+
curl -sL https://docs.godotengine.org/en/4.7/_sources/classes/class_characterbody3d.rst.txt \
|
|
27
|
+
| awk '/_class_CharacterBody3D_method_move_and_slide:/{f=1} f{print; if(++n>18) exit}'
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Class name keeps its original casing in the anchor even though the filename is lowercase.
|
|
31
|
+
|
|
32
|
+
For a signature-only sweep, grep the method lines:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
curl -sL https://docs.godotengine.org/en/4.7/_sources/classes/class_tween.rst.txt | grep '^:ref:.*\*\*\\ ('
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Reading the RST
|
|
39
|
+
|
|
40
|
+
- Signatures render as `:ref:`bool<class_bool>` **move_and_slide**\ (\ )` — the `:ref:` wrappers are link markup; the type is inside the backticks.
|
|
41
|
+
- Method qualifiers appear as `|virtual|`, `|const|`, `|static|`, `|vararg|` after the signature.
|
|
42
|
+
- `.. rst-class:: classref-*` lines are section separators (`classref-method`, `classref-property`, `classref-signal`) — useful as awk boundaries.
|
|
43
|
+
- The trailing `.. |virtual| replace::` block at the end of every page is boilerplate; ignore it.
|
|
44
|
+
|
|
45
|
+
## When to use
|
|
46
|
+
|
|
47
|
+
Look it up rather than recall it for: exact parameter order and defaults, enum constant names, `@export` annotation forms, and anything the user frames as "did this change in 4.x". A 404 on a class page usually means the class was renamed — check the release notes rather than guessing a near-miss name.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: godot-gdscript
|
|
3
|
+
description: "Write, edit, or review GDScript."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# GDScript
|
|
7
|
+
|
|
8
|
+
Target Godot 4.7+. Verify unfamiliar syntax and virtual method signatures against the 4.7 class reference.
|
|
9
|
+
|
|
10
|
+
## Recent changes
|
|
11
|
+
|
|
12
|
+
- `@abstract` classes and methods were introduced in 4.5. Abstract methods have no body; subclasses must implement every abstract method or also be abstract.
|
|
13
|
+
- Variadic functions were introduced in 4.5: `func emit_all(...args: Array) -> void`. The rest parameter must be last, cannot have a default, and cannot use `Array[T]`. GDScript has no spread-call syntax; use `callv()` for an argument array.
|
|
14
|
+
|
|
15
|
+
## Required idioms
|
|
16
|
+
|
|
17
|
+
Statically type every variable, parameter, return, and container; untyped code falls back to `Variant` dispatch. Also use `&"name"` for repeated identifiers, `preload()` for fixed paths, `@onready` or cached references over repeated `get_node()`, signals or groups over polling, packed arrays for bulk buffers, integer vectors for grid data, and static methods when no instance state is needed. Allocate nothing per frame.
|
|
18
|
+
|
|
19
|
+
## Callbacks
|
|
20
|
+
|
|
21
|
+
Match virtual methods to the class reference exactly; never change parameter count, names, types, or return type. Processing for `_process`, `_physics_process`, `_input`, `_shortcut_input`, `_unhandled_key_input`, and `_unhandled_input` is enabled automatically when the method is overridden; the matching `set_process_*()` call only toggles it afterwards.
|
|
22
|
+
|
|
23
|
+
Lifecycle order:
|
|
24
|
+
|
|
25
|
+
- `_enter_tree()` runs parent first, then children. `_ready()` is the reverse: children first, parent last. `_exit_tree()` runs children first, so a node's own `_exit_tree()` is last.
|
|
26
|
+
- `_ready()` fires once per node; re-adding a removed node does not repeat it unless `request_ready()` was called first.
|
|
27
|
+
- `_init()` runs outside the tree: no `@onready`, no `get_node()`. A parameterized `_init()` forces every `.new()` call to pass those arguments.
|
|
28
|
+
- `_process(delta)` and `_physics_process(delta)` run in ascending `process_priority` / `process_physics_priority`, ties in tree order.
|
|
29
|
+
|
|
30
|
+
Input, in the order one event visits the callbacks:
|
|
31
|
+
|
|
32
|
+
`_input()` -> `Control._gui_input()` -> `_shortcut_input()` -> `_unhandled_key_input()` -> `_unhandled_input()`
|
|
33
|
+
|
|
34
|
+
- The non-GUI callbacks visit nodes in reverse depth-first order: deepest node first, root last.
|
|
35
|
+
- Consume an event with `get_viewport().set_input_as_handled()`; inside `_gui_input()` use `accept_event()`.
|
|
36
|
+
- `_gui_input()` requires `mouse_filter` other than `MOUSE_FILTER_IGNORE`, and only the targeted Control and its ancestors receive mouse events. GUI keyboard and joypad events do not travel up the tree; unconsumed ones resurface in `_unhandled_input()`.
|
|
37
|
+
- Prefer `_unhandled_key_input()` over `_unhandled_input()` for key handling: unrelated events such as `InputEventMouseMotion` are filtered out.
|
|
38
|
+
|
|
39
|
+
Other:
|
|
40
|
+
|
|
41
|
+
- `_draw()` runs only after `queue_redraw()`, and the `draw_*` methods are valid only inside it.
|
|
42
|
+
- `_notification(what: int)` is the only hook for notifications without a dedicated virtual, such as `NOTIFICATION_WM_CLOSE_REQUEST`, `NOTIFICATION_THEME_CHANGED`, and `NOTIFICATION_PREDELETE`.
|
|
43
|
+
|
|
44
|
+
## `await`
|
|
45
|
+
|
|
46
|
+
- `await` on a signal or coroutine makes the enclosing function a coroutine, so every caller that needs its return value must `await` it too.
|
|
47
|
+
- Calling a coroutine without `await` runs it asynchronously and leaves the caller a normal function, but reading its return value is an error.
|
|
48
|
+
- Returning a signal from a non-coroutine function makes the caller's `await` wait on that signal.
|
|
49
|
+
- After resumption, recheck referenced objects with `is_instance_valid()`, tree membership with `is_inside_tree()`, and the state or generation that started the work. Guard re-entrant coroutines with a busy flag or generation ID.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: godot-gdshader
|
|
3
|
+
description: "Write, edit, or review Godot 4 GDShader code, materials, and shader performance."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# GDShader
|
|
7
|
+
|
|
8
|
+
Target Godot 4.7+. Verify shader-type processor functions, built-ins, render modes, and renderer support against the 4.7 shader reference; they are not interchangeable between `spatial`, `canvas_item`, `particles`, `sky`, and `fog`.
|
|
9
|
+
|
|
10
|
+
## Recent changes in 4.7
|
|
11
|
+
|
|
12
|
+
- Spatial shaders expose `IN_SHADOW_PASS` globally; branch on it instead of inferring the shadow pass from other state.
|
|
13
|
+
- Spatial `light()` gained an area-light path: `LIGHT_IS_AREA`, `LIGHT_AREA_DIFFUSE_MULTIPLIER`, `LIGHT_AREA_SPECULAR_MULTIPLIER`, and `SPECULAR_AMOUNT`. A `light()` written before 4.7 handles area lights as if they were punctual; branch on `LIGHT_IS_AREA` and use the multipliers there.
|
|
14
|
+
|
|
15
|
+
## Structure and syntax
|
|
16
|
+
|
|
17
|
+
- Write Godot shading language, not raw GLSL. Omit `#version`, layouts, and GLSL entry points. Order code as `shader_type`, `render_mode`, uniforms, constants, varyings, custom functions, then processor functions.
|
|
18
|
+
- Define custom functions before their callers. Use explicit types and constructors: `2.0` for `float`, `uint(2)` for `uint`; implicit numeric casts are not allowed. Use `snake_case` identifiers and `CONSTANT_CASE` constants.
|
|
19
|
+
- Put uniform hints before defaults: `uniform vec4 tint : source_color = vec4(1.0);`. Always mark sRGB color textures with `source_color`; do not use it for linear data such as normal, roughness, metallic, or height maps.
|
|
20
|
+
- Use `/** ... */` immediately above a non-obvious uniform to expose an Inspector tooltip. Use `group_uniforms` only when it improves a material's Inspector.
|
|
21
|
+
- Assign a `varying` only directly inside `vertex()` or `fragment()`, never in a custom function or `light()`. The default interpolation is `smooth`; use `flat` for discrete values.
|
|
22
|
+
- Keep one-off helpers local. Extract `.gdshaderinc` files with `#include` only when multiple shaders actually share code.
|
|
23
|
+
|
|
24
|
+
## Data flow
|
|
25
|
+
|
|
26
|
+
- Track coordinate spaces explicitly. Spatial vertex inputs are local by default and spatial fragment normals are view-space; CanvasItem vertices are local pixel coordinates. Use the provided transform matrices rather than ad-hoc conversions.
|
|
27
|
+
- In a CanvasItem `fragment()`, input `COLOR` already contains the default texture multiplied by vertex color, `modulate`, and `self_modulate`. Preserve it when that is desired; sample `TEXTURE` explicitly only when raw texels or custom UVs are needed.
|
|
28
|
+
- Use `instance uniform` for per-node variation in `canvas_item` and `spatial` shaders instead of duplicating materials.
|
|
29
|
+
- `TIME` rolls over after 3,600 seconds by default, follows `Engine.time_scale`, and ignores pause. Drive a global uniform from GDScript when those semantics do not fit.
|
|
30
|
+
- With `skip_vertex_transform`, perform every required transform yourself. Writing spatial `POSITION` overrides projection and requires a valid clip-space value on every path. Writing `DEPTH` on any path requires writing it on all paths.
|
|
31
|
+
- Read screen, depth, and normal-roughness buffers through declared samplers using `hint_screen_texture`, `hint_depth_texture`, or `hint_normal_roughness_texture` and `SCREEN_UV`. Add `repeat_disable` so edge samples do not wrap, `filter_nearest` when reading the matching pixel, and `filter_linear_mipmap` only when the effect blurs or samples at varying scale.
|
|
32
|
+
- Writing spatial `ALPHA`, even conditionally, moves the material to the transparent pipeline with its sorting, shadow, and screen-texture limitations. Avoid it for opaque materials.
|
|
33
|
+
|
|
34
|
+
## Cost and compatibility
|
|
35
|
+
|
|
36
|
+
- Prefer `CanvasItemMaterial`, `StandardMaterial3D`, and `WorldEnvironment` features when they express the effect; write a custom shader only for behavior they cannot provide.
|
|
37
|
+
- Write only the material outputs the effect needs; Godot removes unused functionality. Do not define `light()` unless replacing the built-in lighting behavior.
|
|
38
|
+
- Avoid `discard` unless clipping is required; it defeats the depth prepass. Move per-fragment work into `vertex()` only when linear interpolation preserves the result.
|
|
39
|
+
- Do not add precision qualifiers speculatively; conversions and mobile drivers can erase the benefit.
|
|
40
|
+
- Godot 4.4+ exposes `CURRENT_RENDERER`, `RENDERER_COMPATIBILITY`, `RENDERER_MOBILE`, and `RENDERER_FORWARD_PLUS` to the shader preprocessor. Branch on them only for real renderer differences.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: godot-input
|
|
3
|
+
description: "Implement or debug Godot input actions, event routing, UI focus/navigation, and pointer input."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Input Handling
|
|
7
|
+
|
|
8
|
+
Target Godot 4.7+.
|
|
9
|
+
|
|
10
|
+
## Actions and event routing
|
|
11
|
+
|
|
12
|
+
- Poll `Input` for held state; use the callback's `InputEvent` for discrete transitions. `Input.get_vector()` orders actions as negative X, positive X, negative Y, positive Y.
|
|
13
|
+
- `event.is_action_pressed(action, allow_echo = false, exact_match = false)` already rejects key echo by default. The second positional boolean enables echo; use a named argument when setting `exact_match`.
|
|
14
|
+
- Handle gameplay in `_unhandled_input()` so GUI gets first refusal. Use `_shortcut_input()` for key, shortcut, and joypad-button shortcuts; reserve pre-GUI `_input()` for global interception.
|
|
15
|
+
- Runtime `InputMap` changes are not saved automatically; persist user bindings separately. Keep built-in `ui_*` actions for UI navigation, never gameplay.
|
|
16
|
+
|
|
17
|
+
## UI focus
|
|
18
|
+
|
|
19
|
+
- Custom keyboard/controller targets need `FOCUS_ALL`, not `FOCUS_CLICK`; decoration uses `FOCUS_NONE`. In Godot 4.7, `FOCUS_ACCESSIBILITY` is focusable only while a screen reader is active.
|
|
20
|
+
- On opening a screen, call `grab_focus.call_deferred()` on its first meaningful control; restore focus to the opener when a modal closes.
|
|
21
|
+
- `focus_neighbor_*` controls directional navigation; `focus_next`/`focus_previous` control Tab order. Leave paths unset for Godot's automatic best guess; set only ambiguous routes.
|
|
22
|
+
- Keep a visible focus style and a mouse-free path through every operation. For an inactive subtree, set `focus_behavior_recursive` to `FOCUS_BEHAVIOR_DISABLED` and leave descendants `FOCUS_BEHAVIOR_INHERITED`; `FOCUS_BEHAVIOR_ENABLED` explicitly bypasses an ancestor override.
|
|
23
|
+
|
|
24
|
+
## Pointer input
|
|
25
|
+
|
|
26
|
+
- `MOUSE_FILTER_STOP` receives and blocks; `MOUSE_FILTER_PASS` receives and bubbles unhandled events to Control ancestors, not Controls behind it; `MOUSE_FILTER_IGNORE` neither receives nor blocks Controls behind it.
|
|
27
|
+
- Handle Control-owned events in `_gui_input()` and call `accept_event()` only when consumed. For non-Control callbacks, use `get_viewport().set_input_as_handled()`; neither API changes `Input` polling state.
|
|
28
|
+
- When a child's `mouse_filter` appears ignored, check `get_mouse_filter_with_override()`: an ancestor's `MOUSE_BEHAVIOR_DISABLED` makes inheriting descendants effectively `IGNORE`, while `MOUSE_BEHAVIOR_ENABLED` explicitly bypasses that override.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: godot-localization
|
|
3
|
+
description: "Add or debug Godot translations: tr/atr lookups, plurals and contexts, catalogs, translation domains, locale switching, and RTL or pseudolocalization checks."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Localization
|
|
7
|
+
|
|
8
|
+
Target Godot 4.7+.
|
|
9
|
+
|
|
10
|
+
## Translating strings
|
|
11
|
+
|
|
12
|
+
- `tr(message, context)` and `tr_n(message, plural_message, n, context)` are `Object` methods. `atr()` and `atr_n()` are the `Node` variants that additionally respect `auto_translate_mode`; prefer them in node scripts so a node marked disabled stays untranslated. In a static context use `TranslationServer.translate()` / `translate_plural()`.
|
|
13
|
+
- `n` selects the plural form but is not substituted: `tr_n("There is %d apple", "There are %d apples", n) % n`. Pluralization applies to non-negative integers only; handle negative and float counts with `tr()`.
|
|
14
|
+
- Translate whole sentences, then format. Prefer named placeholders through `String.format()` so translators can reorder them: `tr("{who} picked up the {what}").format({who = who_name, what = item_name})`.
|
|
15
|
+
- `context` disambiguates identical source strings (`tr("Close", "Actions")`) and is part of the lookup key, so a mismatched context silently returns the source string.
|
|
16
|
+
|
|
17
|
+
## Auto-translated nodes
|
|
18
|
+
|
|
19
|
+
- Controls (`Label`, `Button`, `Window`, …) translate their text properties automatically when the value matches a key. New nodes default to `AUTO_TRANSLATE_MODE_INHERIT`; the root defaults to `ALWAYS` (`ProjectSettings.internationalization/rendering/root_node_auto_translate`). Set `AUTO_TRANSLATE_MODE_DISABLED` on player names, chat, and other user data, or a value that happens to match a key gets replaced. Disabled subtrees are also skipped during POT generation.
|
|
20
|
+
- Strings set from script must be rebuilt on `Node.NOTIFICATION_TRANSLATION_CHANGED`, sent on locale change, on `auto_translate_mode` change, and on tree entry. It arrives alongside `NOTIFICATION_ENTER_TREE`, so `await ready` before touching children.
|
|
21
|
+
- Automatic Control translation does not cover entries carrying plural or context data; call `tr()` / `tr_n()` for those.
|
|
22
|
+
|
|
23
|
+
## Catalogs and domains
|
|
24
|
+
|
|
25
|
+
- CSV: first column is keys, header row is locale codes, `_`-prefixed columns are comments. Save UTF-8 without BOM. Godot 4.6+ adds `?plural` and `?context` columns (any position except the first).
|
|
26
|
+
- gettext: locale comes from the `Language:` header; entries marked `fuzzy` are not read by Godot until the comment is removed. `.mo` is worth it only for very large projects.
|
|
27
|
+
- Importing a CSV does not register it — add it under Project Settings > Localization > Translations. Generate the POT under Localization > Template Generation; the extractor only evaluates constant strings. `# NO_TRANSLATE` excludes a line, `# TRANSLATORS:` attaches a note (same line or the line above).
|
|
28
|
+
- Separate catalogs: `Object.set_translation_domain(name)` with `TranslationServer.get_or_add_domain(name)`; `Node.set_translation_domain_inherited()` restores parent inheritance. Names starting with `godot.` are reserved for the engine.
|
|
29
|
+
|
|
30
|
+
## Locale at runtime
|
|
31
|
+
|
|
32
|
+
- Default to `OS.get_locale_language()` (language only, no region) and let players override it. `TranslationServer.get_locale()` returns the project locale, not the system one. `set_locale()` standardizes input (`en-US` → `en_US`); unmatched locales fall back to `internationalization/locale/fallback`, or `en` when empty.
|
|
33
|
+
- Godot 4.6+: `find_translations(locale, exact)` replaces the deprecated `get_translation_object()`; `TranslationServer.format_number()` / `parse_number()` convert between Western Arabic digits and the locale's numeral system.
|
|
34
|
+
|
|
35
|
+
## Verifying
|
|
36
|
+
|
|
37
|
+
- Test with `--language fr`, the Locale > Test project setting (reset it before committing), or View > Preview Translation in the editor.
|
|
38
|
+
- Pseudolocalization expands and accents every localizable string; `pseudolocalization_override_enabled` replaces characters with `*` to expose non-localizable ones. Changing pseudolocalization properties on a `TranslationDomain` does not refresh the scene tree — propagate `MainLoop.NOTIFICATION_TRANSLATION_CHANGED` manually.
|
|
39
|
+
- RTL locales mirror anchors, text alignment, and container child order automatically; the coordinate system and non-`Control` nodes are not mirrored, so flip directional icons yourself. Use `structured_text_bidi_override` for paths, URIs, and other non-natural text.
|
|
40
|
+
- The default project font covers only a subset of Latin-1. Use `FontFile` fallbacks rather than resource remaps, which do not apply to fonts. Enable Locale > Include Text Server Data (~4 MB) for line breaking in languages written without spaces.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: godot-mesh-lod-3d
|
|
3
|
+
description: "Set up and tune automatic 3D mesh LOD in Godot: which imports generate LODs, the pixel threshold and per-object bias, MultiMeshInstance3D/particles caveats, and when to use visibility ranges instead."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Mesh LOD
|
|
7
|
+
|
|
8
|
+
Target Godot 4.7+.
|
|
9
|
+
|
|
10
|
+
Godot decimates imported meshes with meshoptimizer and swaps levels automatically. Works on any node that draws meshes: `MeshInstance3D`, `MultiMeshInstance3D`, `GPUParticles3D`, `CPUParticles3D`.
|
|
11
|
+
|
|
12
|
+
Use automatic mesh LOD for decimating a single mesh. Use **visibility ranges (HLOD)** instead when the levels are artist-authored, or when you need to swap a group of nodes for one merged node.
|
|
13
|
+
|
|
14
|
+
## Getting LODs generated
|
|
15
|
+
|
|
16
|
+
Imported 3D **scenes** (glTF, `.blend`, Collada, FBX) generate LODs by default — nothing to configure.
|
|
17
|
+
|
|
18
|
+
**OBJ does not.** OBJ imports as a bare mesh resource, not a scene. Select the file → Import dock → **Import As: Scene** → **Reimport**, then restart the editor.
|
|
19
|
+
|
|
20
|
+
Turn LODs off per-mesh in Advanced Import Settings (**Generate LODs**) when decimation breaks a mesh — skinned meshes are the usual offender — or to cut import time.
|
|
21
|
+
|
|
22
|
+
## Tuning
|
|
23
|
+
|
|
24
|
+
Global threshold: `rendering/mesh_lod/lod_change/threshold_pixels`, default `1`. That default is tuned to be perceptually lossless; raise it to trade quality for performance. It is a **per-viewport** property at runtime, not a global:
|
|
25
|
+
|
|
26
|
+
```gdscript
|
|
27
|
+
get_tree().root.mesh_lod_threshold = 4.0
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Per-object, `lod_bias` on any `GeometryInstance3D`:
|
|
31
|
+
|
|
32
|
+
- `> 1.0` — transitions happen later: higher quality, lower performance.
|
|
33
|
+
- `< 1.0` — transitions happen sooner: lower quality, higher performance.
|
|
34
|
+
|
|
35
|
+
`ReflectionProbe` has its own `mesh_lod_threshold`. Raise it for probes with `update_mode = UPDATE_ALWAYS`.
|
|
36
|
+
|
|
37
|
+
Selection is screen-space, so camera FOV and viewport resolution are already accounted for — unlike visibility ranges, which need manual compensation.
|
|
38
|
+
|
|
39
|
+
## MultiMesh and particles
|
|
40
|
+
|
|
41
|
+
LOD is chosen from the point of the **node's AABB** closest to the camera, so every instance in one `MultiMeshInstance3D` or particle node renders at the **same** LOD level.
|
|
42
|
+
|
|
43
|
+
Split instances that are far apart into separate `MultiMeshInstance3D` nodes. This also lets frustum and occlusion culling drop whole nodes — neither can cull individual instances inside a MultiMesh.
|
|
44
|
+
|
|
45
|
+
If a `GPUParticles3D` picks the wrong level, its visibility AABB is probably wrong: select the node and use **GPUParticles3D > Generate AABB** in the 3D viewport toolbar.
|
|
46
|
+
|
|
47
|
+
## Verifying
|
|
48
|
+
|
|
49
|
+
Viewport camera menu → **Display Advanced… > Disable Mesh LOD** to A/B it. Enable **View Information** and watch the primitive count drop, and **View Frame Time** for FPS — FPS won't move if you are CPU-bound.
|
|
50
|
+
|
|
51
|
+
To watch decimation itself: **Display Wireframe**, then drag `threshold_pixels`.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: godot-multimesh-3d
|
|
3
|
+
description: "Draw thousands to millions of copies of one mesh in a single 3D draw call with MultiMeshInstance3D in Godot: setup order, per-instance color/custom data, buffer updates, physics interpolation, and when not to use it."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# MultiMeshInstance3D
|
|
7
|
+
|
|
8
|
+
Target Godot 4.7+.
|
|
9
|
+
|
|
10
|
+
## When it applies
|
|
11
|
+
|
|
12
|
+
Same `Mesh` + same material, repeated many times. Hundreds → plain nodes are fine. Thousands with per-object logic → `RenderingServer` instances directly. Hundreds of thousands to millions → MultiMesh. Instances differ only by transform, one `Color`, and one 4-float custom value.
|
|
13
|
+
|
|
14
|
+
MultiMesh has **no collision, no per-instance visibility, no per-instance script**. Physics needs separate bodies (`PhysicsServer3D` shapes). Culling, shadow settings, LOD, and visibility ranges apply to the whole `MultiMeshInstance3D`, all-or-none — split the world into several MultiMeshes per region instead.
|
|
15
|
+
|
|
16
|
+
## Setup order is mandatory
|
|
17
|
+
|
|
18
|
+
`instance_count` clears and resizes the buffers; format and flags set *after* it are silently ignored. Set them first:
|
|
19
|
+
|
|
20
|
+
```gdscript
|
|
21
|
+
extends MultiMeshInstance3D
|
|
22
|
+
|
|
23
|
+
func _ready() -> void:
|
|
24
|
+
multimesh = MultiMesh.new()
|
|
25
|
+
multimesh.transform_format = MultiMesh.TRANSFORM_3D # default is TRANSFORM_2D (0)
|
|
26
|
+
multimesh.use_colors = true # only settable while instance_count <= 0
|
|
27
|
+
multimesh.use_custom_data = true
|
|
28
|
+
multimesh.mesh = BoxMesh.new()
|
|
29
|
+
multimesh.instance_count = 10000 # allocate the maximum ever needed
|
|
30
|
+
multimesh.visible_instance_count = 1000 # -1 = draw all
|
|
31
|
+
|
|
32
|
+
for i in multimesh.visible_instance_count:
|
|
33
|
+
multimesh.set_instance_transform(i, Transform3D(Basis(), Vector3(i * 20, 0, 0)))
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
To change a flag later, set `instance_count = 0`, change it, then resize again (all data is lost).
|
|
37
|
+
|
|
38
|
+
Standard pooling pattern: allocate once at max, then move only `visible_instance_count` — it never reallocates. Keep live instances packed at the front of the buffer.
|
|
39
|
+
|
|
40
|
+
`custom_aabb` — set it manually to skip costly runtime AABB recalculation; `get_aabb()` returns the computed local-space box.
|
|
41
|
+
|
|
42
|
+
## Per-instance data
|
|
43
|
+
|
|
44
|
+
- `set_instance_transform(i, Transform3D)` / `set_instance_transform_2d(i, Transform2D)` — must match `transform_format`.
|
|
45
|
+
- `set_instance_color(i, Color)` — needs `use_colors = true` **and** `BaseMaterial3D.vertex_color_use_as_albedo = true`. It *multiplies* existing vertex colors, so set albedo to `Color(1, 1, 1)` for an absolute color.
|
|
46
|
+
- `set_instance_custom_data(i, Color)` — a `Color` only as a container for 4 floats. Needs `use_custom_data = true`; read it in a shader as `INSTANCE_CUSTOM`.
|
|
47
|
+
- Forward+/Mobile store color and custom data at 32 bits per component; **Compatibility packs them to 16 bits**.
|
|
48
|
+
- `transform_array`, `transform_2d_array`, `color_array`, `custom_data_array` are deprecated — reading or writing them is very slow.
|
|
49
|
+
|
|
50
|
+
## Bulk updates
|
|
51
|
+
|
|
52
|
+
Per-instance setters are one call each. For thousands updated per frame, build a `PackedFloat32Array` and assign `multimesh.buffer` in one go (`RenderingServer.multimesh_set_buffer()` from a thread/GDExtension for the extreme case). Per-instance stride: 12 floats for `TRANSFORM_3D` (three rows of 4: basis row + origin component), 8 for `TRANSFORM_2D`, then +4 for color and +4 for custom data when enabled. Getting `buffer` returns a **copy** — mutating the returned array does nothing.
|
|
53
|
+
|
|
54
|
+
Better still: keep the transforms constant and do the movement in the vertex shader using `INSTANCE_ID` / `INSTANCE_CUSTOM`, feeding bulk data through a float-format `Image`/texture.
|
|
55
|
+
|
|
56
|
+
## Physics interpolation
|
|
57
|
+
|
|
58
|
+
With interpolation on, use `set_buffer_interpolated(buffer_curr, buffer_prev)` when instance order changes between physics ticks (particle-like reuse); plain `buffer` interpolates correctly only when order is stable. Call `reset_instance_physics_interpolation(i)` (or `reset_instances_physics_interpolation()`) after teleporting/spawning, or the instance streaks in from its old position. `physics_interpolation_quality`: `INTERP_QUALITY_FAST` (Basis lerp, default) is fine except at very low tick rates or fast rotation, where `INTERP_QUALITY_HIGH` (slerp) avoids warping.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: godot-navigation
|
|
3
|
+
description: "Godot 2D/3D navigation agents, path following, avoidance, map synchronization, and navigation baking."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# AI Navigation
|
|
7
|
+
|
|
8
|
+
Target Godot 4.7+. `NavigationAgent2D`/`3D` calculates paths and avoidance; it never moves its parent.
|
|
9
|
+
|
|
10
|
+
## Path queries and following
|
|
11
|
+
|
|
12
|
+
- Setting `target_position` (global coordinates) requests a path on navigation regions selected by the agent's `navigation_layers` mask.
|
|
13
|
+
- NavigationServer changes synchronize at the end of a physics frame. Do not query in `_ready()` or rely on `call_deferred()` alone: wait one physics frame after setup, wait for `map_changed(map: RID)` for the agent's map, or guard the initial query with `map_get_iteration_id(agent.get_navigation_map()) > 0`.
|
|
14
|
+
- In `_physics_process()`, check `is_navigation_finished()` first, then call `get_next_path_position()` exactly once while active. Calling it after completion causes jitter.
|
|
15
|
+
- Do not call path-updating methods from agent signal callbacks; they can emit recursively. Use `CONNECT_DEFERRED`, `call_deferred()`, or the next physics tick.
|
|
16
|
+
- Do not reset `target_position` every frame. Retarget after meaningful movement or on a timer; frequent repaths can make the first path point alternate around the agent.
|
|
17
|
+
- Increase `path_desired_distance`/`target_desired_distance` when speed or update interval lets the actor overshoot them; undersized values cause repath loops.
|
|
18
|
+
|
|
19
|
+
## Clearance and avoidance
|
|
20
|
+
|
|
21
|
+
- Path clearance comes from baked `NavigationPolygon.agent_radius`/`NavigationMesh.agent_radius`. `NavigationAgent.radius` affects avoidance only; use separate navigation maps when actor clearances differ.
|
|
22
|
+
- Avoidance is optional local steering and uses `avoidance_layers`/`avoidance_mask`, not `navigation_layers`. When enabled, match `max_speed` to the actor, set the agent's desired `velocity` each physics tick, and move the parent only from `velocity_computed(safe_velocity)`.
|
|
23
|
+
- Even for avoidance-only use, set `target_position`; otherwise `velocity_computed` returns zero.
|
|
24
|
+
- `NavigationAgent3D` defaults to flat XZ avoidance (`use_3d_avoidance = false`); `keep_y_velocity = true` reapplies the requested Y velocity. Full 3D avoidance uses spheres and ignores `height`.
|
|
25
|
+
|
|
26
|
+
## Runtime baking
|
|
27
|
+
|
|
28
|
+
- `NavigationRegion2D.bake_navigation_polygon(on_thread: bool = true)` and `NavigationRegion3D.bake_navigation_mesh(on_thread: bool = true)` are threaded by default.
|
|
29
|
+
- Wait for `bake_finished`, then for the navigation map to synchronize before querying the replacement data.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: godot-occlusion-culling-3d
|
|
3
|
+
description: "Set up and debug 3D occlusion culling in Godot: OccluderInstance3D baking rules, what is silently excluded from bakes, occludee requirements, runtime toggling, and why an object is or isn't culled."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Occlusion culling
|
|
7
|
+
|
|
8
|
+
Target Godot 4.7+.
|
|
9
|
+
|
|
10
|
+
## Decide before setting it up
|
|
11
|
+
|
|
12
|
+
Occlusion is CPU-rasterized into a low-resolution buffer (Embree) and an occludee is culled only when its **AABB is fully covered** by occluder shapes. Consequences: small occludees cull easily, large ones rarely; few large occluders (walls, floors, terrain ridges) beat many small ones (props).
|
|
13
|
+
|
|
14
|
+
Gain is largest on the **Mobile** renderer — it is the only method without a depth prepass, so culling removes real shading overdraw. **Forward+** and **Compatibility** both prepass, so the win there is draw calls and vertices only. Open scenes with few blockers may not pay back the CPU cost.
|
|
15
|
+
|
|
16
|
+
Level layout matters more than any setting: interiors need opaque walls breaking line of sight at regular intervals, and open scenes cull best with pyramid-like elevation. A flat open map has nothing to occlude with.
|
|
17
|
+
|
|
18
|
+
Not supported in **Web** exports by default — it needs a custom export template built with `module_raycast_enabled=yes`. Otherwise the whole setup silently does nothing.
|
|
19
|
+
|
|
20
|
+
## Enabling
|
|
21
|
+
|
|
22
|
+
Project setting `rendering/occlusion_culling/use_occlusion_culling` (Advanced toggle in the dialog). Applies immediately, no editor restart. The root viewport needs that project setting on; every other Viewport has its own `use_occlusion_culling` and is toggled per-viewport:
|
|
23
|
+
|
|
24
|
+
```gdscript
|
|
25
|
+
get_tree().root.use_occlusion_culling = true
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Nothing is culled until occluders exist — visible geometry is never used automatically.
|
|
29
|
+
|
|
30
|
+
## Baking (default path)
|
|
31
|
+
|
|
32
|
+
Add `OccluderInstance3D`, press **Bake Occluders**; it stores an `Occluder3D` resource drawn as purple wireframe (needs **View Gizmos**).
|
|
33
|
+
|
|
34
|
+
Silently **excluded from bakes**:
|
|
35
|
+
|
|
36
|
+
- Everything that is not a `MeshInstance3D` — `MultiMeshInstance3D`, `GPUParticles3D`, `CPUParticles3D`, CSG nodes. Since 4.4, a CSG node converted to a MeshInstance3D beforehand does bake.
|
|
37
|
+
- Surfaces with a **transparent material**, even when the texture is fully opaque.
|
|
38
|
+
- Meshes whose `VisualInstance3D.layers` fall outside `bake_mask`.
|
|
39
|
+
|
|
40
|
+
These limits apply to occluders only; any `GeometryInstance3D` can be an occludee.
|
|
41
|
+
|
|
42
|
+
Dynamic objects (player, enemies, doors) must be kept out of the bake or they occlude from their editor position forever. Standard setup: put them on visual layer 2, clear layer 2 in `bake_mask` (it defaults to all layers on), rebake. Equivalent, as editor-time setup — changing these at runtime does nothing to an existing bake:
|
|
43
|
+
|
|
44
|
+
```gdscript
|
|
45
|
+
$Enemy/MeshInstance3D.set_layer_mask_value(1, false)
|
|
46
|
+
$Enemy/MeshInstance3D.set_layer_mask_value(2, true)
|
|
47
|
+
$OccluderInstance3D.set_bake_mask_value(2, false)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Bakes are static snapshots: **rebake after moving level geometry**.
|
|
51
|
+
|
|
52
|
+
`bake_simplification_distance` (Bake > Simplification) trades accuracy for CPU cost. Default `0.1` is already fairly aggressive; `0.01` is perceptually unaffected; `0.0` disables simplification (vertices are still merged and the mesh re-indexed). Raise it when complex scenes stutter; too high produces occluders that swallow geometry and cull visible objects.
|
|
53
|
+
|
|
54
|
+
## Manual occluders
|
|
55
|
+
|
|
56
|
+
Set `occluder` on an `OccluderInstance3D` to `QuadOccluder3D`, `BoxOccluder3D`, `SphereOccluder3D`, or `PolygonOccluder3D`. `ArrayOccluder3D` has no editor handles and exists for procedural generation from script. This is the workaround for MultiMesh/particles/CSG.
|
|
57
|
+
|
|
58
|
+
## Movement
|
|
59
|
+
|
|
60
|
+
Moving an `OccluderInstance3D` — or any parent — rebuilds the BVH each frame. Never animate one. Toggling `visible` costs a single update instead, so for a door: parent the occluder outside the door, hide it when the door starts opening, show it when fully closed. If a moving occluder is unavoidable, use a primitive shape, never a baked one.
|
|
61
|
+
|
|
62
|
+
## Troubleshooting
|
|
63
|
+
|
|
64
|
+
**Not culled when it should be** — check the bake exclusions above; then on the occludee check `extra_cull_margin` is `0.0` and `ignore_occlusion_culling` is off, and look at the orange AABB gizmo: an oversized AABB is never fully covered.
|
|
65
|
+
|
|
66
|
+
**Culled when it shouldn't be** — stale bake after geometry moved, dynamic objects baked in, or `bake_simplification_distance` too high. Last resort: enable `ignore_occlusion_culling` on the occludee (correct for first-person view models, which are never occluded anyway).
|
|
67
|
+
|
|
68
|
+
**Inspecting** — viewport camera menu → **Display Advanced… > Occlusion Culling Buffer** shows the actual buffer; pair with **View Information** to compare draw calls with the setting on and off. Results depend heavily on camera angle.
|
|
69
|
+
|
|
70
|
+
**Still CPU-bound** after simplification — `rendering/occlusion_culling/bvh_build_quality` and `rendering/occlusion_culling/occlusion_rays_per_thread`.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: godot-pipeline-compilation
|
|
3
|
+
description: "Diagnose and remove first-playthrough shader (pipeline) compilation stutter in Godot: precompilation warm-up scenes, the pipeline compilation monitors, and the export Shader Baker."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Pipeline compilation stutter
|
|
7
|
+
|
|
8
|
+
Target Godot 4.7+ (mechanisms introduced in 4.4/4.5).
|
|
9
|
+
|
|
10
|
+
## Scope
|
|
11
|
+
|
|
12
|
+
- Ubershaders and pipeline precompilation exist only in **Forward+ and Mobile** (Vulkan / D3D12 / Metal). **Compatibility** (OpenGL/WebGL) has none of it — there the only fix is the legacy one: show the material/shader/particle in the view frustum for at least one frame during loading.
|
|
13
|
+
- Two distinct steps: *shader compilation* = GLSL → intermediate (SPIR-V/DXIL/MIL); *pipeline compilation* = intermediate → GPU pipeline, done on the user's machine, cached by the driver, and wiped when the driver updates. Stutter comes from the pipeline step. Do not test for stutter on a warm driver cache.
|
|
14
|
+
- 4.4+ precompiles automatically, but only from evidence the RenderingServer has already seen. Anything first loaded, instanced, or toggled **during gameplay** can still compile then.
|
|
15
|
+
|
|
16
|
+
## Reading the monitors
|
|
17
|
+
|
|
18
|
+
Debugger → Monitors → pipeline compilations. Values only ever increase (deleted pipelines are not subtracted). Spikes outside loading screens are the stutters your players will hit.
|
|
19
|
+
|
|
20
|
+
- **Canvas** — 2D drawing. No precompilation exists for 2D; the first draw of a 2D node always compiles.
|
|
21
|
+
- **Mesh** — 3D mesh load. Load meshes on a background thread to hide it. Node-level modifiers such as material overrides are *not* covered here.
|
|
22
|
+
- **Surface** — first frame after 3D nodes were added to the tree, visible or not. A gameplay spike here almost always means a rendering feature was not enabled early enough (see below).
|
|
23
|
+
- **Draw** — an ubershader was missing at draw time. Should never happen; report it upstream with a minimal reproduction project.
|
|
24
|
+
- **Specialization** — background optimization. Cannot stutter; many per frame can cost framerate.
|
|
25
|
+
|
|
26
|
+
## Warm up rendering features early
|
|
27
|
+
|
|
28
|
+
Precompilation only enables a feature's pipeline variants for meshes/surfaces created *after* the feature is first seen. Put a trivial scene that uses every feature the game needs **before** the bulk of assets load — offscreen is fine (behind a `ColorRect`, or a `SubViewport` outside the window).
|
|
29
|
+
|
|
30
|
+
Features that must be seen first: 3D MSAA level, `ReflectionProbe`, separate specular (subsurface scattering, compositor effects sampling specular), motion vectors (TAA, FSR2, motion blur), normal/roughness (SDFGI, VoxelGI, SSR, SSAO, SSIL, `normal_roughness_buffer` in a custom shader or `CompositorEffect`), `LightmapGI` with a baked lightmap, `VoxelGI`, SDFGI on the `WorldEnvironment`, multiview (XR), shadow depth precision (16/32-bit), and omni shadow mode — dual paraboloid and cubemap (default) are separate variants.
|
|
31
|
+
|
|
32
|
+
Changing any of these at runtime restarts compilation and stutters immediately. Confine changes to a settings screen behind a loading screen. Only one MSAA level is tracked at a time, so different levels on different viewports stutter unavoidably.
|
|
33
|
+
|
|
34
|
+
## Warm up dynamically instanced effects
|
|
35
|
+
|
|
36
|
+
Preloading a `PackedScene` is not enough — the pipelines need the scene *instanced in the tree at least once*, even hidden or off-camera. For effects spawned by gameplay (bullets, explosions, hit VFX), attach a hidden instance to something guaranteed to exist, e.g. the player. Enable **Editable Children** on that instance to disable its script and hide sub-nodes that would otherwise act.
|
|
37
|
+
|
|
38
|
+
## Shader baker (export)
|
|
39
|
+
|
|
40
|
+
Export preset → Shader Baker → Enabled. Bakes source into the **intermediate** format only, skipping the shader compilation step at runtime; it does **not** bake pipelines and does **not** fix existing stutter — it cuts first-launch load time, most on D3D12 and Metal. Costs a longer export and a few MB of PCK.
|
|
41
|
+
|
|
42
|
+
Bakes only for the driver in `rendering/rendering_device/driver` for that target, and only for drivers the editor's own host OS supports: Windows → Vulkan + D3D12, macOS → Vulkan + Metal, Linux/Android → Vulkan. No effect on Compatibility (so none on web), and unsupported when exporting with `--headless`.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: godot-tween
|
|
3
|
+
description: "Write, debug, or review Godot Tween sequences: property/method/callback tweening, parallel steps, looping, and lifetime."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Tween animation
|
|
7
|
+
|
|
8
|
+
Target Godot 4.7+.
|
|
9
|
+
|
|
10
|
+
## Lifetime
|
|
11
|
+
|
|
12
|
+
- `Tween` is one-shot: it auto-starts on the next frame and goes invalid once finished or killed. There is no replay — build a new one per playback.
|
|
13
|
+
- Two live tweens on one property both keep writing it and the newest-created one wins the final value, so kill before rebuilding:
|
|
14
|
+
|
|
15
|
+
```gdscript
|
|
16
|
+
var _tween: Tween
|
|
17
|
+
|
|
18
|
+
func flash() -> void:
|
|
19
|
+
if _tween:
|
|
20
|
+
_tween.kill()
|
|
21
|
+
_tween = create_tween()
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
- `Node.create_tween()` binds to that node: it halts while the node is outside the tree and dies with it. `get_tree().create_tween()` is unbound and outlives the animated node until `kill()` — add `.bind_node(self)` unless that is deliberate.
|
|
25
|
+
- Guard `create_tween()` in `_process()`/`_physics_process()`; unguarded it appends a new tween every frame.
|
|
26
|
+
- `is_valid()` means "still in the scene tree", not "unfinished"; `is_running()` means "not paused and not finished".
|
|
27
|
+
|
|
28
|
+
## Sequencing
|
|
29
|
+
|
|
30
|
+
- Tweeners run sequentially by default. `parallel()` merges the next tweener into the previous step; `set_parallel(true)` does that for every following tweener until `chain()`. Both also pull in the tweener added _immediately before_ them.
|
|
31
|
+
|
|
32
|
+
```gdscript
|
|
33
|
+
var tween := create_tween().set_parallel()
|
|
34
|
+
tween.tween_property(self, "position", target, 0.4)
|
|
35
|
+
tween.tween_property(self, "modulate:a", 0.0, 0.4) # simultaneous
|
|
36
|
+
tween.chain().tween_callback(queue_free) # after both
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Tweeners
|
|
40
|
+
|
|
41
|
+
- `tween_property(object, property, final_val, duration)` — `property` takes a component path (`"position:x"`, `"modulate:a"`).
|
|
42
|
+
- The start value is read **when that tweener starts**, not when it is created. `.from(value)` overrides it, `.from_current()` re-reads the live value at start, `.as_relative()` makes `final_val` an offset.
|
|
43
|
+
- `tween_method(callable, from, to, duration)` for anything without a property; plus `tween_callback(callable)`, `tween_interval(seconds)`, and `tween_subtween(tween)` to nest a prebuilt tween as one step.
|
|
44
|
+
- Godot 4.7: `tween_await(signal)` holds the sequence until the signal fires; add `.set_timeout(seconds)` when emission is not guaranteed.
|
|
45
|
+
- `set_trans()`/`set_ease()`/`set_delay()` on the `Tween` set defaults for its tweeners; the same calls on a returned tweener override them for that tweener alone.
|
|
46
|
+
|
|
47
|
+
## Timing and pausing
|
|
48
|
+
|
|
49
|
+
- Default process mode is `TWEEN_PROCESS_IDLE`. Use `set_process_mode(Tween.TWEEN_PROCESS_PHYSICS)` when tweening physics-body state so updates land in the physics step.
|
|
50
|
+
- Default pause mode is `TWEEN_PAUSE_BOUND` (follows the bound node's `process_mode`); pause-menu tweens need `TWEEN_PAUSE_PROCESS` to survive `SceneTree.paused`.
|
|
51
|
+
- `set_speed_scale()` scales the tween alone; `set_ignore_time_scale(true)` detaches it from `Engine.time_scale`.
|
|
52
|
+
- `set_loops()` with no argument loops forever. Give every iteration real duration — a zero-duration looped sequence is force-stopped after a few loops.
|
|
53
|
+
|
|
54
|
+
## Signals
|
|
55
|
+
|
|
56
|
+
- `finished` is never emitted by an infinitely looping tween.
|
|
57
|
+
- `loop_finished(loop_count)` skips the final loop; use `finished` for that one.
|
|
58
|
+
- `step_finished(idx)` fires per step, and a parallel group is one step.
|
|
59
|
+
- Every `Tweener` also has its own `finished`, so a mid-sequence step is awaitable: `await create_tween().tween_interval(2.0).finished` is the idiomatic timed wait.
|
|
60
|
+
|
|
61
|
+
## Manual interpolation
|
|
62
|
+
|
|
63
|
+
`Tween.interpolate_value(initial_value, delta_value, elapsed_time, duration, trans, ease)` is static — hand-driven easing with no tween object. `delta_value` is `final - initial`, not the final value.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: godot-ui
|
|
3
|
+
description: "Build, debug, or review responsive Godot Control layouts, sizing, and themes."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Godot UI
|
|
7
|
+
|
|
8
|
+
Target Godot 4.7+.
|
|
9
|
+
|
|
10
|
+
## Container layout
|
|
11
|
+
|
|
12
|
+
- A `Container` arranges its direct `Control` children and overwrites manual rectangle changes when it sorts them. Configure the container and per-axis size flags; insert a non-`Container` wrapper for independently placed descendants.
|
|
13
|
+
- `SIZE_EXPAND` claims surplus space; `SIZE_FILL` stretches within the allocated slot. Shrink flags align instead, and `size_flags_stretch_ratio` only weights expanding siblings.
|
|
14
|
+
- Prefer content-derived minimum sizes. Use `custom_minimum_size` only as a hard floor.
|
|
15
|
+
- Godot 4.7: `custom_maximum_size` defaults to `Vector2(-1, -1)` (unbounded); a positive axis caps that axis, and maximum size takes priority over minimum size. Use `get_bound_minimum_size()`, not `get_combined_minimum_size()`, when a cap applies.
|
|
16
|
+
- For dynamic intrinsic constraints, override `_get_minimum_size()` or 4.7's `_get_maximum_size()`. Call `update_minimum_size()` after a minimum change or `update_maximum_size()` after a maximum change; the latter also invalidates minimum-size caches. These virtuals are not called when the script is attached to a built-in subtype that already overrides the corresponding size.
|
|
17
|
+
|
|
18
|
+
## Anchors and responsiveness
|
|
19
|
+
|
|
20
|
+
- Anchors are parent-relative ratios; offsets are pixel distances from them. Use them only under a non-`Container` parent. To reset both together, use `set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)` rather than changing anchors while leaving stale offsets.
|
|
21
|
+
- Leave `layout_direction` at `LAYOUT_DIRECTION_INHERITED` so forced or locale-derived parent direction propagates. Application-locale RTL requires a valid translation for that locale (or the configured fallback).
|
|
22
|
+
|
|
23
|
+
## Theme
|
|
24
|
+
|
|
25
|
+
- A `Theme` propagates only through an uninterrupted chain of `Control` or `Window` descendants. A child theme merges with inherited items and wins where it defines the same item.
|
|
26
|
+
- Use theme type variations for reusable semantic variants and per-node overrides only for one-offs. Custom controls should read items through `get_theme_*()`.
|
|
27
|
+
- Refresh cached theme-derived values on `NOTIFICATION_THEME_CHANGED`; it is also sent when the node enters the scene tree.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: godot-visibility-ranges-3d
|
|
3
|
+
description: "Set up manual/hierarchical LOD (HLOD) in Godot with visibility ranges: begin/end distances and margins, fade modes and their Forward+-only limitation, visibility_parent dependency trees, and the dithering alternative."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Visibility ranges (HLOD)
|
|
7
|
+
|
|
8
|
+
Target Godot 4.7+.
|
|
9
|
+
|
|
10
|
+
Manual LOD on any `GeometryInstance3D` — `MeshInstance3D`, `MultiMeshInstance3D`, `GPUParticles3D`, `CPUParticles3D`, `Label3D`, `Sprite3D`, `AnimatedSprite3D`, `CSGShape3D` — so levels can change node type (mesh up close, `Sprite3D` impostor far away).
|
|
11
|
+
|
|
12
|
+
Pick this over automatic **mesh LOD** when levels are artist-authored, when one merged node should replace a group of nodes (fewer draw calls at distance, per-node culling up close), or to fade objects out entirely (distant `Label3D`s). Both can run on the same mesh.
|
|
13
|
+
|
|
14
|
+
## Properties
|
|
15
|
+
|
|
16
|
+
All on `GeometryInstance3D`; distance is camera → **center of the instance's AABB**, in 3D units:
|
|
17
|
+
|
|
18
|
+
- `visibility_range_begin` — hidden when *closer* than this. `0.0` disables the check.
|
|
19
|
+
- `visibility_range_end` — hidden when *farther* than this. `0.0` disables the check.
|
|
20
|
+
- `visibility_range_begin_margin` / `visibility_range_end_margin` — hysteresis or fade distance, meaning depends on fade mode. `0.0` makes fade mode irrelevant.
|
|
21
|
+
- `visibility_range_fade_mode`.
|
|
22
|
+
|
|
23
|
+
Center-of-AABB, not screen space: unlike mesh LOD's `threshold_pixels`, these distances are **not** compensated for FOV or viewport resolution, and a large mesh switches based on its center even when its near edge is right in front of the camera. Split oversized meshes.
|
|
24
|
+
|
|
25
|
+
Two nodes, sphere up close and box beyond 10 units, with 1 unit of hysteresis:
|
|
26
|
+
|
|
27
|
+
```gdscript
|
|
28
|
+
$Sphere.visibility_range_end = 10.0
|
|
29
|
+
$Sphere.visibility_range_end_margin = 1.0
|
|
30
|
+
$Box.visibility_range_begin = 10.0
|
|
31
|
+
$Box.visibility_range_begin_margin = 1.0
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Fade modes
|
|
35
|
+
|
|
36
|
+
- `VISIBILITY_RANGE_FADE_DISABLED` (default) — instant switch, margins act as hysteresis so the LOD does not flip back and forth at the boundary. Fastest; stays opaque.
|
|
37
|
+
- `VISIBILITY_RANGE_FADE_SELF` — alpha-fades *itself* out at its own range limits.
|
|
38
|
+
- `VISIBILITY_RANGE_FADE_DEPENDENCIES` — alpha-fades in its `visibility_parent` dependents instead. Identical to `SELF` unless a dependency tree exists.
|
|
39
|
+
|
|
40
|
+
Both fade modes force transparent rendering during the transition — a real cost, and subject to [transparency sorting](https://docs.godotengine.org/en/latest/tutorials/3d/3d_rendering_limitations.html) glitches.
|
|
41
|
+
|
|
42
|
+
**Forward+ only.** On Mobile and Compatibility they behave like `DISABLED` *with hysteresis disabled* — so a project targeting mobile gets harder popping from a fade mode than from leaving it off. Prefer `DISABLED` with margins there.
|
|
43
|
+
|
|
44
|
+
## visibility_parent
|
|
45
|
+
|
|
46
|
+
`visibility_parent` is a `NodePath` on **Node3D**, so it applies to that node *and all its descendants*. Target must be a `GeometryInstance3D`; it need not be an actual scene-tree parent, but pointing at a descendant is a dependency cycle and errors in the Output panel.
|
|
47
|
+
|
|
48
|
+
Dependents are visible only while the parent (and every ancestor in the chain) is hidden by being closer than its own `visibility_range_begin`. So for a `BatchOfHouses` mesh replacing `House1..4` at distance, configure only two things: `visibility_range_begin` on `BatchOfHouses`, and `visibility_parent` on the houses — no `visibility_range_end` on each house to keep in sync.
|
|
49
|
+
|
|
50
|
+
Setting `visible = false` removes a node from the dependency tree entirely: its dependents then ignore its `visibility_range_begin` and stay visible.
|
|
51
|
+
|
|
52
|
+
## Dithering instead of alpha
|
|
53
|
+
|
|
54
|
+
Cheaper than alpha fade and free of sorting glitches, at the cost of a visible noise pattern (hidden well by TAA or high resolution). Two LODs only, since `BaseMaterial3D.distance_fade` fades either near or far, not both:
|
|
55
|
+
|
|
56
|
+
- Margins `0.0` on both nodes; extend the ranges by the fade distance instead — *decrease* `visibility_range_begin`, *increase* `visibility_range_end` — or the dither never shows.
|
|
57
|
+
- Near material: `distance_fade` = **Object Dither**, `distance_fade_min_distance` = its `visibility_range_end`, `distance_fade_max_distance` = that minus the fade distance.
|
|
58
|
+
- Far material: **Object Dither**, min = its `visibility_range_begin`, max = that plus the fade distance.
|
|
59
|
+
|
|
60
|
+
## Cheaper distant materials
|
|
61
|
+
|
|
62
|
+
LOD meshes cut vertices, not per-pixel shading, which is the usual GPU bottleneck. On distant LOD materials disable Normal Map (especially mobile), Rim, Clearcoat, Anisotropy, Height, Subsurface Scattering, Back Lighting, Refraction, Proximity Fade. Measure: each *unique* material has its own cost, so the trade is not automatically a win.
|