three.js - 如果数据纹理不是正方形,则会出现奇怪的行为(1 :1)

标签 three.js glsl textures webgl shader

我有一对着色器程序,如果我的数据纹理是正方形 (1:1),则一切正常,但如果其中一个或两者的比例为 2:1(宽度:高度),则行为会变得困惑。我可以使用未使用的填充符扩展每个缓冲区,以确保它们始终是方形的,但从长远来看,这似乎不必要地昂贵(内存方面),因为两个缓冲区大小之一一开始就相当大。在这种情况下有没有办法处理 2:1 缓冲区?

我有一对着色器程序:

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

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

每粒子纹理像素大小的纹理(大): tPositionstOffsets

每云纹理大小的纹理(小): tGridPositionsAndSeedstSelectionFactors

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

更新:问题是否与我在第二个着色器的 position 属性中容纳纹理像素坐标以读取 tPosition 纹理有关着色器程序?如果是这样,也许这个Github issue关于 position 属性中的纹素坐标可能相关,尽管我在此处找不到相应的问题/答案。

更新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 };

}

这是第一个着色器程序(仅用于物理计算的片段):

   // 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);
   }

这是第二个着色器程序的设置: 注意:此部分的渲染器是窗口大小的 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;
}

这是相应的着色器程序(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 来实现此目的 expected behavior - shaped particle clouds correctly assembled and displayed in an offset grid

奇怪的行为:当较小的DataTextures为2:1时,下图右上角的那些完全水平的云就会形成并扰乱物理。当较大的 DataTextures 为 2:1 时,网格会倾斜,并且云看起来会丢失部分(如下所示)。当小纹理和大纹理均为 2:1 时,会发生两种奇怪的行为(如下图所示)。 weird behavior - particle clouds incorrectly assembled and displayed in a skewed grid

最佳答案

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

在此函数中(以及较大的 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 perMotifUV 创建 y 值,我需要除以 aCellIndex > 缓冲区的高度,uPerMotifBufferHeight(它是“垂直”尺寸)。然而,正如 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) 上运行的原因是,在这种情况下,高度和宽度相等,因此我的函数实际上在错误的行中除以宽度,因为高度=宽度!

关于three.js - 如果数据纹理不是正方形,则会出现奇怪的行为(1 :1),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42789471/

相关文章:

three.js - 如何在Blender中制作骨骼动画并在Three.js中播放

opengl - 使用 std430 限定符进行内存分配

glsl - 尝试将 GLSL 玻璃着色器移植到Processing 3.0

java - GLSL 中没有出现纹理的可能原因有哪些?

c++ - NoDecodeDelegateForThisImageFormat 使用 ImageMagick 读取 .png 文件时出错

javascript - ThreeJS 中的对象未使用 JSONLoader 加载 : Cannot read property 'visible' of undefined

javascript - THREE.js生成UV坐标

three.js - 为什么透明度在我的网格上不起作用?

opengl - 如何在 OpenGL 中可视化深度纹理?

javascript - 如何在 three.js 中创建地形?