https://webglfundamentals.org/webgl/lessons/webgl-image-processing.html
https://github.com/Myoungmin/WebGL_Fundamentals
WebGL Image Processing
WebGL에서 이미지를 그리기 위해서는 텍스처를 사용해야 한다.
렌더링할 때 WebGL이 픽셀 대신 클립 공간 좌표를 유추하는 것과 마찬가지로,
텍스처를 읽을 때 WebGL은 텍스처 좌표를 유추한다.
텍스처 좌표는 텍스처 크기에 상관없이 0.0에서 1.0사이가 된다.
단 하나의 사각형(정확히는 2개의 삼각형)만 그리기 때문에 사각형의 각 점이 텍스처의 어느 위치에 해당하는지 WebGL에 알려줘야 한다.
'varying'이라고 불리는 특수 변수를 이용해 이 정보를 정점 셰이더에서 프래그먼트 셰이더로 전달해야 한다.
정점 셰이더에서 텍스처 좌표 전달을 위한 attribute를 추가한 다음, varying을 이용하여 프래그먼트 셰이더로 전달한다.
//텍스처 좌표 전달을 위한 attribute
attribute vec2 a_texCoord;
//...
varying vec2 v_texCoord;
void main() {
//...
// 프래그먼트 셰이더로 텍스처 좌표 전달
// GPU는 점들 사이의 값을 보간
v_texCoord = a_texCoord;
}
정점 셰이더에서 넘어오는 varying인 텍스처 좌표를 통해, 텍스처의 색상을 찾을 수 있도록 프래그먼트 셰이더를 작성한다.
<script id="fragment-shader-2d" type="x-shader/x-fragment">
precision mediump float;
// 텍스처
uniform sampler2D u_image;
// 정점 셰이더에서 전달된 텍스처 좌표
varying vec2 v_texCoord;
void main() {
// 텍스처의 색상 탐색
gl_FragColor = texture2D(u_image, v_texCoord);
}
</script>
이미지를 불러오고, 텍스처를 생성한 다음, 이미지를 텍스처로 복사한다.
브라우저에서 이미지를 비동기적으로 불러오기 때문에 텍스처 로딩을 기다리도록 코드를 구성해야 한다.
onload 이벤트에 의해 이미지를 불러오자마자 render가 실행된다.
function main() {
var image = new Image();
image.src = "http://someimage/on/our/server"; // 같은 도메인이여야 합니다!!!
image.onload = function() {
render(image);
}
}
function render(image) {
//...
// 이전에 작성한 모든 코드
//...
// 텍스처 좌표가 필요한 곳을 탐색
var texCoordLocation = gl.getAttribLocation(program, "a_texCoord");
// 사각형의 텍스처 좌표 제공
var texCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([
0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
0.0, 1.0,
1.0, 0.0,
1.0, 1.0
]),
gl.STATIC_DRAW
);
gl.enableVertexAttribArray(texCoordLocation);
gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);
// 텍스처 생성
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);
// 텍스처에 이미지 업로드
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
//...
}
컨볼루션 커널을 이용해서 일반적인 이미지 처리
컨볼루션 커널을 프래그먼트 셰이더에서 수행하므로 새로운 프래그먼트 셰이더를 작성한다.
컨볼루션 커널은 행렬의 각 항목이 렌더링하는 픽셀 주변에 있는 8개의 픽셀에 얼마나 곱할지 나타내는 3x3 행렬이다.
그런 다음 결과를 커널의 가중치(커널에 있는 모든 값의 합) 또는 1.0 중에 더 큰 값으로 나눈다.
<script id="fragment-shader-2d" type="x-shader/x-fragment">
precision mediump float;
// 텍스처
uniform sampler2D u_image;
uniform vec2 u_textureSize;
uniform float u_kernel[9];
uniform float u_kernelWeight;
// 정점 셰이더에서 전달된 텍스처 좌표
varying vec2 v_texCoord;
void main() {
vec2 onePixel = vec2(1.0, 1.0) / u_textureSize;
vec4 colorSum =
texture2D(u_image, v_texCoord + onePixel * vec2(-1, -1)) * u_kernel[0] +
texture2D(u_image, v_texCoord + onePixel * vec2( 0, -1)) * u_kernel[1] +
texture2D(u_image, v_texCoord + onePixel * vec2( 1, -1)) * u_kernel[2] +
texture2D(u_image, v_texCoord + onePixel * vec2(-1, 0)) * u_kernel[3] +
texture2D(u_image, v_texCoord + onePixel * vec2( 0, 0)) * u_kernel[4] +
texture2D(u_image, v_texCoord + onePixel * vec2( 1, 0)) * u_kernel[5] +
texture2D(u_image, v_texCoord + onePixel * vec2(-1, 1)) * u_kernel[6] +
texture2D(u_image, v_texCoord + onePixel * vec2( 0, 1)) * u_kernel[7] +
texture2D(u_image, v_texCoord + onePixel * vec2( 1, 1)) * u_kernel[8] ;
// 합계를 가중치로 나누지만 rgb만을 사용
// 알파는 1.0으로 설정
gl_FragColor = vec4((colorSum / u_kernelWeight).rgb, 1.0);
}
</script>
자바스크립트에서 컨볼루션 커널인 3x3 행렬과 가중치를 fragment shader에 제공해 준다.
아래는 윤곽선 검출을 위한 컨볼루션 커널을 적용한다.
function computeKernelWeight(kernel) {
var weight = kernel.reduce(function(prev, curr) {
return prev + curr;
});
return weight <= 0 ? 1 : weight;
}
//...
var kernelLocation = gl.getUniformLocation(program, "u_kernel[0]");
var kernelWeightLocation = gl.getUniformLocation(program, "u_kernelWeight");
//...
var edgeDetectKernel = [
-1, -1, -1,
-1, 8, -1,
-1, -1, -1
];
gl.uniform1fv(kernelLocation, edgeDetectKernel);
gl.uniform1f(kernelWeightLocation, computeKernelWeight(edgeDetectKernel));
//...
https://myoungmin.github.io/WebGL_Fundamentals/
'WebGL' 카테고리의 다른 글
WebGL Fundamentals > WebGL 2D : Translation (0) | 2022.06.30 |
---|---|
WebGL Fundamentals > Image Processing : 여러 개의 이미지 처리 효과 적용하기 (0) | 2022.06.30 |
WebGL Fundamentals > Fundamentals : WebGL Shaders and GLSL (0) | 2022.06.29 |
WebGL Fundamentals > Fundamentals : Varying (0) | 2022.06.28 |
WebGL Fundamentals > Fundamentals : buffer와 attribute 커맨드가 하는 일 (0) | 2022.06.27 |