Replies: 6 comments 11 replies
-
|
Do we need the additional camera? I recon in the extract logic we can just stop iterating the children when we encounter a UiTexture? |
Beta Was this translation helpful? Give feedback.
-
|
My use case was to make miniatures that can be animated(scaled) back into full menus - this would be perfect! |
Beta Was this translation helpful? Give feedback.
-
|
I like this! Especially the ergonomics it brings to world space UI. It might also be worth to consider how this overlaps with (or can have similar APIs to) the reverse, e.g. UI space 2D/3D:
Both of these might be able use a similar approach to UITexture ( |
Beta Was this translation helpful? Give feedback.
-
|
Also, it looks like we’ve got some overlap with ghost nodes here! We are both changing what a UI root node is. We should probably synchronize this a bit. In the current state of the ghost nodes PR (#15341 ) I’ve defined root nodes as “Node without a parent, or Node with only GhostNode ancestors”. I’m starting to question whether there should be a |
Beta Was this translation helpful? Give feedback.
-
|
IMO this is closely related to #16248 I think some of the confusion comes from |
Beta Was this translation helpful? Give feedback.
-
|
What are the chances of this being implemented? Also, I have one question about this suggestion:
Does this mean that it always have to be a separate node to mark hierarchy to use UiLayer? IMHO it would be great to just slap an additional component on any node rather than add one more node. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
This is a proposal for a
UiLayercomponent that causes UI sub-trees to be rendered on textures usingUiMaterials.Objectives
Background
Currently, if you want a fade-in/fade-out effect on UI then you need to define a custom render target and stick a UI tree on a camea with
TargetCamera. This works well, but requires some boilerplate and technical knowledge to set up, and does not extend to the advanced case of applying effects in the middle of a UI tree on nodes that participate in layout. It also means world-space UI is clunky.See:
Summary
The new
UiLayer<M>component sets up a UI node so all children of the node are rendered to aUiMaterialstored on that node.An entity with
UiLayer<M>requires the following:Node: We are usingUiMaterial, so we also needNode(and, transitively,Style/etc.).Handle<M: UiLayerMaterial>: Stores a handle to theUiMaterialthat will present our texture. If no handle is provided by the user, then a new default material will be inserted.UiLayerImage: Stores a handle to theImageasset of our texture. If no handle is provided by the user, then a new default image asset will be inserted. Note that the image will be auto-resized every tick.After a
UiLayer<M>is added, we perform the following steps:Imageasset if necessary.Mmaterial if necessary, and set its texture from the image asset (withUiLayerMaterial::set_texture).Cameraand insert it to the entity in aUiLayerCameracomponent. This camera is hidden, we will update it manually.Then, every tick, we:
UiLayerCamerato propagate the camera entity to children in aUiLayerCameracomponent. The 'render camera' will receive nodes during UI extraction, while theTargetCameraandDefaultUiCamerawill continue to be used for layout calculations (and as fallbacks in case there's noUiLayerCameraon a node).World-space 2D UI
Currently, any entity tree that starts with a
Nodewill be rendered with the UI rendering pipeline. If a root node hasUiLayer, then theUiMaterialon that node will be rendered to a normal UI camera. However, for world-space UI we want a UI tree to render to a texture but for that texture to not be rendered in a UI camera (and instead get used by some mesh in the world).To support this, we can use the
WorldUiRootmarker component to 'disable' nodes that haveUiLayerso it collects children on itsUiMaterialwithout the material being rendered by the UI pipeline.WorldUiRootthen a layout computation will start at theWorldUiRootcomponent (enabling you to stick UI directly on an entity with a mesh for world-space UI (pending analysis of compatibility withGlobalTransformpropagation)).Nodethen a warning is emitted.WorldUiRootthen it will not be rendered.TargetCamerawill propagate fromWorldUiRootentities, which is useful for layout.Note that this is not a complete solution to world-space UI, since we don't have a way to incorporate world coordinates automatically. But it should be relatively easy to expand on this concept in follow-up PRs.
API
Implementation
Comments
UiLayerCameraOverrideis a 'bonus' feature that is not required for an MVP and can be implemented in a follow-up PR.UiLayernode is not rendered on its ownUiLayercomponent because otherwise the node would contain content rendered on two separate cameras (i.e. its UI components on the sub-camera, and itsUiLayerMaterialon a parent camera).Changes to existing code
content_sizefield toNodeusingtaffy::Layout::content_size. Seeupdate_uinode_geometry_recursive.UiPlugin, replacecheck_visibility<WithNode>withcheck_visibility<(WithNode, Without<Changed<UiLayerCamera>>, Without<UiRenderOverrideTarget>)>.UiUpdate::PostLayoutto run betweenVisibilityPropagateandCheckVisibility.extract_ui_material_nodesto incorporate an optionalUiMaterialLayoutcomponent on nodes.TargetCamerapropagation to start from both root nodes andWorldUiRootnodes.Tricks
UiLayerCameracomponents every tick, and then only access them for rendering if they were changed this tick. This avoids complex hierarchy-management code. The perf cost is partially amortized by shifting somecheck_visibilitywork into this traversal. See Add PropagateOpacity for controlling the alpha of entire UI node trees #15206 for the origin of this trick.UiLayerCamerapropagation, we can avoid duplicate traversals by checking if a given 'starter' node withUiLayerCamerahas aUiLayerCameraalready (which was presumably inserted by an ancestor withUiLayerCamerain a previous tick). If it does, then cache the camera entity for a second-pass traversal once all entities withUiLayerCameraand withoutUiLayerCamerahave been traversed (and remove the entity from the cache if we encounter it during traversal down from an ancestor).Scheduling and logic
To make everything fit together, we require precise organization of logic in the schedule.
UiLayercomponents to their UI nodes.UiLayerMaterialassets to create effects.Added<UiLayer<M>>(system inUiLayerMaterialPlugin<M>)Handle<M>is default, add a new material asset.UiLayerImageis default, add a new image asset.UiLayerCamerawith the material's texture as render target.UiLayerCamera::dont_renderfield (system inUiLayerMaterialPlugin<M>).ui_layout_systemWorldUiRootmarkers.TargetCamerafor layout.update_uinode_geometry_recursiveextract thetaffy::Layout::content_sizevalue and save it inNode.(content_size - node_size)as an offset from the upper left node corner (for offsetting the camera) and(content_size - node_size)*2 + node_sizeas total size.UiLayerCamera(need equivalent ofcamera_system)camera.computed.target_infofrom the new texture size.camera.computed.clip_from_viewfrom the projection.UiAntiAliasand possibly other attributes need to be inferred from the destination camera, which need to be transitively inferred all the way up to the rootTargetCamera/DefaultUiCamera.UiLayerImageasset's size.UiMaterialLayoutcomponent.UiLayerCamerato children ofUiLayer(including but stopping at children who also haveUiLayer).UiLayer::camera: Ancestor withUiLayer.UiLayer::should_render: All nested render targets are not transparent (use cumulativeUiLayerCamera::dont_rendervalues), taking into accountUiLayerCameraOverride.UiLayer::render_offset: Offset between render camera origin and target camera origin (taking into account scale factors), taking into accountUiLayerCameraOverride.VisibleEntitiesfor eachUiLayerCamera(equivalent ofcheck_visibility).UiLayerCameraOverride.CheckVisibilityUiLayerCameraOverride, push entities into theVisibleEntitiescomponent on the correct override camera.!UiLayerCamera::should_render && !Has<UiLayerCameraOverride>.WorldUiRoot.clipvalues need to be offset correctly so the clip rectangle lines up on the render target.UiMaterialLayoutto correctly position and size the material.extract_cameras, use collectedVisibleEntitiesfromUiSystem::PostLayout.Testing
UiScaleUiLayer::scale(potentially nested)Transformscaling (does this affect UI?)UiLayer:Outline, then usingUiLayer::marginshould show/hide the outline at the edges.CalculatedClip.Questions
UiBatchandUiLayerSlicerBatch? They don't seem to be accessed anywhere. If the entity is needed then it's an open question what to do, since the render camera may point to aUiLayerCamerarather thanCamera.UiLayerCameraneeds to function properly (i.e. seemlessly, as if it didn't exist), nor the exact setup code to ensure scaling behaves correctly.#[requires(Handle<M>, ...)valid syntax?UiLayer::scaleis not 1.0?DefaultUiLayerMaterial?UiMaterialsdrawn in front of or behind the outline and border of a node?TransparentUi::sort_keyis tied to render-world entity index. Sinceextract_ui_material_nodesis inRenderUiSystem::ExtractBackgroundswhich is afterRenderUiSystem::ExtractBorders, the borders will presumably be drawn on top. It maybe necessary to add a separate step for extractingUiLayermaterials...Imageassets from the world, update them, and reinsert to get new asset ids and update cameras/materials with the new ids.GlobalTransformwould be affected.UiLayerCamera::transform_offsetto force-correct the transform in render extraction. Alternatively, we could directly mutateGlobalTransformin theUiLayerhierarchy traversal.MaterialonDefaultUiMaterial? The use-case is world-space UI.UiLayercomponents for different materials are inserted to a node?DefaultUiLayerStylecomponent that automatically sets upUiLayerfor default material and uses change detection to send style info (like opacity, scale, etc.) to the material andUiLayercomponent?Future work
WorldUiNodeto be an enum that controlsLayoutContextfor taffy and also orientation/occlusion:WorldUiNode::Billboard: Uses default camera or TargetCamera; render on that camera but useGlobalTransformfrom a scene hierarchy to position it; 100% dimension = 100% of viewport. Replacesbevy_mod_billboardwithout depth culling.WorldUiNode::BillboardObstructed: Uses default camera or TargetCamera; render on that camera but useGlobalTransformfrom a scene hierarchy to position it; 100% dimension = 100% of viewport. Replacesbevy_mod_billboardwith depth culling.WorldUiNode::Sized(fixed size): Assume there is a mesh on the node where the material will be rendered. 100% dimension = 100% of specified size.Beta Was this translation helpful? Give feedback.
All reactions