Загружаем каталог…
Загружаем каталог…
유니티에서 그림자 처리를 직접 진행했던 기록을 위한 글 입니다. 커스텀 2D Shadow 커스텀 2D Light 에서 생성한 빛 객체는 그림자를 적용할 수 없던 반쪽자리 빛으로, 여기에 그림자 또한 동작하도록 구현해보았다. 이 그림자 구현에는 기존 커스텀 빛 관련 스크립트와 셰이더를 일부 이용해서 구현해주었다. 왼쪽의 노란 빛과 사과의 그림자가 커스텀 라이트와 섀도우, 오른쪽의 오렌지가 유니티 자체적인 빛과 그림자이다. 관련 코드 ScriptableRendererFeature 이전 빛 처리에 사용했던 Custom2DLightFeature에 그림자 관련 코드들을 넣어서 업데이트 해주었다. //셰이더에서 사용할 패스별 이름 설정 private readonly ShaderTagId m_OccluderShaderTagId = new ShaderTagId("Custom2DOccluder"); private readonly ShaderTagId m_ShadowExtrusionShaderTagId = new ShaderTagId("Custom2DShadowCaster"); //그림자 텍스처를 연결할 프로퍼티 ID 생성 private static readonly int CustomShadowTextureID = Shader.PropertyToID("_CustomShadowTexture"); 그림자 처리에 사용할 두 종류의 셰이더 패스 태그를 준비하고, 이후 완성된 그림자 텍스쳐를 전역으로 등록하기 위한 프로퍼티 ID를 생성해주었다. 이번 그림자 처리에는 빛과 다르게 한번이 아닌 두번의 렌더링이 필요하며, 그를 위한 두 패스 태그와 최종 그림자 텍스쳐를 외부에서 사용할 수 있도록 프로퍼티 ID를 선언해둔 것이다. private class ShadowPassData { public RendererListHandle occluderRendererList; public RendererListHandle shadowExtrusionRendererList; } 같은 이유로, 두개의 렌더러 리스트가 묶여있는 ShadowPassData를 생성하여, 뒤쪽의 실행 단계에서 두번의 렌더링을 진행할 수 있게 준비한다. //기존 커스텀 빛 생성 때의 RecordRenderGraph 메소드 public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) { //기존 빛 관련 내용들.. //그림자가 그려질 그림자 텍스쳐 생성, 기존 빛의 설정(RGB 색상용의 desc) 사용 TextureHandle shadowTexture = UniversalRenderer.CreateRenderGraphTexture( renderGraph, desc, "_CustomShadowTexture", clear: false, filterMode: FilterMode.Bilinear ); //현재 카메라 설정을 가져옴 RenderTextureDescriptor depthDesc = cameraData.cameraTargetDescriptor; //생성할 텍스쳐를 색상(RGB)기반 대신, Depth/Stencil 기반 텍스쳐로 설정 depthDesc.colorFormat = RenderTextureFormat.Depth; depthDesc.depthBufferBits = 24; depthDesc.msaaSamples = 1; //방금 생성한 depthDesc를 사용하는 스텐실 버퍼용 텍스쳐 TextureHandle shadowDepthTexture = UniversalRenderer.CreateRenderGraphTexture( renderGraph, depthDesc, "_CustomShadowDepthTexture", clear: false, filterMode: FilterMode.Point ); //그림자용 래스터 패스 등록, 다음 파트에서 추가 설명 예정 using (var builder = renderGraph.AddRasterRenderPass<ShadowPassData>("Custom 2D Shadow Pass", out var passData)) } 텍스쳐를 생성하고, 패스를 등록하는 RecordRenderGraph 메소드 내부에 그림자 관련 코드들이 추가되었다. 그림자 처리를 위해 텍스쳐가 두개 생성되었는데, 첫번째로는 기존 빛 텍스쳐 생성에 사용됐던 desc 설정값을 이용한, 색상 처리용 그림자 텍스쳐이다. 이 그림자 텍스쳐에는 최종적으로 실제 그림자들이 그려지게 될 예정이다. 그 다음으로는 새롭게 카메라 설정을 가져와서, 색상 기반 대신 Depth/Stencil 기반의 텍스쳐가 될 수 있도록 설정해주었는데, 실제로는 뎁스는 사용하지 않고, 스텐실만 사용해줄 계획이다. 이렇게 설정된 depthDesc를 이용하여, 두번째 텍스쳐인 스텐실 버퍼용 텍스쳐를 생성해주는데, 이 두번째 텍스쳐는 그림자에서 현재 물체 범위를 제외해주기 위해 사용된다. 빛과 빛을 받는 객체에 대해 그림자 생성을 판단할 때, 가장 간단한 방법은 객체의 영역에서 빛을 받았을 때, 그 영역부터 빛의 방향에 따라서 쭉 그림자를 늘어뜨려 주는것이다. 다만 이렇게 되면 빛을 받은 객체 자신 또한 그림자에 휩싸여서 그림자의 색인 검은 계열의 색으로 물들게 된다. 이러한 현상을 방지하기 위해, 두번째 텍스쳐를 이용해서 현 객체에 맞도록 스텐실 버퍼를 생성해주고, 그림자 텍스쳐를 작업하면서 이 스텐실 버퍼 텍스쳐를 참고해서 현재 객체의 범위에는 그림자가 생성되지 않도록 예외처리를 진행해준다. 렌더패스 등록 //렌더패스 등록 using (var builder = renderGraph.AddRasterRenderPass<ShadowPassData>("Custom 2D Shadow Pass", out var passData)) { //작업이 진행될 각 텍스쳐들을 연결해줌 builder.SetRenderAttachment(shadowTexture, 0, AccessFlags.Write); builder.SetRenderAttachmentDepth(shadowDepthTexture, AccessFlags.Write); //렌더러들의 작업 순서 지정(카메라 기본 정렬 기준을 그대로 사용) SortingCriteria sortingCriteria = cameraData.defaultOpaqueSortFlags; //그림자를 생성할 렌더러 필터 설정 FilteringSettings filteringSettings = new FilteringSettings(RenderQueueRange.all, m_Settings.shadowLayerMask); //Custom2DOccluder 패스 태그를 이용한 DrawingSettings 생성 DrawingSettings occluderDrawSettings = RenderingUtils.CreateDrawingSettings( m_OccluderShaderTagId, renderingData, cameraData, lightData, sortingCriteria ); //설정들을 이용해서 첫번째 렌더러 리스트 생성 RendererListParams occluderParams = new RendererListParams(renderingData.cullResults, occluderDrawSettings, filteringSettings); passData.occluderRendererList = renderGraph.CreateRendererList(occluderParams); //Custom2DShadowCaster 패스 태그를 이용한 DrawingSettings 생성 DrawingSettings extrusionDrawSettings = RenderingUtils.CreateDrawingSettings( m_ShadowExtrusionShaderTagId, renderingData, cameraData, lightData, sortingCriteria ); //설정들을 이용해서 두번째 렌더러 리스트 생성 RendererListParams extrusionParams = new RendererListParams(renderingData.cullResults, extrusionDrawSettings, filteringSettings); passData.shadowExtrusionRendererList = renderGraph.CreateRendererList(extrusionParams); //렌더러 리스트 등록 builder.UseRendererList(passData.occluderRendererList); builder.UseRendererList(passData.shadowExtrusionRendererList); //그림자 텍스쳐 전역 등록 builder.SetGlobalTextureAfterPass(shadowTexture, CustomShadowTextureID); builder.AllowPassCulling(false); //실제 동작 등록, 다음 파트에서 추가 설명 예정 builder.SetRenderFunc((ShadowPassData data, RasterGraphContext context) } 렌더 패스는 커스텀 라이트 때의 흐름과 비슷하게 구성되어있다. 먼저 각 작업별 텍스쳐를 등록해주는데, SetRenderAttachment와 SetRenderAttachmentDepth로 등록 작업에 차이가 있는 것을 확인할 수 있다. 이는 렌더 패스에서 작업된 컬러 텍스쳐와 뎁스 텍스쳐를 의미하는것으로, 그에 맞춰 그림자 텍스쳐와 스텐실 버퍼용 텍스쳐를 각각 등록해주었다. 다음으로는 그림자 생성 필터링 설정과 DrawingSettings을 생성하고, 이런 설정들을 통해 각각 렌더러 리스트를 생성해주었다. 각 텍스쳐와 목표에 맞춰서, 그림자용 렌더러 리스트는 Custom2DShadowCaster 패스 태그를 의미하는 m_ShadowExtrusionShaderTagId를, 스텐실용 렌더러 리스트는 Custom2DOccluder 패스 태그를 의미하는 m_OccluderShaderTagId를 지정해서 생성해주었다. 이렇게 생성된 두 리스트를 각각 패스에 등록해주고, 그림자 텍스쳐를 다른 위치에서도 호출할 수 있게 전역으로 등록해준 다음, 작업이 자동으로 제외되지 않게 AllowPassCulling 설정을 해주며, 실제 동작 파트로 넘어간다. builder.SetRenderFunc((ShadowPassData data, RasterGraphContext context) => { //렌더 타겟 전부 초기화(색상은 블랙, 깊이 버퍼는 1, 스텐실 버퍼는 0) context.cmd.ClearRenderTarget(RTClearFlags.All, Color.black, 1.0f, 0); //스텐실 버퍼 기록 context.cmd.DrawRendererList(data.occluderRendererList); //스텐실 버퍼 영역 확인 후, 영역 외에 그림자 영역 지정 context.cmd.DrawRendererList(data.shadowExtrusionRendererList); }); 작업은 총 3단계로, 렌더 타겟을 전부 깔끔하게 초기화 하며 시작한다. 초기화 후, 두번째 작업은 먼저 스텐실 버퍼와 관련된 작업으로, 간단하게 요약하면 오브젝트 범위에 맞춰서 스텐실 버퍼에 데이터를 등록해준다. 마지막인 세번째 작업 또한 간단하게 요약하면, 스텐실 버퍼를 확인해서 그림자 텍스쳐에 그림자 영역을 그려주도록 되어있다. 각 동작들은 셰이더쪽 코드를 통해 동작하기 때문에 자세한건 각 파트에서 설명할 예정이다. 커스텀(그림자) 셰이더 이번 셰이더는 기존 빛 셰이더와 다르게, 두개의 패스로 구성되어있다. 셰이더 공용 코드 Shader "Universal Render Pipeline/2D/Custom2DShadow" { //입력받는 설정값 Properties { //그림자로 이용할 텍스쳐, 그림자의 길이, 컷오프 알파값 기준 _MainTex ("Sprite Texture", 2D) = "white" {} _ShadowLength ("Shadow Extrusion Length", Float) = 5.0 _Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5 } SubShader { Tags { "Queue" = "Transparent" "RenderType" = "Transparent" "RenderPipeline" = "UniversalPipeline" } //블렌딩, 컬링, 깊이처리 전부 Off Blend Off Cull Off ZWrite Off } //계속해서 설명할 두 패스 Pass {...} Pass {...} } } 이번 셰이더의 공용 코드는 위와 같다. 머티리얼을 통해 전달받는 값은 그림자로 이용할 텍스쳐와, 만들어질 그림자의 길이, 컷 오프 될 알파값 기준이 존재한다. 텍스쳐에 애초에 알파값이 낮아 반투명, 혹은 투명한 부분에 대해 그림자가 생성되면 어색하기 때문에 그에 대한 예외처리를 위해 컷오프 기준값 설정을 전달받는다. 첫번째 패스(스텐실 마스킹) Pass { Name "Custom2DOccluder" //Custom2DOccluder를 통한 호출인 경우 실행되는 패스 Tags { "LightMode" = "Custom2DOccluder" } //렌더 타겟에 색상을 그리지 않음을 의미 ColorMask 0 //스텐실 버퍼 설정 Stencil { Ref 0 Comp Always //항상 통과 Pass IncrSat //스텐실 값 1 증가 } HLSLPROGRAM #include "Packages/com.unity.render-pipelines.universal/Shaders/2D/Include/Core2D.hlsl" #pragma vertex LitVertex #pragma fragment LitFragment #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" //버텍스 셰이더에서 사용 struct Attributes { //오브젝트의 버텍스 위치 float3 positionOS : POSITION; }; //픽셀 셰이더에서 사용 struct Varyings { //버텍스 셰이더에서 처리한 좌표 전달용 float4 positionCS : SV_POSITION; }; //버텍스 셰이더 Varyings LitVertex(Attributes input) { Varyings output; //오브젝트 기반 정점의 좌표를 클립 기반 좌표로 변경해서 전달 output.positionCS = TransformObjectToHClip(input.positionOS); return output; } //픽셀 셰이더는 ColorMask 0으로 인해 의미가 없는 상태 half4 LitFragment(Varyings input) : SV_Target { return 0; } ENDHLSL } 첫번째 패스의 목표는 스텐실 버퍼에 현재 오브젝트의 모양을 기록해두는 것이 목표이기 때문에 별 다른 작업은 진행되지 않는다. 가장 중요한건 스텐실 영역으로, 현재 스텐실 버퍼는 렌더 타겟 초기화로 인해 전부 0으로만 채워져있는 상태이다. 이 셰이더 기반 오브젝트를 렌더링하면서, 이번 패스가 동작할 때, 스텐실 버퍼에 오브젝트 범위에 맞게 1이 채워지게 된다. 광원 좌표 전달 public class CustomLightBinder : MonoBehaviour { private static readonly int CustomLightPosWSID = Shader.PropertyToID("_CustomLightPosWS"); void Update() { // 현재 광원 오브젝트의 월드 좌표를 전역 셰이더 변수로 업데이트 Vector4 lightPosWS = transform.position; Shader.SetGlobalVector(CustomLightPosWSID, lightPosWS); } } 두번째 패스로 넘어가기 전에 짧은 스크립트를 하나 봐야한다. 이 스크립트는 광원에 붙게 되는 스크립트인데, 단순하게 매 프레임 광원의 위치를 계속해서 업데이트 해주는 역할을 담당한다. 광원의 위치는 다음에 설명할 두번째 패스에서 사용된다. 두번째 패스(그림자 마스킹) Pass { Name "Custom2DShadowCaster" //Custom2DShadowCaster를 통한 호출인 경우 실행되는 패스 Tags { "LightMode" = "Custom2DShadowCaster" } //스텐실 버퍼, 현재 픽셀의 스텐실이 1이 아닌 경우에만 작업을 적용한다는 의미 Stencil { Ref 1 Comp NotEqual } HLSLPROGRAM #include "Packages/com.unity.render-pipelines.universal/Shaders/2D/Include/Core2D.hlsl" #pragma vertex LitVertex #pragma fragment LitFragment #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" //메인 텍스쳐와 샘플러 선언 TEXTURE2D(_MainTex); SAMPLER(sampler_MainTex); //CustomLightBinder 스크립트에서 공유중인 실시간 광원 위치 float4 _CustomLightPosWS; //텍스쳐(타일링 / 오프셋), 그림자 길이, 컷오프 기준값 CBUFFER_START(UnityPerMaterial) float4 _MainTex_ST; float _ShadowLength; float _Cutoff; CBUFFER_END //버텍스 셰이더에서 사용 struct Attributes { float3 positionOS : POSITION; float2 uv : TEXCOORD0; //이 버텍스가 어느 인스턴스(객체)에 속해있는지 구별하기 위한 값 UNITY_VERTEX_INPUT_INSTANCE_ID }; //픽셀 셰이더에서 사용 struct Varyings { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; UNITY_VERTEX_INPUT_INSTANCE_ID }; Varyings LitVertex(Attributes input) { Varyings output; //현 버텍스의 인스턴스(객체) 정보 세팅 UNITY_SETUP_INSTANCE_ID(input); UNITY_TRANSFER_INSTANCE_ID(input, output); //광원 위치를 오브젝트 공간(Object Space)으로 변환 float3 lightPosOS = TransformWorldToObject(_CustomLightPosWS.xyz); //광원에서 오브젝트 중심(0,0,0)으로 향하는 방향 벡터 //(0, 0, 0) - lightPosOS.xy = -lightPosOS.xy float2 lightToCenterDirOS = -lightPosOS.xy; //광원과 오브젝트 중심 위치가 완전히 겹쳐 0이 될 때의 예외 처리 float dirLen = length(lightToCenterDirOS); if (dirLen < 0.0001) { lightToCenterDirOS = float2(0, 1); //기본 방향 지정 } //두 벡터에 대한 내적 진행(오브젝트 중심 -> 현재 정점 위치, 광원 -> 오브젝트 중심 float facing = dot(input.positionOS.xy, lightToCenterDirOS); //빛을 받는 방향을 구함 float3 positionWS = TransformObjectToWorld(input.positionOS); float2 lightToVertDirWS = positionWS.xy - _CustomLightPosWS.xy; float dist = length(lightToVertDirWS); float2 lightToVertNormWS = lightToVertDirWS / max(dist, 0.0001); //광원 반대편 정점(facing > 0)만 Extrusion 수행 if (facing > 0.0) { //빛을 받는 방향에 맞춰서 그림자 거리 설정만큼 밀어내기 positionWS.xy
То, что RADAR обнаружил и классифицировал для этой возможности. Это опубликованный источником текст, а не подтверждение, что предложение ещё действует.
커스텀 2D Shadow. 유니티에서 그림자 처리를 직접 진행했던 기록을 위한 글 입니다. 커스텀 2D Shadow 커스텀 2D Light 에서 생성한 빛 객체는 그림자를 적용할 수 없던 반쪽자리 빛으로, 여기에 그림자 또한 동작하도록 구현해보았다. 이 그림자 구현에는 기존 커스텀 빛 관련 스크립트와 셰이더를 일부 이용해서 구현해주었다. 왼쪽의 노란 빛과 사과의 그림자가 커스텀 라이트와 섀도우, 오른쪽의 오렌지가 유니티 자체적인 빛과 그림자이다. 관련 코드 ScriptableRendererFeature 이전 빛 처리에 사용했던 Custom2DLightFeature에 그림자 관련 코드들을 넣어서 업데이트 해주었다. //셰이더에서 사용할 패스별 이름 설정 private readonly ShaderTagId…
Открыть источник