커스텀 2D Light

승민·2026년 9월 15일

유니티에서 라이트 객체를 직접 생성했던 기록을 위한 글 입니다.

커스텀 2D Light

유니티 자체적으로 제공하는 라이트 기능들이 존재하지만, 빛을 만들고 적용하는 과정을 직접 다뤄보기 위해 2D 라이트를 직접 구현해보았다.

화면이 렌더링 되는 과정에서 빛을 생성하고 적용해주기 위해 커스텀 포스트 프로세싱 때와 같이 ScriptableRendererFeature, ScriptableRenderPass API들을 이용해주었다.

관련 코드

RendererFeature

커스텀 2D 라이트를 처리할 렌더 패스를 생성하고, 실행 시점을 설정하여 렌더링 과정에 등록하기 위한 코드이다.

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using UnityEngine.Rendering.RenderGraphModule;

//렌더링 과정에 추가하기 위한 ScriptableRendererFeature 상속
public class Custom2DLightFeature : ScriptableRendererFeature
{
    [System.Serializable]
    public class Settings
    {
        //커스텀 라이트 생성 타이밍과 빛으로 만들 객체의 레이어를 설정할 수 있게 열어둠
        public RenderPassEvent renderPassEvent = RenderPassEvent.BeforeRenderingTransparents;
        public LayerMask lightLayerMask = -1;
    }
    
    public Settings settings = new Settings();
    private Custom2DLightPass m_ScriptablePass;

    //렌더 패스가 실행될 타이밍을 지정하면서, 렌더 패스 생성 진행
    public override void Create()
    {
        m_ScriptablePass = new Custom2DLightPass(settings.lightLayerMask)
        {
            renderPassEvent = settings.renderPassEvent
        };
    }

    //생성한 렌더 패스를 렌더링 작업에 등록
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        renderer.EnqueuePass(m_ScriptablePass);
    }
    
    class Custom2DLightPass : ScriptableRenderPass
    //다음 파트에서 추가 설명 예정
}

RendererFeature 단계에서는 인스펙터 설정에서의 패스 실행 시점과 빛으로 사용할 객체의 레이어를 기반으로, 패스를 생성하고 이를 렌더링 작업에 등록해주도록 되어있다.

단순하게 생성과 등록만 진행하기 때문에 복잡한 부분들은 다음 파트부터 확인할 수 있다.

RenderPass

실제 렌더링 과정에서 순서에 맞게 실행 되었을 때, 그 안에서 실행할 작업을 다루는 코드이다.
렌더 패스 코드가 길어 가독성이 좋지 않기에 두단계로 나눠서 설명하려한다.

렌더 패스를 위한 각종 준비

이 단계에서는 렌더 패스 처리를 위해 각종 데이터들을 준비하는데, 이 준비 과정에서 라이트 결과를 담아둘 라이트 텍스쳐의 생성도 진행된다.

//렌더링 과정에서 실행하기 위해 ScriptableRenderPass 상속
class Custom2DLightPass : ScriptableRenderPass
{
    //렌더 패스에서 사용할 레이어, RendererFeature 단계에서 지정된 레이어가 그대로 저장됨
    private LayerMask m_LayerMask;
    
    //셰이더에서 사용할 패스의 이름
    private readonly ShaderTagId m_ShaderTagId = new ShaderTagId("Custom2DLight");
    
    //라이트 텍스처를 연결할 프로퍼티 ID 생성
    private static readonly int CustomLightTextureID = Shader.PropertyToID("_CustomLightTexture");
    
    //RendererFeature 단계에서 패스를 생성할 때 실행되는 레이어 등록용 생성자
    public Custom2DLightPass(LayerMask layerMask)
    {
        m_LayerMask = layerMask;
    }

    //실제 렌더링 과정에서 사용할 데이터를 전달하기 위한 패스 데이터
    private class PassData
    {
        //렌더링 컴포넌트 리스트에 대한 핸들
        public RendererListHandle rendererList;
    }
    
