UNITY3D

Unity Shading Rate

jplee 2025. 4. 3. 14:47

현재 유니티 6.1 베타 버전 상태인데요.
6.1 에 들어온 VRS에 대해서 살펴보고자 합니다. 유니티 테크놀러지 깃허브 문서와 기타 포럼 발췌글의 번역문으로 구성되어 있습니다.
아래 글 외에 VRS 와 관련 된 여러가지 토픽이 존재하는데요. 궁금하시다면...

[INDEX] Vis-Buffer and VRS

[번역] Software VRS with Visibility Buffer Rendering by J.Hable역자의 말: VRS 와 Vis-Buffer 는 매우 중요한 트렌딩 포인트입니다. 이미 텐센트 게임 연구센터에서 모바일 플레폼을 지원하는 클러스터 컬링을 활

techartnomad.tistory.com

 

Software-based Variable Rate Shading in Call of Duty: Modern Warfare

This lecture covers a novel rendering pipeline used in Call of Duty: Modern Warfare (2020). It enables a highly customizable software-based variable rate shading; a method to render parts of the render target at a resolution matching image frequency. As op

research.activision.com

등도 참고해 볼 수 있겠습니다.


VRS란 무엇인가요?

가변 비율 셰이딩(VRS)은 프래그먼트 셰이딩 비율이라고도 하며, 래스터화와 픽셀 셰이딩 비율을 분리할 수 있는 기술입니다. 셰이딩 속도를 낮게 설정하면 픽셀 셰이더가 더 낮은 주파수로 실행되어 GPU 성능을 크게 향상시킬 수 있습니다:

예를 들어, 스칼라 2x2 셰이딩 비율을 적용하면 원본 픽셀의 1/4만 셰이딩됩니다. 결과적으로 픽셀 셰이딩 오버헤드가 크게 감소하는 것을 관찰할 수 있습니다.
기존 업스케일링과 달리 VRS는 화면 공간 전체에 걸쳐 셰이딩 비율을 변경할 수 있습니다. 이는 화면의 여러 영역에 대한 셰이딩 비율을 인코딩하는 셰이딩 비율 이미지(SRI)를 사용하여 이루어집니다:

이 간단한 UI 기반 셰이딩 레이트를 사용하면 VRS 지원 기기를 타겟팅할 때 GPU 시간을 최대 10%까지 줄일 수 있습니다.
그러나 SRI는 셰이딩 성능과 이미지 충실도 간의 미세 조정을 위해 더 흥미롭고 다양한 방식으로 생성 및 적용할 수 있습니다.

VRS 시작하기

