์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐ŸŒ WebGL & WebXR๋กœ ๋งŒ๋“œ๋Š”์ฐจ์„ธ๋Œ€ ์›น๋””์ž์ธ

๐ŸŒ WebGL & WebXR๋กœ ๋งŒ๋“œ๋Š”
์ฐจ์„ธ๋Œ€ ์›น๋””์ž์ธ

๋ธŒ๋ผ์šฐ์ €๊ฐ€ 3D ์„ธ๊ณ„๋ฅผ ํ’ˆ๋Š” ์ˆœ๊ฐ„ โ€” ์›น๋””์ž์ธ์˜ ๊ฒฝ๊ณ„๊ฐ€ ์‚ฌ๋ผ์ง„๋‹ค

WebGL WebXR 3D ์›น ๋ชฐ์ž…ํ˜• UX ์ฐจ์„ธ๋Œ€ ๋””์ž์ธ

์•ผ, ์†”์งํžˆ ๋งํ•ด๋ณผ๊ฒŒ. ์š”์ฆ˜ ์›น์‚ฌ์ดํŠธ ๋ณด๋‹ค ๋ณด๋ฉด "์–ด? ์ด๊ฒŒ ์›น์ด์•ผ, ๊ฒŒ์ž„์ด์•ผ?" ์‹ถ์€ ์ˆœ๊ฐ„์ด ์žˆ์ง€ ์•Š์•„? ๐Ÿค”
๋งˆ์šฐ์Šค ์›€์ง์ด๋ฉด 3D ์˜ค๋ธŒ์ ํŠธ๊ฐ€ ๋”ฐ๋ผ ๋Œ๊ณ , ์Šคํฌ๋กคํ•˜๋ฉด ์šฐ์ฃผ ๊ณต๊ฐ„์„ ๋‚ ์•„๋‹ค๋‹ˆ๋Š” ๊ฒƒ ๊ฐ™๊ณ , ์‹ฌ์ง€์–ด VR ํ—ค๋“œ์…‹ ๋ผ๋ฉด ๊ทธ ์›นํŽ˜์ด์ง€ ์•ˆ์œผ๋กœ ๋“ค์–ด๊ฐˆ ์ˆ˜ ์žˆ๋Š” ์‹œ๋Œ€๊ฐ€ ๋๊ฑฐ๋“ .

์ด๊ฒŒ ๋‹ค WebGL์ด๋ž‘ WebXR ๋•๋ถ„์ด์•ผ. ์ด ๋‘ ๊ธฐ์ˆ ์ด ๋ญ”์ง€, ์–ด๋–ป๊ฒŒ ์“ฐ๋Š”์ง€, ๊ทธ๋ฆฌ๊ณ  ์‹ค์ œ ์›น๋””์ž์ธ์—์„œ ์–ด๋–ป๊ฒŒ ํ™œ์šฉ๋˜๋Š”์ง€ โ€” ์˜ค๋Š˜ ์™„์ „ ์นœ์ ˆํ•˜๊ฒŒ ํŒŒํ—ค์ณ๋ณผ ๊ฑฐ์•ผ. ๊ฐœ๋ฐœ์ž๊ฐ€ ์•„๋‹ˆ์–ด๋„ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๊ฒŒ, ๋””์ž์ด๋„ˆ ์ž…์žฅ์—์„œ๋„ "์•„ ์ด๋ ‡๊ฒŒ ์“ฐ๋ฉด ๋˜๊ฒ ๊ตฌ๋‚˜!" ์‹ถ๊ฒŒ ์„ค๋ช…ํ•ด์ค„๊ฒŒ. ๐Ÿš€

๐Ÿ’ก ์ด ๊ธ€์„ ์ฝ์œผ๋ฉด ์•Œ๊ฒŒ ๋˜๋Š” ๊ฒƒ๋“ค WebGL์ด ๋ญ”์ง€, WebXR์ด ๋ญ”์ง€ ์ •ํ™•ํžˆ ์ดํ•ดํ•˜๊ณ  / ์‹ค์ œ ์›น๋””์ž์ธ์— ์–ด๋–ป๊ฒŒ ์ ์šฉ๋˜๋Š”์ง€ ์•Œ๊ณ  / Three.js ๊ฐ™์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ๋ญ˜ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋Š”์ง€ ํŒŒ์•…ํ•˜๊ณ  / ์ƒ์„ธํŽ˜์ด์ง€๋‚˜ ํฌํŠธํด๋ฆฌ์˜ค์— 3D๋ฅผ ๋…น์ด๋Š” ์‹ค์ „ ํŒ๊นŒ์ง€ ์ฑ™๊ฒจ๊ฐˆ ์ˆ˜ ์žˆ์–ด!

๐ŸŽฎ WebGL์ด ๋ญ”๋ฐ? โ€” GPU๋ฅผ ๋ธŒ๋ผ์šฐ์ €์— ๊ฝ‚๋‹ค

WebGL(Web Graphics Library)์€ ํ•œ๋งˆ๋””๋กœ ์›น ๋ธŒ๋ผ์šฐ์ €์—์„œ GPU๋ฅผ ์ง์ ‘ ์“ธ ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๊ธฐ์ˆ ์ด์•ผ. ์›๋ž˜ 3D ๊ทธ๋ž˜ํ”ฝ์€ ๊ฒŒ์ž„ ์—”์ง„์ด๋‚˜ ์ „๋ฌธ ์†Œํ”„ํŠธ์›จ์–ด์—์„œ๋‚˜ ๊ฐ€๋Šฅํ•œ ๊ฑฐ์˜€์ž–์•„. ๊ทธ๋Ÿฐ๋ฐ WebGL์ด ๋“ฑ์žฅํ•˜๋ฉด์„œ "ํ”Œ๋Ÿฌ๊ทธ์ธ ์—†์ด, ๋ธŒ๋ผ์šฐ์ €๋งŒ์œผ๋กœ 3D ๋ Œ๋”๋ง"์ด ๊ฐ€๋Šฅํ•ด์ง„ ๊ฑฐ์ง€.

๐Ÿ“Œ WebGL์˜ ํƒ„์ƒ ๋ฐฐ๊ฒฝ

WebGL์€ OpenGL ES 2.0์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๋งŒ๋“ค์–ด์กŒ์–ด. OpenGL์ด ๋ญ๋ƒ๊ณ ? 1992๋…„๋ถ€ํ„ฐ ์“ฐ์ธ ์˜คํ”ˆ์†Œ์Šค ๊ทธ๋ž˜ํ”ฝ API์•ผ. ์ด๊ฑธ ์›น ํ™˜๊ฒฝ์— ๋งž๊ฒŒ JavaScript๋กœ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“  ๊ฒŒ WebGL์ด์•ผ.

2011๋…„์— WebGL 1.0์ด ๊ณต์‹ ์ŠคํŽ™์œผ๋กœ ๋‚˜์™”๊ณ , 2017๋…„์— WebGL 2.0์ด ๋“ฑ์žฅํ–ˆ์–ด. WebGL 2.0์€ OpenGL ES 3.0 ๊ธฐ๋ฐ˜์œผ๋กœ ํ›จ์”ฌ ๋” ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ๋“ค์„ ์ง€์›ํ•ด.

WebGL ์ž‘๋™ ์›๋ฆฌ JavaScript ์ฝ”๋“œ ์ž‘์„ฑ WebGL API ๋ธŒ๋ผ์šฐ์ € ๋‚ด์žฅ ๊ทธ๋ž˜ํ”ฝ ์ธํ„ฐํŽ˜์ด์Šค GPU ๋ Œ๋”๋ง ์…ฐ์ด๋” ์‹คํ–‰ ํ”ฝ์…€ ๊ณ„์‚ฐ Canvas ํ™”๋ฉด ์ถœ๋ ฅ ๐Ÿ”ท ๋ฒ„ํ…์Šค ์…ฐ์ด๋” 3D ์ขŒํ‘œ๋ฅผ 2D ํ™”๋ฉด ์ขŒํ‘œ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” GPU ํ”„๋กœ๊ทธ๋žจ ๊ฐ ๊ผญ์ง“์ ๋งˆ๋‹ค ์‹คํ–‰๋จ ๐Ÿ”ถ ํ”„๋ž˜๊ทธ๋จผํŠธ ์…ฐ์ด๋” ๊ฐ ํ”ฝ์…€์˜ ์ƒ‰์ƒ์„ ๊ฒฐ์ •ํ•˜๋Š” GPU ํ”„๋กœ๊ทธ๋žจ ํ…์Šค์ฒ˜ยท์กฐ๋ช… ํšจ๊ณผ ์ฒ˜๋ฆฌ โšก GLSL ์–ธ์–ด ์…ฐ์ด๋”๋ฅผ ์ž‘์„ฑํ•˜๋Š” ์ „์šฉ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด C์–ธ์–ด์™€ ์œ ์‚ฌํ•œ ๋ฌธ๋ฒ• WebGL์€ JavaScript โ†’ GPU ํŒŒ์ดํ”„๋ผ์ธ์„ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ง์ ‘ ์ œ์–ดํ•œ๋‹ค

๐Ÿ” WebGL์˜ ํ•ต์‹ฌ ๊ฐœ๋… โ€” ์…ฐ์ด๋”๊ฐ€ ๋ญ์•ผ?

WebGL์„ ์ดํ•ดํ•˜๋ ค๋ฉด ์…ฐ์ด๋”(Shader)๋ฅผ ์•Œ์•„์•ผ ํ•ด. ์…ฐ์ด๋”๋Š” GPU์—์„œ ์‹คํ–‰๋˜๋Š” ์ž‘์€ ํ”„๋กœ๊ทธ๋žจ์ธ๋ฐ, ๋‘ ์ข…๋ฅ˜๊ฐ€ ์žˆ์–ด:

๐Ÿ”ท ๋ฒ„ํ…์Šค ์…ฐ์ด๋” (Vertex Shader) 3D ๊ณต๊ฐ„์˜ ๊ผญ์ง“์ (vertex) ์œ„์น˜๋ฅผ 2D ํ™”๋ฉด ์ขŒํ‘œ๋กœ ๋ณ€ํ™˜ํ•ด. ์˜ˆ๋ฅผ ๋“ค์–ด 3D ํ๋ธŒ์˜ 8๊ฐœ ๊ผญ์ง“์ ์ด ํ™”๋ฉด ์–ด๋””์— ๊ทธ๋ ค์งˆ์ง€ ๊ณ„์‚ฐํ•˜๋Š” ๊ฑฐ์•ผ. GLSL์ด๋ผ๋Š” ์–ธ์–ด๋กœ ์ž‘์„ฑ๋ผ.
๐Ÿ”ถ ํ”„๋ž˜๊ทธ๋จผํŠธ ์…ฐ์ด๋” (Fragment Shader) ๊ฐ ํ”ฝ์…€์˜ ์ƒ‰์ƒ์„ ๊ฒฐ์ •ํ•ด. ํ…์Šค์ฒ˜๋ฅผ ์ž…ํžˆ๊ฑฐ๋‚˜, ์กฐ๋ช… ํšจ๊ณผ๋ฅผ ๊ณ„์‚ฐํ•˜๊ฑฐ๋‚˜, ๊ทธ๋ฆผ์ž๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒŒ ๋‹ค ์—ฌ๊ธฐ์„œ ์ด๋ฃจ์–ด์ ธ. ์ด๊ฒŒ ์ž˜ ๋งŒ๋“ค์–ด์ง€๋ฉด ์‚ฌ์ง„์ฒ˜๋Ÿผ ์‚ฌ์‹ค์ ์ธ 3D๊ฐ€ ๋‚˜์™€.

์…ฐ์ด๋”๋Š” GLSL(OpenGL Shading Language)์ด๋ผ๋Š” C์–ธ์–ด ๋น„์Šทํ•œ ์–ธ์–ด๋กœ ์ž‘์„ฑํ•ด. ์ฒ˜์Œ์—” ์ข€ ๋‚ฏ์„ค์ง€๋งŒ, ์ด๊ฑธ ๋งˆ์Šคํ„ฐํ•˜๋ฉด ์ง„์งœ ๋ฏธ์นœ ๋น„์ฃผ์–ผ ํšจ๊ณผ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด.

// ๊ฐ„๋‹จํ•œ ๋ฒ„ํ…์Šค ์…ฐ์ด๋” ์˜ˆ์‹œ (GLSL)
attribute vec4 a_position;
uniform mat4 u_matrix;

void main() {
gl_Position = u_matrix * a_position;
}

// ๊ฐ„๋‹จํ•œ ํ”„๋ž˜๊ทธ๋จผํŠธ ์…ฐ์ด๋” ์˜ˆ์‹œ (GLSL)
precision mediump float;
uniform vec4 u_color;

void main() {
gl_FragColor = u_color;
}

