-
Notifications
You must be signed in to change notification settings - Fork 27
WebGL
- javascript 에서 설정한 데이터를 Buffers에 담아 Attributes / Uniform 형식으로 GPU에게 보냄
- GPU에서는 vertex shader에서 position 버퍼에 있는 값에 따라 정점을 그림
- 정해진 좌표를 토대로 Resterization(픽셀화)
- fragment shader에서 픽셀 위에 색깔을 입혀줌
- 이미지 반환
-
모든 객체의 면은 polygon이여야 한다(계산이 쉬워지기 때문에).
- ex) 평명의 법선벡터가 일정하다면 빛 계산을 한번만 해도 된다.
-
3개의 점은 어느 위치에 있더라도 한 평면 위에 존재한다.
-
따라서 polygon의 정의를 만족하기 위해선 3개의 점이 최소가 된다
자 이제부터 webglController를 파해쳐 보는 시간을 가지겠습니다.
import { mat4 } from 'gl-matrix';
import vertexShaderSource from './vertexShaderSource';
import fragmentShaderSource from './fragmentShaderSource';
gl-matrix 모듈에서 4x4 행렬을 import 합니다. npm install gl-matrix
미리 작성해 두었던 vertexShaderSource와 fragmentShaderSource를 import 합니다.
interface Buffers {
position: WebGLBuffer,
textureCoord: WebGLBuffer,
indices: WebGLBuffer,
} // Buffers : 3개의 WebGLBuffer(position, textureCoord, indices) 객체
interface ProgramInfo {
program: WebGLProgram,
attribLocations: {
vertexPosition: number,
textureCoord: number
},
uniformLocations: {
projectionMatrix: WebGLUniformLocation,
modelViewMatrix: WebGLUniformLocation,
uSampler: WebGLUniformLocation
},
} // ProgramInfo : shader program의 정보를 나타내는 객체
사용자 정의 타입을 선언합니다.
class webglControler {
copyVideo: Boolean; // 비디오가 로딩 된 후에 cavnas에 drawImage를 하기 위함
positions: Array<number>; // vertex들의 좌표(x, y)를 나타냄
videoURL: string; // 표시될 비디오의 URL
buffers: Buffers; // bufferData를 담고있는 Buffer 객체
gl: WebGLRenderingContext; // gl : canvas의 Webgl context
constructor(videoURL: string) {
this.copyVideo = false;
this.positions = [-1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0];
this.videoURL = videoURL;
}
맴버 변수들을 선언합니다.
다음으로 main() 함수를 통해 접근을 할 수 있습니다.
webglController.ts
의 main()
main = () => {
const video = document.createElement('video');
video.muted = true;
video.src = this.videoURL;
video.addEventListener('timeupdate', () => {
this.copyVideo = true;
});
// video의 재생위치가 변할 때 timeupdate 발생
video.addEventListener('loadeddata', () => {
this.glInit(video);
video.play();
});
};
// 비디오의 현재 프레임에 대한 데이터가 로드될 때 loadeddata발생
}
비디오의 현재 프레임에 대한 데이터가 로드될 때, loadedddata이벤트가 발생하고, 이때, glInit()함수를 부릅니다
다음은 glInit() 함수를 살펴보겠습니다.
glInit()은 canvas,shaderProgram, buffer, texture를 초기화하고, 계속해서 frame을 그려주는 역할을 합니다.
glInit()
의 초기설정 부분
// 초기화(canvas,texture,buffer)
glInit = (video: HTMLVideoElement) => {
// canvas 초기설정(가로,세로 설정)
this.gl = this.initCanvas(
video.videoWidth.toString(),
video.videoHeight.toString()
);
// buffer 초기설정
this.buffers = this.initBuffers();
// shaderProgram 초기설정
const shaderProgram = this.initShaderProgram();
// drawScene에 넘겨줄 programInfo
const programInfo = {
...
},
};
// texture 초기설정
const texture = this.initTexture();
초기화 함수의 자세한 내용은 다음과 같습니다.
- canvas 초기화 함수
initCanvas = (videoWidth: string, videoHeight: string) => {
const canvas = document.getElementById('glcanvas');
canvas.setAttribute('width', videoWidth); // video의 해상도를 맞춥니다.
canvas.setAttribute('height', videoHeight);
const gl =
canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
// 브라우저가 webgl을 지원하는지를 판단 할 수 있습니다.
return gl;
};
canvas를 초기화하여 context를 리턴합니다. 2. buffer 초기화 함수
initBuffers = () => {
const positionBuffer = this.gl.createBuffer();
...
// 정점의 좌표를 담고있는 버퍼
const textureCoordinates = [0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0];
const textureCoordBuffer = this.gl.createBuffer();
...
// 텍스쳐의 좌표를 담고있는 버퍼
const indices = [0, 1, 2, 0, 2, 3];
const indexBuffer = this.gl.createBuffer();
...
// 정점의 순서를 담고있는 버퍼
return {
position: positionBuffer,
textureCoord: textureCoordBuffer,
indices: indexBuffer,
};
};
- texture초기화 함수
initTexture = () => {
const texture = this.gl.createTexture();
this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
...
this.gl.texImage2D(
this.gl.TEXTURE_2D,
...
);
this.gl.texParameteri(
...
);
return texture;
};
텍스쳐를 생성하고 설정하여 반환합니다
- shader 초기화 함수
initShaderProgram = () => {
const vertexShader = this.loadShader(
this.gl.VERTEX_SHADER,
vertexShaderSource
);
const fragmentShader = this.loadShader(
this.gl.FRAGMENT_SHADER,
fragmentShaderSource
);
...
return shaderProgram;
};
vertexShader Program과 fragmentShader Program를 만들어 반환합니다.
loadShader = (type: number, source: string) => {
const shader = this.gl.createShader(type);
...
return shader;
};
shader의 type과 source에 따라 shader를 만들고 반환합니다
- type : gl.VERTEX_SHADER | gl.FRAGMENT_SHADER
- source : vertexShaderSource | fragmentShaderSource
glInit()
함수의 render()부분
const render = () => {
// 재생위치가 변하고 있을 때,(비디오가 계속 재생되고 있을 때) updateTexture()해준다.
if (this.copyVideo) {
this.updateTexture(texture, video);
}
// 비디오를 멈추지않으면 계속해서 재생되고 drawScene()을 부른다.
if (!this.pause) {
video.play();
this.drawScene(programInfo, texture);
} else {
video.pause();
}
requestAnimationFrame(render);
};
requestAnimationFrame(render);
};
updateTexture()
함수는 texture을 계속해서 바꿔주는 함수입니다. 한 번 살펴보겠습니다.
updateTexture = (texture: WebGLTexture, video: HTMLVideoElement) => {
const level = 0;
// texture에서 원하는 format
const internalFormat = this.gl.RGBA;
// 제공되는 데이터 포맷
const srcFormat = this.gl.RGBA;
// 제공되는 데이터 타입
const srcType = this.gl.UNSIGNED_BYTE;
// 텍스쳐객체를 gl.TEXTURE_2D()에 바인딩한다.
this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
// 비디오 데이터가 텍스쳐에 쓰여진다.
this.gl.texImage2D(
this.gl.TEXTURE_2D,
level,
internalFormat,
srcFormat,
srcType,
video
);
};
bindTexture()
는 텍스쳐를 실질적으로 생성하기 위해서
새로 생성한 텍스쳐 객체를 gl.TEXTURE_2D에 바인딩시키는 과정입니다.
texImage2D()
는 data가 loaded된 비디오객체를 texImage2D()에 전달하여 texture에 쓰여진다.
다음은 drawScene()
를 살펴보겠습니다.
drawScene = (programInfo: ProgramInfo, texture: WebGLTexture) => {
// 작업영역을 지우고 RGBA값이 0,0,0,1이 되도록한다.
this.gl.clearColor(0.0, 0.0, 0.0, 1.0);
// buffer의 깊이가 1만큼인 버퍼를 지운다.
this.gl.clearDepth(1.0);
// depth test에 통과하면 fragment의 z 값을 depth buffer에 저장하고 실패하면 fragment를 폐기하는 것을 허용
this.gl.enable(this.gl.DEPTH_TEST);
// depth buffer의 값보다 작거나 같으면 pass
this.gl.depthFunc(this.gl.LEQUAL);
// canvas지우기
this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
다음은 이해를 돕기 위해 사진을 함께 봐주세요.
// 카메라 0.1떨어진 곳 부터 100까지 보겠다.
const zNear = 0.1;
const zFar = 100.0;
// 카메라의 좌표계
const projectionMatrix = mat4.create();
mat4.ortho(projectionMatrix, -1.0, 1.0, -1.0, 1.0, zNear, zFar);
// 구의 좌표계
const modelViewMatrix = mat4.create();
// object를 보이기 하기 위해 1 만큼 z축 뒤로 이동
mat4.translate(modelViewMatrix, modelViewMatrix, [-0.0, 0.0, -1.0]);
{
const numComponents = 2;
// positions의 element들을 2개씩 (x, y) 좌표 쌍으로 읽어서 그려준다
const type = this.gl.FLOAT;
const normalize = false;
const stride = 0;
const offset = 0;
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffers.position);
this.gl.vertexAttribPointer(
programInfo.attribLocations.vertexPosition,
numComponents,
type,
normalize,
stride,
offset
);
this.gl.enableVertexAttribArray(
programInfo.attribLocations.vertexPosition
);
}
{
const numComponents = 2;
// positions의 element들을 2개씩 (x, y) 좌표 쌍으로 읽어서 그려준다
const type = this.gl.FLOAT;
const normalize = false;
const stride = 0;
const offset = 0;
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffers.textureCoord);
this.gl.vertexAttribPointer(
programInfo.attribLocations.textureCoord,
numComponents,
type,
normalize,
stride,
offset
);
this.gl.enableVertexAttribArray(programInfo.attribLocations.textureCoord);
}
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.buffers.indices);
this.gl.useProgram(programInfo.program);
this.gl.uniformMatrix4fv(
programInfo.uniformLocations.projectionMatrix,
false,
projectionMatrix
);
this.gl.uniformMatrix4fv(
programInfo.uniformLocations.modelViewMatrix,
false,
modelViewMatrix
);
this.gl.activeTexture(this.gl.TEXTURE0);
this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
this.gl.uniform1i(programInfo.uniformLocations.uSampler, 0);
{
const vertexCount = 6;
const type = this.gl.UNSIGNED_SHORT;
const offset = 0;
this.gl.drawElements(this.gl.TRIANGLES, vertexCount, type, offset);
}
};
기존 positions로 그린 vertex
기존 indices를 통해 그린 object
rotateLeft90Degree = () => {
this.positions.push(this.positions.shift());
this.positions.push(this.positions.shift());
this.buffers = this.initBuffers();
};
positions의 첫 번째 vertex를 맨 마지막에 추가함으로써 다음과 같은 결과를 가져옴.
바뀐 positions로 그린 vertex
바뀐 indices를 통해 그린 object
결과