如何在 js 中将 2d 着色器应用于 webgl canvas?

How do I apply a 2d shader to a webgl canvas in js?

我希望我在这里没有采取错误的方法,但我觉得我在正确的轨道上,这应该不会太复杂。我想在屏幕上采用简单的 x 和 y 函数,return 将颜色应用到 webGL 的每个像素 canvas。

例如:f(x,y) -> rgb(x/canvasWidth,y/canvasHeight) 其中 x 和 y 是 canvas 上的位置,颜色是像素的颜色。

我的第一个想法是采用 this example 并对其进行修改,使矩形充满屏幕并且颜色与描述的一样。我认为这是通过修改顶点着色器使矩形覆盖 canvas 和片段着色器来实现颜色来实现的,但我不确定如何根据 window 大小应用顶点着色器或获取我在片段着色器上下文中的 x 和 y 变量。

这是我即将结束的教程的 shader code。除了手动更改片段着色器中的常量颜色并通过更改 intitBuffers 方法中的值来改变正方形之外,我没有尝试太多。

你在正确的轨道上,为了让四边形填满屏幕,你可以跳过顶点着色器中的所有矩阵变换,只需将标准化的设备坐标 (-1 ... +1) 直接输入 gl_Position 然后使用 gl_FragCoord 访问片段着色器中像素的位置。

您需要传入 canvas 宽度和 canvas 高度。您的片段着色器可能如下所示:

    precision mediump float;

    // Require resolution (canvas size) as an input
    uniform vec3 uResolution;
      
    void main() {
      // Calculate relative coordinates (uv)
      vec2 uv = gl_FragCoord.xy / uResolution.xy;

      gl_FragColor = vec4(uv.x, uv.y, 0., 1.0);
    }

然后根据@LJ 的回答,如果您真的希望片段覆盖整个 canvas,您可以修改顶点着色器以忽略法线矩阵变换:

    void main() {
      // Pass through each vertex position without transforming:
      gl_Position = aVertexPosition;
    }

下面的可运行示例主要是 copy-pasted 来自您链接的 example,稍作修改:

const canvas = document.querySelector('#glcanvas');
main();

//
// Start here
//
function main() {
  const gl = canvas.getContext('webgl');
  // If we don't have a GL context, give up now

  if (!gl) {
    alert('Unable to initialize WebGL. Your browser or machine may not support it.');
    return;
  }

  // Vertex shader program

  const vsSource = `
    attribute vec4 aVertexPosition;

    uniform mat4 uModelViewMatrix;
    uniform mat4 uProjectionMatrix;

    void main() {
      // We don't need the projection:
      //gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;

      // Instead we pass through each vertex position as-is:
      gl_Position = aVertexPosition;
    }
  `;

  // Fragment shader program

  const fsSource = `
    precision mediump float;


    // Require resolution (canvas size) as an input
    uniform vec3 uResolution;
      
    void main() {

      // Calculate relative coordinates (uv)
      vec2 uv = gl_FragCoord.xy / uResolution.xy;

      gl_FragColor = vec4(uv.x, uv.y, 0., 1.0);
    }
  `;  

  // Initialize a shader program; this is where all the lighting
  // for the vertices and so forth is established.
  const shaderProgram = initShaderProgram(gl, vsSource, fsSource);

  // Collect all the info needed to use the shader program.
  // Look up which attribute our shader program is using
  // for aVertexPosition and look up uniform locations.
  const programInfo = {
    program: shaderProgram,
    attribLocations: {
      vertexPosition: gl.getAttribLocation(shaderProgram, 'aVertexPosition'),
    },
    uniformLocations: {
      projectionMatrix: gl.getUniformLocation(shaderProgram, 'uProjectionMatrix'),
      modelViewMatrix: gl.getUniformLocation(shaderProgram, 'uModelViewMatrix'),
      resolution: gl.getUniformLocation(shaderProgram, 'uResolution'),
    },
  };

  // Here's where we call the routine that builds all the
  // objects we'll be drawing.
  const buffers = initBuffers(gl);

  // Draw the scene
  drawScene(gl, programInfo, buffers);
}

