THREE.js - 大型int作为Uniform

时间:2015-07-07 18:04:05

标签: arrays three.js glsl webgl

我想为Three.js编写一个片段着色器,需要10000个整数的大数组。当我尝试在着色器的glsl代码中声明这样的数组时:

Application.Interactive = False
Application.Visible = False

然后着色器渲染器抛出

uniform int colorGrid[10000];

我还有哪些其他选择 - 如何将如此大量的数据传递给片段着色器?

2 个答案:

答案 0 :(得分:4)

纹理是大型数组。在纹理中传递整数有点困难但并非不可能(对于WebGL2,见下文)。您需要在纹理的红色,绿色,蓝色和Alpha通道上拆分整数值,或者FLOAT纹理,它将为您提供最多2 ^ 24th的整数值

要将整数打包到纹理中,您可以执行类似这样的操作

// assumes unsigned ints
setPixelFromInt(pixels, width, x, y, intValue) {
   var r = (intValue >> 24) & 0xFF;
   var g = (intValue >> 16) & 0xFF;
   var b = (intValue >>  8) & 0xFF;
   var a = (intValue >>  0) & 0xFF;
   var offset = (y * width + x) * 4;
   pixels[offset + 0] = r;
   pixels[offset + 1] = g;
   pixels[offset + 2] = b;
   pixels[offset + 3] = a;
}

var width = 100;
var height = 100;
var pixels = new Uint8Array(width * height * 4);

...

要在着色器中恢复您的值,请执行以下操作吗?

uniform vec2 textureDimensions;
uniform sampler2D arrayTexture;

int getValueFromTexture(sampler2D arrayTexture, vec2 textureDimensions, int index) {
  float x = mod(float(index), textureDimensions.x);
  float y = floor(float(index) / textureDimensions.x);
  vec2 uv = (vec2(x, y) + .5) / textureDimensions;
  vec4 color = texture2D(arrayTexture, uv);
  return int(color.r * 256.0 * 256.0 * 256.0 +
             color.b * 256.0 * 256.0 +
             color.g * 256.0 +
             color.a);
}

务必将过滤设置为gl.NEAREST

注意:我没有真正运行该代码,但它说明了这个想法

在WebGL2中,您可以使用8,16或32位的整数纹理,并且在着色器中有texelFetch函数,该函数将提取特定lod的特定纹素的值,而不进行过滤。还有textureSize功能,因此您无需手动传递制服中的纹理大小。

  const int lod = 0
  ivec2 textureDimensions = textureSize(arrayTexture, lod);
  int x = index % textureDimensions.x;
  int y = index / textureDimensions.x;
  ivec4 color = texelFetch(arrayTexture, ivec2(x,y), lod);

答案 1 :(得分:0)

