加载图像,缩放,然后重叠 - 标记图像

Load image, scale, then overlap - badge an image

我当前的所有代码都在一个 worker 中。我现在需要一个简单的图形问题。我有两个已知 width/height 的正方形图像。我需要将第一个绘制为 64x64,然后将第二个绘制为 16x16 并放置在右下角。最终图像为 64x64。我基本上是想让第二张图片成为第一张图片上的徽章。

所以现在这是 2d canvas 中的小菜一碟,但是我不能(出于某些原因)与文档进行通信,我必须在 canvas 中完成这一切,在 Firefox 中我们从 Firefox 44(去年)开始支持 webgl canvas。所以我正在努力做到这一点。

这是我从网上收集的内容。我的 drawImage 方法需要正常工作,我将其设置为接受参数,但我删除了所有尊重它的代码,因为它确实破坏了东西。我必须编辑 vec4 第二个 arg 以进行缩放(例如 1.5 将使其缩放到一半大小),但我无法弄清楚如何定位。

function doit() {
 var img, tex, vloc, tloc, vertexBuff, texBuff;

 var cvs3d = document.getElementById('cvs');
 var ctx3d = cvs3d.getContext('experimental-webgl');
 var uLoc;

 // create shaders
 var vertexShaderSrc =
  'attribute vec2 aVertex;' +
  'attribute vec2 aUV;' +
  'varying vec2 vTex;' +
  'uniform vec2 pos;' +
  'void main(void) {' +
  '  gl_Position = vec4(aVertex + pos, 0.0, 1.0);' +
  '  vTex = aUV;' +
  '}';

 var fragmentShaderSrc =
  'precision highp float;' +
  'varying vec2 vTex;' +
  'uniform sampler2D sampler0;' +
  'void main(void){' +
  '  gl_FragColor = texture2D(sampler0, vTex);' +
  '}';

 var vertShaderObj = ctx3d.createShader(ctx3d.VERTEX_SHADER);
 var fragShaderObj = ctx3d.createShader(ctx3d.FRAGMENT_SHADER);
 ctx3d.shaderSource(vertShaderObj, vertexShaderSrc);
 ctx3d.shaderSource(fragShaderObj, fragmentShaderSrc);
 ctx3d.compileShader(vertShaderObj);
 ctx3d.compileShader(fragShaderObj);

 var progObj = ctx3d.createProgram();
 ctx3d.attachShader(progObj, vertShaderObj);
 ctx3d.attachShader(progObj, fragShaderObj);

 ctx3d.linkProgram(progObj);
 ctx3d.useProgram(progObj);

 ctx3d.viewport(0, 0, 64, 64);

 vertexBuff = ctx3d.createBuffer();
 ctx3d.bindBuffer(ctx3d.ARRAY_BUFFER, vertexBuff);
 ctx3d.bufferData(ctx3d.ARRAY_BUFFER, new Float32Array([-1, 1, -1, -1, 1, -1, 1, 1]), ctx3d.STATIC_DRAW);

 texBuff = ctx3d.createBuffer();
 ctx3d.bindBuffer(ctx3d.ARRAY_BUFFER, texBuff);
 ctx3d.bufferData(ctx3d.ARRAY_BUFFER, new Float32Array([0, 1, 0, 0, 1, 0, 1, 1]), ctx3d.STATIC_DRAW);

 vloc = ctx3d.getAttribLocation(progObj, 'aVertex');
 tloc = ctx3d.getAttribLocation(progObj, 'aUV');
 uLoc = ctx3d.getUniformLocation(progObj, 'pos');

 var drawImage = function(imgobj, x, y, w, h) {
  tex = ctx3d.createTexture();
  ctx3d.bindTexture(ctx3d.TEXTURE_2D, tex);
  ctx3d.texParameteri(ctx3d.TEXTURE_2D, ctx3d.TEXTURE_MIN_FILTER, ctx3d.NEAREST);
  ctx3d.texParameteri(ctx3d.TEXTURE_2D, ctx3d.TEXTURE_MAG_FILTER, ctx3d.NEAREST);
  ctx3d.texImage2D(ctx3d.TEXTURE_2D, 0, ctx3d.RGBA, ctx3d.RGBA, ctx3d.UNSIGNED_BYTE, imgobj);

  ctx3d.enableVertexAttribArray(vloc);
  ctx3d.bindBuffer(ctx3d.ARRAY_BUFFER, vertexBuff);
  ctx3d.vertexAttribPointer(vloc, 2, ctx3d.FLOAT, false, 0, 0);

  ctx3d.enableVertexAttribArray(tloc);
  ctx3d.bindBuffer(ctx3d.ARRAY_BUFFER, texBuff);
  ctx3d.bindTexture(ctx3d.TEXTURE_2D, tex);
  ctx3d.vertexAttribPointer(tloc, 2, ctx3d.FLOAT, false, 0, 0);

  ctx3d.drawArrays(ctx3d.TRIANGLE_FAN, 0, 4);
 };

 img = new Image();
 img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAAM9JREFUeNrs2+EJgzAQBtBccTIXcQ8HcA8XcbV0gjZiONKS9/1VAnl43KExaq2lJxHRt0B/4tvF1v5eZfIAAAAAAICZE60+2erz53EN3cC2r11zghIAAAAAAAAzzwGllJ/u89lzghIAAAAAAAATZ8nus71zRPb6SgAAAAAAAJgDnif7fUH2+koAAAAAAACYA/Jy4/u9OUAJAAAAAACAMYkb9/z1OcHzuJwTBAAAAAAAAB7OAa0+v+3r0P8GW33eEwAAAAAAAAB8zBsAAP//AwB6eysS2pA5KAAAAABJRU5ErkJggg==';

 img.onload = function() {
  console.log('drawing base image now');
  drawImage(this, 0, 0, 64, 64);

  var img2 = new Image();
  img2.src = 'data:image/png;base64,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';
  img2.onload = function() {
    drawImage(img2, 64-16, 64-16, 16, 16); // draw in bottom right corner
  }
 };
}

