【问题标题】:Webgl unwanted mixtures of texturesWebgl 不需要的纹理混合
【发布时间】:2020-11-18 10:44:03
【问题描述】:

我正在渲染 2 个平面,每个平面具有 2 个混合纹理。但是当我清空一个纹理时,就像 GPU 用其他应用程序纹理替换缺少的纹理,即使是前一个平面的纹理。 我在着色器中编写了一个解决方案,包括检测 sampler2D textureN 是否已启动:

void main()
{
    vec4 finalPx = vec4(0.0, 0.0, 0.0, 1.0);

    if(has_tex_1 > 0.0)
    {
        finalPx += texture2D(tex_1, uv_1) * 0.5;
    }
    if(has_tex_2 > 0.0)
    {
        finalPx += texture2D(tex_2, uv_2) * 0.5;
    }
    gl_FragColor = finalPx;
}

...但我担心我的解决方案可能会让我以后的着色器编程变得困难,我认为 webgl 应该负责尊重代码逻辑:如果应用程序没有加载纹理,着色器应该什么也不加载。

这是我的应用程序:

const PICTURES = {
    indices: [
        0, 1, 2, 
        0, 3, 1, 
    ],
    vertices: [
        1.000000, -1.000000, 0.000000, 
        -1.000000, 1.000000, 0.000000, 
        -1.000000, -1.000000, 0.000000, 
        1.000000, 1.000000, 0.000000, 
    ],
    uv_layers: {
        'uv_1': [
            1.000000, 1.077309, 
            0.000000, -0.077309, 
            0.000000, 1.077309, 
            1.000000, -0.077309, 
        ],
        'uv_2': [
            1.500000, 1.500000, 
            -0.500000, -0.500000, 
            -0.500000, 1.500000, 
            1.500000, -0.500000, 
        ],
    },
};

const   vertexShaderCode = `
    precision mediump float;

    attribute vec4  vertex;
    attribute vec2  uv;
    varying vec2        UV;
    attribute vec2  uv2;
    varying vec2        UV2;

    uniform mat4 projectionMatrix, viewMatrix, modelMatrix;

    void main()
    {
        UV = uv;
        UV2 = uv2;
        gl_Position = projectionMatrix * viewMatrix * modelMatrix * vertex;
    }`;

const fragmentShaderCode = `
    precision mediump float;

    varying vec2 UV;
    varying vec2 UV2;

    uniform sampler2D bitmap_1;
    uniform sampler2D bitmap_2;

    void main() {
        // Mix the bitmaps in equal proportions
        vec4 px1 = texture2D(bitmap_1, UV) * 0.5;
        vec4 px2 = texture2D(bitmap_2, UV2) * 0.5;
        gl_FragColor = px1 + px2;
    }`;

function loadShader(gl, vertexShaderCode, fragmentShaderCode) {
    
    const vertexShader = gl.createShader(gl.VERTEX_SHADER);
    gl.shaderSource(vertexShader, vertexShaderCode);
    gl.compileShader(vertexShader);
    if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
        alert('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(vertexShader));
        gl.deleteShader(vertexShader);
        return null;
    }

    const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
    gl.shaderSource(fragmentShader, fragmentShaderCode);
    gl.compileShader(fragmentShader);
    if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
        alert('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(fragmentShader));
        gl.deleteShader(fragmentShader);
        return null;
    }
    return [vertexShader, fragmentShader];
}

