WinCNT

커스텀 셰이더에 높이 안개(Height Fog) 구현해보기! 본문

Unity/URP or Shader 관련

커스텀 셰이더에 높이 안개(Height Fog) 구현해보기!

WinCNT_SSS 2024. 8. 1. 11:45

서론

게임에서 사용되는 안개 효과는 여러 종류가 있는데, 그 중에 높이 안개(Height Fog)라는 것이 있다

Exponential Height Fog User Guide

 

기본적으로는 이름 그대로 높은 고도에서 낮은 고도를 바라볼 때 보이는 구름과도 같은 안개를 구현한 효과이다

그런데 이를 잘 활용하면 고도가 딱히 높지 않아도 발 밑에 안개를 형성해서, 늪지대나 불온한 분위기 등등 여러 응용이 가능해지기 때문에 아트 팀의 요망에 따라 구현하기로 했다


왜 반투명 안 써요?

그야 VR에서는 반투명이 특히나 더 무겁기 때문이지!

Fog는 기본적으로 Lerp로 색 보간을 해서 구현하는 경우가 많기에 일반적으로는 반투명보다 가벼울 것이다

Volumetric fog 같은 특정 영역에만 영향을 미치고 싶은 거면 이야기는 좀 다르겠지만…암튼!


Height Fog 구현을 위한 원리

기본적인 원리는 아래 사이트를 참고했다

高さの影響を受けるフォグ 〜「低い所にガスが溜まってる」感を出す〜 - KAYAC engineers' blog

 

高さの影響を受けるフォグ 〜「低い所にガスが溜まってる」感を出す〜 - KAYAC Engineers' Blog

画面写真をクリックするとWebGLビルドに飛びます。 こんにちは。技術部平山です。 今回は「高さ方向に変化があるフォグ」について扱います。 Unityに標準では入っていない奴で、 「地面付

techblog.kayac.com

 

위의 사이트에는 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

 

高さの影響を受けるフォグ 〜「低い所にガスが溜まってる」感を出す〜 - KAYAC Engineers' Blog

画面写真をクリックするとWebGLビルドに飛びます。 こんにちは。技術部平山です。 今回は「高さ方向に変化があるフォグ」について扱います。 Unityに標準では入っていない奴で、 「地面付

techblog.kayac.com