자바스크립트 WebGL: 3D 그래픽 프로그래밍의 세계로 떠나는 모험 🚀✨

콘텐츠 대표 이미지 - 자바스크립트 WebGL: 3D 그래픽 프로그래밍의 세계로 떠나는 모험 🚀✨

 

 

안녕하세요, 여러분! 오늘은 정말 흥미진진한 주제로 여러분을 찾아왔어요. 바로 자바스크립트 WebGL을 이용한 3D 그래픽 프로그래밍에 대해 이야기해볼 거예요. 🎨🖥️

여러분, 혹시 웹 브라우저에서 멋진 3D 그래픽을 본 적이 있나요? 게임이나 인터랙티브한 웹사이트에서 말이죠. 그런 멋진 그래픽들은 대부분 WebGL이라는 기술을 사용해서 만들어진답니다. 오늘은 이 WebGL의 세계로 여러분을 초대하려고 해요!

우리의 여정은 마치 3D 세계를 탐험하는 것처럼 흥미진진할 거예요. 기본적인 개념부터 시작해서 복잡한 3D 모델링까지, 단계별로 천천히 알아갈 거예요. 마치 레고 블록을 하나씩 쌓아가는 것처럼 말이죠. 😊

그리고 여러분, 혹시 재능넷이라는 사이트를 아시나요? 이곳은 다양한 재능을 공유하고 거래하는 플랫폼인데요. WebGL 같은 기술을 배우고 나면, 여러분도 재능넷에서 자신만의 독특한 3D 그래픽 서비스를 제공할 수 있을지도 몰라요! 🌟

자, 그럼 이제 우리의 3D 그래픽 여행을 시작해볼까요? 안전벨트 꽉 매세요. 출발합니다! 🚗💨

1. WebGL이란 무엇인가요? 🤔

자, 먼저 WebGL이 뭔지 알아볼까요? WebGL은 Web Graphics Library의 약자예요. 쉽게 말해, 웹 브라우저에서 2D와 3D 그래픽을 그릴 수 있게 해주는 JavaScript API랍니다.

WebGL의 특징을 간단히 정리해볼게요:

  • 웹 브라우저에서 직접 실행 가능 🌐
  • 하드웨어 가속을 사용해 빠른 성능 제공 🚀
  • JavaScript로 제어 가능 💻
  • 크로스 플랫폼 지원 (데스크톱, 모바일 등) 📱💻

WebGL은 마치 우리가 그림을 그릴 때 사용하는 도구상자와 같아요. 이 도구상자 안에는 선을 그리는 연필, 색을 칠하는 붓, 그리고 3D 효과를 만드는 특별한 도구들이 들어있죠. 우리는 이 도구들을 이용해서 웹 브라우저라는 캔버스 위에 멋진 3D 그래픽을 그릴 수 있답니다!

🎨 WebGL의 마법: WebGL을 사용하면 웹 페이지에 단순한 3D 도형부터 복잡한 게임 그래픽까지 다양한 시각적 요소를 추가할 수 있어요. 마치 평면적인 종이에 입체감을 불어넣는 것과 같죠!

WebGL은 OpenGL ES 2.0을 기반으로 만들어졌어요. OpenGL ES는 모바일 기기나 임베디드 시스템에서 사용되는 3D 그래픽 API인데, WebGL은 이를 웹 환경에 맞게 최적화한 버전이라고 할 수 있죠.

여기서 잠깐! WebGL이 어떻게 동작하는지 간단히 설명해드릴게요:

  1. JavaScript 코드로 3D 장면을 설정해요.
  2. WebGL이 이 정보를 GPU(그래픽 처리 장치)로 전달해요.
  3. GPU가 빠르게 계산을 수행해요.
  4. 결과물이 화면에 표시돼요.

이 과정이 초당 수십 번 반복되면서 부드러운 3D 그래픽이 만들어지는 거죠. 마치 플립북 애니메이션처럼요! 📚➡️📽️

WebGL 동작 과정 JavaScript WebGL GPU

WebGL의 강력한 점은 바로 하드웨어 가속을 사용한다는 거예요. 이게 무슨 뜻이냐고요? 쉽게 설명해드릴게요.

우리가 집에서 요리를 할 때를 생각해봐요. 간단한 요리는 우리가 직접 칼로 재료를 다지고 손으로 반죽을 하죠. 하지만 복잡하고 시간이 오래 걸리는 요리를 할 때는 어떻게 하나요? 바로 믹서기나 반죽기 같은 도구를 사용하죠!

WebGL의 하드웨어 가속도 이와 비슷해요. 복잡한 3D 그래픽을 그릴 때, WebGL은 컴퓨터의 CPU(중앙처리장치) 대신 GPU(그래픽처리장치)를 사용해요. GPU는 그래픽 작업에 특화된 '요리사'같은 존재예요. 덕분에 아주 빠르고 효율적으로 3D 그래픽을 처리할 수 있답니다! 🍳➡️🥘

💡 재미있는 사실: WebGL이 없던 시절에는 웹에서 3D 그래픽을 구현하기 위해 플러그인(예: Flash)을 설치해야 했어요. WebGL 덕분에 이제는 추가 설치 없이 브라우저만으로 멋진 3D 그래픽을 즐길 수 있게 되었답니다!

자, 이제 WebGL이 무엇인지 대략적으로 이해가 되셨나요? 정말 흥미진진하죠? 🎢 이제 우리는 이 강력한 도구를 이용해서 웹 브라우저라는 캔버스 위에 우리만의 3D 세계를 그려나갈 거예요.

다음 섹션에서는 WebGL을 사용하기 위한 기본적인 설정 방법에 대해 알아볼 거예요. 마치 화가가 그림을 그리기 전에 캔버스와 물감을 준비하는 것처럼 말이죠. 여러분의 3D 그래픽 여행을 위한 준비, 함께 시작해볼까요? 🎨🖌️

2. WebGL 시작하기: 기본 설정 🛠️

자, 이제 우리의 3D 그래픽 여행을 위한 첫 발걸음을 내딛어볼 시간이에요! WebGL을 사용하기 위한 기본 설정을 함께 알아보겠습니다. 마치 화가가 그림을 그리기 전에 캔버스를 준비하는 것처럼, 우리도 WebGL을 사용하기 위한 '캔버스'를 준비해야 해요. 🎨

2.1 HTML 캔버스 생성하기

WebGL은 HTML5의 <canvas> 요소를 사용해요. 이 캔버스가 바로 우리의 3D 그래픽이 그려질 곳이에요. 먼저 HTML 파일에 캔버스를 추가해볼까요?

<canvas id="myCanvas" width="800" height="600"></canvas>