๐Ÿ“Š WebGL 1.0 vs WebGL 2.0 ๋น„๊ต

ํ•ญ๋ชฉ WebGL 1.0 WebGL 2.0
๊ธฐ๋ฐ˜ ์ŠคํŽ™ OpenGL ES 2.0 OpenGL ES 3.0
์ถœ์‹œ ์—ฐ๋„ 2011๋…„ 2017๋…„
์ธ์Šคํ„ด์‹ฑ ํ™•์žฅ ํ•„์š” ๊ธฐ๋ณธ ์ง€์›
Transform Feedback ๋ฏธ์ง€์› ์ง€์›
๋‹ค์ค‘ ๋ Œ๋” ํƒ€๊ฒŸ ํ™•์žฅ ํ•„์š” ๊ธฐ๋ณธ ์ง€์›
์ •์ˆ˜ ํ…์Šค์ฒ˜ ๋ฏธ์ง€์› ์ง€์›
๋ธŒ๋ผ์šฐ์ € ์ง€์› ๊ฑฐ์˜ ๋ชจ๋“  ๋ธŒ๋ผ์šฐ์ € ์ตœ์‹  ๋ธŒ๋ผ์šฐ์ € ๋Œ€๋ถ€๋ถ„
๐Ÿ’ก ์‹ค๋ฌด ํŒ: 2024๋…„ ๊ธฐ์ค€์œผ๋กœ Chrome, Firefox, Safari, Edge ๋ชจ๋‘ WebGL 2.0์„ ์ง€์›ํ•ด. ๋ชจ๋ฐ”์ผ๋„ iOS 15+, Android Chrome์—์„œ ์ž˜ ๋Œ์•„๊ฐ€. ๋‹ค๋งŒ ๊ตฌํ˜• ๊ธฐ๊ธฐ ์ง€์›์ด ํ•„์š”ํ•˜๋‹ค๋ฉด WebGL 1.0 ํด๋ฐฑ(fallback)์„ ์ค€๋น„ํ•ด๋‘๋Š” ๊ฒŒ ์ข‹์•„.

๐Ÿฅฝ WebXR์ด ๋ญ”๋ฐ? โ€” ํ˜„์‹ค๊ณผ ๊ฐ€์ƒ์˜ ๊ฒฝ๊ณ„๋ฅผ ํ—ˆ๋ฌผ๋‹ค

WebXR์€ Web Extended Reality์˜ ์•ฝ์ž์•ผ. XR์ด ๋ญ๋ƒ๊ณ ? AR(์ฆ๊ฐ•ํ˜„์‹ค) + VR(๊ฐ€์ƒํ˜„์‹ค) + MR(ํ˜ผํ•ฉํ˜„์‹ค)์„ ํ†ตํ‹€์–ด์„œ XR์ด๋ผ๊ณ  ๋ถˆ๋Ÿฌ. ๊ทธ๋ฆฌ๊ณ  WebXR์€ ์ด ๋ชจ๋“  ๊ฑธ ์›น ๋ธŒ๋ผ์šฐ์ €์—์„œ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” API์•ผ.

์˜ˆ์ „์—๋Š” WebVR์ด๋ผ๋Š” API๊ฐ€ ์žˆ์—ˆ๋Š”๋ฐ, ์ด๊ฒŒ VR๋งŒ ์ง€์›ํ–ˆ์–ด. ๊ทธ๋Ÿฐ๋ฐ AR๊นŒ์ง€ ํฌํ•จํ•˜๋ฉด์„œ ๋” ๋„“์€ ๊ฐœ๋…์ธ WebXR๋กœ ํ†ตํ•ฉ๋๊ณ , 2019๋…„์— W3C์—์„œ ๊ณต์‹ ํ‘œ์ค€ํ™” ์ž‘์—…์ด ์‹œ์ž‘๋์–ด. ํ˜„์žฌ๋Š” WebXR Device API๊ฐ€ ์ฃผ์š” ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ง€์›๋˜๊ณ  ์žˆ์–ด.

WebXR ์ƒํƒœ๊ณ„ WebXR Device API ๐Ÿฅฝ VR ๊ฐ€์ƒํ˜„์‹ค ๐Ÿ“ฑ AR ์ฆ๊ฐ•ํ˜„์‹ค ๐ŸŒ MR ํ˜ผํ•ฉํ˜„์‹ค ํ—ค๋“œ์…‹ ํŠธ๋ž˜ํ‚น ์ปจํŠธ๋กค๋Ÿฌ ์ž…๋ ฅ ํžˆํŠธ ํ…Œ์ŠคํŠธ ํ‰๋ฉด ๊ฐ์ง€ WebXR์€ VRยทARยทMR์„ ํ•˜๋‚˜์˜ ์›น API๋กœ ํ†ตํ•ฉํ•œ ํ‘œ์ค€

๐Ÿ”‘ WebXR์˜ ํ•ต์‹ฌ ๊ธฐ๋Šฅ๋“ค

1
XR ์„ธ์…˜ ๊ด€๋ฆฌ
navigator.xr.requestSession('immersive-vr') ๋˜๋Š” 'immersive-ar'๋กœ XR ์„ธ์…˜์„ ์‹œ์ž‘ํ•ด. ์‚ฌ์šฉ์ž๊ฐ€ VR ํ—ค๋“œ์…‹์„ ์“ฐ๊ฑฐ๋‚˜ AR ๋ชจ๋“œ๋ฅผ ์ผœ๋ฉด ์ด ์„ธ์…˜์ด ํ™œ์„ฑํ™”๋ผ.
2
๊ณต๊ฐ„ ์ถ”์  (Spatial Tracking)
ํ—ค๋“œ์…‹์˜ ์œ„์น˜์™€ ๋ฐฉํ–ฅ์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ถ”์ ํ•ด. ์‚ฌ์šฉ์ž๊ฐ€ ๊ณ ๊ฐœ๋ฅผ ๋Œ๋ฆฌ๋ฉด ๊ฐ€์ƒ ์„ธ๊ณ„๋„ ๊ทธ์— ๋งž๊ฒŒ ์—…๋ฐ์ดํŠธ๋ผ. 6DoF(6 Degrees of Freedom) ์ถ”์ ์„ ์ง€์›ํ•ด.
3
์ž…๋ ฅ ์ฒ˜๋ฆฌ (Input Sources)
VR ์ปจํŠธ๋กค๋Ÿฌ, ์† ์ถ”์ , ๊ฒŒ์ด์ฆˆ(์‹œ์„ ) ์ž…๋ ฅ ๋“ฑ ๋‹ค์–‘ํ•œ ์ž…๋ ฅ ๋ฐฉ์‹์„ ์ง€์›ํ•ด. XRInputSource API๋กœ ์ปจํŠธ๋กค๋Ÿฌ ๋ฒ„ํŠผ ์ƒํƒœ, ์กฐ์ด์Šคํ‹ฑ ๊ฐ’ ๋“ฑ์„ ์ฝ์„ ์ˆ˜ ์žˆ์–ด.
4
ํžˆํŠธ ํ…Œ์ŠคํŠธ (Hit Testing)
AR์—์„œ ํŠนํžˆ ์ค‘์š”ํ•œ ๊ธฐ๋Šฅ์ด์•ผ. ์นด๋ฉ”๋ผ๋กœ ๋ณด์ด๋Š” ์‹ค์ œ ๊ณต๊ฐ„์—์„œ ํ‰๋ฉด(๋ฐ”๋‹ฅ, ํ…Œ์ด๋ธ” ๋“ฑ)์„ ๊ฐ์ง€ํ•˜๊ณ , ๊ทธ ์œ„์— 3D ์˜ค๋ธŒ์ ํŠธ๋ฅผ ๋ฐฐ์น˜ํ•  ์ˆ˜ ์žˆ์–ด.
5
์•ต์ปค (Anchors)
AR ์˜ค๋ธŒ์ ํŠธ๋ฅผ ์‹ค์ œ ๊ณต๊ฐ„์˜ ํŠน์ • ์œ„์น˜์— ๊ณ ์ •์‹œํ‚ค๋Š” ๊ธฐ๋Šฅ์ด์•ผ. ์‚ฌ์šฉ์ž๊ฐ€ ๋ฐฉ์„ ๋Œ์•„๋‹ค๋…€๋„ ๊ฐ€์ƒ ์˜ค๋ธŒ์ ํŠธ๊ฐ€ ์ œ์ž๋ฆฌ์— ์žˆ์–ด.
โš ๏ธ ๋ธŒ๋ผ์šฐ์ € ์ง€์› ํ˜„ํ™ฉ (2024 ๊ธฐ์ค€) WebXR์€ Chrome(Android)์—์„œ ๊ฐ€์žฅ ์ž˜ ์ง€์›๋ผ. iOS Safari๋Š” ์•„์ง WebXR ์ง€์›์ด ์ œํ•œ์ ์ด์•ผ (AR Quick Look์€ ์ง€์›ํ•˜์ง€๋งŒ WebXR Device API๋Š” ์‹คํ—˜์ ). Meta Quest ๋ธŒ๋ผ์šฐ์ €๋Š” ์™„์ „ ์ง€์›. Firefox Reality๋Š” ๊ฐœ๋ฐœ ์ค‘๋‹จ๋จ. ํ”„๋กœ์ ํŠธ ์‹œ์ž‘ ์ „์— ๋ฐ˜๋“œ์‹œ ํƒ€๊ฒŸ ๊ธฐ๊ธฐ ํ™•์ธํ•ด!

๐Ÿ› ๏ธ Three.js โ€” WebGL์„ ์ธ๊ฐ„๋‹ต๊ฒŒ ์“ฐ๋Š” ๋ฒ•

์†”์งํžˆ ๋งํ• ๊ฒŒ. ์ˆœ์ˆ˜ WebGL ์ฝ”๋“œ๋Š” ์ง„์งœ ๋ณต์žกํ•ด. ์‚ผ๊ฐํ˜• ํ•˜๋‚˜ ๊ทธ๋ฆฌ๋Š” ๋ฐ๋„ 100์ค„ ๋„˜๋Š” ์ฝ”๋“œ๊ฐ€ ํ•„์š”ํ•ด. ๊ทธ๋ž˜์„œ ๋“ฑ์žฅํ•œ ๊ฒŒ Three.js์•ผ.

Three.js๋Š” WebGL์„ ์ถ”์ƒํ™”ํ•ด์„œ ํ›จ์”ฌ ์‰ฝ๊ฒŒ 3D ์”ฌ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” JavaScript ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์•ผ. 2010๋…„์— Ricardo Cabello(mrdoob)๊ฐ€ ๋งŒ๋“ค์—ˆ๊ณ , ์ง€๊ธˆ์€ GitHub์—์„œ 10๋งŒ ์Šคํƒ€๊ฐ€ ๋„˜๋Š” ์ดˆ์ธ๊ธฐ ์˜คํ”ˆ์†Œ์Šค ํ”„๋กœ์ ํŠธ์•ผ. ๐ŸŒŸ

๐ŸŽฏ Three.js์˜ ํ•ต์‹ฌ ๊ตฌ์„ฑ ์š”์†Œ

๊ตฌ์„ฑ ์š”์†Œ ์—ญํ•  ๋น„์œ 
Scene ๋ชจ๋“  3D ์˜ค๋ธŒ์ ํŠธ๊ฐ€ ์กด์žฌํ•˜๋Š” ๊ณต๊ฐ„ ์˜ํ™” ์„ธํŠธ์žฅ
Camera ์”ฌ์„ ๋ฐ”๋ผ๋ณด๋Š” ์‹œ์  ์˜ํ™” ์นด๋ฉ”๋ผ
Renderer ์”ฌ์„ ์‹ค์ œ๋กœ ํ™”๋ฉด์— ๊ทธ๋ฆฌ๋Š” ์—”์ง„ ์˜ํ™” ์ดฌ์˜ ์žฅ๋น„
Geometry 3D ์˜ค๋ธŒ์ ํŠธ์˜ ํ˜•ํƒœ(๊ผญ์ง“์  ๋ฐ์ดํ„ฐ) ์กฐ๊ฐ์ƒ์˜ ๋ผˆ๋Œ€
Material ์˜ค๋ธŒ์ ํŠธ์˜ ํ‘œ๋ฉด ์†์„ฑ(์ƒ‰์ƒ, ์งˆ๊ฐ) ํŽ˜์ธํŠธ, ์žฌ์งˆ
Mesh Geometry + Material์˜ ์กฐํ•ฉ ์™„์„ฑ๋œ ์กฐ๊ฐ์ƒ
Light ์”ฌ์˜ ์กฐ๋ช… ์ŠคํŠœ๋””์˜ค ์กฐ๋ช…
// Three.js๋กœ ํšŒ์ „ํ•˜๋Š” ํ๋ธŒ ๋งŒ๋“ค๊ธฐ (๊ธฐ๋ณธ ์˜ˆ์‹œ)
import * as THREE from 'three';

// 1. ์”ฌ ์ƒ์„ฑ
const scene = new THREE.Scene();

// 2. ์นด๋ฉ”๋ผ ์„ค์ • (์›๊ทผ ์นด๋ฉ”๋ผ)
const camera = new THREE.PerspectiveCamera(
75,                                    // ์‹œ์•ผ๊ฐ(FOV)
window.innerWidth / window.innerHeight, // ์ข…ํšก๋น„
0.1,                                   // ๊ทผ๊ฑฐ๋ฆฌ ํด๋ฆฌํ•‘
1000                                   // ์›๊ฑฐ๋ฆฌ ํด๋ฆฌํ•‘
);
camera.position.z = 5;

// 3. ๋ Œ๋”๋Ÿฌ ์ƒ์„ฑ
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 4. ํ๋ธŒ ์ƒ์„ฑ
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x7ab8d4 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

// 5. ์กฐ๋ช… ์ถ”๊ฐ€
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(5, 5, 5);
scene.add(light);

// 6. ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋ฃจํ”„
function animate() {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
}
animate();

์–ด๋•Œ? ์ˆœ์ˆ˜ WebGL๋ณด๋‹ค ํ›จ์”ฌ ์ฝ๊ธฐ ์‰ฝ์ง€? ์ด๊ฒŒ Three.js์˜ ๋งค๋ ฅ์ด์•ผ. ๋ณต์žกํ•œ GPU ํŒŒ์ดํ”„๋ผ์ธ์„ ์‹ ๊ฒฝ ์“ฐ์ง€ ์•Š๊ณ  "์”ฌ์— ํ๋ธŒ ์ถ”๊ฐ€ํ•˜๊ณ , ์กฐ๋ช… ์ผœ๊ณ , ๋ Œ๋”๋งํ•ด!" ์ด๋Ÿฐ ์‹์œผ๋กœ ์ง๊ด€์ ์œผ๋กœ ์ž‘์—…ํ•  ์ˆ˜ ์žˆ์–ด.

๐ŸŒŸ Three.js์˜ ์ฃผ์š” ๊ธฐ๋Šฅ๋“ค

๐Ÿ“ฆ ๋‹ค์–‘ํ•œ ์ง€์˜ค๋ฉ”ํŠธ๋ฆฌ BoxGeometry, SphereGeometry, CylinderGeometry, TorusGeometry, PlaneGeometry ๋“ฑ ๊ธฐ๋ณธ ๋„ํ˜•๋ถ€ํ„ฐ BufferGeometry๋กœ ์™„์ „ ์ปค์Šคํ…€ ํ˜•ํƒœ๊นŒ์ง€ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด. GLTFLoader๋กœ Blender์—์„œ ๋งŒ๋“  3D ๋ชจ๋ธ์„ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜๋„ ์žˆ๊ณ .
๐Ÿ’ก ๋ฌผ๋ฆฌ ๊ธฐ๋ฐ˜ ๋ Œ๋”๋ง (PBR) MeshStandardMaterial, MeshPhysicalMaterial์„ ์“ฐ๋ฉด ๊ธˆ์†, ์œ ๋ฆฌ, ์ฒœ ๊ฐ™์€ ์‹ค์ œ ์žฌ์งˆ์„ ์‚ฌ์‹ค์ ์œผ๋กœ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ์–ด. roughness(๊ฑฐ์น ๊ธฐ), metalness(๊ธˆ์†์„ฑ), transmission(ํˆฌ๋ช…๋„) ๋“ฑ์˜ ์†์„ฑ์œผ๋กœ ์„ธ๋ฐ€ํ•˜๊ฒŒ ์กฐ์ ˆ ๊ฐ€๋Šฅ.
๐ŸŽฌ ํฌ์ŠคํŠธ ํ”„๋กœ์„ธ์‹ฑ EffectComposer๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ Œ๋”๋ง ํ›„ ํ™”๋ฉด์— ๋‹ค์–‘ํ•œ ํšจ๊ณผ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์–ด. ๋ธ”๋ฃธ(Bloom), ๋ށ์Šค ์˜ค๋ธŒ ํ•„๋“œ(DoF), SSAO, ๋ชจ์…˜ ๋ธ”๋Ÿฌ ๋“ฑ ์˜ํ™” ๊ฐ™์€ ํšจ๊ณผ๋ฅผ ์›น์—์„œ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์ง€.
๐ŸŒ ํ™˜๊ฒฝ ๋งต (Environment Map) HDR ์ด๋ฏธ์ง€๋ฅผ ํ™˜๊ฒฝ ๋งต์œผ๋กœ ์‚ฌ์šฉํ•˜๋ฉด ์˜ค๋ธŒ์ ํŠธ์— ์ฃผ๋ณ€ ํ™˜๊ฒฝ์ด ๋ฐ˜์‚ฌ๋˜๋Š” ํšจ๊ณผ๋ฅผ ์ค„ ์ˆ˜ ์žˆ์–ด. ์ž๋™์ฐจ ๊ด‘๊ณ  ๊ฐ™์€ ๋ฐ˜์ง์ด๋Š” ๊ธˆ์† ํ‘œ๋ฉด ํ‘œํ˜„์ด ๊ฐ€๋Šฅํ•ด.

๐ŸŽจ ์›น๋””์ž์ธ์—์„œ WebGL ํ™œ์šฉ โ€” ์‹ค์ „ ์‚ฌ๋ก€๋“ค

์ด๋ก ์€ ์ถฉ๋ถ„ํžˆ ํ–ˆ์œผ๋‹ˆ๊นŒ, ์ด์ œ ์ง„์งœ ์ค‘์š”ํ•œ ๊ฑฐ โ€” ์‹ค์ œ๋กœ ์–ด๋–ป๊ฒŒ ์“ฐ์ด๋Š”์ง€ ์•Œ์•„๋ณด์ž. ์›น๋””์ž์ธ, ํŠนํžˆ ์ƒ์„ธํŽ˜์ด์ง€๋‚˜ ํฌํŠธํด๋ฆฌ์˜ค์—์„œ WebGL์ด ์–ด๋–ป๊ฒŒ ํ™œ์šฉ๋˜๋Š”์ง€ ๊ตฌ์ฒด์ ์ธ ์‚ฌ๋ก€๋“ค์„ ์‚ดํŽด๋ณผ๊ฒŒ.

WebGL ์›น๋””์ž์ธ ํ™œ์šฉ ์‚ฌ๋ก€ ๐Ÿ›๏ธ 3D ์ œํ’ˆ ๋ทฐ์–ด 360ยฐ ํšŒ์ „ ๊ฐ€๋Šฅํ•œ ์ œํ’ˆ ์ƒ์„ธํŽ˜์ด์ง€ ๊ตฌํ˜„ โœจ ํŒŒํ‹ฐํด ํšจ๊ณผ ํžˆ์–ด๋กœ ์„น์…˜ ๋ฐฐ๊ฒฝ, ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ํŒŒํ‹ฐํด ์‹œ์Šคํ…œ ๐ŸŒŠ ์…ฐ์ด๋” ์•„ํŠธ GLSL ์…ฐ์ด๋”๋กœ ๋งŒ๋“œ๋Š” ์œ ์ฒดยท๋…ธ์ด์ฆˆ ๋ฐฐ๊ฒฝ ํšจ๊ณผ ๐Ÿ“œ ์Šคํฌ๋กค 3D ์• ๋‹ˆ๋ฉ”์ด์…˜ ์Šคํฌ๋กค์— ๋ฐ˜์‘ํ•˜๋Š” 3D ์”ฌ ์ „ํ™˜ ํšจ๊ณผ ๐Ÿ“Š 3D ๋ฐ์ดํ„ฐ ์‹œ๊ฐํ™” ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ 3D ์ฐจํŠธ, ์ง€๊ตฌ๋ณธ, ๋„คํŠธ์›Œํฌ ๊ทธ๋ž˜ํ”„ ๐Ÿ  AR ์ œํ’ˆ ๋ฐฐ์น˜ WebXR AR๋กœ ๊ฐ€๊ตฌยท์ œํ’ˆ์„ ์‹ค์ œ ๊ณต๊ฐ„์— ๋ฏธ๋ฆฌ ๋ฐฐ์น˜ WebGL/WebXR์€ ์ด๋ฏธ ๋‹ค์–‘ํ•œ ์›น ๊ฒฝํ—˜์—์„œ ํ™œ๋ฐœํžˆ ํ™œ์šฉ๋˜๊ณ  ์žˆ๋‹ค

๐Ÿ›๏ธ ์‚ฌ๋ก€ 1: 3D ์ œํ’ˆ ๋ทฐ์–ด (์ƒ์„ธํŽ˜์ด์ง€ ํ˜๋ช…)

์ด์ปค๋จธ์Šค ์ƒ์„ธํŽ˜์ด์ง€์—์„œ WebGL์˜ ํ™œ์šฉ์ด ๊ฐ€์žฅ ํญ๋ฐœ์ ์œผ๋กœ ๋Š˜๊ณ  ์žˆ์–ด. ๊ธฐ์กด์—๋Š” ์—ฌ๋Ÿฌ ๊ฐ๋„์—์„œ ์ฐ์€ ์‚ฌ์ง„ ์—ฌ๋Ÿฌ ์žฅ์„ ๋ณด์—ฌ์คฌ๋‹ค๋ฉด, ์ด์ œ๋Š” ์‹ค์ œ 3D ๋ชจ๋ธ์„ 360๋„ ํšŒ์ „์‹œํ‚ค๋ฉด์„œ ๋ณผ ์ˆ˜ ์žˆ์–ด.

Nike, IKEA, Apple ๊ฐ™์€ ๋ธŒ๋žœ๋“œ๋“ค์ด ์ด๋ฏธ ์ด ๋ฐฉ์‹์„ ์“ฐ๊ณ  ์žˆ์–ด. ํŠนํžˆ IKEA์˜ AR ๊ธฐ๋Šฅ์€ WebXR์„ ํ™œ์šฉํ•ด์„œ ๊ฐ€๊ตฌ๋ฅผ ์‹ค์ œ ๋ฐฉ์— ๋ฐฐ์น˜ํ•ด๋ณด๋Š” ๊ฒฝํ—˜์„ ์ œ๊ณตํ•˜๊ณ  ์žˆ์ง€. ๊ตฌ๋งค ์ „ํ™˜์œจ์ด ๊ธฐ์กด ๋Œ€๋น„ ์ตœ๋Œ€ 40% ํ–ฅ์ƒ๋๋‹ค๋Š” ์—ฐ๊ตฌ ๊ฒฐ๊ณผ๋„ ์žˆ์–ด.

๊ตฌํ˜„ ๋ฐฉ๋ฒ•: 1. Blender๋‚˜ 3ds Max์—์„œ ์ œํ’ˆ 3D ๋ชจ๋ธ ์ œ์ž‘
2. GLTF/GLB ํฌ๋งท์œผ๋กœ ์ตœ์ ํ™” ๋‚ด๋ณด๋‚ด๊ธฐ
3. Three.js + GLTFLoader๋กœ ์›น์— ๋กœ๋“œ
4. OrbitControls๋กœ ๋งˆ์šฐ์Šค/ํ„ฐ์น˜ ํšŒ์ „ ๊ตฌํ˜„
5. ํ™˜๊ฒฝ ๋งต์œผ๋กœ ์‚ฌ์‹ค์ ์ธ ๋ฐ˜์‚ฌ ํšจ๊ณผ ์ถ”๊ฐ€

โœจ ์‚ฌ๋ก€ 2: ํŒŒํ‹ฐํด ์‹œ์Šคํ…œ ํžˆ์–ด๋กœ ์„น์…˜

์›น์‚ฌ์ดํŠธ ์ฒซ ํ™”๋ฉด(ํžˆ์–ด๋กœ ์„น์…˜)์— ํŒŒํ‹ฐํด ํšจ๊ณผ๋ฅผ ๋„ฃ์œผ๋ฉด ๋ฐฉ๋ฌธ์ž์˜ ์‹œ์„ ์„ ํ™• ์žก์„ ์ˆ˜ ์žˆ์–ด. ์ˆ˜์ฒœ ๊ฐœ์˜ ์ ๋“ค์ด ๋งˆ์šฐ์Šค๋ฅผ ๋”ฐ๋ผ ์›€์ง์ด๊ฑฐ๋‚˜, ํŠน์ • ํ˜•ํƒœ๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค๊ฐ€ ํฉ์–ด์ง€๋Š” ํšจ๊ณผ โ€” ์ด๊ฒŒ ๋‹ค WebGL ํŒŒํ‹ฐํด ์‹œ์Šคํ…œ์ด์•ผ.