시작하려면 운전 미니 게임의 맥락에서 VRS를 사용하는 방법을 보여주는 새로운 셰이딩 레이트 샘플 프로젝트 (https://github.com/Unity-Technologies/shading-rate-demo)를 확인하는 것이 좋습니다.
이 프로젝트는 커스텀 볼류메트릭 라이팅 패스를 활용하여 'God Ray'를 렌더링하고 더 나은 깊이감을 제공합니다. 볼류메트릭은 셰이딩 오버헤드가 높기 때문에 프레임 속도에 큰 영향을 줄 수 있습니다:

 

볼류메트릭 패스는 ~6.3ms로 측정되었으며, 총 GPU 프레임 시간은 ~11.3ms였습니다. 다양한 셰이딩 속도 기법을 사용하여 이 오버헤드를 완화할 수 있습니다.
예제 프로젝트에서는 주행 중 속도감을 강조하기 위해 '모션 블러' 효과를 사용하고 있습니다. 이렇게 하면 모션 벡터 텍스처가 생성되며, 셰이더 그래프에서 액세스하여 속도 마스크를 생성하는 데 사용할 수 있습니다:
 

GitHub - Unity-Technologies/shading-rate-demo: This project demonstrates how to set the shading rate of Renderer Features for th

This project demonstrates how to set the shading rate of Renderer Features for the Universal Rendering Pipeline - Unity-Technologies/shading-rate-demo

github.com

Unity 가변 비율 셰이딩
프래그먼트 셰이딩 비율이라고도 하는 가변 비율 셰이딩(VRS)은 래스터화 및 픽셀 셰이딩 비율을 분리할 수 있는 기법입니다. VRS를 사용하면 픽셀 셰이딩 오버헤드를 최소화하고 이미지 품질을 유지하면서 성능을 개선할 수 있습니다:
요구 사항

  • Unity 6000.1.0b1 이상
  • URP(유니버설 렌더 파이프라인) 버전 17.0.3 이상

플랫폼 지원

  • DirectX12 가변 비율 셰이딩을 지원하는 Windows 플랫폼
  • 벌칸 프래그먼트 셰이딩 비율을 지원하는 Android 플랫폼
  • 호환 콘솔

사용 방법

  1. "ShadingRateSample" 씬을 엽니다(\\Assets\\Scenes\\ShadingRateSample.unity).
  2. 재생을 누릅니다.
  3. URP 렌더러 설정에서 셰이딩 비율 디버그 뷰를 토글할 수 있습니다(\\Assets\\Settings\\Renderer.asset). "셰이딩 비율 기능"으로 이동하여 "VRS 디버그"를 클릭합니다.

Unity 가변 비율 셰이딩 데모 - 단계별
커스텀 렌더러 기능은 셰이딩 레이트의 2D 배열을 인코딩하는 "셰이딩 레이트 이미지"(SRI)를 계산하는 데 사용됩니다. SRI는 셰이딩 성능과 이미지 충실도 간의 균형을 맞추기 위해 프레임 단위로 생성할 수 있습니다.
시작하려면 새 렌더러 기능(\\Asset\\ShadingRate\\ShadingRateFeature.cs)을 생성하고 렌더 패스 및 리소스를 선언합니다. 렌더 그래프 레코드 함수에서 렌더 패스의 이름을 지정하고 디바이스에서 VRS를 지원하는지 확인합니다:

class VRSGenerationPass : ScriptableRenderPass
{
    private TextureHandle m_ColorMask;
    private TextureHandle m_SRI;
    private Material m_Material;

    private class PassData
    {
        public Material m_Mat;
    }

    public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) 
    {
       const string passName = "VRS Generation";

       if (!ShadingRateInfo.supportsPerImageTile) {
           Debug.Log("VRS is not supported!");
           return;
       }

VRS가 지원되는 경우 VRS 룩업 테이블(LUT)을 생성하여 계속 진행합니다. 이 테이블은 각 셰이딩 비율마다 다른 기본 색상을 인코딩합니다(빨강: 1x1, 녹색: 2x2, 파랑: 4x4). GenerateVRS.shadergraph를 사용하여 새 머티리얼을 생성하고 각 색상을 셰이더 유니폼으로 설정합니다:

  VrsLut lut = new VrsLut();
  lut = VrsLut.CreateDefault();

  if (m_Material == null) {
      m_Material = new Material(Resources.Load<Shader>("Shaders/GenerateVRS"));
      m_Material.SetColor("_ShadingRateColor1x1", lut[ShadingRateFragmentSize.FragmentSize1x1]);
      m_Material.SetColor("_ShadingRateColor2x2", lut[ShadingRateFragmentSize.FragmentSize2x2]);
      m_Material.SetColor("_ShadingRateColor4x4", lut[ShadingRateFragmentSize.FragmentSize4x4]);
  }

셰이더 그래프에서 _ShadingRateColor4x4 프로퍼티를 읽고 그래프의 색상 출력에 전달하기만 하면 됩니다:

다음으로 셰이딩 비율 컬러 마스크를 만들어야 합니다. 먼저 렌더링 대상의 치수를 기준으로 네이티브 타일 크기를 쿼리해야 합니다. 그런 다음 새 렌더 그래프 텍스처를 생성하고 이를 렌더 패스의 색상 대상으로 설정합니다. 셰이딩 비율 컬러 마스크를 렌더링하기 위해 커스텀 머티리얼을 사용하여 전체 화면 드로우를 실행합니다:

  var tileSize = ShadingRateImage.GetAllocTileSize(cameraData.cameraTargetDescriptor.width, cameraData.cameraTargetDescriptor.height);

  RenderTextureDescriptor textureProperties = new RenderTextureDescriptor(tileSize.x, tileSize.y, RenderTextureFormat.Default, 0);

  m_ColorMask = UniversalRenderer.CreateRenderGraphTexture(renderGraph, textureProperties, "_ShadingRateColor", false);
  builder.SetRenderAttachment(m_ColorMask, 0, AccessFlags.Write);

  builder.SetRenderFunc((PassData data, RasterGraphContext context) =>
  {
      RasterCommandBuffer cmd = context.cmd;
      Blitter.BlitTexture(cmd, new Vector4(1,1,0,0), data.m_Mat, 0);
  });

프레임 디버거를 사용하여 패스의 출력이 파란색 텍스처가 되는지 확인합니다:

다음으로 ShadingRateInfo.graphicsFormat 형식을 사용하여 네이티브 셰이딩 레이트 이미지(SRI)를 만들어야 합니다. 그런 다음 유틸리티 함수를 사용하여 컬러 마스크(RGB8)를 네이티브 SRI로 변환합니다:

  RenderTextureDescriptor sriDesc = new RenderTextureDescriptor(tileSize.x, tileSize.y, ShadingRateInfo.graphicsFormat,
      GraphicsFormat.None);
  sriDesc.enableRandomWrite = true;
  sriDesc.enableShadingRate = true;
  sriDesc.autoGenerateMips = false;

  m_SRI = UniversalRenderer.CreateRenderGraphTexture(renderGraph, sriDesc, "_SRI", false);

  Vrs.ColorMaskTextureToShadingRateImage(renderGraph, m_SRI, m_ColorMask, TextureDimension.Tex2D, true);

마지막으로 SRI를 인코딩하여 이후 렌더링 패스에서 균일한 4x4 셰이딩 레이트를 설정하는 데 사용할 수 있습니다. 프레임 디버거를 사용하면 이제 추가 VRS 변환 패스가 생성된 것을 볼 수 있습니다. 이 패스는 파란색 마스크를 읽고 SRI를 출력합니다:

다음 단계는 최적화하고자 하는 관련 렌더 패스에 SRI를 적용하는 것입니다. ContextItem 클래스를 사용하여 렌더 패스 사이에 텍스처 핸들을 전달할 수 있습니다( https://docs.unity3d.com/6000.0/Documentation/Manual/urp/render-graph-pass-textures-between-passes.html 참조). SRI 텍스처 핸들에 대한 공용 멤버를 사용하여 ContextItem을 상속하는 새 VRSData 클래스를 선언합니다. VRSData를 인스턴스화하고 인코딩 후 SRI 텍스처 핸들을 할당합니다:

  public class VRSData : ContextItem {
      public TextureHandle sri;

      public override void Reset()
      {
          sri = TextureHandle.nullHandle;
      }
  }
var vrsData = frameData.Create<VRSData>(); 
Vrs.ColorMaskTextureToShadingRateImage(renderGraph, m_SRI, m_ColorMask, TextureDimension.Tex2D, true);
vrsData.sri = m_SRI;

이제 VRSData를 통해 SRI를 참조하고 관련 렌더 패스에 적용할 수 있습니다. 필요한 경우 여러 셰이딩 레이트 소스를 결합할 수도 있습니다( https://docs.unity3d.com/6000.2/Documentation/ScriptReference/Rendering.CommandBuffer.SetShadingRateCombiner.html 참조):

  if(ShadingRateInfo.supportsPerImageTile && frameData.Contains<ShadingRateFeature.VRSData>()) {
    var vrsData = frameData.Get<ShadingRateFeature.VRSData>();
    if (vrsData.sri.IsValid())
    {
        builder.SetShadingRateImageAttachment(vrsData.sri);
        builder.SetShadingRateCombiner(ShadingRateCombinerStage.Fragment,
        ShadingRateCombiner.Override);
    }
  }

기본 예시에서는 계산 집약적인 볼류메트릭 라이팅 패스에 균일한 4x4 셰이딩 레이트를 적용했습니다:

특히 가까이에서 확대할 때 품질 저하가 상당히 눈에 띕니다:

예제 프로젝트에서는 운전 중 속도감을 강조하기 위해 "모션 블러" 효과를 사용하고 있습니다. 이렇게 하면 모션 벡터 텍스처가 생성되며, 셰이더 그래프에서 액세스하여 속도 마스크를 생성하는 데 사용할 수 있습니다:

동시에 속도계와 미니맵에 해당하는 UI 텍스처도 샘플링합니다:

셰이더는 이러한 마스크를 결합하고 셰이딩 속도별 임계값을 설정합니다. 그 결과 속도가 빠른 픽셀의 셰이딩 레이트가 낮아지는 동적 셰이딩 레이트가 생성됩니다. 또한 투명 UI로 가려진 화면 영역의 셰이딩 비율도 감소합니다:

전체 셰이더는 \Assets\Resources\Shaders\GenerateVRS.shadergraph에서 찾을 수 있습니다. 모션 벡터를 사용하여 화면 중앙에 있는 자동차 모델의 충실도를 유지합니다. 이미 모션 블러의 영향을 받는 고속 픽셀의 셰이딩 비율을 줄이면서 말이죠:

Nvidia RTX 3080 Ti(모바일) GPU 성능 측정: