GIS Developer λμ Three.js κ°μ’ : https://www.youtube.com/watch?v=L_70k2sAuds
μ¬μ©μ μ μ μ§μ€λ©νΈλ¦¬(Custom Geometry)
: 볡μ‘ν νμμ λ§λ€ μ μλ€.
BufferGeometry μ λν΄ μ μν μ μλ μμ±
- position : geometryλ₯Ό ꡬμ±νλ 3μ°¨μ μ’νμ λν μ μ (Vertex)
- normal : κ° μ μ μ λν μμ§ λ²‘ν°
- color : κ° μ μ μ λν μμκ°
- uv : κ° μ μ μ λν ν μ€μ³ λ§€ν μ’ν
- Vertex Indexλ setIndex λ©μλλ‘ μ§μ : position μμ±μΌλ‘ μ§μ λ μ μ μ λν μΈλ±μ€ λ°°μ΄λ‘ μ§μ .
ex) meshλ₯Ό ꡬμ±νλ λ©΄μ μ΅μ λ¨μλ μΌκ°νμ΄κ³ μ΄ μΌκ°νμ 3κ°μ μ μ μΌλ‘ ꡬμ±λλ€. μ΄ 3κ°μ μ μ μ λν position μμ±μμμ μ μ μΈλ±μ€ λ²νΈκ° Vertex Indexλ€.
μ¬κΈ°μ μΌκ°νμ ꡬμ±νλ μ μ μ λ°°μΉ μμλ λ°μκ³ λ°©ν₯μ΄μ¬μΌ νλ€. λ°μκ³ λ°©ν₯μΈ λ©΄μ΄ μλ©΄μ΄κΈ° λλ¬Έμ΄λ€.
κ·Έλ¦¬κ³ λͺ¨λ μ μ μ λν λ²μ 벑ν°λ₯Ό μ§μ ν΄μΌνλ€.
* λ²μ λ²‘ν° : κ΄μμ΄ meshμ νλ©΄μ λΉμΆλ μ μ¬κ°κ³Ό λ°μ¬κ°μ κ³μ°νμ¬ μ¬μ§κ³Ό ν¨κ» νλ©΄μ μμμ κ²°μ νλλ° μ¬μ©λ¨
// 05-custom-geometry.js
import * as THREE from '../build/three.module.js';
import {OrbitControls} from '../examples/jsm/controls/OrbitControls.js';
import {VertexNormalsHelper} from '../examples/jsm/helpers/VertexNormalsHelper.js';
class App {
constructor() {
const divContainer = document.querySelector('#webgl-container');
this._divContainer = divContainer;
const renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setPixelRatio(window.devicePixelRatio);
divContainer.appendChild(renderer.domElement);
this._renderer = renderer;
const scene = new THREE.Scene();
this._scene = scene;
this._setupCamera();
this._setupLight();
this._setupModel();
this._setupControls();
window.onresize = this.resize.bind(this);
this.resize();
requestAnimationFrame(this.render.bind(this));
}
_setupControls() {
new OrbitControls(this._camera, this._divContainer);
}
_setupCamera() {
const width = this._divContainer.clientWidth;
const height = this._divContainer.clientHeight;
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 100);
camera.position.z = 2;
this._camera = camera;
}
_setupLight() {
const color = 0xffffff;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
this._scene.add(light);
}
_setupModel() {
const rawPositions = [
-1, -1, 0,
1, -1, 0,
-1, 1, 0,
1, 1, 0,
];
// λ²μ λ°°μ΄μ λν λ°°μ΄ λ°μ΄ν°
const rawNormals = [
0, 0, 1, // meshμ λ©΄μΌλ‘ λ΄€μ λ λ©΄μ λν μμ§μΈ λ²‘ν° 0,0,1
0, 0, 1,
0, 0, 1,
0, 0, 1,
];
// μ μ μ λν μμκ° μ§μ
const rawColors = [
1, 0, 0,
0, 1, 0,
0, 0, 1,
1, 1, 0,
];
// ν
μ€μ³ λ§΅ν
const rawUvs = [
0, 0, // μ΄λ―Έμ§μ μ’μΈ‘ νλ¨ μμΉλ‘ geometryμ μ μ μ’ν (-1, -1, 0)μ λ§΅ν
1, 0,
0, 1,
1, 1,
];
const positions = new Float32Array(rawPositions);
const normals = new Float32Array(rawNormals);
const colors = new Float32Array(rawColors);
const uvs = new Float32Array(rawUvs);
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setAttribute('normal', new THREE.BufferAttribute(normals, 3));
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));
// λ²μ 벑ν°λ₯Ό μλμΌλ‘ κ³μ°ν΄μ£Όλ λ©μλ λμ rawNormals λ‘ μ§μ μ§μ ν΄μ£Όμλ€.
//geometry.computeVertexNormals();
// vertax index λ μΌκ°ν λ©΄μ μ μν¨. μ¬κ°νμ λ κ°μ μΌκ°νμΌλ‘ μ΄λ£¨μ΄μ Έ μλ€.
geometry.setIndex([
0, 1, 2,
2, 1, 3,
]);
const textureLoader = new THREE.TextureLoader();
const map = textureLoader.load('../examples/textures/uv_grid_opengl.jpg');
// vertexColor : κ° λ²ν
μ€μ μ§μ λ μμλλ‘ meshλ₯Ό ννν κ²μΈμ§μ λν μ¬λΆ
const material = new THREE.MeshPhongMaterial({
color: 0xffffff,
vertexColors: true,
map: map,
});
const box = new THREE.Mesh(geometry, material);
this._scene.add(box);
const helper = new VertexNormalsHelper(box, 0.1, 0xffff00);
this._scene.add(helper);
}
resize() {
const width = this._divContainer.clientWidth;
const height = this._divContainer.clientHeight;
this._camera.aspect = width / height;
this._camera.updateProjectionMatrix();
this._renderer.setSize(width, height);
}
render(time) {
this._renderer.render(this._scene, this._camera);
this.update(time);
requestAnimationFrame(this.render.bind(this));
}
update(time) {
time *= 0.001; // second unit
}
}
window.onload = function() {
new App();
}
κ΄μ(Light)
Lightλ₯Ό μμλ°λ ν΄λμ€
νκ²½κ΄ λλ μ£Όλ³κ΄
- AmbientLight : λ¨μν sceneμ μ‘΄μ¬νλ λͺ¨λ 물체μ λν΄μ λ¨μΌ μμμΌλ‘ λ λλ§νλ€. λλΆλΆμ κ²½μ° μΈκΈ°κ°μ μμ£Ό μ½νκ² μ§μ ν΄μ κ΄μμ μν₯μ λ°μ§ λͺ»νλ 물체λ μ΄μ§ 보μ¬μ§λλ‘ νλλ° μ¬μ©λλ€.
- HemisphereLight : AmbientLight μ λ€λ₯΄κ² λΉμ μμμ΄ 2κ°λ€.
λΉμ λ°©ν₯μ±μ κ°μ§λ κ΄μ
- DirectionalLight : νμμ²λΌ λΉκ³Ό 물체κ°μ 거리μ μκ΄μμ΄ λμΌν λΉμ ν¨κ³Ό. λΉμ positionκ³Ό target μμ±μΌλ‘ κ²°μ λλ λ°©ν₯λ§μ΄ μλ―Έκ° μλ€.
- PointLight : λΉμ΄ κ΄μμ μμΉμμ μ¬λ°©μΌλ‘ νΌμ Έλκ°.
- SpotLight : λΉμ΄ κ΄μμ μμΉμμ κΉλκΈ° λͺ¨μμΌλ‘ νΌμ Έλκ°.
- RectAreaLight : νκ΄λ±μ΄λ μ°½λ¬Έμμ λ€μ΄μ€λ κ΄μ.
* μ€μ λ‘λ λ³΄λ€ λμ λ λλ§ κ²°κ³Όλ₯Ό μν΄ μ¬λ¬ μ’ λ₯μ κ΄μμ 2κ° μ΄μ μ€μΉνμ¬ μ¬μ©νλ€.
// 06-light.js
import * as THREE from '../build/three.module.js';
import {OrbitControls} from '../examples/jsm/controls/OrbitControls.js';
import {RectAreaLightUniformsLib} from '../examples/jsm/lights/RectAreaLightUniformsLib.js';
import {RectAreaLightHelper} from '../examples/jsm/helpers/RectAreaLightHelper.js';
class App {
constructor() {
const divContainer = document.querySelector('#webgl-container');
this._divContainer = divContainer;
const renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setPixelRatio(window.devicePixelRatio);
divContainer.appendChild(renderer.domElement);
this._renderer = renderer;
const scene = new THREE.Scene();
this._scene = scene;
this._setupCamera();
this._setupLight();
this._setupModel();
this._setupControls();
window.onresize = this.resize.bind(this);
this.resize();
requestAnimationFrame(this.render.bind(this));
}
_setupControls() {
new OrbitControls(this._camera, this._divContainer);
}
_setupCamera() {
const width = this._divContainer.clientWidth;
const height = this._divContainer.clientHeight;
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 100);
//camera.position.z = 2;
camera.position.set(7, 7, 0);
// μΉ΄λ©λΌκ° μμ μΈ 0, 0, 0μ λ°λΌλ³΄κ² μ€μ
camera.lookAt(0, 0, 0);
this._camera = camera;
}
_setupLight() {
// AmbientLight(λΉμ μμ, μΈκΈ°)
//const light = new THREE.AmbientLight(0xffffff, 5);
// HemisphereLight(μμμ λΉμΉλ μμ, μλμμ λΉμΉλ μμ, μΈκΈ°)
//const light = new THREE.HemisphereLight('#b0d8f5', '#bb7a1c', 1);
// DirectionalLight
/* const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(0, 5, 0);
light.target.position.set(0, 0, 0);
this._scene.add(light.target);
// κ΄μμ μκ°ν
const helper = new THREE.DirectionalLightHelper(light);
this._scene.add(helper);
this._lightHelper = helper; */
// PointLight
/* const light = new THREE.PointLight(0xffffff, 2);
light.position.set(0, 5, 0);
// distance : μ§μ λ 거리κΉμ§λ§ κ΄μμ μν₯μ λ°μ. 0μ 무ν
light.distance = 0;
// κ΄μμ μκ°ν
const helper = new THREE.PointLightHelper(light);
this._scene.add(helper);
this._lightHelper = helper; */
// SpotLight
/* const light = new THREE.SpotLight(0xffffff, 1);
light.position.set(0, 5, 0);
light.target.position.set(0, 0, 0);
light.angle = THREE.Math.degToRad(40);
// penumbra : λΉμ κ°μμ¨
light.penumbra = 0;
this._scene.add(light.target);
// κ΄μμ μκ°ν
const helper = new THREE.SpotLightHelper(light);
this._scene.add(helper);
this._lightHelper = helper; */
// RectAreaLight (μμ, μΈκΈ°, κ°λ‘ ν¬κΈ°, μΈλ‘ ν¬κΈ°)
RectAreaLightUniformsLib.init();
const light = new THREE.RectAreaLight(0xffffff, 10, 6, 1);
light.position.set(0, 5, 0);
light.rotation.x = THREE.Math.degToRad(-90);
// κ΄μμ μκ°ν
const helper = new RectAreaLightHelper(light);
this._scene.add(helper);
this._lightHelper = helper;
this._scene.add(light);
this._light = light;
}
_setupModel() {
const groundGeometry = new THREE.PlaneGeometry(10, 10);
const groundMaterial = new THREE.MeshStandardMaterial({
color: '#2c3e50',
roughness: 0.5,
metalness: 0.5,
side: THREE.DoubleSide,
});
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = THREE.Math.degToRad(-90);
this._scene.add(ground);
const bigSphereGeometry = new THREE.SphereGeometry(1.5, 64, 64, 0, Math.PI);
const bigSphereMaterial = new THREE.MeshStandardMaterial({
color: '#ffffff',
roughness: 0.1,
metalness: 0.2,
});
const bigSphere = new THREE.Mesh(bigSphereGeometry, bigSphereMaterial);
bigSphere.rotation.x = THREE.Math.degToRad(-90);
this._scene.add(bigSphere);
const torusGeometry = new THREE.TorusGeometry(0.4, 0.1, 32, 32);
const torusMaterial = new THREE.MeshStandardMaterial({
color: '#9b59b6',
roughness: 0.5,
metalness: 0.9,
});
for (let i = 0; i < 8; i++) {
const torusPivot = new THREE.Object3D();
const torus = new THREE.Mesh(torusGeometry, torusMaterial);
torusPivot.rotation.y = THREE.Math.degToRad(45 * i);
torus.position.set(3, 0.5, 0);
torusPivot.add(torus);
this._scene.add(torusPivot);
}
const smallSphereGeometry = new THREE.SphereGeometry(0.3, 32, 32);
const smallSphereMaterial = new THREE.MeshStandardMaterial({
color: '#e74c3c',
roughness: 0.2,
metalness: 0.5,
});
const smallSpherePivot = new THREE.Object3D();
const smallSphere = new THREE.Mesh(smallSphereGeometry, smallSphereMaterial);
smallSpherePivot.add(smallSphere)
// μ΄λ¦μ λΆμ¬ν΄λλ©΄ sceneμμ μ‘°νν μ μλ€.
smallSpherePivot.name = 'smallSpherePivot';
smallSphere.position.set(3, 0.5, 0);
this._scene.add(smallSpherePivot);
}
resize() {
const width = this._divContainer.clientWidth;
const height = this._divContainer.clientHeight;
this._camera.aspect = width / height;
this._camera.updateProjectionMatrix();
this._renderer.setSize(width, height);
}
render(time) {
this._renderer.render(this._scene, this._camera);
this.update(time);
requestAnimationFrame(this.render.bind(this));
}
update(time) {
time *= 0.001; // second unit
const smallSpherePivot = this._scene.getObjectByName('smallSpherePivot');
if (smallSpherePivot) {
smallSpherePivot.rotation.y = THREE.Math.degToRad(time * 50);
// κ΄μμ λν targetμ μμΉκ° νμ νλ ꡬμ μμΉλ₯Ό μΆμ νλλ‘ (DirectionalLight, SpotLight)
if (this._light.target) {
const smallSphere = smallSpherePivot.children[0];
// smallSphere μ world μ’νκ³μ μμΉλ₯Ό ꡬν΄μ κ΄μμ target μμΉμ μ§μ
smallSphere.getWorldPosition(this._light.target.position);
if (this._lightHelper) {
this._lightHelper.update();
}
}
// (PointLight)
/* if (this._light) {
const smallSphere = smallSpherePivot.children[0];
smallSphere.getWorldPosition(this._light.position);
if (this._lightHelper) {
this._lightHelper.update();
}
} */
}
}
}
window.onload = function() {
new App();
}
QnA
Q : blender κ°μ ν΄λ νμ μμ μ°λμ? ꡬκΈλ§ ν΄λ³΄λ©΄ lightλ₯Ό ν μ€μ³ λ§΅νν λ λΈλ λλ₯Ό μ΄λ€λ μκΈ°κ° μ’ μ’ λμμμ. lightκ° λ무 λ§μΌλ©΄ λ λλ§μ΄ λλ €μ§λκΉ λμ ν μ€μ³λ‘ lightκ° μλ κ²μ²λΌ λμμνλ λ°©λ²μ μ°Ύκ³ μλ μ€μΈλ° blenderλ₯Ό μΈμ§ μλλ©΄ λ€λ₯Έ λ°©λ²μ΄ μλμ§ μκ°νλ μ€μ λλ€.
A : λΈλ λλ μ’μ ν΄μ΄κ³ μ μμ κ΄μ¬μ΄ λ§μ ν΄μΈλ°μ. νμ§λ§ νμ₯μμλ λ벨λ‘νΌκ° 3μ°¨μ λͺ¨λΈκΉμ§ νμ§ μμ΅λλ€. μ¬μ©νλ ν΄μ λΆμΌλ§λ€ λ€λ₯Έλ° μ ν¬μͺ½μ ꡬκΈμ μ€μΌμΉμ κ³Ό μ€λλ°μ€ν¬μ μν€λ₯Ό μ¬μ©ν©λλ€.