window.onload = function() {
 doit();
}
#cvs {
  border: 1px solid red;
}
<canvas id="cvs" width=64 height=64></canvas>

这个问题有一百万个答案,因为 WebGL 是一个光栅化库

例如

  1. 您可以调整视口

  2. 您可以在 pos 之上添加一个 uniform vec2 scale,因为没有秤,您无法制作四边形 smaller/larger

  3. 您可以在绘制前更新顶点。

  4. 您可以将 aVertex 乘以 uniform mat3 matrix,这样您就可以任意定位、缩放和旋转

  5. 您可以将 aVertex 乘以 uniform mat4 matrix,这样您就可以任意定位、缩放、旋转和投影到 3d

...等...

5 这是标准解决方案,因为它最灵活。 This article goes over using a mat3 并在它之前展示你的方法以及为什么使用矩阵是更好的选择,然后它扩展到 mat4 来做全 3d。

所以不清楚你想要什么。您是想学习 "good way" (#5),还是只想让您的代码在尽可能少的更改下工作。

这里只是为了好玩#1

function doit() {
  var img, tex, vloc, tloc, vertexBuff, texBuff;

  var cvs3d = document.getElementById('cvs');
  var ctx3d = cvs3d.getContext('experimental-webgl', { 
    preserveDrawingBuffer: true, 
  });
  var uLoc;

  // create shaders
  var vertexShaderSrc =
      'attribute vec2 aVertex;' +
      'attribute vec2 aUV;' +
      'varying vec2 vTex;' +
      'uniform vec2 pos;' +
      'void main(void) {' +
      '  gl_Position = vec4(aVertex + pos, 0.0, 1.0);' +
      '  vTex = aUV;' +
      '}';

  var fragmentShaderSrc =
      'precision highp float;' +
      'varying vec2 vTex;' +
      'uniform sampler2D sampler0;' +
      'void main(void){' +
      '  gl_FragColor = texture2D(sampler0, vTex);' +
      '}';

  var vertShaderObj = ctx3d.createShader(ctx3d.VERTEX_SHADER);
  var fragShaderObj = ctx3d.createShader(ctx3d.FRAGMENT_SHADER);
  ctx3d.shaderSource(vertShaderObj, vertexShaderSrc);
  ctx3d.shaderSource(fragShaderObj, fragmentShaderSrc);
  ctx3d.compileShader(vertShaderObj);
  ctx3d.compileShader(fragShaderObj);

  var progObj = ctx3d.createProgram();
  ctx3d.attachShader(progObj, vertShaderObj);
  ctx3d.attachShader(progObj, fragShaderObj);

  ctx3d.linkProgram(progObj);
  ctx3d.useProgram(progObj);

  vertexBuff = ctx3d.createBuffer();
  ctx3d.bindBuffer(ctx3d.ARRAY_BUFFER, vertexBuff);
  ctx3d.bufferData(ctx3d.ARRAY_BUFFER, new Float32Array([-1, 1, -1, -1, 1, -1, 1, 1]), ctx3d.STATIC_DRAW);

  texBuff = ctx3d.createBuffer();
  ctx3d.bindBuffer(ctx3d.ARRAY_BUFFER, texBuff);
  ctx3d.bufferData(ctx3d.ARRAY_BUFFER, new Float32Array([0, 1, 0, 0, 1, 0, 1, 1]), ctx3d.STATIC_DRAW);

  vloc = ctx3d.getAttribLocation(progObj, 'aVertex');
  tloc = ctx3d.getAttribLocation(progObj, 'aUV');
  uLoc = ctx3d.getUniformLocation(progObj, 'pos');

  var drawImage = function(imgobj, x, y, w, h) {
    tex = ctx3d.createTexture();
    ctx3d.bindTexture(ctx3d.TEXTURE_2D, tex);
    ctx3d.texParameteri(ctx3d.TEXTURE_2D, ctx3d.TEXTURE_MIN_FILTER, ctx3d.NEAREST);
    ctx3d.texParameteri(ctx3d.TEXTURE_2D, ctx3d.TEXTURE_MAG_FILTER, ctx3d.NEAREST);
    ctx3d.texImage2D(ctx3d.TEXTURE_2D, 0, ctx3d.RGBA, ctx3d.RGBA, ctx3d.UNSIGNED_BYTE, imgobj);

    ctx3d.enableVertexAttribArray(vloc);
    ctx3d.bindBuffer(ctx3d.ARRAY_BUFFER, vertexBuff);
    ctx3d.vertexAttribPointer(vloc, 2, ctx3d.FLOAT, false, 0, 0);

    ctx3d.enableVertexAttribArray(tloc);
    ctx3d.bindBuffer(ctx3d.ARRAY_BUFFER, texBuff);
    ctx3d.bindTexture(ctx3d.TEXTURE_2D, tex);
    ctx3d.vertexAttribPointer(tloc, 2, ctx3d.FLOAT, false, 0, 0);

    ctx3d.viewport(x, y, w, h);
    ctx3d.drawArrays(ctx3d.TRIANGLE_FAN, 0, 4);
  };

  img = new Image();
  img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAnUlEQVR42u3byQ3AIAwEQKpPH2kkrZE0wCEQR8RY2i/Y8/IDQowx9OSruDjZKvYPAAAAAAAAAGge8L6epekFAgAAAAAAAE4G2H3AGUAAAAAAAAAAgDaA0YCj+wMAAAAAAAAAWIQAAAAAAAAAAPMXmQ3uBwAAAAAAAE4G+PX7gYr+Qi4AAAAAAAAAgHRVHLD0w0SpPwAAAAAAAABAMi8E/hnSV9Q3nQAAAABJRU5ErkJggg==';

  img.onload = function() {
    console.log('drawing base image now');
    drawImage(this, 0, 0, 64, 64);

    var img2 = new Image();
    img2.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAnUlEQVR42u3byQ3AIAwEQKpPH2kkrZE0wCEQR8RY2i/Y8/IDQowx9OSruDjZKvYPAAAAAAAAAGge8L6epekFAgAAAAAAAE4G2H3AGUAAAAAAAAAAgDaA0YCj+wMAAAAAAAAAWIQAAAAAAAAAAPMXmQ3uBwAAAAAAAE4G+PX7gYr+Qi4AAAAAAAAAgHRVHLD0w0SpPwAAAAAAAABAMi8E/hnSV9Q3nQAAAABJRU5ErkJggg==';
    img2.onload = function() {
      drawImage(img2, 64-16, 64-16, 16, 16); // draw in bottom right corner
    }
  };
}

window.onload = function() {
  doit();
}
#cvs {
  border: 1px solid red;
}
<canvas id="cvs" width=64 height=64></canvas>