여기서 widthheight는 캔버스의 크기를 픽셀 단위로 지정해요. 여러분의 프로젝트에 맞게 조절하시면 됩니다. 😊

2.2 WebGL 컨텍스트 가져오기

캔버스를 만들었다면, 이제 이 캔버스에서 WebGL 컨텍스트를 가져와야 해요. 이 컨텍스트가 바로 우리가 3D 그래픽을 그리는 데 사용할 '붓'이 되는 거죠! 🖌️


const canvas = document.getElementById('myCanvas');
const gl = canvas.getContext('webgl');

if (!gl) {
    console.error('WebGL을 초기화하는 데 실패했습니다. 브라우저가 WebGL을 지원하지 않을 수 있습니다.');
}
  

이 코드는 다음과 같은 일을 해요:

  1. HTML에서 우리가 만든 캔버스 요소를 찾아요.
  2. 캔버스에서 WebGL 컨텍스트를 가져와요.
  3. 만약 WebGL을 사용할 수 없다면 (브라우저가 지원하지 않는 경우 등) 오류 메시지를 콘솔에 출력해요.

🌟 팁: 일부 오래된 브라우저에서는 'experimental-webgl'을 사용해야 할 수도 있어요. 이런 경우를 대비해 다음과 같이 코드를 작성할 수 있습니다:


const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
    

2.3 뷰포트 설정하기

WebGL 컨텍스트를 얻었다면, 이제 뷰포트를 설정해야 해요. 뷰포트는 우리의 3D 장면이 실제로 그려질 영역을 정의해요. 보통은 캔버스 전체 영역을 사용하죠.


gl.viewport(0, 0, canvas.width, canvas.height);
  

이 코드는 뷰포트를 캔버스의 왼쪽 상단 모서리(0, 0)에서 시작해 캔버스의 전체 너비와 높이를 사용하도록 설정해요.

2.4 캔버스 지우기

3D 그래픽을 그리기 전에, 먼저 캔버스를 깨끗이 지워야 해요. 마치 화가가 새 그림을 그리기 전에 캔버스를 하얗게 칠하는 것처럼요! 🧹


gl.clearColor(0.0, 0.0, 0.0, 1.0);  // 검은색으로 설정
gl.clear(gl.COLOR_BUFFER_BIT);
  

이 코드는 다음과 같은 일을 해요:

  • gl.clearColor()는 캔버스를 지울 때 사용할 색상을 설정해요. 여기서는 검은색(0, 0, 0)으로 설정했어요.
  • gl.clear()는 실제로 캔버스를 지우는 역할을 해요.

색상값은 0.0에서 1.0 사이의 값을 사용해요. (0, 0, 0)은 검은색, (1, 1, 1)은 흰색이에요. 네 번째 값은 투명도를 나타내는데, 1.0은 완전 불투명을 의미해요.

WebGL 캔버스 설정 과정 HTML Canvas WebGL Context Cleared Canvas

자, 이제 우리의 WebGL '캔버스'가 준비되었어요! 🎉 이 기본 설정은 모든 WebGL 프로젝트의 시작점이 됩니다. 마치 화가가 캔버스를 설치하고 팔레트를 준비하는 것처럼, 우리도 이제 3D 그래픽을 그릴 준비가 된 거예요.

🚨 주의사항: WebGL은 매우 강력하지만, 동시에 복잡할 수 있어요. 처음에는 어려워 보일 수 있지만, 차근차근 배워나가면 정말 멋진 것들을 만들 수 있답니다. 마치 재능넷에서 새로운 기술을 배우는 것처럼, 꾸준히 연습하면 놀라운 결과를 얻을 수 있어요! 💪

다음 섹션에서는 이 캔버스 위에 실제로 무언가를 그려보는 방법에 대해 알아볼 거예요. 기대되지 않나요? 우리의 첫 번째 3D 도형을 그리는 순간, 여러분은 마치 피카소가 된 것 같은 기분을 느낄 거예요! 🎨✨

자, 이제 우리의 3D 그래픽 여행이 본격적으로 시작됩니다. 다음 정거장에서 만나요! 🚂💨

3. WebGL에서 첫 번째 도형 그리기 🔺

자, 이제 우리의 WebGL 캔버스가 준비되었으니 실제로 무언가를 그려볼 차례예요! 🎨 오늘은 가장 기본적인 도형 중 하나인 삼각형을 그려볼 거예요. 왜 삼각형일까요? 그 이유는 잠시 후에 설명드릴게요. 😉

3.1 버텍스 데이터 준비하기

3D 그래픽에서 모든 것은 버텍스(정점)로 이루어져 있어요. 버텍스는 3D 공간상의 한 점을 나타내죠. 우리의 삼각형은 세 개의 버텍스로 이루어질 거예요.


const vertices = [
    -0.5, -0.5,  // 왼쪽 아래 점
     0.5, -0.5,  // 오른쪽 아래 점
     0.0,  0.5   // 위쪽 중앙 점
];
  

이 숫자들이 무엇을 의미하는지 궁금하시죠? WebGL에서는 캔버스의 중앙이 (0, 0)이에요. x축과 y축은 -1에서 1 사이의 값을 가져요. 따라서 (-0.5, -0.5)는 화면의 왼쪽 아래 부분을 나타내는 거죠.

WebGL 좌표계와 삼각형 x y 0,0 -0.5,-0.5 0.5,-0.5 0,0.5

3.2 버텍스 버퍼 생성하기

이제 이 버텍스 데이터를 GPU로 보내야 해요. 이를 위해 버텍스 버퍼를 사용해요. 버퍼는 데이터를 저장하는 공간이라고 생각하면 돼요.


const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
  

이 코드가 하는 일을 단계별로 설명해드릴게요:

  1. gl.createBuffer(): 새로운 버퍼를 생성해요.
  2. gl.bindBuffer(): 이 버퍼를 WebGL의 현재 버퍼로 설정해요.
  3. gl.bufferData(): 우리의 버텍스 데이터를 이 버퍼에 넣어요.

Float32Array는 JavaScript의 특별한 배열 타입이에요. WebGL이 이해할 수 있는 형식으로 데이터를 변환해주죠.

3.3 셰이더 작성하기

이제 가장 중요한 부분이에요! 셰이더는 GPU에게 우리가 어떤 그림을 그리고 싶은지 알려주는 특별한 프로그램이에요. 두 종류의 셰이더가 필요해요: 버텍스 셰이더와 프래그먼트 셰이더.

버텍스 셰이더


const vertexShaderSource = `
    attribute vec2 a_position;
    void main() {
        gl_Position = vec4(a_position, 0.0, 1.0);
    }
`;
  

이 셰이더는 각 버텍스의 위치를 받아서 화면상의 위치로 변환해요.

프래그먼트 셰이더


