Search Unity

셰이더 그래프의 커스텀 조명: 2019 버전 그래프 확장

, 7월 31, 2019

Unity 에디터 2019.1이 출시되면서 셰이더 그래프(Shader Graph) 패키지의 프리뷰가 공식적으로 종료되었습니다! 2019.2 버전에도 셰이더 그래프에 더 많은 기능이 추가될 예정입니다.

2019 버전에서 향상된 기능

커스텀 함수 및 서브그래프 업그레이드

이제 새로운 커스텀 함수(Custom Function) 노드를 사용하여 셰이더 그래프 내에서 커스텀 코드를 그대로 사용할 수 있습니다. 이 노드를 사용하면 커스텀 입력 및 출력을 정의하고 순서를 변경하며 노드 자체에 직접 또는 외부 파일을 참조하여 커스텀 함수를 삽입할 수 있습니다.

서브그래프(Sub Graph)도 업그레이드되었습니다. 이제 포트의 유형과 이름, 순서를 변경할 수 있고 이를 통해 서브그래프에 대한 출력을 정의할 수 있습니다. 또한 서브그래프용 블랙보드(Blackboard)는 이제 메인 그래프에서 지원하는 모든 데이터 유형을 지원합니다.

컬러 모드 및 정밀 모드

셰이더 그래프를 사용하여 강력하고 최적화된 셰이더를 더 쉽게 제작할 수 있습니다. 2019.2에서는 그래프 전체 또는 노드별로 그래프 계산의 정밀도를 수동으로 설정할 수 있습니다. 새로운 컬러 모드(Color Mode)를 사용하면 노드 카테고리 중 정밀도를 더 쉽고 빠르게 시각화하고 사용자가 원하는 커스텀 컬러를 표현할 수 있습니다.

새로운 기능에 관한 자세한 내용은 셰이더 그래프 기술 자료를 확인하시기 바랍니다.

샘플 프로젝트

 

새로운 커스텀 함수 워크플로를 사용하는 데 참고할 수 있는 프로젝트 예제를 만들었습니다. 저장소에서 프로젝트를 다운로드하고 따라해 보세요! 이 프로젝트는 Custom Function 노드를 사용하여 경량 렌더 파이프라인(LWRP)용 커스텀 조명 셰이더를 작성하는 방법을 보여줍니다. 새로운 프로젝트를 생성하여 따라하려면 2019.2 에디터 및 LWRP 패키지 버전 6.10.0 이상을 사용해야 합니다.

주요 광원에서 데이터 가져오기

먼저 씬의 주요 광원에서 정보를 가져옵니다. Create > Shader > Unlit Graph를 선택하여 새로운 언릿(Unlit) 셰이더 그래프를 생성합니다. Create Node 메뉴에서 새 Custom Function 노드를 찾은 다음 오른쪽 상단의 톱니바퀴 아이콘을 클릭하여 노드 메뉴를 엽니다.

이 메뉴에서 입력 및 출력을 추가할 수 있습니다. Direction 및 Color 출력 포트를 추가하고 둘 다 Vector 3을 선택합니다. “선언되지 않은 식별자” 오류 플래그는 코드를 추가하면 사라집니다. Type 드롭다운 메뉴에서 String을 선택합니다. 함수 이름을 업데이트합니다. 이 예제에서는 “MainLight”를 사용했습니다. 이제 텍스트 박스에 커스텀 코드를 추가할 수 있습니다.

먼저, #ifdef SHADERGRAPH_PREVIEW라는 플래그를 사용하겠습니다. 노드의 프리뷰 박스는 광원 데이터에 액세스할 수 없기 때문에 그래프 프리뷰 박스에 표시할 내용을 노드에 알려줘야 합니다. #ifdef는 컴파일러가 상황에 따라 다른 코드를 사용하도록 지시합니다. 출력 포트의 폴백(fallback) 값을 정의하여 시작합니다.

다음으로 #else를 사용하여 프리뷰가 아닐 때 수행할 작업을 컴파일러에 지시합니다. 이 단계가 실제 광원 데이터를 가져오는 단계입니다. LWRP 패키지의 빌트인 함수인 GetMainLight()를 사용합니다. 이 정보를 사용하여 Direction 및 Color 출력을 할당할 수 있습니다. 이제 커스텀 함수는 아래와 같아집니다.

이때 노드를 그룹에 추가하여 노드가 수행 중인 작업을 표시하도록 합니다. 노드를 마우스 오른쪽 버튼으로 클릭하고 Create Group from Selection을 선택한 후, 그룹 이름을 변경하여 노드가 수행하는 작업을 표시합니다. 예제에서는 “Get Main Light”라고 입력했습니다.