    //렌더링 과정에서 실제로 동작할 내용
    public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
    {
        //현재 프레임의 데이터들 가져오기
        //각각 카메라 관련 데이터, 렌더링 관련 데이터, 빛 관련 데이터
        UniversalCameraData cameraData = frameData.Get<UniversalCameraData>();
        UniversalRenderingData renderingData = frameData.Get<UniversalRenderingData>();
        UniversalLightData lightData = frameData.Get<UniversalLightData>();
        
        //현재 카메라 설정을 가져오고, 색상 포맷 지정 및 뎁스버퍼 사용 X
        RenderTextureDescriptor desc = cameraData.cameraTargetDescriptor;
        desc.colorFormat = RenderTextureFormat.ARGB32;
        desc.depthBufferBits = 0;

        //라이트 텍스쳐 베이스 생성
        TextureHandle customLightTexture = UniversalRenderer.CreateRenderGraphTexture
        (
            renderGraph,
            desc,
            "_CustomLightTexture",
            clear: false,
            filterMode: FilterMode.Bilinear,
            wrapMode: TextureWrapMode.Clamp
        );
        
        //래스터 패스 등록, 다음 파트에서 추가 설명 예정
        using (var builder = renderGraph.AddRasterRenderPass<PassData>("Custom 2D Light RenderGraph Pass", out var passData))
    }
}

렌더링 과정의 본격적인 동작 전, 각종 데이터들을 준비하는 과정이다.

RendererFeature에서의 레이어를 등록해주기도 하고, 추후 셰이더에서 사용할 패스의 이름을 지정하기도 하며, 라이트 텍스쳐를 연결할 프로퍼티 ID도 미리 생성해둔다.

렌더링 과정에서 사용될 패스 데이터도 준비하고, 현재 프레임의 각종 데이터들을 가져오기도 한다.

결국 이런 준비들은 모두 커스텀 라이트를 생성하기 위함으로, 생성된 커스텀 라이트 결과를 담을 빈 텍스쳐 또한 준비하고나서야 실제 동작 과정이 진행된다.

렌더 패스 실제 동작 과정

using (var builder = renderGraph.AddRasterRenderPass<PassData>("Custom 2D Light RenderGraph Pass", out var passData))
{
    //1. 결과가 저장될 렌더 타겟 연결
    builder.SetRenderAttachment(customLightTexture, 0, AccessFlags.Write);
    
    //2. Custom2DLight 패스로 렌더링할 RendererList 생성
    SortingCriteria sortingCriteria = cameraData.defaultOpaqueSortFlags;
    DrawingSettings drawingSettings = RenderingUtils.CreateDrawingSettings(
        m_ShaderTagId, renderingData, cameraData, lightData, sortingCriteria
    );
    FilteringSettings filteringSettings = new FilteringSettings(RenderQueueRange.all, m_LayerMask);

    RendererListParams rendererListParams = new RendererListParams(renderingData.cullResults, drawingSettings, filteringSettings);
    passData.rendererList = renderGraph.CreateRendererList(rendererListParams);

    builder.UseRendererList(passData.rendererList);
    
    //3. 패스 종료 후 라이트 텍스처를 전역 셰이더 프로퍼티로 등록
    builder.SetGlobalTextureAfterPass(customLightTexture, CustomLightTextureID);
    builder.AllowPassCulling(false);

    //4. 실제로 패스가 실행될 때의 명령 등록
    builder.SetRenderFunc((PassData data, RasterGraphContext context) =>
    {
        //렌더 타겟을 검은색(0, 0, 0)으로 초기화 후 오브젝트 렌더링
        context.cmd.ClearRenderTarget(false, true, Color.black);
        context.cmd.DrawRendererList(data.rendererList);
    });
}

이번 렌더 패스의 실질적인 동작 부분으로, 등록할 패스에 대한 최종적인 설정을 의미한다.

래스터 패스를 등록하면서, 설정해둔 PassData를 이용하고, 패스의 이름을 정하며 PassData의 실제 객체인 passData를 받아온다.