Three.js์—์„œ๋Š” Points ์˜ค๋ธŒ์ ํŠธ์™€ PointsMaterial์„ ์‚ฌ์šฉํ•ด. ์ˆ˜์‹ญ๋งŒ ๊ฐœ์˜ ํŒŒํ‹ฐํด๋„ GPU์—์„œ ์ฒ˜๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ๋Œ์•„๊ฐ€.

// ํŒŒํ‹ฐํด ์‹œ์Šคํ…œ ๊ธฐ๋ณธ ๊ตฌํ˜„
const particleCount = 50000;
const positions = new Float32Array(particleCount * 3);

for (let i = 0; i < particleCount; i++) {
positions[i * 3] = (Math.random() - 0.5) * 20;     // x
positions[i * 3 + 1] = (Math.random() - 0.5) * 20; // y
positions[i * 3 + 2] = (Math.random() - 0.5) * 20; // z
}

const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));

const material = new THREE.PointsMaterial({
size: 0.02,
color: 0x7ab8d4,
transparent: true,
opacity: 0.8
});

const particles = new THREE.Points(geometry, material);
scene.add(particles);

๐ŸŒŠ ์‚ฌ๋ก€ 3: GLSL ์…ฐ์ด๋” ๋ฐฐ๊ฒฝ ํšจ๊ณผ

์…ฐ์ด๋” ์•„ํŠธ๋Š” WebGL์˜ ์ง„์งœ ๋งค๋ ฅ์ด์•ผ. ์ˆ˜ํ•™ ํ•จ์ˆ˜๋งŒ์œผ๋กœ ์œ ์ฒด์ฒ˜๋Ÿผ ํ๋ฅด๋Š” ๋ฐฐ๊ฒฝ, ๋…ธ์ด์ฆˆ ํ…์Šค์ฒ˜, ํ™€๋กœ๊ทธ๋žจ ํšจ๊ณผ ๋“ฑ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด. ํŒŒ์ผ ํฌ๊ธฐ๋Š” ๊ฑฐ์˜ ์—†๋Š”๋ฐ ๋น„์ฃผ์–ผ ์ž„ํŒฉํŠธ๋Š” ์—„์ฒญ๋‚˜.

ํŠนํžˆ Perlin Noise๋‚˜ Simplex Noise๋ฅผ ์…ฐ์ด๋”์—์„œ ํ™œ์šฉํ•˜๋ฉด ์ž์—ฐ์Šค๋Ÿฌ์šด ์œ ๊ธฐ์  ์›€์ง์ž„์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด. ๋งŽ์€ ํ…Œํฌ ์Šคํƒ€ํŠธ์—…๋“ค์ด ๋žœ๋”ฉ ํŽ˜์ด์ง€ ๋ฐฐ๊ฒฝ์œผ๋กœ ์ด๋Ÿฐ ์…ฐ์ด๋” ํšจ๊ณผ๋ฅผ ์“ฐ๊ณ  ์žˆ์–ด.

๐Ÿ“œ ์‚ฌ๋ก€ 4: ์Šคํฌ๋กค ๊ธฐ๋ฐ˜ 3D ์• ๋‹ˆ๋ฉ”์ด์…˜

์Šคํฌ๋กคํ•˜๋ฉด 3D ์”ฌ์ด ๋ณ€ํ•˜๋Š” ํšจ๊ณผ โ€” Apple์˜ ์ œํ’ˆ ํŽ˜์ด์ง€์—์„œ ๋งŽ์ด ๋ดค์ง€? ์Šคํฌ๋กค ์œ„์น˜์— ๋”ฐ๋ผ ์นด๋ฉ”๋ผ๊ฐ€ ์ด๋™ํ•˜๊ฑฐ๋‚˜, ์˜ค๋ธŒ์ ํŠธ๊ฐ€ ์กฐ๋ฆฝ๋˜๊ฑฐ๋‚˜, ์”ฌ์ด ์ „ํ™˜๋˜๋Š” ๊ฑฐ์•ผ.

์ด๊ฑธ ๊ตฌํ˜„ํ•˜๋ ค๋ฉด GSAP(GreenSock Animation Platform)์˜ ScrollTrigger ํ”Œ๋Ÿฌ๊ทธ์ธ๊ณผ Three.js๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ์ผ๋ฐ˜์ ์ด์•ผ. ์Šคํฌ๋กค ์ง„ํ–‰๋„(0~1)๋ฅผ Three.js ์”ฌ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ์— ๋งคํ•‘ํ•˜๋ฉด ๋ผ.


๐Ÿ“š ์ฃผ์š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ƒํƒœ๊ณ„ ์™„์ „ ์ •๋ฆฌ

WebGL/WebXR ๊ฐœ๋ฐœ์„ ํ•  ๋•Œ Three.js๋งŒ ์žˆ๋Š” ๊ฒŒ ์•„๋‹ˆ์•ผ. ๋‹ค์–‘ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์™€ ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ์žˆ์–ด์„œ ๋ชฉ์ ์— ๋งž๊ฒŒ ์„ ํƒํ•  ์ˆ˜ ์žˆ์–ด.

๐Ÿ”ง ์ฃผ์š” 3D ์›น ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋น„๊ต

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํŠน์ง• ์ ํ•ฉํ•œ ์šฉ๋„
Three.js ๊ฐ€์žฅ ๋ฒ”์šฉ์ , ํฐ ์ปค๋ฎค๋‹ˆํ‹ฐ, ํ’๋ถ€ํ•œ ์˜ˆ์ œ ์ผ๋ฐ˜ 3D ์›น, ํฌํŠธํด๋ฆฌ์˜ค, ์ œํ’ˆ ๋ทฐ์–ด
Babylon.js ๊ฒŒ์ž„ ์—”์ง„ ์ˆ˜์ค€, TypeScript ์ง€์›, ๊ฐ•๋ ฅํ•œ ๋ฌผ๋ฆฌ ์—”์ง„ ์›น ๊ฒŒ์ž„, ๋ณต์žกํ•œ ์ธํ„ฐ๋ž™์…˜
A-Frame HTML ํƒœ๊ทธ ๊ธฐ๋ฐ˜, WebXR ํŠนํ™”, ์‰ฌ์šด ์ง„์ž…์žฅ๋ฒฝ VR/AR ๊ฒฝํ—˜, ๋น ๋ฅธ ํ”„๋กœํ† ํƒ€์ดํ•‘
React Three Fiber React + Three.js ํ†ตํ•ฉ, ์„ ์–ธ์  3D React ๊ธฐ๋ฐ˜ 3D ์›น์•ฑ
Spline ๋…ธ์ฝ”๋“œ 3D ๋””์ž์ธ ํˆด, ์›น ์ž„๋ฒ ๋“œ ๊ฐ€๋Šฅ ๋””์ž์ด๋„ˆ ์นœํ™”์  3D ์š”์†Œ
GSAP ์• ๋‹ˆ๋ฉ”์ด์…˜ ํŠนํ™”, ScrollTrigger ๊ฐ•๋ ฅ ์Šคํฌ๋กค ๊ธฐ๋ฐ˜ 3D ์• ๋‹ˆ๋ฉ”์ด์…˜

โš›๏ธ React Three Fiber โ€” React ๊ฐœ๋ฐœ์ž์˜ ์„ ํƒ

React๋ฅผ ์“ฐ๋Š” ๊ฐœ๋ฐœ์ž๋ผ๋ฉด React Three Fiber(R3F)๋ฅผ ์ฃผ๋ชฉํ•ด์•ผ ํ•ด. Three.js๋ฅผ React ์ปดํฌ๋„ŒํŠธ ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์•ผ. ์„ ์–ธ์  ๋ฌธ๋ฒ•์œผ๋กœ 3D ์”ฌ์„ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์–ด์„œ ์ฝ”๋“œ๊ฐ€ ํ›จ์”ฌ ๊น”๋”ํ•ด์ ธ.

// React Three Fiber ์˜ˆ์‹œ
import { Canvas } from '@react-three/fiber'
import { OrbitControls, Environment } from '@react-three/drei'

function ProductModel() {
return (
<mesh rotation={[0, Math.PI / 4, 0]}>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="#7ab8d4" metalness={0.5} roughness={0.2} />
</mesh>
)
}

function App() {
return (
<Canvas camera={{ position: [0, 0, 5] }}>
<ambientLight intensity={0.5} />
<directionalLight position={[5, 5, 5]} />
<ProductModel />
<OrbitControls />
<Environment preset="studio" />
</Canvas>
)
}

๐ŸŽญ A-Frame โ€” WebXR์„ HTML์ฒ˜๋Ÿผ

A-Frame์€ Mozilla๊ฐ€ ๋งŒ๋“  WebXR ํ”„๋ ˆ์ž„์›Œํฌ์•ผ. HTML ํƒœ๊ทธ์ฒ˜๋Ÿผ 3D ์”ฌ์„ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์–ด์„œ ์ง„์ž…์žฅ๋ฒฝ์ด ์—„์ฒญ ๋‚ฎ์•„. VR ๊ฒฝํ—˜์„ ๋น ๋ฅด๊ฒŒ ํ”„๋กœํ† ํƒ€์ดํ•‘ํ•  ๋•Œ ์ตœ๊ณ ์•ผ.

<!-- A-Frame์œผ๋กœ VR ์”ฌ ๋งŒ๋“ค๊ธฐ -->
<a-scene>
<a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9"></a-box>
<a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E"></a-sphere>
<a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D"></a-cylinder>
<a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4"></a-plane>
<a-sky color="#ECECEC"></a-sky>
</a-scene>
๐Ÿ’ก Spline ์ถ”์ฒœ! ์ฝ”๋”ฉ์ด ์–ด๋ ต๋‹ค๋ฉด Spline(spline.design)์„ ์จ๋ด. ๋…ธ์ฝ”๋“œ๋กœ 3D ์˜ค๋ธŒ์ ํŠธ๋ฅผ ๋””์ž์ธํ•˜๊ณ , ์ƒ์„ฑ๋œ ์ฝ”๋“œ๋ฅผ ์›น์‚ฌ์ดํŠธ์— ์ž„๋ฒ ๋“œํ•  ์ˆ˜ ์žˆ์–ด. ์žฌ๋Šฅ๋„ท์—์„œ ์›น๋””์ž์ธ ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•˜๋Š” ๋ถ„๋“ค์ด๋ผ๋ฉด Spline์„ ํ™œ์šฉํ•ด์„œ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์ฐจ๋ณ„ํ™”๋œ 3D ์š”์†Œ๋ฅผ ์ œ์•ˆํ•ด๋ณผ ์ˆ˜ ์žˆ์–ด!

โšก ์„ฑ๋Šฅ ์ตœ์ ํ™” โ€” ์˜ˆ์œ ๊ฒƒ๋„ ๋น ๋ฅด๊ฒŒ

WebGL๋กœ ๋ฉ‹์ง„ ๊ฑธ ๋งŒ๋“ค์—ˆ๋Š”๋ฐ ๋А๋ฆฌ๋ฉด ์•„๋ฌด ์†Œ์šฉ ์—†์ž–์•„. ํŠนํžˆ ๋ชจ๋ฐ”์ผ์—์„œ 3D๋Š” ๋ฐฐํ„ฐ๋ฆฌ๋„ ๋งŽ์ด ๋จน๊ณ  ๋ฐœ์—ด๋„ ์‹ฌํ•ด. ๊ทธ๋ž˜์„œ ์„ฑ๋Šฅ ์ตœ์ ํ™”๋Š” ํ•„์ˆ˜์•ผ.

