Fix project structure
This commit is contained in:
@@ -0,0 +1,602 @@
|
||||
# flutter_scene silent-failure traps
|
||||
|
||||
Mistakes that produce wrong pixels with no exception and no console message. Each entry gives the
|
||||
mistake, what you see, and what to do instead. Sorted worst-first (most likely to hit, hardest to
|
||||
diagnose from the symptom).
|
||||
|
||||
Some of these are now caught by the engine in version 0.22.0. Those are tagged **[0.22.0 catches
|
||||
this]** with what the engine does, so if you see that error you know what it means. The rest are
|
||||
still silent, so you have to recognize them yourself.
|
||||
|
||||
---
|
||||
|
||||
## 1. Editing a transform in place instead of assigning it
|
||||
|
||||
**Mistake.** `node.localTransform.setTranslation(v)`, `node.localTransform..rotateY(t)`,
|
||||
`node.position.x = 5`, or any edit of the matrix/vector a getter returns. This is the natural
|
||||
`vector_math` style and the first thing most people reach for.
|
||||
|
||||
**Symptom.** The node does not move. Not "moves wrong", nothing happens, forever, including its
|
||||
children and bounds. Reading `node.localTransform`/`node.position` back shows the value you wrote, so
|
||||
the state looks correct while the render disagrees.
|
||||
|
||||
**Do instead.** Assign a fresh value (`node.position = ...`, `node.localTransform = node.localTransform.clone()..translateByVector3(v)`),
|
||||
use the component setters `node.position`/`node.rotation`/`node.scale`, or edit the raw matrix
|
||||
through `node.mutateLocalTransform((m) => m.translateByVector3(v))`, which dirties the cache for you.
|
||||
|
||||
**[0.22.0 catches this]** Debug builds throw a `StateError` naming the node and the fix, both for an
|
||||
in-place `localTransform` edit and for editing a copy returned by `position`/`rotation`/`scale`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Passing a normal or metallic-roughness map as `TextureContent.color`
|
||||
|
||||
**Mistake.** `material.normalTexture = await Texture2D.fromAsset('brick_normal.png')` without
|
||||
`content: TextureContent.normal`. The `content` parameter defaults to `color`.
|
||||
|
||||
**Symptom.** The base mip is fine, so it looks right up close and progressively wrong with distance:
|
||||
normals flatten and skew, roughness reads too smooth at range, specular shimmers. A
|
||||
distance-dependent symptom is nearly the worst case for screenshot-driven iteration.
|
||||
|
||||
**Do instead.** Build non-color maps with the right content: `Texture2D.fromAsset(path, content:
|
||||
TextureContent.normal)` for normal maps, `TextureContent.data` for metallic-roughness, AO, and other
|
||||
linear data. Still silent, so this is on you.
|
||||
|
||||
---
|
||||
|
||||
## 3. Non-uniform scale on a lit mesh
|
||||
|
||||
**Mistake.** Any non-uniform scale on the node or an ancestor, e.g. `node.scale = Vector3(1, 3, 1)`.
|
||||
|
||||
**Symptom.** Lighting, specular, and reflections are wrong across the whole mesh. It reads as a
|
||||
shading or material bug, so it sends you into the materials, never the transform.
|
||||
|
||||
**Do instead.** Use a uniform scale, or bake the non-uniform scale into the geometry with
|
||||
`MeshData.transformed(matrix)` and build a fresh `MeshGeometry` from it. Still silent.
|
||||
|
||||
---
|
||||
|
||||
## 4. Moving a skinned mesh node
|
||||
|
||||
**Mistake.** `skinnedNode.localTransform = Matrix4.translation(v)` on a node that carries a `Skin`.
|
||||
|
||||
**Symptom.** The mesh does not move. Worse, it *does* move when the node you transformed happens to
|
||||
be an ancestor of the skeleton's joints, so it looks intermittent across models.
|
||||
|
||||
**Do instead.** glTF requires a skinned mesh node's own transform to be ignored, so the engine passes
|
||||
identity. Move the skeleton root (the common ancestor of `skin.joints`) instead, or parent both the
|
||||
mesh node and the skeleton under a shared node and move that. Still silent.
|
||||
|
||||
---
|
||||
|
||||
## 5. Replacing the transform of a runtime-imported model root
|
||||
|
||||
**Mistake.**
|
||||
```dart
|
||||
final model = await Node.fromGlbAsset('assets/ship.glb');
|
||||
model.localTransform = Matrix4.translation(v); // wipes the handedness flip
|
||||
```
|
||||
|
||||
**Symptom.** The model renders mirrored through Z (asymmetric geometry reversed, text backwards) with
|
||||
normals and IBL wrong for the mirrored orientation. It still draws and is not obviously broken.
|
||||
|
||||
**Do instead.** The runtime glTF importer synthesizes a root carrying a `scale(1, 1, -1)` handedness
|
||||
flip. Do not overwrite it. Parent that node under a new `Node` and transform the parent, or
|
||||
pre-multiply your transform by the existing `localTransform`. (The offline `.fscene`/`loadScene` path
|
||||
bakes handedness into the vertices, so its roots are identity and do not have this trap.) Still
|
||||
silent.
|
||||
|
||||
---
|
||||
|
||||
## 6. `EnvironmentMap.fromGpuTextures` with a raw panorama
|
||||
|
||||
**Mistake.**
|
||||
```dart
|
||||
final tex = await gpuTextureFromAsset('assets/panorama.png');
|
||||
scene.environment = EnvironmentMap.fromGpuTextures(prefilteredRadiance: tex);
|
||||
```
|
||||
|
||||
**Symptom.** Every reflective surface (and the sky) shows the top 1/8 of the panorama stretched 8x
|
||||
vertically, cross-fading between slices as roughness varies. Diffuse is fully black.
|
||||
|
||||
**Do instead.** `fromGpuTextures` expects an already-prefiltered radiance atlas, not a plain image.
|
||||
Run the source through `prefilterEquirectRadiance()` first, or just use
|
||||
`EnvironmentMap.fromEquirectImageAsset(assetPath: ...)`/`fromUIImages`, which prefilter and project
|
||||
SH for you. Still silent.
|
||||
|
||||
---
|
||||
|
||||
## 7. `ShaderMaterial(cullingMode: none)` for a double-sided custom material
|
||||
|
||||
**Mistake.** `ShaderMaterial(cullingMode: gpu.CullMode.none)`, or setting `doubleSided = true` on a
|
||||
`ShaderMaterial`. The two do not agree.
|
||||
|
||||
**Symptom.** `cullingMode.none` draws back faces in color but leaves them out of the depth prepass, so
|
||||
SSAO, SSR, and contact shadows sample the wrong surface exactly where a back face shows: dark halos,
|
||||
wrong reflections, occlusion bleeding through a leaf card. `doubleSided = true` does the opposite: the
|
||||
color pass still culls while the prepass draws the extra faces.
|
||||
|
||||
**Do instead.** For a truly double-sided `ShaderMaterial`, understand that the color cull and the
|
||||
prepass cull are driven separately today; keep the mesh single-sided where the depth-based effects
|
||||
need to match, or split it. Still silent.
|
||||
|
||||
---
|
||||
|
||||
## 8. Caller-supplied `bounds` that do not cover the geometry
|
||||
|
||||
**Mistake.** `MeshGeometry.fromArrays(positions: p, bounds: someAabb)` where the AABB does not contain
|
||||
every position, or `setLocalBounds` with a guessed or stale box.
|
||||
|
||||
**Symptom.** The mesh pops out of existence at some camera angles and reappears at others; part of a
|
||||
large mesh vanishes; shadows disappear before the caster does. Intermittent and view-dependent, so a
|
||||
single screenshot can look fine.
|
||||
|
||||
**Do instead.** Widen the bounds to cover every vertex, or just omit the `bounds` argument and let the
|
||||
constructor scan the positions. An absent bounds is safe (it means always-visible); only a wrong one
|
||||
is dangerous. Still silent.
|
||||
|
||||
---
|
||||
|
||||
## 9. Binding a mipless texture to a material
|
||||
|
||||
**Mistake.** `material.baseColorTexture = GpuTextureSource(await gpuTextureFromAsset('brick.png'))`.
|
||||
The helper's own doc even recommends this.
|
||||
|
||||
**Symptom.** Severe minification aliasing: crawling and shimmering on any surface at an angle or
|
||||
distance, sparkling on a metallic-roughness map. `Texture2D.fromAsset` on the same file looks fine, so
|
||||
two seemingly equivalent APIs disagree visually.
|
||||
|
||||
**Do instead.** Use `Texture2D.fromAsset`/`fromImage`/`fromPixels`, which generate a mip chain, or
|
||||
supply a texture you mipped yourself. Reserve `gpuTextureFromAsset` for non-material uses. Still
|
||||
silent.
|
||||
|
||||
---
|
||||
|
||||
## 10. `RenderView.layerMask`/`Node.layers` mismatch, or a zero mask
|
||||
|
||||
**Mistake.** Putting a node on a non-default layer and forgetting the view side (or vice versa), or
|
||||
`layerMask: 0`, or `Node.layers = 2` meaning to select "layer 2" (which is actually `1 << 2 == 4`).
|
||||
|
||||
**Symptom.** A node, a group, or the whole scene is simply absent, with no hint a mask is involved. A
|
||||
`layerMask` of 0 renders nothing.
|
||||
|
||||
**Do instead.** `Node.layers` is a bitmask and is NOT inherited by children, so set it on each node
|
||||
you want the view to see. Use `kRenderLayerAll` to see everything, or a bitmask like `(1 << 2)`.
|
||||
Match the view's `layerMask` to the nodes' `layers`. Still silent (but see #23 for the
|
||||
draws-nothing diagnostic).
|
||||
|
||||
---
|
||||
|
||||
## 11. Mutating a `TextureTransform` in place
|
||||
|
||||
**Mistake.** `material.baseColorTextureTransform.offset.x = 0.5` instead of assigning a fresh
|
||||
`TextureTransform`. Same shape as trap #1, for materials.
|
||||
|
||||
**Symptom.** UV scroll/rotation animation freezes at the first value, but ONLY for materials on the
|
||||
physical-variant path (any material with clearcoat/sheen/transmission/etc). The identical code works
|
||||
on a plain PBR material, so it reads as a shader bug.
|
||||
|
||||
**Do instead.** Assign a new transform each frame: `material.baseColorTextureTransform =
|
||||
TextureTransform(offset: ...)`. Still silent.
|
||||
|
||||
---
|
||||
|
||||
## 12. Environment image that is not 2:1 equirectangular
|
||||
|
||||
**Mistake.** Passing a cube cross, a 1:1 angular light probe, or a cropped panorama to any environment
|
||||
entry point. HDRI downloads are not reliably 2:1.
|
||||
|
||||
**Symptom.** The scene is lit from wildly wrong directions, reflections show mirrored or duplicated
|
||||
content, the sky is smeared.
|
||||
|
||||
**Do instead.** Re-project the source to a 2:1 latitude-longitude panorama before loading. Cube
|
||||
crosses and angular probes are not supported. Still silent.
|
||||
|
||||
---
|
||||
|
||||
## 13. Hand-built triangles wound clockwise
|
||||
|
||||
**Mistake.** Generating triangles with clockwise winding instead of the standard Counter-Clockwise (CCW)
|
||||
right-handed convention when feeding `MeshGeometry.fromArrays` or `GeometryBuilder`.
|
||||
|
||||
**Symptom.** The mesh is invisible from outside and visible from inside; a closed shape looks hollow
|
||||
or inside-out; lighting is inverted where it shows. ("See-through faces.")
|
||||
|
||||
**Do instead.** flutter_scene's front faces wind COUNTER-CLOCKWISE (CCW) in model space, matching glTF
|
||||
and standard 3D conventions. Ensure triangle indices wind CCW around the outward face normal, or omit
|
||||
`normals` and let `GeometryBuilder` derive them from your winding. Still silent.
|
||||
|
||||
---
|
||||
|
||||
## 14. Out-of-range indices in `fromArrays`
|
||||
|
||||
**Mistake.** `MeshGeometry.fromArrays(positions: p /* 100 verts */, indices: [0, 1, 100])`, e.g. from
|
||||
an off-by-one or an index list built against a different vertex array.
|
||||
|
||||
**Symptom.** Stray triangles stretching to the origin or infinity, holes, flicker. On some backends
|
||||
the fetch is clamped and on others it reads adjacent memory, so the symptom differs per backend.
|
||||
|
||||
**Do instead.** Keep every index in `0 .. vertexCount - 1`. (`GeometryBuilder.addTriangle` range-checks
|
||||
for you and throws; the `fromArrays` index path does not.) Still silent on the `fromArrays` path.
|
||||
|
||||
---
|
||||
|
||||
## 15. A `vertexCount` that does not match the buffer in `setVertices`
|
||||
|
||||
**Mistake.** `geometry.setVertices(bufferView, vertexCount)` where `vertexCount` is a byte count, a
|
||||
float count, or a triangle count rather than a vertex count.
|
||||
|
||||
**Symptom.** Too small: part of the mesh is missing. Too large: the draw reads past the buffer, giving
|
||||
stray geometry or a dropped draw depending on backend. The buffer is fine, so the investigation goes
|
||||
to the packing code.
|
||||
|
||||
**Do instead.** `vertexCount` is a count of vertices. Prefer `uploadVertexData` (which validates the
|
||||
stride, see #17) or `fromArrays` over the caller-managed `setVertices` path unless you really own the
|
||||
GPU buffer. Still silent.
|
||||
|
||||
---
|
||||
|
||||
## 16. Oversized texture on a low-end device
|
||||
|
||||
**Mistake.** `EnvironmentMap.fromEquirectImageAsset(assetPath: 'pano_16k.hdr', maxWidth: 16384)` or
|
||||
`EnvironmentMap.radianceCubeSize = 4096` on a device whose max texture size is lower.
|
||||
|
||||
**Symptom.** A completely black environment: no IBL, no reflections, black sky. Works on the dev
|
||||
machine, black on a phone.
|
||||
|
||||
**Do instead.** Keep environment and texture sizes within the device limit; lower `maxWidth` or
|
||||
`radianceCubeSize`. Test on the lowest-end target you support. Still silent.
|
||||
|
||||
---
|
||||
|
||||
## 17. Hand-packing vertex bytes at the wrong stride
|
||||
|
||||
**Mistake.** `SkinnedGeometry()..uploadVertexData(bytes, vertexCount, indices)` with the wrong stride
|
||||
(a common one is 96 bytes having forgotten UV1, or the legacy 80-byte layout).
|
||||
|
||||
**Symptom.** Washed-out colors, see-through faces, geometry smeared toward the origin.
|
||||
|
||||
**Do instead.** Unskinned vertices are 72 bytes (position 3, normal 3, tex_coords 2, tex_coords_1 2,
|
||||
color 4, tangent 4, all float32), skinned are 104 (+ joints 4, weights 4). Better, do not hand-pack:
|
||||
use `MeshGeometry.fromArrays`, `fromMeshData`, or `GeometryBuilder`.
|
||||
|
||||
**[0.22.0 catches this]** `uploadVertexData` on both `SkinnedGeometry` and `UnskinnedGeometry` now
|
||||
throws an `ArgumentError` when the byte length does not match `vertexCount * stride`, naming the
|
||||
expected layout.
|
||||
|
||||
---
|
||||
|
||||
## 18. Custom attribute length not matching the vertex count
|
||||
|
||||
**Mistake.** `geometry.setCustomAttribute('a_wind', data, components: 3)` where `data` has the wrong
|
||||
length, or set before uploading vertices, or not re-set after a `rebuild` changed the count.
|
||||
|
||||
**Symptom.** The attribute is read at the wrong stride, so every vertex gets a neighbor's value: a
|
||||
displacement shader shears the mesh, a color attribute smears. Nearly right, so hard to spot.
|
||||
|
||||
**Do instead.** `data.length` must equal `vertexCount * components`. Set the attribute after uploading
|
||||
vertices, and re-set it after any rebuild. Also note custom attributes are not fetched by depth/shadow
|
||||
passes, so an attribute-driven displacement will not show in shadows.
|
||||
|
||||
**[0.22.0 catches this]** `setCustomAttribute` now throws an `ArgumentError` on a length mismatch
|
||||
(once the vertex count is known).
|
||||
|
||||
---
|
||||
|
||||
## 19. `UnlitMaterial` with `AlphaMode.mask`
|
||||
|
||||
**Mistake.** `UnlitMaterial(colorTexture: foliage)..alphaMode = AlphaMode.mask` for cutout foliage.
|
||||
|
||||
**Symptom.** No alpha test. Cutout edges render soft and blended, the material goes through the
|
||||
translucent pass, writes no depth, sorts badly against itself, and casts no cutout shadow.
|
||||
|
||||
**Do instead.** `UnlitMaterial` does not implement `mask` (it behaves as `blend`). Use
|
||||
`PhysicallyBasedMaterial` for cutouts, or a `.fmat` unlit material that discards below your cutoff.
|
||||
Still silent.
|
||||
|
||||
---
|
||||
|
||||
## 20. `vertexColorWeight` on a material that took a physical variant
|
||||
|
||||
**Mistake.**
|
||||
```dart
|
||||
final m = PhysicallyBasedMaterial()..vertexColorWeight = 0.0;
|
||||
m.clearcoat = 1.0; // or sheen/transmission/anisotropy/ior != 1.5/any extension texture
|
||||
```
|
||||
|
||||
**Symptom.** Vertex colors snap back to full strength the moment an unrelated extension is enabled.
|
||||
On a vertex-colored import, an abrupt tint change with no plausible cause. (The same gap silently
|
||||
drops `specularAntiAliasingVariance` and `specularAntiAliasingThreshold` on the variant path.)
|
||||
|
||||
**Do instead.** Leave `vertexColorWeight` at 1.0 when using any advanced PBR feature, or drop the
|
||||
extension. Still silent.
|
||||
|
||||
---
|
||||
|
||||
## 21. Vertex-stage binding on a `ShaderMaterial` with no vertex shader
|
||||
|
||||
**Mistake.**
|
||||
```dart
|
||||
final m = ShaderMaterial(fragmentShader: frag);
|
||||
m.setUniformBlock('WaveInfo', bytes, stage: ShaderStage.vertex); // never set a vertex shader
|
||||
```
|
||||
|
||||
**Symptom.** The vertex-stage parameter has no effect; geometry stays undisplaced while the fragment
|
||||
stage looks right. Reads as "my vertex shader is not running."
|
||||
|
||||
**Do instead.** Pass a `vertexShader` (and `skinnedVertexShader`/`depthVertexShader` for those mesh
|
||||
kinds) to the constructor before binding vertex-stage blocks, or bind the block on
|
||||
`ShaderStage.fragment`. Still silent.
|
||||
|
||||
---
|
||||
|
||||
## 22. A `ShaderMaterial` vertex shader on line/trail/polyline geometry
|
||||
|
||||
**Mistake.** Attaching a `ShaderMaterial` that supplies a vertex shader to a `LineSegmentsGeometry`, a
|
||||
trail, or a polyline.
|
||||
|
||||
**Symptom.** Lines vanish or explode into garbage. The unskinned vertex shader is paired with the
|
||||
line-segments instanced layout and never does the ribbon expansion.
|
||||
|
||||
**Do instead.** These geometries do their vertex expansion in the engine's own shader; a material
|
||||
vertex shader cannot be used with them. Drop the vertex shader for line/trail/polyline geometry, or
|
||||
use a mesh geometry. Still silent.
|
||||
|
||||
---
|
||||
|
||||
## 23. Four different causes of a blank frame
|
||||
|
||||
**Mistake.** Any of: a degenerate camera (target equals position, or `up` parallel to the view
|
||||
direction, e.g. a top-down camera left at the default `up`), a field of view passed in degrees
|
||||
(`fovRadiansY: 60`), an inverted or zero frustum, `layerMask: 0`, a zero-area draw region, or
|
||||
rendering before `Scene.isReadyToRender`.
|
||||
|
||||
**Symptom.** The entire scene is empty. Every one of these looks identical, so it is easy to "fix"
|
||||
lighting, materials, and geometry for many iterations before suspecting the camera or the mask.
|
||||
|
||||
**Do instead.** For a top-down/bottom-up camera set `up` to `Vector3(0, 0, 1)` or `Vector3(0, 0,
|
||||
-1)`, not the default `(0, 1, 0)`. Pass FOV in radians (`60 * degrees2Radians`). Keep `near > 0` and
|
||||
`far > near`. Give the view a non-zero `layerMask` and a non-empty draw region.
|
||||
|
||||
**[0.22.0 catches most of this]** Degenerate cameras (zero view direction, parallel `up`, degrees-valued
|
||||
FOV, degenerate near/far) assert in debug. And a frame that issues zero draw calls now prints once in
|
||||
debug naming the likely cause (not ready, empty region, no views, no visible meshes, or a layer mask
|
||||
matching nothing).
|
||||
|
||||
---
|
||||
|
||||
## 24. Missing bounds after swapping a primitive's geometry
|
||||
|
||||
**Mistake.** `mesh.primitives[0].geometry = newGeometry` for hand LOD, a rebuilt procedural mesh, or a
|
||||
variant swap.
|
||||
|
||||
**Symptom.** The new geometry is culled against the old geometry's bounds; if it is larger or
|
||||
displaced, it pops in and out exactly like trap #8.
|
||||
|
||||
**Do instead.** Nothing extra is needed anymore.
|
||||
|
||||
**[0.22.0 catches this]** A `Mesh` now recomputes its bounds on its own when a primitive's geometry
|
||||
identity changes, so the manual `markLocalBoundsDirty()` is no longer required.
|
||||
|
||||
---
|
||||
|
||||
## 25. A `.fmat` material that overruns the 15-sampler budget
|
||||
|
||||
**Mistake.** A `lit` or `physical` `.fmat` declaring several `sampler2d` parameters plus
|
||||
`engine_inputs: [scene_color, scene_depth]`, on top of the lit framework's own textures.
|
||||
|
||||
**Symptom.** Geometry disappears on a mid-range Android device while everything is correct on Metal,
|
||||
with no build-time signal. The draw is rejected on GLES drivers reporting the 16-unit minimum.
|
||||
|
||||
**Do instead.** The lit fragment shader budgets 15 fragment samplers. Pack channels into one texture
|
||||
(an ORM-style atlas), drop an `engine_input`, or make the material unlit. Still silent (fails at
|
||||
runtime on the device, not at build).
|
||||
|
||||
---
|
||||
|
||||
## 26. `RenderView.viewport` with a `target` set
|
||||
|
||||
**Mistake.** `RenderView(camera: cam, target: myRenderTexture, viewport: Rect.fromLTWH(0, 0, 0.5, 1))`
|
||||
expecting a half-width render into the texture.
|
||||
|
||||
**Symptom.** The view fills the entire render texture; the passed `viewport` is ignored. Reads as
|
||||
"my viewport math is off."
|
||||
|
||||
**Do instead.** `viewport` is ignored when `target` is set. Size the `RenderTexture` to the region you
|
||||
want, or drop the target to render a sub-rect of the screen. Still silent.
|
||||
|
||||
---
|
||||
|
||||
## 27. Scaled or mirrored camera node
|
||||
|
||||
**Mistake.** Attaching a `CameraComponent` to a scaled node, or parenting a camera node under a scaled
|
||||
one.
|
||||
|
||||
**Symptom.** A uniform scale rescales the world in view; a negative scale mirrors the view, so every
|
||||
surface goes back-facing and the scene renders inside out. The camera's reported `forward`/`up` look
|
||||
correct, which makes it hard.
|
||||
|
||||
**Do instead.** A camera node must carry only rotation and translation, and no ancestor may be scaled.
|
||||
Still silent.
|
||||
|
||||
---
|
||||
|
||||
## 28. Hand-built `Skin` with mismatched joints and inverse-bind matrices
|
||||
|
||||
**Mistake.** `skin.joints.add(n)` without a matching `skin.inverseBindMatrices.add(...)` (both are
|
||||
plain mutable lists).
|
||||
|
||||
**Symptom.** Extra inverse bind matrices are silently ignored and the mesh deforms wrongly. (Too few
|
||||
throws a `RangeError`, so only the extra-matrices direction is silent.)
|
||||
|
||||
**Do instead.** Keep the two lists parallel: one inverse bind matrix per joint (`Matrix4.identity()`
|
||||
if the joint's rest pose is the mesh's model space). Imported skins are validated; hand-built ones are
|
||||
not. Still silent.
|
||||
|
||||
---
|
||||
|
||||
## 29. Cloning a mesh node whose skeleton is a sibling
|
||||
|
||||
**Mistake.** `meshNode.clone()` when the skeleton lives outside the cloned subtree.
|
||||
|
||||
**Symptom.** The clone renders collapsed or in bind-pose garbage. There is a `debugPrint`, but it says
|
||||
only "Index path formation failed" and names neither the skin nor the consequence.
|
||||
|
||||
**Do instead.** Clone the common ancestor of the mesh node and its skeleton, not the mesh node alone.
|
||||
Still effectively silent.
|
||||
|
||||
---
|
||||
|
||||
## 30. `updateInstanceTransforms(recomputeWinding: false)` with a mirroring edit
|
||||
|
||||
**Mistake.** Editing an instance transform to a negative determinant while asking the engine to skip
|
||||
the parity refresh.
|
||||
|
||||
**Symptom.** Those instances render inside out (front faces culled, back faces lit).
|
||||
|
||||
**Do instead.** Drop `recomputeWinding: false`, or keep every instance edit orientation-preserving
|
||||
(no negative/mirrored scale). Still silent.
|
||||
|
||||
---
|
||||
|
||||
## 31. Flipbook frame count vs atlas grid mismatch
|
||||
|
||||
**Mistake.** A `FlipbookModule(frameCount: 16)` without `emitter.flipbookColumns = 4;
|
||||
emitter.flipbookRows = 4`.
|
||||
|
||||
**Symptom.** Particles sample the wrong atlas cells, or only the first cell; the effect animates but
|
||||
shows the wrong art.
|
||||
|
||||
**Do instead.** Set `flipbookColumns * flipbookRows` equal to the module's `frameCount`. Still silent.
|
||||
|
||||
---
|
||||
|
||||
## 32. `LodComponent` blend bands overlapping
|
||||
|
||||
**Mistake.** A `blendRange` larger than the gap between adjacent LOD thresholds.
|
||||
|
||||
**Symptom.** An object sits permanently in the wrong cross-fade pair, dither-blending two levels that
|
||||
should not blend, or skipping a level.
|
||||
|
||||
**Do instead.** Keep `blendRange` smaller than the smallest gap between adjacent `screenSize`
|
||||
thresholds. Still silent.
|
||||
|
||||
---
|
||||
|
||||
## 33. `TextureAtlas` grid not matching its texture
|
||||
|
||||
**Mistake.** `TextureAtlas(columns: 16, rows: 16, tileSize: 32, padding: 2, baseColor: eightBySix)`
|
||||
where the grid does not match the image, or an out-of-range tile `index`.
|
||||
|
||||
**Symptom.** Every UV points at the wrong tile. With the default `repeat` addressing, an out-of-range
|
||||
index in release wraps to a different valid-looking tile rather than failing.
|
||||
|
||||
**Do instead.** Make the grid parameters produce exactly the texture's dimensions
|
||||
(`columns * (tileSize + 2*padding)` etc), keep tile indices in range, and set
|
||||
`TextureSampling.maxMipmapLevels` so tiles do not bleed across the padding gutter at high mips. Still
|
||||
silent.
|
||||
|
||||
---
|
||||
|
||||
## 34. `useEnvironment` sky with no cube-radiance variant
|
||||
|
||||
**Mistake.** `ShaderSkySource(fragmentShader: myShader, useEnvironment: true)` with
|
||||
`radianceCubeFragmentShader` left null.
|
||||
|
||||
**Symptom.** The sky contributes no image-based specular on any backend that builds the cube layout
|
||||
(the default nearly everywhere), so the scene loses its reflections.
|
||||
|
||||
**Do instead.** Supply `radianceCubeFragmentShader`, the entry built with
|
||||
`FLUTTER_SCENE_RADIANCE_CUBE`. Debug builds warn about this at bind; release builds are silent, so do
|
||||
not rely on the warning.
|
||||
|
||||
---
|
||||
|
||||
## 35. `radianceCubeFragmentShader` that is not the cube build
|
||||
|
||||
**Mistake.** `ShaderMaterial(fragmentShader: f, radianceCubeFragmentShader: f)` (the same shader
|
||||
twice), or naming the non-cube entry as the cube twin.
|
||||
|
||||
**Symptom.** The engine binds a cubemap into a shader whose sampler is a `sampler2D`: nothing on some
|
||||
backends, garbage specular on others.
|
||||
|
||||
**Do instead.** The cube variant must be the entry compiled with `FLUTTER_SCENE_RADIANCE_CUBE`, whose
|
||||
`prefiltered_radiance` sampler is a `samplerCube`. Pass the distinct `...Cube` entry from your bundle.
|
||||
(The engine can only catch the identical-shader case, and only in debug.) Still effectively silent.
|
||||
|
||||
---
|
||||
|
||||
## 36. Reading `int`/`bool`/`uint` shader members through `setUniformBlockFromFloats`
|
||||
|
||||
**Mistake.** `setUniformBlockFromFloats('FragInfo', [1.0, 0.5])` where the shader declares `int mode;
|
||||
float amount;`.
|
||||
|
||||
**Symptom.** The shader reads `mode` as the float bit pattern of `1.0` (a huge integer), so every
|
||||
`if (mode == 1)` branch misses and the material takes its fallback path. The block size is correct, so
|
||||
nothing complains.
|
||||
|
||||
**Do instead.** Pack integer members with `ByteData.setInt32` at the member's offset, or use a `.fmat`
|
||||
material whose `MaterialParameters` type-checks every assignment. Unenforceable at runtime.
|
||||
|
||||
---
|
||||
|
||||
## 37. A custom fragment shader that tone-maps or writes straight alpha
|
||||
|
||||
**Mistake.** Ending a `ShaderMaterial`/`ShaderSkySource`/`beforeTonemap` `PostEffect` fragment
|
||||
shader with `frag_color = vec4(color, alpha)` (straight alpha) or `pow(color, vec3(1.0/2.2))`
|
||||
(gamma-encoded).
|
||||
|
||||
**Symptom.** Straight alpha gives edge halos and over-bright overlaps. sRGB output is tone-mapped and
|
||||
EOTF-encoded a second time by the resolve pass, giving washed-out low-contrast color that looks like a
|
||||
bad exposure.
|
||||
|
||||
**Do instead.** Output linear HDR premultiplied by alpha. Exposure, tone mapping, and the display
|
||||
encode are applied later by the full-screen resolve pass. When sampling an sRGB texture, linearize
|
||||
first. `.fmat` materials get the premultiply for free. Unenforceable at runtime.
|
||||
|
||||
---
|
||||
|
||||
## 38. `MaterialParameters.copyStateFrom` across a changed layout
|
||||
|
||||
**Mistake.** Applying a re-realized material onto a live instance whose shader layout changed (an
|
||||
editor hot reload where the `.fmat` gained or lost a parameter).
|
||||
|
||||
**Symptom.** Every parameter reverts to its sidecar default while `assignedValues` still reports your
|
||||
overrides, so the inspector shows the right numbers and the render shows the wrong ones.
|
||||
|
||||
**Do instead.** `copyStateFrom` needs both sides to come from the same compiled shader entry; remap by
|
||||
name through `updateFromMetadata` across a layout change instead. Still silent.
|
||||
|
||||
---
|
||||
|
||||
## 39. Environment or widget textures with sub-255 alpha
|
||||
|
||||
**Mistake.** Passing an equirect image carrying alpha below 255 (an unfilled sky dome, a masked
|
||||
panorama) to `fromUIImages`/`fromEquirectImageAsset`. Or, for `WidgetTexture`/`WidgetComponent`,
|
||||
simply using a widget with anti-aliased or translucent edges.
|
||||
|
||||
**Symptom.** For environments, diffuse ambient comes out darker than the specular reflections of the
|
||||
same environment, so objects look lit by two environments. For widget textures, dark halos around
|
||||
anti-aliased text and rounded corners on the zero-copy path (correct on the web readback path, so it
|
||||
reads as a platform quirk).
|
||||
|
||||
**Do instead.** Use opaque (alpha 255) environment sources. The widget-alpha double-multiply is a
|
||||
backend difference you cannot fully control from the API; keep widget content opaque where you can.
|
||||
Still silent.
|
||||
|
||||
---
|
||||
|
||||
## Now caught by the engine, in one place
|
||||
|
||||
For quick reference, these traps became loud in 0.22.0. If you hit one you get an error, not silent
|
||||
wrong pixels:
|
||||
|
||||
- In-place edit of `localTransform`/`position`/`rotation`/`scale` -> throws in debug (#1).
|
||||
- Degenerate camera and a frame that draws nothing -> asserts/prints once in debug (#23).
|
||||
- `uploadVertexData` and `setCustomAttribute` length mismatches -> throw always (#17, #18).
|
||||
- A `Mesh` whose primitive geometry is swapped -> recomputes bounds itself (#24).
|
||||
- An `AnimationClip` binding zero of its channels -> asserts in debug naming the wanted nodes.
|
||||
- A web-backend bind to a shader uniform/texture name the shader does not declare -> throws (matches
|
||||
native), instead of silently sampling whatever was bound last.
|
||||
- Also fixed outright: `Node.clone()` sharing the original's matrix, the skinning joints texture being
|
||||
too narrow for small joint counts, and `ParticleSystem.reset()` not restarting its random stream.
|
||||
Reference in New Issue
Block a user