일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- ColorGradingLutPass
- Toon Shader
- Cell Look
- working set
- 3d
- Rim Light
- Three(Two) Tone Shading
- 게임 수학
- URP로 변경
- AppSW
- 프로그래밍 기초
- VR
- OculusMotionVectorPass
- 메모리 누수
- ASW(Application SpaceWarp)
- 개인 바이트
- Private Bytes
- 작업 집합
- 벡터
- URP
- Virtual Byte
- 가상 바이트
- Cell Shader
- Specular
- Cartoon Rendering
- Windows Build
- C언어
- Today
- Total
WinCNT
텍스처 샘플링의 부하 측정해보기(Oculus Quest2) 본문
서론
많은 게임들은 Metallic Map이나 AO Map과 같이 색상 값이 아니라 0~1의 값만 필요한 맵의 경우, 하나의 텍스처로 합치곤 한다
이렇게 텍스처를 합치면 성능상으로 크게 2가지 이점을 얻을 수 있는데, 하나는 메모리를 절약할 수 있다는 점이고, 다른 하나는 텍스처의 샘플링에 의한 부하를 줄일 수 있다는 점이다
뭐 그러한 이론적인 이야기를 제처두고, 실제로 VR기기(Oculus Quest2)에서 텍스처 샘플링에 의한 부하가 어느 정도인지는 직접 해보지 않으면 모르기 때문에 대략적인 방법으로 검증해봤다
사실 사수가 시켰다
사족) 텍스처 컴바인으로 인한 메모리를 절약
사실 텍스처의 원본 이미지(png)의 경우, 채널별로 나누면 용량이 줄어든다
하지만 실제로 게임을 빌드할 때의 텍스처의 용량은 채널별로 나눴다고 해서 줄어들지 않는다
즉 텍스처를 합친 경우가 용량을 1/3이 된다는 것을 알 수 있다
당연하다면 당연한데, 원본 이미지(png)가 그대로 텍스처로 쓰이는 것이 아니며, 별도의 압축 포맷(텍스처 압축 포맷)을 사용해야 하기 때문이다
https://namu.wiki/w/%ED%85%8D%EC%8A%A4%EC%B2%98%20%EC%95%95%EC%B6%95%20%ED%8F%AC%EB%A7%B7
아무튼 메모리 절약 측면에서는 텍스처를 합치는 것이 무조건 이득이라 볼 수 있다
검증 준비
코드는 단순하게 다음과 같이 4개/16개의 텍스처를 샘플링하는 셰이더를 각각 만들었다
후자의 셰이더의 텍스처가 16개인 이유는 유니티(정확히는 Direct3D 11)의 텍스처 샘플러의 제한이 16개이기 때문이고, 전자가 4개인 이유는 딱히 없다ㅎㅎ
💡 17개 이상의 텍스처를 사용하고 싶을 경우
샘플러의 제한은 16개이지만 텍스처는 최대 128개까지 지원하므로 샘플러를 돌려쓰면 더 많은 텍스처를 사용하는 것은 가능하다
URP의 Core.hlsl에는 다음과 같이 관련 매크로가 이미 정의되어 있으므로 그걸 사용하면 된다
* TEXTURE2D(_MainTex0);
* SAMPLER(sampler_MainTex0);
검증에 사용한 셰이더는 텍스처와 샘플러가 일체형인 tex2D()를 사용했기 때문에 16개의 제한이 있었을 뿐이다
검증용 셰이더는 대충 다음과 같이 만들었다(많은 부분 생략함)
Shader "Basic/TextureSampling4"
{
Properties
{
[NoScaleOffset] _MainTex0 ("Albedo(rgb)", 2D) = "black" { }
[NoScaleOffset] _MainTex1 ("Albedo(rgb)", 2D) = "black" { }
[NoScaleOffset] _MainTex2 ("Albedo(rgb)", 2D) = "black" { }
[NoScaleOffset] _MainTex3 ("Albedo(rgb)", 2D) = "black" { }
}
SubShader
{
Tags
{
"RenderType"="Opaque"
"RenderPipeline"="UniversalPipeline"
}
Pass
{
// ///
// 대략 생략...
// ///
HLSLPROGRAM
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
sampler2D _MainTex0;
sampler2D _MainTex1;
sampler2D _MainTex2;
sampler2D _MainTex3;
// ///
// 대략 생략...
// ///
float3 frag(v2f i) : SV_Target
{
UNITY_SETUP_INSTANCE_ID(i);
float4 _BaseColor0 = tex2D(_MainTex0, i.tex_coord);
float4 _BaseColor1 = tex2D(_MainTex1, i.tex_coord);
float4 _BaseColor2 = tex2D(_MainTex2, i.tex_coord);
float4 _BaseColor3 = tex2D(_MainTex3, i.tex_coord);
float3 Color = float3(0, 0, 0);
Color += _BaseColor0.rgb;
Color += _BaseColor1.rgb;
Color += _BaseColor2.rgb;
Color += _BaseColor3.rgb;
return Color;
}
ENDHLSL
}
}
}
Shader "Basic/TextureSampling16"
{
Properties
{
[NoScaleOffset] _MainTex0 ("Albedo(rgb)", 2D) = "black" { }
[NoScaleOffset] _MainTex1 ("Albedo(rgb)", 2D) = "black" { }
[NoScaleOffset] _MainTex2 ("Albedo(rgb)", 2D) = "black" { }
[NoScaleOffset] _MainTex3 ("Albedo(rgb)", 2D) = "black" { }
[NoScaleOffset] _MainTex4 ("Albedo(rgb)", 2D) = "black" { }
[NoScaleOffset] _MainTex5 ("Albedo(rgb)", 2D) = "black" { }
[NoScaleOffset] _MainTex6 ("Albedo(rgb)", 2D) = "black" { }
[NoScaleOffset] _MainTex7 ("Albedo(rgb)", 2D) = "black" { }
[NoScaleOffset] _MainTex8 ("Albedo(rgb)", 2D) = "black" { }
[NoScaleOffset] _MainTex9 ("Albedo(rgb)", 2D) = "black" { }
[NoScaleOffset] _MainTex10 ("Albedo(rgb)", 2D) = "black" { }
[NoScaleOffset] _MainTex11 ("Albedo(rgb)", 2D) = "black" { }
[NoScaleOffset] _MainTex12 ("Albedo(rgb)", 2D) = "black" { }
[NoScaleOffset] _MainTex13 ("Albedo(rgb)", 2D) = "black" { }
[NoScaleOffset] _MainTex14 ("Albedo(rgb)", 2D) = "black" { }
[NoScaleOffset] _MainTex15 ("Albedo(rgb)", 2D) = "black" { }
}
SubShader
{
Tags
{
"RenderType"="Opaque"
"RenderPipeline"="UniversalPipeline"
}
Pass
{
// ///
// 대략 생략...
// ///
HLSLPROGRAM
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
sampler2D _MainTex0;
sampler2D _MainTex1;
sampler2D _MainTex2;
// ///
// 대략 생략...
// ///
sampler2D _MainTex15;
CBUFFER_START(UnityPerMaterial)
float4 _MainTex0_ST;
CBUFFER_END
// ///
// 대략 생략...
// ///
float3 frag(v2f i) : SV_Target
{
UNITY_SETUP_INSTANCE_ID(i);
// float4 _BaseColor0 = SAMPLE_TEXTURE2D(_MainTex0, sampler_MainTex0, i.tex_coord);
float4 _BaseColor0 = tex2D(_MainTex0, i.tex_coord);
float4 _BaseColor1 = tex2D(_MainTex1, i.tex_coord);
float4 _BaseColor2 = tex2D(_MainTex2, i.tex_coord);
// ///
// 대략 생략...
// ///
float4 _BaseColor15 = tex2D(_MainTex15, i.tex_coord);
float3 Color = float3(0, 0, 0);
Color += _BaseColor0.rgb;
Color += _BaseColor1.rgb;
Color += _BaseColor2.rgb;
// ///
// 대략 생략...
// ///
Color += _BaseColor15.rgb;
return Color;
}
ENDHLSL
}
}
}
검증은 구와 큐브 오브젝트를 각각 1개씩 배치한 상태에서 셰이더를 바꿨을 때의 GPU Time의 변화와, 구와 큐브 오브젝트를 각각 10개씩 배치한 상태에서 셰이더를 바꿨을 때의 GPU Time의 변화를 비교하는 방식으로 진행했다
참고로 GPU Time은 MQHD의 Metrics Recording으로 출력되는 그래프를 기준으로 했다
검증 결과 정리
아래는 구와 큐브 오브젝트를 각각 10개씩 배치한 상태에서 4텍스처 셰이더와 16텍스처 셰이더를 바꿨을 때의 GPU Time에 대한 결과물이다
대략적으로 2500마이크로초(μs) 정도의 차이가 나왔다
모든 케이스의 결과를 정리하면 다음과 같다
오브젝트 1개씩 | 오브젝트 10개씩 차이 | ||
텍스처 4개 | 대략 6,150μs | 대략 6,750μs | 600μs |
텍스처 16개 | 대략 6,650μs | 대략 9,300μs | 2,650μs |
차이 | 500μs | 2,550μs | 2,050μs |
참고로 오브젝트 10개를 앞에, 그걸 복사한 10개를 조금 뒤에 배치하는 식으로도 검증해봤다
그 결과는 텍스처 4개일 때가 7,710μs이고 텍스처 16개가 9860μs였다
즉 오브젝트의 개수에 무조건 비례하는 것은 아니라 다른 요소(이 경우는 Z-Buffer인가?)도 영향을 미칠 수 있다는 것을 염두해둬야 할 것 같다
마무리
검증은 대략 이정도로 끝났지만 결국 텍스처 합치기 기능은 보류가 되었다
그건 그렇고 ShaderGUI를 이용하면 백그라운드에서 자동으로 합치기 기능을 넣을 수 있지 않을까?
참고 사이트
이번엔 딱히 없다
'Unity > URP or Shader 관련' 카테고리의 다른 글
버텍스 ID 3개로 풀 스크린 쿼드를 대체할 수 있다고!! (0) | 2024.02.27 |
---|---|
URP에서 팔레트 스왑(Palette Swap) 기능 구현하기 (0) | 2024.02.19 |
카메라와 오브젝트가 가까울 때 Dithering해서 반투명처럼 보이게 하는 셰이더 구현해보기! (0) | 2024.02.01 |
URP에서 깊이를 기록하는 Depth Map 셰이더 만들어보기! + After Effect의 이미지의 레벨 조정(Levels adjustment) 흉내기기! (0) | 2024.01.23 |
Unity의 Depth Texture에는 실제로 어떤 오브젝트가 기록되는 걸까?(feat. SSAO) (0) | 2024.01.22 |