TECHARTNOMAD | MAZELINE.TECH

TECH.ART.FLOW.IO

[번역] Unity-URP-원신 스타일 프로시저럴 스카이박스(사례 정리)

jplee 2026. 2. 10. 23:46

역자의 말: 블랜더 커뮤니티에서 열심히 활동하고 있는 XIYAG 군의 블랜더 자료를 보던 중에 이 전의 글이 보여서 공유 해 보려고 준비 했습니다.  개인적으로 메시를 잘라서 쓰는 방식이 아니어도 될테니지만 아마도 pixel discard 에 대한 염려를 하는 스타일로 보입니다. 2018년부터 2020년 까지 개발하던 오픈월드 엠엠오 프로젝트에서도 이와 유사한 방식으로 회사에서 구현 했던 적이 있는데요. 다른 점이라면 SDF 맵을 사용해서 클라우드 커버리지를 제어 한다는 점인데 제목처럼 미호요에서 사용한 방식으로 알고 있어요. 아무튼 정성스럽게 작성 된 테크니컬 아티스트 " 新杨XIYAG" 군의 글 읽어보시죠.


저자: 新杨XIYAG

이 글(시리즈)은 Unity URP 렌더 파이프라인에서, 제가 원신 스타일의 프로시저럴 스카이박스를 만들었던 과정을 처음부터 끝까지 같이 한 번 훑어보는 기록입니다.

조금이라도 아이디어가 되면 좋겠어요.

혹시 글이나 코드에 오류가 있으면… 고수 분들이 꼭(!) 지적해 주세요 \(* ̄︶ ̄)

전체 프로젝트 영상 + 이미지 먼저 보여드릴게요

Unity-URP-프로시저럴 스타일라이즈드 스카이볼 (bilibili)

일몰


1단계 - DCC 툴에서 종이구름(카드형 구름) 만들기

먼저 추출해 둔 “종이구름”이 어떻게 생겼는지 봅시다!

한 장의 텍스처 안에 기본 구름 타입들이 쭉 들어있죠.

가로(2) × 세로(4) 배열이네요 ~φ(゜▽゜*)

그럼 제작 아이디어는 아주 명확해졌습니다.

Blender에서 평면 만들기

Blender에서 Plane(평면) 하나를 만들고,

새 평면

루프 컷

루프 컷을 해 줍니다.

  • 세로 1번
  • 가로 3번

UV 확인 후, 면별로 분리

UV를 확인해 보면 아주 딱 맞게 떨어집니다. 좋아요~

이제 각 면을 분리(Separate) 해 줍니다.

필요한 카드만 골라 반구 형태로 배치

필요한 카드들만 골라서,

반구 형태로 감싸는 구름 군집을 만들어 주면 됩니다.

그리고 FBX로 뽑아서 Unity로 가져오면 끝! \^o^/()


2단계 - Unity에서 태양/달 방향 + 구름 셰이더 처리

SunDir / MoonDir 오브젝트 만들기

빈 오브젝트 2개를 만들고 이름을 이렇게 합니다.

  • SunDir
  • MoonDir

이 둘은 “가상 태양/달의 방향 벡터” 역할을 하게 됩니다.

스크립트: DirectionToSkybox

스크립트를 하나 만들어요. 이름은 DirectionToSkybox.

using UnityEngine;

[ExecuteAlways]
public class DirectionToSkybox : MonoBehaviour
{
    public GameObject sun; // 태양 방향을 나타내는 빈 오브젝트
    public GameObject moon; // 달 방향을 나타내는 빈 오브젝트

    public Material targetMaterialCloudTA; // 구름 머티리얼 A
    public Material targetMaterialCloudTB; // 구름 머티리얼 B

    public string sunDirectionPropertyName = "_SunDirection";
    public string moonDirectionPropertyName = "_MoonDirection";

    void Update()
    {
        if (sun)
        {
            Vector3 sunDirection = -sun.transform.forward.normalized;
            targetMaterialCloudTA.SetVector(sunDirectionPropertyName, sunDirection);
            targetMaterialCloudTB.SetVector(sunDirectionPropertyName, sunDirection);
        }

        if (moon)
        {
            Vector3 moonDirection = -moon.transform.forward.normalized;
            targetMaterialCloudTA.SetVector(moonDirectionPropertyName, moonDirection);
            targetMaterialCloudTB.SetVector(moonDirectionPropertyName, moonDirection);
        }
    }
}

입력 오브젝트 만들기

빈 오브젝트를 하나 만들고 이름을

  • SunDir_MoonDir_Input

으로 한 뒤, 방금 스크립트를 붙여 줍니다.

그리고

  • SunDir
  • MoonDir
  • 구름 머티리얼 A/B

를 할당하면 됩니다.


구름 셰이더 (핵심 부분만 먼저)

Properties

_SunDirection, _MoonDirection은 스크립트로 실시간 갱신됩니다.

Properties
{
    [HDR]_CloudColorD("태양 근처 구름색 B", color) = (1,1,1,1)
    [HDR]_CloudColorB("태양 근처 구름색 B", color) = (1,1,1,1)

    [HDR]_CloudColorC("태양에서 먼 구름색 A", color) = (1,1,1,1)
    [HDR]_CloudColorA("태양에서 먼 구름색 A", color) = (1,1,1,1)

    _CloudMap("CloudMap", 2D) = "white" {}
    _NoiseMap("NoiseMap", 2D) = "white" {}

    _Cloud_SDF_TSb("구름 크기 변화", Range(0.003, 1.5)) = 0.5
    [HDR]_Cloud_edgeColor("구름 가장자리 림라이트 색", color) = (1,1,1,1)

    _SunDirection("_SunDirection", Vector) = (0,0,0,0)
    _MoonDirection("_MoonDirection", Vector) = (0,0,0,0)
    _SunMoon("태양/달 단일 광원 스위치", Range(0, 1)) = 0
}

SubShader 기본 설정

Tags
{
    "Queue"="Transparent"
    "RenderType"="Transparent"
    "IgnoreProjector"="True"
    "RenderPipeline"="UniversalPipeline"
}
LOD 100
Blend SrcAlpha OneMinusSrcAlpha
ZWrite On
ZTest NotEqual

remap 함수

뒤에서 노이즈 값을 아주 작은 UV 오프셋으로 바꿀 때 씁니다.

half remap(half x, half t1, half t2, half s1, half s2)
{
    return (x - t1) / (t2 - t1) * (s2 - s1) + s1;
}

 

버텍스 셰이더

(모델이 UV를 2개 갖고 있어서 texUV2를 사용했습니다. 한 세트만 있으면 texUV1로 바꾸면 돼요.)

Varyings vert(Attributes v)
{
    Varyings o = (Varyings)0;

    o.positionCS = TransformObjectToHClip(v.positionOS.xyz);
    o.positionWS = TransformObjectToWorld(v.positionOS.xyz);

    o.uv = v.texUV2;

    o.noiseuv = TRANSFORM_TEX(v.texUV2, _NoiseMap);
    o.noiseuv = o.noiseuv * _NoiseMap_ST.xy + _NoiseMap_ST.zw + _Time.x * 0.05;

    return o;
}

여기서 노이즈 UV에 시간 값을 더해서 계속 흘러가게 만들었는데,

속도를 0.05로 “고정”해 둔 건 사실 좀 아쉽습니다.