WebGL ์„ฑ๋Šฅ ์ตœ์ ํ™” ์ „๋žต ๋ชฉํ‘œ ํ”„๋ ˆ์ž„๋ ˆ์ดํŠธ 60 FPS (์ดˆ๋‹น ํ”„๋ ˆ์ž„) ๐Ÿ”บ ํด๋ฆฌ๊ณค ์ˆ˜ ์ตœ์ ํ™” LOD(Level of Detail) ์ ์šฉ โ€” ๊ฑฐ๋ฆฌ์— ๋”ฐ๋ผ ๋ชจ๋ธ ๋ณต์žก๋„ ์ž๋™ ์กฐ์ ˆ ๐Ÿ“ฆ ๋“œ๋กœ์šฐ ์ฝœ ์ตœ์†Œํ™” Instancing์œผ๋กœ ๊ฐ™์€ ์˜ค๋ธŒ์ ํŠธ ์ˆ˜์ฒœ ๊ฐœ๋ฅผ ๋‹จ 1๋ฒˆ์˜ ๋“œ๋กœ์šฐ ์ฝœ๋กœ ์ฒ˜๋ฆฌ ๐Ÿ–ผ๏ธ ํ…์Šค์ฒ˜ ์ตœ์ ํ™” KTX2/Basis Universal ์••์ถ• ํฌ๋งท ์‚ฌ์šฉ โ€” ํŒŒ์ผ ํฌ๊ธฐ 80% ๊ฐ์†Œ โšก ์ง€์—ฐ ๋กœ๋”ฉ (Lazy Loading) ๋ทฐํฌํŠธ์— ๋“ค์–ด์˜ฌ ๋•Œ๋งŒ 3D ์”ฌ ์ดˆ๊ธฐํ™” โ€” ์ดˆ๊ธฐ ๋กœ๋”ฉ ์‹œ๊ฐ„ ๋‹จ์ถ• ๋ชจ๋ฐ”์ผ ์ฒดํฌ๋ฆฌ์ŠคํŠธ โœ… ํ”ฝ์…€ ๋น„์œจ ์ œํ•œ โœ… ์•ˆํ‹ฐ์•จ๋ฆฌ์–ด์‹ฑ ์กฐ๊ฑด๋ถ€ โœ… ๊ทธ๋ฆผ์ž ํ•ด์ƒ๋„ ๋‚ฎ์ถค โœ… ํฌ์ŠคํŠธ ํšจ๊ณผ ์ตœ์†Œํ™” โœ… ๋ฐฐํ„ฐ๋ฆฌ ์ƒํƒœ ๊ฐ์ง€ ์„ฑ๋Šฅ๊ณผ ๋น„์ฃผ์–ผ์˜ ๊ท ํ˜•์ด ์ข‹์€ WebGL ๊ฒฝํ—˜์˜ ํ•ต์‹ฌ์ด๋‹ค

๐ŸŽฏ ํ•ต์‹ฌ ์ตœ์ ํ™” ๊ธฐ๋ฒ•๋“ค

1
ํ”ฝ์…€ ๋น„์œจ(Pixel Ratio) ์ œํ•œ
Retina ๋””์Šคํ”Œ๋ ˆ์ด๋Š” ํ”ฝ์…€ ๋น„์œจ์ด 2~3์ด์•ผ. ๊ทธ๋Œ€๋กœ ์“ฐ๋ฉด ๋ Œ๋”๋ง ๋ถ€ํ•˜๊ฐ€ 4~9๋ฐฐ ์ฆ๊ฐ€ํ•ด.
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
๋กœ ์ตœ๋Œ€ 2๋กœ ์ œํ•œํ•˜๋Š” ๊ฒŒ ์ข‹์•„.
2
์ธ์Šคํ„ด์‹ฑ (Instancing)
๊ฐ™์€ ๋ชจ์–‘์˜ ์˜ค๋ธŒ์ ํŠธ๊ฐ€ ๋งŽ์„ ๋•Œ ์‚ฌ์šฉํ•ด. ๋‚˜๋ฌด ์ˆฒ, ํŒŒํ‹ฐํด, ๊ฑด๋ฌผ ๊ตฐ์ง‘ ๋“ฑ์—์„œ InstancedMesh๋ฅผ ์“ฐ๋ฉด ๋“œ๋กœ์šฐ ์ฝœ์„ 1๋ฒˆ์œผ๋กœ ์ค„์ผ ์ˆ˜ ์žˆ์–ด. ์„ฑ๋Šฅ์ด ์ˆ˜์‹ญ ๋ฐฐ ํ–ฅ์ƒ๋ผ.
3
GLTF ์ตœ์ ํ™” (gltf-transform)
3D ๋ชจ๋ธ์„ ์›น์— ์˜ฌ๋ฆฌ๊ธฐ ์ „์— gltf-transform ๋„๊ตฌ๋กœ ์ตœ์ ํ™”ํ•ด์•ผ ํ•ด. ๋ฉ”์‹œ ์••์ถ•(Draco), ํ…์Šค์ฒ˜ ์••์ถ•(KTX2), ๋ถˆํ•„์š”ํ•œ ๋…ธ๋“œ ์ œ๊ฑฐ ๋“ฑ์„ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•ด์ค˜.
4
Frustum Culling
์นด๋ฉ”๋ผ ์‹œ์•ผ ๋ฐ–์— ์žˆ๋Š” ์˜ค๋ธŒ์ ํŠธ๋Š” ๋ Œ๋”๋งํ•˜์ง€ ์•Š๋Š” ๊ธฐ๋ฒ•์ด์•ผ. Three.js๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์ด๊ฑธ ์ง€์›ํ•˜์ง€๋งŒ, ์ปค์Šคํ…€ ์”ฌ์—์„œ๋Š” ๋ช…์‹œ์ ์œผ๋กœ ์„ค์ •ํ•ด์ค˜์•ผ ํ•  ๋•Œ๋„ ์žˆ์–ด.
5
๋ฐฐํ„ฐ๋ฆฌ ์ƒํƒœ API ํ™œ์šฉ
navigator.getBattery()๋กœ ๋ฐฐํ„ฐ๋ฆฌ ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜๊ณ , ๋ฐฐํ„ฐ๋ฆฌ๊ฐ€ ๋‚ฎ์„ ๋•Œ๋Š” 3D ํšจ๊ณผ๋ฅผ ์ค„์ด๊ฑฐ๋‚˜ ๋„๋Š” ๋ฐฐ๋ ค๊ฐ€ ํ•„์š”ํ•ด. ํŠนํžˆ ๋ชจ๋ฐ”์ผ ์‚ฌ์šฉ์ž๋ฅผ ์œ„ํ•œ UX ๋ฐฐ๋ ค์•ผ.
โš ๏ธ WebGL ์ปจํ…์ŠคํŠธ ์†์‹ค ์ฒ˜๋ฆฌ
๋ชจ๋ฐ”์ผ์—์„œ ํƒญ์„ ์ „ํ™˜ํ•˜๊ฑฐ๋‚˜ ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ๋ถ€์กฑํ•˜๋ฉด WebGL ์ปจํ…์ŠคํŠธ๊ฐ€ ์†์‹ค๋  ์ˆ˜ ์žˆ์–ด.
canvas.addEventListener('webglcontextlost', handler)
์™€ webglcontextrestored ์ด๋ฒคํŠธ๋ฅผ ๋ฐ˜๋“œ์‹œ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•ด. ์•ˆ ํ•˜๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ํƒญ ์ „ํ™˜ ํ›„ ๋Œ์•„์™”์„ ๋•Œ ๊ฒ€์€ ํ™”๋ฉด์„ ๋ณด๊ฒŒ ๋ผ.

๐ŸŽจ ์ƒ์„ธํŽ˜์ด์ง€์— 3D ๋…น์ด๊ธฐ โ€” ์‹ค์ „ ๋””์ž์ธ ๊ฐ€์ด๋“œ

์ž, ์ด์ œ ์ง„์งœ ์‹ค์ „์ด์•ผ. ์›น๋””์ž์ธ, ํŠนํžˆ ์ƒ์„ธํŽ˜์ด์ง€์— 3D๋ฅผ ์–ด๋–ป๊ฒŒ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋…น์ผ ์ˆ˜ ์žˆ๋Š”์ง€ ๊ตฌ์ฒด์ ์ธ ๊ฐ€์ด๋“œ๋ฅผ ์ค„๊ฒŒ.

๐Ÿ“ 3D ์š”์†Œ ๋ฐฐ์น˜ ์›์น™

โŒ ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์•ˆ ๋ผ

โ€ข ํŽ˜์ด์ง€ ์ „์ฒด๋ฅผ 3D๋กœ ๋„๋ฐฐ
โ€ข ๋กœ๋”ฉ ์—†์ด ๋ฌด๊ฑฐ์šด 3D ๋ชจ๋ธ ๋ฐ”๋กœ ํ‘œ์‹œ
โ€ข ๋ชจ๋ฐ”์ผ์—์„œ๋„ ๋ฐ์Šคํฌํƒ‘๊ณผ ๋™์ผํ•œ 3D
โ€ข 3D๊ฐ€ ์ฝ˜ํ…์ธ ๋ฅผ ๊ฐ€๋ฆฌ๋Š” ๋ ˆ์ด์•„์›ƒ
โ€ข ์‚ฌ์šฉ์ž๊ฐ€ ์ œ์–ดํ•  ์ˆ˜ ์—†๋Š” ์ž๋™ ํšŒ์ „
โœ… ์ด๋ ‡๊ฒŒ ํ•ด์•ผ ํ•ด

โ€ข ํ•ต์‹ฌ ํฌ์ธํŠธ์—๋งŒ 3D ์š”์†Œ ๋ฐฐ์น˜
โ€ข ๋กœ๋”ฉ ์Šค์ผˆ๋ ˆํ†ค + ์ ์ง„์  ๋กœ๋”ฉ
โ€ข ๋ชจ๋ฐ”์ผ์€ 2D ํด๋ฐฑ ๋˜๋Š” ๊ฒฝ๋Ÿ‰ ๋ฒ„์ „
โ€ข 3D๋Š” ๋ณด์กฐ ์—ญํ• , ํ…์ŠคํŠธ๊ฐ€ ์ฃผ์ธ๊ณต
โ€ข ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ์กฐ์ž‘ํ•˜๋Š” ์ธํ„ฐ๋ž™์…˜

๐Ÿ›’ ์ด์ปค๋จธ์Šค ์ƒ์„ธํŽ˜์ด์ง€ 3D ๊ตฌํ˜„ ํ”Œ๋กœ์šฐ

1
3D ๋ชจ๋ธ ์ค€๋น„
์ œํ’ˆ 3D ๋ชจ๋ธ์„ ์ค€๋น„ํ•ด. ์ง์ ‘ Blender๋กœ ๋งŒ๋“ค๊ฑฐ๋‚˜, ์ œ์กฐ์‚ฌ์—์„œ CAD ํŒŒ์ผ์„ ๋ฐ›์•„์„œ ๋ณ€ํ™˜ํ•ด. ์›น์šฉ์œผ๋กœ๋Š” ํด๋ฆฌ๊ณค ์ˆ˜๋ฅผ ์ตœ๋Œ€ํ•œ ์ค„์ด๊ณ (๋ณดํ†ต 10,000~50,000 ํด๋ฆฌ๊ณค), GLTF ํฌ๋งท์œผ๋กœ ๋‚ด๋ณด๋‚ด์•ผ ํ•ด.
2
๋ชจ๋ธ ์ตœ์ ํ™”
gltf-transform์ด๋‚˜ glTF Pipeline์œผ๋กœ Draco ์••์ถ• ์ ์šฉ. ํ…์Šค์ฒ˜๋Š” WebP ๋˜๋Š” KTX2๋กœ ๋ณ€ํ™˜. ๋ชฉํ‘œ ํŒŒ์ผ ํฌ๊ธฐ๋Š” ๋ชจ๋ฐ”์ผ ๊ธฐ์ค€ 2MB ์ดํ•˜๊ฐ€ ์ด์ƒ์ ์ด์•ผ.
3
Three.js ์”ฌ ์„ค์ •
์ œํ’ˆ ๋ทฐ์–ด์— ๋งž๋Š” ์นด๋ฉ”๋ผ, ์กฐ๋ช…, ํ™˜๊ฒฝ ๋งต ์„ค์ •. ์ŠคํŠœ๋””์˜ค ์กฐ๋ช… ๋А๋‚Œ์„ ์œ„ํ•ด HDR ํ™˜๊ฒฝ ๋งต + ์†Œํ”„ํŠธ ๋ฐ•์Šค ์กฐ๋ช… ์กฐํ•ฉ์„ ๋งŽ์ด ์จ. OrbitControls๋กœ ํšŒ์ „/์คŒ ์ธํ„ฐ๋ž™์…˜ ์ถ”๊ฐ€.
4
์ƒ‰์ƒ/์žฌ์งˆ ๋ณ€๊ฒฝ ๊ธฐ๋Šฅ
์ œํ’ˆ ์ƒ‰์ƒ ์„ ํƒ UI์™€ ์—ฐ๋™ํ•ด์„œ Three.js ๋จธํ‹ฐ๋ฆฌ์–ผ ์ƒ‰์ƒ์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ณ€๊ฒฝ. material.color.set(hexColor)๋กœ ๊ฐ„๋‹จํ•˜๊ฒŒ ๊ตฌํ˜„ ๊ฐ€๋Šฅ. ์ด๊ฒŒ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์—„์ฒญ ๋†’์—ฌ์ค˜.
5
AR ๋ณด๊ธฐ ๋ฒ„ํŠผ ์ถ”๊ฐ€
WebXR AR ์„ธ์…˜ ๋˜๋Š” iOS์˜ AR Quick Look์„ ์—ฐ๊ฒฐํ•ด์„œ "๋‚ด ๋ฐฉ์—์„œ ๋ณด๊ธฐ" ๋ฒ„ํŠผ ์ถ”๊ฐ€. ์ด๊ฒŒ ๊ตฌ๋งค ๊ฒฐ์ •์— ์—„์ฒญ๋‚œ ์˜ํ–ฅ์„ ์ค˜.
6
ํด๋ฐฑ ์ฒ˜๋ฆฌ
WebGL์„ ์ง€์›ํ•˜์ง€ ์•Š๋Š” ํ™˜๊ฒฝ(๊ตฌํ˜• ๋ธŒ๋ผ์šฐ์ €, ์ผ๋ถ€ ๋ชจ๋ฐ”์ผ)์„ ์œ„ํ•œ ํด๋ฐฑ ์ด๋ฏธ์ง€ ์ค€๋น„. WebGLRenderer ์ƒ์„ฑ ์‹œ try-catch๋กœ ์—๋Ÿฌ ์ฒ˜๋ฆฌํ•˜๊ณ  ํด๋ฐฑ UI ํ‘œ์‹œ.

