---
title: "손전등을 비스듬히 비추면 어두워지는 이유"
description: "같은 빛이 넓게 퍼지면 단위 면적이 받는 양이 줄어듭니다. 그 각도를 내적 한 번으로 구하는 것이 Diffuse 입니다."
author: "유니얼"
pubDate: 2026-09-14
category: "game-dev/graphics"
tags: ["렌더링", "셰이더"]
canonical: https://blog.unialgames.com/posts/lighting-diffuse/
---
손전등을 벽에 정면으로 비추면 작고 밝은 원이 생깁니다. 같은 손전등을
비스듬히 기울이면 그 빛이 길쭉한 타원으로 늘어나면서 눈에 띄게 어두워집니다.

빛의 양은 한 톨도 안 줄었습니다. **퍼지는 면적이 넓어져서** 단위 면적이
받는 양이 줄어든 것뿐입니다.

이것이 표면의 기본 밝기를 정하는 규칙입니다. 램버트 코사인 법칙이라고
부릅니다.

## 얼마나 줄어드나

빛다발이 표면에 각도 θ로 들어오면, 받는 면적은 `1 / cos θ` 배로 늘어납니다.
그러니 단위 면적이 받는 양은 `cos θ` 배가 됩니다.

정면이면 θ가 0이라 `cos 0 = 1`, 그대로 다 받습니다.
45도로 기울면 `cos 45° ≈ 0.71`, 30퍼센트를 잃습니다.
90도로 스치면 `cos 90° = 0`, 아무것도 안 받습니다.

## 코사인을 안 구하고 코사인을 씁니다

θ는 표면의 법선과 빛 방향이 이루는 각도입니다. 그런데 각도를 실제로
재고 코사인 함수를 부르는 건 낭비입니다.

**길이 1짜리 벡터 둘의 내적이 곧 사잇각의 코사인**이기 때문입니다.

```hlsl
float  ndotl   = saturate(dot(N, L));
float3 diffuse = lightColor * albedo * ndotl;
```

곱셈 세 번과 덧셈 두 번이면 끝납니다. `N` 은 그 자리의 법선, `L` 은 그
자리에서 광원 쪽을 가리키는 방향입니다. 둘 다 길이를 1로 맞춰 둬야 합니다.
안 맞춰 두면 내적이 코사인이 아니라 그냥 아무 숫자가 됩니다.

`saturate` 로 0에서 자르는 이유는 빛을 등진 면에서 내적이 음수로
나오기 때문입니다. 그대로 두면 **빛을 뺀다**는 뜻이 되어 버립니다.

## 색 셋을 곱합니다

`lightColor * albedo * ndotl` 에서 앞의 둘은 색이고 마지막 하나만 세기입니다.

붉은 빛을 파란 물체에 쏘면 거의 검게 보입니다. 붉은 빛에는 파란 성분이
거의 없고 파란 물체는 붉은 성분을 거의 안 되돌려 보내기 때문입니다.
채널마다 따로 곱하니 이게 저절로 나옵니다.

## 보는 방향은 안 씁니다

계산에 들어가는 것은 법선과 빛 방향 둘뿐입니다. **카메라가 어디에 있는지는
안 씁니다.**

그래서 Diffuse 로만 칠한 표면은 어느 각도에서 봐도 같은 밝기입니다.
벽지, 천, 종이, 무광 페인트가 이렇게 보입니다. 표면이 미세하게 거칠어서
들어온 빛을 사방으로 고르게 흩어 보내는 재질들입니다.

아래에서 빛이 도는 동안 구가 어떻게 칠해지는지 보세요.

<x-shade>
<script type="application/json">
{
  "label": "빛이 도는 동안 각도가 밝기를 정한다",
  "terms": ["ambient", "diffuse"],
  "note": "밝은 자리와 어두운 자리를 가르는 경계를 터미네이터라고 부릅니다. 법선이 빛과 직각이 되는 자리입니다."
}
</script>
</x-shade>

빛이 정면으로 오는 자리가 가장 밝고, 거기서 멀어질수록 부드럽게
어두워집니다. 계단이 아니라 매끄러운 기울기로 변하는 이유는 구 표면의
법선이 자리마다 조금씩 돌아가 있기 때문입니다.

오른쪽 막대에서 Diffuse 칸이 빛을 따라 늘었다 줄었다 합니다.
Ambient 칸은 그동안 꼼짝도 안 합니다.

## 법선은 정점에 들어 있습니다

메시는 정점마다 좌표, UV, 법선을 들고 있습니다. 픽셀 하나하나가 자기
법선을 갖고 있는 게 아닙니다.

래스터라이저가 삼각형을 픽셀로 쪼갤 때 세 정점의 법선을 섞어서 각 픽셀에
넘겨줍니다. 이 보간 덕분에 **각진 저폴리 구체도 둥글게 보입니다.**
실제 모양은 각졌는데 법선이 부드럽게 변하니까요.

정점에서 한 번만 계산하고 그 결과 색을 보간하는 방식도 있습니다. 정점이
적으면 훨씬 싸지만 하이라이트가 뭉개지고 삼각형 경계가 드러납니다.
요즘은 법선을 보간해서 픽셀마다 계산합니다.

## 거리에 따라 줄어드는 것은 따로입니다

각도로 줄어드는 것과 거리로 줄어드는 것은 다른 이야기입니다.

태양처럼 아주 먼 광원은 **거리를 안 씁니다.** 장면 어디서나 같은 방향으로
같은 세기로 들어온다고 봅니다. 방향만 있고 위치가 없어서 디렉셔널
라이트라고 부릅니다.

전구처럼 가까운 광원은 멀어질수록 어두워집니다. 이걸 감쇠라고 하고
Diffuse 에 한 번 더 곱합니다. 물리대로면 거리 제곱에 반비례하는데,
그러면 광원 바로 옆이 무한대로 발산해서 보통 손을 봅니다.

```hlsl
float3 toLight = lightPos - worldPos;
float  dist    = length(toLight);
float3 L       = toLight / dist;
float  atten   = 1.0 / (1.0 + a * dist + b * dist * dist);
```

## 정리

- 표면이 빛을 비스듬히 받으면 같은 빛이 넓게 퍼져서 단위 면적이 받는 양이 `cos θ` 배로 줄어듭니다.
- 그 코사인을 법선과 빛 방향의 내적으로 구합니다. 두 벡터의 길이가 1이어야 성립합니다.
- 등진 면에서 내적이 음수로 나오므로 0에서 잘라야 합니다. 안 자르면 빛을 빼게 됩니다.
- **보는 방향을 안 씁니다.** 어디서 봐도 같은 밝기라, 천이나 종이처럼 빛을 고르게 흩는 재질을 담당합니다.
- 법선은 정점에 저장되고 픽셀에는 보간된 값이 갑니다. 각진 구체가 둥글어 보이는 게 이 보간 덕분입니다.
- 각도로 줄어드는 것과 거리로 줄어드는 것은 별개입니다. 거리 감쇠는 따로 곱합니다.