본문 바로가기

WebGL

WebGL Fundamentals > Image Processing : 여러 개의 이미지 처리 효과 적용하기

https://webglfundamentals.org/webgl/lessons/webgl-image-processing-continued.html

 

WebGL Image Processing Continued

How to apply multiple image processing techniques to images in WebGL

webglfundamentals.org

https://github.com/Myoungmin/WebGL_Fundamentals

 

GitHub - Myoungmin/WebGL_Fundamentals: WebGL 학습 프로젝트

WebGL 학습 프로젝트. Contribute to Myoungmin/WebGL_Fundamentals development by creating an account on GitHub.

github.com

 

 

Image Processing : 여러 개의 이미지 처리 효과 적용하기

어떻게 여러 이미지 처리 효과를 적용할 수 있을까?

즉석으로 셰이더를 생성할 수 있다.

사용자가 쓰고자 하는 효과를 선택하는 UI를 제공한 다음 모든 효과를 수행하는 셰이더를 생성하는 것이다. 

항상 가능한 건 아니지만 이 기술은 종종 실시간 그래픽 효과를 만드는 데 사용된다.

 

하지만 더 유연한 방법은 텍스처 2개를 더 사용하고,

각 텍스처를 차례대로 렌더링하여, 주고 받으면서 매번 다음 효과를 적용하는 것이다.

 

원본 이미지 -> [Blur] -> 텍스처 1
텍스처 1 -> [Sharpen] -> 텍스처 2
텍스처 2 -> [Edge Detect] -> 텍스처 1
텍스처 1 -> [Blur] -> 텍스처 2
텍스처 2 -> [Normal] -> 캔버스

 

이렇게 하기 위해서는 프레임 버퍼를 만들어야 한다.

WebGL/OpenGL 프레임 버퍼는 상태 모음(어태치먼트 목록)일 뿐이며, 

실제로 어떤 종류의 버퍼도 아니다.

하지만 텍스처를 프레임 버퍼에 첨부해서 해당 텍스처에 렌더링할 수 있다.

 

1. 텍스처 생성 함수를 만든다.

 function createAndSetupTexture(gl) {
    var texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, texture);
 
    // 텍스처를 설정하여 어떤 크기의 이미지도 렌더링할 수 있도록 하고 픽셀로 작업합니다.
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
 
    return texture;
  }
 
  // 텍스처를 만들고 이미지를 넣습니다.
  var originalImageTexture = createAndSetupTexture(gl);
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);

2. 함수를 사용하여 텍스처를 2개 더 만들고 프레임 버퍼 2개에 첨부한다.

 // 텍스처 2개를 만들고 프레임 버퍼에 첨부합니다.
  var textures = [];
  var framebuffers = [];
  for (var ii = 0; ii < 2; ++ii) {
    var texture = createAndSetupTexture(gl);
    textures.push(texture);
 
    // 이미지와 같은 크기로 텍스처 만들기
    gl.texImage2D(
      gl.TEXTURE_2D,
      0,
      gl.RGBA,
      image.width,
      image.height,
      0,
      gl.RGBA,
      gl.UNSIGNED_BYTE,
      null
    );
 
    // 프레임 버퍼 생성
    var fbo = gl.createFramebuffer();
    framebuffers.push(fbo);
    gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
 
    // 텍스처 첨부
    gl.framebufferTexture2D(
      gl.FRAMEBUFFER,
      gl.COLOR_ATTACHMENT0,
      gl.TEXTURE_2D,
      texture,
      0
    );
  }

3. 커널 세트와 적용할 목록 생성

// 여러 컨볼루션 커널 정의
  var kernels = {
    normal: [
      0, 0, 0,
      0, 1, 0,
      0, 0, 0
    ],
    gaussianBlur: [
      0.045, 0.122, 0.045,
      0.122, 0.332, 0.122,
      0.045, 0.122, 0.045
    ],
    unsharpen: [
      -1, -1, -1,
      -1,  9, -1,
      -1, -1, -1
    ],
    emboss: [
      -2, -1,  0,
      -1,  1,  1,
       0,  1,  2
    ]
  };
 
  // 적용할 효과 목록
  var effectsToApply = [
    "gaussianBlur",
    "emboss",
    "gaussianBlur",
    "unsharpen"
  ];