const fragmentShaderSource = `
    precision mediump float;
    void main() {
        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
    }
`;
  

이 셰이더는 각 픽셀의 색상을 결정해요. 여기서는 빨간색(1.0, 0.0, 0.0)으로 설정했어요.

3.4 셰이더 컴파일 및 프로그램 생성

이제 이 셰이더들을 컴파일하고 WebGL 프로그램을 만들어야 해요.


function createShader(gl, type, source) {
    const shader = gl.createShader(type);
    gl.shaderSource(shader, source);
    gl.compileShader(shader);
    return shader;
}

const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);

const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
  

이 과정은 마치 요리 레시피를 준비하는 것과 비슷해요. 셰이더는 재료, 프로그램은 완성된 요리라고 생각하면 돼요! 🍳

3.5 버텍스 데이터 연결하기

이제 우리의 버텍스 데이터를 셰이더와 연결해야 해요. 이 과정은 셰이더에게 "여기 있는 데이터를 사용해!"라고 말하는 것과 같아요.


gl.useProgram(program);

const positionAttributeLocation = gl.getAttribLocation(program, "a_position");
gl.enableVertexAttribArray(positionAttributeLocation);

gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
  

이 코드는 다음과 같은 일을 해요:

  1. 우리가 만든 프로그램을 사용하도록 WebGL에 알려줘요.
  2. 셰이더에서 'a_position' 속성의 위치를 찾아요.
  3. 이 속성을 활성화해요.
  4. 버텍스 버퍼를 이 속성에 연결해요.

3.6 드디어 그리기!

자, 이제 모든 준비가 끝났어요! 🎉 드디어 우리의 삼각형을 그릴 차례예요.


gl.drawArrays(gl.TRIANGLES, 0, 3);
  

이 한 줄의 코드가 모든 마법을 부려요! 'gl.TRIANGLES'는 우리가 삼각형을 그리고 싶다고 WebGL에 알려주는 거예요. '0'은 시작 인덱스, '3'은 그릴 버텍스의 수예요.

🎨 완성된 그림: 이제 여러분의 캔버스에 빨간색 삼각형이 그려졌을 거예요! 축하드려요, 여러분은 방금 첫 번째 WebGL 그래픽을 만들었어요! 🎉🎊

3.7 왜 삼각형인가요?

처음에 약속드렸던 대로, 왜 우리가 삼각형을 그렸는지 설명해드릴게요. 3D 그래픽에서 삼각형은 정말 특별한 존재예요. 그 이유는:

  • 삼각형은 가장 단순한 2D 도형이에요. 세 점만 있으면 만들 수 있죠.
  • 어떤 복잡한 3D 모델도 결국은 아주 많은 삼각형으로 이루어져 있어요.
  • 삼각형은 항상 평면이에요. 이는 그래픽 계산을 훨씬 쉽게 만들어줘요.
삼각형으로 이루어진 복잡한 3D 모델 복잡한 3D 모델도 결국 여러 개의 삼각형으로 구성됩니다

그래서 WebGL에서 첫 번째 도형으로 삼각형을 그리는 것은 아주 중요한 첫 걸음이에요. 이것이 바로 모든 복잡한 3D 그래픽의 기초가 되는 거죠! 🏗️

💡 재미있는 사실: 비디오 게임이나 3D 애니메이션 영화에서 보는 모든 캐릭터와 물체들도 사실은 수많은 작은 삼각형으로 이루어져 있어요. 다음에 3D 게임을 할 때 이 점을 기억해보세요!

자, 이제 여러분은 WebGL의 기본을 배웠어요. 삼각형 하나를 그리는 것에서 시작해서, 앞으로 여러분은 더 복잡하고 아름다운 3D 그래픽을 만들 수 있을 거예요. 마치 재능넷에서 새로운 기술을 배우고 발전시키는 것처럼, WebGL도 연습할수록 더 멋진 것들을 만들 수 있답니다! 🌟

다음 섹션에서는 우리의 삼각형에 움직임을 줘볼 거예요. 정적인 그림에서 동적인 애니메이션으로 발전하는 거죠! 기대되지 않나요? 우리의 3D 그래픽 여행은 계속됩니다! 🚀✨

4. 애니메이션: 삼각형에 생명 불어넣기 🎬

자, 이제 우리의 삼각형이 화면에 나타났어요. 하지만 그냥 가만히 있는 삼각형은 조금 심심하죠? 이번에는 우리의 삼각형에 움직임을 줘서 생동감 있는 애니메이션을 만들어볼 거예요! 🕺💃

4.1 애니메이션 루프 만들기

WebGL에서 애니메이션을 만들려면, 우리는 애니메이션 루프라는 것을 사용해야 해요. 이것은 계속해서 화면을 다시 그리는 과정이에요. 마치 플립북을 빠르게 넘기는 것과 같죠!


function drawScene() {
    // 여기에 그리기 코드가 들어갑니다
    
    requestAnimationFrame(drawScene);
}

drawScene();
  

requestAnimationFrame은 브라우저에게 "다음 프레임을 그릴 준비가 되면 이 함수를 다시 호출해줘"라고 말하는 거예요. 이렇게 하면 부드러운 애니메이션을 만들 수 있어요.

4.2 회전하는 삼각형 만들기

이제 우리의 삼각형을 회전시켜볼까요? 이를 위해 우리는 행렬 변환이라는 것을 사용할 거예요. 걱정 마세요, 어려운 수학은 없어요! 😉


// 버텍스 셰이더를 수정합니다
const vertexShaderSource = `
    attribute vec2 a_position;
    uniform mat3 u_matrix;
    void main() {
        vec3 position = u_matrix * vec3(a_position, 1);
        gl_Position = vec4(position.xy, 0, 1);
    }
`;

// JavaScript에서 회전 행렬을 만듭니다
function makeRotationMatrix(angleInRadians) {
    const c = Math.cos(angleInRadians);
    const s = Math.sin(angleInRadians);
    return [
        c, -s, 0,
        s, c, 0,
        0, 0, 1
    ];
}

// 애니메이션 루프 안에서 회전을 적용합니다
let rotation = 0;
function drawScene() {
    rotation += 0.01;
    const matrix = makeRotationMatrix(rotation);
    
    // 행렬을 셰이더에 전달합니다
    const matrixLocation = gl.getUniformLocation(program, "u_matrix");
    gl.uniformMatrix3fv(matrixLocation, false, matrix);
    
    // 삼각형을 그립니다
    gl.drawArrays(gl.TRIANGLES, 0, 3);
    
    requestAnimationFrame(drawScene);
}
  

이 코드가 하는 일을 간단히 설명하자면:

  1. 버텍스 셰이더에 회전 행렬을 적용할 수 있도록 수정했어요.
  2. JavaScript에서 회전 행렬을 만드는 함수를 작성했어요.
  3. 매 프레임마다 회전 각도를 조금씩 증가시키고, 새로운 회전 행렬을 만들어요.
  4. 이 행렬을 셰이더에 전달하고 삼각형을 그려요.
회전하는 삼각형 회전하는 삼각형

와! 이제 우리의 삼각형이 빙글빙글 돌아가고 있어요! 🌀 정말 멋지지 않나요?

4.3 색상 변화 추가하기

회전하는 삼각형도 멋지지만, 여기에 색상 변화까지 추가하면 어떨까요? 이번에는 프래그먼트 셰이더를 수정해볼게요.


const fragmentShaderSource = `
    precision mediump float;
    uniform vec3 u_color;
    void main() {
        gl_FragColor = vec4(u_color, 1.0);
    }
`;

// JavaScript에서 색상을 변경합니다
function hslToRgb(h, s, l) {
    // HSL to RGB 변환 로직
    // (간단히 하기 위해 구현은 생략했습니다)
}

let hue = 0;
function drawScene() {
    rotation += 0.01;
    hue += 0.5;
    if (hue > 360) hue -= 360;
    
    const matrix = makeRotationMatrix(rotation);
    const color = hslToRgb(hue, 1, 0.5);
    
    // 행렬과 색상을 셰이더에 전달합니다
    const matrixLocation = gl.getUniformLocation(program, "u_matrix");
    gl.uniformMatrix3fv(matrixLocation, false, matrix);
    
    const colorLocation = gl.getUniformLocation(program, "u_color");
    gl.uniform3fv(colorLocation, color);
    
    // 삼각형을 그립니다
    gl.drawArrays(gl.TRIANGLES, 0, 3);
    
    requestAnimationFrame(drawScene);
}
  

이제 우리의 삼각형은 회전하면서 동시에 무지개 색으로 변화할 거예요! 🌈

🎨 창의성 발휘하기: 이제 기본적인 애니메이션 방법을 배웠으니, 여러분만의 독특한 애니메이션을 만들어보는 건 어떨까요? 크기를 변화시키거나, 여러 개의 도형을 사용하거나, 다양한 효과를 추가해볼 수 있어요. 가능성은 무한해요!

4.4 성능 최적화 팁

애니메이션을 만들 때는 성능도 고려해야 해요. 여기 몇 가지 팁을 드릴게요:

  • 가능한 한 GPU에서 많은 작업을 처리하도록 하세요. 셰이더를 효율적으로 작성하는 것이 중요해요.
  • 불필요한 상태 변경을 피하세요. 예를 들어, 매 프레임마다 같은 버퍼를 바인딩하는 것은 피해야 해요.
  • 복잡한 계산은 가능한 한 JavaScript에서 미리 해두고, 결과만 셰이더에 전달하세요.

이러한 최적화는 여러분의 애니메이션을 더욱 부드럽고 효율적으로 만들어줄 거예요!

💡 실전 팁: WebGL 애니메이션을 만들 때는 항상 다양한 기기에서 테스트해보세요. 고성능 컴퓨터에서는 잘 동작하더라도 모바일 기기에서는 느릴 수 있어요. 재능넷에서 프로젝트를 공유할 때도 이 점을 고려하면 좋겠죠?

자, 이제 여러분은 WebGL로 멋진 애니메이션을 만드는 방법을 배웠어요! 🎉 이것은 시작에 불과해요. 이 기술을 바탕으로 더 복잡하고 아름다운 3D 그래픽을 만들 수 있을 거예요. 마치 화가가 캔버스 위에 생명을 불어넣듯이, 여러분도 코드로 화면에 생동감을 불어넣을 수 있게 된 거죠! 🖼️✨

다음 섹션에서는 텍스처를 사용해서 우리의 3D 그래픽을 더욱 풍성하게 만드는 방법에 대해 알아볼 거예요. 기대되지 않나요? 우리의 WebGL 여행은 계속됩니다! 🚀🌠

5. 텍스처: 3D 그래픽에 생동감 불어넣기 🖼️

지금까지 우리는 단색의 도형을 다뤄왔어요. 하지만 실제 세계의 물체들은 훨씬 더 복잡하고 다양한 표면을 가지고 있죠. 이번에는 텍스처를 사용해서 우리의 3D 그래픽에 더욱 풍부한 디테일을 추가해볼 거예요! 🎨

5.1 텍스처란 무엇인가요?

텍스처는 간단히 말해 3D 객체의 표면에 입히는 2D 이미지예요. 마치 선물 상자를 포장지로 감싸는 것처럼, 3D 모델을 이미지로 감싸는 거죠. 이를 통해 우리는 복잡한 표면 디테일을 간단하게 표현할 수 있어요.

🌟 텍스처의 마법: 텍스처를 사용하면 단순한 3D 모델도 놀랍도록 현실적으로 보이게 할 수 있어요. 예를 들어, 평평한 사각형에 벽돌 텍스처를 입히면 순식간에 벽돌 벽이 되는 거죠!

5.2 텍스처 로딩하기

먼저 텍스처로 사용할 이미지를 로드해야 해요. JavaScript에서 이렇게 할 수 있어요:


function loadTexture(url) {
    const texture = gl.createTexture();
    const image = new Image();
    
    image.onload = function() {
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
        gl.generateMipmap(gl.TEXTURE_2D);
    };
    
    image.src = url;
    return texture;
}

const myTexture = loadTexture('path/to/your/texture.png');
  

이 코드는 다음과 같은 일을 해요:

  1. 새로운 WebGL 텍스처 객체를 만들어요.
  2. 이미지를 로드해요.
  3. 이미지 로딩이 완료되면, 텍스처 객체에 이미지 데이터를 넣어요.
  4. 밉맵을 생성해요 (이는 텍스처를 다양한 크기로 효율적으로 렌더링하는 데 도움을 줘요).

5.3 셰이더 수정하기

텍스처를 사용하려면 우리의 셰이더도 수정해야 해요. 버텍스 셰이더에 텍스처 좌표를 추가하고, 프래그먼트 셰이더에서 텍스처 색상을 사용할 거예요.


// 버텍스 셰이더
const vertexShaderSource = `
    attribute vec2 a_position;
    attribute vec2 a_texCoord;
    varying vec2 v_texCoord;
    uniform mat3 u_matrix;
    
    void main() {
        vec3 position = u_matrix * vec3(a_position, 1);
        gl_Position = vec4(position.xy, 0, 1);
        v_texCoord = a_texCoord;
    }
`;

// 프래그먼트 셰이더
const fragmentShaderSource = `
    precision mediump float;
    varying vec2 v_texCoord;
    uniform sampler2D u_texture;
    
    void main() {
        gl_FragColor = texture2D(u_texture, v_texCoord);
    }
`;
  

