Skip to content
SSH edited this page Nov 25, 2020 · 1 revision

WebGL

WebGl Pipeline

  1. javascript 에서 설정한 데이터를 Buffers에 담아 Attributes / Uniform 형식으로 GPU에게 보냄
  2. GPU에서는 vertex shader에서 position 버퍼에 있는 값에 따라 정점을 그림
  3. 정해진 좌표를 토대로 Resterization(픽셀화)
  4. fragment shader에서 픽셀 위에 색깔을 입혀줌
  5. 이미지 반환

Webgl에서 Object를 삼각형으로 나누어 그리는 이유

  • 모든 객체의 면은 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();

초기화 함수의 자세한 내용은 다음과 같습니다.

  1. 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,
   };
 };
 
  1. 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;
  };

텍스쳐를 생성하고 설정하여 반환합니다

  1. 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

결과

Clone this wiki locally