내부 동작은 주석에서 확인할 수 있듯이 크게 4단계로 나뉘게 되는데, 이 4가지 단계를 최대한 간단하게 압축하면 다음과 같다.

  1. 빛을 어디에 그릴지 결정
  2. 어떻게 그릴지 결정
  3. 완성된 결과를 어디에서 사용할지 결정
  4. 실제로 빛을 그려줌

위 과정을 자세하게 풀어서 확인해보자.

1단계에서는 렌더링 결과를 저장할 대상으로 위에서 생성한 텍스쳐인 customLightTexture를 지정해주었다.
아직 빛이 그려진 상태는 아니고, 이후 렌더링 과정에서 생성될 빛을 그려줄 공간을 지정해줬다고 보면 된다.

2단계는 카메라 데이터를 이용해서, 여러 빛 후보 오브젝트 중 어떤 오브젝트를 먼저 그려줄지 정한 다음, 어떤 방식으로 그려줄지를 정해준다.

이 과정에서 사용된 m_ShaderTagId은 렌더링을 진행하면서 사용할 셰이더의 패스 이름을 의미하는데, 위에서 new ShaderTagId("Custom2DLight");로 생성해줬었기 때문에 Custom2DLight 라는 이름의 패스 아래에서만 동작한다는 것을 의미한다.

여기에, 방금 정한 그리는 순서인 sortingCriteria까지 사용되기 때문에 drawingSettings 에는 사용할 셰이더의 패스나 그리는 순서 등 무언가를 그리기 위한 각종 설정들이 포함된다.

그리기 설정을 생성한 다음은 바로 이어서 필터 설정을 생성하는데, 현재 코드 상 기본값 기준으로 별 다른 제한을 둔 상태는 아니며, 만약 인스펙터 설정에서 레이어를 따로 지정해줬다면 해당 레이어와 연관된 필터가 생성된다고 보면 된다.

이렇게 생성된 그리기 및 필터 설정들을 하나로 묶어준 후, 렌더러 리스트를 생성한다.
렌더러 리스트는 방금 설정한 내용들을 기반으로, 이번 패스에서 렌더링할 렌더러들(스프라이트 렌더러 등)을 묶어놓은 목록이라고 보면 된다.

그렇게 완성된 렌더러 리스트를 사용한다고 명시적으로 등록해주며 2단계가 종료된다.

//3. 패스 종료 후 라이트 텍스처를 전역 셰이더 프로퍼티로 등록
builder.SetGlobalTextureAfterPass(customLightTexture, CustomLightTextureID);
builder.AllowPassCulling(false);

라이트 텍스쳐는 결국 셰이더에서 사용할 수 있어야 하기 때문에, 이번 단계인 3단계에서는 텍스쳐를 셰이더에서 사용할 수 있게 등록해준다.

또한 유니티는 렌더링 최적화 과정에서 패스를 제외할 수 있는데, 이를 방지하기 위해 제외 없이 그대로 실행하도록 명시해둔다.

//4. 실제로 패스가 실행될 때의 명령 등록
builder.SetRenderFunc((PassData data, RasterGraphContext context) =>
{
    //렌더 타겟을 검은색(0, 0, 0)으로 초기화 후 오브젝트 렌더링
    context.cmd.ClearRenderTarget(false, true, Color.black);
    context.cmd.DrawRendererList(data.rendererList);
});

마지막으로, 4단계는 이 패스의 실제 실행 단계에서 GPU가 작업할 내용을 등록한다.

현재 GPU의 작업으로 등록한 내용은 두가지로, 먼저 렌더 타겟을 검은색으로 초기화 하도록 되어있다.
앞에서 렌더 타겟을 빛 텍스쳐로 지정했기 때문에 빛 텍스쳐를 검은색으로 초기화 한다는 의미를 갖는다.