이제 광원 데이터를 가져왔으므로 셰이딩을 계산할 수 있습니다. 표준 램버시안(Lambertian) 조명부터 계산하겠습니다. 월드 노멀 벡터와 광원 방향의 내적을 구한 후 Saturate 노드에 전달하고 광원 컬러 값을 곱합니다. 이를 Unlit Master 노드의 Color 포트에 연결하면 프리뷰가 커스텀 셰이딩으로 업데이트됩니다!

커스텀 함수 파일 모드 사용

Custom Function 노드를 사용하여 광원 데이터를 가져온 후에 함수를 확장할 수 있습니다. 다음 함수는 주요 광원에서 방향과 컬러 외에도 감쇠 값을 가져옵니다.

이는 더 복잡한 함수이므로 파일 모드로 전환하고 HLSL include 파일을 사용합니다. 이를 통해 그래프에 삽입하기 전에 적절한 코드 에디터에서 더 복잡한 함수를 작성하고, 하나의 통일된 위치에서 코드를 디버깅할 수 있습니다.

프로젝트의 Assets > Include 폴더에서 CustomLighting include 파일을 열어 시작합니다. 이 단계에서는 MainLight_half 함수만 살펴보도록 하겠습니다. 함수는 아래와 같습니다.

위 함수에는 새로운 입력 및 출력 데이터가 포함되어 있으므로 Custom Function 노드로 돌아가 입력 및 출력을 추가합니다. 새로운 DistanceAtten(거리 감쇠) 및 ShadowAtten(그림자 감쇠) 출력을 추가합니다. 그런 다음, 새로운 WorldPos(월드 포지션) 입력을 추가합니다. 이제 입력과 출력이 추가되었으므로 include 파일을 참조할 수 있습니다. Type 드롭다운을 File로 변경합니다. Source 입력에서 include 파일을 찾고 참조할 Asset을 선택합니다. 이제 어떤 함수를 사용할지 노드에 지시해야 합니다. Name 박스에 “MainLight”를 입력하여 해당 함수를 사용하도록 설정합니다.

사용자가 설정한 함수와 달리 include 파일은 함수 이름 끝에 _half가 추가되어 있습니다. 이는 셰이더 그래프 컴파일러가 정밀도 형식을 각 함수 이름에 추가하기 때문입니다. 커스텀 함수를 정의할 때는 소스 코드를 컴파일러에 전달하여 함수가 어떤 정밀도 형식을 사용하는지 알려줘야 합니다. 그러나 노드에서는 메인 함수 이름만 참조하면 됩니다. 플로트 정밀도 모드에서 컴파일하려면 ‘float’ 값을 사용하는 함수의 복사본을 만들면 됩니다. ‘Precision’ 컬러 모드를 사용하면 그래프의 각 노드에 설정된 정밀도가 색상으로 나타나며 파란색은 float을, 빨간색은 half를 나타냅니다.

Custom Function을 서브그래프에 래핑해두면 필요에 따라 이 함수를 재사용할 수 있습니다. 노드와 해당 그룹을 선택하고 마우스 오른쪽 버튼을 클릭하여 Convert to Sub-graph를 찾습니다. 예시에서는 그룹 이름을 “Get Main Light”로 지정했습니다. 서브그래프에서 필요한 출력 포트를 서브그래프 출력 노드에 추가하고 노드의 출력을 서브그래프 출력과 연결합니다. 다음으로 입력에 연결할 월드 포지션 노드를 추가합니다.

서브그래프를 저장하고 언릿 그래프로 돌아갑니다. 기존 로직에 새로운 multiply 노드를 두 개 추가하겠습니다. 먼저, 두 감쇠 출력을 서로 곱합니다. 그런 다음 해당 출력에 광원 컬러 값을 곱합니다. 기본 셰이딩에서 감쇠를 올바르게 계산하려면 이전 단계의 NdotL을 곱하면 됩니다.

다이렉트 스페큘러 셰이더 만들기

앞서 만든 셰이더는 무광택 오브젝트에 적합하며 광택이 있는 오브젝트에 작업할 때는 셰이더에 스페큘러 계산 결과를 추가하면 됩니다. 이 단계에서는 서브그래프에 래핑된 또 다른 Custom Function 노드인 Direct Specular를 사용하겠습니다. CustomLighting include 파일을 확인해 보면 이번에는 같은 파일의 다른 함수를 참조하고 있습니다.

