diff --git a/CHANGELOG.md b/CHANGELOG.md index 71e670429..0fd2a6060 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,83 +1,84 @@ -# Changelog - -*This documents all notable changes to the Visual Pinball Engine and its dependent projects.* - -## Unreleased - -Built with Unity 6.3 - -### Added - -- New threading model ([#552](https://github.com/freezy/VisualPinball.Engine/pull/552)) -- Free transformation ([#500](https://github.com/freezy/VisualPinball.Engine/pull/500)) -- Kinematic collisions ([#460](https://github.com/freezy/VisualPinball.Engine/pull/460)) -- Flipper tricks by nFozzy ([#436](https://github.com/freezy/VisualPinball.Engine/pull/436)) -- Asset Library now has thumbnails. -- Documentation for score reels. -- Score Motor Component ([#435](https://github.com/freezy/VisualPinball.Engine/pull/435), [Documentation](https://docs.visualpinball.org/creators-guide/manual/mechanisms/score-motors.html)). -- Scale support for rubbers. -- Slingarm coil arms can now be any game objects, not just primitives ([#432](https://github.com/freezy/VisualPinball.Engine/pull/432)). -- Gate Lifter Component ([#418](https://github.com/freezy/VisualPinball.Engine/pull/418), [Documentation](https://docs.visualpinball.org/creators-guide/manual/mechanisms/lifting-gates.html)). -- Asset Browser ([#412](https://github.com/freezy/VisualPinball.Engine/pull/412)) -- Trigger meshes can now be easily scaled ([#374](https://github.com/freezy/VisualPinball.Engine/pull/374)) -- We got a new game item called *Metal Wire Guide* (thanks @Cupiii, [#366](https://github.com/freezy/VisualPinball.Engine/pull/366)) -- A *Collision Switch* component ([#344](https://github.com/freezy/VisualPinball.Engine/pull/344), [Documentation](https://docs.visualpinball.org/creators-guide/manual/mechanisms/collision-switches.html)). -- A *Rotator* component ([#337](https://github.com/freezy/VisualPinball.Engine/pull/337), [Documentation](https://docs.visualpinball.org/creators-guide/manual/mechanisms/rotators.html)). -- A *Teleporter* component ([#336](https://github.com/freezy/VisualPinball.Engine/pull/336), [Documentation](https://docs.visualpinball.org/creators-guide/manual/mechanisms/teleporters.html)). -- A *Drop Target Bank* component ([#333](https://github.com/freezy/VisualPinball.Engine/pull/333), [Documentation](https://docs.visualpinball.org/creators-guide/manual/mechanisms/drop-target-banks.html)). -- Editor: Enable manual trigger for coils, switches, lamps and wires during gameplay ([#332](https://github.com/freezy/VisualPinball.Engine/pull/332)) -- Support for dynamic wires, also known as *Fast Flip* ([#330](https://github.com/freezy/VisualPinball.Engine/pull/330), [Documentation](https://docs.visualpinball.org/creators-guide/editor/wire-manager.html#dynamic)). -- Component for light groups, allowing easy grouping of GI lamps. ([#330](https://github.com/freezy/VisualPinball.Engine/pull/330) [Documentation](https://docs.visualpinball.org/creators-guide/manual/mechanisms/light-groups.html)). -- Slingshot component ([#329](https://github.com/freezy/VisualPinball.Engine/pull/329), [Documentation](https://docs.visualpinball.org/creators-guide/manual/mechanisms/slingshots.html)). -- Create insert meshes ([#320](https://github.com/freezy/VisualPinball.Engine/pull/320)). -- Full support for custom playfield meshes. -- Remove Hybrid Renderer ([#316](https://github.com/freezy/VisualPinball.Engine/pull/316)). -- Create and use Unity assets when importing ([#320](https://github.com/freezy/VisualPinball.Engine/pull/302)). -- Native support for nFozzy flipper physics ([#305](https://github.com/freezy/VisualPinball.Engine/pull/305)). -- Automated camera clipping ([#304](https://github.com/freezy/VisualPinball.Engine/pull/304/files)). -- DMD and segment display support ([Documentation](https://docs.visualpinball.org/creators-guide/manual/displays.html)). -- Plugin: Mission Pinball Framework ([Documentation](https://docs.visualpinball.org/plugins/mpf/index.html)). -- Gamelogic Engine: Support for hardware rules ([#293](https://github.com/freezy/VisualPinball.Engine/pull/293)). -- Support for Extended ASCII strings ([#291](https://github.com/freezy/VisualPinball.Engine/pull/291)). -- Support for Elasticity Falloff in walls (added in VP 10.7) ([#291](https://github.com/freezy/VisualPinball.Engine/pull/291)). -- Support for table notes (added in VP 10.7) ([#291](https://github.com/freezy/VisualPinball.Engine/pull/291)). -- Slow motion during gameplay ([#288](https://github.com/freezy/VisualPinball.Engine/pull/288)). -- [Lamp Manager](https://docs.visualpinball.org/creators-guide/editor/lamp-manager.html) ([#282](https://github.com/freezy/VisualPinball.Engine/pull/282)). -- The VPE core is now also available on [NuGet](https://www.nuget.org/packages/VisualPinball.Engine/). -- VPE is now packaged and published on every merge! -- Native trough component ([#229](https://github.com/freezy/VisualPinball.Engine/pull/229), [#248](https://github.com/freezy/VisualPinball.Engine/pull/248), [#256](https://github.com/freezy/VisualPinball.Engine/pull/256), [Documentation](https://docs.visualpinball.org/creators-guide/manual/mechanisms/troughs.html)). - -### Changed -- Removed DOTS in favor of Jobs with Burst ([#459](https://github.com/freezy/VisualPinball.Engine/pull/459)) -- All geometry is now in world space. -- Removed internal ID in gamelogic engine API ([#408](https://github.com/freezy/VisualPinball.Engine/pull/408)) -- When importing, meshes are now saved as easily editable `.fbx` files instead of Unity's internal format ([#387](https://github.com/freezy/VisualPinball.Engine/pull/387)). -- Revised rubber mesh generation ([#384](https://github.com/freezy/VisualPinball.Engine/pull/384)). -- APIs for RGB lamps and Visual Scripting ([#382](https://github.com/freezy/VisualPinball.Engine/pull/382)). -- Playfield is now rotated to the correct angle during gameplay ([#370](https://github.com/freezy/VisualPinball.Engine/pull/370)). -- Decouple light components from transformation override ([#350](https://github.com/freezy/VisualPinball.Engine/pull/350)). -- Refactored drag points. They are nicely separated and typed now. -- Collider debug view is now much faster and intuitive. It's also activated per default when there is no visible mesh. -- Drop and hit targets are now different components. -- Kicker is now a coil device with different coils for different angles/forces. -- Ground truth of data is now the scene, not the imported data anymore ([#302](https://github.com/freezy/VisualPinball.Engine/pull/302)). -- Plunger is now a coil device, meaning it can both be pulled back and fired through different inputs. -- Move render pipelines into separate repos ([#259](https://github.com/freezy/VisualPinball.Engine/pull/259)). -- Put game-, mesh-, collision- animation data into separate components ([#227](https://github.com/freezy/VisualPinball.Engine/pull/227), [Documentation](https://docs.visualpinball.org/creators-guide/editor/unity-components.html)). - -### Fixed -- Disappearing objects due to wrong bounding box ([#441](https://github.com/freezy/VisualPinball.Engine/pull/441)). -- Default table import ([#434](https://github.com/freezy/VisualPinball.Engine/pull/434)) -- Remaining ball spinning issue should now be solved ([#397](https://github.com/freezy/VisualPinball.Engine/pull/397)). -- Physics error when the ball would stop rotate ([#393](https://github.com/freezy/VisualPinball.Engine/pull/393)). -- Finally, ball rotation is rendered correctly ([#386](https://github.com/freezy/VisualPinball.Engine/pull/386)). -- Ball stuttering when rolling over dropped target ([#375](https://github.com/freezy/VisualPinball.Engine/pull/375)). -- Plunger disappearing due to too small bounding box. -- Fixed switch status when multiple mappings point to the same ID ([#347](https://github.com/freezy/VisualPinball.Engine/pull/347)). -- Lighting setup. It's now usable ([#330](https://github.com/freezy/VisualPinball.Engine/pull/330)). -- Ball passing through collider plane and disappearing. -- Alpha channel of color values is now correctly written ([#291](https://github.com/freezy/VisualPinball.Engine/pull/291)). -- Layer names are correctly computed when importing a 10.6 file ([#291](https://github.com/freezy/VisualPinball.Engine/pull/291)). -- Clear texture and material references that don't exist before writing (VP 10.7 behavior) ([#291](https://github.com/freezy/VisualPinball.Engine/pull/291)). -- Bug in writing animation vertices which caused VP to hang when re-reading the file ([#291](https://github.com/freezy/VisualPinball.Engine/pull/291)). -- A few bugs in drag point gizmos ([#246](https://github.com/freezy/VisualPinball.Engine/pull/246)). \ No newline at end of file +# Changelog + +*This documents all notable changes to the Visual Pinball Engine and its dependent projects.* + +## Unreleased + +Built with Unity 6.5 + +### Added + +- Make packaging functional ([#557](https://github.com/freezy/VisualPinball.Engine/pull/557)) +- New threading model ([#552](https://github.com/freezy/VisualPinball.Engine/pull/552)) +- Free transformation ([#500](https://github.com/freezy/VisualPinball.Engine/pull/500)) +- Kinematic collisions ([#460](https://github.com/freezy/VisualPinball.Engine/pull/460)) +- Flipper tricks by nFozzy ([#436](https://github.com/freezy/VisualPinball.Engine/pull/436)) +- Asset Library now has thumbnails. +- Documentation for score reels. +- Score Motor Component ([#435](https://github.com/freezy/VisualPinball.Engine/pull/435), [Documentation](https://docs.visualpinball.org/creators-guide/manual/mechanisms/score-motors.html)). +- Scale support for rubbers. +- Slingarm coil arms can now be any game objects, not just primitives ([#432](https://github.com/freezy/VisualPinball.Engine/pull/432)). +- Gate Lifter Component ([#418](https://github.com/freezy/VisualPinball.Engine/pull/418), [Documentation](https://docs.visualpinball.org/creators-guide/manual/mechanisms/lifting-gates.html)). +- Asset Browser ([#412](https://github.com/freezy/VisualPinball.Engine/pull/412)) +- Trigger meshes can now be easily scaled ([#374](https://github.com/freezy/VisualPinball.Engine/pull/374)) +- We got a new game item called *Metal Wire Guide* (thanks @Cupiii, [#366](https://github.com/freezy/VisualPinball.Engine/pull/366)) +- A *Collision Switch* component ([#344](https://github.com/freezy/VisualPinball.Engine/pull/344), [Documentation](https://docs.visualpinball.org/creators-guide/manual/mechanisms/collision-switches.html)). +- A *Rotator* component ([#337](https://github.com/freezy/VisualPinball.Engine/pull/337), [Documentation](https://docs.visualpinball.org/creators-guide/manual/mechanisms/rotators.html)). +- A *Teleporter* component ([#336](https://github.com/freezy/VisualPinball.Engine/pull/336), [Documentation](https://docs.visualpinball.org/creators-guide/manual/mechanisms/teleporters.html)). +- A *Drop Target Bank* component ([#333](https://github.com/freezy/VisualPinball.Engine/pull/333), [Documentation](https://docs.visualpinball.org/creators-guide/manual/mechanisms/drop-target-banks.html)). +- Editor: Enable manual trigger for coils, switches, lamps and wires during gameplay ([#332](https://github.com/freezy/VisualPinball.Engine/pull/332)) +- Support for dynamic wires, also known as *Fast Flip* ([#330](https://github.com/freezy/VisualPinball.Engine/pull/330), [Documentation](https://docs.visualpinball.org/creators-guide/editor/wire-manager.html#dynamic)). +- Component for light groups, allowing easy grouping of GI lamps. ([#330](https://github.com/freezy/VisualPinball.Engine/pull/330) [Documentation](https://docs.visualpinball.org/creators-guide/manual/mechanisms/light-groups.html)). +- Slingshot component ([#329](https://github.com/freezy/VisualPinball.Engine/pull/329), [Documentation](https://docs.visualpinball.org/creators-guide/manual/mechanisms/slingshots.html)). +- Create insert meshes ([#320](https://github.com/freezy/VisualPinball.Engine/pull/320)). +- Full support for custom playfield meshes. +- Remove Hybrid Renderer ([#316](https://github.com/freezy/VisualPinball.Engine/pull/316)). +- Create and use Unity assets when importing ([#320](https://github.com/freezy/VisualPinball.Engine/pull/302)). +- Native support for nFozzy flipper physics ([#305](https://github.com/freezy/VisualPinball.Engine/pull/305)). +- Automated camera clipping ([#304](https://github.com/freezy/VisualPinball.Engine/pull/304/files)). +- DMD and segment display support ([Documentation](https://docs.visualpinball.org/creators-guide/manual/displays.html)). +- Plugin: Mission Pinball Framework ([Documentation](https://docs.visualpinball.org/plugins/mpf/index.html)). +- Gamelogic Engine: Support for hardware rules ([#293](https://github.com/freezy/VisualPinball.Engine/pull/293)). +- Support for Extended ASCII strings ([#291](https://github.com/freezy/VisualPinball.Engine/pull/291)). +- Support for Elasticity Falloff in walls (added in VP 10.7) ([#291](https://github.com/freezy/VisualPinball.Engine/pull/291)). +- Support for table notes (added in VP 10.7) ([#291](https://github.com/freezy/VisualPinball.Engine/pull/291)). +- Slow motion during gameplay ([#288](https://github.com/freezy/VisualPinball.Engine/pull/288)). +- [Lamp Manager](https://docs.visualpinball.org/creators-guide/editor/lamp-manager.html) ([#282](https://github.com/freezy/VisualPinball.Engine/pull/282)). +- The VPE core is now also available on [NuGet](https://www.nuget.org/packages/VisualPinball.Engine/). +- VPE is now packaged and published on every merge! +- Native trough component ([#229](https://github.com/freezy/VisualPinball.Engine/pull/229), [#248](https://github.com/freezy/VisualPinball.Engine/pull/248), [#256](https://github.com/freezy/VisualPinball.Engine/pull/256), [Documentation](https://docs.visualpinball.org/creators-guide/manual/mechanisms/troughs.html)). + +### Changed +- Removed DOTS in favor of Jobs with Burst ([#459](https://github.com/freezy/VisualPinball.Engine/pull/459)) +- All geometry is now in world space. +- Removed internal ID in gamelogic engine API ([#408](https://github.com/freezy/VisualPinball.Engine/pull/408)) +- When importing, meshes are now saved as easily editable `.fbx` files instead of Unity's internal format ([#387](https://github.com/freezy/VisualPinball.Engine/pull/387)). +- Revised rubber mesh generation ([#384](https://github.com/freezy/VisualPinball.Engine/pull/384)). +- APIs for RGB lamps and Visual Scripting ([#382](https://github.com/freezy/VisualPinball.Engine/pull/382)). +- Playfield is now rotated to the correct angle during gameplay ([#370](https://github.com/freezy/VisualPinball.Engine/pull/370)). +- Decouple light components from transformation override ([#350](https://github.com/freezy/VisualPinball.Engine/pull/350)). +- Refactored drag points. They are nicely separated and typed now. +- Collider debug view is now much faster and intuitive. It's also activated per default when there is no visible mesh. +- Drop and hit targets are now different components. +- Kicker is now a coil device with different coils for different angles/forces. +- Ground truth of data is now the scene, not the imported data anymore ([#302](https://github.com/freezy/VisualPinball.Engine/pull/302)). +- Plunger is now a coil device, meaning it can both be pulled back and fired through different inputs. +- Move render pipelines into separate repos ([#259](https://github.com/freezy/VisualPinball.Engine/pull/259)). +- Put game-, mesh-, collision- animation data into separate components ([#227](https://github.com/freezy/VisualPinball.Engine/pull/227), [Documentation](https://docs.visualpinball.org/creators-guide/editor/unity-components.html)). + +### Fixed +- Disappearing objects due to wrong bounding box ([#441](https://github.com/freezy/VisualPinball.Engine/pull/441)). +- Default table import ([#434](https://github.com/freezy/VisualPinball.Engine/pull/434)) +- Remaining ball spinning issue should now be solved ([#397](https://github.com/freezy/VisualPinball.Engine/pull/397)). +- Physics error when the ball would stop rotate ([#393](https://github.com/freezy/VisualPinball.Engine/pull/393)). +- Finally, ball rotation is rendered correctly ([#386](https://github.com/freezy/VisualPinball.Engine/pull/386)). +- Ball stuttering when rolling over dropped target ([#375](https://github.com/freezy/VisualPinball.Engine/pull/375)). +- Plunger disappearing due to too small bounding box. +- Fixed switch status when multiple mappings point to the same ID ([#347](https://github.com/freezy/VisualPinball.Engine/pull/347)). +- Lighting setup. It's now usable ([#330](https://github.com/freezy/VisualPinball.Engine/pull/330)). +- Ball passing through collider plane and disappearing. +- Alpha channel of color values is now correctly written ([#291](https://github.com/freezy/VisualPinball.Engine/pull/291)). +- Layer names are correctly computed when importing a 10.6 file ([#291](https://github.com/freezy/VisualPinball.Engine/pull/291)). +- Clear texture and material references that don't exist before writing (VP 10.7 behavior) ([#291](https://github.com/freezy/VisualPinball.Engine/pull/291)). +- Bug in writing animation vertices which caused VP to hang when re-reading the file ([#291](https://github.com/freezy/VisualPinball.Engine/pull/291)). +- A few bugs in drag point gizmos ([#246](https://github.com/freezy/VisualPinball.Engine/pull/246)). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 19a53ed93..66ceb0bb0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,45 +1,45 @@ -# How to Contribute - -You want to contribute to VPE? Awesome! Here are a few things you should know. - -## Submitting Changes - -We rarely commit to master directly. Instead, we open [pull requests](https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/about-pull-requests) -and let our peers review the code before it gets merged. - -- We [rebase](https://git-scm.com/book/en/v2/Git-Branching-Rebasing) our commits to master, so the commit history stays linear. -- When a PR is about user-facing changes, we [update the documentation](https://github.com/freezy/VisualPinball.Engine/wiki/Documentation#bigger-changes-or-new-content). -- If a PR contains notable changes, we also update the [changelog](CHANGELOG.md). Add your entry to the top of the appropriate section. -- We try to prefix our commit messages with a word that quickly tells the reader where the change happened. Examples are `editor`, `doc`, ``, etc. -- Changes that touch the core project should be unit-tested. - -### Unity License Setup For Automated Testing - -It's preferred to make the automated tests run when creating your PR. Since Unity needs a license key (which can be obtained with a free account), you'll need to configure your fork to use the correct secrets. You will need to use a Personal license and request a key on behalf of GitHub: - -1. Run the `License` workflow by clicking the `Run workflow` button in the `Actions` tab. When the workflow completes, download and unzip the `Unity_v2021.3.0f1.alf` artifact. -2. Visit [license.unity3d.com](https://license.unity3d.com), sign in, and upload the `Unity_v2021.3.0f1.alf` file. -3. You should now receive your license file (`Unity_v2021.x.ulf`) as a download. -4. Open `Github` > `` > `Settings` > `Secrets` -- Create the following secret: - - `UNITY_LICENSE` - (Copy the contents of the `Unity_v2021.x.ulf` license file here) -5. Delete the `License` workflow run by selecting `Delete workflow run` in the `...` menu. - -## Code Style - -We aren't too picky about code style. Just start reading our code and you'll get the hang of it. There -are a few rules though: - -- We mostly use [C#'s naming conventions](https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/naming-guidelines). - That said, for `MonoBehaviours` we sometimes also use [Unity's style](https://github.com/raywenderlich/c-sharp-style-guide) - that puts field names in camel case. -- For the Unity projects, we use one namespace per project. For the core project, it's a namespace per folder. -- We use tabs for indentation. -- This is open source software. Consider the people who will read your code, and make it look nice for them. It's sort of like - driving a car: Perhaps you love doing donuts when you're alone, but with passengers the goal is to make the ride as smooth - as possible. - -## Talk to us! - -Have a look at the [VPF thread](https://www.vpforums.org/index.php?showtopic=43651) if you have any question. We also have a Discord -server for internal discussion. +# How to Contribute + +You want to contribute to VPE? Awesome! Here are a few things you should know. + +## Submitting Changes + +We rarely commit to master directly. Instead, we open [pull requests](https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/about-pull-requests) +and let our peers review the code before it gets merged. + +- We [rebase](https://git-scm.com/book/en/v2/Git-Branching-Rebasing) our commits to master, so the commit history stays linear. +- When a PR is about user-facing changes, we [update the documentation](https://github.com/freezy/VisualPinball.Engine/wiki/Documentation#bigger-changes-or-new-content). +- If a PR contains notable changes, we also update the [changelog](CHANGELOG.md). Add your entry to the top of the appropriate section. +- We try to prefix our commit messages with a word that quickly tells the reader where the change happened. Examples are `editor`, `doc`, ``, etc. +- Changes that touch the core project should be unit-tested. + +### Unity License Setup For Automated Testing + +It's preferred to make the automated tests run when creating your PR. Since Unity needs a license key (which can be obtained with a free account), you'll need to configure your fork to use the correct secrets. You will need to use a Personal license and request a key on behalf of GitHub: + +1. Run the `License` workflow by clicking the `Run workflow` button in the `Actions` tab. When the workflow completes, download and unzip the `Unity_v6000.5.0f1.alf` artifact. +2. Visit [license.unity3d.com](https://license.unity3d.com), sign in, and upload the `Unity_v6000.5.0f1.alf` file. +3. You should now receive your license file (`Unity_v6000.x.ulf`) as a download. +4. Open `Github` > `` > `Settings` > `Secrets` +- Create the following secret: + - `UNITY_LICENSE` - (Copy the contents of the `Unity_v6000.x.ulf` license file here) +5. Delete the `License` workflow run by selecting `Delete workflow run` in the `...` menu. + +## Code Style + +We aren't too picky about code style. Just start reading our code and you'll get the hang of it. There +are a few rules though: + +- We mostly use [C#'s naming conventions](https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/naming-guidelines). + That said, for `MonoBehaviours` we sometimes also use [Unity's style](https://github.com/raywenderlich/c-sharp-style-guide) + that puts field names in camel case. +- For the Unity projects, we use one namespace per project. For the core project, it's a namespace per folder. +- We use tabs for indentation. +- This is open source software. Consider the people who will read your code, and make it look nice for them. It's sort of like + driving a car: Perhaps you love doing donuts when you're alone, but with passengers the goal is to make the ride as smooth + as possible. + +## Talk to us! + +Have a look at the [VPF thread](https://www.vpforums.org/index.php?showtopic=43651) if you have any question. We also have a Discord +server for internal discussion. diff --git a/VisualPinball.Engine/VisualPinball.Engine.csproj b/VisualPinball.Engine/VisualPinball.Engine.csproj index 43fee5a42..e83eaa1e2 100644 --- a/VisualPinball.Engine/VisualPinball.Engine.csproj +++ b/VisualPinball.Engine/VisualPinball.Engine.csproj @@ -1,5 +1,5 @@  - + netstandard2.1 true @@ -12,11 +12,11 @@ 0.1.0.0 9.0 false - https://visualpinball.org - icon.png - LICENSE - 0.0.4 - + https://visualpinball.org + icon.png + LICENSE + 0.0.4 + win-x64 @@ -28,18 +28,22 @@ - - - - - - - - - - - - + + + + + + + + + + + + + + + + @@ -49,16 +53,16 @@ - - - - true - - - - true - - + + + + true + + + + true + + @@ -67,34 +71,36 @@ + + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - - + + + + + + + + + + + diff --git a/VisualPinball.Unity/Documentation~/creators-guide/editor/asset-library-styleguide.md b/VisualPinball.Unity/Documentation~/creators-guide/editor/asset-library-styleguide.md index c4428ccd7..e2884016e 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/editor/asset-library-styleguide.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/editor/asset-library-styleguide.md @@ -1,258 +1,258 @@ ---- -uid: asset_library_guide -title: Asset Library Style Guide -description: These guidelines describe how the game assets of the pinball asset library should be created so they are of high quality, customizable, consistent and optimized. ---- - -# Asset Library Style Guide - -This document serves as a comprehensive style guide for all 3D assets in the pinball asset library. Our goal is to maintain assets that are high **quality**, visually **consistent**, **optimized** for performance, and **customizable**. These guidelines should be followed for the vast majority of assets; exceptions can be made in special cases. - - -> [!note] -> Throughout this guide, you'll see examples highlighted in red and green. They signify whether you should follow these examples. -> ->
-> -> That said, please consider this guide as work in progress. - -## Design Language - -We're aiming for a photorealistic look, as opposed to stylized visuals. Shapes should match those in the real world and maintain accurate proportions and sizes. - -## Target Hardware - -We're targeting desktop PC hardware. This means we should be able to make use of the latest graphics features while still being able to run on older hardware. - -## Geometry Guidelines - -This section covers modeling practices, i.e. how to create meshes consisting of vertices, edges, and faces. - -### Topology - -Topology describes how vertices and edges form the shapes of your model. While there are many ideas on best practices, here are the essentials for game-ready assets: - -- Use quads where possible and avoid n-gons. -- Maintain clean topology with proper edge flow, if possible. -- Apply proper smoothing groups/hard edges for accurate normal calculation (shade smooth/flat in Blender). -- Avoid non-manifold geometry and floating vertices - make the mesh watertight. - -If you're converting CAD models which aren't polygon-based, you'll likely need to apply some [retopology](https://en.wikipedia.org/wiki/Retopology). The same applies to 3D scans. - -
-A STEP model imported into Blender (left), versus the re-modeled version (right). - -### Poly Count - -In [polygonal modeling](https://en.wikipedia.org/wiki/Polygonal_modeling), the poly count refers to the number of polygons used in a model. In game engines, quads and n-gons are converted to triangles, so we measure by the number of triangles. Higher triangle counts allow for more detail but increase rendering time. - -Follow these poly count guidelines: - -- Small objects (e.g., spinners, drop targets) can typically stay under 500 triangles. -- Standard playfield objects (e.g., flippers, bumpers) can range from about 500 to 2,000 triangles. -- Hero pieces (large ramps, toys): higher counts are acceptable, but remain mindful of necessary detail. - -
-A gate bracket at 108 triangles (left), 420 (middle) and 906 triangles (right) - - -### Scale and Orientation - -Unity uses a [left-handed](https://en.wikipedia.org/wiki/Right-hand_rule) coordinate system, where X points to the right, Y up, and Z forward. Your models should be oriented accordingly. - -
-The various orientations we're dealing with. - -For scale, use **meters** as your unit of measurement. It's crucial to model in real-world units to ensure correct proportions between assets, as improper scaling makes it difficult to accurately size components. Ensure that scaling is applied directly to the model's geometry, eliminating the need for rescaling within the game engine. - -### Complexity - -While we encourage providing details that might only be visible when pivoting around the table, nobody will examine what's below the playfield or hidden inside of other meshes. - -Therefore, don't include geometry that won't be visible. - -
-A drop target with the entire footing which is hidden by the playfield on the right, and reduced on the left. - -### Pivot Point - -The pivot point, also known as *object origin* or *local origin*, defines where your model appears for a given position in 3D space. - -- Static objects should always have their vertical axis (the Y axis in Unity, or Z axis in VPX) of the pivot point at playfield height, so setting it to 0 will position the object on the playfield. -- Objects that rotate need their pivot point on the rotation axis. If such an object is parented to another (static) object, the parent should also have its vertical origin at playfield height. -- On the horizontal plane, the pivot point should be in the center unless the object's topology suggests another more logical position. - -
-A drop target with the origin placed at different locations. - -### UV Maps - -All models must be [UV-mapped](https://en.wikipedia.org/wiki/UV_mapping). - -- UVs should be unwrapped with minimal stretching. -- Maintain 2-4 pixel padding between UV islands to prevent texture bleeding. -- Keep UV shells proportional to their 3D size to maintain consistent texel density. -- Organize UVs within the 0-1 UV space. - -### Decals - -Decal Setup of two drop targets - -If your model contains art that varies from instance to instance, use a [decal mesh](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.2/manual/understand-decals.html). Decals should be used where you would find literal decals or imprints in the real world. Examples include spinners, aprons, targets, and bumpers. - -On the right side you see two drop target meshes with their corresponding decal meshes in orange. - -The decal geometry should be in a separate object parented to the main object. The UVs of the decal mesh should be laid out in a way that allows its textures to be created with non-specialized image editors. - -
-Be sure to keep the aspect ratio without distortion when UV-mapping decals. - -> [!note] -> #### Why Decals? -> Decals are great because they make your workflow more flexible and are at the same time more performant: -> - Flexible, because it allows us to texture our models in a generic way so that they can be used in any context. Imagine a drop target with a star on it. Without decals, the star would be baked into the texture, and if anybody else would want reuse that target with different art on it, they would need to recreate the texture. With decals, they only need to swap out the decal texture. -> - Performance, because Unity is optimized for having thousands of decals in a scene, allowing us to use higher-resolution textures for our decals without having to waste resources on the rest of the object. - -### Colliders - -Decal Setup of two drop targets - -VPE uses separate meshes for collision for some items (currently drop targets and hit targets). These collider meshes should be included in the model. - -- Their pivot point must align with the pivot point of the main mesh. -- The scale must be applied and correspond to the main mesh's scale. -- They shouldn't include any UVs. - -You can see a hit target mesh with its collider mesh in orange on the right side. - -### LODs - -Regarding [LODs](https://en.wikipedia.org/wiki/Level_of_detail_(computer_graphics)) (Levels of Detail), we're only using one LOD. This guideline is based on the compact size of the playfield, where most elements would be rendered at the same LOD anyway. Additionally, most assets will be under 1,000 triangles, making the performance impact of LODs minimal. - -### File Format - -Export your meshes in a format [supported by Unity](https://docs.unity3d.com/6000.0/Documentation/Manual/3D-formats.html). The preferred formats are [glTF](https://en.wikipedia.org/wiki/GlTF) and [FBX](https://en.wikipedia.org/wiki/FBX). Avoid [`.obj`](https://en.wikipedia.org/wiki/Wavefront_.obj_file) due to its limitations and inefficiency. - -
- - -Vendor-specific formats are also to avoid (even `.blend`), because they only can be imported if the corresponding 3D software is installed. - -## Material Guidelines - -We use physically based rendering ([PBR](https://en.wikipedia.org/wiki/Physically_based_rendering)) for realistic visuals. In Unity HDRP, this generally means using the [Lit Shader](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.2/manual/lit-material.html). - -> [!note] -> We're still determining whether authors will be able to choose other shaders, create their own, or if usage will be restricted. We'll update this section as soon as we have more information. For now, we focus on authoring using the Lit Shader. - -### Texture Maps - -In the PBR workflow, these texture maps are most relevant: - -- Color map (also called diffuse or albedo map) -- Normal map (often called bump map) -- Metallic map -- Smoothness map (which is an inverted roughness map) -- Ambient occlusion map - -Unity also supports emissive maps, detail maps, and others depending on the material type, but we'll focus on those mentioned above. - -### Color Maps - -The color map contains RGB values that represent surface color without lighting information. - -If your asset or parts of your asset exist in multiple color variations, consider using only gray tones and tinting the material with the Lit shader's base color. This approach makes it customizable without having to render out textures for each color variant (and is also more memory-efficient). - -
-A drop target with the same base material but different tints, and three different decal materials. - -As mentioned above, don't bake art that varies into the texture; use decals instead. Single-color decals that come in multiple color variations should also use a gray-tone color map and be tinted through the Decal Shader directly, making them more easily customizable without requiring multiple material instances. - -### Normal Maps - -[Normal Maps](https://en.wikipedia.org/wiki/Normal_mapping) simulate lighting effects on surface details without adding geometry. Generally, if a detail doesn't have any silhouette-defining features and isn't deep enough to cast visible shadows, flatten it and bake it as a normal map. - -
-A playfield toy at 185k tris (left), 2.7k tris without normal map (middle) and with normal map (right). - -Edges are particularly important for achieving realistic visuals. In the real world, light always reflects off edges because they are never perfectly sharp. To simulate this, you should always bevel your edges. For models with few prominent edges, you can add bevels in the geometry. However, baking the bevel into a normal map is typically more efficient. - -
-A gate with beveled geometry at 3.6k tris (left), at 420 tris without normal map (middle) and with normal map (right). - -To summarize, use normal maps for: - -- Surface details (scratches, small dents, panel seams) -- Shallow details (<5mm in real scale) -- Beveled edges -- Text or logo embossing -- Pattern detailing - -### Metallic / Smoothness Maps - -With a metallic map, you can define on pixel level whether your material is metallic, or not. -- Only use this if your material covers both metallic and non-metallic parts of your model. Otherwise, use the metallicness property of the Lit Shader directly. -- You should only use values of 0 or 1, as partially metallic materials don't exist in the real world. - -The smoothness map (the inverse of a roughness map) defines how regularly light is reflected at the micro-surface level. A value of 1 behaves like a mirror, while a value of 0 is more like an eraser. - -
-Metallicness set to 1 with smoothness going from 0 to 1. - -### Texture Map Resolution - -All texture maps must use power-of-two dimensions for width and height (e.g., 256, 512, 1024). They don't have to be square. - -
- -We're aiming for a resolution of about 6 pixels per millimeter (approximately 150 DPI). For a playfield texture, this means roughly 4096×8192 pixels. Use this resolution when possible, but don't upscale images — the highest resolution should be from your source. This applies to both color and normal maps. For metallic/smoothness maps, half the resolution of the color map is a good balance between performance and visual fidelity. - -> [!note] -> You can determine the resolution by looking at your UV map and the size of the asset. Let's take the gate from the previous section as an example. -> -> 1. Take a large section of your mesh, and measure it. The larger, the more precise it will be. Here we're measuring the top surface, from where the bevel starts:
-> -> 2. Next, identify that section on your UV map, and note where in UV space they are:
->
-> As you can see, they go from 0.360 to 0.955 on the X axis. -> 3. In terms of dimensions, we now have the following: -> - Width in UV space: 0.955 - 0.36 = 0.595 -> - Width in real world space: 29.2mm -> 4. At 6px / mm, that makes 6px × 29.2mm = 175.2px for the 0.595 UVs -> 5. To get the resolution of the whole UV map: 175.2px / 0.592 = **296px** -> -> So, a texture map at 296×296 would correspond to 6px / mm. Since we're at power of twos, we could go for either 512×512 or 256×256. - -### Compression - -Export your texture maps in **PNG format**. Use 32-bit if they include an alpha channel; otherwise, 24-bit is sufficient. Use halved resolution for the mask map. Don't quantize / TinyPNG your maps, since together with the GPU's block compression they will result in artifacts. - -> [!note] -> Many artists prefer to export their textures as TGA due to faster read/write speeds, but TGA files consume significantly more space. PNG uses lossless compression, and once imported, Unity stores textures in a GPU-friendly format anyway. Additionally, when exporting to the `.vpe` format, image textures are converted into a runtime-optimized format. - -In terms of *packaging*, Unity's HDRP stores metallic, ambient occlusion and smoothness in what they call a [mask map](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.2/manual/Mask-Map-and-Detail-Map.html). Substance Painter exports this out of the box, but it should be pretty easy to do with Blender as well. - -### Wear - -Generally, all items should show visible signs of wear, rather than appearing brand new from the factory. This adds realism and character to the assets. - -
-They certainly have each their look. But in order to keep consistency, you should follow the example in the middle. - -### Standard Materials - -Many materials in the asset library will appear in multiple assets. To keep a consistent look across all assets, you should use existing materials. We're providing Substance Painter Smart Materials for metals and plastics (and hopefully Blender versions as well). - -ℹ️  Table coming soon - -## Attribution - -When submitting assets to the library, please include your name or handle for proper attribution. If you've adapted assets from other sources, ensure you have the necessary permissions and include appropriate credits. - -## Where to Submit - -Don't submit yet! 😄 - -We're still figuring out the best structure and repository for our asset library (our [current repo](https://github.com/VisualPinball/VisualPinball.Unity.AssetLibrary) is getting too large). There will also be additional guidelines about metadata. - -This section will be updated when we're ready! +--- +uid: asset_library_guide +title: Asset Library Style Guide +description: These guidelines describe how the game assets of the pinball asset library should be created so they are of high quality, customizable, consistent and optimized. +--- + +# Asset Library Style Guide + +This document serves as a comprehensive style guide for all 3D assets in the pinball asset library. Our goal is to maintain assets that are high **quality**, visually **consistent**, **optimized** for performance, and **customizable**. These guidelines should be followed for the vast majority of assets; exceptions can be made in special cases. + + +> [!note] +> Throughout this guide, you'll see examples highlighted in red and green. They signify whether you should follow these examples. +> +>
+> +> That said, please consider this guide as work in progress. + +## Design Language + +We're aiming for a photorealistic look, as opposed to stylized visuals. Shapes should match those in the real world and maintain accurate proportions and sizes. + +## Target Hardware + +We're targeting desktop PC hardware. This means we should be able to make use of the latest graphics features while still being able to run on older hardware. + +## Geometry Guidelines + +This section covers modeling practices, i.e. how to create meshes consisting of vertices, edges, and faces. + +### Topology + +Topology describes how vertices and edges form the shapes of your model. While there are many ideas on best practices, here are the essentials for game-ready assets: + +- Use quads where possible and avoid n-gons. +- Maintain clean topology with proper edge flow, if possible. +- Apply proper smoothing groups/hard edges for accurate normal calculation (shade smooth/flat in Blender). +- Avoid non-manifold geometry and floating vertices - make the mesh watertight. + +If you're converting CAD models which aren't polygon-based, you'll likely need to apply some [retopology](https://en.wikipedia.org/wiki/Retopology). The same applies to 3D scans. + +
+A STEP model imported into Blender (left), versus the re-modeled version (right). + +### Poly Count + +In [polygonal modeling](https://en.wikipedia.org/wiki/Polygonal_modeling), the poly count refers to the number of polygons used in a model. In game engines, quads and n-gons are converted to triangles, so we measure by the number of triangles. Higher triangle counts allow for more detail but increase rendering time. + +Follow these poly count guidelines: + +- Small objects (e.g., spinners, drop targets) can typically stay under 500 triangles. +- Standard playfield objects (e.g., flippers, bumpers) can range from about 500 to 2,000 triangles. +- Hero pieces (large ramps, toys): higher counts are acceptable, but remain mindful of necessary detail. + +
+A gate bracket at 108 triangles (left), 420 (middle) and 906 triangles (right) + + +### Scale and Orientation + +Unity uses a [left-handed](https://en.wikipedia.org/wiki/Right-hand_rule) coordinate system, where X points to the right, Y up, and Z forward. Your models should be oriented accordingly. + +
+The various orientations we're dealing with. + +For scale, use **meters** as your unit of measurement. It's crucial to model in real-world units to ensure correct proportions between assets, as improper scaling makes it difficult to accurately size components. Ensure that scaling is applied directly to the model's geometry, eliminating the need for rescaling within the game engine. + +### Complexity + +While we encourage providing details that might only be visible when pivoting around the table, nobody will examine what's below the playfield or hidden inside of other meshes. + +Therefore, don't include geometry that won't be visible. + +
+A drop target with the entire footing which is hidden by the playfield on the right, and reduced on the left. + +### Pivot Point + +The pivot point, also known as *object origin* or *local origin*, defines where your model appears for a given position in 3D space. + +- Static objects should always have their vertical axis (the Y axis in Unity, or Z axis in VPX) of the pivot point at playfield height, so setting it to 0 will position the object on the playfield. +- Objects that rotate need their pivot point on the rotation axis. If such an object is parented to another (static) object, the parent should also have its vertical origin at playfield height. +- On the horizontal plane, the pivot point should be in the center unless the object's topology suggests another more logical position. + +
+A drop target with the origin placed at different locations. + +### UV Maps + +All models must be [UV-mapped](https://en.wikipedia.org/wiki/UV_mapping). + +- UVs should be unwrapped with minimal stretching. +- Maintain 2-4 pixel padding between UV islands to prevent texture bleeding. +- Keep UV shells proportional to their 3D size to maintain consistent texel density. +- Organize UVs within the 0-1 UV space. + +### Decals + +Decal Setup of two drop targets + +If your model contains art that varies from instance to instance, use a [decal mesh](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.2/manual/understand-decals.html). Decals should be used where you would find literal decals or imprints in the real world. Examples include spinners, aprons, targets, and bumpers. + +On the right side you see two drop target meshes with their corresponding decal meshes in orange. + +The decal geometry should be in a separate object parented to the main object. The UVs of the decal mesh should be laid out in a way that allows its textures to be created with non-specialized image editors. + +
+Be sure to keep the aspect ratio without distortion when UV-mapping decals. + +> [!note] +> #### Why Decals? +> Decals are great because they make your workflow more flexible and are at the same time more performant: +> - Flexible, because it allows us to texture our models in a generic way so that they can be used in any context. Imagine a drop target with a star on it. Without decals, the star would be baked into the texture, and if anybody else would want reuse that target with different art on it, they would need to recreate the texture. With decals, they only need to swap out the decal texture. +> - Performance, because Unity is optimized for having thousands of decals in a scene, allowing us to use higher-resolution textures for our decals without having to waste resources on the rest of the object. + +### Colliders + +Decal Setup of two drop targets + +VPE uses separate meshes for collision for some items (currently drop targets and hit targets). These collider meshes should be included in the model. + +- Their pivot point must align with the pivot point of the main mesh. +- The scale must be applied and correspond to the main mesh's scale. +- They shouldn't include any UVs. + +You can see a hit target mesh with its collider mesh in orange on the right side. + +### LODs + +Regarding [LODs](https://en.wikipedia.org/wiki/Level_of_detail_(computer_graphics)) (Levels of Detail), we're only using one LOD. This guideline is based on the compact size of the playfield, where most elements would be rendered at the same LOD anyway. Additionally, most assets will be under 1,000 triangles, making the performance impact of LODs minimal. + +### File Format + +Export your meshes in a format [supported by Unity](https://docs.unity3d.com/6000.5/Documentation/Manual/3D-formats.html). The preferred formats are [glTF](https://en.wikipedia.org/wiki/GlTF) and [FBX](https://en.wikipedia.org/wiki/FBX). Avoid [`.obj`](https://en.wikipedia.org/wiki/Wavefront_.obj_file) due to its limitations and inefficiency. + +
+ + +Vendor-specific formats are also to avoid (even `.blend`), because they only can be imported if the corresponding 3D software is installed. + +## Material Guidelines + +We use physically based rendering ([PBR](https://en.wikipedia.org/wiki/Physically_based_rendering)) for realistic visuals. In Unity HDRP, this generally means using the [Lit Shader](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.2/manual/lit-material.html). + +> [!note] +> We're still determining whether authors will be able to choose other shaders, create their own, or if usage will be restricted. We'll update this section as soon as we have more information. For now, we focus on authoring using the Lit Shader. + +### Texture Maps + +In the PBR workflow, these texture maps are most relevant: + +- Color map (also called diffuse or albedo map) +- Normal map (often called bump map) +- Metallic map +- Smoothness map (which is an inverted roughness map) +- Ambient occlusion map + +Unity also supports emissive maps, detail maps, and others depending on the material type, but we'll focus on those mentioned above. + +### Color Maps + +The color map contains RGB values that represent surface color without lighting information. + +If your asset or parts of your asset exist in multiple color variations, consider using only gray tones and tinting the material with the Lit shader's base color. This approach makes it customizable without having to render out textures for each color variant (and is also more memory-efficient). + +
+A drop target with the same base material but different tints, and three different decal materials. + +As mentioned above, don't bake art that varies into the texture; use decals instead. Single-color decals that come in multiple color variations should also use a gray-tone color map and be tinted through the Decal Shader directly, making them more easily customizable without requiring multiple material instances. + +### Normal Maps + +[Normal Maps](https://en.wikipedia.org/wiki/Normal_mapping) simulate lighting effects on surface details without adding geometry. Generally, if a detail doesn't have any silhouette-defining features and isn't deep enough to cast visible shadows, flatten it and bake it as a normal map. + +
+A playfield toy at 185k tris (left), 2.7k tris without normal map (middle) and with normal map (right). + +Edges are particularly important for achieving realistic visuals. In the real world, light always reflects off edges because they are never perfectly sharp. To simulate this, you should always bevel your edges. For models with few prominent edges, you can add bevels in the geometry. However, baking the bevel into a normal map is typically more efficient. + +
+A gate with beveled geometry at 3.6k tris (left), at 420 tris without normal map (middle) and with normal map (right). + +To summarize, use normal maps for: + +- Surface details (scratches, small dents, panel seams) +- Shallow details (<5mm in real scale) +- Beveled edges +- Text or logo embossing +- Pattern detailing + +### Metallic / Smoothness Maps + +With a metallic map, you can define on pixel level whether your material is metallic, or not. +- Only use this if your material covers both metallic and non-metallic parts of your model. Otherwise, use the metallicness property of the Lit Shader directly. +- You should only use values of 0 or 1, as partially metallic materials don't exist in the real world. + +The smoothness map (the inverse of a roughness map) defines how regularly light is reflected at the micro-surface level. A value of 1 behaves like a mirror, while a value of 0 is more like an eraser. + +
+Metallicness set to 1 with smoothness going from 0 to 1. + +### Texture Map Resolution + +All texture maps must use power-of-two dimensions for width and height (e.g., 256, 512, 1024). They don't have to be square. + +
+ +We're aiming for a resolution of about 6 pixels per millimeter (approximately 150 DPI). For a playfield texture, this means roughly 4096×8192 pixels. Use this resolution when possible, but don't upscale images — the highest resolution should be from your source. This applies to both color and normal maps. For metallic/smoothness maps, half the resolution of the color map is a good balance between performance and visual fidelity. + +> [!note] +> You can determine the resolution by looking at your UV map and the size of the asset. Let's take the gate from the previous section as an example. +> +> 1. Take a large section of your mesh, and measure it. The larger, the more precise it will be. Here we're measuring the top surface, from where the bevel starts:
+> +> 2. Next, identify that section on your UV map, and note where in UV space they are:
+>
+> As you can see, they go from 0.360 to 0.955 on the X axis. +> 3. In terms of dimensions, we now have the following: +> - Width in UV space: 0.955 - 0.36 = 0.595 +> - Width in real world space: 29.2mm +> 4. At 6px / mm, that makes 6px × 29.2mm = 175.2px for the 0.595 UVs +> 5. To get the resolution of the whole UV map: 175.2px / 0.592 = **296px** +> +> So, a texture map at 296×296 would correspond to 6px / mm. Since we're at power of twos, we could go for either 512×512 or 256×256. + +### Compression + +Export your texture maps in **PNG format**. Use 32-bit if they include an alpha channel; otherwise, 24-bit is sufficient. Use halved resolution for the mask map. Don't quantize / TinyPNG your maps, since together with the GPU's block compression they will result in artifacts. + +> [!note] +> Many artists prefer to export their textures as TGA due to faster read/write speeds, but TGA files consume significantly more space. PNG uses lossless compression, and once imported, Unity stores textures in a GPU-friendly format anyway. Additionally, when exporting to the `.vpe` format, image textures are converted into a runtime-optimized format. + +In terms of *packaging*, Unity's HDRP stores metallic, ambient occlusion and smoothness in what they call a [mask map](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.2/manual/Mask-Map-and-Detail-Map.html). Substance Painter exports this out of the box, but it should be pretty easy to do with Blender as well. + +### Wear + +Generally, all items should show visible signs of wear, rather than appearing brand new from the factory. This adds realism and character to the assets. + +
+They certainly have each their look. But in order to keep consistency, you should follow the example in the middle. + +### Standard Materials + +Many materials in the asset library will appear in multiple assets. To keep a consistent look across all assets, you should use existing materials. We're providing Substance Painter Smart Materials for metals and plastics (and hopefully Blender versions as well). + +ℹ️  Table coming soon + +## Attribution + +When submitting assets to the library, please include your name or handle for proper attribution. If you've adapted assets from other sources, ensure you have the necessary permissions and include appropriate credits. + +## Where to Submit + +Don't submit yet! 😄 + +We're still figuring out the best structure and repository for our asset library (our [current repo](https://github.com/VisualPinball/VisualPinball.Unity.AssetLibrary) is getting too large). There will also be additional guidelines about metadata. + +This section will be updated when we're ready! diff --git a/VisualPinball.Unity/Documentation~/creators-guide/manual/sound.md b/VisualPinball.Unity/Documentation~/creators-guide/manual/sound.md index bad2f279a..4463bfe66 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/manual/sound.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/manual/sound.md @@ -12,7 +12,7 @@ Unity, the game engine VPE is built on top of, provides many useful audio features out of the box. It can import and play several common audio file formats, it supports directional audio and surround sound and provides mixing and mastering functionality. For a comprehensive overview, refer to the -[Unity documentation](https://docs.unity3d.com/6000.0/Documentation/Manual/Audio.html). +[Unity documentation](https://docs.unity3d.com/6000.5/Documentation/Manual/Audio.html). On top of this built-in functionality, VPE provides some features specific to pinball. @@ -67,9 +67,9 @@ does: ## Sound Assets After importing your a sound file into Unity, you get an -[Audio Clip](https://docs.unity3d.com/6000.0/Documentation/Manual/class-AudioClip.html). +[Audio Clip](https://docs.unity3d.com/6000.5/Documentation/Manual/class-AudioClip.html). You could create an -[Audio Source](https://docs.unity3d.com/6000.0/Documentation/Manual/class-AudioSource.html) +[Audio Source](https://docs.unity3d.com/6000.5/Documentation/Manual/class-AudioSource.html) in your scene and play this clip directly, but VPE allows you to construct different types of sound assets out of one or multiple of these audio clips to produce variation and customize how the sound is played with pinball in mind. diff --git a/VisualPinball.Unity/Documentation~/creators-guide/setup/installing-vpe.md b/VisualPinball.Unity/Documentation~/creators-guide/setup/installing-vpe.md index c875f5fd9..d27ddac26 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/setup/installing-vpe.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/setup/installing-vpe.md @@ -1,77 +1,77 @@ ---- -description: How to install VPE ---- - -# Installing VPE - -## Unity - -In order to start creating or modifying tables with VPE, the first thing you'll need to do is install [Unity](https://unity3d.com/get-unity/download). You will need a Unity developer account, which is free. - -> [!NOTE] -> As long as you don't use Unity for a game that makes $200K or more in revenue or funding a year, the free [Personal](https://store.unity.com/compare-plans) plan is sufficient for you. - -Unity uses an application called *Unity Hub* to update itself, create new projects and provide quick access to them. The install process is straight-forward and documented [here](https://docs.unity3d.com/Manual/GettingStartedInstallingHub.html) if you run into troubles. - -**Unity 6.2** is the recommended Unity version at the moment. - -You can leave all the other options unchecked during install. - -Once Unity is downloaded and installed, you're ready to create a new VPE project. Click on *New Project*, be sure to have selected the 6.2 version at the top, and you'll see the following choices: - -![New Unity Project](unity-create-new-project.png) - -The relevant options for VPE are: - -- **Universal 3D** - Unity's [URP](https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@8.2/manual/index.html) is aimed at mobile and low-end platforms. -- **High Definition 3D** - Unity's [HDRP](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@12.0/manual/index.html) used for high-end platforms. - -We recommend using HDRP. It's what we're using when developing and should be the most stable pipeline. Alternatively if you're on a laptop don't have a beefy GPU, use the URP. The built-in renderer is legacy not recommended. - -Next, enter a project name and a location for your project. Clicking *Create project* launches the Unity editor, pulls in all the dependencies for the new project, and compiles them. This will take a few minutes. - -### HDRP Setup - -Once the editor has opened you can click away the HDRP Wizard that opens. You should now see an empty scene: - -![HDRP Empty Scene](unity-hdrp-empty-scene.png) - -Click on *File -> New Scene* and select the *Basic Indoors (HDRP)* template. Save it in your *Assets/Scenes* folder as `MyTable.unity`. - -In this scene, there are already some objects we don't need. In the [Hierarchy](https://docs.unity3d.com/Manual/Hierarchy.html), select the *Geometry* game object and delete it by pressing `DEL`. Your scene should now look like this: - -![MyTable Scene](unity-hdrp-test-scene.png) - -Hit *Ctrl+S* to save your scene. - -## VPE Package - -Now that you have your project and scene set up, let's bring in the VPE libraries. VPE ships as a package that you can install using the [Package Manager](https://docs.unity3d.com/Manual/Packages.html) inside of Unity directly. However, since Unity's package registry is only used for official Unity content, we need to add our own registry first. - -To do that, go to *Edit -> Project Settings*, and select the *Package Manager* panel on the left. Under *Scoped Registries*, add the following: - -- Name: `Visual Pinball Engine` -- URL: `https://registry.visualpinball.org/` -- Scope(s): `org.visualpinball` -- Scope(s): `com.bartofzo` - -Also check *Enable Pre-release Packages* (and confirm), as well as *Show dependencies*. Your settings page should now look like this: - -Scoped Registry - -Hit *Save* and close the window. Now you'll add VPE's HDRP package, which will automatically pull in the core package and the assets package. - -Open the package manager by clicking on *Window -> Package Management -> Package Manager*. Then click on the "plus" icon on the top left corner of the window, and choose *Add package by name..*. - -

