You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

269 lines
15 KiB
HTML

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!DOCTYPE html><html lang="ko"><head>
<meta charset="utf-8">
<title>안개(Fog)</title>
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@threejs">
<meta name="twitter:title" content="Three.js 안개(Fog)">
<meta property="og:image" content="https://threejs.org/files/share.png">
<link rel="shortcut icon" href="../../files/favicon_white.ico" media="(prefers-color-scheme: dark)">
<link rel="shortcut icon" href="../../files/favicon.ico" media="(prefers-color-scheme: light)">
<link rel="stylesheet" href="../resources/lesson.css">
<link rel="stylesheet" href="../resources/lang.css">
<script type="importmap">
{
"imports": {
"three": "../../build/three.module.js"
}
}
</script>
<link rel="stylesheet" href="/manual/ko/lang.css">
</head>
<body>
<div class="container">
<div class="lesson-title">
<h1>안개(Fog)</h1>
</div>
<div class="lesson">
<div class="lesson-main">
<p>※ 이 글은 Three.js의 튜토리얼 시리즈로서,
먼저 <a href="fundamentals.html">Three.js의 기본 구조에 관한 글</a>
읽고 오길 권장합니다.</p>
<p>※ 카메라에 대해 잘 모른다면, 먼저 <a href="cameras.html">카메라에 관한 글</a>
먼저 읽기 바랍니다.</p>
<p>3D 엔진에서 안개란, 일반적으로 카메라로부터의 거리에 따라 특정 색상으로
점차 변화하는 것을 말합니다. Three.js에서는 <a href="/docs/#api/ko/scenes/Fog"><code class="notranslate" translate="no">Fog</code></a><a href="/docs/#api/ko/scenes/FogExp2"><code class="notranslate" translate="no">FogExp2</code></a> 객체를
생성한 뒤, 장면(scene)의 <a href="/docs/#api/ko/scenes/Scene#fog"><code class="notranslate" translate="no">fog</code></a> 속성에 지정해 안개를 사용합니다.</p>
<p><a href="/docs/#api/ko/scenes/Fog"><code class="notranslate" translate="no">Fog</code></a>는 인자로 <code class="notranslate" translate="no">near</code><code class="notranslate" translate="no">far</code>값을 받는데, 이는 카메라로부터의 거리값입니다.
<code class="notranslate" translate="no">near</code>값보다 가까운 공간은 안개의 영향이 전혀 없고, <code class="notranslate" translate="no">far</code>값보다 먼 공간은
완전히 안개에 뒤덮입니다. <code class="notranslate" translate="no">near</code><code class="notranslate" translate="no">far</code> 사이의 공간에 있는 물체 또는 물체의
일부는 점차 안개의 색으로 변화하죠.</p>
<p><a href="/docs/#api/ko/scenes/FogExp2"><code class="notranslate" translate="no">FogExp2</code></a>는 카메라에서 멀어질수록 안개의 강도가 강해집니다.</p>
<p>두 가지 안개 모두 마찬가지로, 안개를 사용하려면 장면의 속성에 지정해야 합니다.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const scene = new THREE.Scene();
{
const color = 0xFFFFFF; // 하양
const near = 10;
const far = 100;
scene.fog = new THREE.Fog(color, near, far);
}
</pre>
<p><a href="/docs/#api/ko/scenes/FogExp2"><code class="notranslate" translate="no">FogExp2</code></a>의 경우는 다음처럼 쓸 수 있죠.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const scene = new THREE.Scene();
{
const color = 0xFFFFFF;
const density = 0.1;
scene.fog = new THREE.FogExp2(color, density);
}
</pre>
<p><a href="/docs/#api/ko/scenes/FogExp2"><code class="notranslate" translate="no">FogExp2</code></a>가 더 현실적이긴 하나, 보통 안개의 범위를 특정하기 쉬운
<a href="/docs/#api/ko/scenes/Fog"><code class="notranslate" translate="no">Fog</code></a>를 더 많이 사용합니다.</p>
<div class="spread">
<div>
<div data-diagram="fog"></div>
<div class="code">THREE.Fog</div>
</div>
<div>
<div data-diagram="fogExp2"></div>
<div class="code">THREE.FogExp2</div>
</div>
</div>
<p>한 가지 알아둬야 하는 건 안개는 <em>렌더링되는 물체</em>라는 점입니다.
안개는 물체의 픽셀을 렌더링할 때 같이 렌더링되는데, 이 말은 장면에
특정 색상의 안개 효과를 주려면 안개와 배경색 <strong>둘 다</strong> 같은 색으로
지정해야 한다는 겁니다. 배경색은 <a href="/docs/#api/ko/scenes/Scene#background"><code class="notranslate" translate="no">scene.background</code></a>
속성을 <a href="/docs/#api/ko/math/Color"><code class="notranslate" translate="no">THREE.Color</code></a> 인스턴스로 지정해 바꿀 수 있습니다.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">scene.background = new THREE.Color('#F00'); // 빨강
</pre>
<div class="spread">
<div>
<div data-diagram="fogBlueBackgroundRed" style="height:300px" class="border"></div>
<div class="code">파란 안개, 빨간 배경</div>
</div>
<div>
<div data-diagram="fogBlueBackgroundBlue" style="height:300px" class="border"></div>
<div class="code">파란 안개, 파란 배경</div>
</div>
</div>
<p>아래는 이전에 사용했던 예제에 안개를 추가한 것입니다. 장면을 생성한 뒤
안개를 추가하고, 장면의 배경색을 바꾸기만 했죠.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const scene = new THREE.Scene();
+{
+ const near = 1;
+ const far = 2;
+ const color = 'lightblue';
+ scene.fog = new THREE.Fog(color, near, far);
+ scene.background = new THREE.Color(color);
+}
</pre>
<p>아래 예제의 카메라는 <code class="notranslate" translate="no">near</code>값이 0.1, <code class="notranslate" translate="no">far</code>값이 5입니다. 카메라의 위치는
<code class="notranslate" translate="no">z = 2</code>이죠. 정육면체의 크기는 1칸이고, z축의 원점에 있습니다. 여기서
안개를 <code class="notranslate" translate="no">near = 1</code>, <code class="notranslate" translate="no">far = 2</code>로 설정하면 정육면체가 중간부터 사라지기
시작하겠죠.</p>
<p></p><div translate="no" class="threejs_example_container notranslate">
<div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/fog.html"></iframe></div>
<a class="threejs_center" href="/manual/examples/fog.html" target="_blank">새 탭에서 보기</a>
</div>
<p></p>
<p>인터페이스를 추가해 안개를 조정할 수 있도록 하겠습니다. 이번에도 <a href="https://github.com/georgealways/lil-gui">lil-gui</a>
사용할 거예요. lil-gui는 객체와 객체의 속성 키값을 받아 자동으로 인터페이스를
생성합니다. 단순히 안개의 <code class="notranslate" translate="no">near</code><code class="notranslate" translate="no">far</code> 제어하도록 설정할 수도 있지만, <code class="notranslate" translate="no">near</code>값이
<code class="notranslate" translate="no">far</code>값보다 큰 경우는 없기에 헬퍼를 만들어 <code class="notranslate" translate="no">near</code>값을 항상 <code class="notranslate" translate="no">far</code>보다 같거나
작게, <code class="notranslate" translate="no">far</code>값을 항상 <code class="notranslate" translate="no">near</code>보다 같거나 크게 설정하도록 하겠습니다.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">/**
* 이 클래스의 인스턴스를 lil-gui에 넘겨
* near나 far 속성을 조정할 때 항상
* near는 never &gt;= far, far는 never &lt;= near가 되도록 합니다
**/
class FogGUIHelper {
constructor(fog) {
this.fog = fog;
}
get near() {
return this.fog.near;
}
set near(v) {
this.fog.near = v;
this.fog.far = Math.max(this.fog.far, v);
}
get far() {
return this.fog.far;
}
set far(v) {
this.fog.far = v;
this.fog.near = Math.min(this.fog.near, v);
}
}
</pre>
<p>방금 만든 클래스를 아래처럼 활용합니다.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
const near = 1;
const far = 2;
const color = 'lightblue';
scene.fog = new THREE.Fog(color, near, far);
scene.background = new THREE.Color(color);
+
+ const fogGUIHelper = new FogGUIHelper(scene.fog);
+ gui.add(fogGUIHelper, 'near', near, far).listen();
+ gui.add(fogGUIHelper, 'far', near, far).listen();
}
</pre>
<p><code class="notranslate" translate="no">near</code><code class="notranslate" translate="no">far</code> 인자는 각 안개 속성의 최솟값과 최댓값입니다.</p>
<p>마지막 2줄의 <code class="notranslate" translate="no">.listen()</code> 메서드를 호출하면 lil-gui가 변화를 <em>감지</em>합니다.
<code class="notranslate" translate="no">near</code> 속성을 바꿀 때 동시에 <code class="notranslate" translate="no">far</code> 속성을 재할당하고, <code class="notranslate" translate="no">far</code> 속성을 바꿀 때도
동시에 <code class="notranslate" translate="no">near</code>를 재할당하는데, 이 메서드를 호출하면 조작한 속성 외의 다른
속성의 변화도 UI에 업데이트됩니다.</p>
<p>여기에 안개의 색까지 조정할 수 있으면 금상첨화겠네요. 하지만 아까 설명했듯
안개의 색을 바꾸려면 배경색도 같이 바꿔야 합니다. 헬퍼 클래스에 <em>가상</em> 속성을
하나 만들어 lil-gui가 이 속성을 변경할 때 배경색과 안개색을 같은 값으로
바꿔주면 어떨까요?</p>
<p>lil-gui의 색상 타입은 4가지입니다. 하나는 CSS의 6자리 16진수 문자열(hex string, 예: <code class="notranslate" translate="no">#f8f8f8</code>)이고,
하나는 hue(색상), saturation(채도), value 객체(예: <code class="notranslate" translate="no">{ h: 60, s: 1, v: 0 }</code>),
하나는 RGB 배열(예: <code class="notranslate" translate="no">[ 255, 128, 64 ]</code>) 또는 RGBA 색상 배열(예: <code class="notranslate" translate="no">[ 127, 200, 75, 0.3 ]</code>)이죠.</p>
<p><code class="notranslate" translate="no">lil-gui</code>가 하나의 값만 조작하도록 하는 게 제일 간단하니, 16진수 문자열을 사용하겠습니다.
다행히 <a href="/docs/#api/ko/math/Color"><code class="notranslate" translate="no">THREE.Color</code></a>에는 <a href="/docs/#api/ko/math/Color#getHexString"><code class="notranslate" translate="no">getHexString</code></a> 메서드가 있어 색상을
문자열로 쉽게 바꿀 수 있죠. 앞에 '#'만 덧붙이면 됩니다.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">/**
* 이 클래스의 인스턴스를 lil-gui에 넘겨
* near나 far 속성을 조정할 때 항상
* near는 never &gt;= far, far는 never &lt;= near가 되도록 합니다
**/
+// 또 lil-gui가 color 속성을 조작할 때 안개와 배경색을 동시에 변경합니다
class FogGUIHelper {
* constructor(fog, backgroundColor) {
this.fog = fog;
+ this.backgroundColor = backgroundColor;
}
get near() {
return this.fog.near;
}
set near(v) {
this.fog.near = v;
this.fog.far = Math.max(this.fog.far, v);
}
get far() {
return this.fog.far;
}
set far(v) {
this.fog.far = v;
this.fog.near = Math.min(this.fog.near, v);
}
+ get color() {
+ return `#${this.fog.color.getHexString()}`;
+ }
+ set color(hexString) {
+ this.fog.color.set(hexString);
+ this.backgroundColor.set(hexString);
+ }
}
</pre>
<p>이번에는 <code class="notranslate" translate="no">gui.addColor</code> 메서드를 호출합니다. 색상 UI를 생성하는 메서드로,
방금 추가한 가상 속성을 조작하도록 설정합니다.</p>
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">{
const near = 1;
const far = 2;
const color = 'lightblue';
scene.fog = new THREE.Fog(color, near, far);
scene.background = new THREE.Color(color);
* const fogGUIHelper = new FogGUIHelper(scene.fog, scene.background);
gui.add(fogGUIHelper, 'near', near, far).listen();
gui.add(fogGUIHelper, 'far', near, far).listen();
+ gui.addColor(fogGUIHelper, 'color');
}
</pre>
<p></p><div translate="no" class="threejs_example_container notranslate">
<div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/fog-gui.html"></iframe></div>
<a class="threejs_center" href="/manual/examples/fog-gui.html" target="_blank">새 탭에서 보기</a>
</div>
<p></p>
<p><code class="notranslate" translate="no">near</code>를 1.9 정도, <code class="notranslate" translate="no">far</code>를 2.0 정도로 설정하면 안개의 경계가 굉장히
선명해질 겁니다. 정육면체들이 카메라에서 2칸 떨어져 있으므로 <code class="notranslate" translate="no">near</code>
1.1, <code class="notranslate" translate="no">far</code>를 2.9 정도로 설정하면 경계가 가장 부드러운 것이라고 할 수
있죠.</p>
<p>추가로 재질(material)에는 불린 타입의 <a href="/docs/#api/ko/materials/Material#fog"><code class="notranslate" translate="no">fog</code></a> 속성이 있습니다.
해당 재질로 렌더링되는 물체가 안개의 영향을 받을지의 여부를 결정하는 속성이죠.
"안개 효과를 없애버리면 그만 아닌가?" 생각할 수 있지만, 3D 운전 시뮬레이터를
만드는 경우를 상상해봅시다. 차 밖은 안개가 자욱하더라도 차 안에서 볼 때 차 내부는
깔끔해야 할 수도 있죠.</p>
<p>안개가 짙은 날, 집 안에서 창 밖을 바라보는 장면이 더 와닿을지도 모르겠네요.
안개가 카메라로부터 2미터 이후부터 끼기 시작하고(near = 2), 4미터 이후에는
완전히 안개에 덮히도록(far = 4) 설정합니다. 방은 2미터이고, 집은 최소 4미터입니다.
여기서 집 안의 재질이 안개의 영향을 받도록 놔둔다면 방 끝에서 창 밖을 바라볼
때 방 안도 안개가 낀 것처럼 보이겠죠.</p>
<div class="spread">
<div>
<div data-diagram="fogHouseAll" style="height: 300px;" class="border"></div>
<div class="code">모든 재질의 fog: true</div>
</div>
</div>
<p>방 끝 쪽 천장과 벽에 안개가 낀 것이 보일 겁니다. 집 내부 재질의 fog 옵션을 끄면
안개를 없앨 수 있죠.</p>
<div class="spread">
<div>
<div data-diagram="fogHouseInsideNoFog" style="height: 300px;" class="border"></div>
<div class="code">집 밖 물체의 재질만 fog: true</div>
</div>
</div>
<p><canvas id="c"></canvas></p>
<script type="module" src="../resources/threejs-fog.js"></script>
</div>
</div>
</div>
<script src="../resources/prettify.js"></script>
<script src="../resources/lesson.js"></script>
</body></html>