여기서 v_texCoord는 버텍스 셰이더에서 프래그먼트 셰이더로 텍스처 좌표를 전달하는 변수예요. texture2D 함수는 텍스처에서 색상을 가져오는 역할을 해요.

5.4 텍스처 좌표 설정하기

이제 우리의 도형에 텍스처 좌표를 추가해야 해요. 텍스처 좌표는 텍스처 이미지의 어느 부분을 도형의 각 점에 매핑할지 결정해요.


const vertices = [
    // x, y, texX, texY
    -0.5, -0.5,  0.0, 0.0,
     0.5, -0.5,  1.0, 0.0,
     0.0,  0.5,  0.5, 1.0
];

// 버텍스 데이터 설정 (위치와 텍스처 좌표)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);

const positionAttributeLocation = gl.getAttribLocation(program, "a_position");
gl.enableVertexAttribArray(positionAttributeLocation);
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 16, 0);

const texCoordAttributeLocation = gl.getAttribLocation(program, "a_texCoord");
gl.enableVertexAttribArray(texCoordAttributeLocation);
gl.vertexAttribPointer(texCoordAttributeLocation, 2, gl.FLOAT, false, 16, 8);
  
텍스처 매핑 0,0 1,0 0.5,1 텍스처 매핑 예시

5.5 텍스처 그리기

마지막으로, 우리의 그리기 함수에서 텍스처를 활성화하고 사용해야 해요.


function drawScene() {
    // ... (이전 코드)

    gl.bindTexture(gl.TEXTURE_2D, myTexture);
    gl.uniform1i(gl.getUniformLocation(program, "u_texture"), 0);

    gl.drawArrays(gl.TRIANGLES, 0, 3);

    requestAnimationFrame(drawScene);
}
  

와! 이제 우리의 삼각형에 텍스처가 입혀졌어요! 🎉

🚀 창의력 발휘하기: 이제 기본적인 텍스처 매핑 방법을 배웠으니, 다양한 실험을 해볼 수 있어요. 여러 개의 텍스처를 사용하거나, 텍스처 좌표를 애니메이션화하거나, 프래그먼트 셰이더에서 텍스처를 조작해볼 수 있어요. 가능성은 무한해요!

5.6 텍스처 사용 시 주의사항

텍스처를 사용할 때 몇 가지 주의할 점이 있어요:

  • 텍스처 이미지의 크기는 가급적 2의 거듭제곱(예: 256x256, 512x512)을 사용하세요. 이는 GPU가 효율적으로 처리할 수 있는 크기예요.
  • 큰 텍스처는 메모리를 많이 사용하고 로딩 시간이 길어질 수 있어요. 적절한 크기를 선택하세요.
  • 텍스처 필터링과 래핑 모드를 적절히 설정하세요. 이는 텍스처가 확대/축소되거나 반복될 때의 동작을 결정해요.

텍스처는 3D 그래픽에 생동감을 불어넣는 강력한 도구예요. 재능넷에서 여러분의 3D 프로젝트를 공유할 때, 적절한 텍스처 사용으로 더욱 멋진 결과물을 만들 수 있을 거예요! 🌟

이제 여러분은 WebGL에서 텍스처를 사용하는 기 본적인 방법을 배웠어요. 이 지식을 바탕으로 더욱 현실적이고 아름다운 3D 그래픽을 만들 수 있을 거예요. 다음 섹션에서는 조명과 그림자 효과를 추가하는 방법에 대해 알아볼 거예요. 준비되셨나요? 우리의 3D 세계가 점점 더 풍성해지고 있어요! 🌈✨

6. 조명과 그림자: 3D 그래픽에 깊이 더하기 💡

지금까지 우리는 기본적인 도형과 텍스처를 다뤄봤어요. 하지만 실제 세계의 물체들은 빛에 반응하고 그림자를 만들죠. 이번에는 조명과 그림자 효과를 추가해서 우리의 3D 그래픽을 한층 더 현실적으로 만들어볼 거예요! 🌞🌚

6.1 기본적인 조명 모델 이해하기

3D 그래픽에서 가장 기본적인 조명 모델은 퐁 반사 모델(Phong reflection model)이에요. 이 모델은 세 가지 요소로 구성되어 있죠:

  • 주변광(Ambient): 모든 방향에서 균일하게 오는 빛
  • 확산광(Diffuse): 물체의 표면에서 모든 방향으로 균일하게 반사되는 빛
  • 반사광(Specular): 거울처럼 한 방향으로 강하게 반사되는 빛
퐁 반사 모델 광원 반사광 확산광 주변광: 전체적으로 은은한 빛

6.2 버텍스 셰이더 수정하기

조명 효과를 구현하기 위해 먼저 버텍스 셰이더를 수정해야 해요. 우리는 각 버텍스의 위치뿐만 아니라 법선(normal) 벡터도 필요해요. 법선은 표면에 수직인 벡터로, 빛이 어떻게 반사될지 계산하는 데 사용돼요.


const vertexShaderSource = `
    attribute vec3 a_position;
    attribute vec3 a_normal;
    attribute vec2 a_texCoord;

    uniform mat4 u_modelViewMatrix;
    uniform mat4 u_projectionMatrix;
    uniform mat4 u_normalMatrix;

    varying vec3 v_normal;
    varying vec2 v_texCoord;
    varying vec3 v_position;

    void main() {
        vec4 position = u_modelViewMatrix * vec4(a_position, 1.0);
        v_position = position.xyz;
        gl_Position = u_projectionMatrix * position;
        v_normal = (u_normalMatrix * vec4(a_normal, 0.0)).xyz;
        v_texCoord = a_texCoord;
    }
`;
  

이 셰이더는 버텍스의 위치, 법선, 텍스처 좌표를 받아서 처리해요. 또한 모델뷰 행렬, 투영 행렬, 법선 행렬을 사용해 적절한 변환을 수행하죠.

6.3 프래그먼트 셰이더 수정하기

이제 프래그먼트 셰이더에서 실제 조명 계산을 수행할 거예요.


