如果 DataTextures 不是正方形 (1:1),会出现奇怪的行为

Weird behavior if DataTextures are not square (1:1)

我有一对着色器程序,如果我的 DataTextures 是方形的 (1:1),一切都很好,但如果一个或两个是 2:1(宽度:高度)比率,行为就会变得混乱向上。我可以用未使用的填充符扩展每个缓冲区,以确保它们始终是方形的,但这在长 运行 中似乎不必要地昂贵 (memory-wise),因为两个缓冲区大小之一非常大开始。在这种情况下有没有办法处理 2:1 缓冲区?

我有一对着色器程序:

  1. 第一个是用于为我的程序计算物理的单个片段着色器(它写出一个纹理 tPositions 以供第二组着色器读取)。它由 Three.js 的 GPUComputeRenderer 脚本驱动(分辨率设置为我最大缓冲区的大小。)
  2. 第二对着色器(vert 和 frag)使用第一个着色器程序生成的数据纹理 tPositions 渲染可视化效果(分辨率设置为 window 大小)。

可视化是由各种形状的粒子云组成的网格。在着色器程序中,有两种不同大小的纹理:较小尺寸的纹理包含每个粒子云的信息(每个云一个纹素),较大尺寸的纹理包含所有云中每个粒子的信息(每个粒子一个纹素) ).两者都有一定数量的未使用的填充物固定在末端,以将它们填充到 2 的幂。

Texel-per-particle 大小的纹理(大): tPositionstOffsets

Texel-per-cloud 大小的纹理(小): tGridPositionsAndSeeds, tSelectionFactors

正如我之前所说,问题是当这两个缓冲区大小(大和小)处于 1:1(宽度:高度)比率时,程序工作正常;但是,当其中一个或两者处于 2:1(宽度:高度)比率时,行为就会变得一团糟。是什么原因造成的,我该如何解决?提前致谢!

更新: 问题是否与我在第二个着色器中读取着色器 position 属性中的纹理元素坐标有关程序?如果是这样,也许这个 Github issue 关于 position 属性中的纹素坐标可能是相关的,尽管我在 SO 上找不到相应的 question/answer。

更新 2: 我也在研究这是否可能是 unpack alignment issue。想法?

这是第一个着色器程序在 Three.js 中的设置:

function initComputeRenderer() {

    textureData = MotifGrid.getBufferData();

    gpuCompute = new GPUComputationRenderer( textureData.uPerParticleBufferWidth, textureData.uPerParticleBufferHeight, renderer );

    dtPositions = gpuCompute.createTexture();
    dtPositions.image.data = textureData.tPositions;

    offsetsTexture = new THREE.DataTexture( textureData.tOffsets, textureData.uPerParticleBufferWidth, textureData.uPerParticleBufferHeight, THREE.RGBAFormat, THREE.FloatType );
    offsetsTexture.needsUpdate = true;

    gridPositionsAndSeedsTexture = new THREE.DataTexture( textureData.tGridPositionsAndSeeds, textureData.uPerMotifBufferWidth, textureData.uPerMotifBufferHeight, THREE.RGBAFormat, THREE.FloatType );
    gridPositionsAndSeedsTexture.needsUpdate = true;

    selectionFactorsTexture = new THREE.DataTexture( textureData.tSelectionFactors, textureData.uPerMotifBufferWidth, textureData.uPerMotifBufferHeight, THREE.RGBAFormat, THREE.FloatType );
    selectionFactorsTexture.needsUpdate = true;

    positionVariable = gpuCompute.addVariable( "tPositions", document.getElementById( 'position_fragment_shader' ).textContent, dtPositions );
    positionVariable.wrapS = THREE.RepeatWrapping; // repeat wrapping for use only with bit powers: 8x8, 16x16, etc.
    positionVariable.wrapT = THREE.RepeatWrapping;

    gpuCompute.setVariableDependencies( positionVariable, [ positionVariable ] );

    positionUniforms = positionVariable.material.uniforms;
    positionUniforms.tOffsets = { type: "t", value: offsetsTexture };
    positionUniforms.tGridPositionsAndSeeds = { type: "t", value: gridPositionsAndSeedsTexture };
    positionUniforms.tSelectionFactors = { type: "t", value: selectionFactorsTexture };
    positionUniforms.uPerMotifBufferWidth = { type : "f", value : textureData.uPerMotifBufferWidth };
    positionUniforms.uPerMotifBufferHeight = { type : "f", value : textureData.uPerMotifBufferHeight };
    positionUniforms.uTime = { type: "f", value: 0.0 };
    positionUniforms.uXOffW = { type: "f", value: 0.5 };

}

