【问题标题】:How to scale a texture in webgl?如何在 webgl 中缩放纹理?
【发布时间】:2018-11-22 17:39:23
【问题描述】:

我有一个大小为 800x600 的纹理。如何在 webgl <canvas> 上以其他尺寸缩放它并保持原始纵横比?假设绘图缓冲区和画布具有相同的尺寸。

【问题讨论】:

    标签: matrix webgl aspect-ratio


    【解决方案1】:

    鉴于 WebGL 只关心 clipsapce 坐标,您只需绘制一个 2 单位的四边形(-1 到 +1),然后根据画布的纵横比与图像的纵横比进行缩放。

    换句话说

      const canvasAspect = canvas.clientWidth / canvas.clientHeight;
      const imageAspect = image.width / image.height;
    
      let scaleY = 1;
      let scaleX = imageAspect / canvasAspect;
    

    请注意,您需要决定如何适应图像。 scaleY= 1 意味着图像将始终垂直适合,水平将只是它出现的任何内容。

    如果你想让它水平适合,那么你需要让 scaleX = 1

      let scaleX = 1;
      let scaleY = canvasAspect / imageAspect;
    

    如果你想contain那么

      let scaleY = 1;
      let scaleX = imageAspect / canvasAspect;
      if (scaleX > 1) {
        scaleY = 1 / scaleX;
        scaleX = 1;
      }
    

    如果你想cover那么

      let scaleY = 1;
      let scaleX = imageAspect / canvasAspect;
      if (scaleX < 1) {
        scaleY = 1 / scaleX;
        scaleX = 1;
      }
    

    let scaleMode = 'fitV';
    
    const gl = document.querySelector("canvas").getContext('webgl');
    const vs = `
    attribute vec4 position;
    uniform mat4 u_matrix;
    varying vec2 v_texcoord;
    void main() {
      gl_Position = u_matrix * position;
      v_texcoord = position.xy * .5 + .5;  // because we know we're using a -1 + 1 quad
    }
    `;
    const fs = `
    precision mediump float;
    varying vec2 v_texcoord;
    uniform sampler2D u_tex;
    void main() {
      gl_FragColor = texture2D(u_tex, v_texcoord);
    }
    `;
    
    let image = { width: 1, height: 1 }; // dummy until loaded
    const tex = twgl.createTexture(gl, {
      src: 'https://i.imgur.com/TSiyiJv.jpg',
      crossOrigin: 'anonymous',
    }, (err, tex, img) => {
      // called after image as loaded
      image = img;
      render();
    });
    
    const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
    const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
      position: {
        numComponents: 2,
        data: [
          -1, -1,  // tri 1
           1, -1,
          -1,  1,      
          -1,  1,  // tri 2
           1, -1,
           1,  1,
        ],
      }
    });
    
    
    function render() {
      // this line is not needed if you don't
      // care that the canvas drawing buffer size
      // matches the canvas display size
      twgl.resizeCanvasToDisplaySize(gl.canvas);
    
      gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
      gl.useProgram(programInfo.program);
      twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
    
      const canvasAspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
      const imageAspect = image.width / image.height;
      let scaleX;
      let scaleY;
    
      switch (scaleMode) {
        case 'fitV':
          scaleY = 1;
          scaleX = imageAspect / canvasAspect;
          break;
        case 'fitH':
          scaleX = 1;
          scaleY = canvasAspect / imageAspect;
          break;
        case 'contain':
          scaleY = 1;
          scaleX = imageAspect / canvasAspect;
          if (scaleX > 1) {
            scaleY = 1 / scaleX;
            scaleX = 1;
          }
          break;
        case 'cover':
          scaleY = 1;
          scaleX = imageAspect / canvasAspect;
          if (scaleX < 1) {
            scaleY = 1 / scaleX;
            scaleX = 1;
          }
          break;
      }
      
      twgl.setUniforms(programInfo, {
        u_matrix: [
          scaleX, 0, 0, 0,
          0, -scaleY, 0, 0,
          0, 0, 1, 0,
          0, 0, 0, 1,
        ],
      });
      gl.drawArrays(gl.TRIANGLES, 0, 6);
    }
    
    render();
    window.addEventListener('resize', render);
    document.querySelectorAll('button').forEach((elem) => {
      elem.addEventListener('click', setScaleMode);
    });
    
    function setScaleMode(e) {
     scaleMode = e.target.id;
     render();
    }
    html, body { 
      margin: 0;
      height: 100%;
    }
    canvas {
      width: 100%;
      height: 100%;
      display: block;
    }
    .ui {
      position: absolute;
      left: 0;
      top: 0;
    }
    <script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
    <canvas></canvas>
    <div class="ui">
      <button id="fitV">fit vertical</button>
      <button id="fitH">fit horizontal</button>
      <button id="contain">contain</button>
      <button id="cover">cover</button>
    </div>

    上面的代码使用 4x4 矩阵来应用比例

      gl_Position = u_matrix * position;
    

    也可以直接在秤中轻松通过

      uniform vec2 scale;
      ...
      gl_Position = vec4(scale * position.xy, 0, 1);
    

    【讨论】:

    • 我无法真正理解这部分:v_texcoord = position.xy * .5 + .5;(事实上,如果我省略它,我会得到纹理拉伸并重复边缘)。
    • 位置坐标从-1到+1。乘以 0.5 并加上 0.5 使它们从 0 变为 1,这就是您想要在四边形中显示整个纹理的值。见webglfundamentals.org/webgl/lessons/webgl-3d-textures.html
    • 我能得到一些关于你是如何得到缩放公式的指针吗?我最近遇到了一个缩放问题。我在 x 方向上拉伸,即水平方向有一些值并做 scaleY=scaleX /图像的纵横比..这里 scaleX 只是一些数字..图像已缩放但看起来在 X 方向上被拉伸
    • 有2个方面。画布的纵横比和图像的纵横比(还有四边形坐标的纵横比,但在上面的示例中,它的纵横比是 1.0,因此可以忽略)。当在画布上绘制时,设置 scaleY = 1 和 scaleX = 1/canvasAspect 将使四边形本身成为正方形。但我们需要将其缩放为该画布上的 imageAspect。您应该将代码作为 sn-p 发布在新问题中。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-10-15
    • 1970-01-01
    • 1970-01-01
    • 2012-01-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多