const fragmentShaderSource = `
    precision mediump float;

    varying vec3 v_normal;
    varying vec2 v_texCoord;
    varying vec3 v_position;

    uniform vec3 u_lightPosition;
    uniform vec3 u_viewPosition;
    uniform sampler2D u_texture;

    void main() {
        vec3 normal = normalize(v_normal);
        vec3 lightDir = normalize(u_lightPosition - v_position);
        vec3 viewDir = normalize(u_viewPosition - v_position);
        vec3 reflectDir = reflect(-lightDir, normal);

        // 주변광
        float ambientStrength = 0.1;
        vec3 ambient = ambientStrength * vec3(1.0, 1.0, 1.0);

        // 확산광
        float diff = max(dot(normal, lightDir), 0.0);
        vec3 diffuse = diff * vec3(1.0, 1.0, 1.0);

        // 반사광
        float specularStrength = 0.5;
        float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32.0);
        vec3 specular = specularStrength * spec * vec3(1.0, 1.0, 1.0);

        vec3 result = (ambient + diffuse + specular) * texture2D(u_texture, v_texCoord).rgb;
        gl_FragColor = vec4(result, 1.0);
    }
`;
  

이 셰이더는 퐁 반사 모델을 구현하고 있어요. 주변광, 확산광, 반사광을 계산하고 이를 텍스처 색상과 결합해 최종 색상을 만들어내죠.

6.4 JavaScript에서 조명 설정하기

이제 JavaScript에서 필요한 uniform 변수들을 설정해줘야 해요.


// 조명 위치 설정
const lightPosition = [5, 5, 5];
gl.uniform3fv(gl.getUniformLocation(program, "u_lightPosition"), lightPosition);

// 카메라 위치 설정
const viewPosition = [0, 0, 5];
gl.uniform3fv(gl.getUniformLocation(program, "u_viewPosition"), viewPosition);

// 모델뷰 행렬, 투영 행렬, 법선 행렬 설정
// (이 부분은 여러분의 카메라 설정에 따라 달라질 수 있어요)
  

💡 조명의 마법: 조명 효과를 조절하면서 다양한 분위기를 연출해볼 수 있어요. 예를 들어, 주변광을 강하게 하면 부드러운 느낌을, 반사광을 강하게 하면 금속성 느낌을 줄 수 있죠. 여러분만의 독특한 조명 효과를 실험해보세요!

6.5 그림자 추가하기

그림자를 추가하면 3D 장면이 더욱 현실감 있어 보여요. 기본적인 그림자 매핑(Shadow Mapping) 기법을 간단히 설명해드릴게요:

  1. 광원의 시점에서 장면을 렌더링하고 깊이 정보를 텍스처에 저장해요. (그림자 맵)
  2. 실제 장면을 렌더링할 때, 각 픽셀의 위치를 광원 시점으로 변환해요.
  3. 변환된 위치의 깊이를 그림자 맵의 깊이와 비교해요.
  4. 만약 현재 픽셀의 깊이가 더 크다면, 그 픽셀은 그림자 안에 있는 거예요.

그림자 매핑은 꽤 복잡한 주제이므로, 이 기본 아이디어를 이해하는 것으로 시작해보세요. 실제 구현은 추가적인 학습이 필요할 거예요.

그림자 매핑 원리 광원 물체 그림자 그림자 매핑 원리

6.6 성능 고려사항

조명과 그림자 효과는 멋지지만, 계산 비용이 높을 수 있어요. 몇 가지 팁을 드릴게요:

  • 복잡한 조명 계산은 가능한 한 버텍스 셰이더에서 수행하고 결과를 프래그먼트 셰이더로 보내세요. (퐁 셰이딩 대신 구로 셰이딩 사용)
  • 너무 많은 광원을 사용하지 마세요. 각 광원은 추가적인 계산을 필요로 해요.
  • 그림자 맵의 해상도를 적절히 조절하세요. 높은 해상도는 더 선명한 그림자를 만들지만, 성능에 영향을 줄 수 있어요.

🚀 실전 응용: 이제 기본적인 조명과 그림자 기법을 배웠으니, 재능넷에서 여러분의 3D 프로젝트를 한 단계 업그레이드할 수 있을 거예요. 예를 들어, 인테리어 디자인 시각화나 게임 캐릭터 모델링에 이 기술을 적용해보는 건 어떨까요? 현실감 있는 조명은 여러분의 작품을 돋보이게 할 거예요! ✨

와! 이제 우리는 WebGL로 정말 멋진 3D 그래픽을 만들 수 있게 되었어요. 조명과 그림자 효과로 우리의 3D 세계가 훨씬 더 생동감 넘치고 현실적으로 변했죠. 🌟

다음 섹션에서는 사용자 상호작용을 추가하는 방법에 대해 알아볼 거예요. 마우스나 터치로 3D 객체를 조작하는 방법을 배워볼 거예요. 기대되지 않나요? 우리의 3D 그래픽 여행은 계속됩니다! 🚀🌠

7. 사용자 상호작용: 3D 그래픽에 생명 불어넣기 🖱️👆

지금까지 우리는 멋진 3D 그래픽을 만들어왔어요. 하지만 사용자가 이 3D 세계와 상호작용할 수 없다면 뭔가 부족하지 않을까요? 이번 섹션에서는 마우스나 터치 입력을 통해 우리의 3D 객체를 조작하는 방법을 배워볼 거예요. 준비되셨나요? 😃

7.1 기본적인 마우스 이벤트 처리

먼저 가장 기본적인 마우스 이벤트부터 처리해볼게요. 클릭, 드래그, 휠 스크롤 등의 이벤트를 캐치해서 3D 장면을 조작할 수 있어요.


canvas.addEventListener('mousedown', onMouseDown);
canvas.addEventListener('mousemove', onMouseMove);
canvas.addEventListener('mouseup', onMouseUp);
canvas.addEventListener('wheel', onWheel);

function onMouseDown(event) {
    // 마우스 버튼을 눌렀을 때의 동작
}

function onMouseMove(event) {
    // 마우스를 움직일 때의 동작
}

function onMouseUp(event) {
    // 마우스 버튼을 뗐을 때의 동작
}

function onWheel(event) {
    // 마우스 휠을 굴렸을 때의 동작
}
  

7.2 객체 회전하기

마우스 드래그로 3D 객체를 회전시켜볼까요? 이를 위해 마우스의 움직임을 회전 각도로 변환해야 해요.


let isDragging = false;
let previousMousePosition = { x: 0, y: 0 };
let rotation = { x: 0, y: 0 };

function onMouseDown(event) {
    isDragging = true;
    previousMousePosition = {
        x: event.clientX,
        y: event.clientY
    };
}

function onMouseMove(event) {
    if (!isDragging) return;

    const deltaMove = {
        x: event.clientX - previousMousePosition.x,
        y: event.clientY - previousMousePosition.y
    };

    rotation.x += deltaMove.y * 0.01;
    rotation.y += deltaMove.x * 0.01;

    previousMousePosition = {
        x: event.clientX,
        y: event.clientY
    };

    updateModelViewMatrix();
}

function onMouseUp(event) {
    isDragging = false;
}

function updateModelViewMatrix() {
    // rotation 값을 이용해 모델뷰 행렬 업데이트
    // 그리고 uniform 변수로 셰이더에 전달
}
  
