WebGL 쉐이더 프로그래밍: GLSL 기초부터 고급 기법까지 🎨✨
안녕하세요, 여러분! 오늘은 정말 흥미진진한 주제로 여러분과 함께할 거예요. 바로 WebGL 쉐이더 프로그래밍에 대해 깊이 파헤쳐볼 거랍니다. 😎 GLSL(OpenGL Shading Language)의 기초부터 고급 기법까지, 마치 카톡으로 수다 떨듯이 재미있게 설명해드릴게요. 그럼 준비되셨나요? 고고씽~ 🚀
잠깐! 혹시 여러분, 재능넷이라는 사이트 아세요? 거기서 다양한 재능을 거래할 수 있다던데... 어쩌면 우리가 오늘 배울 WebGL 쉐이더 프로그래밍 스킬도 나중에 재능넷에서 공유할 수 있을지도 몰라요! 🤔 자, 그럼 본격적으로 시작해볼까요?
1. WebGL과 GLSL: 기초 개념 잡기 🌈
자, 여러분! WebGL이 뭔지 아시나요? 간단히 말해서, WebGL은 웹 브라우저에서 3D 그래픽을 구현할 수 있게 해주는 JavaScript API예요. 그리고 GLSL은 이 WebGL에서 사용하는 쉐이딩 언어랍니다. 쉐이더가 뭐냐고요? 음... 쉐이더는 그래픽 카드에서 실행되는 작은 프로그램이라고 생각하면 돼요. 이 꼬마 프로그램들이 픽셀의 색상이나 위치를 결정하는 거죠. 😉
GLSL로 작성된 쉐이더는 크게 두 가지 종류가 있어요:
- 버텍스 쉐이더 (Vertex Shader): 3D 공간에서 점들의 위치를 계산해요.
- 프래그먼트 쉐이더 (Fragment Shader): 각 픽셀의 색상을 결정해요.
이 두 친구들이 협력해서 멋진 그래픽을 만들어내는 거예요. 마치 화가와 색칠하는 사람이 협력하는 것처럼요! 🎨
위의 그림을 보면 WebGL의 렌더링 파이프라인을 한눈에 볼 수 있어요. 버텍스 쉐이더에서 시작해서 프래그먼트 쉐이더로 끝나는 과정이 보이시죠? 이게 바로 우리가 앞으로 다룰 내용의 핵심이에요! 👀
2. GLSL 문법: C 언어와 비슷하지만 다르다구요! 🤓
GLSL의 문법은 C 언어와 비슷해요. 하지만 그래픽스에 특화된 몇 가지 특별한 기능들이 있답니다. 예를 들어, vec2, vec3, vec4 같은 벡터 타입이나 mat2, mat3, mat4 같은 행렬 타입이 기본으로 제공돼요. 이런 타입들은 3D 그래픽스에서 정말 유용하게 쓰인답니다. 👍
간단한 GLSL 코드를 한번 볼까요?
// 버텍스 쉐이더 예제
attribute vec3 position;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
// 프래그먼트 쉐이더 예제
precision mediump float;
uniform vec3 color;
void main() {
gl_FragColor = vec4(color, 1.0);
}
우와~ 뭔가 복잡해 보이죠? 하지만 걱정 마세요. 하나씩 차근차근 설명해드릴게요! 😊
버텍스 쉐이더에서는 position이라는 attribute를 받아서 변환 행렬을 적용한 후, gl_Position에 최종 위치를 저장해요. 여기서 gl_Position은 버텍스 쉐이더의 출력을 나타내는 특별한 변수예요.
프래그먼트 쉐이더에서는 color라는 uniform 변수를 받아서 gl_FragColor에 최종 색상을 저장해요. gl_FragColor는 프래그먼트 쉐이더의 출력을 나타내는 특별한 변수랍니다.
이렇게 GLSL에서는 특별한 내장 변수들을 사용해서 그래픽스 파이프라인과 소통한답니다. 신기하죠? 🌟
3. WebGL 컨텍스트 설정: 캔버스야, 준비됐니? 🎭
자, 이제 GLSL 코드를 작성했으니 이걸 어떻게 실행시킬 수 있을까요? 바로 여기서 WebGL 컨텍스트가 등장합니다! WebGL 컨텍스트는 HTML5 캔버스 요소에서 생성할 수 있어요. 한번 코드로 살펴볼까요?
// HTML
<canvas id="myCanvas" width="800" height="600"></canvas>
// JavaScript
const canvas = document.getElementById('myCanvas');
const gl = canvas.getContext('webgl');
if (!gl) {
console.error('WebGL을 지원하지 않는 브라우저입니다. ㅠㅠ');
}
이렇게 하면 WebGL 컨텍스트가 생성돼요. 이제 이 gl 객체를 사용해서 WebGL 명령을 실행할 수 있답니다. 😎
그런데 잠깐, 여러분! 혹시 이런 생각 들지 않나요? "어... 이거 좀 복잡한데? 🤔" 맞아요, WebGL은 처음에는 좀 어려울 수 있어요. 하지만 걱정 마세요! 우리가 함께 하나씩 배워나가다 보면 어느새 WebGL 마스터가 되어 있을 거예요. 그리고 나중에는 이런 실력을 재능넷같은 플랫폼에서 다른 사람들과 공유할 수도 있겠죠? 멋지지 않나요? 😉
4. 쉐이더 컴파일과 프로그램 링크: 조립의 시간이에요! 🔧
자, 이제 우리가 작성한 GLSL 코드를 WebGL에서 사용할 수 있게 만들어볼 거예요. 이 과정은 마치 레고 블록을 조립하는 것과 비슷해요. 각각의 쉐이더를 만들고, 그것들을 하나의 프로그램으로 연결하는 거죠. 😊
먼저, 쉐이더를 컴파일해볼까요?
function compileShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error('쉐이더 컴파일 에러:', gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
const vertexShader = compileShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
우와~ 뭔가 대단해 보이죠? 😲 하지만 걱정 마세요. 이 코드는 그냥 우리가 작성한 GLSL 코드를 WebGL이 이해할 수 있는 형태로 변환하는 거예요. 마치 우리가 쓴 편지를 컴퓨터가 읽을 수 있는 언어로 번역하는 것과 비슷하답니다.
이제 컴파일된 쉐이더를 프로그램으로 링크해볼까요?
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error('프로그램 링크 에러:', gl.getProgramInfoLog(program));
return;
}
gl.useProgram(program);
짜잔~ 🎉 이제 우리의 쉐이더 프로그램이 완성됐어요! 이 프로그램을 사용해서 멋진 그래픽을 그릴 수 있게 됐답니다. 정말 신나지 않나요?
꿀팁! 쉐이더 컴파일과 링크 과정에서 에러가 발생하면 꼭 콘솔 로그를 확인해보세요. 대부분의 경우, 문법 오류나 타입 불일치 같은 간단한 실수 때문이에요. 디버깅할 때 정말 유용하답니다! 👍
5. 버퍼와 속성: 데이터를 GPU로 보내자! 📤
자, 이제 우리의 쉐이더 프로그램이 준비됐어요. 그런데 아직 뭔가 부족하지 않나요? 맞아요, 바로 데이터예요! 우리가 그리고 싶은 도형의 정보를 GPU에 전달해야 해요. 이때 사용하는 것이 바로 버퍼와 속성이랍니다. 😎
먼저 버퍼를 만들어볼까요?
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
const positions = [
-1.0, -1.0,
1.0, -1.0,
-1.0, 1.0,
1.0, 1.0
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
우와~ 뭔가 복잡해 보이죠? 하지만 걱정 마세요. 이건 그냥 우리가 그리고 싶은 사각형의 꼭지점 위치를 GPU에 전달하는 거예요. 마치 친구에게 "여기 여기 여기 여기에 점을 찍어줘"라고 말하는 것과 비슷하답니다. 😉
이제 이 데이터를 쉐이더의 속성과 연결해볼까요?
const positionAttributeLocation = gl.getAttribLocation(program, 'position');
gl.enableVertexAttribArray(positionAttributeLocation);
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
이 코드는 우리가 만든 버퍼의 데이터를 쉐이더의 'position' 속성과 연결하는 거예요. 마치 "야, 이 데이터는 position이라는 이름의 속성이야. 잘 기억해!"라고 GPU에게 말해주는 것과 같답니다. 😄
알쏭달쏭 포인트! gl.vertexAttribPointer 함수의 매개변수들이 좀 헷갈리시나요? 걱정 마세요. 이 함수는 그냥 "이 데이터는 이런 모양이야"라고 GPU에게 설명해주는 거예요. 2는 각 꼭지점이 x와 y 두 개의 값을 가진다는 뜻이고, gl.FLOAT는 그 값들이 부동소수점 숫자라는 뜻이에요. 나머지 매개변수들은 지금은 신경 쓰지 않아도 돼요. 나중에 더 자세히 배울 거예요! 😉
6. Uniform 변수: 쉐이더에게 정보를 주자! 💌
자, 이제 우리의 쉐이더 프로그램이 데이터도 받을 준비가 됐어요. 그런데 가만히 생각해보니, 우리가 그리는 도형의 색상을 바꾸고 싶다면 어떻게 해야 할까요? 이럴 때 사용하는 것이 바로 Uniform 변수예요! 😃
Uniform 변수는 쉐이더 프로그램 실행 중에 변경할 수 있는 전역 변수 같은 거예요. 예를 들어, 색상이나 변환 행렬 같은 것들을 Uniform 변수로 전달할 수 있답니다.
한번 색상을 전달하는 Uniform 변수를 만들어볼까요?
const colorUniformLocation = gl.getUniformLocation(program, 'color');
gl.uniform3f(colorUniformLocation, 1.0, 0.0, 0.0); // 빨간색!
우와~ 이제 우리의 도형이 빨간색으로 변했어요! 😲 이 코드는 쉐이더에게 "야, color라는 이름의 Uniform 변수가 있을 거야. 거기에 빨간색 정보를 넣어줄게!"라고 말하는 거예요.
그런데 잠깐, 여러분! 혹시 이런 생각 들지 않나요? "어... 이거 좀 신기한데? 🤔" 맞아요, WebGL과 GLSL을 사용하면 이렇게 동적으로 그래픽을 제어할 수 있어요. 이런 기술을 활용하면 정말 멋진 인터랙티브 웹 애플리케이션을 만들 수 있답니다. 어쩌면 나중에 여러분이 만든 멋진 WebGL 작품을 재능넷에서 공유할 수 있을지도 몰라요! 그렇게 되면 정말 멋지겠죠? 😉
7. 그리기 명령: 드디어 화면에 그려볼 시간이에요! 🖌️
자, 이제 모든 준비가 끝났어요! 우리의 쉐이더 프로그램도 있고, 데이터도 GPU에 전달했고, Uniform 변수로 색상도 설정했어요. 이제 남은 건 뭘까요? 바로 그리기 명령을 내리는 거예요! 😄
그리기 명령을 내리는 코드를 한번 볼까요?
gl.clearColor(0.0, 0.0, 0.0, 1.0); // 검은색 배경
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
우와~ 이게 뭘까요? 😲 하나씩 설명해드릴게요:
- gl.clearColor와 gl.clear: 이건 그냥 캔버스를 깨끗이 지우는 거예요. 마치 칠판을 지우는 것처럼요!
- gl.drawArrays: 이게 바로 실제로 그리기를 시작하는 명령이에요. "자, 이제 그려!"라고 GPU에게 말하는 거죠.
gl.TRIANGLE_STRIP은 우리가 그리려는 도형의 타입을 나타내요. 이건 "점들을 이어서 삼각형을 계속 그려나가세요"라는 뜻이에요. 0은 시작 인덱스, 4는 그릴 꼭지점의 개수를 나타내죠.
이 명령을 실행하면... 짜잔! 🎉 우리의 캔버스에 빨간 사각형이 그려졌어요! 정말 신기하지 않나요?
여러분, 우리가 방금 WebGL을 사용해서 첫 번째 그래픽을 만들었어요! 👏👏👏 정말 대단하지 않나요? 이제 여러분은 WebGL의 기본적인 작동 원리를 이해하게 됐어요. 이걸 바탕으로 더 복잡하고 멋진 그래픽을 만들 수 있답니다!
꿀팁! WebGL은 성능이 정말 좋아요. 그래서 복잡한 3D 그래픽이나 애니메이션을 만들 때 자주 사용된답니다. 예를 들어, 웹 기반 게임이나 데이터 시각화 같은 분야에서 많이 쓰여요. 여러분도 이런 멋진 애플리케이션을 만들어볼 수 있을 거예요! 💪
8. 애니메이션: 움직임을 만들어볼까요? 🎬
자, 이제 우리는 정적인 사각형을 그릴 수 있게 됐어요. 근데 뭔가 좀 심심하지 않나요? 그래픽스의 진짜 재미는 바로 움직임에 있답니다! 그럼 우리의 사각형에 생명을 불어넣어볼까요? 😃
애니메이션을 만들기 위해서는 시간에 따라 변하는 값을 쉐이더에 전달해야 해요. 이를 위해 우리는 새로운 Uniform 변수를 사용할 거예요. 이 변수를 통해 시간 정보를 쉐이더에 전달할 수 있답니다.
먼저, 프래그먼트 쉐이더를 조금 수정해볼게요:
// 프래그먼트 쉐이더
precision mediump float;
uniform vec3 color;
uniform float time;
void main() {
vec3 finalColor = vec3(
sin(time) * 0.5 + 0.5,
cos(time) * 0.5 + 0.5,
sin(time + 3.14) * 0.5 + 0.5
);
gl_FragColor = vec4(finalColor, 1.0);
}
우와~ 이게 뭘까요? 😲 이 코드는 시간에 따라 색상이 변하도록 만들어요. sin과 cos 함수를 사용해서 색상 값을 계산하고 있어요. 이렇게 하면 색상이 부드럽게 변화하게 됩니다.
이제 JavaScript에서 이 time 값을 계속 업데이트해주면 돼요: