SRP 정리

2023. 9. 12. 00:38Public/Unity

시작하기전 RenderPass에 대한 이해가 필요하다.
RenderFeature  = 렌더특징이란 뜻이다 Feature뜻이 특징.

 

RenderPass

렌더링 패스 기능은 Windows 10, 버전 1809(10.0; 빌드 17763) Direct3D 12 렌더링 패스의 개념을 소개합니다. 렌더링 패스는 명령 목록에 기록하는 명령의 하위 집합으로 구성됩니다.

각 렌더링 패스가 시작되고 끝나는 위치를 선언하려면  ID3D12GraphicsCommandList4::BeginRenderPass및 EndRenderPass 에 대한 호출 내에서 전달하는 에 속하는 명령을 중첩합니다. 따라서 모든 명령 목록에는 0개, 1개 이상의 렌더링 패스가 포함됩니다.
렌더링 패스는 이러한 요소에 의해 정의됩니다.

  • 렌더링 패스 기간 동안 고정된 출력 바인딩 집합입니다. 이러한 바인딩은 하나 이상의 RTV(렌더링 대상 뷰) 및/또는 DSV(깊이 스텐실 뷰)에 대한 것입니다.
  • 출력 바인딩 집합을 대상으로 하는 GPU 작업 목록입니다.
  • 렌더링 패스가 대상으로 하는 모든 출력 바인딩에 대한 로드/저장소 종속성을 설명하는 메타데이터입니다.

출처 : https://learn.microsoft.com/ko-kr/windows/win32/direct3d12/direct3d-12-render-passes

 

Direct3D 12 렌더링 패스 - Win32 apps

렌더러는 렌더링 단계 기능을 통해 GPU 효율성을 개선할 수 있습니다. 이를 위해 애플리케이션에서 리소스 렌더링의 순서 지정 요구 사항 및 데이터 종속성을 더 잘 파악하도록 하여 오프칩 메모

learn.microsoft.com

 

 

 

 

 

 

 

 

 

 

다음은 기본적인 바탕? 느낌이다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering.Universal;


public class LensFlareRendererFeature : ScriptableRendererFeature
{
    //매 프레임마다 각 카메라에 대해 한 번씩 이 메서드를 호출
    //이 함수를 사용하면 ScriptableRenderPass 인스턴스를 Scriptablerenderer에 삽입할 수 있음.
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        throw new System.NotImplementedException();
    }



    //렌더러 기능이 처음 로드될 때
    //렌더러 기능을 활성화하거나 비활성화할 때
    //렌더러 기능의 인스펙터에서 속성을 변경할 때
    public override void Create()
    {
        throw new System.NotImplementedException();
    }

}

 

 

 

그후에 렌더패스를 선언한다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;


public class LensFlareRendererFeature : ScriptableRendererFeature
{
    class LensFlarePass : ScriptableRenderPass
    {
        //Unity는 매 프레임마다 Execute()를 실행합니다 해당 함수 사용자 정의 렌더링 기능을 구현할 수 있습니다.
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            Debug.Log("Execute() Run");
        }
    }

    private LensFlarePass _lensFlarePass;

    //매 프레임마다 각 카메라에 대해 한 번씩 이 메서드를 호출
    //이 함수를 사용하면 ScriptableRenderPass 인스턴스를 Scriptablerenderer에 삽입할 수 있음.
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        //해당 함수에서 렌더러 개체의 EnqueuePass함수를 사용하여 RenderingQueue에 _lensFlarePass를 대기시킵니다.        
        renderer.EnqueuePass(_lensFlarePass);
    }



    //렌더러 기능이 처음 로드될 때
    //렌더러 기능을 활성화하거나 비활성화할 때
    //렌더러 기능의 인스펙터에서 속성을 변경할 때
    public override void Create()
    {
        //인스턴스화 해줍니다 Initialize과 비슷한 작업인것 같습니다
        _lensFlarePass = new LensFlarePass();
    }

}

 

그후 Renderer에 RendererFeature를 추가하면 다음과 같이 실행된다.

 

이제 렌즈플레어 렌더러 기능이 패스 내에서 Execute(실행) 메서드를 실행하고 있습니다.

 

이제 Execute가 어떻게 작동하는지 알았으니 커스텀을 해봅시다

아래 부분이 Execute의 상용구 입니다.