请注意,默认情况下,WebGL 会在您下次渲染时清除 canvas,因此您渲染两次,每次图像加载后一次,这意味着您只会以第二个结果。为防止您需要将 { preserveDrawingBuffer: true, } 作为第二个参数传递给 getContext.

老实说,这感觉就像 "do my homework for me question"。就像您是否甚至查找过一篇关于 WebGL 的文章?我想我应该给你怀疑的好处,但这里有很多。

你知道WebGL only cares about clip space coordinates吗?

你知道WebGL -1在底部,+1在顶部吗?

你知道WebGL can't draw non-power of 2 images除非你设置各种纹理参数吗?您的示例有效,因为图像是 64x64,但如果它是 65x64,它将失败。

所以这会带来更多问题。

纹理颠倒了。同样,由您决定如何修复它们。

你可以

  1. 翻转纹理坐标

  2. 加载使用gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);

  3. 翻转的纹理
  4. 使用负比例(假设您使用上面的解决方案 #2)。这将使您的 pos 设置变得复杂

  5. 在着色器中调整纹理坐标

  6. 在着色器中取反 gl_Position.y

  7. 使用上面的解决方案 #4 (mat3) 或 #5 (mat4) 并创建一个投影矩阵使得翻转 space.

..等...

同样,矩阵解决方案是最好的解决方案,您真的应该去阅读我发布的文章。

也就是说,在 #2 解决方案中破解您当前的代码

function doit() {
  var img, tex, vloc, tloc, sloc, vertexBuff, texBuff;

  var cvs3d = document.getElementById('cvs');
  var ctx3d = cvs3d.getContext('experimental-webgl', { 
    preserveDrawingBuffer: true, 
  });
  var uLoc;

  // create shaders
  var vertexShaderSrc = `
      attribute vec2 aVertex;
      attribute vec2 aUV;
      varying vec2 vTex;
      uniform vec2 pos;
      uniform vec2 scale; 
      void main(void) {
        gl_Position = vec4(aVertex * scale + pos, 0.0, 1.0);
        vTex = aUV;
      }`;

  var fragmentShaderSrc = `
      precision highp float;
      varying vec2 vTex;
      uniform sampler2D sampler0;
      void main(void){
        gl_FragColor = texture2D(sampler0, vTex);
      }`;

  var vertShaderObj = ctx3d.createShader(ctx3d.VERTEX_SHADER);
  var fragShaderObj = ctx3d.createShader(ctx3d.FRAGMENT_SHADER);
  ctx3d.shaderSource(vertShaderObj, vertexShaderSrc);
  ctx3d.shaderSource(fragShaderObj, fragmentShaderSrc);
  ctx3d.compileShader(vertShaderObj);
  ctx3d.compileShader(fragShaderObj);

  var progObj = ctx3d.createProgram();
  ctx3d.attachShader(progObj, vertShaderObj);
  ctx3d.attachShader(progObj, fragShaderObj);

  ctx3d.linkProgram(progObj);
  ctx3d.useProgram(progObj);

  vertexBuff = ctx3d.createBuffer();
  ctx3d.bindBuffer(ctx3d.ARRAY_BUFFER, vertexBuff);
  ctx3d.bufferData(ctx3d.ARRAY_BUFFER, new Float32Array([-1, 1, -1, -1, 1, -1, 1, 1]), ctx3d.STATIC_DRAW);

  texBuff = ctx3d.createBuffer();
  ctx3d.bindBuffer(ctx3d.ARRAY_BUFFER, texBuff);
  ctx3d.bufferData(ctx3d.ARRAY_BUFFER, new Float32Array([0, 1, 0, 0, 1, 0, 1, 1]), ctx3d.STATIC_DRAW);

  vloc = ctx3d.getAttribLocation(progObj, 'aVertex');
  tloc = ctx3d.getAttribLocation(progObj, 'aUV');
  uLoc = ctx3d.getUniformLocation(progObj, 'pos');
  sLoc = ctx3d.getUniformLocation(progObj, 'scale');

  var drawImage = function(imgobj, x, y, w, h) {
    tex = ctx3d.createTexture();
    ctx3d.bindTexture(ctx3d.TEXTURE_2D, tex);
    ctx3d.texParameteri(ctx3d.TEXTURE_2D, ctx3d.TEXTURE_MIN_FILTER, ctx3d.NEAREST);
    ctx3d.texParameteri(ctx3d.TEXTURE_2D, ctx3d.TEXTURE_MAG_FILTER, ctx3d.NEAREST);
    ctx3d.texImage2D(ctx3d.TEXTURE_2D, 0, ctx3d.RGBA, ctx3d.RGBA, ctx3d.UNSIGNED_BYTE, imgobj);

    ctx3d.enableVertexAttribArray(vloc);
    ctx3d.bindBuffer(ctx3d.ARRAY_BUFFER, vertexBuff);
    ctx3d.vertexAttribPointer(vloc, 2, ctx3d.FLOAT, false, 0, 0);

    ctx3d.enableVertexAttribArray(tloc);
    ctx3d.bindBuffer(ctx3d.ARRAY_BUFFER, texBuff);
    ctx3d.bindTexture(ctx3d.TEXTURE_2D, tex);
    ctx3d.vertexAttribPointer(tloc, 2, ctx3d.FLOAT, false, 0, 0);
    
    // convert x, y to clip space (assuming viewport matches canvas size)
    var cx = x / ctx3d.canvas.width * 2 - 1;
    var cy = y / ctx3d.canvas.height * 2 - 1;
    
    // convert w, h to clip space (quad is 2 units big)
    var cw = w / ctx3d.canvas.width;
    var ch = h / ctx3d.canvas.height;
    
    // because the quad centered over 0.0 we have to add in 
    // half the width and height (cw, ch are already half because
    // it's 2 unit quad
    cx += cw;
    cy += ch;
    
    // then we negate cy and ch because webgl -1 is at the bottom
    ctx3d.uniform2f(uLoc, cx, -cy)
    ctx3d.uniform2f(sLoc, cw, -ch);

    ctx3d.drawArrays(ctx3d.TRIANGLE_FAN, 0, 4);
  };

  img = new Image();
  img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAnUlEQVR42u3byQ3AIAwEQKpPH2kkrZE0wCEQR8RY2i/Y8/IDQowx9OSruDjZKvYPAAAAAAAAAGge8L6epekFAgAAAAAAAE4G2H3AGUAAAAAAAAAAgDaA0YCj+wMAAAAAAAAAWIQAAAAAAAAAAPMXmQ3uBwAAAAAAAE4G+PX7gYr+Qi4AAAAAAAAAgHRVHLD0w0SpPwAAAAAAAABAMi8E/hnSV9Q3nQAAAABJRU5ErkJggg==';

  img.onload = function() {
    console.log('drawing base image now');
    drawImage(this, 0, 0, 64, 64);

    var img2 = new Image();
    img2.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAnUlEQVR42u3byQ3AIAwEQKpPH2kkrZE0wCEQR8RY2i/Y8/IDQowx9OSruDjZKvYPAAAAAAAAAGge8L6epekFAgAAAAAAAE4G2H3AGUAAAAAAAAAAgDaA0YCj+wMAAAAAAAAAWIQAAAAAAAAAAPMXmQ3uBwAAAAAAAE4G+PX7gYr+Qi4AAAAAAAAAgHRVHLD0w0SpPwAAAAAAAABAMi8E/hnSV9Q3nQAAAABJRU5ErkJggg==';
    img2.onload = function() {
      drawImage(img2, 64-16, 64-16, 16, 16); // draw in bottom right corner
    }
  };
}

doit();
#cvs {
  border: 1px solid red;
}
<canvas id="cvs" width=64 height=64></canvas>

我个人建议你 read up on WebGL including how to implement drawImage and how to create a matrix stack

让我补充一点,pos & scale 解决方案本身没有任何问题。 4x4 矩阵最多需要 64 次乘法来计算,并以 drawImage 克隆的标准方式使用它们,它必须至少执行 2 次矩阵乘法,因此有 128 次数字 * 数字乘法。而 pos & scale 解决方案仅使用 6 次乘法。换句话说,它在技术上更快,但不太可能成为瓶颈。

pos & scale 解决方案的另一个优点是它仅使用 2 个 vec2 制服(4 个浮点数),而 mat4 矩阵解决方案使用 mat4(16 个浮点数)。由于您可以使用的制服数量有上限,因此在某些情况下可能需要为其他东西腾出空间,因此 pos & scale 解决方案更好。这就是 WebGL 的乐趣,由您决定使用哪种技术

PS: pngcrush 是你的朋友