마우스 드래그로 객체 회전 회전 마우스 드래그

7.3 객체 확대/축소하기

마우스 휠을 이용해 3D 객체를 확대하거나 축소해볼까요?


let scale = 1;

function onWheel(event) {
    event.preventDefault();
    scale += event.deltaY * -0.001;
    
    // 최소/최대 스케일 제한
    scale = Math.min(Math.max(0.1, scale), 3);
    
    updateModelViewMatrix();
}
  

7.4 객체 선택하기

3D 공간에서 객체를 선택하는 것은 조금 더 복잡해요. 레이캐스팅(Raycasting) 기법을 사용해야 합니다.


function onMouseDown(event) {
    const ray = calculateRayFromMouseClick(event);
    const selectedObject = findIntersectedObject(ray);
    if (selectedObject) {
        // 선택된 객체에 대한 처리
    }
}

function calculateRayFromMouseClick(event) {
    // 마우스 클릭 위치를 3D 공간의 레이로 변환
}

function findIntersectedObject(ray) {
    // 레이와 3D 객체들의 교차 검사
}
  

🎮 인터랙티브의 힘: 사용자 상호작용을 추가함으로써, 여러분의 3D 프로젝트는 단순한 시각적 요소를 넘어 진정한 인터랙티브 경험이 될 수 있어요. 재능넷에서 이런 인터랙티브 3D 모델을 공유한다면, 사용자들은 훨씬 더 깊이 있게 여러분의 작품을 경험할 수 있을 거예요!

7.5 모바일 터치 지원

모바일 기기에서도 동작하도록 터치 이벤트를 지원해야 해요.


canvas.addEventListener('touchstart', onTouchStart);
canvas.addEventListener('touchmove', onTouchMove);
canvas.addEventListener('touchend', onTouchEnd);

function onTouchStart(event) {
    // 터치 시작 시 동작
}

function onTouchMove(event) {
    // 터치 이동 시 동작
}

function onTouchEnd(event) {
    // 터치 종료 시 동작
}
  

7.6 키보드 입력 처리

키보드 입력으로도 3D 장면을 제어할 수 있어요.


document.addEventListener('keydown', onKeyDown);

function onKeyDown(event) {
    switch(event.key) {
        case 'ArrowUp':
            // 위쪽 화살표 키 처리
            break;
        case 'ArrowDown':
            // 아래쪽 화살표 키 처리
            break;
        // 기타 키 처리
    }
}
  

7.7 성능과 사용성 고려사항

사용자 상호작용을 추가할 때 고려해야 할 몇 가지 사항이 있어요:

  • 이벤트 처리는 가능한 한 효율적으로 해야 해요. 불필요한 계산은 피하세요.
  • 부드러운 애니메이션을 위해 requestAnimationFrame을 사용하세요.
  • 모바일 기기의 터치 이벤트와 데스크톱의 마우스 이벤트를 모두 고려하세요.
  • 사용자 경험(UX)을 항상 염두에 두세요. 직관적이고 반응이 빠른 인터페이스를 만드세요.

와! 이제 우리의 3D 그래픽은 정말 살아있는 것 같아요! 사용자가 직접 조작하고 탐험할 수 있는 인터랙티브한 3D 세계를 만들었어요. 🌍✨

다음 섹션에서는 지금까지 배운 모든 것을 종합해서 하나의 완성된 WebGL 프로젝트를 만들어볼 거예요. 여러분만의 독특한 3D 월드를 만들 준비가 되셨나요? 우리의 WebGL 여행은 절정을 향해 달려가고 있어요! 🚀🌠

8. 프로젝트 완성: 나만의 3D 월드 만들기 🌍🏗️

드디어 우리의 WebGL 여행이 절정에 달했어요! 지금까지 배운 모든 것을 종합해서 하나의 멋진 3D 월드를 만들어볼 거예요. 이 프로젝트를 통해 여러분은 실제로 동작하는 WebGL 애플리케이션을 만들게 될 거예요. 준비되셨나요? 시작해볼까요! 🚀

8.1 프로젝트 개요: 인터랙티브 3D 행성계

우리가 만들 프로젝트는 인터랙티브 3D 행성계예요. 사용자는 마우스로 행성계를 회전하고 확대/축소할 수 있으며, 각 행성을 클릭하면 해당 행성에 대한 정보를 볼 수 있어요.

8.2 프로젝트 구조 설정

먼저 프로젝트의 기본 구조를 설정해볼게요.


// HTML
<canvas id="glcanvas" width="800" height="600"></canvas>
<div id="info"></div>

// JavaScript
const canvas = document.getElementById('glcanvas');
const gl = canvas.getContext('webgl');

// 셰이더 프로그램 생성
const program = createShaderProgram(gl, vertexShaderSource, fragmentShaderSource);

// 행성 데이터
const planets = [
    { name: '태양', radius: 0.2, distance: 0, color: [1.0, 0.7, 0.0], rotationSpeed: 0.01 },
    { name: '수성', radius: 0.03, distance: 0.3, color: [0.7, 0.7, 0.7], rotationSpeed: 0.02 },
    { name: '금성', radius: 0.05, distance: 0.5, color: [1.0, 0.9, 0.5], rotationSpeed: 0.015 },
    { name: '지구', radius: 0.05, distance: 0.7, color: [0.0, 0.5, 1.0], rotationSpeed: 0.01 },
    // 다른 행성들 추가...
];

// 버퍼 생성 및 데이터 로드
const { positionBuffer, colorBuffer } = createBuffers(gl, planets);

// 렌더링 루프
function render() {
    updatePlanetPositions();
    drawScene();
    requestAnimationFrame(render);
}

render();
  

8.3 셰이더 작성

이제 우리의 행성을 그리기 위한 셰이더를 작성해볼게요.


const vertexShaderSource = `
    attribute vec3 a_position;
    attribute vec3 a_color;
    uniform mat4 u_modelViewMatrix;
    uniform mat4 u_projectionMatrix;
    varying vec3 v_color;
    void main() {
        gl_Position = u_projectionMatrix * u_modelViewMatrix * vec4(a_position, 1.0);
        v_color = a_color;
    }
`;

const fragmentShaderSource = `
    precision mediump float;
    varying vec3 v_color;
    void main() {
        gl_FragColor = vec4(v_color, 1.0);
    }
`;
  

8.4 행성 그리기

각 행성을 그리는 함수를 만들어볼게요.


