12 – 03 홀로그램(Hologram) 만들기 (Unity Shader)

12 – 03 홀로그램(Hologram) 만들기 (Unity Shader)

Rim 라이트를 구현할 수 있게 되었으니 알파를 사용하여 홀로그램을 만들 수 있습니다.

처음은 Lambert 기본형에서 시작합니다.

Shader "Custom/Holo"
{
    Properties
    {
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        #pragma surface surf Lambert noambient

        #pragma target 3.0
        sampler2D _MainTex;

        struct Input
        {
            float2 uv_MainTex;
        };

        void surf (Input IN, inout SurfaceOutput o)
        {

            fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
            o.Albedo = c.rgb;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

다음은 Fresnel 기본 연산을 구현합니다.

잠시 Albedo 는 끄고 Emission 으로 결과를 확인하겠습니다. 

이제 반투명으로 구현하겠습니다.

홀로그램의 핵심원리는 ” Rim 라이트 연산을 알파 채널에 넣는다 “ 입니다.

반투명 구현은 앞 부분의 불을 만들 때 사용한 방식입니다.

마지막으로 Emission에 원하는 색상을 넣어주면 됩니다.

다음은 두께를 조절하게 해줍니다.

입안이 이상하게 구현된 것은 캐릭터가 입안까지 모델링이 되었기 때문입니다.

반투명이 되면서 반대편의 오브젝트나 안쪽의 오브젝트가 비쳐 보이는 것은 나중에 Z버퍼를 이용한 2pass 쉐이더를 사용하여 해결합니다.

전체 코드

Shader "Custom/Holo"
{
    Properties
    {
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _RimColor("RimColor", Color) = (1,1,1,1)
        _RimPower("RimPower", Range(1,10)) = 3
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent" }
        LOD 200

        CGPROGRAM
        #pragma surface surf Lambert noambient alpha:fade

        #pragma target 3.0
        sampler2D _MainTex;
        float4 _RimColor;
        float _RimPower;

        struct Input
        {
            float2 uv_MainTex;
            float3 viewDir;
        };

        void surf (Input IN, inout SurfaceOutput o)
        {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
            float rim = saturate(dot(o.Normal, IN.viewDir));
            rim = pow(1 - rim, _RimPower);
            o.Emission = _RimColor.rgb;
            o.Alpha = rim;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

기본형은 만들었지만 이것만으로는 부족합니다.

다양한 효과를 넣어 봅시다.

첫번째로 이 홀로그램을 깜빡이게 표현합니다.

Sin(_Time.y) 를 이용합니다.

깜빡임 속도 증가

그런데, 이러면 깜빡일 떄 안보이는 시간이 너무 길게 나타납니다.

이것은 sin 또는 cos 곡선에서 일어나는 당연한 결과 입니다.

이 곡선들이 -1 ~ 1을 반복하다 보니 그림처럼 0 이하로 내려가는 부분이 절반이기 때문입니다.

saturate 해주는 것은 0 이나 – 1 이나 우리눈에 안보이는 것은 같기 때문에 의미가 없습니다.

첫번째로  * 0.5 + 0.5 를 사용하는 방법이 있습니다.

하프 램버트에서 사용했던 공식을 이용하면 -1 ~ 1을 0 ~ 1로 변화시켜주므로 부드럽게 지속적으로 반짝이는 곡선을 구할 수 있게 됩니다.

sin(_Time.y) * 0.5 + 0.5;

또 다른 방법으로는 abs () 함수를 사용하여 모든 음수를 양수로 변화시킬 수 있습니다.

abs(sin(_Time.y));

Shader "Custom/Holo"
{
    Properties
    {
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _RimColor("RimColor", Color) = (1,1,1,1)
        _RimPower("RimPower", Range(1,10)) = 3
        _HoloTwinkling("HoloTwinkling",Range(1,5)) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent" }
        LOD 200

        CGPROGRAM
        #pragma surface surf Lambert noambient alpha:fade

        #pragma target 3.0
        sampler2D _MainTex;
        float4 _RimColor;
        float _RimPower;
        float _HoloTwinkling;

        struct Input
        {
            float2 uv_MainTex;
            float3 viewDir;
        };

        void surf (Input IN, inout SurfaceOutput o)
        {

            fixed4 c = tex2D (_MainTex, IN.uv_MainTex);
            float rim = saturate(dot(o.Normal, IN.viewDir));
            rim = pow(1 - rim, _RimPower);
            o.Emission = _RimColor.rgb;

            //o.Alpha = rim * sin(_Time.y * _HoloTwinkling);
            //o.Alpha = rim * sin(_Time.y * _HoloTwinkling) *0.5 + 0.5;
            o.Alpha = rim * abs(sin(_Time.y * _HoloTwinkling));
        }
        ENDCG
    }
    FallBack "Diffuse"
}

다음은 홀로그램의 줄무늬가 올라가는 효과를 만들겠습니다.

버텍스 컬러를 이용하여 아래에서 위로 밝아지게 칠할 수도 있고, 두번째 UV를 펴는 방법도 있습니다.

여기서는 Input 구조체에서 다른 값을 이용하겠습니다.

https://docs.unity3d.com/Manual/SL-SurfaceShaders.html <- 영문

https://docs.unity3d.com/kr/2021.3/Manual/SL-SurfaceShaders.html <-한글

표면 셰이더 입력 구조

입력 구조 Input에는 일반적으로 셰이더가 필요로 하는 텍스처 좌표가 있습니다. 텍스처 좌표의 이름은 텍스처 이름 앞에 “uv”가 붙는 형식으로 지어야 합니다(또는 두 번째 텍스처 좌표 세트를 사용하려면 “uv2”로 시작해야 합니다).

다음 값을 입력 구조에 추가할 수 있습니다.

float3 viewDir – 뷰 방향을 포함합니다. 패럴랙스 이펙트, 림 조명 등의 컴퓨팅에 사용합니다.

float4와 COLOR 시맨틱 – 보간된 버텍스당 컬러를 포함합니다.

float4 screenPos – 반사 또는 스크린 공간 효과를 위한 스크린 공간 포지션을 포함합니다. GrabPass에는 적합하지 않습니다. ComputeGrabScreenPos 함수를 사용하여 커스텀 UV를 직접 계산해야 합니다.

float3 worldPos – 월드 공간 포지션을 포함합니다.

float3 worldRefl – 표면 셰이더가 o.Normal에 기록하지 않는 경우 월드 반사 벡터를 포함합니다. 예를 들어 리플렉트-디퓨즈(Reflect-Diffuse) 셰이더가 있습니다.

float3 worldNormal – 표면 셰이더가 o.Normal에 기록하지 않는 경우 월드 노멀 벡터를 포함합니다.

float3 worldRefl; INTERNAL_DATA – 표면 셰이더가 o.Normal에 기록하는 경우 월드 반사 벡터를 포함합니다. 픽셀당 노멀 맵을 기반으로 반사 벡터를 얻으려면 WorldReflectionVector (IN, o.Normal)를 사용해야 합니다. 예를 들어 리플렉트-범프드(Reflect-Bumped) 셰이더가 있습니다.

float3 worldNormal; INTERNAL_DATA – 표면 셰이더가 o.Normal에 기록하는 경우 월드 노멀 벡터를 포함합니다. 픽셀당 노멀 맵에 기반한 노멀 벡터를 얻으려면 WorldNormalVector (IN, o.Normal)를 사용해야 합니다.

이번에는 float3 worldPos를 사용합니다.

해당 변수의 기능을 확인하기 위해서 코드를 변경합니다.

해당 x, y, z 가 각각 r, g, b 에 대입하여 나타납니다.

우리는 상/하의 값인 Y만 필요하기 때문에 g (y)만 출력합니다.

위의 코드로는  위로 올라가면 1이 넘는 값이 될 것이고 아래로 내려가면 -1의 아래 값으로 변하게 됩니다.

이러한 상황에서 유용한 내장 함수는 frac ( ) 입니다.

frac ( ) 은 안에 들어온 숫자의 “소수점 부분” 만 리턴해 줍니다.

즉, 1.1 은 0.1을 리턴하고 1.9는 0.9를 리턴합니다.

이제 흰 부분을 줄여 주겠습니다.

그라디에이션에서 검정 부분을 확장시키고 흰 부분을 줄이는 것은 이전에  fresnel 을 배울 때 pow ( ) 함수를 이용하여 해결했습니다.

추가로 시간에 따라 위로 흘러가게 해줍니다.

NormalMap 적용 후 변경이 가능하게 프로퍼티로 설정

Shader "Custom/Holo"
{
    Properties
    {
        _BumpMap("Normal Map", 2D) = "white" {}
        _RimColor ("RimColor", Color) = (1,1,1,1)
        _RimPower ("RimPower", Range(1,10)) = 3
        _HoloTwinkling ("HoloTwinkling",Range(1,5)) = 1
        _HoloSpeed ("HoloSpeed",Range(1,5)) = 1
        _HoloInterval ("HoloInterval",Range(1,5)) = 5
        
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent" }
        LOD 200

        CGPROGRAM
        #pragma surface surf Lambert noambient alpha:fade

        #pragma target 3.0
        sampler2D _BumpMap;
        float4 _RimColor;
        float _RimPower;
        float _HoloTwinkling;
        float _HoloSpeed;
        float _HoloInterval;

        struct Input
        {
            float2 uv_BumpMap;
            float3 viewDir;
            float3 worldPos;
        };

        void surf (Input IN, inout SurfaceOutput o)
        {

            float3 d = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
            o.Normal = d;
            float rim = saturate(dot(o.Normal, IN.viewDir));
            rim = pow(1 - rim, _RimPower);
            o.Emission = pow (frac(IN.worldPos.g * _HoloInterval - _Time.y * _HoloSpeed), 5) + _RimColor.rgb;
            o.Alpha = rim * abs(sin(_Time.y * _HoloTwinkling));
        }
        ENDCG
    }
    FallBack "Transparent/Diffuse"
}

Transparent/Diffuse 는 그림자를 생성하지 않게 하도록 넣는 코드입니다.

원래 FallBack은 그래픽 카드에서 이 쉐이더 연산을 실패 했을 때의 대체 쉐이더를 적는 란이지만

이 책의 저자분은 그림자 연산할 때 이 FallBack에 씌여진 쉐이더가 영향을 끼치게 된다는 것을 발견했다고 합니다.

이 쉐이더는 마치 이펙트와 같은 쉐이더이기 때문에 그림자가 필요없으므로 해당 코드를 넣습니다.


홀로그램 2Pass

Shader "Custom/Holo"
{
    Properties
    {
        _BumpMap("Normal Map", 2D) = "white" {}
        _RimColor ("RimColor", Color) = (1,1,1,1)
        _RimPower ("RimPower", Range(1,10)) = 3
        _HoloTwinkling ("HoloTwinkling",Range(1,5)) = 1
        _HoloSpeed ("HoloSpeed",Range(1,5)) = 1
        _HoloInterval ("HoloInterval",Range(1,5)) = 5
        
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent" }

        ZWrite On
        ColorMask 0

        LOD 200

        CGPROGRAM
        #pragma surface surf _NoLight
        #pragma target 3.0

        struct Input
        {
            float4 color:COLOR;
        };

        void surf (Input IN, inout SurfaceOutput o)
        {
        }

        float4 Lighting_NoLight(SurfaceOutput s, float3 lightDir, float atten) {
            return 0;
        }
        ENDCG

        ZWrite Off

        CGPROGRAM
        #pragma surface surf Lambert noambient alpha:fade

        #pragma target 3.0
        sampler2D _BumpMap;
        float4 _RimColor;
        float _RimPower;
        float _HoloTwinkling;
        float _HoloSpeed;
        float _HoloInterval;

        struct Input
        {
            float2 uv_BumpMap;
            float3 viewDir;
            float3 worldPos;
        };

        void surf (Input IN, inout SurfaceOutput o)
        {
            float3 d = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
            o.Normal = d;
            float rim = saturate(dot(o.Normal, IN.viewDir));
            rim = pow(1 - rim, _RimPower);
            o.Emission = pow (frac(IN.worldPos.g * _HoloInterval - _Time.y * _HoloSpeed), 30) + _RimColor.rgb;
            o.Alpha = rim * abs(sin(_Time.y * _HoloTwinkling));
        }
        ENDCG
    }
    FallBack "Transparent/Diffuse"
}

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

Scroll to Top