webgl обработка изображений, среднее между средним, правым и левым пикселем

Изучаю как обрабатывать изображения в webgl

Не понимаю что это значит

// среднее между средним, правым и левым пикселем
gl_FragColor = (
    texture2D(u_image, v_texCoord) +
    texture2D(u_image, v_texCoord + vec2(onePixel.x, 0.0)) +
    texture2D(u_image, v_texCoord + vec2(-onePixel.x, 0.0))) / 3.0;

И вообще не понимаю как это выполняется, объясните по простому

function debounce(f, t) {
  return function(args) {
    let previousCall = this.lastCall;
    this.lastCall = Date.now();
    if (previousCall && ((this.lastCall  -  previousCall) <= t)) {
      clearTimeout(this.lastCallTimer);
    }
    this.lastCallTimer = setTimeout(() => f(args), t);
  }
}

function createShader(gl, type, source) {
  const shader = gl.createShader(type);
  gl.shaderSource(shader, source);
  gl.compileShader(shader);
  const sucess = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
  if (sucess) {
    return shader;
  }

  console.log(gl.getShaderInfoLog(shader));
  gl.deleteShader(shader);
}

function createProgram(gl, v, f) {
  const program = gl.createProgram();
  gl.attachShader(program, v);
  gl.attachShader(program, f);
  gl.linkProgram(program);
  const sucess = gl.getProgramParameter(program, gl.LINK_STATUS);
  if (sucess) {
    return program;
  }

  console.log(gl.getProgramInfoLog(program));
  gl.deleteProgram(program);
}

function resizeCanvas(gl) {
  const realToCSSPixels = window.devicePixelRatio; // для дисплеев повышенной четкости HD-DPI
  const width = Math.floor(gl.canvas.scrollWidth * realToCSSPixels);
  const height = Math.floor(gl.canvas.scrollHeight * realToCSSPixels);
  gl.canvas.width = width;
  gl.canvas.height = height;
  gl.viewport(0, 0, width, height);
}

function requestCORSIfNotSameOrigin(img, url) {
  if ((new URL(url)).origin !== window.location.origin) {
    img.crossOrigin = "";
  }
}

function drawWebglCanvas(f, v, gl, image) {
  function resizeGlAndCanvas(gl) {
    resizeCanvas(gl);
    const resizeFunc = () => {
      resizeCanvas(gl);
      render();
    };
    const lazyFunc = debounce(resizeFunc, 100);
    window.addEventListener('resize', lazyFunc);
  }
  resizeGlAndCanvas(gl);
  gl.clearColor(0, 0, 0, 0);
  gl.clear(gl.COLOR_BUFFER_BIT);

  const vertexShader = createShader(gl, gl.VERTEX_SHADER, v);
  const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, f);
  const program = createProgram(gl, vertexShader, fragmentShader);


  const positionAttributeLocation = gl.getAttribLocation(program, 'a_position');
  const resolutionUniformLocation = gl.getUniformLocation(program, "u_resolution");
  gl.useProgram(program);
  gl.uniform2f(resolutionUniformLocation, gl.canvas.width, gl.canvas.height);

  const positionBuffer = gl.createBuffer();
  gl.enableVertexAttribArray(positionAttributeLocation);
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  setRectangle(gl, 0, 0, gl.canvas.width, gl.canvas.height);
  gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);

  // указываем координаты текстуры для прямоугольника
  const texCoordLocation = gl.getAttribLocation(program, "a_texCoord");
  const 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);

  // создаём текстуру
  const 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);
  const textureSizeLocation = gl.getUniformLocation(program, "u_textureSize");
  gl.uniform2f(textureSizeLocation, gl.canvas.width, gl.canvas.height);

  function render() {
    const primitiveType = gl.TRIANGLES;
    const offset = 0;
    const count = 6;
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.drawArrays(primitiveType, offset, count);
  }
  render();
}

function setRectangle(gl, x, y, width, height) {
  const x1 = x;
  const x2 = x + width;
  const y1 = y;
  const y2 = y + height;
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
    x1, y1,
    x2, y1,
    x1, y2,
    x1, y2,
    x2, y1,
    x2, y2,
  ]), gl.STATIC_DRAW);
}


const fragment = document.querySelector('#fragment-shader-2d').innerHTML;
const vertex = document.querySelector('#vertex-shader-2d').innerHTML;
const canvas = document.querySelector('#glcanvas');
const gl = canvas.getContext("webgl");
const image = new Image();
const url = "https://picsum.photos/1000/700";
requestCORSIfNotSameOrigin(image, url)
image.src = url;
image.crossOrigin = "";
image.addEventListener('load', () => {
  drawWebglCanvas(fragment, vertex, gl, image);
});
*,
*:before,
*:after {
  -webkit-box-sizing: border-box;
  -moz-box-sizing: border-box;
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body,
html {
  position: relative;
  height: 100%;
}

#glcanvas {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
<script  id="vertex-shader-2d" type="x-shader/x-vertex">
attribute vec2 a_texCoord;

attribute vec2 a_position;
uniform vec2 u_resolution;

varying vec2 v_texCoord;

void main() {
    // преобразуем положение в пикселях к диапазону от 0.0 до 1.0
    vec2 zeroToOne = a_position / u_resolution;

    // преобразуем из 0->1 в 0->2
    vec2 zeroToTwo = zeroToOne * 2.0;

    // преобразуем из 0->2 в -1->+1 (пространство отсечения)
    vec2 clipSpace = zeroToTwo - 1.0;

    gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);

    v_texCoord = a_texCoord;
}
</script>

<script  id="fragment-shader-2d" type="not-js">
precision mediump float;


uniform sampler2D u_image; // наша текстура
uniform vec2 u_textureSize; // размер текстуры


varying vec2 v_texCoord; // texCoords, переданные из вершинного шейдера

void main(){
    // рассчитываем один пиксель в текстурных координатах
    vec2 onePixel = vec2(1.0, 1.0) / u_textureSize;

    // среднее между средним, правым и левым пикселем
    gl_FragColor = (
        texture2D(u_image, v_texCoord) +
        texture2D(u_image, v_texCoord + vec2(onePixel.x, 0.0)) +
        texture2D(u_image, v_texCoord + vec2(-onePixel.x, 0.0))) / 3.0;
}
</script>
<canvas id="glcanvas">


Ответы (1 шт):

Автор решения: Михаил Камахин

gl_FragColor принимает vec4
texture2D возвращает vec4 по координатам vec2 в текстуре

Это можно легче понять, если просто сделать переменные среднего, левого и правого пикселя

void main(){
    // рассчитываем один пиксель в текстурных координатах
    vec2 onePixel = vec2(1.0, 1.0) / u_textureSize;

    vec2 mediumPixel = v_texCoord;
    vec2 rightPixel = v_texCoord + vec2(onePixel.x, 0.0);
    vec2 leftPixel = v_texCoord + vec2(-onePixel.x, 0.0);

   // среднее между средним, правым и левым пикселем
   gl_FragColor = (
       texture2D(u_image, mediumPixel) +
       texture2D(u_image, rightPixel) +
       texture2D(u_image, leftPixel)
    ) / 3.0;
}

В итоге мы складываем три vec4, делим на 3, получаем средний цвет между этими тремя пикселями

→ Ссылка