我想为Three.js编写一个片段着色器,需要10000个整数的大数组。当我尝试在着色器的glsl代码中声明这样的数组时:
Application.Interactive = False
Application.Visible = False
然后着色器渲染器抛出
uniform int colorGrid[10000];
我还有哪些其他选择 - 如何将如此大量的数据传递给片段着色器?
答案 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
而不是rgba
。 texelFetch
返回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
结合使用以外,其他方法也可能给出正确的结果(我尚未测试)。