function drawPlanet(planet, time) {  
    const { radius, distance, color, rotationSpeed } = planet;
    
    // 행성의 위치 계산
    const angle = time * rotationSpeed;
    const x = Math.cos(angle) * distance;
    const y = Math.sin(angle) * distance;
    
    // 모델 뷰 행렬 생성
    const modelViewMatrix = mat4.create();
    mat4.translate(modelViewMatrix, modelViewMatrix, [x, y, 0]);
    mat4.scale(modelViewMatrix, modelViewMatrix, [radius, radius, radius]);
    
    // 유니폼 변수 설정
    gl.uniformMatrix4fv(u_modelViewMatrixLocation, false, modelViewMatrix);
    gl.uniform3fv(u_colorLocation, color);
    
    // 구체 그리기
    gl.drawArrays(gl.TRIANGLES, 0, sphereVertexCount);
}

function drawScene() {
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    
    const time = performance.now() * 0.001;  // 시간을 초 단위로 변환
    
    planets.forEach(planet => drawPlanet(planet, time));
}
  

8.5 사용자 상호작용 추가

이제 사용자가 마우스로 행성계를 조작할 수 있게 만들어볼게요.


let rotationX = 0;
let rotationY = 0;
let scale = 1;
let isDragging = false;
let lastMouseX, lastMouseY;

canvas.addEventListener('mousedown', e => {
    isDragging = true;
    lastMouseX = e.clientX;
    lastMouseY = e.clientY;
});

canvas.addEventListener('mousemove', e => {
    if (!isDragging) return;
    
    const deltaX = e.clientX - lastMouseX;
    const deltaY = e.clientY - lastMouseY;
    
    rotationY += deltaX * 0.01;
    rotationX += deltaY * 0.01;
    
    lastMouseX = e.clientX;
    lastMouseY = e.clientY;
});

canvas.addEventListener('mouseup', () => {
    isDragging = false;
});

canvas.addEventListener('wheel', e => {
    e.preventDefault();
    scale += e.deltaY * -0.001;
    scale = Math.min(Math.max(0.1, scale), 3);
});

function updateCameraMatrix() {
    const cameraMatrix = mat4.create();
    mat4.translate(cameraMatrix, cameraMatrix, [0, 0, -5]);
    mat4.rotate(cameraMatrix, cameraMatrix, rotationX, [1, 0, 0]);
    mat4.rotate(cameraMatrix, cameraMatrix, rotationY, [0, 1, 0]);
    mat4.scale(cameraMatrix, cameraMatrix, [scale, scale, scale]);
    return cameraMatrix;
}
  

8.6 행성 선택 및 정보 표시

사용자가 행성을 클릭하면 해당 행성의 정보를 표시해볼게요.


canvas.addEventListener('click', e => {
    const ray = calculateRayFromMouseClick(e);
    const selectedPlanet = findIntersectedPlanet(ray);
    if (selectedPlanet) {
        displayPlanetInfo(selectedPlanet);
    }
});

function calculateRayFromMouseClick(event) {
    // 마우스 클릭 위치를 정규화된 디바이스 좌표로 변환
    const rect = canvas.getBoundingClientRect();
    const x = ((event.clientX - rect.left) / canvas.width) * 2 - 1;
    const y = -((event.clientY - rect.top) / canvas.height) * 2 + 1;
    
    // 역행렬을 이용해 월드 좌표로 변환
    const invProjectionMatrix = mat4.create();
    mat4.invert(invProjectionMatrix, projectionMatrix);
    const invViewMatrix = mat4.create();
    mat4.invert(invViewMatrix, viewMatrix);
    
    const rayClip = vec4.fromValues(x, y, -1, 1);
    const rayEye = vec4.create();
    vec4.transformMat4(rayEye, rayClip, invProjectionMatrix);
    rayEye[2] = -1;
    rayEye[3] = 0;
    
    const rayWorld = vec4.create();
    vec4.transformMat4(rayWorld, rayEye, invViewMatrix);
    vec3.normalize(rayWorld, rayWorld);
    
    return rayWorld;
}

function findIntersectedPlanet(ray) {
    // 각 행성에 대해 광선 교차 검사
    for (const planet of planets) {
        if (intersectsSphere(ray, planet)) {
            return planet;
        }
    }
    return null;
}

function intersectsSphere(ray, planet) {
    // 구체와 광선의 교차 검사 로직
    // (간단히 하기 위해 상세 구현은 생략)
}

function displayPlanetInfo(planet) {
    const infoDiv = document.getElementById('info');
    infoDiv.innerHTML = `
        <h2>${planet.name}</h2>
        <p>반지름: ${planet.radius}</p>
        <p>태양으로부터의 거리: ${planet.distance}</p>
        <p>공전 속도: ${planet.rotationSpeed}</p>
    `;
}
  

8.7 최적화 및 성능 향상

프로젝트의 성능을 높이기 위해 몇 가지 최적화를 적용해볼게요.

  • 정적인 데이터는 렌더링 루프 밖에서 미리 계산해 둡니다.
  • 객체 풀링을 사용해 메모리 할당을 최소화합니다.
  • LOD(Level of Detail) 기법을 적용해 멀리 있는 행성은 덜 상세하게 그립니다.

// 객체 풀링 예시
const vec3Pool = [];
function getVec3() {
    return vec3Pool.pop() || vec3.create();
}
function releaseVec3(v) {
    vec3Pool.push(v);
}

// LOD 적용 예시
function getPlanetDetail(distance) {
    if (distance > 10) return 10;
    if (distance > 5) return 20;
    return 30;
}
  

🚀 프로젝트 확장하기: 이제 기본적인 3D 행성계가 완성되었어요! 여기에 더 많은 기능을 추가해볼 수 있어요. 예를 들어, 행성의 텍스처를 추가하거나, 행성 주변에 위성을 추가하거나, 배경에 별을 그려넣을 수 있죠. 재능넷에서 이런 프로젝트를 공유하면 많은 사람들이 관심을 가질 거예요!

8.8 마무리 및 배포

프로젝트를 완성했다면 이제 배포할 차례예요. GitHub Pages나 Netlify 같은 서비스를 이용해 무료로 웹에 배포할 수 있어요. 그리고 재능넷에 여러분의 프로젝트를 소개하는 글을 올려보는 건 어떨까요?

축하합니다! 🎉 여러분은 이제 WebGL을 이용해 멋진 3D 행성계를 만들었어요. 이 프로젝트를 통해 WebGL의 기본 개념부터 고급 기술까지 다양한 내용을 실제로 적용해볼 수 있었죠. 이제 여러분은 더 복잡하고 멋진 3D 그래픽 프로젝트에 도전할 준비가 되었어요!

WebGL의 세계는 정말 넓고 깊어요. 이번 여행은 그저 시작일 뿐이에요. 계속해서 학습하고 실험하면서 여러분만의 독특한 3D 세계를 만들어가세요. 여러분의 상상력이 곧 한계니까요! 🌠✨