따로 노이즈 스크롤 속도 프로퍼티를 빼서 조절 가능하게 하는 걸 추천해요.

프래그먼트 셰이더

half4 frag(Varyings i) : SV_Target
{
    float3 LightDirection = lerp(_SunDirection.xyz, _MoonDirection.xyz, _SunMoon);

    float3 SunDirection = clamp(dot(normalize(i.positionWS), LightDirection.xyz), 0, 1);
    SunDirection = pow(SunDirection, 2);

    float3 CloudColorAB = lerp(_CloudColorA, _CloudColorB, SunDirection.x);
    float3 CloudColorCD = lerp(_CloudColorC, _CloudColorD, SunDirection.x);

    float4 Noise = tex2D(_NoiseMap, i.noiseuv);
    float UVdisturbance = remap(Noise.b, 0, 1, 0, 0.03);

    float4 baseMap = tex2D(_CloudMap, i.uv + UVdisturbance);

    float baseMapSMstep = smoothstep(
        clamp((_Cloud_SDF_TSb - 0.08), 0, 1.5),
        _Cloud_SDF_TSb,
        baseMap.b
    );

    float3 CloudColor = lerp(CloudColorAB, CloudColorCD, baseMap.r);

    float3 EdgeColor = _Cloud_edgeColor * baseMap.g * SunDirection.x;
    CloudColor = CloudColor + EdgeColor;

    return float4(CloudColor, baseMapSMstep * baseMap.a);
}

  • LightDirection: 태양/달 “단일 광원” 방향 스위치(lerp 0~1)
  • SunDirection: 월드 위치(정규화)와 광원 방향의 dot로 태양 주변 원형 영향권을 만듦

CloudColorAB / CloudColorCD는 “태양에 가까운 정도”로 구름 색이 바뀌게 하는 부분입니다.

노이즈를 0~0.03로 remap해서 아주 작은 오프셋을 만들고,

그걸로 CloudMap UV를 살짝 흔들어 줍니다.

그럼 구름이 “살아있는 느낌”으로 흔들려요 \<( ̄︶ ̄)↗

CloudMap 채널 구성(이게 진짜 포인트)

CloudMap의 4채널을 따로 보면 바로 이해가 됩니다.

R 채널 (명암)

G 채널 (가장자리 림라이트)

B 채널 (구름 SDF)

A 채널 (구름 범위 클램프)

한 장의 텍스처 4채널로 이런 스타일의 종이구름을 만들 수 있다니…

박수칠만하죠 (☆▽☆)

SDF smoothstep 구간은 0~1.5 범위를 쓰고,

차이를 0.08 정도로 잡아서(저는 괜찮다고 느꼈어요) 처리했습니다.

clamp도 꼭 해 줘야 하고요. 0 아래로 내려가면 안 좋은 일 생깁니다…

_Cloud_SDF_TSb 값이 바뀌면 SDF 기반으로 구름이 설정한 방식대로 변형됩니다.

이제 R 채널(명암)에 위에서 만든 “태양 거리 기반 색”을 lerp해 주고,

가장자리 림라이트를 추가합니다.

여기선 태양 영향권(SunDirection)을 곱해 줬어요.

태양 근처에서만 림라이트가 살아야 더 그럴듯하니까요.

마지막 알파는 SDF 결과(baseMapSMstep)에 A 채널을 곱해서,

SDF 처리 후에도 “구름 영역”을 예쁘게 유지하게 합니다.


3단계 - Timeline 설정(구름 파라미터 애니메이션)

빈 오브젝트를 하나 만들고 이름은 대충 이렇게:

  • TL_controlling_Skybox_mat_parameters

그리고 이 오브젝트에 Timeline을 만들어요.

구름에 회전도 좀 주고(중요: DCC에서 카드들을 합친 뒤 Transform 적용도 잊지 말기!),

색도 키프레임 찍고,

크기 변화도 키프레임 찍고,

태양/달 회전도 키프레임 찍고…

느리더라도 꼼꼼하게 해야 예쁜 게 나옵니다 ㅠㅠ

완성! \^o^/()

이 글이 도움이 됐으면 좋겠습니다~


(부록) 종이구름 전체 셰이더 코드

아래는 글에서 사용한 Cloud 셰이더 전체 코드입니다.

Shader "Unlit URP Shader Cloud"
{
    Properties
    {
        [HDR]_CloudColorD("태양 근처 구름색 B", color) = (1,1,1,1)
        [HDR]_CloudColorB("태양 근처 구름색 B", color) = (1,1,1,1)

        [HDR]_CloudColorC("태양에서 먼 구름색 A", color) = (1,1,1,1)
        [HDR]_CloudColorA("태양에서 먼 구름색 A", color) = (1,1,1,1)

        _CloudMap("CloudMap", 2D) = "white" {}
        _NoiseMap("NoiseMap", 2D) = "white" {}

        _Cloud_SDF_TSb("구름 크기 변화", Range(0.003, 1.5)) = 0.5
        [HDR]_Cloud_edgeColor("구름 가장자리 림라이트 색", color) = (1,1,1,1)

        _SunDirection("_SunDirection", Vector) = (-0.26102,0.12177,-0.95762, 0)
        _MoonDirection("_MoonDirection", Vector) = (-0.33274, -0.11934, 0.93544, 0)
        _SunMoon("태양/달 단일 광원 스위치", Range(0, 1)) = 0
    }

    SubShader
    {
        Tags
        {
            "Queue"="Transparent"
            "RenderType"="Transparent"
            "IgnoreProjector"="True"
            "RenderPipeline"="UniversalPipeline"
        }

        LOD 100
        Blend SrcAlpha OneMinusSrcAlpha
        ZWrite On
        ZTest NotEqual

        Pass
        {
            Name "Unlit"

            HLSLPROGRAM
            #pragma prefer_hlslcc gles
            #pragma exclude_renderers d3d11_9x
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fog

            #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
            #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/UnityInstancing.hlsl"

            struct Attributes
            {
                float4 positionOS : POSITION;
                float2 texUV1 : TEXCOORD0;
                float2 texUV2 : TEXCOORD1;
            };

            struct Varyings
            {
                float4 positionCS : SV_POSITION;
                float2 uv : TEXCOORD0;
                float fogCoord : TEXCOORD1;
                float2 noiseuv : TEXCOORD2;
                float3 positionWS : TEXCOORD3;
            };

            CBUFFER_START(UnityPerMaterial)
                float4 _CloudColorA;
                float4 _CloudColorB;
                float4 _CloudColorC;
                float4 _CloudColorD;

                sampler2D _CloudMap;
                float4 _CloudMap_ST;

                sampler2D _NoiseMap;
                float4 _NoiseMap_ST;

                float _Cloud_SDF_TSb;

                float4 _Cloud_edgeColor;
                float3 _SunDirection;
                float3 _MoonDirection;
                float _SunMoon;
            CBUFFER_END

            half remap(half x, half t1, half t2, half s1, half s2)
            {
                return (x - t1) / (t2 - t1) * (s2 - s1) + s1;
            }

            Varyings vert(Attributes v)
            {
                Varyings o = (Varyings)0;

                o.positionCS = TransformObjectToHClip(v.positionOS.xyz);
                o.positionWS = TransformObjectToWorld(v.positionOS.xyz);

                o.uv = v.texUV2;
                o.noiseuv = TRANSFORM_TEX(v.texUV2, _NoiseMap);
                o.noiseuv = o.noiseuv * _NoiseMap_ST.xy + _NoiseMap_ST.zw + _Time.x * 0.05;

                return o;
            }

            half4 frag(Varyings i) : SV_Target
            {
                float3 LightDirection = lerp(_SunDirection.xyz,_MoonDirection.xyz,_SunMoon);

                float3 SunDirection = clamp((dot(normalize(i.positionWS),LightDirection.xyz)),0,1);
                SunDirection = pow(SunDirection,2);

                float3 CloudColorAB = lerp(_CloudColorA,_CloudColorB,SunDirection.x);
                float3 CloudColorCD = lerp(_CloudColorC,_CloudColorD,SunDirection.x);

                float4 Noise = tex2D(_NoiseMap,i.noiseuv);
                float UVdisturbance = remap(Noise.b,0,1,0,0.03);

                float4 baseMap = tex2D(_CloudMap, i.uv + UVdisturbance);

                float baseMapSMstep = smoothstep(clamp((_Cloud_SDF_TSb-0.08),0,1.5),_Cloud_SDF_TSb,baseMap.b);

                float3 CloudColor = lerp(CloudColorAB,CloudColorCD,baseMap.r);

                float3 EdgeColor = _Cloud_edgeColor * baseMap.g * SunDirection.x;
                CloudColor = CloudColor + EdgeColor;

                return float4(CloudColor, baseMapSMstep * baseMap.a);
            }

            ENDHLSL
        }
    }
}