๐ŸŽฏ ํฌํŠธํด๋ฆฌ์˜ค ์‚ฌ์ดํŠธ์—์„œ์˜ ํ™œ์šฉ

๋””์ž์ด๋„ˆ๋‚˜ ๊ฐœ๋ฐœ์ž ํฌํŠธํด๋ฆฌ์˜ค์—์„œ WebGL์€ ์ •๋ง ๊ฐ•๋ ฅํ•œ ์ฐจ๋ณ„ํ™” ํฌ์ธํŠธ์•ผ. ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ ์ž์‹ ์˜ ์žฌ๋Šฅ์„ ์–ดํ•„ํ•  ๋•Œ, ํฌํŠธํด๋ฆฌ์˜ค ์‚ฌ์ดํŠธ์— WebGL ์š”์†Œ๊ฐ€ ์žˆ์œผ๋ฉด ์ฒซ์ธ์ƒ์ด ์™„์ „ํžˆ ๋‹ฌ๋ผ์ง€๊ฑฐ๋“ .

ํฌํŠธํด๋ฆฌ์˜ค WebGL ํ™œ์šฉ ์•„์ด๋””์–ด: โ€ข ์ด๋ฆ„/๋กœ๊ณ ๋ฅผ 3D ํ…์ŠคํŠธ๋กœ ํ‘œํ˜„ (Three.js TextGeometry)
โ€ข ์Šคํ‚ฌ ์ฐจํŠธ๋ฅผ 3D ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ๊ทธ๋ž˜ํ”„๋กœ ์‹œ๊ฐํ™”
โ€ข ์ž‘์—…๋ฌผ ๊ฐค๋Ÿฌ๋ฆฌ๋ฅผ 3D ๊ณต๊ฐ„์— ๋ฐฐ์น˜ (๊ฐ€์ƒ ๊ฐค๋Ÿฌ๋ฆฌ)
โ€ข ๋งˆ์šฐ์Šค ์›€์ง์ž„์— ๋ฐ˜์‘ํ•˜๋Š” ํŒŒํ‹ฐํด ๋ฐฐ๊ฒฝ
โ€ข ์Šคํฌ๋กคํ•˜๋ฉด 3D ์˜ค๋ธŒ์ ํŠธ๊ฐ€ ์กฐ๋ฆฝ๋˜๋Š” ํšจ๊ณผ

๐Ÿ”ฎ WebXR ์‹ค์ „ ๊ตฌํ˜„ โ€” AR ์ œํ’ˆ ๋ฐฐ์น˜ ๋งŒ๋“ค๊ธฐ

์ด์ œ WebXR์˜ ์‹ค์ „ ๊ตฌํ˜„์„ ์‚ดํŽด๋ณด์ž. AR ์ œํ’ˆ ๋ฐฐ์น˜ ๊ธฐ๋Šฅ์€ ์ด์ปค๋จธ์Šค์—์„œ ๊ฐ€์žฅ ์ˆ˜์š”๊ฐ€ ๋†’์€ WebXR ํ™œ์šฉ ์‚ฌ๋ก€์•ผ.

๐Ÿ“ฑ WebXR AR ์„ธ์…˜ ์‹œ์ž‘ํ•˜๊ธฐ

// WebXR AR ์„ธ์…˜ ์‹œ์ž‘ ์˜ˆ์‹œ
async function startAR() {
// AR ์ง€์› ์—ฌ๋ถ€ ํ™•์ธ
if (!navigator.xr) {
console.log('WebXR ๋ฏธ์ง€์› ๋ธŒ๋ผ์šฐ์ €');
return;
}

const supported = await navigator.xr.isSessionSupported('immersive-ar');
if (!supported) {
console.log('AR ๋ฏธ์ง€์› ๊ธฐ๊ธฐ');
return;
}

// AR ์„ธ์…˜ ์š”์ฒญ
const session = await navigator.xr.requestSession('immersive-ar', {
requiredFeatures: ['hit-test'],  // ํžˆํŠธ ํ…Œ์ŠคํŠธ ํ•„์ˆ˜
optionalFeatures: ['dom-overlay', 'anchors']  // ์„ ํƒ์  ๊ธฐ๋Šฅ
});

// WebGL ์ปจํ…์ŠคํŠธ์™€ XR ์„ธ์…˜ ์—ฐ๊ฒฐ
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl', { xrCompatible: true });
await gl.makeXRCompatible();

session.updateRenderState({
baseLayer: new XRWebGLLayer(session, gl)
});

// ํžˆํŠธ ํ…Œ์ŠคํŠธ ์†Œ์Šค ์ƒ์„ฑ (๋ฐ”๋‹ฅ ๊ฐ์ง€)
const hitTestSource = await session.requestHitTestSource({
space: await session.requestReferenceSpace('viewer')
});

// ๋ Œ๋” ๋ฃจํ”„ ์‹œ์ž‘
session.requestAnimationFrame(onXRFrame);
}

// XR ๋ Œ๋” ๋ฃจํ”„
function onXRFrame(time, frame) {
const session = frame.session;
session.requestAnimationFrame(onXRFrame);

// ํžˆํŠธ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ ๊ฐ€์ ธ์˜ค๊ธฐ
const hitTestResults = frame.getHitTestResults(hitTestSource);

if (hitTestResults.length > 0) {
const hit = hitTestResults[0];
const pose = hit.getPose(referenceSpace);

// ๊ฐ์ง€๋œ ํ‰๋ฉด ์œ„์น˜์— 3D ์˜ค๋ธŒ์ ํŠธ ๋ฐฐ์น˜
if (pose) {
reticle.visible = true;
reticle.matrix.fromArray(pose.transform.matrix);
}
}
}

๐ŸŒ A-Frame์œผ๋กœ ๋น ๋ฅด๊ฒŒ AR ๊ตฌํ˜„ํ•˜๊ธฐ

์ฝ”๋“œ๊ฐ€ ๋ณต์žกํ•˜๋‹ค๋ฉด A-Frame์„ ์จ๋ด. HTML ํƒœ๊ทธ ๋ช‡ ์ค„๋กœ AR ๊ฒฝํ—˜์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด.

<!-- A-Frame WebXR AR ์˜ˆ์‹œ -->
<script src="https://aframe.io/releases/1.4.0/aframe.min.js"></script>
<script src="https://raw.githack.com/AR-js-org/AR.js/master/aframe/build/aframe-ar.js"></script>

<a-scene embedded arjs="sourceType: webcam; debugUIEnabled: false;">
<!-- ๋งˆ์ปค ๊ธฐ๋ฐ˜ AR -->
<a-marker preset="hiro">
<a-box position="0 0.5 0" material="color: #7ab8d4;" rotation="0 45 0"></a-box>
</a-marker>

<a-entity camera></a-entity>
</a-scene>
๐Ÿ’ก iOS์—์„œ AR ๊ตฌํ˜„ํ•˜๊ธฐ iOS Safari๋Š” WebXR AR์„ ์™„์ „ํžˆ ์ง€์›ํ•˜์ง€ ์•Š์•„. ๋Œ€์‹  Apple์˜ AR Quick Look์„ ํ™œ์šฉํ•ด. USDZ ํŒŒ์ผ ํฌ๋งท์œผ๋กœ 3D ๋ชจ๋ธ์„ ์ค€๋น„ํ•˜๊ณ , <a rel="ar" href="model.usdz"> ๋งํฌ๋ฅผ ๋งŒ๋“ค๋ฉด iOS์—์„œ AR ๋ณด๊ธฐ๊ฐ€ ๊ฐ€๋Šฅํ•ด. Android๋Š” WebXR, iOS๋Š” AR Quick Look โ€” ๋‘ ๊ฐ€์ง€๋ฅผ ๋ชจ๋‘ ์ง€์›ํ•˜๋Š” ๊ฒŒ ํ˜„์‹ค์ ์ธ ์ ‘๊ทผ์ด์•ผ.

๐Ÿš€ ๋ฏธ๋ž˜ ํŠธ๋ Œ๋“œ โ€” ์›น๋””์ž์ธ์˜ ๋‹ค์Œ ๋‹จ๊ณ„

WebGL๊ณผ WebXR์€ ๊ณ„์† ์ง„ํ™”ํ•˜๊ณ  ์žˆ์–ด. ์•ž์œผ๋กœ ์–ด๋–ค ๋ฐฉํ–ฅ์œผ๋กœ ๋ฐœ์ „ํ• ์ง€ ์‚ดํŽด๋ณด์ž.

WebGL/WebXR ๋ฏธ๋ž˜ ๋กœ๋“œ๋งต ํ˜„์žฌ 2024๋…„ WebGL 2.0 ์•ˆ์ •ํ™” WebXR ํ‘œ์ค€ํ™” Three.js r160+ ๊ทผ๋ฏธ๋ž˜ 2025~2026๋…„ WebGPU ์ฃผ๋ฅ˜ํ™” AI ์…ฐ์ด๋” ์ƒ์„ฑ ๊ณต๊ฐ„ ์ปดํ“จํŒ… ํ™•์‚ฐ Vision Pro ์›นXR ๋ฏธ๋ž˜ 2027๋…„+ ๋ฉ”ํƒ€๋ฒ„์Šค ์›น ํ‘œ์ค€ ๋‰ด๋Ÿด ์ธํ„ฐํŽ˜์ด์Šค ํ™€๋กœ๊ทธ๋žจ ๋””์Šคํ”Œ๋ ˆ์ด โšก WebGPU WebGL์˜ ํ›„๊ณ„์ž Vulkan/Metal ๊ธฐ๋ฐ˜ ์ปดํ“จํŠธ ์…ฐ์ด๋” ์ง€์› AI/ML ๊ฐ€์† ๊ฐ€๋Šฅ Chrome 113+ ์ง€์› NEW ์›น ๊ทธ๋ž˜ํ”ฝ ๊ธฐ์ˆ ์€ WebGL โ†’ WebGPU๋กœ ์ง„ํ™”ํ•˜๋ฉฐ ๋”์šฑ ๊ฐ•๋ ฅํ•ด์ง€๊ณ  ์žˆ๋‹ค

โšก WebGPU โ€” WebGL์˜ ํ›„๊ณ„์ž

WebGPU๋Š” WebGL์„ ๋Œ€์ฒดํ•  ์ฐจ์„ธ๋Œ€ ์›น ๊ทธ๋ž˜ํ”ฝ API์•ผ. 2023๋…„ Chrome 113์—์„œ ์ •์‹ ์ง€์›์„ ์‹œ์ž‘ํ–ˆ์–ด. WebGL์ด OpenGL ES ๊ธฐ๋ฐ˜์ด๋ผ๋ฉด, WebGPU๋Š” Vulkan, Metal, Direct3D 12 ๊ฐ™์€ ์ตœ์‹  ๊ทธ๋ž˜ํ”ฝ API๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•ด.

WebGPU๊ฐ€ WebGL๋ณด๋‹ค ๋‚˜์€ ์ : โ€ข ์ปดํ“จํŠธ ์…ฐ์ด๋”: GPU๋ฅผ ๊ทธ๋ž˜ํ”ฝ๋ฟ ์•„๋‹ˆ๋ผ ๋ฒ”์šฉ ๊ณ„์‚ฐ(GPGPU)์—๋„ ํ™œ์šฉ ๊ฐ€๋Šฅ
โ€ข ๋” ๋‚ฎ์€ ์˜ค๋ฒ„ํ—ค๋“œ: CPU-GPU ํ†ต์‹ ์ด ๋” ํšจ์œจ์ 
โ€ข ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋”ฉ: Web Worker์—์„œ GPU ์ž‘์—… ๊ฐ€๋Šฅ
โ€ข AI/ML ๊ฐ€์†: ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋”ฅ๋Ÿฌ๋‹ ๋ชจ๋ธ ์‹คํ–‰ ๊ฐ€๋Šฅ
โ€ข ๋” ๋‚˜์€ ์—๋Ÿฌ ์ฒ˜๋ฆฌ: ๋””๋ฒ„๊น…์ด ํ›จ์”ฌ ์‰ฌ์›Œ์ง