function loadTexture(gl, url) {

    function isPowerOf2(value){ return (value & (value - 1)) == 0; }

    const texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, texture);

    // Because images have to be download over the internet they might take a moment until they are ready.
    // Until then put a single pixel in the texture so we can use it immediately.
    // When the image has finished downloading we'll update the texture with the contents of the image.
    const level                 = 0;
    const internalFormat= gl.RGBA;
    const width                 = 1;
    const height                = 1;
    const border                = 0;
    const srcFormat         = gl.RGBA;
    const srcType               = gl.UNSIGNED_BYTE;
    const pixel                 = new Uint8Array([1.0, 0.0, 1.0, 1.0]);  // magenta to warn if there is no texture
    gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, width, height, border, srcFormat, srcType, pixel);

    const image = new Image();
    image.onload = function() {
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, srcFormat, srcType, image);

        // WebGL1 has different requirements for power of 2 images vs non power of 2 images so check if the image is a
        // power of 2 in both dimensions.
        if (isPowerOf2(image.width) && isPowerOf2(image.height)) {
            // Yes, it's a power of 2. Generate mips.
            gl.generateMipmap(gl.TEXTURE_2D);
        } else {
            // No, it's not a power of 2. Turn of mips and set wrapping to clamp to edge
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        }
    };
    image.src = url;

    return texture;
}

function Material(gl, shaders) {

    this.shaderProgram = gl.createProgram();
    gl.attachShader(this.shaderProgram, shaders[0]); // Vertex shader
    gl.attachShader(this.shaderProgram, shaders[1]); // Fragment shader
    gl.linkProgram(this.shaderProgram);
    if (!gl.getProgramParameter(this.shaderProgram, gl.LINK_STATUS)) {
        alert('Unable to initialize the shader program: ' + gl.getProgramInfoLog(this.shaderProgram));
        return null;
    }
}

function main() {

    var surface = document.getElementById('glcanvas');
    var gl = surface.getContext('webgl');

    if (!gl) {
        alert('Unable to initialize WebGL. Your browser or machine may not support it.');
        return;
    }

    // One material for two textures maped within its own uv maps, like any 3D software could allow
    let shaders = loadShader(gl,vertexShaderCode, fragmentShaderCode);

    // -------------------------------------mesh 1---------------------------------------

    // Create the index buffer
    const indexBuffer_1 = gl.createBuffer();
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer_1);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(PICTURES.indices), gl.STATIC_DRAW);

    // The vertex buffer
    const vertexBuffer_1 = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer_1);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(PICTURES.vertices), gl.STATIC_DRAW);

    // uv layers buffers (two uv channels for two different bitmaps)
    const uvBuffer_1_A = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, uvBuffer_1_A);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(PICTURES.uv_layers['uv_1']), gl.STATIC_DRAW);

    const uvBuffer_1_B = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, uvBuffer_1_B);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(PICTURES.uv_layers['uv_2']), gl.STATIC_DRAW);

    const material_1 = new Material(gl, shaders);

    // Create the textures for the respectives uv buffers
    const texture_1 = loadTexture(gl, TEXTURES_FOLDER+'old_fashioned_portrait_1.jpg');
    const texture_2 = loadTexture(gl, TEXTURES_FOLDER+'demon.jpg');

    // Transform data
    let modelMatrix_1 = mat4.create();
    mat4.translate(modelMatrix_1, modelMatrix_1, [-1.5, 0.0, 0.0]);

    let meshData_1 = {
        indices         : PICTURES.indices,
        indexBuffer : indexBuffer_1,
        vertexBuffer: vertexBuffer_1,
        uvBuffer_A  : uvBuffer_1_A,
        uvBuffer_B  : uvBuffer_1_B,
        material        : material_1,
        texture_A       : texture_1,
        texture_B       : texture_2,
        modelMatrix : modelMatrix_1,
    };

    // -------------------------------------mesh 2---------------------------------------

    // Create the index buffer
    const indexBuffer_2 = gl.createBuffer();
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer_2);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(PICTURES.indices), gl.STATIC_DRAW);

    // The vertex buffer
    const vertexBuffer_2 = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer_2);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(PICTURES.vertices), gl.STATIC_DRAW);

    // uv layers buffers (two uv channels for two different bitmaps)
    const uvBuffer_2_A = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, uvBuffer_2_A);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(PICTURES.uv_layers['uv_1']), gl.STATIC_DRAW);

    const uvBuffer_2_B = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, uvBuffer_2_B);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(PICTURES.uv_layers['uv_2']), gl.STATIC_DRAW);

    const material_2 = new Material(gl, shaders);

    const texture_3 = loadTexture(gl, TEXTURES_FOLDER+'old_fashioned_portrait_2.jpg');
    const texture_4 = loadTexture(gl, TEXTURES_FOLDER+'phantom_girl.jpg');

    // Transform data
    let modelMatrix_2 = mat4.create();
    mat4.translate(modelMatrix_2, modelMatrix_2, [1.5, 0.0, 0.0]);

    let meshData_2 = {
        indices         : PICTURES.indices,
        indexBuffer : indexBuffer_2,
        vertexBuffer: vertexBuffer_2,
        uvBuffer_A  : uvBuffer_2_A,
        uvBuffer_B  : uvBuffer_2_B,
        material        : material_2,
        texture_A       : texture_3,
        texture_B       : texture_4,
        modelMatrix : modelMatrix_2,
    };

    // Render data and methods
    requestAnimationFrame(function () {render(gl, meshData_1, meshData_2, Date.now()*0.001);});
}

