일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- Cartoon Rendering
- Windows Build
- 개인 바이트
- Three(Two) Tone Shading
- ColorGradingLutPass
- ASW(Application SpaceWarp)
- 게임 수학
- Toon Shader
- 프로그래밍 기초
- AppSW
- OculusMotionVectorPass
- 벡터
- Specular
- Rim Light
- Virtual Byte
- 메모리 누수
- Cell Look
- 가상 바이트
- URP
- VR
- 작업 집합
- URP로 변경
- C언어
- 3d
- Cell Shader
- Private Bytes
- working set
- Today
- Total
WinCNT
커스텀 셰이더에 높이 안개(Height Fog) 구현해보기! 본문
서론
게임에서 사용되는 안개 효과는 여러 종류가 있는데, 그 중에 높이 안개(Height Fog)라는 것이 있다
Exponential Height Fog User Guide
기본적으로는 이름 그대로 높은 고도에서 낮은 고도를 바라볼 때 보이는 구름과도 같은 안개를 구현한 효과이다
그런데 이를 잘 활용하면 고도가 딱히 높지 않아도 발 밑에 안개를 형성해서, 늪지대나 불온한 분위기 등등 여러 응용이 가능해지기 때문에 아트 팀의 요망에 따라 구현하기로 했다
왜 반투명 안 써요?
그야 VR에서는 반투명이 특히나 더 무겁기 때문이지!
Fog는 기본적으로 Lerp로 색 보간을 해서 구현하는 경우가 많기에 일반적으로는 반투명보다 가벼울 것이다
Volumetric fog 같은 특정 영역에만 영향을 미치고 싶은 거면 이야기는 좀 다르겠지만…암튼!
Height Fog 구현을 위한 원리
기본적인 원리는 아래 사이트를 참고했다
高さの影響を受けるフォグ 〜「低い所にガスが溜まってる」感を出す〜 - KAYAC engineers' blog
위의 사이트에는 Height Fog에 대한 구현 방법들과 원리 등이 소개되어 있다
참가로 마지막 방식은 필자가 아직도 잘 모르는 미적분이나 그런 복잡한 이야기가 나와서 이해하는 데 고생했지만, 결론부터 말하자면 그 방식은 기각했다
실제로 만들어보니 물리적으로 올바른 안개가 만들어지긴 했지만, 원하는 표현을 나타내기에는 좀 직관적이지 않았기 때문…
Max 높이를 설정할 수 있도록! 그리고 Noise로 좀 더 Fog처럼!
이번의 Height Fog의 목적은 늪지대나 불온한 분위기를 나타내기 위한 것이었으므로 물리적으로 올바른 안개보다는, 직관적으로 Max 높이를 설정할 수 있는 방법으로 구현하고자 하였다
그리고 안개가 흐트러지는 표현도 하고 싶었으므로 노이즈로 높이를 랜덤하게 바꿀 수 있도록 했다
우선은 샘플 코드!
여러모로 생략하긴 했지만 아무튼 샘플 코드!
Properties
{
// ...생략...
[Header(Height Fog)][Space(10)]
[Toggle] _HEIGHT_FOG("HeightFog On/Off", Float) = 0.0
_MaxFogHeight("Max Fog Height", float) = 1.0
_HeightFogDensity("Height Fog Density", Range(0.0, 1.0)) = 0.0
[HDR] _HeightFogColor ("Height Fog Color", Color) = (0.5, 0.5, 0.5, 1)
[NoScaleOffset] _HeightFogNoise ("Height Fog Noise", 2D) = "black" { }
_HeightFogNoisePower("Noise Power", Range(0.0, 5.0)) = 1.0
_HeightFogNoiseSpeedX("Noise Speed Axis X", Range(-1.0, 1.0)) = 0.0
_HeightFogNoiseSpeedY("Noise Speed Axis Y", Range(-1.0, 1.0)) = 0.0
}
SubShader
{
Tags
{
"RenderType"="Opaque"
"RenderPipeline"="UniversalPipeline"
}
Pass
{
Name "Universal Forward"
Tags
{
"LightMode"="UniversalForward"
}
Cull [_CullMode]
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
// 추가!
#pragma multi_compile __ _HEIGHT_FOG_ON
CBUFFER_START(UnityPerMaterial)
//...생략...
// Height Fog
sampler2D _HeightFogNoise;
float _HeightFogNoisePower;
float _HeightFogNoiseSpeedX;
float _HeightFogNoiseSpeedY;
half4 _HeightFogColor;
float _HeightFogDensity;
float _MaxFogHeight;
//...생략...
CBUFFER_END
// Height Fog를 계산하는 함수!
float3 MixUniformHeightFog(
float3 color,
float3 heightFogcolor,
float3 objectPos,
float3 cameraPos,
float heightFogDensity,
float maxFogHeight,
sampler2D noiseTex,
float2 noiseUV,
float noiseUVScrollX,
float noiseUVScrollY,
float noisePower)
{
noiseUV.x += frac(_Time.y * noiseUVScrollX);
noiseUV.y += frac(_Time.y * noiseUVScrollY);
const float noise = tex2D(noiseTex, noiseUV).r * noisePower;
// noise로 안개의 적용 위치를 조정
objectPos = objectPos + noise;
float3 camToObj = cameraPos - objectPos;
float t = 0.0;
const float a = objectPos.y < maxFogHeight ? 0 : cameraPos.y;
const float b = cameraPos.y > maxFogHeight ? maxFogHeight : -maxFogHeight;
const float c = cameraPos.y > maxFogHeight ? -objectPos.y : 0;
float d = a + b + c;
// 오브젝트도 카메라도 안개 속에 있는 경우
d = max(objectPos.y, cameraPos.y) < maxFogHeight ? camToObj.y : d;
// 오브젝트도 카메라도 안개 밖에 있는 경우
d = min(objectPos.y, cameraPos.y) > maxFogHeight ? 0 : d;
t = saturate(d / camToObj.y);
const float distance = length(camToObj) * t;
const float heightFogFactor = exp2(-heightFogDensity * distance * LOG2_E);
color = lerp(heightFogcolor.rgb, color, heightFogFactor);
return color;
}
Varyings vert(Attributes IN)
{
//...생략...
// World Position
OUT.PositionWS = TransformObjectToWorld(IN.positionOS.xyz);
//...생략...
}
half4 frag(Varyings IN) : SV_Target
{
half4 _FinalColor = half4(0.0, 0.0, 0.0, 1.0);
//Fragment Shader의 계산을 한다!
//...생략...
// 마지막에 Height Fog를 계산한다!
#ifdef _HEIGHT_FOG_ON
_FinalColor.rgb = MixUniformHeightFog(
_FinalColor,
_HeightFogColor.rgb,
IN.PositionWS,
_WorldSpaceCameraPos,
_HeightFogDensity,
_MaxFogHeight,
_HeightFogNoise,
IN.texcoord,
_HeightFogNoiseSpeedX,
_HeightFogNoiseSpeedY,
_HeightFogNoisePower
);
#endif
return _FinalColor;
}
ENDHLSL
}
}
일반적인 셰이더에서도 하는 처리들은 생략하도록 했다
그 외에는 Height Fog에 필요한 파라미터들을 Properties와 CBUFFER에 선언하고 Height Fog 함수를 추가한 정도?
버텍스 셰이더에서는 월드 포지션을 계산하고, 프래그먼트 셰이더의 마지막 부분에서 Height Fog 함수를 호출했다
그리고 의미 없는 발악일 순 있는데 Height Fog 함수는 가능한 if문을 생략하도록 노력하였다
결과
노이즈가 없으면 일단 이런 느낌이다
모처럼이니 노이즈도 설정해보자!
이번에 사용한 노이즈는 그 유명한 Perlin 노이즈이다
노이즈를 쓰니 좀 더 안개다워졌다!
이건 실제 에셋에 적용해본 샘플이다
마무리
다 만들고 나니 각각의 머티리얼에 Height Fog의 파라미터가 있는 게 좀 걸린다
차라리 Global Shader Variables로 변경해서 스크립트로 컨트롤할 수 있게 하는 게 좋으려나?
이 부분은 나중에 다시 수정해보자
참고 사이트
Exponential Height Fog User Guide
高さの影響を受けるフォグ 〜「低い所にガスが溜まってる」感を出す〜 - KAYAC engineers' blog
'Unity > URP or Shader 관련' 카테고리의 다른 글
포션의 의사 액체(Liquid) 셰이더 만들기! (0) | 2024.12.08 |
---|---|
높이 안개(Height Fog)를 Global Shader Variable로 변경하기! (0) | 2024.09.18 |
Visual Effect Graph(VFX Graph)의 도입 체험기! (0) | 2024.07.09 |
Alpha 값으로 Blue Noise를 이용한 Dither를 해보자! (0) | 2024.06.24 |
Fill Amount 기능이 있는 간단(?) Dial Gauge UI(가칭) 셰이더 만들어보기! (1) | 2024.06.16 |