//
// initBuffers
//
// Initialize the buffers we'll need. For this demo, we just
// have one object -- a simple two-dimensional square.
//
function initBuffers(gl) {

  // Create a buffer for the square's positions.

  const positionBuffer = gl.createBuffer();

  // Select the positionBuffer as the one to apply buffer
  // operations to from here out.

  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

  // Now create an array of positions for the square.

  const positions = [
     1.0,  1.0,
    -1.0,  1.0,
     1.0, -1.0,
    -1.0, -1.0,
  ];

  // Now pass the list of positions into WebGL to build the
  // shape. We do this by creating a Float32Array from the
  // JavaScript array, then use it to fill the current buffer.

  gl.bufferData(gl.ARRAY_BUFFER,
                new Float32Array(positions),
                gl.STATIC_DRAW);

  return {
    position: positionBuffer,
  };
}

//
// Draw the scene.
//
function drawScene(gl, programInfo, buffers) {
  gl.clearColor(0.0, 0.0, 0.0, 1.0);  // Clear to black, fully opaque
  gl.clearDepth(1.0);                 // Clear everything
  gl.enable(gl.DEPTH_TEST);           // Enable depth testing
  gl.depthFunc(gl.LEQUAL);            // Near things obscure far things

  // Clear the canvas before we start drawing on it.

  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

  // Create a perspective matrix, a special matrix that is
  // used to simulate the distortion of perspective in a camera.
  // Our field of view is 45 degrees, with a width/height
  // ratio that matches the display size of the canvas
  // and we only want to see objects between 0.1 units
  // and 100 units away from the camera.

  const fieldOfView = 45 * Math.PI / 180;   // in radians
  const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
  const zNear = 0.1;
  const zFar = 100.0;
  const projectionMatrix = mat4.create();

  // note: glmatrix.js always has the first argument
  // as the destination to receive the result.
  mat4.perspective(projectionMatrix,
                   fieldOfView,
                   aspect,
                   zNear,
                   zFar);

  // Set the drawing position to the "identity" point, which is
  // the center of the scene.
  const modelViewMatrix = mat4.create();

  // Now move the drawing position a bit to where we want to
  // start drawing the square.

  mat4.translate(modelViewMatrix,     // destination matrix
                 modelViewMatrix,     // matrix to translate
                 [-0.0, 0.0, -6]);  // amount to translate

  // Tell WebGL how to pull out the positions from the position
  // buffer into the vertexPosition attribute.
  {
    const numComponents = 2;
    const type = gl.FLOAT;
    const normalize = false;
    const stride = 0;
    const offset = 0;
    gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position);
    gl.vertexAttribPointer(
        programInfo.attribLocations.vertexPosition,
        numComponents,
        type,
        normalize,
        stride,
        offset);
    gl.enableVertexAttribArray(
        programInfo.attribLocations.vertexPosition);
  }

  // Tell WebGL to use our program when drawing

  gl.useProgram(programInfo.program);

  // Set the shader uniforms

  gl.uniformMatrix4fv(
      programInfo.uniformLocations.projectionMatrix,
      false,
      projectionMatrix);
  gl.uniformMatrix4fv(
      programInfo.uniformLocations.modelViewMatrix,
      false,
      modelViewMatrix);
  
  gl.uniform3f(programInfo.uniformLocations.resolution, canvas.width, canvas.height, 1.0);  

  {
    const offset = 0;
    const vertexCount = 4;
    gl.drawArrays(gl.TRIANGLE_STRIP, offset, vertexCount);
  }
}

//
// Initialize a shader program, so WebGL knows how to draw our data
//
function initShaderProgram(gl, vsSource, fsSource) {
  const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
  const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);

  // Create the shader program

  const shaderProgram = gl.createProgram();
  gl.attachShader(shaderProgram, vertexShader);
  gl.attachShader(shaderProgram, fragmentShader);
  gl.linkProgram(shaderProgram);

  // If creating the shader program failed, alert

  if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
    alert('Unable to initialize the shader program: ' + gl.getProgramInfoLog(shaderProgram));
    return null;
  }

  return shaderProgram;
}

//
// creates a shader of the given type, uploads the source and
// compiles it.
//
function loadShader(gl, type, source) {
  const shader = gl.createShader(type);

  // Send the source to the shader object

  gl.shaderSource(shader, source);

  // Compile the shader program

  gl.compileShader(shader);

  // See if it compiled successfully

  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    alert('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader));
    gl.deleteShader(shader);
    return null;
  }

  return shader;
}
canvas {
    border: 2px solid black;
    background-color: black;
}
video {
    display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.8.1/gl-matrix-min.js"></script>
<body>
  <canvas id="glcanvas" width="640" height="480"></canvas>
</body>

故障相同: