# Bevy Skill Research: Camera, Rendering & Common Gotchas > **Bevy version**: 0.18, with 0.19 notes > **Date**: 2026-07-07 ## Camera Setup **Camera3d** is a marker component with `#[require(...)]` that auto-inserts: `Camera`, `DebandDither`, `CameraRenderGraph`, `Projection`, `Tonemapping`, `ColorGrading`, `Exposure`. Just spawning `Camera3d` gives a working 3D camera. Coordinate system: right-handed, X-right, Y-up, Z-back (forward = -Z). **0.18 change**: `RenderTarget` moved from a `Camera` field to a separate required component. **0.19 change**: `Hdr` moved from `bevy_render` to `bevy_camera`; `screen_space_specular_transmission_*` fields extracted into `ScreenSpaceTransmission` component. ## Camera Ordering / Multiple Cameras - `Camera.order` (isize) controls render order. Higher values render later (on top) - Each camera with `is_active: true` renders independently - `ClearColorConfig` controls whether a camera clears the previous output - For overlays: use `ClearColorConfig::None` on higher-order cameras ## Camera Gotchas - Default `far` plane is 1000 units — objects beyond are culled silently - Forward is **-Z**, not +Z - Viewport coordinates are in physical pixels, not logical - GLTF models use +Z as visual forward (opposite Bevy convention) ## Coordinate System | Axis | Bevy native | GLTF models | |------|-------------|-------------| | Forward | -Z (`Transform::forward()`) | +Z (`Transform::back()`) | | Up | +Y | +Y | | Right | +X | -X (from camera perspective) | **GLTF orientation fix**: Use `look_to(-velocity_dir, Vec3::Y)` (negate direction) or read `transform.back()` for the model's visual forward. ## Rendering / Mesh / Material Gotchas - **Mesh requires normals** for `StandardMaterial`. Missing normals = invisible mesh, no error - **GLTF scenes** must be spawned with scene labels (`"model.gltf#Scene0"`), not the raw asset handle - **Back-face culling** is on by default; incorrect vertex winding = invisible faces - **Visibility hierarchy**: parent entities need visibility components or children are invisible - **0.18**: Mesh access methods became `try_*` returning `Result<_, MeshAccessError>`. `MaterialPlugin` fields (`prepass_enabled`, `shadows_enabled`) became `Material` trait methods - **0.19**: GLTF material loading returns `GltfMaterial` (use `#Material0/std` suffix for `StandardMaterial`). `Assets::get_mut` returns `AssetMut` with mutation tracking. `Scene`/`SceneRoot` renamed to `WorldAsset`/`WorldAssetRoot` ## Transform Gotchas - Bevy: +Y up, -Z forward, right-handed - `Transform::forward()` returns -Z, `Transform::back()` returns +Z - Transform propagation runs in **PostUpdate** — reading `GlobalTransform` in `Update` gives stale (last-frame) values - **0.18**: `GltfPlugin` coordinate conversion restructured — `use_model_forward_direction` replaced by `convert_coordinates` with `rotate_scene_entity` and `rotate_meshes` flags ## Asset Loading Gotchas - Assets are NOT ready immediately after `asset_server.load()`. Must check `AssetEvent::LoadedWithDependencies` or use `AssetServer::is_loaded_with_dependencies()` - **0.18**: `LoadContext::path()` returns `AssetPath` not `Path`. Image `reinterpret_*` methods return `Result` - **0.19**: `AssetPath::resolve()`/`resolve_embed()` take `&AssetPath` not `&str`; string variants renamed to `resolve_str()`/`resolve_embed_str()` ## Lighting Gotcha **`AmbientLight` split (0.18)**: - `GlobalAmbientLight` — resource, affects everything - `AmbientLight` — camera component, per-camera ambient ## General LLM Traps These are patterns where LLMs most commonly produce incorrect code: | Trap | Wrong | Correct | |------|-------|---------| | Parent component | `Parent` | `ChildOf(entity)` (0.16+) | | Child iteration | `children.iter().copied()` | `children.into_iter()` | | Event writer | `EventWriter` | `MessageWriter` (0.18+) | | Event reader | `EventReader` | `MessageReader` (0.18+) | | Run condition | `on_event::()` | `on_message::()` (0.18+) | | Despawn recursive | `despawn_recursive()` | `despawn()` (auto-recursive 0.16+) | | Bundles | `#[derive(Bundle)]` | Tuples + `#[require(...)]` (0.16+) | | WinitPlugin | `WinitPlugin::` | `WinitPlugin` (not generic 0.18+) | | GltfPlugin | `init_asset::()` | `GltfPlugin::default()` (0.18+) | | Material bind group | `@group(2)` in shader | `@group(3)` (group 2 is mesh in 0.18) | | System error handling | Return nothing | `-> Result` (0.18+) | | GlobalTransform timing | Read in Update | Read in PostUpdate or accept lag | | Same-state transition | `state.set(current)` no-ops | Triggers OnExit/OnEnter in 0.18; use `set_if_neq()` | ### 0.19-Specific Traps | Trap | Detail | |------|--------| | Resources as components | `Query` now matches resource entities — filter with `Without` | | Scene renamed | `Scene`/`SceneRoot` → `WorldAsset`/`WorldAssetRoot` | | Ref clone | `Ref.clone()` returns `Ref`, not inner value. Use `ref.deref().clone()` | | Assets::get_mut | Returns `AssetMut` with mutation tracking, not `&mut A` directly | | GLTF materials | Returns `GltfMaterial`, use `#Material0/std` suffix for `StandardMaterial` |