4. 이미지 처리 효과를 적용하고 텍스처를 주고 받는다.

  // 원본 이미지로 시작
  gl.bindTexture(gl.TEXTURE_2D, originalImageTexture);
 
  // 텍스처에 그리는 동안 이미지 y축 뒤집지 않기
  gl.uniform1f(flipYLocation, 1);
 
  // 적용하고 싶은 각 효과를 반복합니다.
  for (var ii = 0; ii < effectsToApply.length; ++ii) {
    // 프레임 버퍼 중 하나에 그리도록 설정합니다.
    setFramebuffer(framebuffers[ii % 2], image.width, image.height);
 
    drawWithKernel(effectsToApply[ii]);
 
    // 다음 그리기를 위해, 방금 렌더링한 텍스처를 사용합니다.
    gl.bindTexture(gl.TEXTURE_2D, textures[ii % 2]);
  }
 
  // 마지막으로 결과를 캔버스에 그립니다.
  gl.uniform1f(flipYLocation, -1);  // 캔버스 y축 뒤집기 필요
  // gl.bindFramebuffer에 null을 넘기는 것은 캔버스에 그리라는 뜻이다.
  setFramebuffer(null, canvas.width, canvas.height);
  drawWithKernel("normal");
 
  function setFramebuffer(fbo, width, height) {
    // 이걸 렌더링할 프레임 버퍼로 만듭니다.
    gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
 
    // 프레임 버퍼의 해상도를 셰이더에 알려줍니다.
    gl.uniform2f(resolutionLocation, width, height);
 
    // WebGL에 프레임 버퍼에 필요한 뷰포트 설정을 알려줍니다.
    gl.viewport(0, 0, width, height);
  }
 
  function drawWithKernel(name) {
    // 커널 설정
    gl.uniform1fv(kernelLocation, kernels[name]);
 
    // 사각형을 그립니다.
    gl.drawArrays(gl.TRIANGLES, 0, 6);
  }

 

gl.bindFramebuffer에 null을 넘겨 호출하는 것은 프레임 버퍼 중 하나 대신 캔버스에 렌더링하고 싶다고 WebGL에 알려준다.

WebGL은 클립 공간에서 다시 픽셀로 변환해야 한다.
이건 gl.viewport의 설정에 따라 수행된다.

렌더링할 프레임 버퍼는 캔버스 크기와 다르기 때문에, 
프레임 버퍼 텍스처를 렌더링할 때 뷰포트를 적절하게 설정하고, 
마지막으로 캔버스를 렌더링할 때 다시 설정해야 한다.

WebGL이 0,0을 2D에서 더 전통적인 좌측 상단 대신 좌측 하단 모서리로 캔버스에 표시하기 때문에 Y좌표를 뒤집었었는데, 프레임 버퍼에 렌더링할 때는 필요가 없다.

프레임 버퍼는 표시되지 않기 때문에, 어느 부분이 상단 혹은 하단인지는 관계가 없다.

중요한 건 프레임 버퍼에서 픽셀 0,0이 우리가 계산한 0,0에 해당한다는 것이다.

이걸 해결하기 위해 셰이더에 입력 하나를 더 추가해서 뒤집을지 말지 설정 가능하도록 만들었다.

 

<script id="vertex-shader-2d" type="x-shader/x-vertex">
...
uniform float u_flipY;
...
 
void main() {
   ...
 
   gl_Position = vec4(clipSpace * vec2(1, u_flipY), 0, 1);
 
   ...
}
</script>

 

이것은 렌더링할 때 설정할 수 있다.

 

  //...
 
  var flipYLocation = gl.getUniformLocation(program, "u_flipY");
 
  //...
 
  // 뒤집지 않기
  gl.uniform1f(flipYLocation, 1);
 
  ...
 
  // 뒤집기
  gl.uniform1f(flipYLocation, -1);

 

 

 

https://myoungmin.github.io/WebGL_Fundamentals/

 

WebGL_Fundamentals

WebGL이란? WebGL은 Web Graphics Library의 약자로 웹상에서 2D 및 3D 그래픽을 렌더링하기 위한 로우 레벨 Javascript API.

myoungmin.github.io