【发布时间】:2018-11-22 17:39:23
【问题描述】:
我有一个大小为 800x600 的纹理。如何在 webgl <canvas> 上以其他尺寸缩放它并保持原始纵横比?假设绘图缓冲区和画布具有相同的尺寸。
【问题讨论】:
标签: matrix webgl aspect-ratio
我有一个大小为 800x600 的纹理。如何在 webgl <canvas> 上以其他尺寸缩放它并保持原始纵横比?假设绘图缓冲区和画布具有相同的尺寸。
【问题讨论】:
标签: matrix webgl aspect-ratio
鉴于 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;(事实上,如果我省略它,我会得到纹理拉伸并重复边缘)。