다음으로는 앞에서 생성한 렌더리스트의 대상들을 실제로 그려주도록 명령한다.
작업 결과는 앞에서 설정해둔 customLightTexture 텍스쳐에 처리되도록 되어있기 때문에 결과적으로 이때 그려지는 객체는 모두 해당 커스텀 라이트 전용 텍스쳐에 그려지게 된다.

커스텀 셰이더

이 렌더 패스는 복잡해 보이지만 핵심은 결국 어떤 객체, 조금 더 정확하게는 스프라이트 렌더러와 같은 어떤 렌더러들을 빛 텍스쳐에 그려줄지를 정하는 과정이다.

그 대상이 되는 객체의 레이어도 중요하긴 하지만, 결국은 필요한 어떤 셰이더를 머티리얼로 만들어서 등록해놓은 렌더러들이 빛 텍스쳐에 그려지기 때문에, 그 대상으로 처리하게 할 수 있는 셰이더 또한 중요하다.

셰이더 핵심 코드

Shader "Universal Render Pipeline/2D/CustomLight2D"
{
    //머티리얼에서 입력하는 설정값들
    Properties
    {
        //원본 텍스쳐 및 생성할 빛의 색상값
        _MainTex("Light Shape", 2D) = "white" {}
        [HDR] _LightColor ("Light Color & Intensity", Color) = (1,1,0,1)
    }

    SubShader
    {
        Blend One One //Additive Blending(광원이 겹칠수록 더 강해짐)

        Pass
        {
            //다음 파트에서 설명
        }
    }
}

커스텀 셰이더의 기본적인 설정들 중 핵심이 되는 부분들은 위와 같다.

먼저 머티리얼의 입력값인 Properties는 메인 텍스쳐와 색상값을 받도록 되어있는데, 이 두 값은 빛의 모양과 빛의 색상을 결정할 때 사용된다.

Blend는 블렌딩 방식을 의미하는데, One One 이라는 설정은 현재 위치에 존재하는 값들을 서로 더해준다는 것을 의미한다.

따라서 A라는 빛이 어떤 위치에 (0.5, 0.2, 0.2)라는 값을 그려놨다면, 같은 위치에 (0.3, 0.4, 0.5)라는 B의 빛이 그려지게 되었을 때, 결과적으로 (0.8, 0.6, 0.7)이라는 빛이 그려지게 된다.

렌더패스 과정에서 타겟을 검은색으로 초기화 해줬었는데, 그 이후에 위 블렌딩 방식으로 광원들을 그려주기 때문에 광원의 정확한 색상들을 계속해서 합쳐줄 수 있게된다.

커스텀 셰이더 패스 핵심 코드

Pass
{
    Name "Custom2DLight"
    Tags
    {
        //렌더 패스에서 지정한 패스명 Custom2DLight 지정
        //기존에 등록해둔 패스가 동작할 때, 이번 셰이더 코드가 실행된다는 것을 의미
        "LightMode" = "Custom2DLight"
    }

    HLSLPROGRAM
    #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

    #pragma vertex LitVertex
    #pragma fragment LitFragment

    //Vertex shader 에서 입력받을 정점 정보
    struct Attributes
    {
        float3 positionOS : POSITION;
        float2 uv : TEXCOORD0;
        float4 color : COLOR;
    };

    //Vertex shader 에서 Fragment Shader로 전달할 정보
    struct Varyings
    {
        float4 positionCS : SV_POSITION;
        float2 uv : TEXCOORD0;
        float4 color : COLOR;
    };

    //텍스쳐, 샘플러 선언(변수 선언)
    TEXTURE2D(_MainTex);
    SAMPLER(sampler_MainTex);

    //마찬가지로 변수 선언, 각각 텍스쳐 및 색상과 관련되어있음
    CBUFFER_START(UnityPerMaterial)
        float4 _MainTex_ST;
        float4 _LightColor;
    CBUFFER_END

    //각종 메소드, 이어서 설명 진행
    ENDHLSL
}