이 함수는 간단한 스페큘러 계산을 수행하며, 이에 대한 자세한 내용은 여기에서 확인할 수 있습니다. 이 함수의 서브그래프는 블랙보드에 다음과 같은 입력을 포함합니다.

새 노드에 함수와 맞는 입력 및 출력 포트가 모두 있는지 확인해야 합니다. 블랙보드에 프로퍼티를 추가하는 방법은 간단합니다. 먼저 오른쪽 상단의 Add (+) 아이콘을 클릭하고 데이터 유형을 선택합니다. 타원형 모양의 항목을 더블 클릭하여 입력의 이름을 변경하고 이를 드래그 앤 드롭하여 그래프에 추가합니다. 마지막으로 서브그래프의 출력 포트를 업데이트하고 저장합니다. 

이제 스페큘러 계산이 설정되었으므로 언릿 그래프로 돌아가 Create Node 메뉴를 사용하여 이를 추가할 수 있습니다. Attenuation 출력을 Direct Specular 서브그래프의 Color 입력에 연결합니다. 다음으로 Get Main Light 함수의 Direction 출력을 스페큘러 서브그래프의 Direction 입력에 연결합니다. NdotL*Attenuation의 결과값을 Direct Specular 서브그래프의 출력에 추가하고 이를 Color 출력에 연결합니다.

이렇게 하면 약간의 광택이 추가됩니다!

다중 광원으로 작업하기

LWRP의 주요 광원은 오브젝트에 대해 상대적으로 가장 밝은 방향 광원(일반적으로 태양)을 말합니다. 로우엔드 하드웨어에서 성능을 향상시키기 위해 LWRP는 주요 광원과 추가 광원을 따로 계산합니다. 셰이더가 씬 내의 가장 밝은 방향 광원뿐만 아니라 모든 광원을 정확하게 계산하려면 함수에 루프를 생성해야 합니다.

예제에서는 추가 광원 데이터를 가져오기 위해 새 Custom Function 노드를 래핑할 새 서브그래프를 사용했습니다. CustomLighting include 파일의 AdditionalLight_float 함수를 살펴보겠습니다.

이전과 마찬가지로 Custom Function 노드의 파일 참조에 AdditionalLights 함수를 사용하고 올바른 입력 및 출력을 모두 생성했는지 확인합니다. 노드가 래핑된 서브그래프의 블랙보드에 Specular Color와 Specular Smoothness가 표시되는지 확인합니다. Position, Normal Vector, 및 View Direction 노드를 사용하여 서브그래프에 World PositionWorld Normal, 및 World Space View Direction을 연결합니다.

함수를 설정한 후 사용방법은 다음과 같습니다. 먼저, 이전 단계에서 메인 언릿 그래프를 가져와 서브그래프로 합칩니다. 노드를 선택하고 Convert to Sub-graph를 마우스 오른쪽 버튼으로 클릭합니다. 마지막 Add 노드를 제거하고 출력을 서브그래프의 출력 포트에 연결합니다. Specular 및 Smoothness에 대한 입력 프로퍼티도 생성할 것을 권장합니다. 

이제 주요 광원 계산과 추가 광원 계산을 조합할 수 있습니다. 메인 언릿 그래프에 주요 광원 계산 및 추가 광원 계산을 위한 새 노드를 생성합니다. 그리고 주요 광원과 추가 광원의 Diffuse와 Specular 출력을 함께 추가하면 간단히 완성됩니다.

간단한 툰 셰이더 만들기

이제LWRP 프로젝트에서 가져온 씬의 모든 광원 데이터를 사용하는 방법을 알아보겠습니다. 셰이더에서 커스텀 조명이 가장 많이 사용되는 것 중 하나가 바로 클래식 툰 셰이더입니다.

모든 광원 데이터를 사용하면 간단하게 툰 셰이더를 만들 수 있습니다. 먼저, 지금까지 한 모든 광원 계산을 가져와 다시 한번 서브그래프에 래핑합니다. 이는 최종 셰이더의 가독성을 높이는 데 도움이 됩니다. 반드시 마지막 Add 노드를 제거하고 서브그래프 출력 노드의 각 출력 포트에 Diffuse 및 Specular를 연결합니다.