4단계 - 스카이박스 본체(하늘 + 태양/달 + 별/은하)

종이구름을 만들었으니, 이제 스카이박스 본체를 보러 갑시다~

이 파트의 셰이더는 雪涛4 님 프로젝트를 참고했고,

저는 수정/조정하면서 원래 스카이볼(구체) 기반 구현을 스카이박스 쪽으로 옮긴 버전이라고 보시면 돼요.


(1) 버텍스 셰이더(상단 코드) - 컨텍스트 먼저

아래는 “버텍스 셰이더 위쪽”에 깔려 있는 프로퍼티/구조체/CBUFFER/유틸 함수들입니다.

문맥 확인용으로 먼저 던져둘게요.

Properties
{
    _upPartSunColor("상공(천정) 태양 근처 색", Color) = (0.00326,0.18243,0.63132,1)
    _upPartSkyColor("상공(천정) 태양에서 먼 색", Color) = (0.02948,0.1609,0.27936,1)
    _downPartSunColor("수평선 태양 근처 색", Color) = (0.30759,0.346,0.24592,1)
    _downPartSkyColor("수평선 태양에서 먼 색", Color) = (0.04305,0.26222,0.46968,1)

    _IrradianceMapR_maxAngleRange("하늘 주색 수직 변화 범위", Range(0, 1)) = 0.44837
    _mainColorSunGatherFactor("태양 근처 색 집중도", Range(0, 5)) = 0.31277

    _SunAdditionColor("태양 추가 포인트 색", Color) = (0.90409,0.7345,0.13709, 1)
    _SunAdditionIntensity("태양 추가 포인트 강도", Range(0, 3)) = 1.48499
    _IrradianceMapG_maxAngleRange("태양 추가 포인트 수직 변화 범위", Range(0.001, 1)) = 0.69804

    _SunRadius("태양 원반 크기", Range(0, 50)) = 1
    _SunInnerBoundary("태양 내부 경계", Range(0, 10)) = 1
    _SunOuterBoundary("태양 외부 경계", Range(0, 10)) = 1
    _sun_disk_power_999("태양 원반 power", Range(0, 1000)) = 1000
    _SunScattering("산란 확산", Range(0, 2)) = 1
    _sun_color_intensity("태양 원반 색 강도", Range(0, 10)) = 1.18529
    _sun_color("태양 원반 색", Color) = (0.90625, 0.43019, 0.11743, 1)
    _sun_color_Scat("일출/일몰 산란 색", Color) = (0.90625, 0.43019, 0.11743, 1)

    _MoonTex("달 텍스처", 2D) = "white"{}
    _MoonRadius("달 크기", Range(0, 10)) = 3
    _MoonMaskRadius("달 마스크 크기", Range(1, 10)) = 5
    _mainColorMoonGatherFactor("달 근처 색 집중도", Range(0, 5)) = 0.31277
    _MoonScatteringColor("달 산란 색", Color) = (1,1,1,1)
    _Moon_color("달 원반 색", Color) = (0.90625, 0.43019, 0.11743, 1)
    _Moon_color_intensity("달 색 강도", Range(0, 10)) = 1.18529

    _IrradianceMap("Irradiance Map", 2D) = "white"{}

    _starColorIntensity("별 색 강도", Range(0, 50)) = 22.7
    _starIntensityLinearDamping("별 가림(감쇠)", Range(0, 1)) = 0.80829

    _NoiseMap("NoiseMap", 2D) = "white" {}
    _StarDotMap("StarDotMap", 2D) = "white" {}
    StarColorLut("StarColorLut", 2D) = "white" {}

    [HideInInspector] _StarColorLut_ST("_NoiseMap_ST", Vector) = (0.5,1,0,0)
    [HideInInspector] _StarDotMap_ST("StarDotMap_ST", Vector) = (10,10,0,0)
    _NoiseSpeed("NoiseSpeed", Range(0, 1)) = 0.293

    _SunDirection("_SunDirection", Vector) = (-0.26102,0.12177,-0.95762, 0)
    _MoonDirection("_MoonDirection", Vector) = (-0.33274, -0.11934, 0.93544, 0)

    _galaxyTex("은하 텍스처", 2D) = "white"{}
    _galaxy_INT("은하 기본 강도", Range(0,1)) = 0.2
    _galaxy_intensity("은하 강도", Range(0,2)) = 1
}

// ... 생략 ...

#define UNITY_HALF_PI       1.57079632679f
#define UNITY_INV_HALF_PI   0.636619772367f
#define UNITY_PI            3.14159265359f

float4x4 LToW; // 달 방향 오브젝트의 localToWorldMatrix를 스크립트로 전달

float FastAcosForAbsCos(float in_abs_cos)
{
    float _local_tmp = ((in_abs_cos * -0.0187292993 + 0.0742610022) * in_abs_cos - 0.2121143937) * in_abs_cos + 1.5707287788;
    return _local_tmp * sqrt(1.0 - in_abs_cos);
}

float FastAcos(float in_cos)
{
    float local_abs_cos = abs(in_cos);
    float local_abs_acos = FastAcosForAbsCos(local_abs_cos);
    return in_cos < 0.0 ? UNITY_PI - local_abs_acos : local_abs_acos;
}

(2) 버텍스 셰이더 본문

자, 이제 버텍스 셰이더 본문을 봅시다~