패스 내부 코드인데, 핵심이라곤 하지만 사실상 대부분의 코드 전체에 가깝다.

가장 중요한건 Tags 부분으로, 그 안쪽을 보면 LightMode를 Custom2DLight로 지정해주었는데, 이는 렌더패스에서 지정해뒀던 ShaderTagId와 동일하다.

그로 인해 렌더패스에서 RendererList의 오브젝트들을 그릴 때, 각 Renderer가 사용하는 셰이더 중 LightMode가 Custom2DLight로 지정된 현재 Pass가 선택되어 실행된다.

태그 다음으로는 버텍스, 프래그먼트 셰이더용 구조체를 생성해준다.
각 로직별 동작에서 사용할 내용에 맞춰서 포지션이나 uv, 색상값 등의 변수들이 선언되어있다.

이후 추가적인 변수 선언 과정이 존재하는데, 이는 GPU가 명령을 처리할 수 있도록 메인 텍스쳐나 샘플러, 색상값등을 그에 맞춰 선언해 둔 상태라고 보면 된다.

남은 부분들은 버텍스, 프래그먼트 셰이더로 우선은 버텍스 셰이더부터 설명하려한다.

//버텍스 셰이더
//프래그먼트에서 사용할 데이터를 준비해서 전달
Varyings LitVertex(Attributes input)
{
    Varyings output;
    
    //오브젝트 기반 좌표를 MVP 이후의 클립 스페이스 좌표로 전환
    output.positionCS = TransformObjectToHClip(input.positionOS);
    
    //메인 텍스쳐의 타일링 및 오프셋 설정 적용, 이때 uv는 오브젝트 기준 uv
    output.uv = TRANSFORM_TEX(input.uv, _MainTex);
    
    //색상은 있는 그대로 전달
    output.color = input.color;

    return output;
}

이번 버텍스 셰이더는 프래그먼트 셰이더를 위해 데이터를 준비하는 정도로만 구성되어있다.

먼저 오브젝트 스페이스 기반 버텍스의 좌표를 클립 스페이스 좌표 기반 좌표로 변환하여 저장하고, 다음은 메인 텍스쳐에 설정된 타일링과 오프셋 설정을 적용해서 uv 값을 조정해준다.
만약 타일링과 오프셋이 기본값으로 되어있다면 uv는 변동없이 있는 그대로 전달해주게 된다.

색상의 경우 전달받은 색상을 그대로 저장해서 넘겨준다.

//프래그먼트 셰이더
//각 픽셀별로 어떤 색상을 출력할지 결정함
half4 LitFragment(Varyings input) : SV_Target
{
    //오브젝트 uv 기준 메인 텍스쳐의 색상을 가져옴
    half4 tex = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);

    //광원의 중심에서 떨어진 정도를 기반으로, 보정값 계산
    half attenuation = CalculateRadialAtteunation(input.uv, 1);

    //현재 색상에 빛의 색상과 보정값을 곱해서 적용, 알파값도 동일한 방식으로 적용
    half3 finalLight = tex.rgb * _LightColor.rgb * attenuation;
    half finalAlpha = tex.a * _LightColor.a * attenuation;

    //계산된 최종 색상 반환
    return half4(finalLight, finalAlpha);
}

프래그먼트 셰이더의 경우 메인 텍스쳐의 색상을 가져와서 보정값을 계산하고, 보정값을 빛의 색상과 알파값에 적용한 이후, 메인 텍스쳐의 색상에 그대로 적용해준다.

이때 보정값을 계산하는 CalculateRadialAtteunation 메소드는 다음과 같다.

