如何在 webgl 中缩放纹理?

How to scale a texture in webgl?

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

鉴于 WebGL 只关心 clipsapce 坐标,您可以绘制一个 2 单位四边形(-1 到 +1)并根据 canvas 与图像的纵横比缩放它。

换句话说

  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);