public class LensFlareRendererFeature : ScriptableRendererFeature
{
    class LensFlarePass : ScriptableRenderPass
    {
        //Unity는 매 프레임마다 Execute()를 실행합니다 해당 함수 사용자 정의 렌더링 기능을 구현할 수 있습니다.
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            //새로운 명령 버퍼를 가져오고 여기에 이름을 할당합니다
            CommandBuffer cmd = CommandBufferPool.Get(name: "LensFlarePass");

            Debug.Log("Execute() Run");


            //명령 버퍼를 실행하고 해제하는 라인입니다
            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);
        }
    }

 

상용구 부분을 준비했으니 로직을 짜봅시다

이 예제에서 렌더러의 기능은 렌즈 플레어를 쿼드의 텍스처로 그립니다. 구현에는 머티리얼과 메시(쿼드)가 필요합니다 

그리고 나머지 부분을 정리해봅시다.

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;


public class LensFlareRendererFeature : ScriptableRendererFeature
{
    class LensFlarePass : ScriptableRenderPass
    {
        //해당 렌더러 기능에서는 렌즈 플레어를 쿼드의 텍스처로 그린다, 구현에는 Material과 Mesh가 필요하다
        private Material _material;
        private Mesh _mesh;

        //인수를 받아줄 생성자를 선언한다.
        public LensFlarePass(Material material,Mesh mesh)
        {
            _material = material;
            _mesh = mesh;
        }

        //Unity는 매 프레임마다 Execute()를 실행합니다 해당 함수 사용자 정의 렌더링 기능을 구현할 수 있습니다.
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            //새로운 명령 버퍼를 가져오고 여기에 이름을 할당합니다
            CommandBuffer cmd = CommandBufferPool.Get(name: "LensFlarePass");

            Debug.Log("Execute() Run");
            //Execute 메서드에서 cmd 개체의 DrawMesh 메서드를 사용합니다.
            //메서드는 _material 및 _mesh 필드를 인수로 사용합니다.            
            cmd.DrawMesh(_mesh, Matrix4x4.identity, _material);            

            //명령 버퍼를 실행하고 해제하는 라인입니다
            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);
        }
    }

    //LensFlarePass를 초기화하려면 필요.
    public Material material;
    public Mesh mesh;

    private LensFlarePass _lensFlarePass;

    //매 프레임마다 각 카메라에 대해 한 번씩 이 메서드를 호출
    //이 함수를 사용하면 ScriptableRenderPass 인스턴스를 Scriptablerenderer에 삽입할 수 있음.
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        //해당 함수에서 렌더러 개체의 EnqueuePass함수를 사용하여 RenderingQueue에 _lensFlarePass를 대기시킵니다.
        //null 확인 조건으로 호출을 래핑합니다
        if(material != null && mesh != null)
        {
            renderer.EnqueuePass(_lensFlarePass);
        }    
    }



    //렌더러 기능이 처음 로드될 때
    //렌더러 기능을 활성화하거나 비활성화할 때
    //렌더러 기능의 인스펙터에서 속성을 변경할 때
    public override void Create()
    {
        //인스턴스화 해줍니다 Initialize과 비슷한 작업인것 같습니다        
        _lensFlarePass = new LensFlarePass(material,mesh); //material과 mesh를 넘겨줍니다.
    }

}

LensFlarePass클래스의 Execute함수에는 다음과 같은 기본 로직이 있습니다.

  1. 새 명령 버퍼를 가져와 이름을 할당합니다 CommandBuffer cmd = CommandBufferPool.Get(name: "LensFlarePass");
  2. 렌더링 명령을 추가합니다. cmd.DrawMesh(_mesh, Matrix4x4.identity, _material);     
  3. 명령 버퍼를 실행합니다. context.ExecuteCommandBuffer(cmd);   
  4. 버퍼를 해제합니다. CommandBufferPool.Release(cmd);

 

 

 

한번 여기까지 왔으면 테스트를 해보자.

Material을 하나 생성해준다. 셰이더는 Universal Render Pipeline/Unlit으로 설정 색상을 빨강을 했다.하고 

Mesh는 Quad로 설정했다.

실행결과
실행결과2

 

결과는 검정색으로 나왔다. 그 이유는 

Universal Render Pipeline/Unlit셰이더에는 여러 패스가 있고 그 중 하나가 쿼드를 검정색으로 칠하기 때문입니다

다음과 같이 변경해줍시다.

cmd.DrawMesh(_mesh, Matrix4x4.identity, _material, 0, 0);

정상적으로 나옵니다.

 

기본적인 전체코드

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using static Unity.Burst.Intrinsics.X86.Avx;


public class LensFlareRendererFeature : ScriptableRendererFeature
{
    class LensFlarePass : ScriptableRenderPass
    {
        //해당 렌더러 기능에서는 렌즈 플레어를 쿼드의 텍스처로 그린다, 구현에는 Material과 Mesh가 필요하다
        private Material _material;
        private Mesh _mesh;

        //인수를 받아줄 생성자를 선언한다.
        public LensFlarePass(Material material,Mesh mesh)
        {
            _material = material;
            _mesh = mesh;
        }

        //Unity는 매 프레임마다 Execute()를 실행합니다 해당 함수 사용자 정의 렌더링 기능을 구현할 수 있습니다.
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            //새로운 명령 버퍼를 가져오고 여기에 이름을 할당합니다
            CommandBuffer cmd = CommandBufferPool.Get(name: "LensFlarePass");

            Debug.Log("Execute() Run");
            //Execute 메서드에서 cmd 개체의 DrawMesh 메서드를 사용합니다.
            //메서드는 _material 및 _mesh 필드를 인수로 사용합니다.            
            //cmd.DrawMesh(_mesh, Matrix4x4.identity, _material);
            cmd.DrawMesh(_mesh, Matrix4x4.identity, _material, 0/*subMeshIndex*/, 0/*ShaderPass*/);

            //명령 버퍼를 해제하는 라인입니다
            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);
        }
    }

    //LensFlarePass를 초기화하려면 필요.
    public Material material;
    public Mesh mesh;

    private LensFlarePass _lensFlarePass;

    //매 프레임마다 각 카메라에 대해 한 번씩 이 메서드를 호출
    //이 함수를 사용하면 ScriptableRenderPass 인스턴스를 Scriptablerenderer에 삽입할 수 있음.
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        //해당 함수에서 렌더러 개체의 EnqueuePass함수를 사용하여 RenderingQueue에 _lensFlarePass를 대기시킵니다.
        //null 확인 조건으로 호출을 래핑합니다
        if(material != null && mesh != null)
        {
            renderer.EnqueuePass(_lensFlarePass);
        }    
    }



    //렌더러 기능이 처음 로드될 때
    //렌더러 기능을 활성화하거나 비활성화할 때
    //렌더러 기능의 인스펙터에서 속성을 변경할 때
    public override void Create()
    {
        //인스턴스화 해줍니다 Initialize과 비슷한 작업인것 같습니다        
        _lensFlarePass = new LensFlarePass(material,mesh); //material과 mesh를 넘겨줍니다.
    }

}

 

 

 

 

 

 

 

여기부터는 Lens효과구현

LensFlarePass의 Execute를 다음과 같이 변경해줍니다.

        //Unity는 매 프레임마다 Execute()를 실행합니다 해당 함수 사용자 정의 렌더링 기능을 구현할 수 있습니다.
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            //새로운 명령 버퍼를 가져오고 여기에 이름을 할당합니다
            CommandBuffer cmd = CommandBufferPool.Get(name: "LensFlarePass");

            Debug.Log("Execute() Run");


            //renderingData에서 카메라 데이터를 가져옵니다
            Camera camera = renderingData.cameraData.camera;
            //Screen Space에서 쿼드를 그리도록 투영 행렬을 설정합니다
            cmd.SetViewProjectionMatrices(Matrix4x4.identity, Matrix4x4.identity);
            //스케일 변수를 추가하고 좌표에 카메라 종횡비를 사용합니다
            Vector3 scale = new Vector3(1, camera.aspect, 1);
            // 조명의 ScreenSpace 위치에 각 조명에 대해 쿼드를 그립니다
            foreach (VisibleLight visibleLight in renderingData.lightData.visibleLights)
            {
                Light light = visibleLight.light;
                //각 조명의 위치를 월드에서 뷰포트로 변환
                Vector3 position = camera.WorldToViewportPoint(light.transform.position) * 2 - Vector3.one;
                //같은 평면에 쿼드를 그리도록 z = 0
                position.z = 0;
                //포지션을 사용하기 위해 cmd.DrawMesh 함수에서 Matrix4x4 인수를 변경
                cmd.DrawMesh(_mesh, Matrix4x4.TRS(position, Quaternion.identity, scale), _material, 0, 0);
            }

            //명령 버퍼를 해제하는 라인입니다
            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);
        }
    }

 

 

 

다음과 같이 Directional Light를 따라다닌다.

이제 머테리얼의 기본 Map에 다음 텍스쳐를 추가한다.

 

 

SurfaceType은 Transparent로 설정하고

BlendType은 Addtive로 설정한다.

 