//현재 uv 위치를 기반으로 0~1의 보정값 반환
half CalculateRadialAtteunation(float2 uv, float falloffPower)
{
    //0 ~ 1의 uv를 중심을 0,0으로하는 -0.5 ~ 0.5로 변경
    float2 centerOffset = uv - float2(0.5, 0.5);

    //오브젝트 중심으로부터 떨어진 거리를 구하고, 2를 곱해서 테두리를 0.5 대신 1로 변경
    float dist = length(centerOffset) * 2.0;

    //지수 감쇄 연산 (Gaussian/Exponential Falloff)
    //중앙은 1, 멀어질수록 0에 가까워짐
    half attenuation = exp(-dist * dist * falloffPower);

    //중심과 외곽의 값을 전환하면서, 0~1 사이의 값으로 제한
    attenuation *= saturate(1.0 - dist);

    //보정값 반환
    return attenuation;
}

보정값 계산 메소드인데, 위에서 사용하는 부분을 봤으니 결론적으로 얘기하자면 1에 가깝게 반환되면 있는 그대로 빛을 표현하고, 0에 가깝게 반환되면 그만큼 빛을 약하게 표현한다.

먼저 오브젝트의 uv에서 0.5씩 빼주어서 0,0이 중심이 되는 uv 값을 구해주고, 이 값을 이용해서 현재 uv 위치가 중앙에서 얼마나 떨어져있는지 확인해준다.

다음으로는 떨어진 정도를 구한 값을 지수연산에 사용해주는데, 계산의 결과는 현재 위치가 오브젝트 중앙이라면 1, 중앙에서 멀어질수록 0에 가까운 상태가 된다.

이 시점에서의 두 값을 다시 확인해보면 떨어진 정도인 dist값은 중앙이 0에 가깝고, 지수 연산의 값은 중앙이 1에 가까운 상태이다.

이런 상황에서 dist값을 1에서 빼주고, 그 값을 0~1 사이로 제한해준다.
이 결과는 uv가 오브젝트의 중심부였다면 1로, 외곽이었을 수록 0에 가까운 상태가 되는데 위에서 진행했던 지수 연산결과에 이를 곱해준다.

값을 두개로 나눠서 처리한 이유는 두 값의 목적이 각각 다르기 때문이다.

지수 연산은 기본적으로 바깥쪽으로 갈 수록 점점 빛이 자연스럽게 줄어들 수 있도록 그에 맞게 값이 줄어들지만, 외곽인 dist 1 기준으로 약 0.368 정도의 결과값이 나오기 때문에 빛이 37% 정도가 남은 상태에서 갑자기 빛이 사라지는 형태로 구현될 수 있다.

반대로 지수 연산 없이 dist만 사용했다면 빛이 너무 일정한 형태로 감소되는 모양이 만들어지기 때문에 두 연산을 같이 진행하고 적용해서, 중심 부근은 빛이 최대한 유지되다가 어느 지점을 넘으면 빠르게 감소되는 듯하면서, 빛이 끊기지 않고 끝까지 제대로 줄어드는 형태의 보정값이 완성된다.


커스텀 셰이더(빛을 받을 오브젝트)

커스텀 라이트 텍스쳐를 읽고, 그 빛을 처리해줄 별도의 셰이더가 필요하다.

이는 기존 Lit 셰이더를 그대로 가져와서 일부만 수정할 수도 있고, 아예 새로운 셰이더를 만들어낼 수도 있다.

중요한건 커스텀 라이트 텍스쳐를 읽고 처리해주는 셰이더 코드가 필요하다는 점이다.

빛을 받는쪽의 커스텀 셰이더는 핵심중의 핵심만 설명하려 한다.

//렌더패스 단계에서 글로벌로 등록한 라이트 텍스쳐 읽어오기
TEXTURE2D(_CustomLightTexture);
SAMPLER(sampler_CustomLightTexture);

렌더패스 실제 동작 3단계에서 SetGlobalTextureAfterPass 메소드를 이용하여 전역으로 텍스쳐를 등록해줬었는데, 여기에서 빛 텍스쳐를 읽어서 적용해주기 위함이었다.

따라서 그때 등록했던 텍스쳐를 불러와주었다.

