Fix project structure
This commit is contained in:
@@ -0,0 +1,474 @@
|
||||
# What exists in flutter_scene
|
||||
|
||||
Complete public API inventory (package version 0.22.0). flutter_scene has lights, shadows, PBR
|
||||
materials, instancing, LOD, skeletal animation, and a full post-processing stack. If you think a
|
||||
feature is missing, it is almost certainly here under the name below. Look before you hand-roll.
|
||||
|
||||
The public surface is the explicit `show` lists in `lib/scene.dart` (plus the separate barrels
|
||||
`gpu.dart`, `fscene.dart`, `build_hooks.dart`, `physics.dart`, `audio.dart`). Nothing under
|
||||
`lib/src` is public unless a barrel shows it.
|
||||
|
||||
Import:
|
||||
```dart
|
||||
import 'package:flutter_scene/scene.dart';
|
||||
import 'package:vector_math/vector_math.dart' as vm; // NOT vector_math_64
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Node and scene graph
|
||||
|
||||
`Node` (`base class Node implements SceneGraph`). Construct `Node({String name = '', Matrix4?
|
||||
localTransform, Mesh? mesh})`. A non-null `mesh` is wrapped in a `MeshComponent`.
|
||||
|
||||
Transform API (0.22.0 added the component properties; older docs say only `localTransform` exists):
|
||||
|
||||
| Member | Type | Notes |
|
||||
| --- | --- | --- |
|
||||
| `position` | `Vector3` get/set | Getter returns a copy; editing the copy in place throws in debug. Assign to move. |
|
||||
| `rotation` | `Quaternion` get/set | Same copy rule. |
|
||||
| `scale` | `Vector3` get/set | Same copy rule. |
|
||||
| `localTransform` | `Matrix4` get/set | Getter returns the LIVE matrix; in-place edit throws in debug on next read. Assign a fresh matrix. |
|
||||
| `mutateLocalTransform(void Function(Matrix4) edit)` | method | Edits in place AND dirties the cache. Correct raw-matrix path. |
|
||||
| `globalTransform` | `Matrix4` get/set | Cached world transform; setter solves for the needed local. |
|
||||
| `lookAt(target, {up})` | method | Orients the node's forward axis (local +Z) at a world-space target; preserves world position and scale. |
|
||||
| `lookAtFrom(eye, target, {up})` | method | Positions at `eye` and aims +Z at `target` in one call (the imperative camera one-liner). |
|
||||
| `Node.lookAtTransform(eye, target, {up})` | static -> `Matrix4` | The `lookAt` basis as a local transform, for `Node(localTransform:)` and declarative `transform:`. |
|
||||
|
||||
+Z is the forward axis engine-wide (cameras, directional/spot lights, imported models), so the
|
||||
lookAt helpers aim any of them. Compose a plain matrix with `vm.Matrix4.translation(v)`,
|
||||
`vm.Matrix4.rotationY(a)`, `vm.Matrix4.compose(t, q, s)`. There is no `translate`/`rotateX` on Node.
|
||||
|
||||
Hierarchy (`SceneGraph` is a mixin): `add`, `addAll`, `addMesh`, `remove`, `removeAll`. `add` throws
|
||||
if the child already has a parent. `parent`, `children`, `detach()`, `getRoot()`, `getDepth()`.
|
||||
|
||||
Lookup: `getChildByName(name, {excludeAnimationPlayers})`, `getChildByNamePath`,
|
||||
`getChildByIndexPath`, static `getNamePath`/`getIndexPath`, `meshNodes`, `clone({recursive = true})`.
|
||||
|
||||
Per-node flags: `visible` (true), `frustumCulled` (true), `layers` (`kRenderLayerDefault`, a 32-bit
|
||||
mask, NOT inherited), `castsShadows` (true, not inherited), `shadowStatic` (false), `raycastable`
|
||||
(true), `highlightColor` (`Vector4?`), `skin` (`Skin?`, set by importers).
|
||||
|
||||
Bounds: `combinedLocalBounds`, `combinedWorldBounds`, `markBoundsDirty()`, `isVisibleTo(camera,
|
||||
size)`. A `null` bounds means always-visible.
|
||||
|
||||
Loading models (see Assets): `Node.fromGlbAsset`, `Node.fromGlbBytes`, `Node.fromGltfBytes`.
|
||||
|
||||
Geometry readback: `extractMeshData({Matrix4? transform})` flattens the subtree to one `MeshData`.
|
||||
Throws on instanced meshes, non-triangle primitives, caller-managed geometry, or an empty subtree.
|
||||
|
||||
`Scene` (`base class Scene implements SceneGraph`, cannot be subclassed). See the render/lighting/
|
||||
post sections. `Mesh(geometry, material)`/`Mesh.primitives({primitives})`; `MeshPrimitive(geometry,
|
||||
material)`; `Mesh.clone()` (shallow, shares geometry+material); `Mesh.localBounds`,
|
||||
`Mesh.markLocalBoundsDirty()`.
|
||||
|
||||
### Camera
|
||||
|
||||
- `PerspectiveCamera({double fovRadiansY = 45 * degrees2Radians, Vector3? position /*(0,0,-5)*/,
|
||||
Vector3? target /*(0,0,0)*/, Vector3? up /*(0,1,0)*/, double fovNear = 0.1, double fovFar =
|
||||
1000.0})`. Field names are `fovNear`/`fovFar`, NOT `near`/`far`.
|
||||
- `PerspectiveCamera.framing(Aabb3 bounds, {direction, fovRadiansY, up, margin = 1.1})`.
|
||||
- `PerspectiveProjection({fovRadiansY, near = 0.1, far = 1000.0})` and abstract `CameraProjection`,
|
||||
`Camera`. Camera helpers: `screenPointToRay`, `worldToScreen`, `getViewMatrix`, `getFrustum`.
|
||||
- There is NO `OrthographicCamera`. Implement `CameraProjection`/`Camera` for other projections.
|
||||
- Node-driven: `CameraComponent({CameraProjection? projection, activateOnMount = false})` ->
|
||||
`toCamera()` gives a `NodeCamera`. Camera node must not be scaled.
|
||||
- Interactive cameras: `CameraController` components attached to the camera node. `OrbitCameraController`
|
||||
(turntable around `target`; `orbitBy`/`dollyBy`/`panBy`/`frame`), `FlyCameraController` (WASD + drag
|
||||
free flight; `moveVertical: false` = grounded first-person; `look`), `FollowCameraController`
|
||||
(third-person easing behind `followTarget` node; `orbitBy`/`dollyBy`). All ease with frame-rate
|
||||
independent `smoothing` (settle seconds), clamp pitch short of vertical, and write the node via
|
||||
`lookAtFrom`. Wire input with the `CameraControls({required controller, enabled, autofocus, child})`
|
||||
widget (Focus + gestures + wheel); `SceneView` has no camera-input params by design.
|
||||
|
||||
---
|
||||
|
||||
## Components
|
||||
|
||||
`abstract class Component`. Lifecycle hooks (exact names): `onAttach`, `onLoad` (async), `onMount`,
|
||||
`update(double deltaSeconds)` (NOT `onUpdate`), `fixedUpdate(double)`, `onUnmount`, `onDetach`,
|
||||
`cloneFor(Node)`. Node side: `addComponent`, `removeComponent`, `getComponent<T>()`,
|
||||
`getComponents<T>()`.
|
||||
|
||||
| Component | Constructor/notes |
|
||||
| --- | --- |
|
||||
| `MeshComponent` | `MeshComponent(mesh)`; `mesh` get/set, `refreshMaterials()` |
|
||||
| `InstancedMeshComponent` | `InstancedMeshComponent(instancedMesh)` |
|
||||
| `LodComponent` | `LodComponent(List<LodLevel>, {lodBias = 1.0, hysteresis = 0.1, blendRange = 0.0})`; extends MeshComponent |
|
||||
| `CameraComponent`, `NodeCamera` | see Camera |
|
||||
| `DirectionalLightComponent` | `(light)` aims down node local +Z; `.aimed(light, localDir)`; `.fromLightDirection(light)` |
|
||||
| `PointLightComponent` | `(light)`; `worldPosition` |
|
||||
| `SpotLightComponent` | `(light)`; `worldPosition`, `worldDirection` |
|
||||
| `RectAreaLightComponent` | `(light)`; `worldPosition`, `worldRight`, `worldUp` |
|
||||
| `EnvironmentVolumeComponent` | `({required settings, shape = box, extents, radius = 5.0, blendDistance = 1.0, priority = 0.0, weight = 1.0})` |
|
||||
| `ReflectionProbeComponent` | `({extents = Vector3.all(5), blendDistance = 1.0, priority = 10.0, weight = 1.0, faceResolution = 128, captureOnActivate = true})`; parallax-corrected local reflections in the box; `requestCapture()` re-captures |
|
||||
| `MaterialsVariantsComponent` | No public ctor. `MaterialsVariantsComponent.of(root)`/`.allOf(root)`, then `select(name)`, `variants`, `selected` |
|
||||
| `SemanticsComponent` | `({label, value, hint, button, onTap, ... boundsOverride, properties})` |
|
||||
| `WidgetComponent` | `({required Widget child, required Size size, pixelRatio = 1.0, worldHeight = 1.0, update = everyFrame, input = automatic, ...})`; `.bindOnly(...)` |
|
||||
| `SplatComponent` | `SplatComponent(GaussianSplats)`; `opacity`, `splatScale`, `tint`, `shDegree`, `cropBox`, `cropMode` |
|
||||
| `ParticleEmitterComponent` | `({required system, SpriteMaterial? material})`; `facing`, `flipbookColumns/Rows/Blend`, `paused` |
|
||||
| `MeshParticleEmitterComponent` | `({required system, required List<Geometry> geometries, required material, facing = tumble})` |
|
||||
| `TrailComponent` | `({width = 0.25, lifetime = 0.6, minVertexDistance = 0.05, maxPoints = 48, ...})`; `emitting`, `clear()` |
|
||||
|
||||
`SemanticsComponent`, `SplatComponent`, particle emitters, `TrailComponent`, `WidgetInput`,
|
||||
`MeshParticleFacing`, `LodLevel`, `EnvironmentVolumeShape` are all exported.
|
||||
|
||||
---
|
||||
|
||||
## Geometry
|
||||
|
||||
### Primitives (`primitives.dart`, all factory constructors, all `extends MeshGeometry`)
|
||||
|
||||
| Class | Constructor | Facing/notes |
|
||||
| --- | --- | --- |
|
||||
| `CuboidGeometry` | `CuboidGeometry(Vector3 extents, {debugColors = false})` | positional extents; box `-extents/2..+extents/2`; debugColors off |
|
||||
| `WedgeGeometry` | `WedgeGeometry(Vector3 size)` | triangular prism; base on `y=0`, not Y-centered |
|
||||
| `PlaneGeometry` | `({width = 1.0, depth = 1.0, segmentsX = 1, segmentsZ = 1})` | XZ plane, faces +Y; no collisionShape |
|
||||
| `SphereGeometry` | `({radius = 0.5, segments = 32, rings = 16})` | UV sphere |
|
||||
| `CylinderGeometry` | `({bottomRadius = 0.5, topRadius = 0.5, height = 1.0, radialSegments = 32, heightSegments = 1, bottomCap = true, topCap = true})` | topRadius 0 = cone |
|
||||
| `CapsuleGeometry` | `({radius = 0.5, height = 1.0, radialSegments = 32, capRings = 8})` | `height` is the mid-section; total Y = height + 2*radius |
|
||||
| `TorusGeometry` | `({radius = 0.5, tubeRadius = 0.2, radialSegments = 32, tubularSegments = 16})` | XZ plane |
|
||||
| `DiscGeometry` | `({radius = 0.5, segments = 32})` | XZ, faces +Y |
|
||||
| `RingGeometry` | `({innerRadius = 0.25, outerRadius = 0.5, segments = 32})` | annulus, XZ, +Y |
|
||||
| `IcosphereGeometry` | `({radius = 0.5, subdivisions = 2})` | subdivided icosahedron |
|
||||
|
||||
Every primitive except `PlaneGeometry` has a `Shape get collisionShape`.
|
||||
|
||||
### Swept/procedural (sweep a `ScenePath`; also `BezierPath`, `CatmullRomPath`, `PolylinePath`)
|
||||
|
||||
- `RibbonGeometry(path, {width = 1.0, stations = 64, alignment = RibbonAlignment.ground, up, storage = fixed})`; `updatePath(path)`. `RibbonAlignment` = `ground` | `path`.
|
||||
- `TubeGeometry(path, {radius = 0.5, radialSegments = 12, stations = 64, caps = true, storage})`.
|
||||
- `ExtrudeGeometry(path, {required List<Vector2> profile, stations = 64, caps = true, storage})`.
|
||||
- `PolylineGeometry(List<Vector3> points, {width = 8.0, widthMode = screenPixels, cap = butt, dash, perVertexWidth, perVertexColor})`. INERT until `updateForCamera(camera, viewportSize)` is called every frame. `PolylineWidthMode` = `screenPixels` | `worldUnits`; `PolylineCap` = `butt` | `round`; `DashPattern({dashLength, gapLength, cap})`.
|
||||
- `LineSegmentsGeometry(LineSegmentData segments, {width = 0.01, normalOffset = 0.0})`. `extends Geometry`, GPU-expanded, no per-frame CPU work. For large independent-segment sets.
|
||||
- `BillboardGeometry({capacity = 256})`. `floatsPerInstance = 14`; `BillboardFacing` = `spherical` | `axisLocked` | `velocityStretched`.
|
||||
|
||||
### MeshGeometry, GeometryBuilder, MeshData
|
||||
|
||||
`MeshGeometry.fromArrays({required Float32List positions, Float32List? normals, texCoords,
|
||||
texCoords1, colors, tangents, List<int>? indices, primitiveType = triangle, Aabb3? bounds, storage =
|
||||
fixed, GeometryBufferArena? bufferArena, retainCpuData = true})`. Components per vertex: positions 3,
|
||||
normals 3, texCoords/texCoords1 2, colors/tangents 4. Omitted normals on a triangle list are
|
||||
generated. Omitted indices need a vertex count divisible by 3. `bounds` skips the position scan (it
|
||||
must actually cover every vertex).
|
||||
|
||||
`MeshGeometry.fromMeshData(MeshData data, {storage, bufferArena, retainCpuData})`.
|
||||
|
||||
In-place update (require `GeometryStorage.updatable`, all take `{dirtyStart, dirtyCount}`):
|
||||
`updatePositions`, `updateNormals`, `updateTexCoords`, `updateTexCoords1`, `updateColors`,
|
||||
`updateTangents`. `rebuild({positions, normals, ...})` may change the vertex/index count.
|
||||
`applyMeshData(data)`. `GeometryStorage` = `fixed` | `updatable`.
|
||||
|
||||
`GeometryBuilder({deduplicate = true})`: `normal(v)`, `texCoord(v)`, `texCoord1(v)`, `color(v)`,
|
||||
`tangent(v)`, `addVertex(Vector3) -> int`, `addTriangle(a, b, c)` (throws RangeError on bad index),
|
||||
`packVertices()`, `build({storage, bufferArena, retainCpuData})`. Attribute setters are STICKY.
|
||||
Calling `normal()` once disables generated normals for the whole mesh.
|
||||
|
||||
`MeshData` (isolate-transferable, pure): `MeshData({required positions, required vertexCount,
|
||||
normals, ..., customAttributes})`, `MeshData.build({required positions, ...})` (derives vertexCount,
|
||||
generates normals). Derivations: `triangleCount`, `triangles`, `transformed(Matrix4)` (inverse
|
||||
transpose for normals; a mirror flips winding), `toTriMeshShape()` (hollow static collider),
|
||||
`toConvexHullShape()` (dynamic body), `unweld({attributes})`, `extractEdges({creaseAngleDegrees})`,
|
||||
static `merge(parts)`. `MeshAttributeData(data, {components})`; `UnweldAttribute` = `centroid` |
|
||||
`seed` | `triangleIndex` | `barycentric`; `LineSegmentData({positions, normals})`.
|
||||
|
||||
`Geometry` base: `primitiveType`, `localBounds`, `localBoundingSphere`, `setLocalBounds(aabb,
|
||||
sphere)`, `setVertices(BufferView, vertexCount)`, `setIndices(BufferView, indexType)`,
|
||||
`setCustomAttribute(name, Float32List, {required components})` (1..4; not fetched by depth passes so
|
||||
it does not affect shadows), `uploadVertexData(ByteData, vertexCount, ByteData? indices, {indexType =
|
||||
int16})`, `isReadable`, `extractMeshData()`, `setVertexShader`/`setVertexShaderName`,
|
||||
`setVertexLayout(descriptor, {bindsModelTransform = true})`, `draw(pass, {instanceCount = 1})`.
|
||||
`SkinnedGeometry`/`UnskinnedGeometry` subclasses. `GeometryBufferArena({blockSizeInBytes = 16MB})`.
|
||||
|
||||
Vertex layout: unskinned 72 bytes/18 floats = position(3) normal(3) texture_coords(2)
|
||||
texture_coords_1(2) color(4) tangent(4). Skinned 104 bytes/26 floats = + joints(4) weights(4). Do
|
||||
not hand-pack; use `fromArrays`/`fromMeshData`/`GeometryBuilder`.
|
||||
|
||||
### Instancing and LOD
|
||||
|
||||
`InstancedMesh({required geometry, required material, cullInstances = false,
|
||||
sortTransparentInstances = true})`: `instanceCount`, `addInstance(Matrix4, {Vector4? color}) -> int`
|
||||
(clones the matrix), `setInstanceTransform(i, m)`, `updateInstanceTransforms(update,
|
||||
{recomputeWinding = true})`, `setInstanceColor(i, color)`, `removeInstanceAt(i)`, `clearInstances()`.
|
||||
Attach via `InstancedMeshComponent`.
|
||||
|
||||
`LodLevel({required geometry, required material, required double screenSize})` (screenSize = projected
|
||||
bounding-sphere diameter as a fraction of viewport height, descending, last is the cull floor).
|
||||
Attach via `LodComponent`. Shadow/depth passes always draw level 0.
|
||||
|
||||
---
|
||||
|
||||
## Materials and textures
|
||||
|
||||
`Material` (abstract): `name`, `doubleSided` (false), `depthBias` (0.0), `setFragmentShader`,
|
||||
`setFragmentShaderName(name, {cubeName})`, `setRadianceCubeFragmentShader`, `isOpaque()`.
|
||||
|
||||
### UnlitMaterial
|
||||
|
||||
`UnlitMaterial({TextureSource? colorTexture})`. `baseColorTexture` (field name differs from the ctor
|
||||
arg), `baseColorTextureTransform`, `baseColorTextureTexCoord` (0), `alphaMode` (`opaque`; `mask` not
|
||||
implemented, behaves as blend), `baseColorFactor` (white), `vertexColorWeight` (1.0). Fog applies.
|
||||
|
||||
### PhysicallyBasedMaterial
|
||||
|
||||
`PhysicallyBasedMaterial({baseColorTexture, metallicRoughnessTexture, normalTexture, emissiveTexture,
|
||||
occlusionTexture, EnvironmentMap? environment})`. Every texture slot is a `TextureSource?` with a
|
||||
`<slot>TextureTransform` and `<slot>TextureTexCoord`.
|
||||
|
||||
Core: `baseColorFactor` (white), `vertexColorWeight` (1.0), `metallicFactor` (1.0), `roughnessFactor`
|
||||
(1.0), `normalScale` (1.0), `emissiveFactor` (`Vector4.zero()`), `emissiveStrength` (1.0),
|
||||
`occlusionStrength` (1.0), `environment` (null, falls back to `Scene.environment`), `alphaMode`
|
||||
(opaque), `alphaCutoff` (0.5), `specularAntiAliasingVariance` (0.15), `specularAntiAliasingThreshold`
|
||||
(0.2).
|
||||
|
||||
Advanced KHR_materials_* (setting any flips onto an internal physical-variant shader; each has a
|
||||
`<name>Texture`): `specular` (1.0), `specularColor`, `ior` (1.5), `clearcoat` (0.0),
|
||||
`clearcoatRoughness` (0.0), `clearcoatNormalScale`, `sheenColor` (zero), `sheenRoughness` (0.0),
|
||||
`transmission` (0.0), `diffuseTransmission`, `diffuseTransmissionColor`, `thickness` (0.0),
|
||||
`attenuationDistance` (inf), `attenuationColor`, `dispersion` (0.0), `iridescence` (0.0),
|
||||
`iridescenceIor` (1.3), `iridescenceThicknessMinimum` (100.0), `iridescenceThicknessMaximum` (400.0),
|
||||
`anisotropy` (0.0), `anisotropyRotation` (0.0).
|
||||
|
||||
`isOpaque()` is false when `transmission > 0`, `alphaMode == blend`, or `baseColorFactor.a < 1.0`.
|
||||
|
||||
`AlphaMode` = `opaque` | `mask` | `blend`. `TextureTransform({offset, scale, rotation})` (glTF
|
||||
KHR_texture_transform order).
|
||||
|
||||
### SpriteMaterial
|
||||
|
||||
`SpriteMaterial({TextureSource? colorTexture})`: `colorTexture`, `tint` (white), `blendMode`
|
||||
(`SpriteBlendMode.alpha` | `additive`), `softDepthFade` (0.0), `cameraNearFade` (0.0), `sampler`.
|
||||
Always non-opaque, always cull none.
|
||||
|
||||
### ShaderMaterial (raw GLSL escape hatch)
|
||||
|
||||
`ShaderMaterial({gpu.Shader? fragmentShader, radianceCubeFragmentShader, vertexShader,
|
||||
skinnedVertexShader, depthVertexShader, useEnvironment = false, cullingMode = backFace, windingOrder =
|
||||
counterClockwise, isOpaqueOverride = true})`. `setVertexShader(shader, {variant = unskinned})`,
|
||||
`vertexShaderFor(variant)`, `setUniformBlock(name, ByteData?, {stage = fragment})`,
|
||||
`setUniformBlockFromFloats(name, List<double>, {stage})`, `getUniformBlock`, `uniformBlockNames`,
|
||||
`setTexture(name, texture, {sampler, stage})` (accepts `gpu.Texture`/`Texture2D`/`RenderTexture`),
|
||||
`getTexture`, `textureNames`. `ShaderStage` = `vertex` | `fragment`; `MeshVariant` = `unskinned` |
|
||||
`skinned` | `depth`.
|
||||
|
||||
Fragment shaders MUST output linear HDR premultiplied by alpha (exposure, tone mapping, and the
|
||||
display encode are applied later by the resolve pass). Same contract for `ShaderSkySource` and
|
||||
`PostInsertion.beforeTonemap` effects. std140 packing is by hand.
|
||||
|
||||
### .fmat (declarative, recommended over ShaderMaterial)
|
||||
|
||||
`loadFmatMaterial(sourcePath) -> PreprocessedMaterial`, `loadFmatSky(...) -> PreprocessedSky`.
|
||||
`PreprocessedMaterial`: `parameters` (`MaterialParameters`), `shadingModel`, `environment`.
|
||||
`MaterialParameters` (typed, reflection-backed, throws on wrong type/name): `setFloat`, `setInt`,
|
||||
`setVec2/3/4`, `setMat4`, `setColor(name, Color)`, `setTexture(name, gpu.Texture, {sampler})`,
|
||||
`operator []=`, `parameterNames`, `samplerNames`, `hasUniformBlock`.
|
||||
|
||||
### Textures
|
||||
|
||||
`TextureSource` (interface): implementers are `Texture2D`, `RenderTexture`, `GpuTextureSource`. Every
|
||||
built-in material slot takes a `TextureSource`, not a raw `gpu.Texture`.
|
||||
|
||||
`Texture2D` (factories, generates a mip chain): `Texture2D.fromPixels(Uint8List, w, h, {content =
|
||||
color, sampling})`, `fromImage(ui.Image, {...})`, `fromAsset(String, {content = color, sampling,
|
||||
bundle})`. `TextureContent` = `color` (sRGB) | `data` (linear, e.g. metallic-roughness/AO) | `normal`
|
||||
(vector-averaged). `TextureSampling({mipmaps = true, maxMipmapLevels, minFilter = linear, magFilter =
|
||||
linear, mipFilter = linear, maxAnisotropy = 8, addressMode = repeat})`.
|
||||
|
||||
`GpuTextureSource(gpu.Texture, {sampler})` adapts a raw texture. Barrel helpers:
|
||||
`gpuTextureFromImage`, `gpuTextureFromAsset` (mipless, aliases on materials), `imageFromAsset`,
|
||||
`imageFromBytes`. Cooked `.fstex`: `loadTexture(sourcePath, {package, bundle, sampling}) ->
|
||||
TextureSource`, `releaseTexture`, `clearTextureCache`.
|
||||
|
||||
Custom-shader GPU barrel (`package:flutter_scene/gpu.dart`): `Shader`, `ShaderLibrary`,
|
||||
`loadShaderLibraryAsync` (use this, not `ShaderLibrary.fromAsset` which throws on web),
|
||||
`resolveShaderBundleKey`, `Texture`, `SamplerOptions`, `MinMagFilter`, `MipFilter`,
|
||||
`SamplerAddressMode`, `IndexType`, `VertexFormat`, `VertexStepMode`.
|
||||
|
||||
---
|
||||
|
||||
## Lighting and environment
|
||||
|
||||
Lights (all in `light.dart`, all fields mutable):
|
||||
|
||||
- `DirectionalLight({direction /*(-0.3,-1,-0.2)*/, color, intensity = 3.0, priority = 0, castsShadow
|
||||
= false, cacheStaticShadows = true, shadowFadeRange = 2.0, shadowSoftness = 0.08, shadowCascadeCount
|
||||
= 4, shadowMaxDistance = 150.0, shadowCascadeSplitLambda = 0.6, shadowMapResolution = 1024,
|
||||
shadowDepthBias = 0.02, shadowNormalBias = 0.02, shadowAmbientStrength = 0.0, shadowFilter =
|
||||
rotatedPoisson, shadowCasterFaces = front, contactShadows = false, contactShadowDistance = 0.3,
|
||||
angularRadius = 0.005})`.
|
||||
- `PointLight({color, intensity = 1.0, range = 0.0, falloffExponent = 2.0})`. No shadows.
|
||||
- `SpotLight({color, intensity = 1.0, range = 0.0, falloffExponent = 2.0, direction /*(0,-1,0)*/,
|
||||
innerConeAngle = 0.0, outerConeAngle = pi/4, castsShadow = false, ...})`.
|
||||
- `RectAreaLight({color, intensity = 1.0, width = 1.0, height = 1.0, range = 0.0})`. Local XY plane,
|
||||
emits along +Z, no shadows.
|
||||
- `SunLight(SunSky source, {castsShadow = true, ...})` drives `Scene.directionalLight` from a sky.
|
||||
|
||||
`ShadowCasterFaces` = `front` | `back` | `both`. `DirectionalShadowFilter` = `rotatedPoisson` |
|
||||
`fixedPcf` | `pcss`. `ShadowCascade`, `Lighting` (per-draw state) are exported.
|
||||
|
||||
Scene lighting: `Scene.directionalLight` (`DirectionalLight?`, null = IBL only; honors `direction`;
|
||||
highest-priority one gets cascaded shadows), `Scene.sunLight`, `Scene.environment` (`EnvironmentMap?`,
|
||||
null falls back to `EnvironmentMap.studio()`; for genuinely no IBL use `EnvironmentMap.empty()`),
|
||||
`Scene.environmentIntensity` (1.0), `Scene.environmentTransform` (`Matrix3.identity()`),
|
||||
`Scene.skybox` (`Skybox?`, null = transparent), `Scene.skyEnvironment`.
|
||||
|
||||
### EnvironmentMap
|
||||
|
||||
Carries a prefiltered specular radiance atlas AND SH-9 diffuse coefficients (one texture path, no
|
||||
separate radiance/irradiance). Factories: `.empty()`, `.constantDiffuse(ambientRadiance)`,
|
||||
`.fromGpuTextures({required prefilteredRadiance, diffuseSphericalHarmonics, diffuseShTexture})` (the
|
||||
texture must already be prefiltered), `.fromUIImages({required radianceImage, ...})`,
|
||||
`.fromEquirectHdr({required Float32List linearPixels, w, h, ...})`,
|
||||
`.fromEquirectImageAsset({required assetPath, maxWidth = 4096, ...})` (auto-detects .hdr/.exr/LDR),
|
||||
`.fromEquirectImageBytes(...)`, `.fromSky(SkySource, {...})`, `.studio()` (zero-config default).
|
||||
Deprecated: `.fromAssets` (use `.fromEquirectImageAsset`). Env images must be equirect 2:1.
|
||||
`prefilterEquirectRadiance` is exported. `Scene.loadEnvironment(assetPath, {showSkybox = true,
|
||||
skyBlur = 0.0, intensity, exposure, rotationY, maxWidth = 4096, bundle})` is one-call setup.
|
||||
|
||||
### Skybox/sky sources
|
||||
|
||||
`Skybox(SkySource source, {intensity = 1.0})`. `SkySource` implementers: `EnvironmentSkySource({blurriness
|
||||
= 0.0})`, `ShaderSkySource({fragmentShader, fragmentShaderName, radianceCubeFragmentShader,
|
||||
useEnvironment = false})`, `GradientSkySource({zenithColor, horizonColor, groundColor, sunDirection,
|
||||
sunColor, sunSharpness = 400.0})`, `PhysicalSkySource({sunDirection, sunAngularRadius = 0.0175,
|
||||
rayleighCoefficient = 2.0, mieCoefficient = 0.005, turbidity = 10.0, energy = 1.0, ...})`.
|
||||
`SkyEnvironment(ShaderSkySource, {refresh = manual, interval, faceResolution = 128, equirectWidth =
|
||||
512})`; `SkyEnvironmentRefresh` = `manual` | `interval` | `everyFrame`.
|
||||
|
||||
### Exposure and tone mapping
|
||||
|
||||
`Scene.exposure` (1.0; not 2.0), `Scene.toneMapping` (`ToneMappingMode.pbrNeutral`; also `aces`,
|
||||
`reinhard`, `linear`, `agx`), `Scene.agxWhite` (16.29), `Scene.agxContrast` (1.25). Static
|
||||
`Scene.physicalCameraExposure({required aperture, shutterSpeed, iso})` returns a multiplier to assign
|
||||
to `exposure`.
|
||||
|
||||
---
|
||||
|
||||
## Post-processing
|
||||
|
||||
Every effect is a settings object on `Scene`, off by default, turned on with `enabled`. Environment
|
||||
looks blend via `EnvironmentSettings` (snapshot/lerp of the whole look) and `EnvironmentVolume` /
|
||||
`EnvironmentVolumeComponent` (spatial).
|
||||
|
||||
| Scene field | Type | Key fields (default) | Requires |
|
||||
| --- | --- | --- | --- |
|
||||
| `ambientOcclusion` | `AmbientOcclusionSettings` | `method` (obscurance/`groundTruth`), `radius` (0.33), `intensity` (1.0), `power` (1.5), `bentNormals` (false), `halfResolution` (true), `indirectLight` (0.0 = SSGI), `specularMode` | perspective camera |
|
||||
| `screenSpaceReflections` | `ScreenSpaceReflectionsSettings` | `intensity` (1.0), `maxDistance` (24.4), `thickness` (0.46), `stride` (9.0), `maxSteps` (90), `blur` (0.3), `debugView` | perspective camera |
|
||||
| `fog` | `Fog` | `mode` (`FogMode.exponential`; also none/linear/exponentialSquared), `color`, `density` (0.02), `start`/`end`, needs both `enabled` AND non-none `mode` | any camera |
|
||||
| `godRays` | `GodRaysSettings` | `intensity` (1.0), `density` (0.5), `anisotropy` (0.7), `stepCount` (24), `maxDistance` (200), `color` | shadow-casting DirectionalLight + perspective camera |
|
||||
| `depthOfField` | `DepthOfField` | `focusDistance` (10.0), `fStop` (2.8), `focalLength`, `sensorHeight` (0.024), `bladeCount`, `quality` (low/medium/high) | perspective camera |
|
||||
| `autoExposure` | `AutoExposureSettings` | `strength` (0.55), `compensation`, `minEv` (-4), `maxEv` (4), `speedUp` (3.0), `speedDown` (1.0); multiplies on top of `exposure` | none |
|
||||
| `postProcess` | `PostProcessSettings` | see below | none |
|
||||
|
||||
`AmbientOcclusionMethod` = `obscurance` (McGuire SAO) | `groundTruth` (GTAO). `SpecularAmbientOcclusionMode`
|
||||
= `none` | `simple` | `bentCone`. `SsrDebugView` = composite/reflectedUv/hitMask/normal/confidence/depth.
|
||||
|
||||
`PostProcessSettings` (all sub-settings off by default; mutate the nested objects):
|
||||
- `colorGrading` (`ColorGradingSettings`): `brightness` (1.0), `contrast` (1.0), `saturation` (1.0),
|
||||
`temperature`, `tint`, `lift`/`gamma`/`gain`, `lut` (`ColorLut?`, applies after tone mapping,
|
||||
independent of `enabled`), `lutBlend` (1.0).
|
||||
- `chromaticAberration` (`intensity` 0.2), `vignette` (`intensity` 0.5, `radius` 0.75, `smoothness`
|
||||
0.5), `filmGrain` (`intensity` 0.3), `bloom` (`threshold` 1.0, `intensity` 0.15, `scatter` 0.7,
|
||||
and `lensFlare`: `enabled` false, `intensity` 1.0, `ghostCount` 4, `ghostSpacing` 0.3, `haloRadius`
|
||||
0.35, `haloIntensity` 1.0, `chromaticAberration` 0.005; rides the bloom, needs bloom enabled).
|
||||
- `customEffects` (`List<PostEffect>`).
|
||||
|
||||
`ColorLut.fromCubeString`/`.fromCubeAsset` (Adobe `.cube`, edge 2..64).
|
||||
|
||||
Custom post: `PostEffect({gpu.Shader? fragmentShader, insertion = beforeTonemap, enabled = true,
|
||||
useFrameInfo = false})`, added to `scene.postProcess.customEffects`. Engine binds `uniform sampler2D
|
||||
input_color` at `in vec2 v_uv`. `PostInsertion` = `beforeTonemap` (linear HDR premultiplied) |
|
||||
`afterTonemap` (display-referred).
|
||||
|
||||
---
|
||||
|
||||
## Scene, render, and widgets
|
||||
|
||||
`Scene()` (no args; calls `initializeStaticResources()`, needs a live Flutter GPU context). Methods:
|
||||
`add`, `addAll`, `addMesh`, `remove`, `removeAll`, `update(dt)` (optional), `render(camera, canvas,
|
||||
{viewport, pixelRatio})`, `renderViews(views, canvas, {region, pixelRatio})`, `warmUp(views,
|
||||
{includeOffscreen})`, `raycast(ray, {maxDistance, layerMask, where, includeInvisible})`,
|
||||
`raycastAll(...)`, `addRenderPass`/`removeRenderPass`, `captureRenderGraph({viewIndex, request,
|
||||
timeout})`, `captureEnvironment({required position, faceResolution = 128, equirectWidth = 512,
|
||||
layerMask})` -> `EnvironmentMap` (one-shot static capture; use `ReflectionProbeComponent` for a
|
||||
node-anchored, parallax-corrected, auto-blended probe). Statics: `Scene.initializeStaticResources()`,
|
||||
`Scene.isReadyToRender`, `Scene.physicalCameraExposure`, `Scene.isAntiAliasingModeSupported`,
|
||||
`Scene.effectiveAntiAliasingMode`.
|
||||
|
||||
`Scene.antiAliasingMode` (`AntiAliasingMode.auto` -> msaa or fxaa; also `none`, `msaa`, `fxaa`, `smaa`),
|
||||
`Scene.renderScale` (1.0), `Scene.filterQuality` (`FilterQuality.medium`), `Scene.views`
|
||||
(`List<RenderView>` for RenderTexture targets).
|
||||
|
||||
`RenderView({required Camera camera, RenderTexture? target, Rect? viewport /*normalized 0..1,
|
||||
ignored when target set*/, int layerMask = kRenderLayerAll, order = 0, AntiAliasingMode?
|
||||
antiAliasingMode, double? renderScale, FilterQuality? filterQuality, List<Plane> cullingPlanes})`.
|
||||
`kRenderLayerDefault = 1`, `kRenderLayerAll = 0xFFFFFFFF`.
|
||||
|
||||
`RenderTexture`, `RenderTextureSampling`, `RenderTextureUpdate`, `RenderTextureView(renderTexture,
|
||||
{fit = contain, filterQuality = medium, followLayout = false})`.
|
||||
|
||||
Widgets:
|
||||
- `SceneView(Scene scene, {Camera? camera, SceneCameraBuilder? cameraBuilder, SceneViewsBuilder?
|
||||
viewsBuilder, autoTick = true, pixelRatio, onTick, loading, loadingBuilder, revealMinDuration,
|
||||
warmUp = false, children})`. App-owned scene; does not write scene properties. `camera`,
|
||||
`cameraBuilder`, `viewsBuilder` are mutually exclusive.
|
||||
- `SceneView.declarative({environment, environmentIntensity = 1.0, exposure = 1.0, toneMapping =
|
||||
pbrNeutral, camera, cameraBuilder, viewsBuilder, children, ...})`. View-owned scene.
|
||||
- `SceneViewsBuilder` is exported as of 0.22.0 (older docs list it as a trap; it is public now).
|
||||
- Declarative widgets: `SceneNode`, `SceneMesh`, `SceneModel`, `SceneSubtree`, `SceneNodeHost`,
|
||||
`SceneNodeController`, `SceneModelSource`, `AssetModelSource`, `MemoryModelSource`,
|
||||
`SceneAnimationSpec`. `WidgetTexture`, `WidgetTextureController`, `WidgetUpdatePolicy`. `SceneScope`.
|
||||
- Camera resolution precedence: `camera` -> `cameraBuilder(elapsed)` -> `scene.camera` (or first
|
||||
mounted `CameraComponent`) -> default `PerspectiveCamera()`.
|
||||
|
||||
`CustomRenderPass`, `RenderInput`, `RenderPassContext`, `RenderStage`, `TransientWriter`,
|
||||
`NodeFilter`, `HighlightStyle`, render-graph capture types (`CapturedPass`, `CapturedResource`,
|
||||
`RenderGraphCaptureRequest`, `RenderGraphCaptureResult`) are all exported.
|
||||
|
||||
---
|
||||
|
||||
## Assets and animation
|
||||
|
||||
Setup: `flutter pub add flutter_scene` then `dart run flutter_scene:init`. Enable Flutter GPU with
|
||||
`flutter run --enable-flutter-gpu` (native only; nothing for web). Requires Flutter 3.47 stable+, NOT
|
||||
master. Impeller is default; do not pass `--enable-impeller`. Never pass
|
||||
`--enable-experiment=native-assets` (breaks the build on Dart 3.10+).
|
||||
|
||||
Two model-loading paths, do not conflate:
|
||||
|
||||
- Pipeline (preferred, needs the `buildScenes` hook): `loadScene(sourcePath, {package, bundle,
|
||||
registry, onReload, applyStageTo}) -> Future<Node>`. `sourcePath` is the SOURCE path relative to
|
||||
the package root (e.g. `'assets/level.glb'`), NOT a generated name. Companions:
|
||||
`loadSceneSubtree`, `releaseScene`, `clearSceneTemplateCache`.
|
||||
- Runtime glTF (no hook, parses every load): `Node.fromGlbAsset(assetPath)`,
|
||||
`Node.fromGlbBytes(bytes)`, `Node.fromGltfBytes(gltfJson, {required resolveUri})`. Each synthesizes
|
||||
a root node.
|
||||
|
||||
Sibling loaders: `loadTexture` (`.fstex`), `loadFmatMaterial`/`loadFmatSky` (`.fmat`).
|
||||
|
||||
Build hooks (`package:flutter_scene/build_hooks.dart`): `buildScenes({buildInput, buildOutput,
|
||||
inputFilePaths, discoveryRoot = 'assets/', assetMode = generatedTree, compressTextures = false})`,
|
||||
`buildMaterials({...})`, `buildTextures({..., required textures, contents})`, `buildEngineAssets`,
|
||||
`buildTargetShaderBundleJson`. Outputs land in `flutter_scene_generated/` (never commit
|
||||
`.fsceneb`/`.shaderbundle`/`.fmat.json`/`.fstex`). Removed 0.21.0: `legacyOnly`,
|
||||
`dataAssetsIfAvailable`, `outputDirectory`.
|
||||
|
||||
Animation (`Animation`, `AnimationClip`, `AnimationPlayer` exported):
|
||||
|
||||
- Off a loaded model: `node.parsedAnimations`, `node.findAnimationByName(name)`,
|
||||
`node.createAnimationClip(animation)`, `node.removeAnimationClip(clip)`. Clips start paused at t=0;
|
||||
call `play()`.
|
||||
- `AnimationClip`: `playbackTime` (assignment is seek), `playbackTimeScale` (1; negative reverses),
|
||||
`weight` (0..1), `playing`, `loop`. `play()`, `pause()`, `stop()`, `replay()`, `gotoAndPlay(t)`,
|
||||
`seek(t)`, `advance(dt)`, `rebind(newTarget, {animation})`. Channels bind by node NAME; channels
|
||||
whose node is absent from the subtree are dropped (0.22.0 asserts in debug when ALL channels drop).
|
||||
- `AnimationPlayer`: `createAnimationClip(animation, bindTarget)` (a second call with the same
|
||||
`Animation.name` replaces), `getClipByName`, `rebind`, `update(dt)` (auto-driven per frame).
|
||||
- `Animation({name, channels})`, `AnimationChannel`, `BindKey({required nodeName, property =
|
||||
translation})`, `AnimationProperty` = `translation` | `rotation` | `scale`.
|
||||
- Declarative: `SceneModel(assetPath, animations: [SceneAnimationSpec(name, {playing = true, loop =
|
||||
true, weight = 1.0, speed = 1.0})])`. Note `SceneModel` loads via the runtime glTF path.
|
||||
|
||||
The engine-agnostic scene-document core is a separate package `scene` (0.2.0), re-exported through
|
||||
`package:flutter_scene/fscene.dart`. `flutter_scene_importer` and `flutter_gpu_shim` no longer exist
|
||||
(folded in). Physics and audio are separate barrels (`physics.dart`, `audio.dart`).
|
||||
Reference in New Issue
Block a user