diff --git a/Assets/NiloToonURP/Runtime/NiloToonPerCharacterRenderController.cs b/Assets/NiloToonURP/Runtime/NiloToonPerCharacterRenderController.cs index fbf69a2cd..122144b1c 100644 --- a/Assets/NiloToonURP/Runtime/NiloToonPerCharacterRenderController.cs +++ b/Assets/NiloToonURP/Runtime/NiloToonPerCharacterRenderController.cs @@ -1109,10 +1109,20 @@ namespace NiloToon.NiloToonURP if (regenerateSmoothedNormalInUV8) { - NiloBakeSmoothNormalTSToMeshUv8.GenGOSmoothedNormalToUV8(gameObject); + NiloBakeSmoothNormalTSToMeshUv8.GenGOSmoothedNormalToUV8(gameObject); } - + AutoFillInMissingProperties(); + + // Streamingle: 이미 등록된 renderer에도 updateWhenOffscreen 적용 (씬에 미리 있던 캐릭터 대응). + // RefillAllRenderers는 새 등록 때만 호출되므로 OnEnable에서 한 번 더 강제. + for (int i = 0; i < allRenderers.Count; i++) + { + if (allRenderers[i] is SkinnedMeshRenderer existingSmr && !existingSmr.updateWhenOffscreen) + { + existingSmr.updateWhenOffscreen = true; + } + } // To support VRMBlendShapeProxy, NiloToon now generates material instances on OnEnable(), which is before VRMBlendShapeProxy's Start() // Note: @@ -1537,7 +1547,15 @@ namespace NiloToon.NiloToonURP // we don't want to add particle/vfx/trail....renderers if(!(renderer is MeshRenderer or SkinnedMeshRenderer)) continue; - + + // Streamingle: Pass.cs의 manual DrawRenderer로 cullResults 우회 시, + // SkinnedMeshRenderer가 화면 밖이면 Unity가 본 매트릭스 갱신을 skip하여 stale pose가 그려질 수 있음. + // updateWhenOffscreen=true 강제. (localBounds는 GetCharacterBoundCenter에 영향 주므로 건드리지 않음) + if (renderer is SkinnedMeshRenderer smr && !smr.updateWhenOffscreen) + { + smr.updateWhenOffscreen = true; + } + var NiloToonPerCharacterRenderControllerFound = renderer.transform.GetComponentInParent(); if(NiloToonPerCharacterRenderControllerFound) { diff --git a/Assets/NiloToonURP/Runtime/RendererFeatures/Passes/NiloToonCharSelfShadowMapRTPass.cs b/Assets/NiloToonURP/Runtime/RendererFeatures/Passes/NiloToonCharSelfShadowMapRTPass.cs index 29f957be0..96c9f44aa 100644 --- a/Assets/NiloToonURP/Runtime/RendererFeatures/Passes/NiloToonCharSelfShadowMapRTPass.cs +++ b/Assets/NiloToonURP/Runtime/RendererFeatures/Passes/NiloToonCharSelfShadowMapRTPass.cs @@ -174,6 +174,31 @@ namespace NiloToon.NiloToonURP List validCharList = new List(); List finalValidCharList = new List(); + // Streamingle: shader → "NiloToonSelfShadowCaster" pass index 캐시. + // Unity 6 RG path는 main camera cullResults 만 쓰므로 카메라가 캐릭터 안 보면 shadow map 비어 그림자 사라짐. + // ExecutePass에서 manual DrawRenderer로 cullResults 우회. shader 별 pass index 조회는 비싸 캐시 필요. + static readonly Dictionary s_shadowCasterPassIndexCache = new Dictionary(); + static readonly ShaderTagId s_lightModeTagId = new ShaderTagId("LightMode"); + static readonly ShaderTagId s_shadowCasterTagId = new ShaderTagId("NiloToonSelfShadowCaster"); + + static int GetShadowCasterPassIndex(Shader shader) + { + if (shader == null) return -1; + if (s_shadowCasterPassIndexCache.TryGetValue(shader, out int cached)) return cached; + int found = -1; + int passCount = shader.passCount; + for (int i = 0; i < passCount; i++) + { + if (shader.FindPassTagValue(i, s_lightModeTagId) == s_shadowCasterTagId) + { + found = i; + break; + } + } + s_shadowCasterPassIndexCache[shader] = found; + return found; + } + // Constructor(will not call on every frame) public NiloToonCharSelfShadowMapRTPass(NiloToonRendererFeatureSettings allSettings) @@ -448,27 +473,15 @@ namespace NiloToon.NiloToonURP // https://docs.microsoft.com/en-us/windows/win32/dxtecharts/common-techniques-to-improve-shadow-depth-maps //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - GeometryUtility.CalculateFrustumPlanes(camera, cameraPlanes); - validCharList.Clear(); - // [1] filter list + // [1] Streamingle: 메인 카메라 frustum cull 제거. (controller bound 기반 필터) + // 캐릭터가 메인 카메라 frustum 밖이어도 ortho box / keyword 가 정상 동작해야 함. + // 실제 cullResults 우회는 ExecutePass의 manual DrawRenderer 가 담당. foreach (var targetChar in NiloToonAllInOneRendererFeature.characterList) { - // if target is not valid, skip it if (targetChar == null) continue; - if (!targetChar.isActiveAndEnabled) continue; // character GameObject not enabled(not rendering) but in list - - // if character bounding sphere is completely not visible in game camera frustum, skip it - var boundRadius = targetChar.GetCharacterBoundRadius(); - var centerPosWS = targetChar.GetCharacterBoundCenter(); - // TODO: this section is not correct, which may incorrectly cull effective shadow caster that is OUTSIDE of main camera frustum - if (!GeometryUtility.TestPlanesAABB(cameraPlanes, new Bounds(centerPosWS, Vector3.one * boundRadius))) - { - continue; - } - - // it is a valid visible char, add to list + if (!targetChar.isActiveAndEnabled) continue; validCharList.Add(targetChar); } @@ -602,43 +615,7 @@ namespace NiloToon.NiloToonURP } #endif - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // set culling for shadow camera -> do culling - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - camera.TryGetCullingParameters(out var cullingParameters); - - // update culling matrix - cullingParameters.cullingMatrix = shadowCamProjectionMatrix * shadowCamViewMatrix; - - // update culling planes - GeometryUtility.CalculateFrustumPlanes(cullingParameters.cullingMatrix, cameraPlanes); - for (int i = 0; i < cameraPlanes.Length; i++) - { - cullingParameters.SetCullingPlane(i, cameraPlanes[i]); - } - - CullingResults cullResults; - - bool terrainExist = false; - if (settings.terrainCrashSafeGuard) - { - terrainExist = Terrain.activeTerrains.Length != 0; - } - if (settings.perfectCullingForShadowCasters && !terrainExist) - { - // use the above new cullResults in DrawRenderers() below, - // so even a renderer is not visible in the perspective of main camera, - // it can still render correctly in shadow camera's perspective due to this new culling - - // (2021-07-14) unity will crash if code running this line and terrain exist in scene - // (2024-03-21) enable this will make VLB's SRP batcher mode flicker randomly, not sure why, should we do something to revert this culling line? - cullResults = context.Cull(ref cullingParameters); // original working code, but will crash if terrain exist - } - else - { - // (2021-07-14) a special temp fix to avoid terrain crashing unity, but will make shadow culling not always correctly if shadow caster is not existing on screen - cullResults = renderingData.cullResults; - } + // Streamingle: cullResults / custom culling 코드 제거. manual DrawRenderer가 cullResults에 의존하지 않으므로 불필요. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Set uniform (before context.DrawRenderers) @@ -708,12 +685,12 @@ namespace NiloToon.NiloToonURP */ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // draw all char renderer using SRP batching (must set all uniforms and executed before draw!) + // Streamingle: cullResults 의존성 완전 제거. context.DrawRenderers + custom culling 모두 제거. + // characterList의 모든 NiloToon 캐릭터를 manual draw로 그려 카메라 frustum 무관하게 shadow map 채움. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ShaderTagId shaderTagId = new ShaderTagId("NiloToonSelfShadowCaster"); - var drawSetting = CreateDrawingSettings(shaderTagId, ref renderingData, SortingCriteria.CommonOpaque); - var filterSetting = new FilteringSettings(RenderQueueRange.opaque); - context.DrawRenderers(cullResults, ref drawSetting, ref filterSetting); // using custom cullResults from shadow camera's perspective, instead of main camera's cull result + DrawNiloToonCharsManuallyLegacy(cmd); + context.ExecuteCommandBuffer(cmd); + cmd.Clear(); // Note: Since we are providing our own _NiloToonSelfShadowWorldToClip to shader for VP transform, // this section is not needed anymore, it will trigger a bug in multi pass mode XR @@ -812,8 +789,7 @@ namespace NiloToon.NiloToonURP // copy and edit of https://docs.unity3d.com/6000.0/Documentation/Manual/urp/render-graph-draw-objects-in-a-pass.html private class PassData { - // Create a field to store the list of objects to draw - public RendererListHandle rendererListHandle; + // Streamingle: RendererListHandle 제거. manual DrawRenderer로 대체됨. public bool shouldRender; public Matrix4x4 _NiloToonSelfShadowWorldToClip; public Vector4 _NiloToonSelfShadowParam; @@ -840,21 +816,10 @@ namespace NiloToon.NiloToonURP UniversalCameraData cameraData = frameContext.Get(); UniversalRenderingData renderingData = frameContext.Get(); UniversalLightData lightData = frameContext.Get(); - - SortingCriteria sortFlags = SortingCriteria.CommonOpaque; //cameraData.defaultOpaqueSortFlags; - RenderQueueRange renderQueueRange = RenderQueueRange.opaque; - FilteringSettings filterSettings = new FilteringSettings(renderQueueRange, ~0); - // Redraw only objects that have their LightMode tag set to "NiloToonSelfShadowCaster" - ShaderTagId shadersToOverride = new ShaderTagId("NiloToonSelfShadowCaster"); - - // Create drawing settings - DrawingSettings drawSettings = RenderingUtils.CreateDrawingSettings(shadersToOverride, renderingData, cameraData, lightData, sortFlags); - - // Create the list of objects to draw - var rendererListParameters = new RendererListParams(renderingData.cullResults, drawSettings, filterSettings); - - + // Streamingle: RendererList 기반 그리기 제거. ExecutePass의 manual DrawRenderer가 + // characterList를 직접 그리므로 cullResults에 의존하는 RendererList는 불필요 + double-draw 방지. + // RendererList CPU 컬링 비용도 절약. // create RT (temp) // Create texture properties that match the screen size @@ -871,21 +836,17 @@ namespace NiloToon.NiloToonURP { shouldRender = true; } - - // Convert the list to a list handle that the render graph system can use - passData.rendererListHandle = renderGraph.CreateRendererList(rendererListParameters); + passData.shouldRender = shouldRender; - + RenderTextureDescriptor renderTextureDescriptor = new RenderTextureDescriptor(shadowMapSize, shadowMapSize, RenderTextureFormat.Shadowmap, 16); // Create a temporary texture TextureHandle shadowMapRT = UniversalRenderer.CreateRenderGraphTexture(renderGraph, renderTextureDescriptor, "_NiloToonCharSelfShadowMapRT", true); - + // Set the render target as the color and depth textures of the active camera texture UniversalResourceData resourceData = frameContext.Get(); - - builder.UseRendererList(passData.rendererListHandle); - + //builder.SetRenderAttachment(resourceData.activeColorTexture, 0); builder.SetRenderAttachmentDepth(shadowMapRT, AccessFlags.Write); @@ -1036,29 +997,15 @@ namespace NiloToon.NiloToonURP // https://docs.microsoft.com/en-us/windows/win32/dxtecharts/common-techniques-to-improve-shadow-depth-maps //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - GeometryUtility.CalculateFrustumPlanes(camera, cameraPlanes); - validCharList.Clear(); - // [1] filter list + // [1] Streamingle: 메인 카메라 frustum cull 제거. (controller bound 기반 필터) + // 캐릭터가 메인 카메라 frustum 밖이어도 ortho box / keyword 가 정상 동작해야 함. + // 실제 cullResults 우회는 ExecutePass의 manual DrawRenderer 가 담당. foreach (var targetChar in NiloToonAllInOneRendererFeature.characterList) { - // if target is not valid, skip it if (targetChar == null) continue; - if (!targetChar.isActiveAndEnabled) - continue; // character GameObject not enabled(not rendering) but in list - - // if character bounding sphere is completely not visible in game camera frustum, skip it - var boundRadius = targetChar.GetCharacterBoundRadius(); - var centerPosWS = targetChar.GetCharacterBoundCenter(); - // TODO: this section is not correct, which may incorrectly cull effective shadow caster that is OUTSIDE of main camera frustum - if (!GeometryUtility.TestPlanesAABB(cameraPlanes, - new Bounds(centerPosWS, Vector3.one * boundRadius))) - { - continue; - } - - // it is a valid visible char, add to list + if (!targetChar.isActiveAndEnabled) continue; validCharList.Add(targetChar); } @@ -1328,10 +1275,67 @@ namespace NiloToon.NiloToonURP cmd.SetGlobalVector("_NiloToonSelfShadowSoftShadowParam", data._NiloToonSelfShadowSoftShadowParam); cmd.SetKeyword(GlobalKeyword.Create(_NILOTOON_RECEIVE_SELF_SHADOW_Keyword), true); - - // Draw the objects in the list - cmd.DrawRendererList(data.rendererListHandle); + + // Streamingle: cullResults 의존성 완전 제거. manual draw만 사용. + // characterList의 모든 NiloToon 캐릭터를 직접 그려 카메라 frustum 무관하게 shadow map 채움. + DrawNiloToonCharsManually(cmd); + } + + static void DrawNiloToonCharsManually(RasterCommandBuffer cmd) + { + var charList = NiloToonAllInOneRendererFeature.characterList; + if (charList == null) return; + for (int c = 0; c < charList.Count; c++) + { + var character = charList[c]; + if (character == null || !character.isActiveAndEnabled) continue; + var renderers = character.allRenderers; + if (renderers == null) continue; + for (int r = 0; r < renderers.Count; r++) + { + var renderer = renderers[r]; + if (renderer == null || !renderer.enabled || !renderer.gameObject.activeInHierarchy) continue; + var sharedMats = renderer.sharedMaterials; + if (sharedMats == null) continue; + for (int subIdx = 0; subIdx < sharedMats.Length; subIdx++) + { + var mat = sharedMats[subIdx]; + if (mat == null) continue; + int passIdx = GetShadowCasterPassIndex(mat.shader); + if (passIdx < 0) continue; + cmd.DrawRenderer(renderer, mat, subIdx, passIdx); + } + } + } } #endif + + static void DrawNiloToonCharsManuallyLegacy(CommandBuffer cmd) + { + var charList = NiloToonAllInOneRendererFeature.characterList; + if (charList == null) return; + for (int c = 0; c < charList.Count; c++) + { + var character = charList[c]; + if (character == null || !character.isActiveAndEnabled) continue; + var renderers = character.allRenderers; + if (renderers == null) continue; + for (int r = 0; r < renderers.Count; r++) + { + var renderer = renderers[r]; + if (renderer == null || !renderer.enabled || !renderer.gameObject.activeInHierarchy) continue; + var sharedMats = renderer.sharedMaterials; + if (sharedMats == null) continue; + for (int subIdx = 0; subIdx < sharedMats.Length; subIdx++) + { + var mat = sharedMats[subIdx]; + if (mat == null) continue; + int passIdx = GetShadowCasterPassIndex(mat.shader); + if (passIdx < 0) continue; + cmd.DrawRenderer(renderer, mat, subIdx, passIdx); + } + } + } + } } } \ No newline at end of file