//빛을 받는 오브젝트의 커스텀 프래그먼트 셰이더
half4 LitFragment(Varyings input) : SV_Target
{
    //기본 Lit 셰이더에서 사용하는 프래그먼트 셰이더의 메소드
    //주석처리를 진행함으로써, 유니티 기본 빛에는 영향을 받지 않도록 설정
    //return CommonLitFragment(input, input.color);

    //메인 텍스쳐(오브젝트)에서 현재 uv에 해당하는 색상을 읽어옴
    const half4 main = input.color * SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);

    //커스텀 라이트 텍스쳐에서 현재 uv에 해당하는 빛 색상을 읽어옴
    half4 customLight = SAMPLE_TEXTURE2D(_CustomLightTexture, sampler_CustomLightTexture, input.lightingUV);

    //메인 텍스쳐 색상과 빛의 색상을 곱해줌
    half3 finalCustomLight = customLight.rgb * main;

    //알파값은 메인 텍스쳐의 알파값을 그대로 적용
    return half4(finalCustomLight, main.a);
}

빛을 받는 오브젝트의 프래그먼트 셰이더인데, 첫 주석을 보면 기존의 CommonLitFragment가 주석처리 되어있는 것을 알 수 있다.

이 CommonLitFragment는 유니티 기본 빛을 받고 처리해주는 메소드로, 주석처리를 진행했기 때문에 유니티의 기본 빛은 받지 않겠다는 것을 의미한다.

이후 두 텍스쳐의 색상을 읽어오는데, 하나는 오브젝트 메인 텍스쳐의 색상, 또 다른 하나는 생성해둔 커스텀 라이트 텍스쳐의 색상을 읽어온다.

그렇게 가져온 두 색상을 곱해서 메인 텍스쳐에 커스텀 라이트를 적용해주고, 알파값은 있는 그대로 메인 텍스쳐의 알파값을 적용해서 반환한다.

결과적으로 이 객체는 유니티 기본 제공 빛은 일절 받지 않고, 지금까지 작업한 커스텀 라이트만 적용해서 표현한다는 것을 의미한다.

오브젝트 설정

커스텀 라이트를 확실하게 보기 위해 몇가지 설정을 진행했다.

  1. 글로벌 라이트 생성 후, 글로벌 라이트의 색상을 검은색으로 지정해주었다.
  2. 배경은 빛의 영향을 받지 않는 머티리얼을 적용해주었다.
  3. 특정 오브젝트(사과)에만 커스텀 라이트를 받을 수 있는 머티리얼을 적용해주었다.

첫번째로 커스텀 라이트 셰이더를 머티리얼로 만들어서, 유니티 기본 2D Circle 오브젝트에 적용해주었다.

이러한 객체를 왼쪽 위의 사과영역에 하나, 우측 위의 오렌지 영역에 하나 총 두개의 커스텀 라이트 객체를 설정해주었다.

이후, 유니티의 Renderer2D 설정으로 가서, Renderer Feature를 추가하여, 가장 처음 작업해줬던 렌더러 패스를 등록해주었다.
설정값은 그대로, 즉 모든 레이어를 대상으로 커스텀 라이트를 생성할 수 있도록 해주었다.

마지막으로, 사과 오브젝트들의 머티리얼을 커스텀 라이트를 받는 셰이더 기반 머티리얼로 변경해주었다.

이러한 과정들을 통해 위와 같은 화면을 확보할 수 있었다.

사과 오브젝트는 글로벌 라이트 대신 직접 생성한 커스텀 라이트의 영향을 받아 자연스러운 빛을 연출해주었고, 우측의 오렌지 오브젝트는 관련 머티리얼을 사용하지 않았기 때문에 커스텀 라이트의 영향을 받지 못하였다.

배경 또한 커스텀 라이트의 영향을 받는 머티리얼로 교체해준다면 위의 화면처럼 전체적인 노란색 빛을 두개 확인할 수 있으며, 사과는 빛을 받아 정확하게 표현되었고, 오렌지는 아무런 빛을 받지 못해 검은색 그대로 표현되어 있는 것을 확인할 수 있었다.

0개의 댓글