# Part 16 | 커스텀 라이트 완성

## 01 Diffuse Reflection 연산 - 04 Fresnel 연산

![](https://velog.velcdn.com/images/biomatrix117/post/d2ff190b-69a6-40c8-a321-2909c010893c/image.png)

* Lambert 라이트와 텍스쳐를 곱해서 Diffuse 연산
* Baked GI와 Base Map 텍스쳐를 곱하여 Ambient Light 연산
* Ambient Occlusion 맵을 Ambient Light와 곱하기
* Blinn Phong 또는 Phong으로 specular 연산
* Gloss 맵을 specular와 곱하여 마스킹 (색을 표현하는게 아니니 sRGB 끄기)
* Fresnel Effect를 Baked GI와 곱하기. 각 노드에 World Normal Map 삽입.

## 05 Fresnel을 이용한 가짜 Specular 만들기

![](https://velog.velcdn.com/images/biomatrix117/post/75ecd312-7453-4ce6-a1d7-490fb0179418/image.png)

Fresnel을 One Minus로 뒤집고, Power로 작게 만든 후, 컬러를 조절할 수 있게 컬러를 곱하면 스페큘러처럼 보인다.\
스페큘러는 특정 방향을 바라볼 때 생기는 하이라이트이므로 크게 다를게 없기 때문.\
하지만 Fresnel의 특성상 내가 바라보는 방향만이 언제나 빛난다. (카메라 위에 붙어 있는 조명 느낌)

이걸 스페큘러로 써도 아무 문제 없다.\
빛의 영향이 없는 뒷면에서도 보이기 때문에 어두운 곳에서도 음영과 질감을 좀 더 잘 보이게 할 수 있다. 이 공식은 림 라이트 연산에서 나오는 부산물로 만든 결과물이므로 연산량도 절약할 수 있다.

Normal map을 적용하고 Gloss로 마스킹도 해주면 가짜 스페큘러 완성.

(이쯤 오면 Add가 너무 많아지는데, Sub graph로 묶어 4Add, 5Add 이런 식으로 사용하면 정리하기 좋다)

#### 06 그림자 감쇠(Shadow Attenuation)

ShaderGraph에서는 그림자가 그려지는 Shadow Pass를 자동으로 생성한다.\
HLSL 코드로 셰이더를 작성하면 Shadow Pass와 Depth Pass까지 모두 작성해야 한다.

하지만 우리가 만든 Shader Graph를 적용한 오브젝트 위에 공을 가져다 놓으면 그림자가 드리워지지 않는다.\
'**그림자를 드리우는 연산**'은 했는데, '**내가 그림자를 받는 연산**'은 해주지 않았기 때문이다.

이것을 연산하려면, 그림자 연산을 받아와서 Lambert 연산과 Specular 연산에 곱해줘 조명 계산을 없애줘야 한다.

![](https://velog.velcdn.com/images/biomatrix117/post/7a8bcd20-c039-4f9c-bfd6-05f2425d2e72/image.png)

```cs
void CustomLight_File_float(out float3 Direction, out float3 Color)
{
    #ifdef SHADERGRAPH_PREVIEW
        Direction = float3(1,1,1);
        Color = float3(1,1,1);
    #else
        Light light = GetMainLight();
        Direction = light.direction;
        Color = light.color;
    #endif
}
```

CustomLight.hlsl 파일을 만들고 sub graph로 만들었던 Lambert에 가서 Cumstom Function의 Precision, Type, Name 등을 바꿔주면 String이 아닌 File 형식으로 CustomLight.hlsl을 적용시킬 수 있다.

```cs
//커스텀라이트
void CustomLight_File_float(out float3 Direction, out float3 Color)
{
    #ifdef SHADERGRAPH_PREVIEW
        Direction = float3(1, 1, 1);
        Color = float3(1, 1, 1);
    #else
        Light light = GetMainLight();
        Direction = light.direction;
        Color = light.color;
    #endif
}


//커스텀라이트 셰도우
void CustomLight_Shadow_float(float3 worldPos, out float ShadowAtten)
{
    #ifdef SHADERGRAPH_PREVIEW
        ShadowAtten = 1.0f;
    #else
        //shadow Coord 만들기
        #if defined(_MAIN_LIGHT_SHADOWS_SCREEN) && !defined(_SURFACE_TYPE_TRANSPARENT)
            half4 clipPos = TransformWorldToHClip(worldPos);
            half4 shadowCoord = ComputeScreenPos(clipPos);
        #else
            half4 shadowCoord = TransformWorldToShadowCoord(worldPos);
        #endif
        
        Light light = GetMainLight();
        //메인라이트가 없거나 받는 셰도우 오프 옵션이 되어 있을때는 그림자를 없앤다
        #if !defined(_MAIN_LIGHT_SHADOWS) || defined(_RECEIVE_SHADOWS_OFF)
            ShadowAtten = 1.0f;
        #endif

        //ShadowAtten 받아와서 만들기
        #if SHADOWS_SCREEN
            ShadowAtten = SampleScreenSpaceShadowmap(shadowCoord);
        #else
            ShadowSamplingData shadowSamplingData = GetMainLightShadowSamplingData();
            half shadowStrength = GetMainLightShadowStrength();
            ShadowAtten = SampleShadowmap(shadowCoord, TEXTURE2D_ARGS(_MainLightShadowmapTexture,
            sampler_MainLightShadowmapTexture),
            shadowSamplingData, shadowStrength, false);
        #endif
    #endif
}
```

![](https://velog.velcdn.com/images/biomatrix117/post/38b78616-918b-4457-a8c9-8089db71fc75/image.png)

CustomLight.hlsl에 CustomLight\_Shadow 함수를 추가한 뒤 테스트해보면 두 가지 문제가 있다.

* 거리가 멀어지면 아예 새까맣게 되거나 새하얗게 됨
* Soft Shadow 설정이 되지 않아 그림자의 픽셀이 두드러짐

이유는 Keyword로 Multi Compile을 제대로 설정해줘야 하기 때문이다.

그림자는 유니티 엔진 내부에서 제어하고 있다.

* 그림자 On/Off 명령
* 오브젝트에 받아라/받지 말아라는 명령
* 소프트 셰도우와 하드 셰도우 옵션
* 그림자 Cascade 1개-4개까지의 옵션
* 그림자 거리 조절 등

그래서 그림자 관련 셰이더를 만든다면, 유니티에서 제어되고 있는 그림자 옵션과 동일하게 만들어야 사용할 수 있다.

**Keyword**\
키워드는 Property의 일종이다. 경우에 따라 다른 셰이더 옵션을 활성시키는 기능이다.\
키워드는 플랫폼별, 머테리얼별, 조건별로 변화하는 셰이더를 만들어야 할 때 유리하다.

* Boolean : On/Off로 선택
* Enum : A, B, C와 같은 다중 메뉴 중에서 선택
* Material Quality : 내장된 상중하 셰이더 옵션 선택

if문은 실시간으로 필요에 따라 작동하지만,\
키워드는 미리 지정한 선택대로 셰이더를 자동으로 분리해서 갖고 있다는 점이 다르다.\
즉, 이 작업이 많아지면 셰이더의 개수가 제곱으로 늘어난다.

**Keyword의 Node Settings**

* Name : Property의 이름. Refernce와 같은 이름을 써주는 것이 가장 일반적.
* Exposed : 이 기능이 체크돼있으면 Property가 Inspector에 노출된다
* Reference Name : 키워드의 이름
  * 키워드는 모두 영어 대문자로 써야 한다
  * HLSL에서 지원하지 않는 문자는 모두 `_`로 변환된다
  * Reference의 이름을 오른 클릭하고 Reset Reference를 선택하면 기본 이름으로 돌아간다
* Definition : 키워드가 정의되는 방식을 설정한다
  * Shader Feature : 빌드할 때 사용되지 않는 셰이더 베리언트를 제거한다. 용량을 절약할 수 있지만 필요한 베리언트가 포함되지 않을 수도 있다.
  * Multi Compile : 빌드할 때 사용되지 않는 셰이더 베리언트도 제거하지 않는다. 용량이 커질 수 있으니 조심해야 한다.
  * Predefined : 현재의 Render Pipeline에서 이미 이 키워드를 정의했으므로, 셰이더 그래프에서 생성되는 코드에서는 이를 정의하지 않음을 의미
* Scope : 키워드를 정의할 범위를 설정한다. Predefined Keywords일 경우 이 옵션은 비활성화된다.
  * Global Keywords : 전체 프로젝트에 대한 키워드를 정의하며, 글로벌 키워드 한도에 포함된다
  * Local Keywords : 로컬 키워드 제한이 있는 하나의 셰이더에 대해서만 키워드를 정의한다

![](https://velog.velcdn.com/images/biomatrix117/post/4a1c6da3-59b3-49c3-9a78-ea3ac8e97db4/image.png)

그림자는 직접광, 즉 Diffuse와 Specular에만 영향을 끼치는 것이므로\
Diffuse + Specular에 Shadow를 곱해주면 된다.

> **Troubleshooting**\
> 책과 유니티 버전이 달라서인지 혼란이 조금 있었다. 책에서는 `Keyword로 Multi Compile을 제대로 설정해줘야 하기 때문이다.`라는 언급이 있었는데, 확인해보니 Keyword를 추가해주지 않아도 Soft Shadow가 설정됐다. Soft Shadow 옵션을 켜니 뭔가 달라지긴 하는데, 이상하게도 바닥에 드리워진 그림자와 돼지에게 드리워진 그림자 둘 다 "Soft"하진 않아 보인다. 일단 그림자 드리워졌으니 넘어가기.

**아직 남은 것들**

* 포인트 라이트에 받는 영향
* Fog 연산
* 라이트맵과의 관계
* 라이트 프로브
* 리플렉션 프로브


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://lazyartisan.gitbook.io/note/main-page/books/urp/part-16-or.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