function render(gl, meshData_1, meshData_2, before)
{
    var now = Date.now()*0.001;
    var delta = now-before;
    rotation += delta;

    // ------------------------------------canvas----------------------------------------

    gl.clearColor(0.0, 0.5, 0.5, 1.0);
    gl.clearDepth(1.0);
    gl.enable(gl.DEPTH_TEST);
    gl.depthFunc(gl.LEQUAL);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    const aspect            = gl.canvas.clientWidth / gl.canvas.clientHeight;
    const fieldOfView   = 45 * Math.PI / 180;
    const zNear             = 0.1;
    const zFar              = 100.0;

    let projectionMatrix = mat4.create();
    mat4.perspective(projectionMatrix, fieldOfView, aspect, zNear, zFar);

    let viewMatrix  = mat4.create();
    mat4.translate(viewMatrix, viewMatrix, [0.0, 0.0, -5.0]);


    // -------------------------------------mesh 1---------------------------------------

    gl.useProgram(meshData_1.material.shaderProgram);

    // Vertices transformations
    gl.uniformMatrix4fv(gl.getUniformLocation(meshData_1.material.shaderProgram, 'projectionMatrix'), false, projectionMatrix);
    gl.uniformMatrix4fv(gl.getUniformLocation(meshData_1.material.shaderProgram, 'viewMatrix'), false, viewMatrix);
    gl.uniformMatrix4fv(gl.getUniformLocation(meshData_1.material.shaderProgram, 'modelMatrix'), false, meshData_1.modelMatrix);

    // Give the GPU the order of the points to form the triangles
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, meshData_1.indexBuffer);

    // Give the GPU the transformed vertices
    gl.bindBuffer(gl.ARRAY_BUFFER, meshData_1.vertexBuffer);
    gl.vertexAttribPointer(gl.getAttribLocation(meshData_1.material.shaderProgram, 'vertex'), 3, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(gl.getAttribLocation(meshData_1.material.shaderProgram, 'vertex'));

    // First texture mapped in the triangles with the first uv coordinates
    gl.bindBuffer(gl.ARRAY_BUFFER, meshData_1.uvBuffer_A);
    gl.vertexAttribPointer(gl.getAttribLocation(meshData_1.material.shaderProgram, 'uv'), 2, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(gl.getAttribLocation(meshData_1.material.shaderProgram, 'uv'));
    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, meshData_1.texture_A);
    gl.uniform1i(gl.getUniformLocation(meshData_1.material.shaderProgram, 'bitmap_1'), 0);

    // Second texture mapped in the triangles with the second uv coordinates
    gl.bindBuffer(gl.ARRAY_BUFFER, meshData_1.uvBuffer_B);
    gl.vertexAttribPointer(gl.getAttribLocation(meshData_1.material.shaderProgram, 'uv2'), 2, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(gl.getAttribLocation(meshData_1.material.shaderProgram, 'uv2'));
    gl.activeTexture(gl.TEXTURE1);
    gl.bindTexture(gl.TEXTURE_2D, meshData_1.texture_B);
    gl.uniform1i(gl.getUniformLocation(meshData_1.material.shaderProgram, 'bitmap_2'), 1);

    gl.drawElements(gl.TRIANGLES, meshData_1.indices.length, gl.UNSIGNED_SHORT, 0);

    gl.useProgram(null);


    // -------------------------------------mesh 2---------------------------------------

    gl.useProgram(meshData_2.material.shaderProgram);

    // Vertices transformations
    gl.uniformMatrix4fv(gl.getUniformLocation(meshData_2.material.shaderProgram, 'projectionMatrix'), false, projectionMatrix);
    gl.uniformMatrix4fv(gl.getUniformLocation(meshData_2.material.shaderProgram, 'viewMatrix'), false, viewMatrix);
    gl.uniformMatrix4fv(gl.getUniformLocation(meshData_2.material.shaderProgram, 'modelMatrix'), false, meshData_2.modelMatrix);

    // Give the GPU the order of the points to form the triangles
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, meshData_2.indexBuffer);

    // Give the GPU the transformed vertices
    gl.bindBuffer(gl.ARRAY_BUFFER, meshData_2.vertexBuffer);
    gl.vertexAttribPointer(gl.getAttribLocation(meshData_2.material.shaderProgram, 'vertex'), 3, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(gl.getAttribLocation(meshData_2.material.shaderProgram, 'vertex'));

    // First texture mapped in the triangles with the first uv coordinates

我在继续代码之前指出了问题:

如果我评论下一个纹理渲染就像应用程序(GPU?)用前一个平面的第一个纹理提供变量“uniform sampler2D bitmap_1”:“old_fashioned_portrait_1.jpg”

    /*
    gl.bindBuffer(gl.ARRAY_BUFFER, meshData_2.uvBuffer_A);
    gl.vertexAttribPointer(gl.getAttribLocation(meshData_2.material.shaderProgram, 'uv'), 2, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(gl.getAttribLocation(meshData_2.material.shaderProgram, 'uv'));
    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, meshData_2.texture_A);
    gl.uniform1i(gl.getUniformLocation(meshData_2.material.shaderProgram, 'bitmap_1'), 0);
    */

    // Second texture mapped in the triangles with the second uv coordinates
    gl.bindBuffer(gl.ARRAY_BUFFER, meshData_2.uvBuffer_B);
    gl.vertexAttribPointer(gl.getAttribLocation(meshData_2.material.shaderProgram, 'uv2'), 2, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(gl.getAttribLocation(meshData_2.material.shaderProgram, 'uv2'));
    gl.activeTexture(gl.TEXTURE1);
    gl.bindTexture(gl.TEXTURE_2D, meshData_2.texture_B);
    gl.uniform1i(gl.getUniformLocation(meshData_2.material.shaderProgram, 'bitmap_2'), 1);

    gl.drawElements(gl.TRIANGLES, meshData_2.indices.length, gl.UNSIGNED_SHORT, 0);

    gl.useProgram(null);

    requestAnimationFrame(function(){render(gl, meshData_1, meshData_2, now);});
}

main();

【问题讨论】:

    标签: javascript 3d glsl webgl textures


    【解决方案1】:

    LJ 所说的 WebGL 是一个状态机,或者更确切地说 it has a bunch of state that stays the same until you set something different 是正确的,但我想补充几点

    1. 如果可能,最好不要在着色器中分支。

      如果您需要一个着色器来做一件事或另一件事,请制作 2 个着色器,一个做一件事的着色器和一个做另一件事的不同着色器。像 Unreal 和 Unity 这样的引擎会生成 1000 多种着色器变化。甚至 three.js 也会生成很多着色器变体。

    2. 使用变量will break 进行分支和纹理查找。

      该链接的重要部分是这个

      如果与 someOtherSampler 关联的纹理使用任何类型的 mipmapping 或各向异性过滤,则任何需要片段着色器中隐式导数的纹理函数将在统一控制流之外检索未定义的结果。 ...

      注意:GLSL 编译器不会为此给出错误。它是完全合法的 GLSL 代码,它只会根据与 someOtherSampler 关联的纹理和采样器对象产生未定义的行为。

      也就是说这段代码

       vec4 finalPx = vec4(0.0, 0.0, 0.0, 1.0);
      
       if(has_tex_1 > 0.0)
       {
           finalPx += texture2D(tex_1, uv_1) * 0.5;
       }
       if(has_tex_2 > 0.0)
       {
           finalPx += texture2D(tex_2, uv_2) * 0.5;
       }
      

      真的需要这样

       vec4 finalPx = vec4(0.0, 0.0, 0.0, 1.0);
      
       vec4 color1 = texture2D(tex_1, uv_1) * 0.5;
       vec4 color2 = texture2D(tex_2, uv_2) * 0.5;
       if(has_tex_1 > 0.0)
       {
           finalPx += color1;
       }
       if(has_tex_2 > 0.0)
       {
           finalPx += color2;
       }
      

      使根据变量(uv_1 和 uv_2)从纹理中查找事物的部分不是有条件的。

    3. 您通常可以设计一个着色器,使其无需分支即可使用。

      在您的示例中,您可以这样做

       vec4 finalPx = vec4(0.0, 0.0, 0.0, 1.0);
       finalPx += texture2D(tex_1, uv_1) * 0.5;
       finalPx += texture2D(tex_2, uv_2) * 0.5;
      

      然后像这样制作一个单像素黑色纹理

      const blackTexture = gl.createTexture();
      gl.bindTexture(gl.TEXTURE_2D, blackTexture);
      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE,
                    new Uint8Array([0, 0, 0, 0]));
      

      现在,当您只想使用一种纹理时,只需将 blackTexture 绑定为 其他纹理。因为它是 0、0、0、0,所以着色器中的数学运算将添加 0.0 所以它不会有任何影响。不再需要has_tex_1has_tex_2

      这也比依赖绑定没有纹理要好得多 恰好是 0,0,0,1 但会在浏览器中生成警告。

    【讨论】:

    • 如果我正确理解了你的答案,你可能会说:texture_N 必须有它自己的 uv_buffer_N 和它自己的着色器属性 uv_N,总是如此。但问题是一些 3D 网格不使用纹理然后它们没有 uv 坐标。考虑到这一点,我现在能看到的唯一解决方案是评估网格是否定义了 uv 坐标,然后向 GLSL 发送一个类似布尔值的变量,以警告着色器纹理“槽”是否已启动。你建议我可以创建一个黑色纹理来替换“空纹理”,这意味着我不得不为该纹理创建 uv 坐标,对吧?
    • 我的第一个建议是,如果您有一个使用 UV 的网格和另一个不使用的网格,您应该制作 2 个着色器,一个使用 UV 的着色器和一个不使用 UV 的着色器。这就是 AAA 游戏可能会做的事情。这也是three.js 所做的。如果您打算只制作一个着色器,如果您的网格没有 UV 关闭该属性,请将纹理设置为单个白色像素 (1,1,1,1)。您的颜色数学可以类似于color = diffuseColor * texture2D(tex, uvs);使用 1x1 像素的白色纹理,不管你总是得到什么 UV 1,1,1,1 和 1,1,1,1 *diffuseColor = justdiffuseColor
    【解决方案2】:

    WebGL 是一个状态机,您设置的每个状态都会保留,直到您设置不同的状态。如果你想取消绑定纹理,你必须这样做:

    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, null);
    

    从未绑定的纹理单元读取任何内容都会产生vec4(0,0,0,1)

    【讨论】:

    • 如果你的着色器需要一个,你真的不想在没有纹理绑定的情况下进行渲染。浏览器必须进行大量验证,它会发出警告,指出该单元没有绑定纹理。可以说,附加一些多余的纹理(如 1x1 像素黑色或白色纹理)会更好,无论哪个更适合您的着色器数学(0 或 1)。在上面的示例中,适合使用 0,0,0,0 1x1 像素纹理。
    • 我同意,它的可读性也更好,所以不需要知道“默认”值。这个答案旨在展示如何实际“清空”纹理,因为这是 OP 最初打算做的事情。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-18
    相关资源
    最近更新 更多