这里是第一个shader程序(只是物理计算的frag):

   // tPositions is handled by the GPUCompute script
    uniform sampler2D tOffsets; 
    uniform sampler2D tGridPositionsAndSeeds;
    uniform sampler2D tSelectionFactors;
    uniform float uPerMotifBufferWidth;
    uniform float uPerMotifBufferHeight;
    uniform float uTime;
    uniform float uXOffW;

    [...skipping a noise function for brevity...]

    void main() {

        vec2 uv = gl_FragCoord.xy / resolution.xy;

        vec4 offsets = texture2D( tOffsets, uv ).xyzw;
        float alphaMass = offsets.z;
        float cellIndex = offsets.w;

        if (cellIndex >= 0.0) {

            float damping = 0.98;

            float texelSizeX = 1.0 / uPerMotifBufferWidth;
            float texelSizeY = 1.0 / uPerMotifBufferHeight;
            vec2 perMotifUV = vec2( mod(cellIndex, uPerMotifBufferWidth)*texelSizeX, floor(cellIndex / uPerMotifBufferHeight)*texelSizeY );
            perMotifUV += vec2(0.5*texelSizeX, 0.5*texelSizeY);

            vec4 selectionFactors = texture2D( tSelectionFactors, perMotifUV ).xyzw;
            float swapState = selectionFactors.x;
            vec4 gridPosition = texture2D( tGridPositionsAndSeeds, perMotifUV ).xyzw;
            vec2 noiseSeed = gridPosition.zw;
            vec4 nowPos;
            vec2 velocity;

            nowPos = texture2D( tPositions, uv ).xyzw;
            velocity = vec2(nowPos.z, nowPos.w);

            if ( swapState == 0.0 ) {
                nowPos = texture2D( tPositions, uv ).xyzw;
                velocity = vec2(nowPos.z, nowPos.w);
            } else { // if swapState == 1
                //nowPos = vec4( -(uTime) + gridPosition.x + offsets.x, gridPosition.y + offsets.y, 0.0, 0.0 );
                nowPos = vec4( -(uTime) + offsets.x, offsets.y, 0.0, 0.0 );
                velocity = vec2(0.0, 0.0);
            }

            [...skipping the physics for brevity...]

            vec2 newPosition = vec2(nowPos.x - velocity.x, nowPos.y - velocity.y);
            // Write new position out
            gl_FragColor = vec4(newPosition.x, newPosition.y, velocity.x, velocity.y);
   }

这是第二个着色器程序的设置: 注意:此部分的渲染器是 window 大小

的 WebGLRenderer
function makePerParticleReferencePositions() {

    var positions = new Float32Array( perParticleBufferSize * 3 );

    var texelSizeX = 1 / perParticleBufferDimensions.width;
    var texelSizeY = 1 / perParticleBufferDimensions.height;

    for ( var j = 0, j3 = 0; j < perParticleBufferSize; j ++, j3 += 3 ) {

        positions[ j3 + 0 ] = ( ( j % perParticleBufferDimensions.width ) / perParticleBufferDimensions.width ) + ( 0.5 * texelSizeX );
        positions[ j3 + 1 ] = ( Math.floor( j / perParticleBufferDimensions.height ) / perParticleBufferDimensions.height ) + ( 0.5 * texelSizeY );
        positions[ j3 + 2 ] = j * 0.0001; // this is the real z value for the particle display

    }

    return positions;
}

var positions = makePerParticleReferencePositions();

...

// Add attributes to the BufferGeometry: 
gridOfMotifs.geometry.addAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
gridOfMotifs.geometry.addAttribute( 'aTextureIndex', new THREE.BufferAttribute( motifGridAttributes.aTextureIndex, 1 ) );
gridOfMotifs.geometry.addAttribute( 'aAlpha', new THREE.BufferAttribute( motifGridAttributes.aAlpha, 1 ) );
gridOfMotifs.geometry.addAttribute( 'aCellIndex', new THREE.BufferAttribute(
        motifGridAttributes.aCellIndex, 1 ) );

uniformValues = {};
uniformValues.tSelectionFactors = motifGridAttributes.tSelectionFactors;
uniformValues.uPerMotifBufferWidth = motifGridAttributes.uPerMotifBufferWidth;
uniformValues.uPerMotifBufferHeight = motifGridAttributes.uPerMotifBufferHeight;

gridOfMotifs.geometry.computeBoundingSphere();

...

function makeCustomUniforms( uniformValues ) {

    selectionFactorsTexture = new THREE.DataTexture( uniformValues.tSelectionFactors, uniformValues.uPerMotifBufferWidth, uniformValues.uPerMotifBufferHeight, THREE.RGBAFormat, THREE.FloatType );
    selectionFactorsTexture.needsUpdate = true;

    var customUniforms = {
        tPositions : { type : "t", value : null },
        tSelectionFactors : { type : "t", value : selectionFactorsTexture },
        uPerMotifBufferWidth : { type : "f", value : uniformValues.uPerMotifBufferWidth },
        uPerMotifBufferHeight : { type : "f", value : uniformValues.uPerMotifBufferHeight },
        uTextureSheet : { type : "t", value : texture }, // this is a sprite sheet of all 10 strokes
        uPointSize : { type : "f", value : 18.0 }, // the radius of a point in WebGL units, e.g. 30.0
        // Coords for the hatch textures:
        uTextureCoordSizeX : { type : "f", value : 1.0 / numTexturesInSheet },
        uTextureCoordSizeY : { type : "f", value : 1.0 }, // the size of a texture in the texture map ( they're square, thus only one value )
    };
    return customUniforms;
}

这里是对应的shader程序(vert & frag):

顶点着色器:

    uniform sampler2D tPositions;
    uniform sampler2D tSelectionFactors;
    uniform float uPerMotifBufferWidth;
    uniform float uPerMotifBufferHeight;
    uniform sampler2D uTextureSheet;
    uniform float uPointSize; // the radius size of the point in WebGL units, e.g. "30.0"
    uniform float uTextureCoordSizeX; // vertical dimension of each texture given the full side = 1
    uniform float uTextureCoordSizeY; // horizontal dimension of each texture given the full side = 1

    attribute float aTextureIndex;
    attribute float aAlpha;
    attribute float aCellIndex;

    varying float vCellIndex;
    varying vec2 vTextureCoords;
    varying vec2 vTextureSize;
    varying float vAlpha;
    varying vec3 vColor;
    varying float vDensity;

   [...skipping noise function for brevity...]

    void main() {

        vec4 tmpPos = texture2D( tPositions, position.xy );
        vec2 pos = tmpPos.xy;
        vec2 vel = tmpPos.zw;

        vCellIndex = aCellIndex;

        if (aCellIndex >= 0.0) { // buffer filler cell indexes are -1

            float texelSizeX = 1.0 / uPerMotifBufferWidth;
            float texelSizeY = 1.0 / uPerMotifBufferHeight;
            vec2 perMotifUV = vec2( mod(aCellIndex, uPerMotifBufferWidth)*texelSizeX, floor(aCellIndex / uPerMotifBufferHeight)*texelSizeY );
            perMotifUV += vec2(0.5*texelSizeX, 0.5*texelSizeY);

            vec4 selectionFactors = texture2D( tSelectionFactors, perMotifUV ).xyzw;
            float aSelectedMotif = selectionFactors.x;
            float aColor = selectionFactors.y;
            float fadeFactor = selectionFactors.z;

            vTextureCoords = vec2( aTextureIndex * uTextureCoordSizeX, 0 );
            vTextureSize = vec2( uTextureCoordSizeX, uTextureCoordSizeY );

            vAlpha = aAlpha * fadeFactor;
            vDensity = vel.x + vel.y;
            vAlpha *= abs( vDensity * 3.0 );

            vColor = vec3( 1.0, aColor, 1.0 ); // set RGB color associated to vertex; use later in fragment shader.

            gl_PointSize = uPointSize;

        } else { // if this is a filler cell index (-1)
            vAlpha = 0.0;
            vDensity = 0.0;
            vColor = vec3(0.0, 0.0, 0.0);
            gl_PointSize = 0.0;
        }
        gl_Position = projectionMatrix * modelViewMatrix * vec4( pos.x, pos.y, position.z, 1.0 ); // position holds the real z value. The z value of "color" is a component of velocity
    }

片段着色器:

    uniform sampler2D tPositions;
    uniform sampler2D uTextureSheet;

    varying float vCellIndex;
    varying vec2 vTextureCoords;
    varying vec2 vTextureSize;
    varying float vAlpha;
    varying vec3 vColor;
    varying float vDensity;  

    void main() {
        gl_FragColor = vec4( vColor, vAlpha );

        if (vCellIndex >= 0.0) { // only render out the texture if this point is not a buffer filler
            vec2 realTexCoord = vTextureCoords + ( gl_PointCoord * vTextureSize );
            gl_FragColor = gl_FragColor * texture2D( uTextureSheet, realTexCoord );
        }
    }

预期行为: 我可以通过强制所有 DataTextures 为 1:1 来实现此目的

奇怪的行为:当较小的 DataTextures 是 2:1 时,下图右上角的那些完全水平的云形成并扰乱了物理学.当较大的 DataTextures 是 2:1 时,网格是倾斜的,并且云似乎缺少部分(如下所示)。当小纹理和大纹理都是 2:1 时,两种奇怪的行为都会发生(下图中就是这种情况)。

感谢对我的相关问题 的回答,我现在知道出了什么问题。问题出在我使用索引数组 (1,2,3,4,5...) 访问着色器中的 DataTextures 纹素的方式。

在此函数中(以及用于较大数据纹理的函数)...

float texelSizeX = 1.0 / uPerMotifBufferWidth;
float texelSizeY = 1.0 / uPerMotifBufferHeight;
vec2 perMotifUV = vec2( 
    mod(aCellIndex, uPerMotifBufferWidth)*texelSizeX, 
    floor(aCellIndex / uPerMotifBufferHeight)*texelSizeY );
perMotifUV += vec2(0.5*texelSizeX, 0.5*texelSizeY);

...我假设为了为我的自定义 uv 创建 y 值,perMotifUV,我需要将 aCellIndex 除以缓冲区的高度, uPerMotifBufferHeight(这是 "vertical" 维度)。但是,正如 SO Q&A here 中所解释的那样,索引当然应该除以缓冲区的宽度,然后它会告诉你有多少行!

因此,函数应该修改为...

float texelSizeX = 1.0 / uPerMotifBufferWidth;
float texelSizeY = 1.0 / uPerMotifBufferHeight;
vec2 perMotifUV = vec2( 
    mod(aCellIndex, uPerMotifBufferWidth)*texelSizeX, 
    floor(aCellIndex / uPerMotifBufferWidth)*texelSizeY ); **Note the change to uPerMotifBufferWidth here
perMotifUV += vec2(0.5*texelSizeX, 0.5*texelSizeY);

我的程序在方形 DataTextures (1:1) 上工作的原因是在这种情况下高度和宽度相等,所以我的函数在错误的行中有效地除以宽度,因为高度=宽度!