从现有纹理创建 2D 纹理

Create a 2D texture from existing textures

我有一个应用程序,我可以在其中加载一系列医学图像 (jpg),为每个图像创建一个纹理对象并在 3D 平面上显示这些纹理。图像数量取决于 CT 扫描仪的分辨率,但我的原型最多可处理 512 张图像。

作为一项附加任务,我想使用这些纹理在单独的 3D 上执行体积渲染 canvas。

所有解决 WebGL 中缺乏 3D 纹理问题的算法都有一个 "silent" 先决条件,即图像格式的纹理图集已经存在。

但我没有这样的地图集。

  1. 假设硬件支持此 2D 纹理图集的最终大小,我如何将已经加载的纹理裁剪成一个单一的 2D 纹理并将其提供给着色器?

  2. 我考虑过将每个图像的数据合并到一个数组中,并用它创建 THREE.DataTexture。但是我找不到从使用图像的纹理中读取图像数据的方法。还有其他方法可以提取加载图像的数据吗?

最简单的方法可能是将纹理加载到 2d canvas 中以构建图集。

假设我们有一个下载 512 个纹理的函数,我们想将它们放在 32 x 16 的网格中

var width = 128;  // just assuming this is the size of a single texture
var height = 128; 
var across = 32;
var down = 16;

asyncLoad512Images(useLoaded512Images);

function useLoaded512Images(arrayOfImages) {
  var canvas = document.createElement("canvas");
  canvas.width = width * across;
  canvas.height = height * down;
  var ctx = canvas.getContext("2d");

  // draw all the textures into the canvas
  arrayOfImagess.forEach(function(image, ndx) {
    var x = ndx % across;
    var y = Math.floor(ndx / across);

    ctx.drawImage(image, x * width, y * height);
  }

  // now make a texture from canvas.
  var atlasTex = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, atlasTex);
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE,
                canvas);
}

一些优化:您可以更改代码,以便在开头制作 canvas 并在加载每个图像时将其绘制到正确位置的 2d canvas 中。优点是浏览器不需要将所有 512 个图像保存在内存中。它可以在您绘制后立即丢弃每一个。

var width = 128;  // just assuming this is the size of a single texture
var height = 128; 
var across = 32;
var down = 16;
var numImages = 32 * 16;
var numImagesDownloaded = 0;

// make a canvas to hold all the slices
var canvas = document.createElement("canvas");
canvas.width = width * across;
canvas.height = height * down;
var ctx = canvas.getContext("2d");

// assume the images are named image-x.png
for (var ii = 0; ii < numImages; ++ii) {
  loadImage(ii);
}

function loadImage(num) {
  var img = new Image();
  img.onload = putImageInCanvas(img, num);
  img.src = "image-" + num + ".png";
}

function putImageInCanvas(img, num) {
  var x = num % across;
  var y = Math.floor(num / across);

  ctx.drawImage(img, x * width, y * height);

  ++numImagesDownloaded;
  if (numImagesDownloaded === numImages) {
    // now make a texture from canvas.
    var atlasTex = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, atlasTex);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE,
                  canvas);
    ....
  }
}

或者,您可以将每个图像转换为纹理,并使用附加到帧缓冲区的纹理将图像纹理渲染到图集纹理中。那是更多的工作。您需要制作一对简单的 2d 着色器,然后将每个图像纹理渲染到正确位置的图集纹理。

这样做的唯一原因是如果纹理有 4 个数据通道而不是 3 个或更少,因为自 2d 以来无法使用 2d canvas 的所有 4 个通道canvas 始终使用预乘 alpha。

将纹理绘制成纹理与绘制周期相同。 See any example that draws into a texture.

three.js 中的简短版本是

制作渲染目标

rtTexture = new THREE.WebGLRenderTarget( 
      width * across, height * down, { 
        minFilter: THREE.LinearFilter, 
        magFilter: THREE.NearestFilter, 
        format: THREE.RGBFormat,
        depthBuffer: false,
        stencilBuffer: false,
    } ); 
 rtTexture.generateMipmaps = false;

设置一个平面和一个material来渲染,把它放在场景中。对于每个图像纹理设置 material 以使用该图像纹理并设置任何其他参数以使其在您希望将其绘制到图集纹理中的位置绘制四边形。 I'm guessing an orthographic camera would make that easiest。然后使用渲染目标调用渲染。

renderer.autoClear = false;
renderer.render( sceneRTT, cameraRTT, rtTexture, false );

这将呈现为 rtTexture

完成后 rtTexture 是您的图集纹理。就像使用任何纹理一样使用纹理。将其分配给 material.