@gman答案很有帮助,但是某些错误总是存在于未经测试的代码中;)。在getValueFromTexture中,他在return语句中使用rbga而不是rgbatexelFetch返回vec4而不是ivec4
(我正在使用#version es 300 webGL2)。

他的答案缺少的是THREE.js部分,这很令人困惑,花了我4小时以上的时间才能找到答案。

在谷歌搜索时,您可能会看到THREE.ImageUtils.generateDataTexture()的用法,但已将其删除:
Create texture from Array THREE.js
What happened to THREE.ImageUtils?

我还对版本进行了一些更改,因为我的着色器中需要2d数组(可以通过x和y访问)。

@gman函数的打字稿版本:

  export type uint = number;


  /**
   * Based on: https://stackoverflow.com/questions/31276114/three-js-large-array-of-int-as-uniform
   */
  function packIntegers(width: uint, height: uint, unsignedIntegers: Readonly<uint[][]>): Uint8Array {
    const pixels = new Uint8Array(width * height * 4);

    // assumes unsigned ints
    function setPixelFromInt(x: uint, y: uint, intValue: uint) {
      const r = (intValue >> 24) & 0xFF;
      const g = (intValue >> 16) & 0xFF;
      const b = (intValue >>  8) & 0xFF;
      const a = (intValue >>  0) & 0xFF;
      const offset = (y * width + x) * 4;
      // noinspection PointlessArithmeticExpressionJS
      pixels[offset + 0] = r;
      pixels[offset + 1] = g;
      pixels[offset + 2] = b;
      pixels[offset + 3] = a;
    }

    for (let x = 0; x < unsignedIntegers.length; x++) {
      for (let y = 0; y < unsignedIntegers[x].length; y++) {
        setPixelFromInt(x, y, unsignedIntegers[x][y]);
      }
    }

    return pixels;
  }

创建THREE.DataTexture时,您将以统一的方式通过(不要像我这样犯错误,尝试以sampler2D统一方式传递Uint8Array)。 https://threejs.org/docs/#api/en/textures/DataTexture

  function getMandala2DInfo(pattern: TrianglePattern): THREE.DataTexture {
    // Your Uint8Array made with `packIntegers` function. `getTextureArray` call `packIntegers` in my code.
    const data: Uint8Array = getTextureArray(pattern.slice().map(e => e.slice().map(e => e-1)).reverse());

    // should be the width in pixels of your texture
    const width = pattern[0].length;

    // should be the height in pixels of your texture
    const height = pattern.length;

    const texture = new THREE.DataTexture(
        // a quick way to convert your Uint8Array, but it is better to start with a Float32Array
        new Float32Array(Array.from(data)), // important

        width,
        height,
        THREE.RGBAFormat,

        // UnsignedByteType is default... Refer to doc (not typing) to know what are defaults.
        THREE.FloatType, // important
    );

    return texture;
  }

将制服添加到您的材料中。

      const mat = new THREE.ShaderMaterial({
        uniforms: {
          // [...]
          mandalaInfo2D: {
            type: "t",
            value: getMandala2DInfo(props.pattern)
          }
        } as any,
        // [...]
      });

您可以稍后更新制服:

      mat.uniforms.mandalaInfo2D.value = getMandala2DInfo(props.pattern);

着色器代码,请确保具有正确的顺序rgba。

uniform sampler2D mandalaInfo2D;
const int lod = 0;

int getValueFromTexture(in sampler2D arrayTexture2, in ivec2 index)
{
    vec4 color = texelFetch(arrayTexture2, index, lod);
    return int(color.r * 256.0 * 256.0 * 256.0 +
               color.g * 256.0 * 256.0 +
               color.b * 256.0 +
               color.a);
}

现在有了代码,您应该怀疑为什么我们使用Float32Array。我一直在寻找答案的是https://dev.to/nicolasrannou/create-textures-from-data-in-threejs-5bap#what-is-confusing--me--

所有整数纹理(包括UnsignedByteType)在上载到着色器时会自动归一化,而浮动/整体纹理(包括Float和HalfFloat)将按原样传递。

这也很重要,但仅凭https://www.khronos.org/opengl/wiki/Sampler_(GLSL)本身找不到问题即可:

纹理坐标可以标准化或在texel空间中。归一化的纹理坐标意味着纹理的大小映射到每个维度中范围[0,1]上的坐标。这允许纹理坐标独立于任何特定纹理的大小。纹素空间纹理坐标表示坐标在[0,size]范围内,其中size是该维度上纹理的大小。

矩形纹理始终在纹理空间中获取纹理坐标。除非另有说明,否则所有其他纹理坐标将被标准化。

THREE.js文档对此行为保持沉默:https://threejs.org/docs/#api/en/constants/Textures

用于纹理的type属性,该属性必须与正确的格式相对应。有关详情,请参见下文。

默认为UnsignedByteType。

在dev.to上赞扬Nicolas,因为当它仍然对Three.js和glsl还是陌生的时,要弄清楚哪里出了问题(以及在哪里)是一件令人烦恼的事情。 (让我想知道是否不应该使用比Three.js更浅的颜色来渲染着色器。)


编辑:搜索时,您可能还会看到texture.needsUpdate = true;的用法,我不需要它。我还看到了Prevent DataTexture value normalization in THREE,也许除了THREE.RGBAFormat与usampler2d结合使用以外,其他方法也可能给出正确的结果(我尚未测试)。