v2f vert(appdata v)
{
    v2f o = (v2f)0;

    VertexPositionInputs vertexInput = GetVertexPositionInputs(v.vertex);
    o.positionWS.xyz = vertexInput.positionWS;

    float3 _worldPos = mul(UNITY_MATRIX_M, float4(v.vertex.xyz, 1.0)).xyz;
    float3 NormalizeWorldPos = normalize(_worldPos);

    float4 _clippos = mul(UNITY_MATRIX_VP, float4(_worldPos, 1.0));
    o.positionCS = _clippos;

    o.UV = v.uv;

    // 별 점 맵 + 노이즈 UV
    o.Varying_StarColorUVAndNoise_UV.xy = TRANSFORM_TEX(v.uv.xz, _StarDotMap);
    o.Varying_StarColorUVAndNoise_UV.zw = v.uv * 20.0;

    float4 _timeScaleValue = _Time.y * _NoiseSpeed * float4(0.4, 0.2, 0.1, 0.5);

    o.Varying_NoiseUV_large.xy = (v.uv.xz * _NoiseMap_ST.xy) + _timeScaleValue.xy;
    o.Varying_NoiseUV_large.zw = (v.uv.xz * _NoiseMap_ST.xy * 2.0) + _timeScaleValue.zw;

    // 태양 방향 관련
    float3 SunDirection = dot(normalize(vertexInput.positionWS), _SunDirection.xyz);
    float SunDirectionRemapClamp = clamp((SunDirection * 0.5) + 0.5, 0, 1.0);

    // 위/아래(천정~수평선) 각도 계산
    float _miu = clamp(dot(float3(0,1,0), NormalizeWorldPos), -1, 1);
    float _angle_up_to_down_1_n1 = (UNITY_HALF_PI - FastAcos(_miu)) * UNITY_INV_HALF_PI;

    o.Varying_WorldPosAndAngle.xyz = NormalizeWorldPos;
    o.Varying_WorldPosAndAngle.w = _angle_up_to_down_1_n1;

    // IrradianceMap(G 채널) 샘플링
    float2 _irradianceMap_G_uv;
    _irradianceMap_G_uv.x = abs(_angle_up_to_down_1_n1) / max(_IrradianceMapG_maxAngleRange, 0.001f);
    _irradianceMap_G_uv.y = 0.5;

    float _irradianceMapG = tex2Dlod(_IrradianceMap, float4(_irradianceMap_G_uv, 0.0, 0.0)).y;

    float3 _sunAdditionPartColor = _irradianceMapG * _SunAdditionColor * _SunAdditionIntensity;

    float _upFactor = smoothstep(0, 1, clamp((abs(_SunDirection.y) - 0.2) * 10/3, 0, 1));
    float _VDotSunFactor = smoothstep(0, 1, (SunDirectionRemapClamp - 1) / 0.7 + 1);
    float _sunAdditionPartFactor = lerp(_VDotSunFactor, 1.0, _upFactor);

    float3 _additionPart = _sunAdditionPartColor * _sunAdditionPartFactor;

    o.Varying_IrradianceColor.xyz = _additionPart;

    return o;
}

기본적인 공간 변환 + UV 셋업

별 점 맵(_StarDotMap)과 노이즈 UV를 시간에 따라 흘려서, 별이 “반짝”하는 느낌을 만들 준비를 합니다.

_StarDotMap

위 코드는 프래그먼트 쪽에서 _StarDotMap과 노이즈를 어떻게 조합하는지와 이어지고, 결과적으로 별이 깜빡이는 효과를 만들게 됩니다~

여기서 _miu는 “월드 Up(0,1,0)”과 “정규화된 월드 위치”의 dot이고,

이를 FastAcos로 각도로 바꿔서 천정→수평선 방향의 수직 그라데이션 파라미터를 얻습니다.

FastAcos가 뭐냐면요

진짜 acos는 비싸니까, 근사식을 써서 빠르게 arccos 값을 얻는 함수입니다.

실시간 렌더링에서 이런 방식은 정말 자주 씁니다. 헤헤 ╰( ̄▽ ̄)


(3) IrradianceMap(조도 맵) 이해하기

_IrradianceMap은 이런 텍스처입니다.

R 채널

G 채널

_angle_up_to_down_1_n1 값을 U 좌표로 써서 샘플링하면 이렇게 나옵니다.

R
G

  • R: 하늘 “주색”의 수직(천정↔수평선) 전이
  • G: 태양/달 근처에서 수평선 부근의 전이(추가 포인트)

또 태양이 수평선에 가까운지 여부에 따라

“반구형 수평선광” ↔ “링 형태 수평선광”을 섞는 방식도 들어갑니다.


(4) 프래그먼트 셰이더 본문