그리고 다음과 같이 변경해준다

    //렌더러 기능이 처음 로드될 때
    //렌더러 기능을 활성화하거나 비활성화할 때
    //렌더러 기능의 인스펙터에서 속성을 변경할 때
    public override void Create()
    {
        //인스턴스화 해줍니다 Initialize과 비슷한 작업인것 같습니다        
        _lensFlarePass = new LensFlarePass(material,mesh); //material과 mesh를 넘겨줍니다.
        _lensFlarePass.renderPassEvent = RenderPassEvent.AfterRenderingSkybox;
    }

 

 

 

 

 

해당 예제의 전체코드입니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using static Unity.Burst.Intrinsics.X86.Avx;


public class LensFlareRendererFeature : ScriptableRendererFeature
{
    class LensFlarePass : ScriptableRenderPass
    {
        //해당 렌더러 기능에서는 렌즈 플레어를 쿼드의 텍스처로 그린다, 구현에는 Material과 Mesh가 필요하다
        private Material _material;
        private Mesh _mesh;

        //인수를 받아줄 생성자를 선언한다.
        public LensFlarePass(Material material,Mesh mesh)
        {
            _material = material;
            _mesh = mesh;
        }

        //Unity는 매 프레임마다 Execute()를 실행합니다 해당 함수 사용자 정의 렌더링 기능을 구현할 수 있습니다.
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            //새로운 명령 버퍼를 가져오고 여기에 이름을 할당합니다
            CommandBuffer cmd = CommandBufferPool.Get(name: "LensFlarePass");

            Debug.Log("Execute() Run");


            //renderingData에서 카메라 데이터를 가져옵니다
            Camera camera = renderingData.cameraData.camera;
            //Screen Space에서 쿼드를 그리도록 투영 행렬을 설정합니다
            cmd.SetViewProjectionMatrices(Matrix4x4.identity, Matrix4x4.identity);
            //스케일 변수를 추가하고 좌표에 카메라 종횡비를 사용합니다
            Vector3 scale = new Vector3(1, camera.aspect, 1);
            // 조명의 ScreenSpace 위치에 각 조명에 대해 쿼드를 그립니다
            foreach (VisibleLight visibleLight in renderingData.lightData.visibleLights)
            {
                Light light = visibleLight.light;
                //각 조명의 위치를 월드에서 뷰포트로 변환
                Vector3 position = camera.WorldToViewportPoint(light.transform.position) * 2 - Vector3.one;
                //같은 평면에 쿼드를 그리도록 z = 0
                position.z = 0;
                //포지션을 사용하기 위해 cmd.DrawMesh 함수에서 Matrix4x4 인수를 변경
                cmd.DrawMesh(_mesh, Matrix4x4.TRS(position, Quaternion.identity, scale), _material, 0, 0);
            }

            //명령 버퍼를 해제하는 라인입니다
            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);
        }
    }

    //LensFlarePass를 초기화하려면 필요.
    public Material material;
    public Mesh mesh;

    private LensFlarePass _lensFlarePass;

    //매 프레임마다 각 카메라에 대해 한 번씩 이 메서드를 호출
    //이 함수를 사용하면 ScriptableRenderPass 인스턴스를 Scriptablerenderer에 삽입할 수 있음.
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        //해당 함수에서 렌더러 개체의 EnqueuePass함수를 사용하여 RenderingQueue에 _lensFlarePass를 대기시킵니다.
        //null 확인 조건으로 호출을 래핑합니다
        if(material != null && mesh != null)
        {
            renderer.EnqueuePass(_lensFlarePass);
        }    
    }



    //렌더러 기능이 처음 로드될 때
    //렌더러 기능을 활성화하거나 비활성화할 때
    //렌더러 기능의 인스펙터에서 속성을 변경할 때
    public override void Create()
    {
        //인스턴스화 해줍니다 Initialize과 비슷한 작업인것 같습니다        
        _lensFlarePass = new LensFlarePass(material,mesh); //material과 mesh를 넘겨줍니다.
        _lensFlarePass.renderPassEvent = RenderPassEvent.AfterRenderingSkybox;
    }

}

 

 

예제의 출처 : https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@14.0/manual/renderer-features/create-custom-renderer-feature.html#scriptable-renderer-feature

'Public > Unity' 카테고리의 다른 글

최적화 테스트(Job,Burst)  (0) 2024.08.13
CustomTMP_Flip  (0) 2023.10.09
JungUtil(MyCustomUtil)  (0) 2023.09.11
Custom SRP (진행중)  (0) 2023.09.10
Unity Tip  (0) 2023.09.04