๐Ÿค– AI + WebGL์˜ ๋งŒ๋‚จ

AI๊ฐ€ ์›น 3D ๋ถ„์•ผ์—๋„ ํŒŒ๊ณ ๋“ค๊ณ  ์žˆ์–ด. ๋ช‡ ๊ฐ€์ง€ ํฅ๋ฏธ๋กœ์šด ํŠธ๋ Œ๋“œ๋ฅผ ์†Œ๊ฐœํ• ๊ฒŒ:

๐ŸŽจ AI ์…ฐ์ด๋” ์ƒ์„ฑ ํ…์ŠคํŠธ ํ”„๋กฌํ”„ํŠธ๋กœ GLSL ์…ฐ์ด๋”๋ฅผ ์ž๋™ ์ƒ์„ฑํ•˜๋Š” ๋„๊ตฌ๋“ค์ด ๋“ฑ์žฅํ•˜๊ณ  ์žˆ์–ด. "ํŒŒ๋ž€ ๋ฌผ๊ฒฐ์ด ํ๋ฅด๋Š” ๋ฐฐ๊ฒฝ" ๊ฐ™์€ ์„ค๋ช…์œผ๋กœ ์…ฐ์ด๋” ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ค์–ด์ค˜.
๐Ÿ—๏ธ AI 3D ๋ชจ๋ธ ์ƒ์„ฑ ํ…์ŠคํŠธ๋‚˜ ์ด๋ฏธ์ง€์—์„œ 3D ๋ชจ๋ธ์„ ์ž๋™ ์ƒ์„ฑํ•˜๋Š” AI ๊ธฐ์ˆ (NeRF, Gaussian Splatting ๋“ฑ)์ด ๋น ๋ฅด๊ฒŒ ๋ฐœ์ „ํ•˜๊ณ  ์žˆ์–ด. ์ œํ’ˆ ์‚ฌ์ง„ ๋ช‡ ์žฅ์œผ๋กœ 3D ๋ชจ๋ธ์„ ๋งŒ๋“ค์–ด ์›น์— ์˜ฌ๋ฆฌ๋Š” ๊ฒŒ ๊ฐ€๋Šฅํ•ด์ง€๊ณ  ์žˆ์–ด.
๐Ÿง  Gaussian Splatting 2023๋…„์— ๋“ฑ์žฅํ•œ ํ˜์‹ ์ ์ธ 3D ํ‘œํ˜„ ๊ธฐ์ˆ ์ด์•ผ. ์‚ฌ์ง„ ์—ฌ๋Ÿฌ ์žฅ์œผ๋กœ ํฌํ† ๋ฆฌ์–ผํ•œ 3D ์”ฌ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด. Three.js์—์„œ๋„ Gaussian Splatting ๋ Œ๋”๋Ÿฌ๊ฐ€ ๊ฐœ๋ฐœ๋˜๊ณ  ์žˆ์–ด.

๐ŸŒ ๊ณต๊ฐ„ ์ปดํ“จํŒ…๊ณผ ์›น

Apple Vision Pro์˜ ๋“ฑ์žฅ์œผ๋กœ ๊ณต๊ฐ„ ์ปดํ“จํŒ…์ด ํ˜„์‹ค์ด ๋์–ด. visionOS์˜ Safari๋Š” WebXR์„ ์ง€์›ํ•˜๊ณ , ์›น์‚ฌ์ดํŠธ๊ฐ€ 3D ๊ณต๊ฐ„์— ์ฐฝ์œผ๋กœ ๋–  ์žˆ๋Š” ๊ฒฝํ—˜์„ ์ œ๊ณตํ•ด. ์•ž์œผ๋กœ ์›น๋””์ž์ธ์€ 2D ํ™”๋ฉด๋ฟ ์•„๋‹ˆ๋ผ 3D ๊ณต๊ฐ„๋„ ๊ณ ๋ คํ•ด์•ผ ํ•˜๋Š” ์‹œ๋Œ€๊ฐ€ ์˜ค๊ณ  ์žˆ์–ด.


๐ŸŽ“ ์‹œ์ž‘ํ•˜๋Š” ๋ฐฉ๋ฒ• โ€” ํ•™์Šต ๋กœ๋“œ๋งต

์ž, ์ด์ œ "๋‚˜๋„ ํ•ด๋ณด๊ณ  ์‹ถ๋‹ค!"๋Š” ์ƒ๊ฐ์ด ๋“ค์ง€? ์–ด๋””์„œ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์•ผ ํ• ์ง€ ๋ช…ํ™•ํ•œ ๋กœ๋“œ๋งต์„ ์ค„๊ฒŒ.

๐Ÿ—บ๏ธ WebGL/WebXR ํ•™์Šต ๋กœ๋“œ๋งต

1
๊ธฐ์ดˆ: JavaScript & Canvas 2D
WebGL ์ „์— JavaScript๋ฅผ ์ž˜ ์•Œ์•„์•ผ ํ•ด. ํŠนํžˆ ๋ฐฐ์—ด, ํƒ€์ž…๋“œ ์–ด๋ ˆ์ด(Float32Array), ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ์ดํ•ดํ•ด์•ผ ํ•ด. Canvas 2D API๋„ ๋จผ์ € ์ตํ˜€๋‘๋ฉด ์ข‹์•„. ๊ธฐ๊ฐ„: 1~2๊ฐœ์›”
2
Three.js ์ž…๋ฌธ
์ˆœ์ˆ˜ WebGL๋ณด๋‹ค Three.js๋ถ€ํ„ฐ ์‹œ์ž‘ํ•˜๋Š” ๊ฑธ ๊ฐ•๋ ฅ ์ถ”์ฒœํ•ด. Three.js Journey(Bruno Simon์˜ ๊ฐ•์˜)๊ฐ€ ์ตœ๊ณ ์•ผ. ์”ฌ, ์นด๋ฉ”๋ผ, ๋จธํ‹ฐ๋ฆฌ์–ผ, ์กฐ๋ช…์˜ ๊ธฐ๋ณธ์„ ์ตํ˜€. ๊ธฐ๊ฐ„: 1~2๊ฐœ์›”
3
GLSL ์…ฐ์ด๋” ํ•™์Šต
The Book of Shaders(thebookofshaders.com)๊ฐ€ ์ตœ๊ณ ์˜ ๋ฌด๋ฃŒ ๋ฆฌ์†Œ์Šค์•ผ. ์ˆ˜ํ•™ ๊ฐœ๋…(๋ฒกํ„ฐ, ํ–‰๋ ฌ, ์‚ผ๊ฐํ•จ์ˆ˜)๋„ ํ•จ๊ป˜ ๊ณต๋ถ€ํ•ด์•ผ ํ•ด. Shadertoy์—์„œ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์˜ ์…ฐ์ด๋”๋ฅผ ๋ถ„์„ํ•˜๋Š” ๊ฒƒ๋„ ์ข‹์•„. ๊ธฐ๊ฐ„: 2~3๊ฐœ์›”
4
WebXR ์‹ค์Šต
A-Frame์œผ๋กœ ๊ฐ„๋‹จํ•œ VR/AR ์”ฌ์„ ๋งŒ๋“ค์–ด๋ด. ๊ทธ ๋‹ค์Œ WebXR Device API๋ฅผ ์ง์ ‘ ๋‹ค๋ค„๋ด. Immersive Web Weekly ๋‰ด์Šค๋ ˆํ„ฐ๋ฅผ ๊ตฌ๋…ํ•˜๋ฉด ์ตœ์‹  ํŠธ๋ Œ๋“œ๋ฅผ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ์–ด. ๊ธฐ๊ฐ„: 1~2๊ฐœ์›”
5
ํฌํŠธํด๋ฆฌ์˜ค ํ”„๋กœ์ ํŠธ
๋ฐฐ์šด ๊ฑธ ์‹ค์ œ ํ”„๋กœ์ ํŠธ์— ์ ์šฉํ•ด๋ด. ์ œํ’ˆ ๋ทฐ์–ด, ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ํฌํŠธํด๋ฆฌ์˜ค, ๋ฐ์ดํ„ฐ ์‹œ๊ฐํ™” ์ค‘ ํ•˜๋‚˜๋ฅผ ๊ณจ๋ผ์„œ ์™„์„ฑ๋„ ์žˆ๊ฒŒ ๋งŒ๋“ค์–ด๋ด. ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์— ์˜ฌ๋ ค์„œ ์‹ค์ œ ํด๋ผ์ด์–ธํŠธ์™€ ์ผํ•ด๋ณด๋Š” ๊ฒƒ๋„ ์ข‹์€ ๊ฒฝํ—˜์ด์•ผ.

๐Ÿ“š ์ถ”์ฒœ ํ•™์Šต ๋ฆฌ์†Œ์Šค

๋ฆฌ์†Œ์Šค ์ข…๋ฅ˜ ํŠน์ง•
Three.js Journey ์œ ๋ฃŒ ๊ฐ•์˜ ๊ฐ€์žฅ ์ฒด๊ณ„์ ์ธ Three.js ๊ฐ•์˜, Bruno Simon ์ œ์ž‘
The Book of Shaders ๋ฌด๋ฃŒ ์ฑ… GLSL ์…ฐ์ด๋” ์ž…๋ฌธ์˜ ๋ฐ”์ด๋ธ”
WebGL Fundamentals ๋ฌด๋ฃŒ ํŠœํ† ๋ฆฌ์–ผ ์ˆœ์ˆ˜ WebGL ๊ธฐ์ดˆ๋ฅผ ๊ฐ€์žฅ ์ž˜ ์„ค๋ช…ํ•œ ์‚ฌ์ดํŠธ
Shadertoy ์ปค๋ฎค๋‹ˆํ‹ฐ ์ „ ์„ธ๊ณ„ ์…ฐ์ด๋” ์•„ํ‹ฐ์ŠคํŠธ๋“ค์˜ ์ž‘ํ’ˆ ๊ณต์œ 
Sketchfab 3D ๋ชจ๋ธ ๋งˆ์ผ“ ๋ฌด๋ฃŒ/์œ ๋ฃŒ 3D ๋ชจ๋ธ ๋‹ค์šด๋กœ๋“œ
Immersive Web ๊ณต์‹ ๋ฌธ์„œ W3C WebXR ๊ณต์‹ ์˜ˆ์ œ ๋ฐ ๋ฌธ์„œ
๐Ÿ’ก ๋””์ž์ด๋„ˆ๋ผ๋ฉด Spline๋ถ€ํ„ฐ! ์ฝ”๋”ฉ์ด ๋ถ€๋‹ด์Šค๋Ÿฌ์šด ๋””์ž์ด๋„ˆ๋ผ๋ฉด Spline(spline.design)๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด๋ด. ๋…ธ์ฝ”๋“œ๋กœ 3D ์š”์†Œ๋ฅผ ๋งŒ๋“ค๊ณ  ์›น์— ์ž„๋ฒ ๋“œํ•  ์ˆ˜ ์žˆ์–ด. ๊ธฐ๋ณธ์ ์ธ 3D ๊ฐœ๋…์„ ์ตํžˆ๋ฉด์„œ ์‹ค์ œ ๊ฒฐ๊ณผ๋ฌผ์„ ๋น ๋ฅด๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด. ์ดํ›„์— Three.js๋กœ ๋„˜์–ด๊ฐ€๋ฉด ํ›จ์”ฌ ์ˆ˜์›”ํ•ด.

๐Ÿ’ก ์‹ค์ „ ํŒ ๋ชจ์Œ โ€” ํ˜„์—…์—์„œ ๋ฐ”๋กœ ์“ฐ๋Š” ๊ฒƒ๋“ค

๋งˆ์ง€๋ง‰์œผ๋กœ ์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„œ ๋ฐ”๋กœ ์จ๋จน์„ ์ˆ˜ ์žˆ๋Š” ํŒ๋“ค์„ ๋ชจ์•„๋ดค์–ด.

๐Ÿ”ง Three.js ์‹ค์ „ ํŒ