코드 먼저 던지고(ヽ(ω`)), 그 다음에 하나씩 뜯어봅시다.

half4 frag(v2f i) : SV_Target
{
    float sunDist = distance(i.UV.xyz, _SunDirection.xyz);
    float MoonDist = distance(i.UV.xyz, _MoonDirection);

    float sunArea = 1 - (sunDist * _SunRadius);
    float moonArea = 1 - clamp((MoonDist * _MoonMaskRadius), 0, 1);

    float moonGalaxyMask = step(0.084, MoonDist);

    float sunArea2 = 1 - (sunDist * _SunScattering);
    float moonArea2 = 1 - (MoonDist * 0.5);
    moonArea2 = smoothstep(0.5, 1, moonArea2);

    float sunArea3 = 1 - (sunDist * 0.4);
    sunArea3 = smoothstep(0.05, 1.21, sunArea3);

    sunArea = smoothstep(_SunInnerBoundary, _SunOuterBoundary, sunArea);

    // 달 텍스처를 위한 UV 변환(행렬 LToW 사용)
    float3 MoonUV = mul(i.UV.xyz, LToW);
    float2 moonUV = MoonUV.xy * _MoonTex_ST.xy * _MoonRadius + _MoonTex_ST.zw;

    // 수평선 아래로 일/월이 내려가지 않게 마스크
    float _WorldPosDotUp = dot(i.Varying_WorldPosAndAngle.xyz, float3(0,1,0));
    float _WorldPosDotUpstep = smoothstep(0, 0.05, _WorldPosDotUp);

    float _WorldPosDotUpstep1 = 1 - abs(_WorldPosDotUp);
    _WorldPosDotUpstep1 = smoothstep(0.4, 1, _WorldPosDotUpstep1);

    float _WorldPosDotUpstep2 = clamp(0, 1,
        smoothstep(0, 0.01, _WorldPosDotUp) + smoothstep(0.5, 1, _WorldPosDotUpstep1)
    );

    float _WorldPosDotUp_Multi999 = _sun_disk_power_999;

    float4 moonTex = tex2D(_MoonTex, moonUV) * moonArea * _WorldPosDotUpstep;

    float4 galaxyTex = tex2D(_galaxyTex, i.UV.xz * _galaxyTex_ST.xy + _galaxyTex_ST.zw);

    sunArea = sunArea * _WorldPosDotUpstep;

    float3 _sun_disk = dot(
        min(1, pow(sunArea3, _WorldPosDotUp_Multi999 * float3(1, 0.1, 0.01))),
        float3(1, 0.16, 0.03)
    ) * _sun_color_intensity * _sun_color;

    float3 _sun_disk_sunArea = sunArea * _sun_color_intensity * _sun_color;
    _sun_disk = _sun_disk + _sun_disk_sunArea * 3;

    float _LDotDirClampn11_smooth = smoothstep(0, 1, sunArea3);

    // IrradianceMap(R 채널) 샘플
    float2 _irradianceMap_R_uv;
    _irradianceMap_R_uv.x = abs(i.Varying_WorldPosAndAngle.w) / max(_IrradianceMapR_maxAngleRange, 0.001f);
    _irradianceMap_R_uv.y = 0.5;

    float _irradianceMapR = tex2Dlod(_IrradianceMap, float4(_irradianceMap_R_uv, 0.0, 0.0)).x;

    // 태양 주색(상공/수평선) 섞기
    float _VDotSunDampingA = max(0, lerp(1, sunArea2, _mainColorSunGatherFactor));
    float _VDotSunDampingA_pow3 = _VDotSunDampingA * _VDotSunDampingA * _VDotSunDampingA;

    float3 _upPartColor = lerp(_upPartSkyColor, _upPartSunColor, _VDotSunDampingA_pow3);
    float3 _downPartColor = lerp(_downPartSkyColor, _downPartSunColor, _VDotSunDampingA_pow3);
    float3 _mainColor = lerp(_upPartColor, _downPartColor, _irradianceMapR);

    // 달 색(원반 + 산란)
    float _VDotMoonDampingA = max(0, lerp(1, moonArea2, _mainColorMoonGatherFactor));
    float _VDotMoonDampingA_pow3 = _VDotMoonDampingA * _VDotMoonDampingA;

    float SSS = clamp(_VDotSunDampingA_pow3 * _VDotSunDampingA * _VDotSunDampingA * _WorldPosDotUpstep1, 0, 1);
    SSS = smoothstep(0.02, 0.5, SSS);
    SSS = SSS * _WorldPosDotUpstep2;

    float3 SSSS = SSS * _sun_color_Scat;

    float3 FmoonColor = (moonTex.xyz * _Moon_color * _Moon_color_intensity) + _VDotMoonDampingA_pow3 * _MoonScatteringColor;

    float3 _day_part_color = (_sun_disk * _LDotDirClampn11_smooth) + i.Varying_IrradianceColor.xyz + _mainColor + FmoonColor;

    // 별(StarDotMap * Noise)
    float _starExistNoise1 = tex2D(_NoiseMap, i.Varying_NoiseUV_large.xy).r;
    float _starExistNoise2 = tex2D(_NoiseMap, i.Varying_NoiseUV_large.zw).r;
    float _starSample = tex2D(_StarDotMap, i.UV.xz * _StarDotMap_ST.xy + _StarDotMap_ST.zw).r;
    float _star = _starSample * _starExistNoise2 * _starExistNoise1;

    float _miuResult = i.Varying_WorldPosAndAngle.w * 1.5;
    _miuResult = clamp(_miuResult, 0.0, 1.0);

    float _star_intensity = _star * _miuResult;
    _star_intensity *= 3.0;

    float _starColorNoise = tex2D(_NoiseMap, i.Varying_StarColorUVAndNoise_UV.zw).r;
    float _starIntensityDamping = (_starColorNoise - _starIntensityLinearDamping) / (1.0 - _starIntensityLinearDamping);
    _starIntensityDamping = clamp(_starIntensityDamping, 0.0, 1.0);

    _star_intensity = _starIntensityDamping * _star_intensity;

    float2 _starColorLutUV;
    _starColorLutUV.x = (_starColorNoise * _StarColorLut_ST.x) + _StarColorLut_ST.z;
    _starColorLutUV.y = 0.5;

    float3 _starColorLut = tex2D(_StarColorLut, _starColorLutUV).xyz;
    float3 _starColor = _starColorLut * _starColorIntensity;

    float3 _finalStarColor = _star_intensity * _starColor * moonGalaxyMask;

    // 은하
    galaxyTex.w = pow(galaxyTex.w, 10);
    float3 galaxyColor = clamp((galaxyTex.xyz * galaxyTex.w * _WorldPosDotUp * _galaxy_INT * moonGalaxyMask * _galaxy_intensity), 0, 1);

    return float4(SSSS + _day_part_color + _finalStarColor + galaxyColor, 1);
}

태양은 “방향 벡터와의 거리” 기반으로 원반 영역을 잡고(smoothstep으로 부드럽게),

달도 비슷하지만 달은 텍스처가 있으니 UV를 맞춰줘야 합니다.


달 텍스처 UV 문제(핵심 포인트)

여기 부분은 蛋白胨 님 글을 참고했습니다.

핵심은 이것:

  • 스카이볼의 UV(=방향)를 방향광 공간으로 변환한다
  • 그 변환 행렬을 스크립트에서 transform으로 뽑아 셰이더에 넣는다

좋아… 그럼 이전 글에서 쓰던 DirectionToSkybox를 업그레이드합니다.

스크립트 개선: LToW 행렬 전달

using UnityEngine;

[ExecuteAlways]
public class DirectionToSkybox : MonoBehaviour
{
    public GameObject sun;
    public GameObject moon;

    public Material targetMaterial; // 스카이박스 머티리얼
    public Material targetMaterialCloudTA;
    public Material targetMaterialCloudTB;

    public string sunDirectionPropertyName = "_SunDirection";
    public string moonDirectionPropertyName = "_MoonDirection";

    void Start()
    {
        if (targetMaterial == null)
        {
            Debug.LogError("Please assign a target Skybox material.");
            return;
        }
    }

    void Update()
    {
        if (moon)
        {
            Matrix4x4 LtoW = moon.transform.localToWorldMatrix;
            targetMaterial.SetMatrix("LToW", LtoW);
        }

        if (targetMaterial == null)
            return;

        if (sun)
        {
            Vector3 sunDirection = -sun.transform.forward.normalized;
            targetMaterial.SetVector(sunDirectionPropertyName, sunDirection);
            targetMaterialCloudTA.SetVector(sunDirectionPropertyName, sunDirection);
            targetMaterialCloudTB.SetVector(sunDirectionPropertyName, sunDirection);
        }

        if (moon)
        {
            Vector3 moonDirection = -moon.transform.forward.normalized;
            targetMaterial.SetVector(moonDirectionPropertyName, moonDirection);
            targetMaterialCloudTA.SetVector(moonDirectionPropertyName, moonDirection);
            targetMaterialCloudTB.SetVector(moonDirectionPropertyName, moonDirection);
        }
    }
}

셰이더 쪽에도 float4x4 LToW를 추가하는 것 잊지 마세요.

결과는 이렇게:

텍스처 Wrap Mode는 Clamp로!

쿨…(≧▽≦)ゝ

여기서 _WorldPosDotUpstep 계열은 전부 “수평선 아래로 일/월이 내려가지 않게” 하는 마스킹을 위한 작업입니다.

태양 원반 자체 광

스카이박스 주색(상공/수평선 4색 혼합)

수평선 산란(시뮬레이션)

달 텍스처와 조명 범위

별 + 은하

별과 은하는 최종 결과에 “달 마스크”를 곱해줘야,

달 위에 별/은하가 덮이지 않습니다.

LUT는 노이즈를 UV.x로 넣어 별 색을 뽑습니다.

마지막으로 전부 합치면 이렇게 예쁜 하늘이 됩니다 o( ̄▽ ̄)

Timeline으로 스카이박스 머티리얼을 제어하기(커스텀 트랙)

앗… 종이구름은 오브젝트가 있으니 Timeline에서 바로 키프레임을 찍을 수 있는데,

스카이박스는 “씬 오브젝트”가 아니니까 그냥은 K를 못 찍어요.

그래서 Timeline 커스텀 트랙을 만들어서 스카이박스 머티리얼 파라미터를 K프레임으로 제어합니다.

1) MaterialProperty

using UnityEngine;

[System.Serializable]
public class MaterialProperty
{
    public enum PropertyType
    {
        Float,
        Color
    }

    public PropertyType type;
    public string propertyName;

    public AnimationCurve curve;
    public Gradient gradient;
}
  • 타입(Float/Color)
  • 셰이더 Properties의 propertyName
  • Float는 AnimationCurve
  • Color는 Gradient

2) MySkyboxBehaviour

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Playables;

public class MySkyboxBehaviour : PlayableBehaviour
{
    public List<MaterialProperty> properties;

    public override void ProcessFrame(Playable playable, FrameData info, object playerData)
    {
        Material material = playerData as Material;
        if (material == null) return;

        RenderSettings.skybox = material;

        float t = (float)(playable.GetTime() / playable.GetDuration()); // 0~1

        foreach (var property in properties)
        {
            if (property.type == MaterialProperty.PropertyType.Float)
            {
                float value = property.curve.Evaluate(t);
                material.SetFloat(property.propertyName, value);
            }
            else if (property.type == MaterialProperty.PropertyType.Color)
            {
                Color color = property.gradient.Evaluate(t);
                material.SetColor(property.propertyName, color);
            }
        }
    }
}

RenderSettings.skybox로 머티리얼을 바꿔치기

시간을 0~1로 정규화해서 Curve/Gradient에서 쓰기

3) MySkyboxAsset

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Playables;

public class MySkyboxAsset : PlayableAsset
{
    public List<MaterialProperty> properties;

    public override Playable CreatePlayable(PlayableGraph graph, GameObject owner)
    {
        var playable = ScriptPlayable<MySkyboxBehaviour>.Create(graph);
        var behaviour = playable.GetBehaviour();
        behaviour.properties = properties;
        return playable;
    }
}

4) MySkyboxTrack

using UnityEngine;
using UnityEngine.Timeline;

[TrackClipType(typeof(MySkyboxAsset))]
[TrackBindingType(typeof(Material))]
public class MySkyboxTrack : TrackAsset
{
}

Timeline에서 이렇게 쓰면 됩니다.

트랙 생성

스카이박스 머티리얼 바인딩

이제 타임라인에서 스카이박스 머티리얼 파라미터를 신나게 조절할 수 있습니다… (땀)

주의! Gradient 컬러 노드는 최대 8개

저는 신나게 색 조절하다가 9번째 컬러 노드 만들려는 순간 Unity가 멈췄습니다.

네… Gradient는 컬러 노드가 최대 8개입니다… (망연자실)

24시간 하늘을 다 커버하기엔 8개로 부족하죠.

그래서 트랙을 여러 개로 나눠야 합니다.

그리고 팁 하나:

  • 아래쪽 트랙 우선순위가 더 높습니다.
  • 트랙이 겹치면 아래 트랙 값이 이깁니다.

AnimationCurve의 0~1은 “해당 클립 길이 전체” 기준입니다.


(옵션) Timeline 전체 속도 조절

using UnityEngine;
using UnityEngine.Playables;

public class TimelineConfig : MonoBehaviour
{
    [Range(0f, 10f)]
    public float m_speed = 1.0f;

    private PlayableDirector playableDirector;

    void Start()
    {
        playableDirector = GetComponent<PlayableDirector>();
        SetPlayableSpeed();
    }

    private void SetPlayableSpeed()
    {
        if (playableDirector != null)
        {
            var playableGraph = playableDirector.playableGraph;

            if (!playableGraph.IsValid())
            {
                playableDirector.RebuildGraph();
            }

            if (playableGraph.IsValid())
            {
                playableDirector.playableGraph.GetRootPlayable(0).SetSpeed(m_speed);
            }
        }
    }
}

대! 완! 성!


(부록) 스카이박스 전체 셰이더

원문에 있는 전체 셰이더는 길이가 아주 길어서, 그대로 유지해서 붙여둘게요.

Shader "Unlit URP Shader"
{
    Properties
    {
        _upPartSunColor("상공 태양 근처 색", Color) = (0.00326,0.18243,0.63132,1)
        _upPartSkyColor("상공 태양에서 먼 색", Color) = (0.02948,0.1609,0.27936,1)
        _downPartSunColor("수평선 태양 근처 색", Color) = (0.30759,0.346,0.24592,1)
        _downPartSkyColor("수평선 태양에서 먼 색", Color) = (0.04305,0.26222,0.46968,1)
        _IrradianceMapR_maxAngleRange("하늘 주색 수직 변화 범위", Range(0, 1)) = 0.44837
        _mainColorSunGatherFactor("태양 근처 색 집중도", Range(0, 5)) = 0.31277

        _SunAdditionColor("태양 추가 포인트 색", Color) = (0.90409,0.7345,0.13709, 1)
        _SunAdditionIntensity("태양 추가 포인트 강도", Range(0, 3)) = 1.48499
        _IrradianceMapG_maxAngleRange("태양 추가 포인트 수직 변화 범위", Range(0.001, 1)) = 0.69804

        _SunRadius("태양 원반 크기", Range(0, 50)) = 1
        _SunInnerBoundary("태양 내부 경계", Range(0, 10)) = 1
        _SunOuterBoundary("태양 외부 경계", Range(0, 10)) = 1
        _sun_disk_power_999("태양 원반 power", Range(0, 1000)) = 1000
        _SunScattering("산란 확산", Range(0, 2)) = 1
        _sun_color_intensity("태양 원반 색 강도", Range(0, 10)) = 1.18529
        _sun_color("태양 원반 색", Color) = (0.90625, 0.43019, 0.11743, 1)
        _sun_color_Scat("일출/일몰 산란 색", Color) = (0.90625, 0.43019, 0.11743, 1)

        _MoonTex("달 텍스처", 2D) = "white"{}
        _MoonRadius ("달 크기", Range(0, 10)) = 3
        _MoonMaskRadius("달 마스크 크기", range(1, 10)) = 5
        _mainColorMoonGatherFactor("달 근처 색 집중도", Range(0, 5)) = 0.31277
        _MoonScatteringColor("달 산란 색", Color) = (1,1,1,1)
        _Moon_color("달 원반 색", Color) = (0.90625, 0.43019, 0.11743, 1)
        _Moon_color_intensity("달 색 강도", Range(0, 10)) = 1.18529

        _IrradianceMap("Irradiance Map",2D)= "while"{}

           _starColorIntensity("별 색 강도", Range(0, 50)) = 22.7
        _starIntensityLinearDamping("별 가림(감쇠)", Range(0, 1)) = 0.80829

        _NoiseMap("NoiseMap", 2D) = "white" {}
        _StarDotMap("StarDotMap", 2D) = "white" {}
        StarColorLut("StarColorLut", 2D) = "white" {}
        [HideInInspector] _StarColorLut_ST("_NoiseMap_ST", Vector) = (0.5,1,0,0)

        [HideInInspector]_StarDotMap_ST("StarDotMap_ST", Vector) = (10,10,0,0)
        _NoiseSpeed("c_NoiseSpeed", Range( 0 , 1)) = 0.293

        _SunDirection("_SunDirection", Vector) = (-0.26102,0.12177,-0.95762, 0)
        _MoonDirection("_MoonDirection", Vector) = (-0.33274, -0.11934, 0.93544, 0)

        _galaxyTex("은하 텍스처", 2D) = "white"{}
        _galaxy_INT("은하 기본 강도", range(0,1)) = 0.2
        _galaxy_intensity("은하 강도", range(0,2)) = 1

    }
 
    SubShader
    {
        Tags { "Queue"="Geometry" 
               "RenderType" = "Opaque"
               "IgnoreProjector" = "True" 
               "RenderPipeline" = "UniversalPipeline" 
             }
        LOD 100
 
        Pass
        {
            Name "Unlit"
            HLSLPROGRAM
            // Required to compile gles 2.0 with standard srp library
            #pragma prefer_hlslcc gles
            #pragma exclude_renderers d3d11_9x
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fog
            #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
            #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/UnityInstancing.hlsl"
       
         
            #define UNITY_HALF_PI       1.57079632679f   //반원주율
            #define UNITY_INV_HALF_PI   0.636619772367f  //반원주율의 역수
            #define UNITY_PI            3.14159265359f   //원주율
            float4x4 LToW;                               //MoonDir//Matrix4x4 LtoW = moon.transform.localToWorldMatrix; for DirectionToSkybox;
          
            struct appdata
            {
                float4 vertex       : POSITION;
                float4 uv           : TEXCOORD0;
               
            };
 
            struct v2f
            {
                float4 Varying_StarColorUVAndNoise_UV : TEXCOORD0;
                float4 Varying_NoiseUV_large          : TEXCOORD1;
                float4 Varying_WorldPosAndAngle    : TEXCOORD2;
                float4 Varying_IrradianceColor        : TEXCOORD3;
                float3 Test                          : TEXCOORD4;
                float4 UV  : TEXCOORD5; 
                float4 positionWS : TEXCOORD6;
                float4 positionCS : SV_POSITION;


            
            };
 
            CBUFFER_START(UnityPerMaterial)
            float3  _upPartSunColor;
            float3  _upPartSkyColor;
            float3  _downPartSunColor;
            float3  _downPartSkyColor;
            float _IrradianceMapG_maxAngleRange;
            float3 _SunAdditionColor;
            float _SunAdditionIntensity;
            float  _sun_disk_power_999;
            float  _sun_color_intensity;
            float3 _sun_color;
            float _SunInnerBoundary;
            float _SunOuterBoundary;
            float _SunScattering;

            float _IrradianceMapR_maxAngleRange;
            float _mainColorSunGatherFactor;
      
            float _SunRadius;

            sampler2D _IrradianceMap;
            float4 _IrradianceMap_ST;
            sampler2D _MoonTex;
            float4 _MoonTex_ST;

            float _MoonRadius;
            float _MoonMaskRadius;

            float3 _SunDirection;
            float3 _MoonDirection;
            float  _mainColorMoonGatherFactor;
            float3 _MoonScatteringColor;
            float3  _Moon_color;
            float _Moon_color_intensity;
            float3 _sun_color_Scat;

            float _starColorIntensity;
            float _starIntensityLinearDamping;

            sampler2D _StarDotMap;
             float4 _StarDotMap_ST;

            float _NoiseSpeed;

             sampler2D _NoiseMap;
            float4 _NoiseMap_ST;

           sampler2D _StarColorLut;
            float4 _StarColorLut_ST;

            sampler2D _galaxyTex;
            float4 _galaxyTex_ST;
            float _galaxy_INT;
            float  _galaxy_intensity;


            CBUFFER_END

            float FastAcosForAbsCos(float in_abs_cos) //빠른 역코사인 함수
            {
                float _local_tmp = ((in_abs_cos * -0.0187292993068695068359375 + 0.074261002242565155029296875) * in_abs_cos - 0.212114393711090087890625) * in_abs_cos + 1.570728778839111328125;
                return _local_tmp * sqrt(1.0 - in_abs_cos);
            }

             float FastAcos(float in_cos)//빠른 역코사인 함수
            {
                float local_abs_cos = abs(in_cos);
                float local_abs_acos = FastAcosForAbsCos(local_abs_cos);
                return in_cos < 0.0 ?  UNITY_PI - local_abs_acos : local_abs_acos;
            }



       
 
            v2f vert(appdata v)
            {
                v2f o = (v2f)0;
                VertexPositionInputs vertexInput = GetVertexPositionInputs(v.vertex);
                o.positionWS.xyz = vertexInput.positionWS;
                float3 _worldPos = mul(UNITY_MATRIX_M, float4(v.vertex.xyz, 1.0)).xyz;
                float3 NormalizeWorldPos = normalize(_worldPos);
			        	float4 _clippos  = mul(UNITY_MATRIX_VP, float4(_worldPos, 1.0));

                o.positionCS= _clippos;
                o.UV = v.uv;


                o.Varying_StarColorUVAndNoise_UV.xy = TRANSFORM_TEX(v.uv.xz , _StarDotMap);
                o.Varying_StarColorUVAndNoise_UV.zw = v.uv * 20.0;

                float4 _timeScaleValue = _Time.y * _NoiseSpeed * float4(0.4, 0.2, 0.1, 0.5);
                
                o.Varying_NoiseUV_large.xy = (v.uv.xz * _NoiseMap_ST.xy) + _timeScaleValue.xy;
                o.Varying_NoiseUV_large.zw = (v.uv.xz * _NoiseMap_ST.xy * 2.0) + _timeScaleValue.zw;



              
                float3 SunDirection = dot(normalize(vertexInput.positionWS),_SunDirection.xyz);
                float SunDirectionRemapClamp =clamp((SunDirection * 0.5) + 0.5,0,1.0);

                float _miu = clamp( dot(float3(0,1,0), NormalizeWorldPos.xyz), -1, 1 );
                float _angle_up_to_down_1_n1 = (UNITY_HALF_PI - FastAcos(_miu)) * UNITY_INV_HALF_PI;
             
             
                o.Varying_WorldPosAndAngle.xyz = NormalizeWorldPos;
                o.Varying_WorldPosAndAngle.w   = _angle_up_to_down_1_n1;

                  float2 _irradianceMap_G_uv;
                  _irradianceMap_G_uv.x = abs(_angle_up_to_down_1_n1) / _IrradianceMapG_maxAngleRange;
                  _irradianceMap_G_uv.y = 0.5;

                  float _irradianceMapG = tex2Dlod(_IrradianceMap, float4( float2( _irradianceMap_G_uv.x,0.5), 0.0, 0.0 )).y;

                  float3 _sunAdditionPartColor = _irradianceMapG * _SunAdditionColor * _SunAdditionIntensity;

                     
                 float _upFactor = smoothstep(0, 1, clamp((abs(_SunDirection.y) - 0.2) * 10 / 3, 0, 1));
                 float _VDotSunFactor = smoothstep(0, 1, (SunDirectionRemapClamp -1)/0.7 + 1);
                 float _sunAdditionPartFactor = lerp(_VDotSunFactor, 1.0, _upFactor);
                 float3 _additionPart = _sunAdditionPartColor * _sunAdditionPartFactor;
                 float3 _sumIrradianceRGColor =  _additionPart;

                 o.Varying_IrradianceColor.xyz = _sumIrradianceRGColor;

                 o.Test.xyz = float3(_sumIrradianceRGColor );
           

 
                return o;
            }
 
            half4 frag(v2f i) : SV_Target
            {
             
      
                 float sunDist = distance(i.UV.xyz, _SunDirection.xyz);
                 float MoonDist = distance(i.UV.xyz,_MoonDirection);
                 float sunArea = 1 - (sunDist * _SunRadius);
                 float moonArea = 1 - clamp((MoonDist * _MoonMaskRadius),0,1);
                
                 float moonGalaxyMask = step(0.084,MoonDist);

                 float sunArea2 = 1- (sunDist*_SunScattering);//산란 확산
                 float moonArea2 = 1 - (MoonDist*0.5);
                 moonArea2 = smoothstep(0.5,1,moonArea2);
                 float sunArea3 = 1- (sunDist*0.4);
                 sunArea3 = smoothstep(0.05,1.21,sunArea3);
               
                 sunArea = smoothstep(_SunInnerBoundary,_SunOuterBoundary,sunArea);

                
                float3 MoonUV = mul(i.UV.xyz,LToW);
                float2 moonUV = MoonUV.xy * _MoonTex_ST.xy*_MoonRadius + _MoonTex_ST.zw;
               
               

                float  _WorldPosDotUp = dot(i.Varying_WorldPosAndAngle.xyz, float3(0,1,0));
                float  _WorldPosDotUpstep = smoothstep(0,0.05,_WorldPosDotUp);


               float _WorldPosDotUpstep1  = 1-abs(_WorldPosDotUp );
               _WorldPosDotUpstep1 = smoothstep(0.4,1,_WorldPosDotUpstep1 );
            
              
                float _WorldPosDotUpstep2 = clamp(0,1,smoothstep(0,0.01,_WorldPosDotUp)+ smoothstep(0.5,1,_WorldPosDotUpstep1)) ;

       
                float  _WorldPosDotUp_Multi999 = _sun_disk_power_999;

       
           
                  float4 moonTex = tex2D(_MoonTex, moonUV)*moonArea*_WorldPosDotUpstep; 

          
                  float4 galaxyTex = tex2D(_galaxyTex,i.UV.xz * _galaxyTex_ST.xy + _galaxyTex_ST.zw);
           
                  sunArea = sunArea *  _WorldPosDotUpstep;


                float3 _sun_disk = dot(min(1, pow(sunArea3 , _WorldPosDotUp_Multi999 * float3(1, 0.1, 0.01))),float3(1, 0.16, 0.03))* _sun_color_intensity * _sun_color;
                
                float3 _sun_disk_sunArea = sunArea * _sun_color_intensity * _sun_color ;
                _sun_disk = _sun_disk + _sun_disk_sunArea * 3;

         
                float _LDotDirClampn11_smooth = smoothstep(0, 1, sunArea3);
    

                float2 _irradianceMap_R_uv;
                    _irradianceMap_R_uv.x = abs(i.Varying_WorldPosAndAngle.w) / max(_IrradianceMapR_maxAngleRange,0.001f);
                    _irradianceMap_R_uv.y = 0.5;

                 float _irradianceMapR = tex2Dlod(_IrradianceMap, float4(_irradianceMap_R_uv, 0.0, 0.0)).x;


                  float _VDotSunDampingA = max(0, lerp( 1, sunArea2 , _mainColorSunGatherFactor ));
                  float _VDotSunDampingA_pow3 = _VDotSunDampingA * _VDotSunDampingA * _VDotSunDampingA;
              
                  float3 _upPartColor   = lerp(_upPartSkyColor, _upPartSunColor, _VDotSunDampingA_pow3);
                  float3 _downPartColor = lerp(_downPartSkyColor, _downPartSunColor, _VDotSunDampingA_pow3);
                  float3 _mainColor = lerp(_upPartColor, _downPartColor, _irradianceMapR);

                float _VDotMoonDampingA = max(0, lerp( 1, moonArea2 , _mainColorMoonGatherFactor ));
                  float _VDotMoonDampingA_pow3 = _VDotMoonDampingA *_VDotMoonDampingA;

                 float SSS = clamp( _VDotSunDampingA_pow3*_VDotSunDampingA *_VDotSunDampingA  * _WorldPosDotUpstep1 ,0,1);////개선 중
            
                  SSS = smoothstep(0.02,0.5, SSS );

                  SSS = SSS *  _WorldPosDotUpstep2;
      
                 float3 SSSS =  SSS *_sun_color_Scat;
                  
                  float3 FmoonColor =  (moonTex.xyz*_Moon_color*_Moon_color_intensity) + _VDotMoonDampingA_pow3*_MoonScatteringColor;

            
                float3 _day_part_color = (_sun_disk * _LDotDirClampn11_smooth ) + i.Varying_IrradianceColor.xyz + _mainColor+ FmoonColor;



                float _starExistNoise1 = tex2D(_NoiseMap, i.Varying_NoiseUV_large.xy).r;
                float _starExistNoise2 = tex2D(_NoiseMap, i.Varying_NoiseUV_large.zw).r;
                float _starSample = tex2D(_StarDotMap, i.UV.xz*_StarDotMap_ST.xy+_StarDotMap_ST.zw  ).r;
                float _star = _starSample * _starExistNoise2 * _starExistNoise1;

                float _miuResult = i.Varying_WorldPosAndAngle.w * 1.5;
                _miuResult = clamp(_miuResult, 0.0, 1.0);
                float _star_intensity = _star * _miuResult;
                _star_intensity *= 3.0;
                
   
                float _starColorNoise = tex2D(_NoiseMap, i.Varying_StarColorUVAndNoise_UV.zw).r;
                float _starIntensityDamping = (_starColorNoise - _starIntensityLinearDamping) / (1.0 -_starIntensityLinearDamping);
                _starIntensityDamping = clamp(_starIntensityDamping, 0.0, 1.0);
                _star_intensity = _starIntensityDamping * _star_intensity;
                
                float2 _starColorLutUV;
                _starColorLutUV.x = (_starColorNoise * _StarColorLut_ST.x) + _StarColorLut_ST.z;
                _starColorLutUV.y = 0.5;
                float3 _starColorLut = tex2D(_StarColorLut, _starColorLutUV).xyz;
                float3 _starColor = _starColorLut * _starColorIntensity;

                float3 _finalStarColor = _star_intensity * _starColor*moonGalaxyMask;

               galaxyTex.w = pow(galaxyTex.w,10);
              float3 galaxyColor =clamp((galaxyTex.xyz*galaxyTex.w*_WorldPosDotUp *_galaxy_INT*moonGalaxyMask*_galaxy_intensity),0,1);


             return float4(SSSS+_day_part_color+_finalStarColor+galaxyColor,1);
          

              
            }
            ENDHLSL
        }
    }
}

스카이박스 전체 셰이더는 본문 각 섹션에서 나눠서 설명했으므로, 한데 모어 보고 싶으시면 원문 링크의 전체 코드를 참고해 주세요.

마무리

 

GitHub - xinyangaa/Unity_URP_Genshin_Impact_Programmed_Skybox: Unity的URP中制作的仿原神程序化天空球

Unity的URP中制作的仿原神程序化天空球. Contribute to xinyangaa/Unity_URP_Genshin_Impact_Programmed_Skybox development by creating an account on GitHub.

github.com

 


원문 링크