툰 셰이딩을 만드는 방법은 많지만, 이 예제에서는 빛 강도를 사용하여 램프 텍스처(Ramp Texture)에서 컬러를 찾도록 하겠습니다. 이 기술을 일반적으로 램프 조명(Ramp Lighting)이라고 합니다.  샘플 프로젝트에 램프 조명에 필요한 텍스처 에셋 예제가 포함되어 있습니다. 램프 조명에서 동적 램프를 사용하도록 그레디언트를 샘플링할 수도 있습니다.

먼저 Diffuse 및 Specular의 강도를 RGB 값에서 HSV 값으로 전환합니다. 이를 통해 광원 컬러의 강도(HSV 값)를 사용하여 셰이더에서 밝기를 결정하고 에셋의 가로축을 따라 다른 지점에 있는 텍스처를 샘플링할 수 있습니다. UV의 Y 채널에는 정적 값을 사용하여 전체를 기준으로 이미지의 어떤 부분을 샘플링할지 결정합니다. 이 정적 값을 인덱스로 사용하여 단일 텍스처 에셋에서 프로젝트의 여러 조명 램프를 참조할 수 있습니다.

UV 값을 설정한 후 Sample Texture 2D LOD 노드를 사용하여 램프 텍스처를 샘플링합니다. 일반 Sample Texture 2D 노드를 사용하면 램프가 자동으로 씬에 밉(mip)되고 멀리 있는 오브젝트의 조명 동작이 달라지므로 Sample LOD를 유의하여 사용합니다. Sample Texture 2D LOD 노드를 사용하면 수동으로 밉 레벨을 결정할 수 있습니다. 또한 램프 텍스처의 높이가 2픽셀이기 때문에 텍스처에 자체 샘플러 상태를 생성했습니다. 텍스처가 정확하게 샘플링되도록 하기 위해 필터(Filter)를 포인트(Point)로, 래핑(Wrap)을 클램프(Clamp)로 설정했습니다. 텍스처 에셋이 변경되는 경우 설정을 변경할 수 있도록 이를 블랙보드에 프로퍼티로 표시해두었습니다. 

마지막으로 확산 계산의 램프 샘플에 컬러 프로퍼티인 Diffuse를 곱해 오브젝트의 컬러를 변경할 수 있습니다. 스페큘러 계산의 램프 샘플을 Diffuse 출력에 추가하고 최종 컬러를 마스터 노드에 연결합니다.

커스텀 조명 확장하기

이렇게 간단한 커스텀 조명 설정을 모든 종류의 씬에서 다양하게 확장하여 적용할 수 있습니다. 프로젝트 예제에 커스텀 조명 설정을 사용하는 셰이더로 구성된 전체 씬이 포함되어 있습니다. 또한 버텍스 애니메이션, 간단한 피하 산란 근사뿐만 아니라 뎁스를 사용하는 굴절 및 컬러링이 포함되어 있습니다. 프로젝트를 다운로드하여 에셋 예시를 확인하고 더 많은 고급 기능을 사용하여 보시기 바랍니다.

더 알아보기

셰이더 그래프와 이를 사용하여 제작할 수 있는 셰이더에 관해 의견을 나누고 싶으시면 새로운 포럼 공간에 참여해 주시기 바랍니다! 또한 Discord 채널 이용하면 유니티 사용자 및 개발자와 대화할 수 있습니다.

또한 유니티의 SIGGRAPH 2019 세션 녹화본에서 커스텀 조명에 셰이더 그래프를 사용하는 방법에 관한 더 자세한 내용을 보실 수 있습니다.

23 코멘트

코멘트 구독