1. ๋ Œ๋”๋Ÿฌ ์„ค์ • ์ตœ์ ํ™”
renderer.outputColorSpace = THREE.SRGBColorSpace๋ฅผ ์„ค์ •ํ•ด์•ผ ์ƒ‰์ƒ์ด ์ •ํ™•ํ•˜๊ฒŒ ๋‚˜์™€. ์•ˆ ํ•˜๋ฉด ์ƒ‰์ด ์–ด๋‘ก๊ฑฐ๋‚˜ ์ด์ƒํ•˜๊ฒŒ ๋ณด์—ฌ. ๋˜
renderer.toneMapping = THREE.ACESFilmicToneMapping
์„ ์“ฐ๋ฉด ์˜ํ™” ๊ฐ™์€ ์ƒ‰๊ฐ์ด ๋‚˜์™€.
2. ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ Three.js๋Š” ์ž๋™์œผ๋กœ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ํ•ด์ œํ•˜์ง€ ์•Š์•„. ์”ฌ์—์„œ ์˜ค๋ธŒ์ ํŠธ๋ฅผ ์ œ๊ฑฐํ•  ๋•Œ geometry.dispose(), material.dispose(), texture.dispose()๋ฅผ ๋ฐ˜๋“œ์‹œ ํ˜ธ์ถœํ•ด์•ผ ํ•ด. ์•ˆ ํ•˜๋ฉด ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜๊ฐ€ ์ƒ๊ฒจ.
3. ๊ทธ๋ฆผ์ž ์ตœ์ ํ™” ๊ทธ๋ฆผ์ž๋Š” ์„ฑ๋Šฅ์„ ๋งŽ์ด ์žก์•„๋จน์–ด. ๊ผญ ํ•„์š”ํ•œ ์˜ค๋ธŒ์ ํŠธ์—๋งŒ castShadow์™€ receiveShadow๋ฅผ ์„ค์ •ํ•ด. ๊ทธ๋ฆผ์ž ๋งต ํฌ๊ธฐ๋Š” light.shadow.mapSize.set(1024, 1024)๋กœ ์ ์ ˆํžˆ ์กฐ์ ˆํ•ด.
4. ๋ฐ˜์‘ํ˜• ์ฒ˜๋ฆฌ
์ฐฝ ํฌ๊ธฐ๊ฐ€ ๋ฐ”๋€” ๋•Œ ์นด๋ฉ”๋ผ ์ข…ํšก๋น„์™€ ๋ Œ๋”๋Ÿฌ ํฌ๊ธฐ๋ฅผ ์—…๋ฐ์ดํŠธํ•ด์•ผ ํ•ด:
camera.aspect = window.innerWidth / window.innerHeight;

camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
5. ๋กœ๋”ฉ ๋งค๋‹ˆ์ € ํ™œ์šฉ THREE.LoadingManager๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ชจ๋“  ์—์…‹์˜ ๋กœ๋”ฉ ์ง„ํ–‰๋„๋ฅผ ํ•œ ๊ณณ์—์„œ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด. ๋กœ๋”ฉ ํ™”๋ฉด UI๋ฅผ ๋งŒ๋“ค ๋•Œ ํ•„์ˆ˜์•ผ.

๐ŸŽจ ๋””์ž์ธ ๊ด€์ ์—์„œ์˜ ํŒ

1. 3D๋Š” ๋ณด์กฐ ์—ญํ•  3D ์š”์†Œ๊ฐ€ ์ฝ˜ํ…์ธ ๋ฅผ ๋ฐฉํ•ดํ•˜๋ฉด ์•ˆ ๋ผ. ํ…์ŠคํŠธ ๊ฐ€๋…์„ฑ์„ ํ•ญ์ƒ ์ตœ์šฐ์„ ์œผ๋กœ. 3D ๋ฐฐ๊ฒฝ ์œ„์— ํ…์ŠคํŠธ๋ฅผ ์˜ฌ๋ฆด ๋•Œ๋Š” ๋ฐ˜ํˆฌ๋ช… ์˜ค๋ฒ„๋ ˆ์ด๋‚˜ ๋ธ”๋Ÿฌ ํšจ๊ณผ๋กœ ๋Œ€๋น„๋ฅผ ๋†’์—ฌ์ค˜.
2. ๋กœ๋”ฉ ๊ฒฝํ—˜ ์„ค๊ณ„ 3D ์—์…‹์€ ๋ฌด๊ฑฐ์›Œ. ๋กœ๋”ฉ ์ค‘์— ์‚ฌ์šฉ์ž๊ฐ€ ์ง€๋ฃจํ•˜์ง€ ์•Š๋„๋ก ์Šค์ผˆ๋ ˆํ†ค UI๋‚˜ ํ”„๋กœ๊ทธ๋ ˆ์Šค ๋ฐ”๋ฅผ ๋ณด์—ฌ์ค˜. ๋กœ๋”ฉ์ด ์™„๋ฃŒ๋˜๋ฉด ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ํŽ˜์ด๋“œ์ธํ•˜๋Š” ์ „ํ™˜ ํšจ๊ณผ๋ฅผ ์ถ”๊ฐ€ํ•ด.
3. ์ ‘๊ทผ์„ฑ ๊ณ ๋ ค ๋ชจ์…˜์— ๋ฏผ๊ฐํ•œ ์‚ฌ์šฉ์ž๋ฅผ ์œ„ํ•ด prefers-reduced-motion ๋ฏธ๋””์–ด ์ฟผ๋ฆฌ๋ฅผ ํ™•์ธํ•˜๊ณ , ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์ค„์ด๊ฑฐ๋‚˜ ๋„๋Š” ์˜ต์…˜์„ ์ œ๊ณตํ•ด. WebGL์„ ์ง€์›ํ•˜์ง€ ์•Š๋Š” ํ™˜๊ฒฝ์„ ์œ„ํ•œ ํด๋ฐฑ๋„ ํ•„์ˆ˜์•ผ.
4. ์ƒ‰์ƒ ๊ณต๊ฐ„ ์ฃผ์˜ Three.js์—์„œ ์ƒ‰์ƒ์„ ์ง€์ •ํ•  ๋•Œ sRGB ์ƒ‰์ƒ ๊ณต๊ฐ„์„ ์‚ฌ์šฉํ•ด. new THREE.Color().setStyle('#7ab8d4')์ฒ˜๋Ÿผ CSS ์ƒ‰์ƒ ๋ฌธ์ž์—ด์„ ์ง์ ‘ ์‚ฌ์šฉํ•˜๋ฉด ์ƒ‰์ƒ ๊ณต๊ฐ„ ๋ณ€ํ™˜์ด ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌ๋ผ.
5. ํ„ฐ์น˜ ์ธํ„ฐ๋ž™์…˜ ๋ชจ๋ฐ”์ผ์—์„œ OrbitControls์˜ ๊ธฐ๋ณธ ํ„ฐ์น˜ ๋™์ž‘์ด ์Šคํฌ๋กค๊ณผ ์ถฉ๋Œํ•  ์ˆ˜ ์žˆ์–ด. controls.enablePan = false๋กœ ํŒจ๋‹์„ ๋น„ํ™œ์„ฑํ™”ํ•˜๊ฑฐ๋‚˜, ํŠน์ • ์˜์—ญ์—์„œ๋งŒ 3D ์ธํ„ฐ๋ž™์…˜์ด ํ™œ์„ฑํ™”๋˜๋„๋ก ์„ค๊ณ„ํ•ด.

๐ŸŽฏ ๋งˆ๋ฌด๋ฆฌ โ€” ์›น๋””์ž์ธ์˜ ์ƒˆ ์ง€ํ‰

์ž, ์—ฌ๊ธฐ๊นŒ์ง€ WebGL๊ณผ WebXR์˜ ์„ธ๊ณ„๋ฅผ ํ•จ๊ป˜ ํƒํ—˜ํ–ˆ์–ด! ๐ŸŽ‰

์ •๋ฆฌํ•˜์ž๋ฉด, WebGL์€ ๋ธŒ๋ผ์šฐ์ €์—์„œ GPU๋ฅผ ์ง์ ‘ ํ™œ์šฉํ•ด 3D ๊ทธ๋ž˜ํ”ฝ์„ ๋ Œ๋”๋งํ•˜๋Š” ๊ธฐ์ˆ ์ด๊ณ , WebXR์€ VR/AR/MR ๊ฒฝํ—˜์„ ์›น์—์„œ ๊ตฌํ˜„ํ•˜๋Š” ํ‘œ์ค€ API์•ผ. ์ด ๋‘ ๊ธฐ์ˆ ์ด ๊ฒฐํ•ฉ๋˜๋ฉด์„œ ์›น๋””์ž์ธ์˜ ๊ฒฝ๊ณ„๊ฐ€ ์™„์ „ํžˆ ์ƒˆ๋กœ ๊ทธ๋ ค์ง€๊ณ  ์žˆ์–ด.

Three.js, React Three Fiber, A-Frame ๊ฐ™์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์ด ์ง„์ž…์žฅ๋ฒฝ์„ ๋‚ฎ์ถฐ์ฃผ๊ณ  ์žˆ๊ณ , Spline ๊ฐ™์€ ๋…ธ์ฝ”๋“œ ๋„๊ตฌ๋“ค๋„ ๋“ฑ์žฅํ•˜๋ฉด์„œ ๋””์ž์ด๋„ˆ๋„ 3D ์›น ๊ฒฝํ—˜์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋Š” ์‹œ๋Œ€๊ฐ€ ๋์–ด.

์ƒ์„ธํŽ˜์ด์ง€์—์„œ 3D ์ œํ’ˆ ๋ทฐ์–ด, ํฌํŠธํด๋ฆฌ์˜ค์˜ ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ 3D ์š”์†Œ, AR ์ œํ’ˆ ๋ฐฐ์น˜ ๊ธฐ๋Šฅ โ€” ์ด๋Ÿฐ ๊ฒƒ๋“ค์ด ์ด์ œ "ํŠน๋ณ„ํ•œ ๊ฒƒ"์ด ์•„๋‹ˆ๋ผ "๊ธฐ๋ณธ ๊ธฐ๋Œ€์น˜"๊ฐ€ ๋˜์–ด๊ฐ€๊ณ  ์žˆ์–ด. ์ง€๊ธˆ ์‹œ์ž‘ํ•˜๋ฉด ์ถฉ๋ถ„ํžˆ ์•ž์„œ๊ฐˆ ์ˆ˜ ์žˆ์–ด.

๐Ÿš€ ์ง€๊ธˆ ๋‹น์žฅ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ 1. Spline(spline.design)์—์„œ ๊ณ„์ • ๋งŒ๋“ค๊ณ  ๊ฐ„๋‹จํ•œ 3D ์˜ค๋ธŒ์ ํŠธ ๋งŒ๋“ค์–ด๋ณด๊ธฐ
2. Three.js ๊ณต์‹ ์‚ฌ์ดํŠธ(threejs.org)์˜ ์˜ˆ์ œ๋“ค ์‚ดํŽด๋ณด๊ธฐ
3. Shadertoy(shadertoy.com)์—์„œ ์…ฐ์ด๋” ์•„ํŠธ ๊ฐ์ƒํ•˜๊ธฐ
4. A-Frame(aframe.io)์œผ๋กœ ๊ฐ„๋‹จํ•œ VR ์”ฌ 5๋ถ„ ๋งŒ์— ๋งŒ๋“ค์–ด๋ณด๊ธฐ
5. ์žฌ๋Šฅ๋„ท์—์„œ WebGL/Three.js ์ „๋ฌธ๊ฐ€๋ฅผ ์ฐพ์•„ ํ˜‘์—…ํ•˜๊ฑฐ๋‚˜, ๋ณธ์ธ์˜ 3D ์›น ์Šคํ‚ฌ์„ ์„œ๋น„์Šค๋กœ ๋“ฑ๋กํ•ด๋ณด๊ธฐ!

์›น๋””์ž์ธ์˜ ๋ฏธ๋ž˜๋Š” 2D ํ‰๋ฉด์„ ๋„˜์–ด 3D ๊ณต๊ฐ„์œผ๋กœ ํ™•์žฅ๋˜๊ณ  ์žˆ์–ด. WebGL๊ณผ WebXR์€ ๊ทธ ๋ณ€ํ™”์˜ ์ตœ์ „์„ ์— ์žˆ๋Š” ๊ธฐ์ˆ ์ด์•ผ. ์ด ํ๋ฆ„์— ์˜ฌ๋ผํƒ€๋Š” ๊ฑด ์„ ํƒ์ด ์•„๋‹ˆ๋ผ ํ•„์ˆ˜๊ฐ€ ๋˜์–ด๊ฐ€๊ณ  ์žˆ์–ด. ์ง€๊ธˆ ๋ฐฐ์šฐ๊ธฐ ์‹œ์ž‘ํ•˜๋ฉด, ๋ช‡ ๋…„ ํ›„์— "๊ทธ๋•Œ ์‹œ์ž‘ํ•˜๊ธธ ์ž˜ํ–ˆ๋‹ค"๋Š” ์ƒ๊ฐ์ด ๋“ค ๊ฑฐ์•ผ. ๐Ÿ’ช

์ž, ์ด์ œ ๋ธŒ๋ผ์šฐ์ €๋ฅผ ์—ด๊ณ  ์ฝ”๋“œ๋ฅผ ์ณ๋ด. 3D ์„ธ๊ณ„๊ฐ€ ๊ธฐ๋‹ค๋ฆฌ๊ณ  ์žˆ์–ด! ๐ŸŒโœจ

๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.