일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- C언어
- Three(Two) Tone Shading
- VR
- Cell Look
- 벡터
- 가상 바이트
- 3d
- ASW(Application SpaceWarp)
- 프로그래밍 기초
- 게임 수학
- Rim Light
- AppSW
- URP
- working set
- Cartoon Rendering
- OculusMotionVectorPass
- URP로 변경
- 메모리 누수
- Private Bytes
- Specular
- 작업 집합
- Windows Build
- ColorGradingLutPass
- Cell Shader
- 개인 바이트
- Virtual Byte
- Toon Shader
- Today
- Total
WinCNT
Alpha 값으로 Blue Noise를 이용한 Dither를 해보자! 본문
서론
예전에 다른 프로젝트에서 Dithering를 구현해본 적이 있다
https://wincnt-shim.tistory.com/395
그 때는 카메라와의 거리에 따라 Bayer Matrix 알고리즘을 이용한 디더링이었다
하지만 이번에는 방식을 바꿔서 Alpha 값에 따라 Blue Noise를 통한 디더링를 구현하고자 한다!
Bayer Matrix의 문제점들
사실 Blue Noise 방식으로 변경한 건 Bayer Matrix의 단점들을 회피할 수 있지 않을까라는 기대감에서였다
예를 들어 다음의 텍스처를 Bayer Matrix 방식으로 디더링했다고 해보자
그러면 다음과 같은 결과가 나온다
우선 첫번째 문제점으로 경계 주변에서 이상한 아티팩트가 생긴다는 것이다
ditherOut = saturate(_BaseTex.a) < 1.0 ? ditherOut : 1.0;
일단 이것은 다음과 같이 경계값을 조정하니 눈에 띄는 아티팩트는 사라졌지만 미세하게는 존재하는 것 같기도 하지만...
아무튼 문제점 중 하나라 할 수 있다
ditherOut = saturate(_BaseTex.a) < 0.99 ? ditherOut : 1.0;
두번째 문제점은 Banding 현상이다
여기서의 Banding이란 디더링 패턴이 레이어를 이루는 것을 말한다
참고로 이 Banding 현상은 실제로 게임 플레이 중에도 본 적이 있었다
Blue Noise!
그래서 이번에 사용할 것은 Blue Noise를 이용한 Dithering!!
블루 노이즈가 무엇인지는 Wiki로 갈음하려고 한다
사실 필자도 잘 모른다
아무튼 세세한 건 넘어가고 Wiki의 Blue Noise 항목에 따르면, CG에서 블루 노이즈는 스파이크가 없는 노이즈를 의미하며 Dithering에 적합할 수 있다고 나와 있다
참고로 URP의 패키지에는 Blue Noise가 디폴트로 들어있다!
원래는 LOD Cross Fase에 사용되는 것 같은데 아무튼 감사히 잘 이용하도록 하자
샘플 코드!
아래는 그다지 관련 없는 부분은 최대한 생략한 샘플 코드이다
Properties
{
// ...생략...
_BlueNoiseTexture("Blue Noise", 2D) = "black" {}
// ...생략...
}
SubShader
{
// ...생략...
Pass
{
HLSLPROGRAM
// ...생략...
TEXTURE2D(_BlueNoiseTexture);
// ...생략...
half4 frag(Varyings IN) : SV_Target
{
const half4 _BaseTex = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.texcoord);
// Blue Noise Dither
float alpha = _BaseTex.a * _BaseColor.a;
half4 blueNoise = SAMPLE_TEXTURE2D(_BlueNoiseTexture, sampler_BaseMap, IN.texcoord * 8.0);
float dithered = blueNoise.a;
dithered = alpha - dithered;
dithered = alpha > 0.05 ? dithered : -1.0;
clip(dithered);
return _BaseTex;
}
ENDHLSL
}
}
기존의 Alpha에 blueNoise를 샘플링한 값을 빼주면 된다
물론 이번엔 딱히 참고한 게 없어서 필자는 쉽진 않았다
half4 blueNoise = SAMPLE_TEXTURE2D(_BlueNoiseTexture, sampler_BaseMap, IN.texcoord * 8.0);
여기서 UV좌표에 8.0을 곱하는 것은 Blue Noise 텍스처를 스케일링하기 위해서이다
(8.0은 매직 넘버라 딱히 적당한 숫자여도 된다)
이러면 해상도가 낮은 Blue Noise Texture라도 큰 차이 없이 사용할 수 있을테니 상황에 알맞게 사용하면 된다
dithered = alpha > 0.05 ? dithered : -1.0;
이 부분은 디더링한 알파 값이 일정 수준 이하가 되면 확실히 폐기하기 위한 코드이다
Base Color나 Texture에선 알파 값이 0인데도 Noise에 의해 일부 픽셀이 남는 케이스가 있어서 추가했다
결과
어디에 사용할 수 있을까?
아쉽게도 오브젝트의 반투명, 예를 들어 유리 등을 대체할 수는 없을 듯 했다
한 번 적용해봤는데 생각 이상으로 위화감이 엄청났다
대표적인 사용처로는 이전과 마찬가지로 카메라와 오브젝트가 가까울 때의 Dithering이라 생각한다
Bayer Matrix를 사용하는 것보다 사각적인 퀄리티는 더 높아진다고 생각한다
예를 들어 2024년 2월에 발매한 그랑블루 판타지 리링크에서도 Blue Noise를 이용한 Dithering으로 보이는 구현이 확인되었다
(카메라와 캐릭터 사이에 오브젝트가 끼어들면 그 Blue Noise Dithering으로 사라진다)
또는 원형 그림자(Shadow Blob)에도 사용할 수 있지 않을까?
그 외에는…아직 생각나는 건 없지만 언젠간 유용하게 쓰이지 않을까?
사족) URP 패키지에 있는 Blue Noise를 그대로 쓸 수 없나?
일단 샘플에서는 URP 패키지에 있는 Blue Noise를 복사해봐서 사용했지만, 이미 패키지에 있는 거 그대로 쓰고 싶다라는 생각이 들었을 수도 있겠다
결론부터 말하자면 가능했다
물론 Blue Noise가 설정된 변수는 internal(private이었나?)라서 바로 접근할 수는 없었기에 Reflection를 이용할 필요가 있었다
아래는 그 샘플 코드이다
필자는 최대한 셰이더와라는 범위 내에서 해결하고 싶었기에 ShaderGUI에서 구현해봤는데 문제 없이 작동했다
public class SampleShaderGUI : ShaderGUI
{
public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties)
{
if (materialEditor == null)
throw new ArgumentNullException("materialEditor");
_materialEditor = materialEditor;
Material material = materialEditor.target as Material;
FindProperties(properties);
if (_firstTimeApply)
{
RegisterHeader(material, _materialEditor);
_defaultInspector = false;
_firstTimeApply = false;
// URP Asset -> Renderer Data List -> Post Process Data에 있는
// Blue Noise를 Reflection을 통해서 가져온다
UniversalRenderPipelineAsset pipelineAsset = GraphicsSettings.currentRenderPipeline as UniversalRenderPipelineAsset;
if (pipelineAsset != null)
{
FieldInfo propertyInfo = pipelineAsset.GetType().GetField("m_RendererDataList", BindingFlags.Instance | BindingFlags.NonPublic);
ScriptableRendererData[] rendererDatas = (ScriptableRendererData[])propertyInfo.GetValue(pipelineAsset);
if (rendererDatas != null && rendererDatas.Length > 0)
{
var urd = rendererDatas[0] as UniversalRendererData;
if (urd != null)
blueNoise16LTex = urd.postProcessData.textures.blueNoise16LTex[0];
}
}
Texture texture = blueNoise16LTex;
material.SetTexture("_BlueNoiseTexture", texture);
}
}
// ...생략...
}
일단 빌드해서도 잘 작동하는 것까지는 확인!
물론 동적으로 키워드를 바꿀 때도 작동하는지까지는 확인하진 않았다…
(딱히 동적으로 머티리얼을 생성하는 게 아니면 되지 않을까? …아마)
마무리
만들어 놓은 코드를 보면 별 거 없지만 그를 위한 삽질이 좀 많았다
뭐 이런 것도 모두 결국에는 실력이 되는 것이겠지…?
참고 사이트
https://www.youtube.com/watch?v=VG-Ux8RHMoA
Shader Advanced - Color Banding and Dithering | GPU Shader Tutorial
Dither Gradient Shader - Godot Shaders
Blue-noise Dithered Samplingの実験をしてみた - 穴日記
Blue noise crossfade / dither / lod
'Unity > URP or Shader 관련' 카테고리의 다른 글
커스텀 셰이더에 높이 안개(Height Fog) 구현해보기! (0) | 2024.08.01 |
---|---|
Visual Effect Graph(VFX Graph)의 도입 체험기! (0) | 2024.07.09 |
Fill Amount 기능이 있는 간단(?) Dial Gauge UI(가칭) 셰이더 만들어보기! (1) | 2024.06.16 |
Fill Amount 기능이 있는 간단 Bar Gauge UI 셰이더 만들어보기! (0) | 2024.04.11 |
Vignette를 불투명 Mesh와 반투명 Mesh로 나눠서 구현해보자! (0) | 2024.04.08 |