코멘트를 달 수 없습니다.

  1. Hello there! I’m trying to follow the tutorial, but when I try to use the Subgraph created to calculate the additional lights an error shows up. It sais that is an invalid subgraph asset. I tried to change things to make it work but there is no way. Maybe it’s something about the version (2019.2.6f1) because I can’t create a subgraph from a graph and have to do it manualy or maybe I don’t understand that part of the tutorial. Anyway, any help will be well recibed

  2. Noah Bannister

    9월 23, 2019 3:22 오후

    I have done a quick write-up on creating an intermediate Lightweight PBR node for use in unlit graphs to achieve custom effects. I’m hoping it can help some people out. https://noahbannister.blog/2019/09/23/unity-lwrp-pbr-shader-node/

  3. looking for a way to add custom lighting i gave this approach a try.
    unfortunately i had to realize that shadows actually do not work as already posted by Fries.
    the reason is obvious (missing _MAIN_LIGHT_SHADOWS keyword), so i also had a look into the project of natalie burke which somehow solves this problem to a certain degree: it does not support cascaded shadows. and soft shadows of course.
    so i looked into another approach – whcih turned out to work well: using the pbr master node instead!
    more about this soon.

  4. This is a nice and helpful tutorial. However I am stuck with the subgraphs as one other user commented already below: “the subgraph is an invalid asset with some GUID number”. I can’t get the subgraphs to work at all because of this. I am using Unity 2019.2.1f1 with LWRP.

  5. Shadows are not working in Unity 2019.2.0f1 in the custom shaders.
    For some reason the shadowAttenuation is always 1.
    I noticed that the custom shaders don’t get the keyword “_MAIN_LIGHT_SHADOWS” set to true.

    A workaround I found is to edit the file “Packages/Lightweight RP/ShaderLibrary/Lighting.hlsl”:

    Line 185:
    #if !defined(_MAIN_LIGHT_SHADOWS) || defined(_RECEIVE_SHADOWS_OFF)

    changed to:
    #if defined(_RECEIVE_SHADOWS_OFF)

    This is not a viable solution as this is only working in editor

    1. Sorry, the line is not found in …/Lighting.hlsl but in
      ““Packages/Lightweight RP/ShaderLibrary/Shadows.hlsl”

  6. With this version, I can’t open any 2019.1 graphs. Are they definitly incompatible ? Thanks.

  7. Looks nice and the instructions are clear, but even when I download the project folder I can’t manage to get this working. Everything is just purple and when I try to do the hlsl and subshader by myself, I alsways get an error that the subgraph is an invalid asset with some GUID number. Unity 2019.2.0f1 and ShaderGraph 6.9.1 are installed. But im fairly new to Unity, maybe I miss something, so I will try later again.
    Nontheless, thanks for this cool tutorial. I was a looking for a tutorial like this, for the current Unity Version, for a long time..

  8. Does this work with the new 2D lights?

  9. nice

  10. Bastien Giafferi

    8월 1, 2019 10:58 오전

    Is there any to have similar results with HDRP but I can’t manage to get the main light direction. Any ideas?

  11. It would be great if you could, in such blog posts, actually show examples that don’t work by “magic”. The one above just works because the ShaderGraph around is #including all the necessary files itself, automagically making all those Lighting calls work.

    However, if you want to do something a bit more involved – say, access something that is not included already – this structure breaks pretty quickly with redefinitions, multiple definitions of the same stuff, … At least you should point out why stuff “just works” in this example.

  12. It would be great to have an ability to set/get variables in Shader Graph to better organaize everything.

  13. Is this even possible to do in HDRP?

  14. Pierre-Henri BARRALIS

    7월 31, 2019 5:44 오후

    “LWRP 6.10.0 or higher.”,
    6.10.0 isn’t out yet (at the time I’m writing this), the highest is 6.9.1.

    1. Sorry, looks like a typo. The project and examples are compatible with 6.9.1 .

      1. Pierre-Henri BARRALIS

        7월 31, 2019 7:14 오후

        thanks for the clarification ! Also the unity project on github is targetting 2019.2.0b10, you might want to change it :)
        Otherwise, the project and examples are awesome! Thanks a lot!
        At first I’d hoped that we could change how the lighting is computed in the for loop (LightingLambert and LightingSpecular calls) directly in ShaderGraph. I know this would require being able to do for loops in ShaderGraph, which is difficult itself. Do you see this as something that could be done in the future or is it never going to happen ?
        Thanks

      2. Pierre-Henri BARRALIS

        7월 31, 2019 7:19 오후

        Thanks for the clarification Alex! Also the unity github project is targetting 2019.2.0b10, you might want to change it :)
        Otherwise the project and examples are awesome, thanks for the work!
        I’d hoped that we could change how the lighting is computed in the for loop (changing LightingLambert and LightingSpecular) without having to write any HLSL (directly in SG). I know this would require the ability to write for loop in nodes, and it’s complicated.
        Do you see this as a something that could be done in the future, or is it never going to happen ?
        Thanks Alex

      3. Pierre-Henri BARRALIS

        7월 31, 2019 7:20 오후

        Doing a triple-post to ask if you can remove this and the double post above (as I can’t do it), as it took a few minutes to appear I thought it was a bug and rewrote it.

  15. Wendelin REICH

    7월 31, 2019 5:21 오후

    Does this update include the ability to turn off ‘Receive Shadows’? Not having this basic ability is currently a dealbreaker for me.

    I’m asking this here because I’m not getting a response anymore on this issue from your colleagues (see the following thread: https://forum.unity.com/threads/turn-off-receive-shadows-on-custom-pbr-graph-lwrp.657814/).