Package Manager

- -There, enter `org.visualpinball.engine.unity.hdrp` and click *Install*. This will take a moment as Unity downloads and compiles all of VPE's dependencies and parses all the assets that we ship in our library. - -> [!WARNING] -> Our patcher, which is currently part of the main package, depends on the PinMAME package. Until we move the patcher into a separate package, you will have to install the PinMAME package as well. To do that, click on the plus button again and enter `org.visualpinball.engine.pinmame`, then click on *Add*. - -When complete, you should now have a *Pinball* menu in the editor, and you should see the following new packages in the package manager (version numbers will vary): - -

Unity Input System Warning

- -Now that VPE is installed let's [import a table](xref:setup-running-vpe)! +--- +description: How to install VPE +--- + +# Installing VPE + +## Unity + +In order to start creating or modifying tables with VPE, the first thing you'll need to do is install [Unity](https://unity3d.com/get-unity/download). You will need a Unity developer account, which is free. + +> [!NOTE] +> As long as you don't use Unity for a game that makes $200K or more in revenue or funding a year, the free [Personal](https://store.unity.com/compare-plans) plan is sufficient for you. + +Unity uses an application called *Unity Hub* to update itself, create new projects and provide quick access to them. The install process is straight-forward and documented [here](https://docs.unity3d.com/Manual/GettingStartedInstallingHub.html) if you run into troubles. + +**Unity 6.5** is the recommended Unity version at the moment. + +You can leave all the other options unchecked during install. + +Once Unity is downloaded and installed, you're ready to create a new VPE project. Click on *New Project*, be sure to have selected the 6.5 version at the top, and you'll see the following choices: + +![New Unity Project](unity-create-new-project.png) + +The relevant options for VPE are: + +- **Universal 3D** - Unity's [URP](https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@8.2/manual/index.html) is aimed at mobile and low-end platforms. +- **High Definition 3D** - Unity's [HDRP](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@12.0/manual/index.html) used for high-end platforms. + +We recommend using HDRP. It's what we're using when developing and should be the most stable pipeline. Alternatively if you're on a laptop don't have a beefy GPU, use the URP. The built-in renderer is legacy not recommended. + +Next, enter a project name and a location for your project. Clicking *Create project* launches the Unity editor, pulls in all the dependencies for the new project, and compiles them. This will take a few minutes. + +### HDRP Setup + +Once the editor has opened you can click away the HDRP Wizard that opens. You should now see an empty scene: + +![HDRP Empty Scene](unity-hdrp-empty-scene.png) + +Click on *File -> New Scene* and select the *Basic Indoors (HDRP)* template. Save it in your *Assets/Scenes* folder as `MyTable.unity`. + +In this scene, there are already some objects we don't need. In the [Hierarchy](https://docs.unity3d.com/Manual/Hierarchy.html), select the *Geometry* game object and delete it by pressing `DEL`. Your scene should now look like this: + +![MyTable Scene](unity-hdrp-test-scene.png) + +Hit *Ctrl+S* to save your scene. + +## VPE Package + +Now that you have your project and scene set up, let's bring in the VPE libraries. VPE ships as a package that you can install using the [Package Manager](https://docs.unity3d.com/Manual/Packages.html) inside of Unity directly. However, since Unity's package registry is only used for official Unity content, we need to add our own registry first. + +To do that, go to *Edit -> Project Settings*, and select the *Package Manager* panel on the left. Under *Scoped Registries*, add the following: + +- Name: `Visual Pinball Engine` +- URL: `https://registry.visualpinball.org/` +- Scope(s): `org.visualpinball` +- Scope(s): `com.bartofzo` + +Also check *Enable Pre-release Packages* (and confirm), as well as *Show dependencies*. Your settings page should now look like this: + +Scoped Registry + +Hit *Save* and close the window. Now you'll add VPE's HDRP package, which will automatically pull in the core package and the assets package. + +Open the package manager by clicking on *Window -> Package Management -> Package Manager*. Then click on the "plus" icon on the top left corner of the window, and choose *Add package by name..*. + +

Package Manager

+ +There, enter `org.visualpinball.engine.unity.hdrp` and click *Install*. This will take a moment as Unity downloads and compiles all of VPE's dependencies and parses all the assets that we ship in our library. + +> [!WARNING] +> Our patcher, which is currently part of the main package, depends on the PinMAME package. Until we move the patcher into a separate package, you will have to install the PinMAME package as well. To do that, click on the plus button again and enter `org.visualpinball.engine.pinmame`, then click on *Add*. + +When complete, you should now have a *Pinball* menu in the editor, and you should see the following new packages in the package manager (version numbers will vary): + +

Unity Input System Warning

+ +Now that VPE is installed let's [import a table](xref:setup-running-vpe)! diff --git a/VisualPinball.Unity/Documentation~/creators-guide/tutorials/realistic-playfield/4-import-into-unity.md b/VisualPinball.Unity/Documentation~/creators-guide/tutorials/realistic-playfield/4-import-into-unity.md index 1a5b2db1a..0546ee9f3 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/tutorials/realistic-playfield/4-import-into-unity.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/tutorials/realistic-playfield/4-import-into-unity.md @@ -46,7 +46,7 @@ This is our starting point. The playfield is the original import from a VPX file > [!note] > **Crash course in *Scene* navigation** > -> Move the camera like in a first-person shooter. Hold the right mouse button, then use the `WASD` keys for horizontal movement, `Q` and `E` for vertical movement. The mouse wheel changes the movement speed. Just panning the screen is done while holding the middle mouse button. Full documentation [here](https://docs.unity3d.com/6000.0/Documentation/Manual/SceneViewNavigation.html). +> Move the camera like in a first-person shooter. Hold the right mouse button, then use the `WASD` keys for horizontal movement, `Q` and `E` for vertical movement. The mouse wheel changes the movement speed. Just panning the screen is done while holding the middle mouse button. Full documentation [here](https://docs.unity3d.com/6000.5/Documentation/Manual/SceneViewNavigation.html). In the *Project* panel you see our FBX file. It's a tree structure because Unity imported it and found out that it contains two objects and two meshes. Drag the entire *Playfield* object from the *Project* panel into the *Hierarchy* and drop it over the *Playfield* GameObject. @@ -58,7 +58,7 @@ It does it with an animation, so that gives you a hint whether the imported play ![Model visible](unity-model-visible.png) -At scale `1000` we're starting to see something. However, it's oriented incorrectly. Setting the X-rotation to `-90` and Z to `180` fixes that. Now, we need to align the model over the existing one. For that, let's switch into orthogonal camera view by clicking on the small *Persp* icon in the top right corner of the *Scene* view, followed by a click on the Y axis of the 3D gizmo to switch to *Top View*. Click the [shading mode icon](https://docs.unity3d.com/6000.0/Documentation/Manual/ViewModes.html) and choose *Wireframe*. This allows you to align the playfield without anything being hidden. +At scale `1000` we're starting to see something. However, it's oriented incorrectly. Setting the X-rotation to `-90` and Z to `180` fixes that. Now, we need to align the model over the existing one. For that, let's switch into orthogonal camera view by clicking on the small *Persp* icon in the top right corner of the *Scene* view, followed by a click on the Y axis of the 3D gizmo to switch to *Top View*. Click the [shading mode icon](https://docs.unity3d.com/6000.5/Documentation/Manual/ViewModes.html) and choose *Wireframe*. This allows you to align the playfield without anything being hidden. ![Align from top](unity-align-wireframe-top.png) diff --git a/VisualPinball.Unity/Documentation~/developer-guide/index.md b/VisualPinball.Unity/Documentation~/developer-guide/index.md index 0998d8f01..a46e1e708 100644 --- a/VisualPinball.Unity/Documentation~/developer-guide/index.md +++ b/VisualPinball.Unity/Documentation~/developer-guide/index.md @@ -65,8 +65,9 @@ Use it for reusable art content rather than engine or gameplay code. ## Future integrations -The developer guide also tracks design work for integrations that are not yet fully implemented in VPE. These pages are intended to capture architectural direction early, so implementation work across native input, runtime systems, and tooling can converge on the same design. - -- [Accelerometer Input Design](xref:developer-guide-accelerometer-input-design) covers analog nudge input, Open Pinball Device support, calibration, and how a future player app should participate in initial setup. -- [B2S Integration Design](xref:developer-guide-b2s-integration-design) proposes modernizing the upstream B2S runtime into a shared cross-platform core with a Windows COM shim, a native second-monitor host, and a Unity texture output for VR backglasses. -- [DOF Integration Design](xref:developer-guide-dof-integration-design) covers a Windows-first `DirectOutput` integration for the future player app and the later hybrid path toward a `libdof` backend. \ No newline at end of file +The developer guide also tracks design work for integrations that are not yet fully implemented in VPE. These pages are intended to capture architectural direction early, so implementation work across native input, runtime systems, and tooling can converge on the same design. + +- [Packaging](xref:developer-guide-packaging-overview) documents the `.vpe` format, the export/import split, the renderer-agnostic material vocabulary, and the benchmarked optimization work around package loading. +- [Accelerometer Input Design](xref:developer-guide-accelerometer-input-design) covers analog nudge input, Open Pinball Device support, calibration, and how a future player app should participate in initial setup. +- [B2S Integration Design](xref:developer-guide-b2s-integration-design) proposes modernizing the upstream B2S runtime into a shared cross-platform core with a Windows COM shim, a native second-monitor host, and a Unity texture output for VR backglasses. +- [DOF Integration Design](xref:developer-guide-dof-integration-design) covers a Windows-first `DirectOutput` integration for the future player app and the later hybrid path toward a `libdof` backend. diff --git a/VisualPinball.Unity/Documentation~/developer-guide/packaging/benchmarks.md b/VisualPinball.Unity/Documentation~/developer-guide/packaging/benchmarks.md new file mode 100644 index 000000000..a45a475f5 --- /dev/null +++ b/VisualPinball.Unity/Documentation~/developer-guide/packaging/benchmarks.md @@ -0,0 +1,283 @@ +--- +uid: developer-guide-packaging-benchmarks +title: Packaging Benchmarks +description: Benchmarks, validated optimization results, and packaging experiments around .vpe loading. +--- + +# Packaging Benchmarks + +This page is a record of the optimization work around `.vpe` load time and package size. It is here for two reasons: to make the validated wins easy to keep, and to stop future work from re-running dead-end experiments without context. + +## Summary + +The table below is the short version. + +| Experiment | Status | Recommendation | Main result | +| --- | --- | --- | --- | +| Source textures + player-side GPU cook & cache | Validated and merged (June 2026) | Shipping architecture | T2: first load ~9.5s, cached ~1.1s; package lossless at 477 MB | +| Cooked GPU textures embedded in the package | Superseded | Replaced by the player-side cook | Proved the BC7/no-decode path (~1.7-2.2s) but broke losslessness and grew packages to 689 MB | +| Packed `textures.bin` sidecar | Superseded | Don't reintroduce; use the source layer | Improved package shape, but the lossless per-file `table/textures/` source layer replaced it entirely | +| GPU HDRP normal repack (resolver) | Superseded by the cook | Keep cook-side GPU repack | The resolver's GPU repack shader was removed; the GPU normal repack now runs once during the texture cook and is cached (the resolver only CPU-repacks the imported-GLB fallback path) | +| Per-texture mip control via the `GenerateMipMaps` flag | Validated and merged | Keep | Replaces the old hard-coded linear-mip skip; export can still skip mips on heavy linear payloads, small but measurable win | +| PNG side-channel textures | Validated | Baseline only | Correct but decode-heavy | +| `DXT5` linear + `BC7` sRGB side textures in `textures.bin` | Tested and validated as a benchmark | Superseded by the player-side cook | Best in-package size/load tradeoff found, but the cache reached the same cached-load speed losslessly | +| Raw `RGBA32` side textures | Tested and invalidated | Do not use as shipping format | Very slow export, regression risk | +| Move GLB normals to sidecar | Tested and invalidated | Do not use without deeper investigation | Smaller/faster, but caused ghosting | +| DDS/BC7 embedded directly in GLB | Tested and invalidated | Do not use | Smaller package, much slower load | +| KTX2 / `KHR_texture_basisu` for GLB normals | Tested and invalidated for startup speed | Do not prioritize for load time | Smaller package, slower load | +| Persistent raw runtime texture cache | Tested and invalidated | Do not revive in the same form | Speedups came with corruption/crash risk | + +## Source textures + player-side GPU cook (shipping since June 2026) + +The final architecture honors two hard format constraints — no Unity dependency, lossless +editor re-import — while keeping the no-decode load path: + +- the `.vpe` carries **original asset file bytes** (PNG/JPEG, no re-encoding) for every + captured texture; `table.glb` carries no images for captured materials (9.8 MB vs 345.8 MB + on T2) +- the **player cooks** the sources on first load: parallel decode (StbImageSharp on workers, + native `LoadImage` on the main thread for the few huge files), GPU mip generation, GPU BC7 + encoding (DirectXTex compute shaders, MIT), AG normal repack — then persists a per-table + cache keyed by package size/mtime and cook settings +- cached loads upload raw BC7 straight from the cache blob + +Measured on Terminator 2 (editor play mode): + +- baseline (PNG everywhere, 486 MB package): **23.3 s** every load +- source + cook (477 MB package): first load **9.5 s** (7.7 s cook), cached loads **1.1 s** + +An intermediate iteration embedded the cooked BC7 payload in the package itself (no cook at +load, ~1.7-2.2 s every load) — it proved the decode-free path but was dropped: BC7-only +packages are lossy one-way data (editor re-import degrades per generation) and 42% larger than +the sources. The player-side cook keeps the speed while the package stays lossless, and lets +users choose cook quality/resolution locally (see `VpeTextureCookSettings.ResolutionDivisor`). + +Supporting runtime work that landed with this: uninterrupted glTFast defer agent, +worker-thread prefetch of the texture cache (or source blob) and `materials.json`, +worker-thread bulk-read of all item/ref entries, worker-thread GLB extension/tangent scans, a +cached `PackagedRefs` type scan (was 450 ms per load), time-budget yields, and GPU work/upload +budgets per frame (without them, queueing hundreds of MB of GPU work in one frame crashed the +editor with `DXGI_ERROR_DEVICE_HUNG`). + +Editor re-import reconstructs texture *assets* from the source layer (normal-map type, sRGB, +sampling and max-size restored) and rebuilds materials through the same `HdrpMaterialResolver` +the player uses — restoring masks, thickness, transmission and refraction state the old +GLB-based import silently dropped. + +## Baseline context + +The optimization work started from a package that had two different classes of problems: too much texture data was effectively being paid for twice, and the runtime was doing expensive work that was easy to miss until proper timing logs were added. + +In practical terms, the baseline package: + +- carried far too much texture data across GLB and sidecar paths +- spent large amounts of runtime in material reconstruction and normal handling +- loaded the benchmark table in roughly the `10-11s` range + +After the merged fixes, the stable baseline moved down substantially, which made it much easier to see where the remaining bottlenecks actually were. + +## Merged and validated improvements + +### 1. Remove duplicate texture storage + +The export path now keeps opaque GLB textures on the glTF path and only side-channels the texture classes that actually need it. + +Result: + +- package size dropped materially from the original `200+ MB` range +- the remaining size largely represented real payload, not double-stored content + +### 2. Pack side textures into `textures.bin` + +The old one-file-per-texture side path was replaced by one packed blob plus metadata offsets. + +Result: + +- better package structure +- lower per-entry ZIP overhead +- better foundation for later compression work + +This was not the biggest speed win by itself, but it changed the shape of the format in an important way. Once the side textures lived in one blob, experiments around compression and direct upload became much easier to reason about. + +**Superseded:** the packed `textures.bin` was later dropped in favor of one plain PNG/JPEG file per texture under `table/textures/`. The zip central directory already provides the offset table, so the custom blob index was redundant, and per-file source entries keep the package losslessly re-exportable. The compression/direct-upload work it enabled now lives in the player-side cook and cache. + +### 3. Cache resolved materials during import + +`VpeMaterialReader` now reuses already-resolved materials instead of rebuilding equivalent runtime materials repeatedly. + +Result: + +- measurable reduction in material-restore time +- especially helpful on tables with many repeated imported materials + +### 4. GPU normal repack for HDRP + +At the time, this was the most important merged optimization: instead of repacking RGB normals for HDRP on the CPU inside `HdrpMaterialResolver`, a GPU shader did the channel conversion. + +Representative outcome: + +- total import dropped from roughly `10s` toward the `7-8s` range +- the normal repack hotspot dropped from multiple seconds to effectively negligible + +**Where it lives now:** the resolver's GPU repack shader (`VpePackNormalForHdrp`) was removed once the player-side cook took over. The cook does its own GPU normal repack (to dxt5nm) and bakes the result into the cache, so the cached load pays nothing for normals. The resolver now CPU-repacks only the normals that arrive through the non-cooked / imported-GLB fallback path. + +### 5. Per-texture mip control via the `GenerateMipMaps` flag + +An earlier version hard-coded a skip of runtime mip generation for linear side textures. That was generalized: each side texture now carries a `GenerateMipMaps` flag that the reader honors for both sRGB and linear payloads (`VpeMaterialReader` builds the `Texture2D` and calls `Apply` with the serialized value). Export can disable mips for the classes that do not need them — typically the heavy linear mask/thickness payloads — so the decision lives with the package rather than the reader. + +Linear side textures can additionally be block-compressed at load (`Texture2D.Compress`, BC7) when their `RuntimeCompress` flag is set, trading a little load time for much lower runtime memory. (The pre-cooked cache path skips this entirely — it uploads raw BC7 with baked mips and no decode.) + +Result: + +- small but real improvement on the heavy linear classes +- low risk, and the choice now lives with the package author + +## Best unmerged result + +The best benchmark result came from compressing side-channel textures into GPU-native formats while leaving GLB textures on the normal glTF path. + +Format split: + +- `DXT5` for linear side textures +- `BC7` for sRGB side textures + +Representative result on the benchmark table: + +- package around `142.5 MB` +- total import around `6.23s` +- side-texture load time around `80 ms` +- `compressedTextureLoads=97` +- `encodedTextureLoads=0` + +Why it worked: + +- it avoided PNG decode for VPE-owned textures +- it preserved the stable GLB semantics +- it aligned the side-channel path with direct GPU upload + +Why it stayed unmerged: + +- desktop-oriented GPU format choice that needs a fallback for unsupported platforms +- needs a clear editor re-import story +- and, ultimately, the player-side cook reached the same cached-load speed (~1s) without giving up lossless re-import, so the in-package route was dropped + +It is kept here as the strongest *in-package* size/load result on record (≈142.5 MB, ≈6.23s); the shipped path is the cook plus cache (see [Status](#status)), not this. + +## Invalidated experiments + +### Raw `RGBA32` side textures + +Intent: + +- eliminate decode entirely by storing raw pixels + +Observed behavior: + +- package around `178 MB` +- export time ballooned to about `3 minutes` +- visual regressions were observed + +Conclusion: + +- useful as a proof that decode avoidance matters +- not suitable as a shipping format + +### Move GLB normals to sidecar + +Intent: + +- remove the heaviest remaining GLB image class +- reuse the side-channel path for normals + +Observed behavior: + +- package could shrink dramatically, down near `119 MB` +- visual regressions returned +- the main symptom was insert/plastic ghosting + +Conclusion: + +- do not revive this path without first understanding why imported GLB normals preserve behavior that the sidecar path did not + +### DDS / BC7 inside GLB + +Intent: + +- store precompressed GPU blocks directly inside GLB + +Observed behavior: + +- package shrank to roughly `116.8 MB` +- `table.glb` import became far slower +- total import climbed to roughly `11.3s` + +Conclusion: + +- smaller file on disk +- much worse uncached load time in this container format + +### KTX2 / `KHR_texture_basisu` for GLB normals + +Intent: + +- reduce GLB size while keeping payload compressed in a standard way + +Observed behavior: + +- package around `130.6 MB` +- GLB really contained KTX2 normals +- runtime was slower than the PNG/JPG GLB baseline + +Conclusion: + +- useful if package footprint is the goal +- not the right optimization if startup time is the goal + +### Persistent runtime texture cache + +Intent: + +- get close to editor-like warm-load behavior + +Observed behavior: + +- promising speedups +- but corrupted textures and second-run crashes appeared + +Conclusion: + +- caching has potential +- the tested implementation was not safe + +## glTFast-related findings + +Several experiments were constrained by glTFast: + +- export remains fundamentally PNG/JPG-oriented +- KTX2 import exists, but export is not a supported stock path +- custom GLB material extension export is easier as a local rewrite than as a glTFast-native feature +- smaller GLB image payloads do not automatically mean faster import + +These findings are why sidecar compression outperformed the GLB-focused experiments. + +## Status + +The load-time work is **done**. The player-side cook plus per-table cache (see the shipping section above) reached the goal: a table loads in about **one second** on a cached run, while the package stays lossless. + +The path this page used to list as the highest-value next step — compressed side textures with a runtime direct-upload path and an explicit format discriminator — is realized, just via a per-machine cache instead of in-package compression: + +- **direct upload, no decode** — the cached path uploads raw BC7 with baked mips (`isRawPayload` in `VpeMaterialReader`) +- **explicit format discriminator** — `VpeTexturePayload.PixelFormat` (the runtime/cache descriptor; the package's `VpeTexture` source entries are always encoded PNG/JPEG) +- **safer persistent cache** — the cook cache is keyed on package size/mtime and cook settings, so it invalidates correctly +- **overlapped IO/decode/setup** — worker-thread prefetch and parallel decode, with per-frame GPU work/upload budgets + +In-package compression (a smaller shipped package, at the cost of losslessness and a per-platform format choice) was measured — see [Best unmerged result](#best-unmerged-result) — but deliberately not pursued: the cache reaches the same cached-load speed without giving up lossless re-import. + +### Still not worth reviving + +These stayed dead ends; don't re-run them as startup-speed optimizations without new information: + +- GLB-normal relocation without deeper semantic investigation (caused insert/plastic ghosting) +- DDS-in-GLB for load time +- KTX2-in-GLB as a startup-speed optimization +- raw `RGBA32` side textures as a shipping format diff --git a/VisualPinball.Unity/Documentation~/developer-guide/packaging/export.md b/VisualPinball.Unity/Documentation~/developer-guide/packaging/export.md new file mode 100644 index 000000000..ddc258b77 --- /dev/null +++ b/VisualPinball.Unity/Documentation~/developer-guide/packaging/export.md @@ -0,0 +1,123 @@ +--- +uid: developer-guide-packaging-export +title: Packaging Export +description: How a table is written into the .vpe package structure. +--- + +# Packaging Export + +This page explains what gets written into a `.vpe` package and why each part exists. It is deliberately organized by package structure, not by the exact call order inside `PackageWriter`, because that is usually the more useful mental model when you are trying to understand or extend the format. + +The export entry point is `PackageWriter`. Export runs asynchronously (`PackageWriter.WritePackageAsync`) and is driven from the editor by `VpeExportRunner` behind a cancelable progress bar: the writer yields between stages and offloads texture byte-loading to worker threads, so the editor stays responsive and the user can cancel a long export. + +## Scene Payload + +The visible scene is exported to `table/table.glb` through [Unity's fork of glTFast](https://docs.unity3d.com/Packages/com.unity.cloud.gltfast@6.18/manual/index.html). + +The GLB contains: + +- hierarchy +- transforms +- meshes +- lights +- imported fallback materials (for shaders VPE does not translate) +- textures **only** for those unsupported-shader fallback materials + +For materials VPE captures, the GLB carries no image bytes at all — those textures move to the source layer (see [Texture Ownership](#texture-ownership)). The GLB also does not contain: + +- component packables +- cross-reference wiring +- globals +- editor assets +- table metadata +- VPE material vocabulary +- the VPE-owned texture source files + +### Scene Preparation + +The exporter does a little housekeeping before it hands the table to glTFast. The point of this step is to make sure the GLB contains the scene the player actually needs, not the slightly awkward authoring-time version of it. + +- table meshes are made readable +- author-time disabled `Light` components are enabled so they flow into `KHR_lights_punctual` +- invalid mesh renderers are suppressed so glTF export does not fail +- a temporary material-sanitizing scope (`IVpeMaterialGltfExportPreprocessor`) strips the textures VPE captures into the source layer, so the GLB does not duplicate those bytes +- after glTFast writes the GLB, the writer swaps glTFast's re-encoded images back to the original asset bytes (`GlbImageSwap`), keeping the textures that *do* remain in the GLB lossless + +## Collider Payload + +Physics-only meshes are exported to `table/colliders.glb`. Related metadata is written to `table/meta/colliders.json`, including: + +- prefab linkage +- whether the collider mesh was overridden +- path within the prefab + +This lets editor re-import reconnect those meshes correctly. + +## Packables and References + +Gameplay and authoring data is split into two trees: + +- `table/items/` - contains the data needed to instantiate and configure components. +- `table/refs/` - contains data that restores cross-references after the hierarchy and components already exist. + +The split exists because some data can be applied immediately, while other data only makes sense after the whole object/component graph has been rebuilt. + +## Table Metadata + +Table-level metadata is written to `table/table.json` as a `TableMetadata` object: table name, abbreviation, primary and secondary authors, release date, original release year, and manufacturer. Captured table screenshots are not part of this file — they are written separately, under the top-level `screenshots/` folder, alongside a `table-bounds.json` crop sidecar. + +## Globals, Assets, and Sounds + +The remaining package content is the non-scene part of the table. These files are small compared to the GLB and texture payloads, but they are what make the table playable rather than just renderable. + +- `table/global/` - switches, coils, lamps and wires +- `table/assets/` - serialized `ScriptableObject` assets plus metadata +- `table/sounds/` - sound bytes +- `table/meta/sounds.json` - sound lookup metadata + +## Material Payload + +If an `IVpeMaterialTranslator` is registered and captures material data, export writes: + +- `table/meta/materials.json` - a `VpeMaterialsPayload` with material profiles, texture metadata, and per-renderer state not covered by glTF. It carries a `FormatVersion` so readers can skip versions they don't understand. +- `table/textures/` - one plain PNG/JPEG **source file per texture**, stored (not deflated). These are the original asset bytes, shipped losslessly: no re-encode and no block compression at export time. + +Each texture is described by a `VpeTexture` entry in the payload. Instead of a byte range, the entry references its file by name (`FileName`) — the zip central directory *is* the offset table. Each entry also records: + +- `Id` (the value `VpeTextureRef.TextureId` points at) +- `MimeType` (`image/png` or `image/jpeg`) +- `ColorSpace` (`sRGB` or `Linear`) +- `WrapMode`, `FilterMode`, `AnisoLevel` +- `GenerateMipMaps` +- `RuntimeCompress` (a hint for readers that load the source without a cook path) +- `Width`, `Height` + +The important detail is that texture metadata and texture bytes are separate. `materials.json` tells runtime what a texture means and where it belongs; the file under `table/textures/` carries the bytes. Texture byte-loading at export is deferred and run on worker threads (`VpeTextureBlobSource` / `VpeTextureBlobLoader`), so disk reads and any PNG-16→8 conversion stay off the main thread. + +The table inspector exposes two texture-compression toggles. Because the package now ships lossless sources, both only affect the **fallback** runtime path — a reader that decodes the source itself instead of using the cooked cache (see [import](import.md)): + +- `Compress sidecar textures (Unity runtime compression)` sets `RuntimeCompress` on every `VpeTexture`, so a non-cooking reader block-compresses decoded linear textures. +- `Compress runtime normal maps (Unity runtime compression)` sets `RuntimeCompress` on RGB-packed normal references, so a non-cooking reader block-compresses normals after repacking them. + +The older `Compress glTF textures` toggle is gone: captured-material textures no longer live in the GLB, so there is nothing to compress there. + +### Texture Ownership + +The exporter splits texture ownership by **material**, not by individual map. For every material the active translator recognizes — HDRP/Lit, HDRP/Decal, HDRP/Unlit, and VPE's metal, rubber, DMD and fabric/silk shader graphs — **all** of its textures (base color, normal, mask, emissive, thickness, and so on) are written to the source layer (`table/textures/`) and referenced by `TextureId` from the profile. The GLB carries no image bytes for those materials. + +Textures stay in the GLB only for **unsupported** shaders — materials the translator does not recognize and therefore does not capture (it calls `RegisterUnsupportedMaterial` for them). Those keep their textures on the imported glTF path as a visual fallback, with the original asset bytes swapped back in (`GlbImageSwap`) so that path stays lossless too. + +This is the change that makes runtime import fast: the GLB path does no PNG decode for captured materials, and the player cooks the source textures once into GPU-ready data and caches them locally (see [import](import.md) and [benchmarks](benchmarks.md)). + +### VPE Shader Graph Materials + +The translator has special handling for VPE's own shader graphs, which plain glTF cannot represent: + +- metal shader-graph materials export as `vpe.metal` +- rubber shader-graph materials export as `vpe.rubber` +- DMD (dot-matrix display) materials export as `vpe.dmd` +- HDRP fabric/silk materials export as `vpe.fabric.silk` + +`vpe.metal`, `vpe.rubber` and `vpe.dmd` store a `TemplateName` (in their `VpeShaderGraphProfile` block) so the player can clone the matching measured-material template. `vpe.metal` and `vpe.rubber` additionally populate the profile's sibling `Lit` field with a `vpe.lit` fallback derived from the exposed shader-graph properties; `vpe.fabric.silk` carries its own `vpe.lit` fallback plus HDRP thread/fuzz hints. + +At runtime, an HDRP player should prefer the template path when it ships the same measured-material library. If the template is missing, `vpe.metal` and `vpe.rubber` fall back to their carried Lit payload so the package stays renderable. `vpe.fabric.silk` also carries a Lit fallback for renderers without fabric support, but the HDRP resolver needs the fabric template specifically. `vpe.dmd` has no Lit fallback — without its template the DMD material is skipped. diff --git a/VisualPinball.Unity/Documentation~/developer-guide/packaging/import.md b/VisualPinball.Unity/Documentation~/developer-guide/packaging/import.md new file mode 100644 index 000000000..20f8ec59e --- /dev/null +++ b/VisualPinball.Unity/Documentation~/developer-guide/packaging/import.md @@ -0,0 +1,120 @@ +--- +uid: developer-guide-packaging-import +title: Packaging Import +description: How runtime loading reconstructs a playable table from a .vpe package. +--- + +# Packaging Import + +The runtime import entry point is `RuntimePackageReader`. + +Its job is to reconstruct a playable table from the package without relying on Unity editor asset import. That sounds obvious, but it is the reason the import path looks different from editor re-import: runtime cannot lean on the asset database to quietly sort things out later. + +## High-level Flow + +Runtime loading is built around one rule: + +1. import the GLB +2. restore everything else against the imported hierarchy + +That gives the importer a concrete transform tree and renderer set before packables, refs, globals, and material restoration run. + +## GLB import + +`RuntimePackageReader` reads `table.glb` from the ZIP storage and imports it directly from memory through `GltfImport`. + +This produces: + +- the scene hierarchy +- renderer components +- imported fallback materials (for unsupported shaders) +- imported textures for those fallback materials only — captured materials carry no images in the GLB + +If present, the importer also probes the optional `VPE_materials` GLB extension before scene instantiation. That code exists for experiments and future work, but normal exported packages use the sidecar path instead. + +## Post-GLB Restoration + +After the hierarchy exists, runtime import restores: + +- sounds +- assets +- collider meshes +- packables from `items/` +- references from `refs/` +- globals +- table metadata from `table.json` +- material profiles from `materials.json` (restored last, against the fully built hierarchy) + +## Material Restore + +`VpeMaterialReader` owns the material-restore pass. + +It reads: + +- `meta/materials.json` +- the source textures under `table/textures/`, cooked through the local cache (see [Texture Loading](#texture-loading)) + +It then: + +- matches imported materials by normalized material name +- creates runtime materials through the active `IVpeMaterialResolver` +- reuses resolved materials aggressively to avoid rebuilding equivalent materials +- restores per-renderer state such as shadow casting and rendering layers + +After this step, the table is supposed to look like authored in the editor. + +## Texture Loading + +Textures are the heaviest part of the load, so this is where most of the runtime work goes. + +The package ships **lossless source files** (`table/textures/`), not GPU-ready data. On first load the player *cooks* the sources and writes the result to a per-table on-disk cache; later loads read straight from that cache. + +**First (uncached) load** — `VpeTextureSources` reads the PNG/JPEG entries from `table/textures/` into a transient in-memory blob, then `VpeTextureCook` turns them into GPU-ready payloads: + +1. decode on worker threads with StbImageSharp (very large files decode on the main thread via `ImageConversion.LoadImage`) +2. generate mips on the GPU +3. encode to BC7 on the GPU (DirectXTex compute shaders) +4. repack normal maps into the dxt5nm layout HDRP expects +5. persist the cooked payloads to the cache + +**Cached load** — the cache is validated, then the cooked BC7 payloads are uploaded directly, with no decode, mip generation, or encode. + +The cache lives under `Application.persistentDataPath/TextureCache/`, one file per package, keyed on the package path. Its header records the package file size and last-write time plus a hash of the cook settings (`VpeTextureCookSettings`: `ResolutionDivisor`, `CompressTextures`, format version), so it invalidates automatically when the package or the cook parameters change. Payloads are zstd-compressed in independent chunks for parallel decompression, and the format discriminator is `VpeTexturePayload.PixelFormat` (BC7 / DXT5 / RGBA32) — a payload already holding GPU-ready blocks is uploaded raw (`isRawPayload`). + +To keep a heavy table from stalling or crashing the GPU, the cook and upload run under per-frame budgets (a pixel budget for BC7 encode, a byte budget for uploads) and yield between batches. + +If a platform cannot cook (no GPU BC7 support), the reader falls back to decoding the source itself: it builds a `Texture2D` via `ImageConversion.LoadImage`, honoring `GenerateMipMaps`, and — for linear textures whose `RuntimeCompress` flag is set — block-compresses with `Texture2D.Compress(highQuality: true)` before making the texture non-readable. This fallback path is what the export compression toggles target. + +## Mipmapping Behavior + +`GenerateMipMaps` is part of the package contract: it says whether a given texture should have mips at runtime. The cook bakes mips into the cached payload accordingly, and the non-cooking fallback path honors the same flag when it calls `Apply`. Export can disable mips for the classes that don't need them — typically the heavy linear mask/thickness payloads — so the decision lives with the package rather than the reader. + +`RuntimeCompress` only matters on the fallback path: the cook already produces block-compressed (BC7) payloads when `CompressTextures` is on. Treat `GenerateMipMaps` as the runtime contract regardless of which path a reader takes. + +## HDRP Resolver Details + +The HDRP implementation of `IVpeMaterialResolver` is `HdrpMaterialResolver`. It: + +- clones pre-authored HDRP template materials +- applies VPE material intent onto those templates +- restores transmission, mask packing, decals, and renderer-specific state +- repacks RGB normal maps (the source packing) into the dxt5nm layout HDRP expects, optionally block-compressing the result when the normal's `RuntimeCompress` flag is set +- resolves `vpe.metal`, `vpe.rubber` and `vpe.dmd` by cloning player-shipped measured-material templates; `vpe.metal`/`vpe.rubber` fall back to their carried `vpe.lit` payload when a template is unavailable, and `vpe.fabric.silk` resolves a fabric template with a `vpe.lit` fallback +- consumes already-cooked textures from the reader — it does not decode or cook anything itself + +The resolver's own normal repack currently runs on the **CPU**. An earlier GPU repack shader (`VpePackNormalForHdrp`) was removed; the heavy normal work now happens once during the texture cook (which has its own GPU repack path) and is cached, so the resolver only repacks normals that arrive through the non-cooked or imported-GLB path. + +## Shader Variants in Player Builds + +Because the resolver flips HDRP/Lit `shader_feature` keywords at runtime, the variants it produces exist on no build-time material. The editor compiles them on demand, but a standalone build ships only the `shader_feature` combinations referenced by a build-time material or by a preloaded `ShaderVariantCollection`. A player therefore has to ship a captured collection, or runtime-resolved materials render with the wrong variant (broken transparency, refraction, and reflections) even though import succeeds. See [Shader Variants](shader-variants.md) for why this happens and how to keep the collection current as tables are added. + +## Dependencies + +Runtime import assumes: + +- `table.glb` is valid and self-consistent +- `materials.json` matches material names emitted into the GLB +- every `VpeTexture.FileName` resolves to an entry under `table/textures/` +- a compatible `IVpeMaterialResolver` is registered by the player + +If a resolver is not registered, runtime falls back to the glTF-imported materials and the table will not match authoring visuals. diff --git a/VisualPinball.Unity/Documentation~/developer-guide/packaging/index.md b/VisualPinball.Unity/Documentation~/developer-guide/packaging/index.md new file mode 100644 index 000000000..c0a998ee6 --- /dev/null +++ b/VisualPinball.Unity/Documentation~/developer-guide/packaging/index.md @@ -0,0 +1,78 @@ +--- +uid: developer-guide-packaging-overview +title: Packaging +description: Overview of the .vpe table format and how its parts fit together. +--- + +# Packaging + +*This section explains the `.vpe` table format.* + +A `.vpe` file is a ZIP container, split into a few layers with different jobs. We've tried to use open and documented formats throughout: the scene is glTF, every metadata file is JSON, and textures are stored as their original PNG/JPEG source files. Basically, VPE is trying to solve two problems at once: + +- package a table losslessly, in a way that stays editable and re-exportable +- keep the package independent from any specific render pipeline, while still loading fast + +So, the scene itself lives in glTF. Gameplay and authoring metadata live in JSON. Material behavior that glTF cannot express cleanly is described in VPE's own vocabulary, with texture bytes stored as separate source image files. The package ships lossless sources only; the GPU-ready (cooked, block-compressed) form is derived on the player's machine on first load and cached locally — it is never shipped. See [Benchmarks](benchmarks.md) for why. + +## Container Structure + +The package looks like this at a high level: + +```mermaid +treeView-beta + "(package root)" + "manifest.json" + "table/" + "table.glb" + "colliders.glb" + "table.json" + "textures/" + "items/" + "refs/" + "global/" + "assets/" + "sounds/" + "meta/" + "materials.json" + "colliders.json" + "sounds.json" + "lights.json" + "screenshots/" +``` + +The exact file and folder names are centralized in `PackageApi`, and every JSON file is written through `PackageApi.Packer` (currently `JsonPacker`, so the extension is `.json`). + +| Part | Why it exists | +| --- | --- | +| `manifest.json` | Root manifest with the container format version. Nodes are addressed by stable ids carried in glTF node extras (`vpeId`). | +| `table/table.glb` | Carries the visible scene graph: hierarchy, transforms, meshes, lights, and imported fallback materials. For materials VPE captures, the GLB carries **no** image bytes — those move to `table/textures/`. Only unsupported-shader materials keep their textures in the GLB. | +| `table/colliders.glb` | Carries meshes that exist for physics but are not naturally part of the visible glTF export. | +| `table/table.json` | Carries table-level metadata from `TableComponent.Metadata` (`TableMetadata`). | +| `table/textures/` | Carries VPE-owned textures as plain PNG/JPEG source files, one zip entry per texture, referenced by file name from `materials.json`. Stored (not deflated). This is the lossless source layer. | +| `table/items/` | Carries component and item data needed to rebuild gameplay objects after the hierarchy exists. | +| `table/refs/` | Carries cross-references that can only be restored after all items and components are in place. | +| `table/global/` | Carries table-wide mapping data: `switches.json`, `coils.json`, `lamps.json`, `wires.json`. | +| `table/assets/` | Carries serialized `ScriptableObject` assets used by the table. | +| `table/sounds/` and `table/meta/sounds.json` | Carry sound bytes and the metadata needed to resolve them. | +| `table/meta/materials.json` | Carries renderer-agnostic material intent for data that glTF does not express well enough for VPE. | +| `table/meta/lights.json` | Optional per-light profile data that glTF/`KHR_lights_punctual` does not carry. | +| `screenshots/` | Captured table screenshots (lights on/off, HDRI) plus a `table-bounds.json` crop sidecar. | + +## Materials + +If glTF covered everything VPE needed, there would be no `materials.json` and no separate `table/textures/` layer. In practice, glTF gets us a long way, but not all the way. Some authored material features are either renderer-specific, packed in ways glTF does not understand, or too fragile to trust to the fallback export/import path. + +VPE therefore uses a layered approach: + +- glTF carries what it already does well +- VPE captures the missing material intent in its own schema +- the active renderer in the player resolves that schema into real runtime materials + +That design keeps the package from depending on HDRP, while still letting an HDRP-based player reconstruct the authored look closely. + +For more details about how VPE's material abstraction, what glTF covers, what VPE adds, and what a renderer must implement, see [this page](materials.md). + +## Shader Variants + +The renderer-agnostic package describes material *intent*; the player turns that intent into real materials at runtime. Under HDRP this means runtime-built materials whose shader variants the editor compiles on demand but a standalone build strips — so a player must ship a captured shader variant collection. For why that is and how to keep it current as you add tables, see [Shader Variants](shader-variants.md). \ No newline at end of file diff --git a/VisualPinball.Unity/Documentation~/developer-guide/packaging/materials.md b/VisualPinball.Unity/Documentation~/developer-guide/packaging/materials.md new file mode 100644 index 000000000..7ad29dea4 --- /dev/null +++ b/VisualPinball.Unity/Documentation~/developer-guide/packaging/materials.md @@ -0,0 +1,279 @@ +--- +uid: developer-guide-packaging-materials +title: Packaging Materials +description: How VPE represents material intent beyond glTF and how a renderer consumes that contract. +--- + +# Packaging Materials + +This page covers the material side of the `.vpe` format, for two readers: + +- anyone trying to understand why materials are split between glTF and VPE metadata +- developers implementing a renderer for a different pipeline + +## glTF versus Custom Materials + +glTF carries the scene graph, meshes, transforms, lights, images, and a subset of physically based material data. For a mesh with a base color and a normal map, it is enough on its own. + +Pinball tables need more than that: alpha-bearing inserts and plastics, HDRP mask packing, decals whose albedo alpha drives where they apply, and renderer state glTF has no way to describe. A `.vpe` file also has to stay table content rather than a dependency on HDRP — it describes what a material should do, not which Unity shader property the authoring project happened to set. + +VPE therefore defines its own material vocabulary: + +- export translates authoring materials into VPE's schema +- runtime reads that schema and asks the active renderer to realize it +- the renderer realizes it with HDRP, URP, or anything else + +In code, that vocabulary lives in: + +- `VpeMaterialsPayload` +- `VpeMaterialProfile` +- `VpeTexture` +- `VpeRendererState` + +The schema is versioned by a `FormatVersion` field on the payload rather than by a type-name suffix; readers check it and skip versions they don't understand. + +## glTF Data + +The GLB carries the scene itself, plus a material fallback for shaders VPE does not translate. + +| Concern | Where it lives | Why | +| --- | --- | --- | +| Scene hierarchy | `table.glb` | Native glTF responsibility. | +| Meshes and transforms | `table.glb` | Native glTF responsibility. | +| Lights | `table.glb` | Exported through glTF/glTFast light support. | +| Materials for **unsupported** shaders | Imported GLB material (+ its textures) | VPE has no profile for them, so the imported glTF material is the fallback. These are the only materials whose textures stay in the GLB. | + +Earlier the GLB also carried the pixel data for "well-behaved" maps (opaque base color, emissive, normals) while only special maps were side-channeled. That split is gone: for every material VPE captures, **all** of its textures move to the source layer (`table/textures/`) and the GLB holds no image bytes for them. A profile that reads a texture from the imported GLB material (an *imported* texture reference) now only happens for those unsupported-shader fallbacks. + +## VPE Data + +VPE owns the data where glTF is lossy, underspecified for the feature, or too fragile in practice. + +### Texture and State + +| Authoring concern | VPE field(s) | Why it is not left to glTF | +| --- | --- | --- | +| HDRP `MaskMap` | `VpeLitProfile.MaskMap`, `MaskPacking` | HDRP mask packing is renderer-specific and not lossless in plain glTF. | +| HDRP `ThicknessMap` | `VpeLitProfile.ThicknessMap` | Thickness/transmission intent is outside plain glTF's core model. | +| Lit base color (incl. transparent / alpha-tested) | `VpeLitProfile.BaseColor.Texture` with `TextureId` | The alpha channel is load-bearing for inserts and plastics; the bytes ship in the source layer, not the GLB. | +| Decal base color | `VpeDecalProfile.BaseColor.Texture` with `TextureId` | The decal albedo alpha controls where the decal applies. | +| Decal mask map | `VpeDecalProfile.MaskMap` | Same problem as HDRP mask packing: the meaning is renderer-specific. | +| Per-renderer shadow and lighting state | `VpeRendererState` | glTF does not carry Unity's shadow casting mode, receive-shadows flag, or rendering layer mask. | + +### Storage + +Those VPE-owned textures are serialized as `VpeTexture` entries, and their bytes ship as plain PNG/JPEG files under `table/textures/` — one zip entry per texture, stored uncompressed. There is no packed blob and no byte-range index: the entry points at its file by name and the zip central directory does the rest. + +Each entry records: + +| Field | Meaning | +| --- | --- | +| `Id` | Stable logical identifier used by `TextureId`. | +| `FileName` | The texture's file name inside `table/textures/`. | +| `MimeType` | Declared encoding of the stored bytes (`image/png` or `image/jpeg`). | +| `ColorSpace` | `sRGB` or `Linear`. | +| `WrapMode`, `FilterMode`, `AnisoLevel` | Texture sampling intent. | +| `GenerateMipMaps` | Whether the runtime should generate/keep mips for this texture. Honored for both `sRGB` and `Linear`. | +| `RuntimeCompress` | Whether a reader *without* a cook path should call `Texture2D.Compress(...)` after decoding. Defaults to `true`. | +| `Width`, `Height` | Source dimensions (after any importer max-size clamp). | + +## Ownership Model + +Each authored material becomes one logical VPE profile, keyed by normalized material name. The profile contains semantic intent; it does not try to preserve the authoring shader as data. + +There are two texture-reference modes in the schema: + +| Reference style | Shape | Runtime behavior | +| --- | --- | --- | +| Imported texture ref | `TextureId = null` | Read the texture from the imported GLB material. Only used for unsupported-shader fallback materials. | +| Source texture ref | `TextureId != null` | Resolve the texture through its `VpeTexture` entry and load the file from `table/textures/` (cooked through the local cache). This is the path captured materials use. | + +This split is what keeps the schema portable. A profile can record "base color texture for a transparent insert" without binding that data to a particular HDRP shader property. + +## Supported Material Types + +The schema currently defines seven material types: + +| VPE type | Purpose | +| --- | --- | +| `vpe.lit` | Main physically based table materials, including transparency, transmission, and mask-based surface properties. | +| `vpe.decal` | Projected decal materials. | +| `vpe.unlit` | Unlit color-based materials. | +| `vpe.metal` | VPE metal shader graph materials. The profile stores a player template key and a `vpe.lit` fallback payload. | +| `vpe.rubber` | VPE rubber shader graph materials. The profile stores a player template key and a `vpe.lit` fallback payload. | +| `vpe.dmd` | Dot-matrix display materials. The profile stores a player template key, with no `vpe.lit` fallback. | +| `vpe.fabric.silk` | HDRP fabric/silk materials. The profile carries a `vpe.lit` fallback plus HDRP thread/fuzz hints. | + +The HDRP translator maps these authoring shaders into those types: + +| Authoring shader | VPE type | +| --- | --- | +| `HDRP/Lit` | `vpe.lit` | +| `HDRP/Decal` | `vpe.decal` | +| `HDRP/Unlit` | `vpe.unlit` | +| HDRP fabric/silk shader | `vpe.fabric.silk` | +| VPE metal shader graph | `vpe.metal` | +| VPE rubber shader graph | `vpe.rubber` | +| VPE DMD shader graph | `vpe.dmd` | + +Unsupported shaders are not fatal: they produce no VPE profile, and runtime falls back to the imported GLB material for them. + +## Runtime Resolution + +The package never instantiates pipeline-specific materials directly. Instead, runtime performs a translation step: + +1. `VpeMaterialReader` parses `materials.json`. +2. It resolves source textures from `table/textures/`, cooking them through the local cache. +3. It matches imported materials by normalized name. +4. It calls the active `IVpeMaterialResolver`. + +The last step is the pipeline-specific one. For HDRP, `HdrpMaterialResolver` clones authored template materials, applies VPE intent onto them, and lets HDRP reconcile the resulting state. + +The package stays portable; the player owns the final rendering behavior. + +Because a resolver builds these materials at runtime, the shader variants it produces are not present on any build-time material. A player that ships standalone builds must therefore preload those variants, or runtime-resolved materials render with the wrong variant in the build even though resolution succeeds. See [Shader Variants](shader-variants.md). + +## Specs + +This section is the renderer-facing contract — the part to read if you are implementing a new pipeline. It is a subset of HDRP and will grow as table builds start using more attributes. The tables link to the relevant HDRP documentation. + +### Inputs a renderer must consume + +A resolver implementation must be able to work with: + +| Input | Purpose | +| --- | --- | +| `VpeMaterialsPayload` | Top-level material payload. | +| `VpeMaterialProfile` | Per-material semantic intent. | +| `VpeTexture` | Metadata for a source texture file. | +| Imported fallback `Material` | Access point for textures on unsupported-shader fallback materials. | + +### Matching + +Profiles are matched to imported materials by normalized material name. A renderer should therefore treat the imported material name as the lookup key and degrade gracefully when there is no match. + +### Texture-source contract + +A renderer must support both texture-source modes: + +| Mode | How to detect it | What to do | +| --- | --- | --- | +| Imported | `TextureId == null` | Read the texture from the imported material using aliases appropriate for the pipeline. | +| Source | `TextureId != null` | Resolve the `VpeTexture`, load its file from `table/textures/`, create a texture, and apply the stored sampling/color-space settings. | + +### `vpe.lit` + +`vpe.lit` is a reduced HDRP Lit material. Read the table as a translation guide: each VPE field names a behavior to reproduce, not a Unity property another renderer is required to bind to. + +| VPE field | HDRP counterpart | HDRP documentation | +| --- | --- | --- | +| `BaseColor.Color`, `BaseColor.Texture`, `BaseColor.Texture.Scale`, `BaseColor.Texture.Offset` | HDRP Lit `Base Map`, including base tint, texture, tiling, and offset. The base-map alpha is also the source for transparency on transparent Lit materials. | [Lit Material Inspector reference: Base Map](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/lit-material-inspector-reference.html), [Surface Type](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/Surface-Type.html) | +| `Metallic`, `Smoothness` | HDRP Lit scalar `Metallic` and `Smoothness` fallback values when no packed mask texture is driving those channels. | [Lit Material Inspector reference: Metallic / Smoothness](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/lit-material-inspector-reference.html) | +| `OcclusionStrength` | Closest to HDRP ambient occlusion contribution from the mask map. This is not a clean one-to-one HDRP inspector property in the current subset, so other renderers should treat it as the scalar AO contribution knob. | [Lit Material Inspector reference: Mask Map / Ambient Occlusion Remapping](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/lit-material-inspector-reference.html), [Mask and detail maps](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/Mask-Map-and-Detail-Map.html) | +| `MaskMap`, `MaskPacking` | HDRP Lit `Mask Map`. In HDRP the packed layout is `R=Metallic`, `G=Ambient Occlusion`, `B=Detail Mask`, `A=Smoothness`. VPE keeps the packing explicit because other pipelines may not share HDRP's assumptions. | [Lit Material Inspector reference: Mask Map](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/lit-material-inspector-reference.html), [Mask and detail maps](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/Mask-Map-and-Detail-Map.html) | +| `MetallicRemap`, `SmoothnessRemap`, `AoRemap`, `AlphaRemap` | HDRP Lit remap sliders for packed texture channels. | [Lit Material Inspector reference: Metallic / Smoothness / Ambient Occlusion / Alpha Remapping](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/lit-material-inspector-reference.html) | +| `NormalMap.Texture`, `NormalMap.Texture.Scale`, `NormalMap.Texture.Offset`, `NormalMap.Strength`, `NormalMap.Packing` | HDRP Lit `Normal Map`, `Normal Map Space`, and normal intensity. VPE's packing enum preserves source intent so runtime can convert or reinterpret it as needed. | [Lit Material Inspector reference: Normal Map](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/lit-material-inspector-reference.html), [HDRP Glossary: tangent space normal map / object space normal map](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/Glossary.html) | +| `NormalMap.RuntimeCompress` | Whether HDRP should call `Texture2D.Compress(...)` after repacking an imported/runtime-loaded normal map into HDRP's expected layout. Older packages default to `true`. | No dedicated HDRP material page. | +| `Emissive.Color`, `Emissive.Texture`, `Emissive.Texture.Scale`, `Emissive.Texture.Offset`, `Emissive.Intensity`, `Emissive.IntensityUnit`, `Emissive.ExposureWeight` | HDRP Lit `Emission Map`, emission color, emission intensity, physical light units, and exposure weight. | [Lit Material Inspector reference: Emission inputs](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/lit-material-inspector-reference.html), [Physical light units](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/Physical-Light-Units.html) | +| `SurfaceType` | HDRP `Surface Type` and the extra transparent workflow it unlocks. | [Surface Type](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/Surface-Type.html) | +| `AlphaCutoff` | HDRP `Alpha Clipping > Threshold`. | [Alpha Clipping](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/Alpha-Clipping.html) | +| `DoubleSided`, `DoubleSidedGi` | HDRP `Double Sided` and `Double-Sided GI`. | [Double sided](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/Double-Sided.html), [Lit Material Inspector reference: Double-Sided GI](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/lit-material-inspector-reference.html) | +| `TransparentBlendMode` | HDRP transparent `Blending Mode`. | [Surface Type](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/Surface-Type.html) | +| `EnableFogOnTransparent`, `TransparentDepthPrepass`, `TransparentDepthPostpass`, `TransparentWritesMotionVectors` | HDRP transparent-surface options for fog participation, depth pre/post passes, and transparent motion vectors. | [Surface Type](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/Surface-Type.html), [Lit Material Inspector reference: Surface Options](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/lit-material-inspector-reference.html) | +| `DisableSsr`, `DisableSsrTransparent` | HDRP `Receive SSR` and `Receive SSR Transparent`. VPE names the field from the opt-out direction, HDRP names the inspector property from the opt-in direction. | [Lit Material Inspector reference: Receive SSR / Receive SSR Transparent](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/lit-material-inspector-reference.html) | +| `RenderQueueOverride` | Closest to HDRP sorting / render-pass override behavior. This is one of the places where VPE carries an explicit renderer-facing escape hatch rather than a pure material meaning. | [Surface Type](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/Surface-Type.html) | +| `RefractionModel`, `Ior` | HDRP transparent refraction settings: `Refraction Model` and `Index of Refraction`. | [Surface Type](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/Surface-Type.html) | +| `HasTransmission`, `Thickness`, `ThicknessMap` | HDRP transmission / translucent workflow. In HDRP this spans `Transmission`, `Thickness Map`, `Thickness`, and the `Diffusion Profile` asset. | [Material Type](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/Material-Type.html), [Diffusion Profile reference](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/diffusion-profile-reference.html) | + +### `vpe.decal` + +`vpe.decal` is the decal-projector/material subset that VPE currently uses. HDRP exposes most of this through the Decal Material inspector. + +| VPE field | HDRP counterpart | HDRP documentation | +| --- | --- | --- | +| `BaseColor.Color`, `BaseColor.Texture`, `BaseColor.Texture.Scale`, `BaseColor.Texture.Offset` | HDRP Decal `Base Map / Opacity`. In HDRP, the base-map alpha is part of the decal effect and also feeds other opacity-related controls. | [Decal Material Inspector reference: Base Map / Opacity](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/decal-material-inspector-reference.html) | +| `NormalMap.Texture`, `NormalMap.Texture.Scale`, `NormalMap.Texture.Offset`, `NormalMap.Strength`, `NormalMap.Packing` | HDRP Decal `Normal Map` plus its opacity-source behavior. VPE preserves the normal payload and strength; HDRP's extra opacity-source choices are currently hidden behind the resolver. | [Decal Material Inspector reference: Normal Map / Normal Opacity channel](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/decal-material-inspector-reference.html) | +| `MaskMap`, `MaskPacking` | HDRP Decal `Mask Map`. In HDRP decal materials the packed layout is `R=Metallic`, `G=Ambient Occlusion`, `B=Opacity Mask`, `A=Smoothness`. | [Decal Material Inspector reference: Mask Map](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/decal-material-inspector-reference.html), [Mask and detail maps](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/Mask-Map-and-Detail-Map.html) | +| `AffectAlbedo`, `AffectNormal`, `AffectMask` | HDRP Decal `Affect BaseColor`, `Affect Normal`, and the mask-derived surface toggles (`Affect Metal`, `Affect Ambient Occlusion`, `Affect Smoothness`). VPE collapses those mask-derived toggles into one semantic switch today. | [Decal Material Inspector reference: Affect BaseColor / Affect Normal / Affect Metal / Affect Ambient Occlusion / Affect Smoothness](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/decal-material-inspector-reference.html) | +| `DecalBlend`, `NormalBlendSrc`, `MaskBlendSrc` | HDRP decal blending behavior. There is no tidy single inspector property with these exact names in the docs, so these should be read as VPE's explicit representation of HDRP decal blend-source behavior. | [Decals](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/decals.html), [Decal Material Inspector reference](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/decal-material-inspector-reference.html) | +| `Smoothness`, `Metallic`, `AmbientOcclusion` | HDRP Decal scalar controls for the corresponding channels. | [Decal Material Inspector reference: Metallic / Ambient Occlusion / Smoothness](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/decal-material-inspector-reference.html) | + +### `vpe.unlit` + +`vpe.unlit` is small: it covers only the part of HDRP Unlit that VPE tables use. + +| VPE field | HDRP counterpart | HDRP documentation | +| --- | --- | --- | +| `BaseColor.Color`, `BaseColor.Texture`, `BaseColor.Texture.Scale`, `BaseColor.Texture.Offset` | HDRP Unlit `Color`, including the base texture, color multiplier, tiling, and offset. | [Unlit Material Inspector reference: Color](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/unlit-material-inspector-reference.html) | +| `SurfaceType` | HDRP Unlit `Surface type` and transparent workflow. | [Unlit Material Inspector reference: Surface type](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/unlit-material-inspector-reference.html), [Surface Type](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/Surface-Type.html) | +| `AlphaCutoff` | HDRP Unlit `Alpha Clipping`. | [Unlit Material Inspector reference: Alpha Clipping](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/unlit-material-inspector-reference.html), [Alpha Clipping](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/Alpha-Clipping.html) | +| `DoubleSided` | HDRP Unlit `Double-Sided`. | [Unlit Material Inspector reference: Double-Sided](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/unlit-material-inspector-reference.html), [Double sided](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/Double-Sided.html) | + +### `vpe.metal` + +`vpe.metal` covers VPE's measured metal shader-graph materials. They are authored as VPE-owned shader graphs rather than plain HDRP Lit materials, so the profile carries both a template key and a portable `vpe.lit` fallback for players that do not ship the same templates. + +The profile contains: + +| VPE field | Meaning | +| --- | --- | +| `Metal.TemplateName` | Stable template key owned by the player. HDRP resolves this through its measured-material override table and clones that template when available. | +| `Lit` | Fallback material intent built from the shader graph's exposed properties. The fallback includes color, mask map, metallic/smoothness/AO remaps, surface options, emissive settings, and renderer hints. | + +For HDRP, `HdrpMaterialResolver` first tries to create the material from `Metal.TemplateName`. If no matching player template exists, it falls back to the `Lit` payload and builds a normal HDRP Lit material. + +### `vpe.rubber` + +`vpe.rubber` follows the same template-plus-fallback pattern as `vpe.metal`, but targets VPE's measured rubber shader graph materials. + +The profile contains: + +| VPE field | Meaning | +| --- | --- | +| `Rubber.TemplateName` | Stable template key owned by the player. HDRP resolves this through its measured-material override table and clones that template when available. | +| `Lit` | Fallback material intent built from the shader graph's exposed properties. The fallback includes base color, optional base color texture, normal map, mask map, smoothness/AO remaps, surface options, emissive settings, and renderer hints. | + +For HDRP, `HdrpMaterialResolver` first tries to create the material from `Rubber.TemplateName`. If no matching player template exists, it falls back to the `Lit` payload and builds a normal HDRP Lit material. + +### `vpe.dmd` + +`vpe.dmd` covers dot-matrix display materials, which are VPE-owned shader graphs. + +| VPE field | Meaning | +| --- | --- | +| `Dmd.TemplateName` | Stable template key owned by the player. HDRP clones the matching template when available. | + +Unlike `vpe.metal`/`vpe.rubber`, `vpe.dmd` carries **no** `vpe.lit` fallback: a DMD has no meaningful Lit approximation, so if the player ships no matching template the material is skipped rather than substituted. + +### `vpe.fabric.silk` + +`vpe.fabric.silk` covers HDRP fabric/silk materials. The profile follows the fallback pattern but adds fabric-specific HDRP hints. + +| VPE field | Meaning | +| --- | --- | +| `Fabric.Lit` | Portable `vpe.lit` fallback for readers without an HDRP fabric implementation. | +| `Fabric.Hdrp` | HDRP fabric/silk hints: optional thread map (with AO/normal/smoothness strengths and UV channel) and fuzz map (with strength and UV scale). | + +For HDRP, `HdrpMaterialResolver` clones the fabric/silk template and applies the hints; if no fabric template is configured it returns no material (the imported fallback then stays in place). Other pipelines can render the carried `vpe.lit` fallback. + +### Renderer state + +In addition to material properties, a renderer should restore `VpeRendererState`. Only part of this has a dedicated HDRP manual page, because some of it is ordinary Unity renderer state rather than HDRP-specific shader behavior. + +| VPE field | HDRP / Unity counterpart | HDRP documentation | +| --- | --- | --- | +| `CastShadows` | Unity `Renderer.shadowCastingMode`. This is renderer state, not an HDRP material feature. | No dedicated HDRP material page. | +| `ReceiveShadows` | Unity `Renderer.receiveShadows`. This is renderer state, not an HDRP material feature. | No dedicated HDRP material page. | +| `RenderingLayerMask` | HDRP rendering layers for light / decal filtering. | [Use light rendering layers](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.6/manual/Rendering-Layers.html) | +| `Hdrp.RayTracingMode` | HDRP per-renderer ray-tracing mode override (`-1` = no override). | No dedicated HDRP material page. | + +### Failure behavior + +A package should stay loadable even if a renderer implements only part of the vocabulary, so a resolver should behave conservatively: + +- unknown material types should be skipped with a warning +- missing texture IDs should be skipped with a warning +- unsupported semantics should degrade gracefully rather than abort package loading +- if no resolver is registered, runtime should leave the imported GLB materials in place + +That degrades the visuals, but keeps the content loadable while a renderer is still being brought up. diff --git a/VisualPinball.Unity/Documentation~/developer-guide/packaging/shader-variants.md b/VisualPinball.Unity/Documentation~/developer-guide/packaging/shader-variants.md new file mode 100644 index 000000000..2f61e1707 --- /dev/null +++ b/VisualPinball.Unity/Documentation~/developer-guide/packaging/shader-variants.md @@ -0,0 +1,47 @@ +--- +uid: developer-guide-packaging-shader-variants +title: Packaging Shader Variants +description: Why runtime-resolved materials need a shipped shader variant collection in player builds, and how to keep it current as tables are added. +--- + +# Shader Variants + +This page is for people building and shipping a player. It explains why a table that looks correct in the editor can render with broken materials in a standalone build, and what you do about it when you add a new table. + +It is specifically about HDRP. Other renderers that resolve materials at runtime will hit the same class of problem, but the keyword and tooling details below are HDRP/Unity-specific. + +## Why a build needs help + +The HDRP resolver builds its materials at runtime: it clones a small set of template materials and flips HDRP/Lit keywords (surface type, refraction model, transmission, SSR-transparent, thickness, double-sided, and so on) to match each `materials.json` profile. See [import](import.md) for the resolver flow. + +Those keywords are HDRP `shader_feature` keywords, and `shader_feature` behaves differently in the editor than in a build: + +- In the **editor** the shader compiler is present. The first time a material asks for a keyword combination that has not been compiled yet, the editor compiles it on demand and caches it (the *"Compiling shader variants…"* hitches). A runtime-built material always finds its variant, so the table looks correct. +- In a **player build** there is no shader compiler. Only the `shader_feature` combinations that some material asset uses at build time — or that a `ShaderVariantCollection` referenced from *Project Settings → Graphics → Preloaded Shaders* lists — are compiled and shipped. The resolver's runtime combinations are not present on any build-time material, so without a preloaded collection they are stripped. HDRP then substitutes the nearest available variant, and the visible result is broken transparency, refraction, and reflections — even though import itself succeeds. + +This is why the editor and a build of the *same* table off the *same* `.vpe` can look different. It is not a packaging problem; the `.vpe` is renderer-agnostic and correct. The variants are a property of the **player + render pipeline + platform + engine version**, not of the table, which is why they live in the player and not in the package. + +## The fix: a preloaded ShaderVariantCollection + +A player ships a `ShaderVariantCollection` that covers the variants the resolver produces, referenced from *Project Settings → Graphics → Preloaded Shaders*. The build then keeps exactly those variants (and only those), so runtime-built materials find the right one. + +Because the needed combinations depend on per-material data (which texture maps and material features each profile enables) and on per-pass keyword differences, the collection cannot be reliably enumerated by hand. It is **captured** from real table loads instead, which records exactly the `(shader, pass, keyword)` tuples that are actually drawn. + +## Adding variants for a new table + +A new table can introduce material/keyword combinations the collection has not seen yet. When you add one, capture it and rebuild: + +1. **Clear the tracked set.** *Project Settings → Graphics*, in the shader-loading section, clear the currently-tracked shader variants. +2. **Exercise the table.** Enter play mode, load the table, and move the camera so every insert, plastic, ramp, and post is actually drawn — only drawn variants are recorded. To broaden coverage, load several tables in the same session **without clearing in between**; the tracked set accumulates. +3. **Save to asset.** Save the tracked set to the player's `.shadervariants` collection. +4. **Preload it.** Make sure the collection is referenced under *Preloaded Shaders* (the player provides a menu helper that wires it for you). +5. **Rebuild** and verify the table renders correctly in the build. + +Capture while the editor is on the **same quality level the build ships** (see the quality-level note below), so the recorded variants match the shipped pipeline. + +## Things that bite + +- **Quality level / pipeline must match.** Variants captured under one HDRP pipeline asset may not be exactly what another needs. Develop and capture on the quality level you actually ship. +- **Ray tracing is kept out of player builds for now.** The DXR pipeline pulls in ray-tracing passes that the imported glTF shadergraphs cannot compile, and it multiplies the variant count enormously. The shipped quality level is a rasterized one (deferred + SSR); the DXR asset stays in the project for future work but is not the player default. +- **The imported glTF shadergraphs are intentionally not in the build.** The HDRP glTF material generator builds its import placeholders on HDRP/Lit, and the resolver replaces every imported material by name, so the original glTF shadergraph is never actually used and does not need to ship. Keeping it out also sidesteps a real problem: gltFast's glTF materials are HDRP Shader Graphs, so HDRP auto-generates ray-tracing passes for them (`ForwardDXR`, `GBufferDXR`, …), and gltFast's versions have an invalid gradient `Sample` in the ray-tracing `closesthit` context. Those passes are only compiled when ray tracing is enabled in the build, so they failed only while the standalone still defaulted to the DXR pipeline — and they would break a future ray-tracing build the same way if the shadergraphs were shipped. A harmless "shader missing" line for those shadergraphs may appear in the *Development Console*; it does not affect rendering and does not appear in non-development builds. +- **Guard against regressions.** The player fails a standalone build when the collection is missing, empty, or not preloaded, so a fresh checkout or a settings reset cannot silently ship broken transparency. diff --git a/VisualPinball.Unity/Documentation~/developer-guide/toc.yml b/VisualPinball.Unity/Documentation~/developer-guide/toc.yml index edd7d45ef..f20b1fcd8 100644 --- a/VisualPinball.Unity/Documentation~/developer-guide/toc.yml +++ b/VisualPinball.Unity/Documentation~/developer-guide/toc.yml @@ -4,6 +4,20 @@ href: setup.md - name: Threading Model href: threading-model.md +- name: Packaging + items: + - name: Overview + href: packaging/index.md + - name: Export + href: packaging/export.md + - name: Import + href: packaging/import.md + - name: Materials + href: packaging/materials.md + - name: Shader Variants + href: packaging/shader-variants.md + - name: Benchmarks + href: packaging/benchmarks.md - name: Future Integrations items: - name: Accelerometer Input Design @@ -11,4 +25,4 @@ - name: B2S Integration Design href: b2s-integration-design.md - name: DOF Integration Design - href: dof-integration-design.md \ No newline at end of file + href: dof-integration-design.md diff --git a/VisualPinball.Unity/Documentation~/docfx.json b/VisualPinball.Unity/Documentation~/docfx.json index fbe3d67a3..2603259e7 100644 --- a/VisualPinball.Unity/Documentation~/docfx.json +++ b/VisualPinball.Unity/Documentation~/docfx.json @@ -30,6 +30,12 @@ "**/*.webmanifest", "../VisualPinball.Unity.Editor/Resources/Icons/*.png", "CNAME" + ], + "exclude": [ + "_site/**", + "_exported_templates/**", + "obj/**", + "bin/**" ] } ], @@ -44,7 +50,7 @@ "postProcessors": [ "ExtractSearchIndex" ], "globalMetadata": { "_appTitle": "VPE Documentation", - "_appFooter": "Copyright © 2026 VPE Team", + "_appFooter": "Copyright © 2026 VPE Team", "_appFaviconPath": "favicon.png", "_gitContribute": { "branch": "master" @@ -62,4 +68,4 @@ "cleanupCacheHistory": false, "disableGitFeatures": false } -} +} diff --git a/VisualPinball.Unity/Documentation~/template/vpe/layout/_master.tmpl b/VisualPinball.Unity/Documentation~/template/vpe/layout/_master.tmpl index 67a770ab1..c631b9cfb 100644 --- a/VisualPinball.Unity/Documentation~/template/vpe/layout/_master.tmpl +++ b/VisualPinball.Unity/Documentation~/template/vpe/layout/_master.tmpl @@ -40,7 +40,7 @@ - +