diff --git a/skills/migrate-birp-to-urp/SKILL.md b/skills/migrate-birp-to-urp/SKILL.md new file mode 100644 index 0000000..9b643ef --- /dev/null +++ b/skills/migrate-birp-to-urp/SKILL.md @@ -0,0 +1,543 @@ +--- +name: migrate-birp-to-urp +description: Plans, executes, and troubleshoots Unity projects moving from the Built-in Render Pipeline (BiRP/BIRP/Built-in RP) to the Universal Render Pipeline (URP). Use when the user asks to upgrade, convert, switch, or migrate a project, scene, material, or shader to URP/Universal Render Pipeline; fix pink or magenta materials after URP; convert Built-in materials/shaders; move a 2D project to URP 2D; review lighting, quality, post-processing, baked lightmaps, or reflection probes after URP; or diagnose visual problems after a render-pipeline migration. +--- +Classify the request, inspect the current project state, choose the correct migration path, and validate the Built-in to URP migration outcome carefully. + +## Mandatory Execution Reference + +For any actual migration or repair pass, read [references/implementation-patterns.md](references/implementation-patterns.md) before the first `eval` call that edits project settings, materials, post-processing, lighting, probes, or scenes. This is mandatory even for short prompts such as "migrate this project to URP" when rollback safety is already confirmed. + +If that resource cannot be loaded, use the embedded rules in this SKILL.md and lower your confidence: do not claim PPv2 conversion, lighting/probe refresh, or migration completion from intent or tool logs alone. + +## Critical Default + +For simple requests like "migrate this Built-in project to URP" or "move this project to URP", start or resume the safe migration workflow rather than jumping straight to conversion. + +A generic upgrade prompt must not require the user to mention every migration risk. Inspect and handle common Built-in dependencies such as PPv2, baked lighting/lightmaps, reflection probes, particle/fog/smoke materials, Quality levels, camera post-processing, and representative scene validation. + +Default result is phase-based: + +1. Detect whether the project is Built-in, partially migrated, already URP, or HDRP. +2. Classify the request as standard 3D URP, URP 2D, selected-material conversion, planning-only, or troubleshooting. +3. Inspect migration risks before recommending changes: opaque materials, particle/VFX materials, custom shaders, post-processing, camera effects, baked lighting/lightmaps/reflection probes, Quality levels, and representative scenes. +4. Stop before any pipeline-mutating work until the user confirms a rollback point such as a branch, backup, archive, or disposable copy. Pipeline-mutating work includes installing URP, assigning a URP asset in Graphics/Quality settings, running converters, and editing materials or scenes. +5. Choose the next migration phase, execute that phase, save/re-query its outputs, and report its phase status. +6. Treat custom shaders, `GrabPass`, Surface Shaders, `OnRenderImage`, replacement shaders, and package-owned shaders as scoped follow-up work, not automatic converter work. +7. Validate representative scenes, Console output, camera rendering, lighting/baked GI state, materials, post-processing persistence, and quality-level assignments before declaring full migration success. + +Default phases: + +- Phase 0: Inspect and plan. Detect pipeline state, representative scenes, rollback safety, PPv2, material/shader risks, Quality levels, lighting/lightmap/probe state, and custom render code. +- Phase 1: URP setup and supported material conversion. Install/reuse URP, create/assign URP assets in Graphics/Quality, convert supported opaque and particle/effect materials, and verify no supported materials remain on Built-in shaders. +- Phase 2: Post-processing and camera migration. Create persistent URP Volume profiles, wire scene Volumes/camera post-processing, disable legacy PPv2 for URP validation, and classify unsupported PPv2 effects such as SSR. +- Phase 3: Lighting, lightmaps, and reflection probes. Resolve stale Built-in lighting state, configure URP lighting/probe settings, rebake/refresh when feasible, or clearly mark lighting/probes partial. +- Phase 4: Final validation and report. Save/reload/re-query project state, capture representative scenes, check Console, and report complete/incomplete/manual items. + +One turn may complete multiple phases if the project is small and Unity remains stable, but do not force all phases into one response. Prefer an honest phase boundary over an over-claimed "complete" result. + +Ask configuration questions only when the project state or user intent leaves a real decision unresolved. If the user says "do not modify files", stay in audit/planning mode and do not perform conversion or asset edits. + +### Generic Upgrade Contract + +When the user gives a generic migration request and permits changes, drive the next phase from the project state: + +1. Do not ask the user to enumerate known Built-in features before inspection. Discover them. +2. If PPv2, baked lighting, reflection probes, particle/fog/smoke materials, or multiple Quality levels exist, include them in the migration automatically. +3. Prefer phase completion over all-in-one completion. Each phase should save/re-query its own outputs and end with `Phase complete`, `Phase partial`, or `Blocked`. +4. If Unity compilation, package installation, domain reload, or a long lighting bake interrupts the pass, report the interruption as a phase boundary and resume from saved partial state in the next turn rather than starting over. +5. Do not rely on the user writing a detailed checklist prompt to get correct behavior. The detailed checklist is for benchmarking or stress testing; normal user prompts should still trigger this phased contract. +6. A generic migration request gives permission to inspect and plan, but it does not prove rollback safety. If no rollback point is confirmed, stop before package install or URP assignment and ask for confirmation. Do not leave the project in a magenta partial state just to reach the backup question. +7. If rollback safety is already confirmed or the user says the project is a disposable copy, continue within the current phase without asking again for routine work. +8. At the end of a phase, give a concise next-phase prompt or say what phase should run next. Ask for confirmation before the next phase only when it introduces a new costly or risky operation, such as a long lighting bake, deletion/cleanup of legacy assets, or a custom shader rewrite. +9. If rollback safety was confirmed and ordinary work inside the active phase remains incomplete, do not ask "would you like me to continue?" Continue repairing that phase until its gate passes, a tool/domain-reload boundary interrupts execution, or a genuinely new risky decision appears. + +### Generic Migration Success Gate + +Before saying a generic migration is "successful", "complete", or "fully migrated", verify saved project state, not just tool logs: + +1. Graphics settings and every relevant Quality level point to the intended URP asset. +2. The URP asset has a valid default renderer in saved state, and current Console output does not contain unresolved render-pipeline errors such as "Default Renderer is missing". If a renderer error appears after assignment, re-query the saved renderer list/default index, clear stale Console output when possible, trigger a fresh scene/camera validation, and continue repair instead of stopping at "URP setup complete". +3. Supported Built-in materials, including particle/fog/smoke materials, have URP-compatible shaders or are explicitly listed as unresolved custom/package shader cases. Textured/colorized source materials must preserve their source maps/colors; a URP shader assignment with missing `_BaseMap` or all-white `_BaseColor` is incomplete if the source `_MainTex` or `_Color` had content. Particle, fog, smoke, steam, decal, VFX, additive, and transparent effect materials should not be blindly forced to `Universal Render Pipeline/Lit`; prefer URP particle/effect shaders such as `Universal Render Pipeline/Particles/Unlit` when appropriate. +4. If the source scene used PPv2, a saved URP `VolumeProfile` exists with persistent non-null override components, the representative scene references it through a URP `Volume`, and legacy PPv2 is disabled for URP visual validation. A saved profile with `components: []`, `{fileID: 0}` component refs, or a scene that still only references the old `PostProcessProfile` is incomplete. +5. If the representative scene uses baked/mixed lighting, an Enlighten/realtime-GI Lighting Settings asset, a Lighting Data asset, lightmaps, light probes, or reflection probes, do not leave routine lighting/probe repair as manual follow-up during a generic full migration. Attempt URP-compatible Lighting Settings assignment, clear/rebake or resume a bake when needed, refresh reflection probes when feasible, then save/reload and verify the scene references the intended lighting state. Creating a `.lighting` asset and calling `Lightmapping.lightingSettings = target` plus `AssetDatabase.SaveAssets()` is not enough; mark/save the scene, reload/re-query, and compare the saved scene `m_LightingSettings` reference or asset GUID. If a long bake or tool interruption prevents this, call the migration partial and resume from that phase. +6. A representative scene has been opened or selected deliberately before migration validation; do not infer baked-lighting or PPv2 absence from a default/test scene. The scene has been captured or inspected after saving/reloading, and Console output has been checked for render-pipeline, shader, or renderer-feature errors. +7. Final wording must match the gate result. Use "complete" only when every gate passes. Use "partial" when URP is set up but PPv2 Volume persistence, legacy PPv2 disablement, lighting bake/probe refresh, or saved-state validation remains. Do not start the final response with "successfully migrated" if any required gate is partial. + +Complete-report checklist: + +- If the old PPv2 profile contains active `DepthOfField`, the new URP profile must contain a saved `DepthOfField` override or the final report must call it omitted/manual. +- If the old PPv2 profile contains active `AmbientOcclusion`, configure a URP SSAO renderer feature when feasible, or mark AO as manual/partial. +- If the old PPv2 profile contains active `ScreenSpaceReflections`, list SSR as unsupported/manual unless a URP renderer-feature/custom replacement was actually added and validated. +- If the scene still serializes the old Lighting Settings GUID or a non-zero old `m_LightingDataAsset`, lighting is partial. +- If the scene has reflection probes and the URP asset still serializes `m_ReflectionProbeBlending: 0` or `m_ReflectionProbeBoxProjection: 0`, reflections are partial unless intentionally disabled and reported. +- If any checklist item is false, do not use "complete", "fully migrated", or "fully functional on URP" in the final answer. + +Unsupported features can remain manual, but name them precisely. Examples include PPv2 Screen Space Reflections needing a URP renderer-feature/custom/third-party replacement, complex custom shader ports, `GrabPass`, replacement shaders, or package-owned rendering code. If any success-gate item fails, continue repairing when allowed or report a partial migration with incomplete items; do not present the project as fully migrated. + +Regression guard summary: + +- Use the detailed [Execution Regression Checklist](references/implementation-patterns.md#execution-regression-checklist) for actual migration or repair work. +- Verify saved scene references before status claims: URP Volume/profile, Quality assignments, Lighting Settings/Data, URP asset/renderer, and reflection-probe settings. +- Preserve material source data from a pre-conversion snapshot, then restore `_BaseMap`, `_BaseColor`, texture scale/offsets, and relevant maps after shader changes. +- Validate representative visuals for PPv2, foliage, particles/effects, baked lighting, probes, and exposure before final wording. +- Report exact phase status. `Phase complete` is allowed for a passed phase; project-level `complete` is allowed only when every success gate passes. + +## Execution path: running C# in the Editor + +Every C# step in this skill runs inside a live Editor through the Unity CLI. **The `unity-cli` +skill owns getting you there** — installing the CLI, confirming a connected Editor, adding the +project's `com.unity.pipeline` package, telling a genuinely absent Editor apart from one stuck in +Safe Mode, and discovering the Editor's command catalog. Follow it first; don't re-derive any of +it here. + +Two things it can't know for you: + +- **You need `eval` in particular**, not just a reachable Editor. Confirm it appears in the + catalog. Its presence depends on the Pipeline package version, not on the CLI, so a healthy + install can still lack it — if it's missing, say so and stop. +- **On an Editor older than 6000.3, expect the Pipeline package not to work at all**, and read the + symptom correctly rather than retrying. `com.unity.pipeline` uses `IPreprocessBuildWithContext` + and `BuildCallbackContext`, which Unity introduced in **6000.3**; the package's own manifest + declares `"unity": "6000.0"`, so it installs happily and then fails to compile. Measured: + present in 6000.3 / 6000.4 / 6000.5, absent in 6000.0 / 6000.1 / 6000.2 and in the 2022 and 2023 + lines. The symptom is misleading — the server never starts, `unity status` shows no row and no + error, and the real cause is `CS0246` on those two types in the Editor log. If you see that, + tell the user the Editor is too old for the Pipeline package rather than debugging the CLI. + + **This matters here more than in most skills:** someone migrating a project off the Built-in + pipeline is, by definition, often on an older Editor. +- **A render-pipeline migration is not safely authorable blind.** Assigning a URP asset, converting + materials, and rebaking lighting all need a live Editor. An unreachable Editor is a stop, not a + cue to hand-edit `ProjectSettings/GraphicsSettings.asset`. + +Run C# with `unity command eval --code ''`. `unity command` defaults to a 30 second +timeout, which matters here: installing URP triggers a package refresh and domain reload that will +outlast it. Treat that as a phase boundary rather than raising the timeout. + +### Passing C# to `eval` + +`eval` compiles a **statement block, not a file**. Three consequences, all of which cause a compile +error rather than a warning: + +- **No `using` directives.** The compiler reads `using UnityEngine;` as a resource-disposal + statement and rejects it (`CS0210`). +- **Types must be fully qualified.** A bare `GraphicsSettings` or `Volume` does not resolve + (`CS0246` / `CS0103`), and a bare `Object` is ambiguous with `object` (`CS0104`). +- **Extension methods are unavailable**, because they resolve through `using`. Two that this skill + would otherwise reach for: `camera.GetUniversalAdditionalCameraData()` becomes + `camera.GetComponent()`, and LINQ + calls must be written statically — `System.Linq.Enumerable.FirstOrDefault(sequence, predicate)` + rather than `sequence.FirstOrDefault(predicate)`. + +### Two execution modes — pick by the snippet's shape + +The references carry both shapes, and they are not interchangeable: + +- **Statement-shaped** (a bare sequence of statements, like the detection snippets above) — pass + straight to `eval`, fully qualified, with no `using` lines. +- **Class- or method-shaped** (anything declaring a `class`, a `static` method, or a `[MenuItem]`, + such as the material-snapshot pattern) — these are **project files, not `eval` input**. A class + declaration cannot be flattened into a statement block. Save the snippet under + `Assets/Editor/`, let Unity compile it, then invoke its entry point through a one-line `eval` + call. Keep the `using` directives in that file; they are correct there. + +For a multi-step migration the script route is the more reliable one anyway: it survives the domain +reloads that URP installation and material conversion trigger, whereas a long `eval` payload does +not. + +### Detecting the active render pipeline + +```csharp +var rp = UnityEngine.Rendering.GraphicsSettings.defaultRenderPipeline; +var qrp = UnityEngine.QualitySettings.renderPipeline; +return $"graphics={(rp == null ? "NULL (Built-in)" : rp.GetType().Name + ":" + rp.name)}, " + + $"activeQualityLevel={(qrp == null ? "inherits Graphics" : qrp.GetType().Name + ":" + qrp.name)}"; +``` + +A `UniversalRenderPipelineAsset` means URP; `HDRenderPipelineAsset` means HDRP; `NULL` on both +means Built-in. **Check the per-quality-level assignment too** — a project can be switched in +Graphics settings while a Quality level still points at a different asset, or at none: + +```csharp +var names = UnityEngine.QualitySettings.names; +var rows = new System.Collections.Generic.List(); +for (int i = 0; i < names.Length; i++) +{ + var a = UnityEngine.QualitySettings.GetRenderPipelineAssetAt(i); + rows.Add($"{i}:{names[i]}={(a == null ? "inherits Graphics" : a.name)}"); +} +return string.Join(", ", rows); +``` + +If `GetRenderPipelineAssetAt` is unavailable in the project's Unity version, read +`ProjectSettings/QualitySettings.asset` instead rather than switching levels at runtime — +`SetQualityLevel` mutates project state. + +## 0. Pre-Flight: Pipeline Detection and Reference Loading + +Before doing anything else, you **must determine the active render pipeline and migration mode**: + +1. **Detect Pipeline:** Run the render-pipeline detection snippet from the execution-path section above. + - If `currentRenderPipeline` or `defaultRenderPipelineAsset` references a `UniversalRenderPipelineAsset` -> **URP**. + - If no render pipeline asset is assigned -> **Built-in**. + - If it references an `HDRenderPipelineAsset` -> **HDRP**. Explain that this skill only covers Built-in to URP migration. Basic comparison advice is fine, but do not drive an HDRP migration with this skill. +2. **Load Base Reference:** Read [references/migration-workflow.md](references/migration-workflow.md). +3. **Load Shader References When Relevant:** If the request mentions materials, shaders, magenta materials, rendering errors, image effects, or custom rendering, also read: + - [references/custom-shader-triage.md](references/custom-shader-triage.md) + - [references/complex-shader-situations.md](references/complex-shader-situations.md) when inspection or project-file search finds advanced shader/effect patterns +4. **Load Quality Reference When Relevant:** If the request mentions shadows, lighting, baked lighting, lightmaps, reflection probes, quality settings, visual mismatch, or performance after migration, also read [references/quality-settings-map.md](references/quality-settings-map.md). +5. **Load Implementation Patterns When Executing:** If the request permits actual migration changes, post-processing conversion, baked-lighting/probe repair, or resume from a partial migration, also read [references/implementation-patterns.md](references/implementation-patterns.md). +6. **Classify the Request** as one of: + - Full-project Built-in to URP migration + - Built-in 2D to URP 2D migration + - Selected material-only conversion + - Planning / explanation only + - Troubleshooting after a previous migration +7. **If the project is already on URP,** switch to troubleshooting mode instead of re-running setup blindly. +8. **Proceed** only after the pipeline and migration path are clear. + +## 1. Assess Current Migration State + +Before making any changes, **inspect what already exists**: + +1. **Inspect pipeline state and assignment points:** + - Run both render-pipeline detection snippets from the execution-path section above — the Graphics-settings one and the per-quality-level one. + - If the project has Quality levels, inspect which Render Pipeline Asset each level uses before assuming the project is fully switched. +2. **Inventory migration surfaces:** Use `eval` with `UnityEditor.AssetDatabase.FindAssets` or equivalent asset queries to inventory: + - materials, including particle/VFX materials that often use built-in particle shaders + - vegetation materials, including grass, tree, terrain-detail, SpeedTree, billboard, leaf-card, cutout, and wind-driven foliage materials + - shaders + - scenes and important prefabs + - URP assets and renderer assets, if any + - post-processing profiles / volume profiles + - Lighting Settings assets, Lighting Data assets, lightmaps, light probes, reflection probes, and mixed/baked lights +3. **Scan for rendering risk markers:** Use available project-file search, `AssetDatabase.FindAssets`, or a short `eval` file scan to search the project for rendering-specific patterns such as: + - `PostProcessLayer`, `PostProcessVolume`, `UnityEngine.Rendering.PostProcessing` + - `OnRenderImage(`, `RenderWithShader`, `SetReplacementShader` + - `#pragma surface`, `GrabPass`, `CGPROGRAM` + - `CommandBuffer`, custom shader include paths, or package shader namespaces + - vegetation markers such as `Nature/`, `SpeedTree`, `TreeCreator`, `Grass`, `Foliage`, `_Cutoff`, `_AlphaClip`, `_Cull`, billboard, or wind keywords +4. **If PPv2 is installed but grep finds nothing,** inspect open scenes and profile assets through Unity APIs by component/type. Scene YAML and serialized package references can be missed by text search. +5. **Determine project shape:** Decide whether the project is primarily 3D, primarily 2D, or mixed. +6. **Report Findings:** Summarize the current state before proposing conversion. Example: + - "The project is still on Built-in, has no URP asset assigned, contains PPv2 references, and includes several custom shaders using surface-shader syntax." + +## 2. Gather Requirements + +Determine what the user actually wants. If the request is ambiguous, ask. + +Use these defaults for common requests: + +| User says | Default interpretation | +|-----------|------------------------| +| "Upgrade this project to URP" | Full-project migration | +| "Move this 2D project to URP" | Built-in 2D to URP 2D migration | +| "Convert these materials" | Targeted material conversion | +| "My materials turned pink" | Post-migration shader/material troubleshooting | +| "Lighting looks wrong after URP" | Quality and visual parity troubleshooting | +| "Do not change anything yet" | Planning / audit only | + +### Information to Gather + +- **Scope:** full project, selected materials, or troubleshooting only +- **Target renderer:** standard URP or URP 2D +- **Validation targets:** which scenes, prefabs, or cameras matter most to the user +- **Risk tolerance:** whether limited manual follow-up is acceptable +- **Rendering dependencies:** custom shaders, Asset Store shaders, PPv2, baked lighting/lightmaps/probes, image effects, command buffers, replacement shaders, or multiple Quality levels +- **Execution permission:** planning only vs actual project changes + +## 3. Safety Gate + +Before any pipeline-mutating step: + +1. **Confirm rollback safety.** + - NEVER install URP, assign a URP asset, run the Render Pipeline Converter, edit materials, or edit migration scene state before the user confirms a backup, branch, archive, disposable copy, or rollback point. + - If rollback safety is missing, do not partially switch the project to URP. Stop in planning mode and ask for the rollback confirmation first. +2. **Separate engine-upgrade risk from pipeline-upgrade risk.** + - If the project is also moving to a new Unity version, recommend doing the engine upgrade first and the pipeline migration second. Do not treat both as a single blind operation. +3. **If rollback safety is confirmed,** treat it as covering the whole migration pass. Do not ask again before each converter; proceed through setup, material conversion, PPv2 migration, lighting/probe work, save/reload verification, and final reporting unless a new risky decision appears. +4. **If the user already authorized a full disposable migration,** do not ask for permission to continue after routine setup or recoverable errors. Continue with the next incomplete migration phase. Ask only when a new destructive cleanup decision appears, a package/domain reload stops execution, or repeated repair attempts hit the validation iteration limit. +4. **If the project is already partially migrated,** identify whether rollback safety was previously confirmed. If yes, resume from the first incomplete item. If no, report the partial state and ask before making additional changes. + +## 4. Choose the Migration Path + +Use the correct path for the project: + +### Path A: Standard 3D Built-in to URP +- Use this for normal 3D Built-in projects moving to standard URP. + +### Path B: Built-in 2D to URP 2D +- Use this when the project is primarily 2D and the user expects URP 2D lighting or renderer behavior. +- Do not treat this as the same workflow as standard URP. + +### Path C: Targeted Material-Only Conversion +- Use this only when the project is already on URP and the user wants selected materials converted. + +### Path D: Troubleshooting an Existing Migration +- Use this when the project is already on URP or the migration has already been attempted. +- Prioritize the highest-impact breakages instead of re-running the entire migration blindly. + +## 5. URP Setup Workflow + +Follow this exact setup order: + +1. **Ensure URP is installed.** + - Installing URP can trigger package refresh, compilation, and domain reload. Treat this as a phase boundary. + - Do not spin-wait indefinitely inside an `eval` call on `UnityEditor.PackageManager.Client.Add`. Request installation, then verify through `Packages/manifest.json`, package listing, or the presence of URP types/assets after Unity finishes refreshing. + - If the package install causes the `eval` call to time out or return nothing, resume in a fresh turn after Unity finishes compiling. Do not restart the whole migration or reinstall URP; inspect the partial state and continue from there. +2. **Create the required URP asset and renderer asset** if they do not already exist. +3. **Assign the URP asset in Graphics settings** and in all relevant Quality levels. +4. **For 2D projects,** create and assign the correct 2D Renderer asset before conversion. +5. **If a URP asset already exists,** inspect and reuse it when appropriate instead of creating duplicates automatically. +6. **Do not continue** until URP is actually the active render pipeline for the target configuration. + +## 6. Conversion Workflow + +For actual conversion work, follow [references/migration-workflow.md](references/migration-workflow.md). + +1. **Choose the correct Render Pipeline Converter path:** + - `Built-in Render Pipeline to URP` + - `Built-in Render Pipeline 2D to URP 2D` +2. **Initialize converters and inspect the candidate changes** before converting. +3. **For standard Built-in to URP migration,** prefer the applicable converters described in the reference: + - `Rendering Settings` + - `Material Upgrade` + - `Animation Clip Converter` + - `Read-only Material Converter` + - `Post-processing Stack v2 Converter` +4. **For selected-material conversion,** use the targeted material conversion workflow instead of converting the whole project. +5. **Review warnings and failures before converting.** +6. **Run the conversion only after the user confirms the project is safe to change.** +7. **Verify which shader each material actually landed on** — do not assume the converter chose the 3D + target. `MaterialUpgrader.FetchAllUpgradersForPipeline` returns the 2D provider set alongside the 3D + one, and both claim `Standard` at equal priority, so on a 3D project a plain `Standard` material can + silently convert to `Universal Render Pipeline/2D/Mesh2D-Lit-Default` instead of + `Universal Render Pipeline/Lit`. Nothing errors and the material does not go magenta, so this is + invisible unless you read the shader name back — measured on 6000.5.8f1. + + Read every converted material's `shader.name` afterwards and confirm it matches the intended target + from the mapping table. If any landed on a `2D/` shader in a 3D project, restore those materials from + the rollback point and convert them with a 3D-filtered upgrader list, or with the manual pattern in + [references/implementation-patterns.md](references/implementation-patterns.md). +7. **After conversion, verify the saved project state.** + - Re-open or re-query representative assets instead of trusting command output alone. + - Save both assets and open scenes after scene-level edits. Changes to scene components, camera data, active lighting settings, reflection probes, and PPv2 enable states are not proven by `AssetDatabase.SaveAssets()` alone. + - Confirm the URP asset is assigned in Graphics and intended Quality levels. + - For Quality levels, validate by re-querying `QualitySettings.GetRenderPipelineAssetAt(i)` or by reading the saved `customRenderPipeline` entries in `ProjectSettings/QualitySettings.asset`. If they remain `{fileID: 0}`, the Quality levels are not explicitly assigned. + - Do not call a guessed quality API such as `QualitySettings.SetRenderPipelineAssetAt`; in Unity versions where that API is unavailable, switch to each target level with `QualitySettings.SetQualityLevel(index)`, set `QualitySettings.renderPipeline = urpAsset`, then restore the original level and verify the saved `customRenderPipeline` values. + - Confirm converted materials actually reference URP shaders. + - Confirm representative materials that had source albedo textures now have non-null `_BaseMap` values. This check must compare against a pre-conversion material snapshot; do not use post-conversion `_MainTex` as the source of truth. + - Confirm particle/VFX materials no longer reference Built-in particle shader IDs or legacy shader names; use URP particle shaders such as `Universal Render Pipeline/Particles/Unlit` where appropriate. If material names or paths include `Particle`, `Fog`, `Smoke`, `Steam`, `VFX`, `Additive`, or similar effect terms, preserve transparent/additive behavior rather than defaulting them to URP Lit. + - Confirm vegetation materials preserve cutout/alpha clipping, render face intent, textures, tint, normals, and expected wind/billboard behavior. If grass/tree cards become solid or static because their specialized shader behavior was lost, material conversion is partial. + - If PPv2 conversion was attempted, confirm saved URP Volume profiles contain persistent non-null `components` entries and the scene/camera wiring uses URP-compatible components. + - If baked lighting exists, preserve the old Lighting Data and lightmaps as reference data, then verify whether they still produce acceptable URP visuals. If a new Lighting Settings asset is created, verify the saved scene actually references it after saving/reloading; the asset existing on disk is not enough. In an actual generic migration, attempt rebaking/refreshing when stale baked data or reflection probes affect parity; if that cannot finish in the current turn, report the migration as partial and resume from the lighting/probe phase later. + - Do not claim "refreshed reflection probes" from intent. Verify new or updated probe outputs, changed saved reflection-probe assets, or successful probe-render tool output. If the existing EXR files remain unchanged, say probes were preserved as reference or still need refresh. + - If any verification fails, report the item as incomplete/manual follow-up, not as migrated. + +## 7. Shader and Material Triage + +When materials are magenta, shaders fail to compile, or visuals drift heavily, use [references/custom-shader-triage.md](references/custom-shader-triage.md) first. + +1. **Identify affected materials and shaders.** + - Inspect the exact material shader assignments, not just scene symptoms. +2. **Read Console and Inspector errors before editing shader code.** +3. **Classify the shader case:** + - supported Built-in shaders that the converter should handle + - simple custom shaders that might be ported safely + - complex custom shaders that need a scoped migration plan + - foliage/vegetation shaders that require cutout, two-sided leaves, billboards, terrain detail rendering, or wind behavior + - package-owned or Asset Store shaders that may require maintainer documentation, scoped custom porting, or manual follow-up +4. **For complex shader situations,** use [references/complex-shader-situations.md](references/complex-shader-situations.md). +5. **Prefer the smallest safe fix.** + - Restore rendering on representative materials first. + - Validate the result before scaling the fix to more assets. +6. **Never bulk search-and-replace shader code across the whole project** unless the mapping is explicit, tested, and scoped. + +## 8. Post-Processing, Cameras, and Rendering-Effect Triage + +Built-in projects often rely on more than material conversion. + +1. **If PPv2 is present,** inspect the converter output and validate the resulting URP volumes, profiles, and camera behavior. + - Inventory existing `PostProcessVolume`, `PostProcessLayer`, and PPv2 `PostProcessProfile` assets before conversion. + - Prefer the URP `Post-processing Stack v2 Converter` when available instead of hand-building equivalent profiles from memory. + - After conversion, verify that saved URP `VolumeProfile` assets contain persistent override components, not an empty `components: []` profile or dangling `{fileID: 0}` component references. + - If creating a profile by script, `VolumeProfile.Add()` only creates a component object; for persistent profile assets, also call `AssetDatabase.AddObjectToAsset(component, profile)`, mark the profile and component dirty, save assets, reload the asset, and count saved non-null components before reporting success. If the saved profile has `{fileID: 0}` component entries, recreate or repair the profile before claiming migration success. + - Verify representative scenes contain the intended `Volume` objects and that target cameras have `UniversalAdditionalCameraData` with post-processing enabled when required. + - If the source scene still only serializes a PPv2 `sharedProfile` reference and no URP `Volume` references the new `VolumeProfile`, PPv2 migration is incomplete even if a URP profile asset exists on disk. + - If an active URP `Volume` references an empty profile, treat that as incomplete scene wiring, not as active post-processing. Repair the profile or report PPv2 as partial. + - Check whether old `PostProcessVolume` / `PostProcessLayer` components remain. If they remain, explain whether they are intentionally retained, harmless legacy leftovers, or unresolved migration work. + - If old PPv2 components are retained only as reference while a URP Volume is active, disable the old PPv2 component/layer for URP visual validation to avoid double post-processing. Do not delete it until parity is accepted or the user approves cleanup. + - Do not remove PPv2 packages, components, or profiles as a cleanup step until a verified URP replacement exists or the user accepts that those effects will be dropped/manual follow-up. + - During an actual migration, do not leave common PPv2 parity as a vague manual task if the source profile is available. Create persistent URP overrides for common mappable effects such as Bloom, Color Adjustments/Tonemapping, Vignette, and Depth of Field, then verify the saved profile has non-null components. + - It is acceptable to mark unsupported or non-equivalent effects as manual follow-up, such as PPv2 Screen Space Reflections or Ambient Occlusion that should become a renderer feature / SSAO setup instead of a direct Volume override. + - If URP Volume overrides cannot be created reliably, document the PPv2 effects and their URP equivalents instead of claiming they were migrated. +2. **Do not treat transient tool success as post-processing success.** + - A command log that says "Added Bloom" is not enough. Re-read the saved `VolumeProfile` asset or inspect the scene after saving/reloading. + - If the saved profile is empty, say the PPv2 setup was documented or partially prepared, not migrated. +3. **If grep finds `OnRenderImage`,** treat it as a custom full-screen effect case. + - In URP, custom full-screen effects should move toward `ScriptableRenderPass`, a Renderer Feature, or URP custom post-processing instead of staying on the Built-in image-effect path. +4. **If grep finds `RenderWithShader` or `SetReplacementShader`,** treat it as a replacement-shader case. + - These effects often need a deliberate URP renderer-feature or custom-pass strategy. +5. **If an effect depends on scene color, depth, or normals,** verify the URP-compatible path rather than assuming the Built-in approach still applies. +6. **If custom cameras were stacking effects in Built-in,** validate camera output explicitly after migration instead of assuming parity. +7. **For advanced shader/effect troubleshooting questions, provide concrete replacement patterns.** + - For `GrabPass`, mention `_CameraOpaqueTexture` / Scene Color, the required URP asset setting, and the limitation that transparent ordering can differ from Built-in. + - For `OnRenderImage`, mention `ScriptableRendererFeature` plus `ScriptableRenderPass`. When showing a Unity 6-style template, use the reference pattern with a temporary `RTHandle` and `Blitter.BlitCameraTexture`. + - Never recommend `Blitter.BlitCameraTexture(cmd, source, source, material, pass)` or other source-to-source blits as the main `OnRenderImage` replacement. If you are not going to show the safer temporary-target pattern, omit the code sample and explain the architecture instead. + - For Surface Shaders, state that `#pragma surface` has no direct URP equivalent and choose Shader Graph or a URP HLSL vertex/fragment rewrite based on effect complexity. + - Do not stop at "rewrite it"; give the user a practical first porting step and a validation target. + +## 9. Quality, Lighting, and Visual Parity Review + +Use [references/quality-settings-map.md](references/quality-settings-map.md) when the user mentions visual mismatch, shadows, performance, or quality settings. + +1. **Review Graphics settings and each active Quality level.** + - If a Quality level has no custom URP asset, state whether that level intentionally falls back to Graphics settings or still needs explicit assignment. + - Do not claim all quality levels are migrated unless each relevant level has been inspected. + - If using serialized project settings, the saved field is commonly `customRenderPipeline`; writing a guessed field such as `renderPipelineAsset` is not enough unless the saved asset proves the assignment. + - If assigning through script, use the same pattern as URP's Render Settings converter: cache `QualitySettings.GetQualityLevel()`, call `QualitySettings.SetQualityLevel(index)` for each target level, assign `QualitySettings.renderPipeline = urpAsset`, then restore the original quality level and verify on disk. +2. **Check URP asset settings** that commonly affect parity: + - shadows + - shadow distance and cascades + - MSAA + - render scale + - HDR, opaque texture, and depth texture settings when effects depend on them +3. **Do not promise identical lighting automatically.** + - Built-in and URP can differ in light falloff, baked GI appearance, shadow tuning, reflection probe response, tonemapping, exposure, and post-processing behavior. +4. **For baked lighting scenes,** treat existing lightmaps and Lighting Data as reference material, not guaranteed-final URP output. + - Inventory `Lightmapping.lightingSettings`, scene `LightmapSettings`, baked/mixed lights, light probes, reflection probes, and any existing `LightingDataAsset`. + - Preserve baked data until the user has a visual reference or rollback point, but do not keep stale Built-in lightmaps active as the final URP lighting solution if they blow out or distort the scene. + - If the scene looks blown out, too dark, or mismatched, first isolate post-processing/exposure by disabling legacy PPv2 during URP validation, then review URP Volume exposure/tonemapping/bloom before changing lights. + - If clearing baked data makes the scene stop being blown out, identify the old lightmaps/Lighting Data as stale or incompatible active data. Then rebake under URP with the final URP asset, renderer, Volume, and Quality settings instead of tuning lights against the stale bake. + - If creating or assigning URP-compatible `LightingSettings`, mark the lighting settings and active scene dirty, save the scene, reload or re-query, and verify the saved scene references the intended `.lighting` asset. `AssetDatabase.SaveAssets()` alone does not save the scene's `Lightmapping.lightingSettings` reference. Do not report "Baked GI enabled" if the saved scene still points at an Enlighten/realtime-GI settings asset. + - When visual parity is part of the request and old `LightingData.asset` / reflection-probe EXRs remain from the Built-in bake, treat them as reference data until rebaked/refreshed under URP. Do not claim visual parity is preserved from old bake data alone. + - Recommend clearing/rebaking lighting and refreshing reflection probes when visual parity matters. Do not claim baked lighting was successfully migrated unless a representative scene has been visually checked after URP setup, and preferably after a URP bake. +5. **For 2D lighting projects,** ensure sprites and tilemaps use URP-compatible lit materials where required. +6. **For performance regressions,** inspect whether the issue is coming from: + - heavier URP asset settings + - post-processing + - extra shadow cost + - custom shader ports or non-batched shaders +7. **When changing URP asset settings,** re-read the saved asset or query the property after saving before reporting values such as MSAA, additional-light limits, HDR, depth texture, or opaque texture. + +## 10. Validation + +After setup, conversion, or troubleshooting, validate the result: + +1. **Capture the scene:** Capture the Scene View or a specific camera on a representative scene — see [references/capturing-the-editor.md](references/capturing-the-editor.md). +2. **Evaluate the result:** + - no magenta materials unless unresolved custom shader blockers remain + - main lighting and shadows behave as expected + - baked GI/lightmaps, light probes, and reflection probes are acceptable or explicitly marked for rebake/refresh + - cameras render expected content + - post-processing or fullscreen effects still behave correctly + - sprites, tilemaps, or 2D lights work when relevant +3. **Verify persistent project data, not only visual output.** + - Inspect saved URP assets, renderer assets, scene references, material shader GUIDs/names, Quality settings, and Volume profiles. + - Confirm the representative scene, not a default/test scene, was opened or otherwise inspected before concluding that baked lighting, PPv2, or reflection probes are absent. + - For material migration, include particle/VFX materials in the verification. Remaining built-in particle shader IDs or legacy particle shader names mean material migration is incomplete. + - For PPv2 migrations, saved URP `VolumeProfile` assets must contain the expected override components before reporting them as migrated. + - If a tool log reports mapped post-processing but the saved profile reloads with `components: []`, override the tool log and report the migration as failed/incomplete. + - For baked-lighting scenes, inspect Lighting Settings, Lighting Data/lightmap references, light probes, and reflection probes. Compilation success does not prove baked lighting parity. If old lightmaps cause overexposure, clear active baked data and rebake under URP before judging parity. After assigning new lighting settings, verify the saved scene reference, not only the existence of the new `.lighting` asset. + - If old `LightingData.asset` remains assigned and reflection-probe EXRs were not regenerated or explicitly accepted after visual inspection, classify lighting/probes as preserved or partial rather than refreshed. +4. **Check Console output** with `Unity.GetConsoleLogs` for shader, render pipeline, or renderer-feature errors. +5. **Fix the highest-impact issue first,** then validate again. +6. **Repeat for at most 3 iterations** before reporting remaining blockers or asking the user how they want to proceed. + +## 11. Troubleshooting Decision Tree + +If the user reports a migration problem, follow this diagnostic flow: + +### Project still behaves like Built-in after "migration" +1. Check whether a URP asset is assigned in Graphics settings. +2. Check whether the relevant Quality levels also point to a URP asset. +3. Verify that the active render pipeline is actually URP before troubleshooting anything else. + +### Migration stopped after installing URP +1. Treat this as a package-refresh/domain-reload boundary, not a failed full migration by itself. +2. Re-check `Packages/manifest.json` and package state. If URP is installed, do not reinstall it. +3. Inspect for partial assets such as URP assets, renderer assets, converted materials, empty Volume profiles, and scene component changes. +4. If URP is installed/assigned but Built-in materials still dominate or the scene is magenta, material conversion is the immediate next incomplete item. Do not call URP setup complete and stop there if rollback safety has already been confirmed. +5. Continue from the first incomplete verification item: Graphics/Quality assignment, renderer asset validity, material conversion, PPv2-to-URP Volume migration, baked-lighting rebake, then final validation. +6. If the previous chat/export has no final response, report it as incomplete evidence and continue in a fresh chat/turn. + +### Materials are magenta / bright pink +1. Check Console and Inspector for shader errors. +2. Confirm whether the material uses a supported Built-in shader, a custom shader, or a package-owned shader. +3. If it is a supported Built-in shader, review the converter path or targeted material conversion first. +4. If the project is already on URP and many supported Built-in materials remain, treat this as incomplete material conversion, not as final visual parity work. +5. If it is a custom or package shader, move into [references/custom-shader-triage.md](references/custom-shader-triage.md) and, when needed, [references/complex-shader-situations.md](references/complex-shader-situations.md). + +### Scene is much darker, brighter, or just "wrong" +1. Compare Scene View and Game View captures. +2. Check for double post-processing first: retained PPv2 `PostProcessVolume` / `PostProcessLayer` plus an active URP `Volume` can overexpose or otherwise distort validation captures. +3. Review shadows, ambient/environment lighting, tone mapping, skybox, exposure, bloom, and post-processing. +4. Inspect baked lighting state: Lighting Settings, Lighting Data asset, lightmap references, mixed/baked lights, light probes, and reflection probes. +5. Review quality-level assignments and URP asset settings before rewriting content. +6. If clearing baked data fixes severe overexposure, treat the old lightmaps as stale active data: keep them only as reference/rollback evidence, then rebake under URP. +7. If the scene depends on baked lighting, recommend a URP rebake and reflection-probe refresh before claiming visual parity. +8. Remember that Built-in and URP light falloff can differ; treat that as a tuning task, not proof that conversion failed. + +### Post-processing or fullscreen effects disappeared +1. Check whether PPv2 was converted and whether the target camera and volume setup are still valid. +2. Inspect saved URP `VolumeProfile` assets. Empty profiles mean PPv2 effects were not actually migrated. +3. Check whether old `PostProcessVolume` / `PostProcessLayer` components remain active in scenes. +4. Search for `OnRenderImage`, custom blit code, or replacement-shader camera effects. +5. Port the effect using a URP-compatible approach rather than trying to preserve the Built-in callback path unchanged. + +### Transparent / refraction / distortion effects broke +1. Inspect whether the shader relied on `GrabPass` or a similar Built-in screen-copy workflow. +2. If so, use [references/complex-shader-situations.md](references/complex-shader-situations.md) and choose a Scene Color / Renderer Feature / custom-pass approach. + +### 2D lights are not affecting sprites +1. Confirm the project is actually using the 2D Renderer. +2. Confirm existing sprite materials were upgraded to URP-compatible lit materials where needed. +3. Do not assume dragged-in new sprites prove old project materials are correct. + +### Performance regressed after migration +1. Review render scale, shadows, additional lights, MSAA, post-processing, opaque/depth textures, and other URP asset settings. +2. Check whether custom shader ports lost batching compatibility or introduced extra passes. +3. Tune settings before assuming the only answer is a rollback. + +### Custom shader compiles but visuals are still wrong +1. Determine whether the issue is simple parameter drift or a structural incompatibility. +2. If the shader came from a surface shader, `GrabPass`, replacement-shader workflow, or custom lighting path, treat it as a complex migration case. +3. Validate one representative material before rolling the approach out project-wide. + +## 12. Core Guardrails + +- Detect the active render pipeline first; for HDRP, explain the mismatch and offer only basic comparison guidance. +- Do not mutate the project before rollback safety is confirmed; instead stop in planning mode before URP install, asset assignment, converters, material edits, or scene edits. +- Do not treat tool logs or created assets as proof; instead save/reload/re-query Graphics, Quality, materials, Volumes, lighting, probes, Console, and representative scenes before status claims. +- Do not collapse partial phases into project-level completion; instead report `Phase complete`, `Partial migration`, and `Manual follow-up` based on the success gate. +- Do not bulk-rewrite fragile rendering code; instead use scoped mappings for custom shaders, `GrabPass`, `OnRenderImage`, replacement shaders, package-owned render code, and unsupported PPv2 effects. + +## 13. Reporting Back + +Summarize: + +- which migration path was used +- whether the project is still Built-in, partially migrated, or fully on URP +- which converters ran +- what was fixed automatically +- what still needs manual work +- which scenes, materials, or cameras were validated +- what remains risky, especially around complex shaders, package effects, and visual parity +- whether post-processing was fully migrated, partially prepared, or only documented for manual follow-up +- whether Quality levels are explicitly assigned or intentionally relying on Graphics settings fallback +- whether particle/VFX materials were converted or still need URP particle-shader follow-up +- whether baked lighting/lightmaps/reflection probes were preserved as reference, verified visually, rebaked/refreshed, or left as manual follow-up +- whether exposure, tonemapping, ambient fill, and camera/Volume post-processing wiring were visually balanced or still remain partial + +Use completion language conservatively. If the report contains any `Partial`, `Manual follow-up`, `not validated`, `may still need`, or unsupported-feature item, state that specific completed phases passed and call the overall migration partial/manual. Do not pair a project-level "complete" claim with manual follow-up bullets. + +## References + +- [Migration Workflow](references/migration-workflow.md) +- [Custom Shader Triage](references/custom-shader-triage.md) +- [Complex Shader Situations](references/complex-shader-situations.md) +- [Quality Settings Map](references/quality-settings-map.md) +- [Implementation Patterns](references/implementation-patterns.md) + +When this skill is activated, proactively read [references/migration-workflow.md](references/migration-workflow.md). If the request involves materials, shaders, magenta materials, custom rendering, or fullscreen effects, also read [references/custom-shader-triage.md](references/custom-shader-triage.md) and [references/complex-shader-situations.md](references/complex-shader-situations.md). If the request involves visual mismatch, lighting, baked lighting, lightmaps, reflection probes, shadows, or performance after migration, also read [references/quality-settings-map.md](references/quality-settings-map.md). If the user permits actual migration changes or asks to continue/repair a partial migration, also read [references/implementation-patterns.md](references/implementation-patterns.md). diff --git a/skills/migrate-birp-to-urp/references/capturing-the-editor.md b/skills/migrate-birp-to-urp/references/capturing-the-editor.md new file mode 100644 index 0000000..7b56429 --- /dev/null +++ b/skills/migrate-birp-to-urp/references/capturing-the-editor.md @@ -0,0 +1,66 @@ +# Capturing the Editor for visual checks + +There is no capture command in the Pipeline catalog. Run the capture as C# through `eval`, write +a PNG, then **read that file** — reading an image is something the agent does natively, so the +two-step version is equivalent to a single capture tool. + +Both variants below were verified against a live Unity 6 Editor. + +## Scene View + +```csharp +var sv = UnityEditor.SceneView.lastActiveSceneView; +if (sv == null) throw new System.Exception("No active SceneView to capture."); +var cam = sv.camera; +var rt = new UnityEngine.RenderTexture(1280, 720, 24); +var prevTarget = cam.targetTexture; cam.targetTexture = rt; cam.Render(); cam.targetTexture = prevTarget; +var prevActive = UnityEngine.RenderTexture.active; UnityEngine.RenderTexture.active = rt; +var tex = new UnityEngine.Texture2D(1280, 720, UnityEngine.TextureFormat.RGB24, false); +tex.ReadPixels(new UnityEngine.Rect(0, 0, 1280, 720), 0, 0); tex.Apply(); +UnityEngine.RenderTexture.active = prevActive; +var path = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "unity-sceneview.png"); +System.IO.File.WriteAllBytes(path, UnityEngine.ImageConversion.EncodeToPNG(tex)); +return path; +``` + +## Game camera + +Same shape, rendering `Camera.main` instead: + +```csharp +var cam = UnityEngine.Camera.main; +if (cam == null) throw new System.Exception("No Camera.main in the active scene."); +var rt = new UnityEngine.RenderTexture(1280, 720, 24); +var prevTarget = cam.targetTexture; cam.targetTexture = rt; cam.Render(); cam.targetTexture = prevTarget; +var prevActive = UnityEngine.RenderTexture.active; UnityEngine.RenderTexture.active = rt; +var tex = new UnityEngine.Texture2D(1280, 720, UnityEngine.TextureFormat.RGB24, false); +tex.ReadPixels(new UnityEngine.Rect(0, 0, 1280, 720), 0, 0); tex.Apply(); +UnityEngine.RenderTexture.active = prevActive; +var path = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "unity-gameview.png"); +System.IO.File.WriteAllBytes(path, UnityEngine.ImageConversion.EncodeToPNG(tex)); +return path; +``` + +## Several angles + +Move the Scene View camera between captures and write a distinct filename each time. `sv.pivot` +sets what it looks at, `sv.rotation` the direction, `sv.size` the distance; call `sv.Repaint()` +after changing them, then capture as above. + +```csharp +var sv = UnityEditor.SceneView.lastActiveSceneView; +sv.pivot = new UnityEngine.Vector3(0, 0, 0); +sv.rotation = UnityEngine.Quaternion.Euler(30, 45, 0); // vary this per angle +sv.size = 10f; +sv.Repaint(); +``` + +To frame specific objects rather than a fixed point, select them and use +`UnityEditor.SceneView.FrameLastActiveSceneView()`, or set `sv.pivot` to the centre of their +combined bounds. + +**Then read the returned path.** Write each capture to a distinct filename so a later capture +doesn't get confused with an earlier one — comparing before and after a change is the whole point. + +Temp files are the default here rather than `Assets/`: a PNG written into the project becomes an +imported asset the user then has to clean up. diff --git a/skills/migrate-birp-to-urp/references/complex-shader-situations.md b/skills/migrate-birp-to-urp/references/complex-shader-situations.md new file mode 100644 index 0000000..b5b0ecf --- /dev/null +++ b/skills/migrate-birp-to-urp/references/complex-shader-situations.md @@ -0,0 +1,272 @@ +# Complex Shader Situations + +Use deliberate URP migration strategies for higher-risk Built-in rendering patterns instead of quick shader rewrites. + +## Table of Contents + +- [Official References](#official-references) +- [Ground Truth](#ground-truth) +- [Case 1: Surface Shaders](#case-1-surface-shaders) +- [Case 2: GrabPass, Refraction, and Scene-Color Effects](#case-2-grabpass-refraction-and-scene-color-effects) +- [Case 3: Built-in Fullscreen Effects and `OnRenderImage`](#case-3-built-in-fullscreen-effects-and-onrenderimage) +- [Case 4: Replacement Shader and Alternate-Camera Rendering Workflows](#case-4-replacement-shader-and-alternate-camera-rendering-workflows) +- [Case 5: Multi-Pass, Custom Lighting, and Deferred-Specific Shaders](#case-5-multi-pass-custom-lighting-and-deferred-specific-shaders) +- [Case 6: Package-Owned and Framework Shaders](#case-6-package-owned-and-framework-shaders) +- [Stop-and-Escalate Signals](#stop-and-escalate-signals) + +## Official References + +- [Render pipeline feature comparison](https://docs.unity3d.com/Manual/render-pipelines-feature-comparison.html) +- [Upgrade custom shaders for URP compatibility](https://docs.unity3d.com/Manual/urp/urp-shaders/birp-urp-custom-shader-upgrade-guide.html) +- [Camera.OnRenderImage](https://docs.unity3d.com/ScriptReference/Camera.OnRenderImage.html) +- [Custom post-processing in URP](https://docs.unity3d.com/Manual/urp/post-processing/custom-post-processing.html) +- [Make a shader compatible with the Deferred rendering path in URP](https://docs.unity3d.com/Manual/urp/rendering/make-shader-compatible-with-deferred.html) + +## Ground Truth + +- The Render Pipeline Converter does not auto-upgrade custom shaders. +- URP does not support `GrabPass`. +- URP does not support Surface Shaders. +- `OnRenderImage` is a Built-in image-effect path; URP custom fullscreen effects should move toward `ScriptableRenderPass`, Renderer Features, or URP custom post-processing. +- Hand-written shaders can work in URP, but Shader Graph is often the safer option for simpler effects. + +## Case 1: Surface Shaders + +Indicators: + +- `#pragma surface` +- Surface-shader lighting models +- heavy reliance on Built-in lighting helpers + +Implication: + +- Surface shaders do not carry over directly to URP. + +Safer migration path: + +1. Do not attempt a blind syntax patch. +2. Decide whether the shader should be: + - rebuilt in Shader Graph + - rewritten as a URP HLSL shader + - replaced with an existing URP shader if the effect is not special +3. Port and validate one representative shader before scaling. + +## Case 2: GrabPass, Refraction, and Scene-Color Effects + +Indicators: + +- `GrabPass` +- screen-copy distortion or refraction effects +- shaders that expect to sample the already-rendered scene the Built-in way + +Implication: + +- URP does not support `GrabPass`. + +Safer migration path: + +1. Determine whether the effect really needs scene color. +2. Enable **Opaque Texture** in the URP Asset if the effect needs to sample already-rendered opaque color. +3. For simple material-level effects, consider a Shader Graph Scene Color path when appropriate. +4. For hand-written HLSL, prefer URP's opaque texture include instead of a raw `GrabPass` replacement: + + ```hlsl + #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareOpaqueTexture.hlsl" + + float2 screenUV = input.positionCS.xy / _ScaledScreenParams.xy; + half3 sceneColor = SampleSceneColor(screenUV); + ``` + +5. For broader effect pipelines, plan a Renderer Feature or custom pass approach. +6. Call out that Built-in `GrabPass` and URP opaque texture are not perfectly equivalent. Transparent sorting, when the texture is captured, and camera stacking can affect parity. +7. Do not claim direct parity until the effect is revalidated in-scene. + +## Case 3: Built-in Fullscreen Effects and `OnRenderImage` + +Indicators: + +- `OnRenderImage(RenderTexture, RenderTexture)` +- custom image-effect scripts +- blit-style camera callbacks + +Implication: + +- In URP, these effects should move toward `ScriptableRenderPass`, Renderer Features, or URP custom post-processing. + +Safer migration path: + +1. Identify what the effect actually needs: full-screen color, depth, normals, or custom buffers. +2. Choose a URP-compatible implementation path. +3. For a normal fullscreen blit in Unity 6 / recent URP, propose a `ScriptableRendererFeature` with a `ScriptableRenderPass`, `RTHandle`, and `Blitter` rather than preserving `OnRenderImage`. +4. Port one effect at a time. +5. Validate in Game View, not only in Scene View. + +Avoid this common mistake: + +```csharp +Blitter.BlitCameraTexture(cmd, source, source, material, 0); +``` + +Do not present source-to-source blits as the recommended replacement for `OnRenderImage`. Use a temporary target, then blit back to the camera color target. + +Minimal shape for an answer: + +```csharp +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine.Rendering.Universal; + +public sealed class CustomFullscreenEffectFeature : ScriptableRendererFeature +{ + sealed class CustomFullscreenEffectPass : ScriptableRenderPass + { + readonly ProfilingSampler m_ProfilingSampler = new("CustomFullscreenEffect"); + Material m_Material; + RTHandle m_TemporaryColor; + + public CustomFullscreenEffectPass(Material material) + { + m_Material = material; + renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing; + } + + public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData) + { + var descriptor = renderingData.cameraData.cameraTargetDescriptor; + descriptor.depthBufferBits = 0; + RenderingUtils.ReAllocateIfNeeded(ref m_TemporaryColor, descriptor, name: "_CustomFullscreenEffectTemp"); + } + + public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) + { + if (m_Material == null) + return; + + var cmd = CommandBufferPool.Get(); + using (new ProfilingScope(cmd, m_ProfilingSampler)) + { + var source = renderingData.cameraData.renderer.cameraColorTargetHandle; + Blitter.BlitCameraTexture(cmd, source, m_TemporaryColor, m_Material, 0); + Blitter.BlitCameraTexture(cmd, m_TemporaryColor, source); + } + context.ExecuteCommandBuffer(cmd); + CommandBufferPool.Release(cmd); + } + + public void Dispose() + { + m_TemporaryColor?.Release(); + } + } + + [SerializeField] Material m_Material; + CustomFullscreenEffectPass m_Pass; + + public override void Create() + { + m_Pass = new CustomFullscreenEffectPass(m_Material); + } + + public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) + { + if (m_Material != null) + renderer.EnqueuePass(m_Pass); + } + + protected override void Dispose(bool disposing) + { + m_Pass?.Dispose(); + } +} +``` + +Use this as a starting point, not a guarantee. The exact pass event, intermediate texture needs, camera stacking behavior, XR handling, and RenderGraph path may need adjustment for the target URP version. + +## Case 4: Replacement Shader and Alternate-Camera Rendering Workflows + +Indicators: + +- `RenderWithShader` +- `SetReplacementShader` +- camera-based normal/depth/edge pipelines + +Implication: + +- These workflows often depend on Built-in rendering assumptions and pass tags. + +Safer migration path: + +1. Identify the purpose of the replacement pass — outlines, selection, depth, normals, masks, and so on. +2. Determine whether URP already exposes a better path through renderer configuration, depth/normal textures, or a custom pass. +3. If a custom shader is still required, port the shader with explicit URP pass/tag expectations instead of assuming Built-in tags still drive the same behavior. + +## Case 5: Multi-Pass, Custom Lighting, and Deferred-Specific Shaders + +Indicators: + +- many passes in one shader +- custom forward-add or deferred assumptions +- lighting code tightly coupled to Built-in helper includes +- shaders that need specific deferred compatibility + +Implication: + +- These are not simple "replace includes and tags" conversions. + +Safer migration path: + +1. Determine which passes are still needed in URP. +2. If the shader must support URP Deferred, validate the required URP pass tags and rendering-path behavior. +3. If the shader can be forward-only, say so explicitly and scope the port accordingly. +4. Expect multiple validation rounds. + +## Case 6: Package-Owned and Framework Shaders + +Indicators: + +- Asset Store shader frameworks +- water, foliage, toon, outline, dissolve, or special rendering packages +- package namespace includes or generated shader code + +Implication: + +- These are not safe to rewrite blindly. They may need maintainer guidance, a scoped custom replacement, or manual follow-up if the project does not already contain an approved URP-compatible path. + +Safer migration path: + +1. Identify the exact materials and shader files affected. +2. Do not edit immutable package files or generated shader internals in bulk. +3. Use local material copies or scoped custom replacements when the user approves that approach. +4. Limit manual ports to the exact materials the user needs and call out the risk clearly. + +## Case 7: Foliage, Grass, Tree, and Billboard Shaders + +Indicators: + +- grass, tree, bush, leaf, terrain detail, SpeedTree, billboard, or vegetation material names +- `Nature/`, `TreeCreator`, `Grass`, `Foliage`, or similar shader names +- cutout or alpha-test properties such as `_Cutoff`, `_AlphaClip`, `_AlphaTest`, or alpha threshold keywords +- culling, two-sided, billboard, wind, bend, hue variation, or terrain-detail keywords + +Implication: + +- A grass or tree material can be "not pink" but still wrong if the converter drops alpha clipping, two-sided leaf rendering, wind, billboard behavior, normals, tint, or terrain detail integration. +- Plain URP Lit can be acceptable only for simple opaque bark or non-specialized meshes. It is usually not enough for grass cards, leaf cards, billboards, or wind-driven foliage. + +Safer migration path: + +1. Separate bark/opaque mesh materials from leaf, grass, billboard, and terrain-detail materials. +2. For simple bark/opaque materials, preserve base map, color, normal, metallic/smoothness, and tiling/offset. +3. For foliage cards, preserve base texture, tint, alpha cutoff, transparent/cutout intent, culling/two-sided intent, and normal/detail maps. +4. Do not call foliage complete until a representative scene/camera confirms grass and leaves are not solid opaque quads. +5. If wind, billboard, or terrain-detail behavior cannot be preserved in the current pass, report that exact behavior as manual/custom-shader follow-up instead of claiming full visual parity. + +## Stop-and-Escalate Signals + +Stop and summarize the blocker instead of guessing when: + +- multiple high-risk shader categories overlap in the same project +- the effect depends on undocumented package internals +- the shader compiles but visual parity is clearly not explainable by parameter tuning +- the project relies heavily on custom fullscreen or replacement-shader rendering paths +- the user expects production-safe parity but the port strategy is still exploratory diff --git a/skills/migrate-birp-to-urp/references/custom-shader-triage.md b/skills/migrate-birp-to-urp/references/custom-shader-triage.md new file mode 100644 index 0000000..cfe6258 --- /dev/null +++ b/skills/migrate-birp-to-urp/references/custom-shader-triage.md @@ -0,0 +1,123 @@ +# Custom Shader Triage + +Use this triage for shader and material cases that the Render Pipeline Converter cannot solve automatically. + +## Table of Contents + +- [Official Reference](#official-reference) +- [Ground Truth](#ground-truth) +- [Triage Workflow](#triage-workflow) +- [Classification Guide](#classification-guide) +- [Manual Conversion Guidance](#manual-conversion-guidance) +- [Shader Graph Option](#shader-graph-option) +- [Validation Checklist](#validation-checklist) +- [Never Rules](#never-rules) + +## Official Reference + +- [Upgrade custom shaders for URP compatibility](https://docs.unity3d.com/Manual/urp/urp-shaders/birp-urp-custom-shader-upgrade-guide.html) +- [Upgrade material assets to URP or HDRP](https://docs.unity3d.com/Manual/upgrade-material.html) +- [Render pipeline feature comparison](https://docs.unity3d.com/Manual/render-pipelines-feature-comparison.html) + +## Ground Truth + +- Built-in custom shaders are not automatically upgraded by the Render Pipeline Converter. +- After URP migration, materials that still rely on unsupported shaders often appear magenta. +- The safe response is triage first, not blind bulk rewriting. +- Hand-written ShaderLab shaders can work in URP, but they often need deliberate porting. +- Shader Graph is often safer than hand-porting when the original visual behavior is conceptually simple. + +## Triage Workflow + +1. Identify which materials are magenta or failing. +2. Find the shader asset each failing material uses. +3. Determine how widely each shader is used before changing it. +4. Inspect Console and Inspector errors before editing shader code. +5. Separate the cases into: + - Unity built-in shaders the converter should have handled + - particle, VFX, fog, smoke, steam, decal, or transparent effect materials that need URP particle/effect shader mapping + - simple custom shaders that may be rewritten safely + - complex or package-owned shaders that need scoped manual work +6. Prefer the smallest safe fix that restores rendering for the user. +7. Validate representative materials before scaling a fix across the project. + +## Classification Guide + +### Lower-Risk Cases + +These are better candidates for a scoped manual conversion: + +- simple unlit custom shaders +- legacy particle/fog/smoke materials that can map cleanly to URP particle shaders without changing the intended blend mode +- very small single-pass shaders +- effect shaders where the visual intent is narrow and obvious +- custom shaders used by only one or two materials + +### Medium-Risk Cases + +These often need a representative port and careful validation: + +- shaders with multiple exposed material properties +- hand-written lit shaders that expect Built-in lighting helpers +- shaders used across many materials or prefabs +- shaders that must preserve batching or performance characteristics + +### Higher-Risk Cases + +These should be treated as deliberate migration tasks, not quick fixes: + +- surface shaders +- `GrabPass`-based shaders +- large multi-pass shaders +- package-owned shaders +- water, foliage, toon, outline, x-ray, or dissolve frameworks +- shaders tied to custom lighting models, replacement shaders, fullscreen image effects, or camera callbacks +- shaders that assume specific Built-in forward/deferred behavior +- shaders with platform-specific or XR-specific paths +- vegetation shaders that rely on cutout alpha, two-sided leaf cards, billboards, terrain detail rendering, wind animation, or SpeedTree-style behavior + +For advanced cases, continue with [complex-shader-situations.md](complex-shader-situations.md). + +## Manual Conversion Guidance + +The official URP guide shows a typical direction for simple shader rewrites: + +- move from `CGPROGRAM` to `HLSLPROGRAM` +- use URP shader library includes such as `Core.hlsl` +- add the URP render pipeline tag +- rewrite incompatible Built-in pipeline code instead of expecting compatibility + +Do not assume this is enough for lit, multi-pass, or effect-heavy shaders. + +Do not generalize one sample shader rewrite across an entire project. + +## Shader Graph Option + +If the original shader behavior is conceptually simple, recreating it in Shader Graph can be safer than hand-porting a complex Built-in shader. + +Good Shader Graph candidates include: + +- simple unlit or fresnel-based materials +- color-mask, dissolve, or rim-light effects +- sprite and VFX-style materials with limited lighting requirements +- simple distortion or scene-color-driven effects that have a URP-compatible replacement path + +## Validation Checklist + +After triaging or porting a shader: + +1. Validate at least one representative material in-scene. +2. Check Console and Inspector for shader warnings and errors. +3. Check whether the shader still supports the required rendering path and passes. +4. Check whether batching or performance changed significantly. +5. For particle and VFX materials, confirm blend mode, softness, alpha clipping, and sorting still look correct. +6. For foliage and vegetation materials, confirm grass/tree cards are not solid opaque quads, cutout thresholds are preserved, leaves render from the expected sides, texture/tint/normal data survived, and wind or billboard behavior is either still working or explicitly listed as manual follow-up. +7. Only then consider applying the same approach to more materials. + +## Never Rules + +- NEVER claim that Render Pipeline Converter handles custom shaders automatically. +- NEVER bulk search-and-replace shader code across a project without a verified mapping. +- NEVER promise perfect visual parity after a shader port without validation. +- NEVER continue editing shader files when Console errors show the approach is diverging. Stop, summarize the blocker, and propose the next safe step instead. +- NEVER treat package-owned shaders as safe to rewrite blindly. Check whether the package already has URP support first. diff --git a/skills/migrate-birp-to-urp/references/implementation-patterns.md b/skills/migrate-birp-to-urp/references/implementation-patterns.md new file mode 100644 index 0000000..decffcb --- /dev/null +++ b/skills/migrate-birp-to-urp/references/implementation-patterns.md @@ -0,0 +1,529 @@ +# Built-in to URP Implementation Patterns + +> **How to run the C# in this file.** Blocks that declare a `class`, a `static` method, or a +> `[MenuItem]` are project files — save them under `Assets/Editor/`, let Unity compile, then call +> their entry point through a one-line `unity command eval`. Keep their `using` directives; they are +> correct in a file. Blocks that are a bare sequence of statements can go straight to `eval`, but +> only fully qualified and with no `using` lines — `eval` compiles a statement block, so a `using` +> is read as a resource-disposal statement and rejected (`CS0210`). Extension methods are +> unavailable through `eval` for the same reason: write `System.Linq.Enumerable.FirstOrDefault(seq, +> pred)` and `GetComponent()` instead of the extension forms. + +Use these patterns when executing a migration, not for planning-only answers. They exist for fragile steps where a generic script often appears to work in tool logs but fails to persist saved project state. Treat these patterns as lower-freedom implementation requirements, not optional inspiration. + +## Table of Contents + +- [Persistent URP VolumeProfile Pattern](#persistent-urp-volumeprofile-pattern) +- [Pre-Conversion Material Snapshot Pattern](#pre-conversion-material-snapshot-pattern) +- [Safe Manual Material Conversion Pattern](#safe-manual-material-conversion-pattern) +- [Legacy PPv2 Disable Pattern](#legacy-ppv2-disable-pattern) +- [Reflection Probe URP Asset Pattern](#reflection-probe-urp-asset-pattern) +- [Lighting And Probe Claim Rules](#lighting-and-probe-claim-rules) +- [Visual Exposure Balance Pattern](#visual-exposure-balance-pattern) +- [Execution Regression Checklist](#execution-regression-checklist) +- [Final Status Pattern](#final-status-pattern) + +## Pre-Conversion Material Snapshot Pattern + +Before running Unity's material converter, a bulk material script, or any manual shader assignment, snapshot source material values while the material is still on the Built-in shader. Use this snapshot as the source of truth for `_BaseMap` and `_BaseColor` restoration. Do not try to recover `_MainTex` after shader conversion. + +Minimum snapshot fields: + +- asset path and GUID +- source shader name +- `_MainTex`, texture scale, and texture offset +- `_Color` +- `_BumpMap` +- `_MetallicGlossMap`, `_SpecGlossMap`, `_Metallic`, `_Glossiness`, and `_SpecColor` where present +- `_EmissionMap` and `_EmissionColor` +- `_Cutoff` and `_Mode` + +```csharp +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +sealed class MaterialSnapshot +{ + public string Path; + public string Guid; + public string SourceShader; + public Texture MainTex; + public Vector2 MainTexScale = Vector2.one; + public Vector2 MainTexOffset = Vector2.zero; + public Color Color = Color.white; + public Texture Normal; + public Texture MetallicGloss; + public Texture SpecGloss; + public Texture Emission; + public Color EmissionColor = Color.black; + public float Metallic; + public float Glossiness = 0.5f; + public Color SpecColor = Color.black; + public float Cutoff = 0.5f; + public int Mode; +} + +static Dictionary SnapshotProjectMaterials() +{ + var snapshots = new Dictionary(); + foreach (var guid in AssetDatabase.FindAssets("t:Material")) + { + var path = AssetDatabase.GUIDToAssetPath(guid); + if (path.StartsWith("Packages/") || path.StartsWith("Library/PackageCache/")) + continue; + + var mat = AssetDatabase.LoadAssetAtPath(path); + if (mat == null) + continue; + + snapshots[path] = new MaterialSnapshot + { + Path = path, + Guid = guid, + SourceShader = mat.shader != null ? mat.shader.name : "", + MainTex = mat.HasProperty("_MainTex") ? mat.GetTexture("_MainTex") : null, + MainTexScale = mat.HasProperty("_MainTex") ? mat.GetTextureScale("_MainTex") : Vector2.one, + MainTexOffset = mat.HasProperty("_MainTex") ? mat.GetTextureOffset("_MainTex") : Vector2.zero, + Color = mat.HasProperty("_Color") ? mat.GetColor("_Color") : Color.white, + Normal = mat.HasProperty("_BumpMap") ? mat.GetTexture("_BumpMap") : null, + MetallicGloss = mat.HasProperty("_MetallicGlossMap") ? mat.GetTexture("_MetallicGlossMap") : null, + SpecGloss = mat.HasProperty("_SpecGlossMap") ? mat.GetTexture("_SpecGlossMap") : null, + Emission = mat.HasProperty("_EmissionMap") ? mat.GetTexture("_EmissionMap") : null, + EmissionColor = mat.HasProperty("_EmissionColor") ? mat.GetColor("_EmissionColor") : Color.black, + Metallic = mat.HasProperty("_Metallic") ? mat.GetFloat("_Metallic") : 0f, + Glossiness = mat.HasProperty("_Glossiness") ? mat.GetFloat("_Glossiness") : 0.5f, + SpecColor = mat.HasProperty("_SpecColor") ? mat.GetColor("_SpecColor") : Color.black, + Cutoff = mat.HasProperty("_Cutoff") ? mat.GetFloat("_Cutoff") : 0.5f, + Mode = mat.HasProperty("_Mode") ? (int)mat.GetFloat("_Mode") : 0 + }; + } + return snapshots; +} +``` + +After conversion, restore and verify from the snapshot: + +```csharp +static bool RestoreBasePropertiesFromSnapshot(Material mat, MaterialSnapshot snapshot) +{ + if (mat == null || snapshot == null) + return false; + + if (snapshot.MainTex != null && mat.HasProperty("_BaseMap")) + { + mat.SetTexture("_BaseMap", snapshot.MainTex); + mat.SetTextureScale("_BaseMap", snapshot.MainTexScale); + mat.SetTextureOffset("_BaseMap", snapshot.MainTexOffset); + } + if (mat.HasProperty("_BaseColor")) + mat.SetColor("_BaseColor", snapshot.Color); + if (snapshot.Normal != null && mat.HasProperty("_BumpMap")) + { + mat.SetTexture("_BumpMap", snapshot.Normal); + mat.EnableKeyword("_NORMALMAP"); + } + if (snapshot.MetallicGloss != null && mat.HasProperty("_MetallicGlossMap")) + mat.SetTexture("_MetallicGlossMap", snapshot.MetallicGloss); + if (snapshot.Emission != null && mat.HasProperty("_EmissionMap")) + { + mat.SetTexture("_EmissionMap", snapshot.Emission); + mat.EnableKeyword("_EMISSION"); + } + if (mat.HasProperty("_Cutoff")) + mat.SetFloat("_Cutoff", snapshot.Cutoff); + + EditorUtility.SetDirty(mat); + return snapshot.MainTex == null || !mat.HasProperty("_BaseMap") || mat.GetTexture("_BaseMap") != null; +} +``` + +If no snapshot was captured, do not claim texture preservation. A post-conversion pass such as `if (mat.HasProperty("_MainTex")) mat.SetTexture("_BaseMap", mat.GetTexture("_MainTex"))` is not reliable after the shader has changed, because `_MainTex` may already be null/default. + +## Safe Manual Material Conversion Pattern + +Prefer Unity's Render Pipeline Converter when available. Use manual `mat.shader = Shader.Find(...)` only as a fallback, and never after losing the source material data. Before changing a shader, snapshot source properties and restore mapped URP values immediately after assignment. Skip immutable package assets. + +**Whichever route you take, read the resulting `shader.name` back.** `MaterialUpgrader.FetchAllUpgradersForPipeline` returns the 2D provider set as well as the 3D one, and two upgraders claim `Standard` at equal priority — so on a 3D project the converter can land a `Standard` material on `Universal Render Pipeline/2D/Mesh2D-Lit-Default` rather than `Universal Render Pipeline/Lit`. It throws nothing and the material does not render magenta, so the only way to catch it is to check the name. Measured on 6000.5.8f1. + +If it happens: restore the affected materials from the rollback point and either filter the upgrader list to the 3D providers, or convert them with the manual pattern below, which names its target shader explicitly and so cannot be hijacked. + +```csharp +// Verify, per material, after any conversion route. +var expected = "Universal Render Pipeline/Lit"; // or the mapped target for this material +if (mat.shader.name != expected) + Debug.LogWarning($"{path}: landed on '{mat.shader.name}', expected '{expected}'"); +``` + +```csharp +using UnityEditor; +using UnityEngine; + +static bool TryConvertStandardMaterialSafely(Material mat, string path) +{ + if (mat == null || path.StartsWith("Packages/") || path.StartsWith("Library/PackageCache/")) + return false; + + var sourceMain = mat.HasProperty("_MainTex") ? mat.GetTexture("_MainTex") : null; + var sourceColor = mat.HasProperty("_Color") ? mat.GetColor("_Color") : Color.white; + var sourceScale = mat.HasProperty("_MainTex") ? mat.GetTextureScale("_MainTex") : Vector2.one; + var sourceOffset = mat.HasProperty("_MainTex") ? mat.GetTextureOffset("_MainTex") : Vector2.zero; + var normal = mat.HasProperty("_BumpMap") ? mat.GetTexture("_BumpMap") : null; + var metallic = mat.HasProperty("_MetallicGlossMap") ? mat.GetTexture("_MetallicGlossMap") : null; + var emission = mat.HasProperty("_EmissionMap") ? mat.GetTexture("_EmissionMap") : null; + var cutoff = mat.HasProperty("_Cutoff") ? mat.GetFloat("_Cutoff") : 0.5f; + + var urpLit = Shader.Find("Universal Render Pipeline/Lit"); + if (urpLit == null) + return false; + + mat.shader = urpLit; + + if (mat.HasProperty("_BaseMap")) + { + mat.SetTexture("_BaseMap", sourceMain); + mat.SetTextureScale("_BaseMap", sourceScale); + mat.SetTextureOffset("_BaseMap", sourceOffset); + } + if (mat.HasProperty("_BaseColor")) + mat.SetColor("_BaseColor", sourceColor); + if (normal != null && mat.HasProperty("_BumpMap")) + { + mat.SetTexture("_BumpMap", normal); + mat.EnableKeyword("_NORMALMAP"); + } + if (metallic != null && mat.HasProperty("_MetallicGlossMap")) + mat.SetTexture("_MetallicGlossMap", metallic); + if (emission != null && mat.HasProperty("_EmissionMap")) + { + mat.SetTexture("_EmissionMap", emission); + mat.EnableKeyword("_EMISSION"); + } + if (mat.HasProperty("_Cutoff")) + mat.SetFloat("_Cutoff", cutoff); + + if (sourceMain != null && mat.HasProperty("_BaseMap") && mat.GetTexture("_BaseMap") == null) + throw new System.Exception($"Texture was lost while converting {path}"); + + EditorUtility.SetDirty(mat); + return true; +} +``` + +After conversion, sample representative materials. If source `_MainTex` was non-null, the converted material must have a non-null `_BaseMap` or an intentionally different URP property with the same texture. If source `_Color` was not white, `_BaseColor` should not silently become white. Do not report "fixed texture/color properties" when the copied source values are already null/default because the shader was changed before snapshotting. + +## Persistent URP VolumeProfile Pattern + +When creating URP Volume overrides by script, `profile.Add()` alone is not enough proof. Persist the override objects as sub-assets, save, reload, and verify the expected non-null components before claiming PPv2 migration. For a generic PPv2 migration, include the common mappable overrides shown below unless inspection proves the source did not use them; if you omit one, report it as omitted/manual instead of implying full PPv2 parity. + +```csharp +using UnityEditor; +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine.Rendering.Universal; +using System.Linq; + +static VolumeProfile CreateOrRepairProfile(string profilePath) +{ + var profile = AssetDatabase.LoadAssetAtPath(profilePath); + if (profile == null) + { + profile = ScriptableObject.CreateInstance(); + AssetDatabase.CreateAsset(profile, profilePath); + } + + // If previous attempts left an empty/broken profile, recreate the known mappable overrides. + AddPersistentOverride(profile); + AddPersistentOverride(profile); + AddPersistentOverride(profile); + AddPersistentOverride(profile); + AddPersistentOverride(profile); + + EditorUtility.SetDirty(profile); + AssetDatabase.SaveAssets(); + AssetDatabase.ImportAsset(profilePath); + + var reloaded = AssetDatabase.LoadAssetAtPath(profilePath); + var validCount = 0; + if (reloaded != null) + { + foreach (var component in reloaded.components) + { + if (component != null) + validCount++; + } + } + + var requiredTypes = new[] + { + typeof(Bloom), + typeof(ColorAdjustments), + typeof(Tonemapping), + typeof(DepthOfField), + typeof(Vignette) + }; + + foreach (var requiredType in requiredTypes) + { + if (!reloaded.components.Any(component => component != null && component.GetType() == requiredType)) + throw new System.Exception($"URP VolumeProfile is missing persisted {requiredType.Name} override."); + } + + if (validCount < requiredTypes.Length) + throw new System.Exception("URP VolumeProfile did not persist all required override components."); + + return reloaded; +} + +static T AddPersistentOverride(VolumeProfile profile) where T : VolumeComponent +{ + if (profile.TryGet(out var existing) && existing != null) + return existing; + + var component = profile.Add(true); + component.name = typeof(T).Name; + AssetDatabase.AddObjectToAsset(component, profile); + EditorUtility.SetDirty(component); + EditorUtility.SetDirty(profile); + return component; +} +``` + +After using this pattern, also verify the representative scene has a `UnityEngine.Rendering.Volume` component whose `sharedProfile` points to the reloaded profile. A scene Volume with an empty profile, missing common mappable overrides, or `{fileID: 0}` component references is not active migrated post-processing. + +When reusing the old PPv2 GameObject as the location for URP post-processing, add a URP `Volume` component and set that component's `sharedProfile`. Do not accidentally set the PPv2 `PostProcessVolume.sharedProfile` to a URP profile; that leaves the scene with old PPv2 still active and no URP Volume reference. + +```csharp +using UnityEditor; +using UnityEditor.SceneManagement; +using UnityEngine; +using UnityEngine.Rendering; + +static void EnsureSceneVolumeReferencesProfile(VolumeProfile profile) +{ + var volume = Object.FindFirstObjectByType(); + if (volume == null) + { + var go = GameObject.Find("Post-process Volume") ?? new GameObject("URP Global Volume"); + volume = go.GetComponent() ?? go.AddComponent(); + } + + volume.isGlobal = true; + volume.sharedProfile = profile; + volume.enabled = true; + EditorUtility.SetDirty(volume); + EditorSceneManager.MarkSceneDirty(volume.gameObject.scene); + EditorSceneManager.SaveScene(volume.gameObject.scene); +} +``` + +## Legacy PPv2 Disable Pattern + +If a URP Volume replacement exists and has persisted non-null components, disable legacy PPv2 during URP validation. Keep the components/assets as rollback/reference unless the user approves deletion. + +```csharp +using UnityEditor; +using UnityEditor.SceneManagement; +using UnityEngine; +using UnityEngine.SceneManagement; + +static void DisableLegacyPostProcessingForValidation() +{ + foreach (var behaviour in Object.FindObjectsByType(FindObjectsSortMode.None)) + { + var typeName = behaviour.GetType().FullName; + if (typeName == "UnityEngine.Rendering.PostProcessing.PostProcessVolume" || + typeName == "UnityEngine.Rendering.PostProcessing.PostProcessLayer") + { + behaviour.enabled = false; + EditorUtility.SetDirty(behaviour); + EditorSceneManager.MarkSceneDirty(behaviour.gameObject.scene); + } + } + + EditorSceneManager.SaveScene(SceneManager.GetActiveScene()); +} +``` + +After disabling PPv2, save and re-query the scene serialization or component state. Do not claim PPv2 is disabled if the saved `PostProcessVolume` block still has `m_Enabled: 1`. Do not disable PPv2 and then claim migration success if the URP replacement profile is empty. In that case, report PPv2 as partial/incomplete. + +## Reflection Probe URP Asset Pattern + +When the representative Built-in scene uses reflection probes, verify the URP asset settings that control probe behavior. The Render Settings converter should map these from Built-in tiers, but hand-created URP assets often leave them disabled. If the scene relies on localized probes, enable and save both probe blending and box projection, then re-read the saved URP asset before reporting reflections as configured. + +```csharp +using UnityEditor; +using UnityEngine.Rendering.Universal; + +// `urpAsset.reflectionProbeBlending` and `.reflectionProbeBoxProjection` are public but +// READ-ONLY (assigning either is CS0200), so writing them means going through +// SerializedObject and the private field names. Those names carry no compatibility +// guarantee, so this must fail loudly rather than silently: if a name stops resolving, +// tell the user to tick the two boxes under Lighting → Reflection Probes on the URP asset +// instead of reporting success. Never leave a setting silently unapplied. +static bool EnableReflectionProbeSettings(UniversalRenderPipelineAsset urpAsset) +{ + var serialized = new SerializedObject(urpAsset); + // internal-api-ok: the public reflectionProbeBlending / reflectionProbeBoxProjection + // properties are get-only (assigning either is CS0200), so there is no public write + // path. Guarded below: a null property reports and defers to the user's inspector + // rather than claiming success, and the result is read back through the public + // properties to confirm the write landed. + var blending = serialized.FindProperty("m_ReflectionProbeBlending"); + var boxProjection = serialized.FindProperty("m_ReflectionProbeBoxProjection"); + + if (blending == null || boxProjection == null) + { + Debug.LogWarning( + "Could not set Probe Blending / Box Projection programmatically on " + + urpAsset.name + ". Ask the user to enable them on the URP asset under " + + "Lighting → Reflection Probes, then continue."); + return false; + } + + blending.boolValue = true; + boxProjection.boolValue = true; + + serialized.ApplyModifiedProperties(); + // Read back through the public properties to confirm it actually took. + if (!urpAsset.reflectionProbeBlending || !urpAsset.reflectionProbeBoxProjection) + { + Debug.LogWarning("Probe settings did not apply on " + urpAsset.name + + "; ask the user to set them in the URP asset inspector."); + return false; + } + return true; + EditorUtility.SetDirty(urpAsset); + AssetDatabase.SaveAssets(); +} +``` + +This does not prove reflection probes were re-rendered. It only proves the URP asset is allowed to use blending and box projection. Report probe refresh separately, based on successful render output, changed probe assets, or visual acceptance. + +## Lighting And Probe Claim Rules + +For baked-lighting scenes, distinguish three states: + +- `Preserved reference`: old `LightingData.asset`, lightmaps, and reflection-probe EXRs remain assigned/unchanged. +- `Partial`: a URP-compatible `.lighting` asset exists or is assigned, but old bake/probe outputs remain active or unchanged. +- `Refreshed/rebaked`: there is saved evidence of a completed URP bake or probe refresh, such as changed/generated lighting/probe assets, successful bake/probe-render tool output, and a post-save representative scene validation. + +`Lightmapping.Clear()` and `Lightmapping.ClearLightingDataAsset()` are not enough to claim lighting was migrated or cleared in saved state. After clearing, save the scene and re-query serialized scene state. If the scene still contains a non-zero `m_LightingDataAsset` reference, still points at an old Enlighten/realtime-GI `.lighting` asset, or no URP bake/probe refresh has completed, call lighting `Partial`, not `Complete`. + +If the final answer includes "Rebake Lighting", "refresh probes", or similar routine lighting work as a next step, the whole generic full migration is partial. Do not pair those next steps with "complete" or "visual parity preserved". + +If you start a bake with `Lightmapping.BakeAsync()`, poll `Lightmapping.isRunning` while the tool budget allows. If it is still running when you must stop, report a phase boundary/partial migration and do not say the scene has been rebaked. Before claiming that new lighting settings are active, save the scene and verify the saved scene no longer points at the old `.lighting` GUID or a non-zero old `m_LightingDataAsset`. + +Never say "reflection probes refreshed" from intent alone. If the saved EXR files are unchanged and no successful probe-render evidence exists, say they were preserved and still need refresh or visual acceptance. + +## Visual Exposure Balance Pattern + +Use this after URP asset assignment, supported material conversion, PPv2/URP Volume setup, and initial lighting/probe repair when the representative capture is visibly too dark, too bright, washed out, or much higher contrast than the Built-in reference. + +Do not ask the user for a separate "balance exposure" prompt when rollback safety was already confirmed and the current phase is visual validation. Treat this as part of the same migration pass. + +Before tuning values, prove the rendering path is actually using the URP post-processing setup: + +```csharp +var report = new System.Collections.Generic.List(); + +var urpAsset = UnityEngine.Rendering.GraphicsSettings.defaultRenderPipeline + as UnityEngine.Rendering.Universal.UniversalRenderPipelineAsset; +report.Add($"Graphics URP Asset: {(urpAsset != null ? urpAsset.name : "NULL")}"); + +if (urpAsset != null) +{ + // `rendererDataList` is public, so no SerializedObject is needed here. The default + // renderer's index is not exposed publicly — report every renderer instead, which is + // more useful anyway: a missing PostProcessData on any renderer the project switches + // to will produce the same symptom. + var renderers = urpAsset.rendererDataList; + report.Add($"Renderer Count: {renderers.Length}"); + for (int i = 0; i < renderers.Length; i++) + { + var rendererData = renderers[i] as UnityEngine.Rendering.Universal.UniversalRendererData; + report.Add($"Renderer[{i}]: {(renderers[i] != null ? renderers[i].name : "NULL")}, " + + $"PostProcessData: {rendererData != null && rendererData.postProcessData != null}"); + } +} + +var cameras = UnityEngine.Object.FindObjectsByType( + UnityEngine.FindObjectsSortMode.None); +var camera = System.Linq.Enumerable.FirstOrDefault(cameras, c => c.name == "MainCamera") + ?? UnityEngine.Camera.main; +if (camera != null) +{ + // GetUniversalAdditionalCameraData() is an extension method and needs a `using`, which eval + // rejects — read the component it wraps instead. + var data = camera.GetComponent(); + report.Add(data != null + ? $"Camera {camera.name}: renderPostProcessing={data.renderPostProcessing}, volumeLayerMask={data.volumeLayerMask.value}" + : $"Camera {camera.name}: no UniversalAdditionalCameraData component"); +} + +foreach (var volume in UnityEngine.Object.FindObjectsByType( + UnityEngine.FindObjectsSortMode.None)) +{ + report.Add($"Volume {volume.name}: global={volume.isGlobal}, enabled={volume.enabled}, " + + $"weight={volume.weight}, priority={volume.priority}, layer={volume.gameObject.layer}, " + + $"profile={(volume.sharedProfile != null ? volume.sharedProfile.name : "NULL")}, " + + $"overrides={(volume.sharedProfile != null ? volume.sharedProfile.components.Count : 0)}"); +} + +report.Add($"Ambient: mode={UnityEngine.RenderSettings.ambientMode}, " + + $"intensity={UnityEngine.RenderSettings.ambientIntensity}, " + + $"skyColor={UnityEngine.RenderSettings.ambientSkyColor}"); + +return string.Join("\n", report); +``` + +If the wiring is wrong, fix the wiring first. Common fixes are assigning missing `PostProcessData` to the active renderer, enabling `renderPostProcessing` on the representative camera, setting the camera volume layer mask to include the Volume layer, making the Volume global or correctly bounded, disabling legacy PPv2 during URP validation, and recreating an empty/broken VolumeProfile. + +Only after the wiring is valid, tune in bounded steps and capture again after each meaningful pass: + +- Start with source PPv2 color grading values where available, then adjust around them rather than replacing them with arbitrary extremes. +- Use `ColorAdjustments.postExposure`, contrast, saturation, `Tonemapping`, `LiftGammaGain`, Bloom threshold/intensity, ambient sky color/intensity, URP additional-light count, shadow distance, and cascades as the first tuning surfaces. +- Prefer moderate persistent values for the final saved state. Very high exposure, very high ambient, or very high bloom can be used as diagnostics, but if they are required to see the scene, report missing GI/bake parity as partial. +- If the source scene relied on Enlighten realtime GI or baked bounce light, exposure balancing can approximate the look, but it does not replace a URP bake. Report the lighting state honestly. + +After tuning, save assets and the active scene, re-query the VolumeProfile, camera data, ambient settings, and URP asset, then capture again. Do not claim visual parity if the final report still asks the user to "balance exposure", "fix brightness", or "tune lighting" as an ordinary required next step. + +## Execution Regression Checklist + +Use this checklist during actual migration, repair, or final validation. Treat each row as a positive execution invariant: verify the saved state, repair when feasible, and report partial/manual status when evidence is missing. + +| Area | Verify / repair / report | +| --- | --- | +| Phase continuity | Resume from the first incomplete item after package install, compilation, domain reload, or tool interruption. A generic migration can validly finish one phase per turn. | +| Pipeline and Quality | Re-read Graphics settings and each relevant Quality level after saving. Confirm the URP asset and renderer are active before claiming setup completion. | +| Materials | Snapshot source material data before conversion, then restore `_BaseMap`, `_BaseColor`, texture scale/offset, normal, metallic/specular, emission, alpha cutoff, and surface/blend intent after shader assignment. | +| Particles and effects | Preserve transparent/additive behavior for particle, fog, smoke, steam, decal, VFX, additive, and transparent materials. Prefer URP particle/effect shaders where appropriate. | +| Foliage | Validate grass, trees, terrain details, SpeedTree, billboards, leaf cards, alpha clipping, two-sided rendering, normal/detail maps, wind, and billboard behavior before marking material conversion complete. | +| PPv2 to URP Volumes | Save persistent URP `VolumeProfile` override components, wire a scene `Volume.sharedProfile` to the new profile, and disable legacy PPv2 components for URP visual validation unless intentionally preserved for comparison. | +| PPv2 effect parity | Map common effects such as Bloom, Color Adjustments/Tonemapping, Vignette, and Depth Of Field when present. Report PPv2 SSR as unsupported/manual unless a validated URP/custom replacement exists. Handle AO through renderer SSAO where feasible. | +| Lighting and probes | Open the representative scene, verify saved Lighting Settings and Lighting Data references, clear stale Built-in data when needed, rebake or resume baking under URP when feasible, and refresh reflection probes with saved evidence. | +| Reflection settings | For reflection-probe-heavy scenes, verify URP reflection probe blending and box projection settings or report reflections as partial/manual. | +| Renderer and Console | Re-query the URP renderer list/default index and current Console output after assignment. Repair renderer, shader, and render-pipeline errors, or classify unrelated errors explicitly. | +| Exposure balance | When captures are too dark, blown out, or high contrast, first verify URP Volume/camera/renderer wiring, then perform a bounded tuning pass and save/re-query final values. | +| Final wording | Start with `Partial migration` when any material category, PPv2/URP Volume, baked lighting, reflection probe, renderer feature, custom shader, foliage behavior, visual-parity item, or Console issue remains unresolved. | + +## Final Status Pattern + +Use wording that matches saved-state evidence: + +- `Complete`: every success gate passed. +- `Partial migration`: URP setup/material conversion worked, but PPv2, lighting/probes, Console, renderer, or saved-state verification remains incomplete. +- `Manual follow-up`: unsupported features such as PPv2 SSR, complex custom shaders, `GrabPass`, replacement shaders, or package-owned render code remain. + +If any required gate is partial, do not start the final answer with "successfully migrated", "complete", or "visual parity preserved". If `Unity.GetConsoleLogs` still returns errors, either repair them or list them under incomplete/unrelated findings; do not say "no errors remain" while errors are present. A final answer that contains required routine next steps such as rebaking lighting, refreshing probes, or validating scene capture is a partial migration report. + +Before using `Complete`, run or reason from a saved-state gate that checks the actual serialized files/components, not only tool intentions: + +- URP profile contains the mapped source effects, including `DepthOfField` when the old PPv2 profile had it. +- Scene URP `Volume.sharedProfile` references the new profile GUID. +- Legacy PPv2 `PostProcessVolume` and `PostProcessLayer` components are disabled. +- Active scene no longer references the old Lighting Settings GUID or a non-zero old Lighting Data asset, unless lighting is explicitly reported as partial. +- URP asset reflection probe blending and box projection are enabled for reflection-probe-heavy scenes, or reflections are reported as partial/manual. +- SSAO/renderer-feature work is complete or PPv2 Ambient Occlusion is reported as manual/partial. PPv2 Screen Space Reflections are reported as unsupported/manual unless replaced by a validated URP/custom equivalent. + +If any saved-state gate fails, the final answer should start with `Partial migration` and list `Complete`, `Incomplete`, and `Manual follow-up` items. Do not say the project is "fully functional on URP" while any gate is false. diff --git a/skills/migrate-birp-to-urp/references/migration-workflow.md b/skills/migrate-birp-to-urp/references/migration-workflow.md new file mode 100644 index 0000000..1e00697 --- /dev/null +++ b/skills/migrate-birp-to-urp/references/migration-workflow.md @@ -0,0 +1,265 @@ +# Built-in to URP Migration Workflow + +Use this workflow to follow the official Unity migration flow and identify when to slow down, branch, or stop. + +## Table of Contents + +- [Official References](#official-references) +- [Safety First](#safety-first) +- [Project Preparation](#project-preparation) +- [Phased Migration Model](#phased-migration-model) +- [Standard 3D Path: Built-in Render Pipeline to URP](#standard-3d-path-built-in-render-pipeline-to-urp) +- [What Each Converter Does](#what-each-converter-does) +- [Targeted Material-Only Path](#targeted-material-only-path) +- [2D Path: Built-in Render Pipeline 2D to URP 2D](#2d-path-built-in-render-pipeline-2d-to-urp-2d) +- [2D Lighting Note](#2d-lighting-note) +- [Baked Lighting, Lightmaps, And Reflection Probes](#baked-lighting-lightmaps-and-reflection-probes) +- [Manual Follow-Up Hot Spots](#manual-follow-up-hot-spots) +- [When To Stop And Escalate](#when-to-stop-and-escalate) + +## Official References + +- [Upgrading from the Built-in Render Pipeline to URP](https://docs.unity3d.com/Manual/urp/upgrading-from-birp.html) +- [Convert assets using the Render Pipeline Converter](https://docs.unity3d.com/Manual/urp/features/rp-converter.html) +- [Upgrade material assets to URP or HDRP](https://docs.unity3d.com/Manual/upgrade-material.html) +- [URP asset](https://docs.unity3d.com/Manual/urp/urp-asset-and-renderer.html) +- [Prepare and upgrade sprites for 2D lighting in URP](https://docs.unity3d.com/Manual/urp/PrepShader.html) +- [Set up a render pipeline](https://docs.unity3d.com/Manual/render-pipelines-set-up.html) +- [Update believable visuals in URP and HDRP](https://docs.unity3d.com/Manual/BestPracticeMakingBelievableVisuals0.html) + +## Safety First + +- Render Pipeline Converter changes are one-way. Run conversion only after the user has confirmed a backup, branch, or rollback point. +- Installing URP and assigning a URP asset can also leave the project in a partial magenta state before materials are converted. If rollback safety is not confirmed, stop before package install or Graphics/Quality assignment, not after. +- If the user has confirmed rollback safety or says this is a disposable copy, treat that confirmation as covering the full migration pass. Do not ask again before each converter unless a new risky decision appears. +- Inspect warnings before and after conversion instead of treating converter output as success by default. +- If the project is also moving to a new Unity version, the safer order is: engine upgrade first, render-pipeline migration second. + +## Project Preparation + +Before conversion: + +1. Inspect whether the project is truly still on Built-in or already partially on URP. +2. Identify likely migration hot spots: + - custom shaders + - Asset Store rendering packages + - Post-processing Stack v2 + - image effects or camera callbacks + - replacement shaders or special camera rendering paths + - multiple quality levels with different graphics expectations + - baked lighting, lightmaps, light probes, and reflection probes +3. If rendering plug-ins or packages are clearly Built-in-specific, do not assume they will survive the migration unchanged. + +Do this even when the user gives a short prompt like "upgrade this project to URP." The skill, not the user, is responsible for discovering these common migration surfaces. + +## Phased Migration Model + +Use phases for real project upgrades. A generic "migrate this project to URP" prompt starts or resumes the next phase; it does not require finishing every fragile subsystem in one response. + +| Phase | Goal | Completion gate | +| --- | --- | --- | +| Phase 0: Inspect and plan | Detect pipeline state, representative scene, rollback safety, PPv2, materials, Quality levels, lighting/probes, custom render code | Findings are reported and rollback safety is confirmed before mutation | +| Phase 1: URP setup and materials | Install/reuse URP, create/assign URP asset/renderer, assign Graphics/Quality, convert supported opaque and particle/effect materials | Saved Graphics/Quality and material shader assignments verify correctly | +| Phase 2: Post-processing and cameras | Convert PPv2 to persistent URP Volumes/profiles, wire cameras, disable legacy PPv2 for validation, classify unsupported PPv2 effects | Saved URP profile has non-null expected overrides, scene references it, legacy PPv2 is disabled or clearly preserved for comparison | +| Phase 3: Lighting and probes | Resolve stale Built-in lighting data, configure URP lighting/probe settings, rebake/refresh where feasible | Saved scene lighting/probe state is verified, or the phase is explicitly partial/blocked | +| Phase 4: Final validation | Save/reload/re-query, capture representative scenes, check Console, report complete/incomplete/manual items | Every success gate passes before using "complete" | + +At the end of each phase, report: + +- `Completed`: saved-state evidence for that phase. +- `Incomplete`: specific failed checks and whether they are repairable. +- `Manual follow-up`: unsupported or intentionally deferred items. +- `Next phase`: the exact phase to run next. + +Continue into the next phase only when Unity remains stable, the next phase does not introduce a new costly/risky operation, and the user has already authorized that scope. Prefer a clear phase boundary over a rushed final answer. + +## Standard 3D Path: Built-in Render Pipeline to URP + +Use this path for normal 3D Built-in projects moving to URP. + +For a generic "migrate/upgrade to URP" request, treat the following as part of the standard phased path unless inspection proves they are absent: URP asset/renderer setup, Graphics and Quality assignment, material conversion, particle/VFX material verification, PPv2-to-URP Volume verification, baked-lighting/lightmap/probe verification, representative scene capture, saved-state re-query, and a final complete/incomplete/manual report. + +Do not downgrade common migration surfaces into manual follow-up just because the user used a short prompt. Discover PPv2, stale baked lighting, lightmaps, reflection probes, particle materials, and Quality-level assignments. A generic migration can be called complete only after these surfaces are either verified in saved project state or precisely reported as unsupported/manual edge cases. Until then, report the current phase as complete or partial instead of reporting the whole migration as complete. + +Before step 1, confirm rollback safety. If no backup, branch, archive, or disposable copy is confirmed, do not install URP or assign a URP asset. Explain that those steps can immediately make supported Built-in materials render magenta until material conversion runs. + +Before and after pipeline assignment, deliberately open or inspect the representative scene(s). Do not infer that baked lighting, PPv2, reflection probes, or lightmaps are absent from a default/test scene. + +1. Ensure URP is installed. + - Package installation is a phase boundary. It may trigger package refresh, compilation, and domain reload. + - Do not rely on a single `PackageManager.Client.Add` tool call to complete the rest of the migration. If the chat/tool call stops or returns `null`, wait for Unity to finish compiling, then resume by inspecting the partial project state. + - If `Packages/manifest.json` already contains `com.unity.render-pipelines.universal`, do not reinstall URP; continue from asset setup and validation. +2. Create the URP asset and renderer asset if they do not already exist. +3. Assign the URP asset in Graphics settings. Assign it in relevant Quality levels as well. + - If the Console reports "Default Renderer is missing", do not stop at setup. Re-query the saved URP asset's renderer list/default index, validate a fresh scene/camera render after save/reload, and either repair the renderer asset or mark the migration partial. +4. Open `Window > Rendering > Render Pipeline Converter`. +5. Select `Built-in Render Pipeline to URP`. +6. Initialize converters and inspect the result set. +7. Run the converters that apply to the project: + - `Rendering Settings` + - `Material Upgrade` + - `Animation Clip Converter` + - `Read-only Material Converter` + - `Post-processing Stack v2 Converter` +8. Re-open representative scenes and validate before doing broad cleanup. +9. Verify saved assets after conversion. Do not rely only on converter or command logs. + - If the migration changed scene objects, camera components, PPv2 enable states, active Lighting Settings, or reflection probes, save the open scene as well as project assets. `AssetDatabase.SaveAssets()` does not prove scene changes persisted. +10. For Quality levels, verify saved `customRenderPipeline` entries or re-query `QualitySettings.GetRenderPipelineAssetAt(i)`. A tool log that says Quality levels were assigned is not enough. +11. If a script must assign Quality-level URP assets, follow URP's converter pattern: cache the current quality level, call `QualitySettings.SetQualityLevel(index)` for each target level, set `QualitySettings.renderPipeline = urpAsset`, restore the original level, save, and verify the saved `customRenderPipeline` entries. Do not use guessed APIs such as `QualitySettings.SetRenderPipelineAssetAt`. +12. If representative scenes use baked lighting, verify the baked-lighting state separately. Do not treat a clean compile or material conversion as proof that lightmaps and probes look correct in URP. +13. Before the final response, run a success gate: + - Saved Graphics and relevant Quality levels point at the intended URP asset. + - The saved URP asset has a valid default renderer and current validation is not blocked by renderer errors. + - Saved material assets, including particles/fog/smoke/VFX materials, use URP-compatible shaders or are listed as unresolved custom/package shader cases. Materials that had source textures/colors still have mapped URP textures/colors; white/gray untextured output is not a successful material conversion. + - If PPv2 existed, the saved URP Volume profile has persistent non-null `components` entries and the scene references the URP `Volume`; `components: []` means incomplete. + - If baked/mixed lighting, Enlighten/realtime-GI settings, Lighting Data, lightmaps, light probes, or reflection probes existed, lighting/probe repair has been attempted or the migration is explicitly partial because that phase is still running/incomplete. + - If a new URP `.lighting` asset was created, the saved scene's `m_LightingSettings` reference points to that asset after save/reload. Creating the asset and calling `Lightmapping.lightingSettings = target` is not sufficient evidence. + - A representative scene was inspected or captured after saving/reloading. + - If the representative capture is materially too dark, blown out, or high contrast, post-processing wiring and exposure/tonemapping/ambient balance have been verified and adjusted, or visual parity is explicitly partial. + - The final status wording matches the gate result. Say "complete" only when every required item passes. Say "partial" when URP setup/material conversion succeeded but PPv2, baked lighting, probes, or saved-state validation still need repair. + +If only Phase 1 is complete, say "Phase 1 complete" and list Phase 2/3/4 as next work. Do not label the whole migration complete from Phase 1 evidence. + +## What Each Converter Does + +- `Rendering Settings`: creates URP assets and maps Built-in settings to URP equivalents. +- `Material Upgrade`: converts supported Built-in materials. It does not solve custom shader compatibility. +- Particle and VFX materials need explicit verification. If they still point at built-in particle shaders or built-in shader IDs after conversion, migrate them to URP particle shaders such as `Universal Render Pipeline/Particles/Unlit` where appropriate. +- Do not blindly force particle, fog, smoke, steam, decal, additive, VFX, or transparent effect materials to `Universal Render Pipeline/Lit`. Preserve transparent/additive behavior where possible, and validate off-screen/inactive particle systems by asset inspection rather than only by the current camera. +- `Animation Clip Converter`: runs after material conversion and helps when animation clips affect material properties or Post-processing Stack v2 properties. +- `Read-only Material Converter`: handles built-in read-only materials such as `Default-Diffuse` and can take longer because it indexes the project. +- `Post-processing Stack v2 Converter`: converts PPv2 volumes, profiles, and related camera data to URP equivalents and can also take longer because it indexes the project. + +## Post-Processing Verification + +PPv2 conversion is successful only if the saved project state proves it. + +After any PPv2 migration attempt: + +1. Re-open or re-query the converted scene. +2. Inspect old `PostProcessVolume`, `PostProcessLayer`, and `PostProcessProfile` usage. +3. Inspect the resulting URP `VolumeProfile` asset. A profile with `components: []` is empty and should not be reported as migrated. A profile with `components` entries that point to `{fileID: 0}` is also invalid and should not be reported as migrated. + - If the profile was created by script, `VolumeProfile.Add()` must be paired with `AssetDatabase.AddObjectToAsset(component, profile)` for persistent assets. Mark both the component and profile dirty, save assets, reload it from `AssetDatabase`, and verify the non-null component count persisted. + - If the saved profile contains `{fileID: 0}` entries where Bloom, Tonemapping, Vignette, or Depth Of Field should be, treat that as broken persistence. Repair or recreate the profile before claiming PPv2 parity. +4. Confirm representative cameras have URP camera data and post-processing enabled when the visual target depends on it. +5. Confirm the scene has the intended URP `Volume` setup, or explicitly document that the PPv2 setup still needs manual migration. + - If the scene still serializes only the old PPv2 `sharedProfile` and no URP `Volume` references the new `VolumeProfile`, PPv2 migration is incomplete even if a URP profile asset exists. + - If a scene URP `Volume` references a profile with `components: []`, the scene has empty URP post-processing plumbing, not migrated post-processing. Repair the profile or mark PPv2 as incomplete. +6. If PPv2 effects include Screen Space Reflections, call out that URP has no direct equivalent in the same built-in PPv2 form. Recommend reflection probes, screen-space reflection alternatives, or a custom/third-party solution when required. +7. For common mappable PPv2 effects, create persistent URP overrides instead of leaving a vague manual parity task. Typical mappings include Bloom -> `UnityEngine.Rendering.Universal.Bloom`, Color Grading/exposure -> `ColorAdjustments` plus `Tonemapping`, Vignette -> `Vignette`, and Depth Of Field -> `DepthOfField`. +8. It is acceptable to mark unsupported or structurally different effects as manual follow-up, such as PPv2 Screen Space Reflections or Ambient Occlusion that should be handled through reflection probes, SSAO renderer features, or custom/third-party rendering. +9. If old PPv2 components remain as a reference while a URP Volume is active, disable the old PPv2 layer/component during URP visual validation to avoid double post-processing. Do not delete it until the saved URP replacement has been verified and the user approves cleanup. +10. Do not uninstall PPv2 or remove `PostProcessLayer` / `PostProcessVolume` components until the saved URP replacement has been verified, unless the user explicitly accepts effect loss or manual follow-up. + +If a command log says effects were added but the saved profile is empty, report the result as a failed or incomplete post-processing migration and do not claim success. If legacy PPv2 is still active while an empty URP profile exists, do not claim URP post-processing is active; state that the old PPv2 setup is preserved but the URP replacement is incomplete. + +## Material And Particle Verification + +After material conversion: + +1. Re-query material shader assignments on disk or through `AssetDatabase`. +2. Confirm standard opaque materials use URP-compatible shaders, usually `Universal Render Pipeline/Lit`. +3. Confirm particle, fog, smoke, steam, decal, VFX, and transparent effect materials are not left on Built-in particle shaders. +4. Do not rely only on "no magenta in the current camera" as proof. Some particle systems may be off-screen, inactive, or visually subtle. +5. If a material is custom, package-owned, or effect-heavy, classify it as manual follow-up instead of forcing it to URP Lit. +6. For vegetation, grass, tree, terrain-detail, SpeedTree, billboard, leaf-card, and wind-driven materials, verify more than the shader name. Preserve alpha cutoff/clipping, face/culling intent, textures, tint, normals, and expected wind/billboard behavior where possible. If foliage becomes solid opaque cards, static when it was wind-driven, or visually loses its specialized behavior, report the foliage set as partial/manual instead of calling material conversion complete. +7. For any converter or manual shader-assignment fallback, prove source material data survived from a pre-conversion snapshot. Cache `_MainTex`, `_Color`, normal, metallic/specular, emission, alpha/cutoff, texture tiling/offset, and source shader before conversion, then verify representative converted assets have non-null `_BaseMap`/expected textures and non-default color values when the source did. +8. Do not run a "repair" that copies `_MainTex` to `_BaseMap` after conversion unless the value comes from the pre-conversion snapshot. Once a shader has changed, `_MainTex` may already be null/default; copying it later can silently preserve the broken state. +9. Do not edit immutable package materials under `Packages/` or `Library/PackageCache`. If those assets appear in the conversion set, classify them as package-owned/read-only and use a local material copy or scoped custom replacement instead. +10. If a representative capture shows broad white/gray untextured objects after conversion, or if snapshot comparison shows source materials had albedo textures but converted materials have null `_BaseMap`, treat the material phase as incomplete even if the materials are no longer magenta. + +If rollback safety was already confirmed, continue repairing ordinary material, renderer, PPv2, lighting, or probe failures instead of asking the user whether to continue. Ask only when a new destructive cleanup choice, unsupported custom shader strategy, long-running phase boundary, or repeated validation failure requires the user's decision. + +## Partial Migration Resume Checklist + +If a migration stops after installing URP, after a domain reload, after a phase boundary, or after a long-running bake: + +1. Re-check whether URP is now installed in `Packages/manifest.json`. +2. Check whether a URP asset and renderer asset exist and are valid. +3. Verify Graphics settings and every relevant Quality level point to the intended URP asset. +4. Re-query material shader assignments, including particle/fog/smoke/VFX materials. If supported Built-in shader GUIDs or names still dominate after URP is assigned, material conversion is the immediate next incomplete phase; do not stop at "URP setup complete" when the scene is still magenta. +5. Inspect PPv2 state and URP Volume profiles. Empty `components: []` profiles are incomplete. +6. Inspect baked lighting/lightmap state. If old lightmaps blow out the scene, preserve them as reference, clear active baked data, rebake under URP, and refresh reflection probes. + - If a new URP-compatible Lighting Settings asset exists, verify the saved scene actually references it. A scene that still references the old Enlighten/realtime-GI `.lighting` asset is not fully repaired. + - If old `LightingData.asset` remains assigned and old reflection-probe EXRs are unchanged, do not say lighting or probes were refreshed. Report them as preserved reference data or partial until a URP bake/probe refresh completes. +7. If a previous run ended at "would you like me to continue?" but rollback safety was already confirmed, resume from the first incomplete item instead of restarting or asking again. +8. Capture a representative scene and report exactly which items are complete, incomplete, or manual follow-up. +9. Name the next phase instead of restarting. For example: "Resume at Phase 2: Post-processing and cameras" or "Resume at Phase 3: Lighting and probes." + +## Targeted Material-Only Path + +Use this only when the user wants selected materials converted instead of a full migration. + +1. Confirm the project is already on URP. +2. Select the Built-in materials in the Project window. +3. Use `Edit > Rendering > Materials > Convert Selected Built-in Materials to URP`. +4. If a material still errors in the Inspector or stays magenta, treat it as a shader triage case rather than retrying blindly. +5. Validate one or two representative scenes after conversion because selected material upgrades can still create visual drift. + +## 2D Path: Built-in Render Pipeline 2D to URP 2D + +Use this path when the project is primarily 2D and the target is URP 2D. + +1. Ensure URP is installed. +2. Create and assign a 2D Renderer asset. +3. Open `Window > Rendering > Render Pipeline Converter`. +4. Select `Built-in Render Pipeline 2D to URP 2D`. +5. Use `Material and Material Reference Upgrade`. +6. If the user wants 2D lighting, verify that sprites and materials are using URP-compatible lit materials after conversion. + +## 2D Lighting Note + +For 2D lighting in URP, Unity can assign `Sprite-Lit-Default` when sprites are dragged into the scene. Existing project materials still need to be upgraded if they should react to 2D lights. + +## Baked Lighting, Lightmaps, And Reflection Probes + +Baked lighting does not become visually correct just because the project compiles after URP setup. + +For scenes with baked or mixed lighting: + +1. Preserve existing Lighting Data, lightmaps, light probes, and reflection probes as reference data until visual parity is accepted. +2. Inventory `Lightmapping.lightingSettings`, scene `LightmapSettings`, baked/mixed lights, light probes, reflection probes, and the scene's Lighting Data asset. +3. Check whether the migrated scene is overexposed or too dark because both old PPv2 and new URP Volume effects are active. For URP validation, disable legacy PPv2 components/layers but do not delete them. +4. Review URP Volume exposure, tonemapping, bloom, and color adjustments before changing light intensities. +5. If the scene is still too dark, blown out, or high contrast after PPv2/URP Volume migration, run an exposure-balance check before the final report. Verify renderer `PostProcessData`, camera `renderPostProcessing`, `volumeLayerMask`, active Volume weight/priority/layer, and non-empty saved Volume overrides before changing values. Then tune exposure, tonemapping, lift/gamma/gain, bloom, ambient fill, additional-light limits, and shadows in bounded steps with captures between attempts. +6. If clearing baked data makes the scene stop being blown out, treat the previous lightmaps/Lighting Data as stale active data for URP. Keep them as reference/rollback evidence, but do not tune final URP lighting against them. +7. If parity matters, clear/rebake lighting under URP and refresh reflection probes after URP assets, renderer features, volumes, and quality settings are finalized when the user has permitted changes. If the bake is too long or interrupted, report a partial migration and resume from this phase later rather than claiming completion. +8. When assigning new URP-compatible `LightingSettings`, remember that the active lighting-settings reference is scene state. Mark the settings and active scene dirty, save the scene, reload or re-query, and verify the saved scene references the intended asset before reporting it as active. `AssetDatabase.SaveAssets()` alone does not prove the scene's `m_LightingSettings` reference changed. +9. If the saved scene still references the old Enlighten/realtime-GI `.lighting` asset, the lighting phase is incomplete even if a new URP lighting settings asset exists on disk. +10. Report baked lighting as one of: preserved as reference, stale active bake cleared, visually checked and acceptable, rebaked/refreshed under URP, exposure-balanced with remaining bake risk, or manual follow-up. Do not report it as fully migrated from compile success alone. +11. Do not claim visual parity is preserved while old Built-in `LightingData.asset` and old reflection-probe EXRs remain active without a URP rebake/probe refresh or explicit saved-state visual verification. +12. Do not claim reflection probes were refreshed from intent alone. Verify successful probe-render output, changed probe assets, or a saved-state visual check. Unchanged old EXR files mean probe refresh is still partial/manual. + +## Manual Follow-Up Hot Spots + +Even when the converter succeeds, these areas often still need attention: + +- custom shaders and package shaders +- PPv2 and fullscreen image effects +- replacement-shader camera workflows +- baked lighting, lightmaps, light probes, reflection probes, and shadow tuning +- quality-level assignments +- 2D lit materials and sprite workflows +- visual parity around tone mapping, exposure, and light falloff + +For real project upgrades, explicitly verify: + +- material shader assignments on disk +- particle/VFX material shader assignments +- Graphics settings and each relevant Quality level +- URP renderer features that were added +- persistent URP Volume profile components +- representative scene camera data and active legacy post-processing components +- baked-lighting state and whether a URP rebake/probe refresh is still required + +## When To Stop And Escalate + +Stop and explain the blocker instead of guessing when: + +- custom shaders drive important visuals +- complex shader situations such as surface shaders, `GrabPass`, replacement shaders, or fullscreen camera callbacks are involved +- package-owned shaders are involved and the safe fix is unclear +- converter results contain unresolved warnings or failures +- baked lighting is important to the scene and no URP bake/visual parity check has been performed; for a generic full migration, this means the migration is partial, not complete +- the project mixes 2D and 3D requirements and the target renderer choice is unclear +- the user has not confirmed rollback safety for irreversible conversion diff --git a/skills/migrate-birp-to-urp/references/quality-settings-map.md b/skills/migrate-birp-to-urp/references/quality-settings-map.md new file mode 100644 index 0000000..e1f6b99 --- /dev/null +++ b/skills/migrate-birp-to-urp/references/quality-settings-map.md @@ -0,0 +1,69 @@ +# Quality Settings Map + +Use this map to explain where common Built-in quality settings end up after URP migration. + +## Official References + +- [Convert Built-in quality settings to URP](https://docs.unity3d.com/Manual/urp/birp-onboarding/quality-presets.html) +- [Find Built-in quality settings in URP](https://docs.unity3d.com/Manual/urp/birp-onboarding/quality-settings-location.html) +- [Universal Render Pipeline asset](https://docs.unity3d.com/Manual/urp/urp-asset-and-renderer.html) +- [Change how lights fade to match the Built-In Render Pipeline](https://docs.unity3d.com/Manual/urp/birp-onboarding/birp-light-falloff-in-urp.html) + +## Common Mappings + +| Built-in concept | URP location | +| --- | --- | +| Render Pipeline Asset | `Project Settings > Quality > Rendering > Render Pipeline Asset` | +| Default pipeline assignment | `Project Settings > Graphics > Render Pipeline Asset` | +| MSAA | `URP Asset > Quality > Anti-aliasing (MSAA)` | +| Camera anti-aliasing | `Camera > Rendering > Anti-aliasing` | +| Main light shadows | `URP Asset > Lighting > Main Light > Cast Shadows` | +| Additional light shadows | `URP Asset > Lighting > Additional Lights > Cast Shadows` | +| Main light shadow resolution | `URP Asset > Lighting > Main Light > Shadow Resolution` | +| Additional light shadow atlas and tiers | `URP Asset > Lighting > Additional Lights > Shadow Atlas Resolution` and `Shadow Resolution Tiers` | +| Shadow distance | `URP Asset > Shadows > Max Distance` | +| Shadow cascades | `URP Asset > Shadows > Cascade Count` and split controls | +| HDR | `URP Asset > Quality > HDR` | +| Depth texture | `URP Asset > Rendering > Depth Texture` | +| Opaque texture | `URP Asset > Rendering > Opaque Texture` | +| Real-time reflection probes | `Project Settings > Quality > Rendering > Real-time Reflection Probes` | +| Resolution scaling | `Project Settings > Quality > Rendering > Resolution Scaling Fixed DPI Factor` and `URP Asset > Quality > Render Scale` | +| LOD cross fade | `URP Asset > Quality > LOD Cross Fade` | +| Baked GI / lightmaps | Scene Lighting Settings, `LightmapSettings`, Lighting Data asset; verify visually and rebake under URP when parity matters | +| Reflection probe bake data | Scene reflection probes; refresh/rebake after URP lighting and post-processing settings are finalized | + +## Guidance + +- Many settings that used to live only in Built-in quality settings are split between Project Settings and the URP asset. +- Treat the official URP values as a starting point, not a promise of identical performance or visuals. +- After migration, verify the user's important quality levels explicitly instead of assuming Low and High presets still behave the same way. +- A Quality level with no custom render pipeline asset may fall back to Graphics settings. That can be acceptable, but state this explicitly instead of claiming that Quality was fully assigned. +- For full-project migrations, prefer assigning the intended URP asset to all relevant runtime Quality levels unless the project deliberately relies on Graphics settings fallback. +- When validating on disk, look for each Quality level's saved `customRenderPipeline` entry. `{fileID: 0}` means that level has no explicit pipeline asset, even if Graphics Settings has a URP asset. +- If using scripts or serialized project settings to assign Quality assets, verify by re-querying after saving. Do not trust a guessed serialized field name or a command log that says assignment happened. +- In Unity versions without a direct per-index setter, use the converter-style assignment pattern: store `QualitySettings.GetQualityLevel()`, call `QualitySettings.SetQualityLevel(index)`, set `QualitySettings.renderPipeline = urpAsset`, repeat for each target level, restore the original level, then verify saved `customRenderPipeline` references. Do not invent APIs such as `QualitySettings.SetRenderPipelineAssetAt`. +- Light falloff and overall scene brightness can differ between Built-in and URP. Treat that as a tuning step, not automatically a failed migration. +- Baked GI, lightmaps, light probes, and reflection probes are visual-parity risks. Preserve existing baked data as reference, but recommend a URP rebake/refresh when the scene depends on baked lighting. +- If a migrated scene is extremely bright or washed out, check for double post-processing first: retained PPv2 components/layers plus an active URP Volume can stack exposure, bloom, tonemapping, or color grading during validation. +- If a migrated scene is too dark, too flat, or still high contrast after material and PPv2 migration, treat exposure balance as part of visual validation. Verify URP post-processing wiring first, then tune Volume exposure/tonemapping/lift-gamma-gain, ambient fill, additional-light limits, and shadow settings with representative captures. +- If clearing baked data makes the scene stop being washed out, classify the old Built-in lightmaps or Lighting Data as stale active data. Preserve them as reference, but clear active baked data and rebake under URP before final tuning. +- If a new Lighting Settings asset is created for URP baked GI, verify the saved scene references it after saving/reloading. A `.lighting` asset with baked GI enabled is not active if the scene still points to the old Enlighten/realtime-GI settings asset. +- Do not claim baked lighting parity from compile success, material conversion, or URP asset assignment alone. + +## Validation Checklist + +After migration or troubleshooting: + +1. Confirm the correct URP asset is assigned in Graphics settings. +2. Confirm each relevant Quality level points to the expected URP asset. + - If it does not, report whether this is intentional fallback behavior or an incomplete migration item. +3. Compare shadow distance, cascades, and shadow resolution against the user's expected look. +4. If fullscreen effects depend on them, verify whether depth texture and opaque texture are enabled. +5. If the user reports blurrier or sharper visuals, review MSAA, render scale, and camera anti-aliasing. +6. If lighting looks "off," review light falloff, post-processing, active PPv2/URP volumes, and ambient/environment settings before rewriting content. +7. For baked-lighting scenes, inspect Lighting Settings, `LightmapSettings`, baked/mixed lights, light probes, reflection probes, and Lighting Data assets. +8. If old lightmaps are causing severe overexposure, clear active baked data before final URP validation. +9. If visual parity matters, rebake lighting under URP and refresh reflection probes after URP renderer, Quality, and Volume settings are finalized. +10. After assigning new Lighting Settings, save the scene and verify the scene reference, not only the settings asset contents. +11. If the final capture is too dark or too bright, verify renderer `PostProcessData`, camera `renderPostProcessing`, camera volume layer mask, active Volume weight/priority/layer, and saved VolumeProfile overrides before changing lighting. +12. When exposure balance is required, prefer bounded changes to `ColorAdjustments.postExposure`, contrast, saturation, `Tonemapping`, `LiftGammaGain`, Bloom, ambient sky color/intensity, additional-light count, and shadow settings. If extreme values are only compensating for missing GI or an unfinished bake, report lighting parity as partial.