генерирование текстуры для извлечения значений во время затенения фрагмента дает пустой экран для правильной ширины и высоты - PullRequest
0 голосов
/ 17 января 2020

Я хотел бы создать текстуру в коде, состоящем из массива значений цвета RGBA, и использовать эти значения для определения цветов плиток, которые я генерирую в фрагментном шейдере. Я получил идею, и большая часть кода, чтобы сделать это из верхнего решения, предоставленного для этого вопроса SO: Выражение индекса должно быть постоянным - ошибка WebGL / GLSL

Однако, если я создаю текстура с использованием высоты и ширины, которые соответствуют моей цветовой гамме, я не вижу ничего визуализировать на холсте. Если я жестко кодирую разные значения, я иногда получаю изображение, но это изображение, конечно, не помещает цвета плитки в нужные позиции, и они перемещаются, когда я меняю переменные viewPos.

Из пробной версии и при тестировании ошибок с несколькими выбранными вручную значениями кажется, что я МОГУТ получать изображение только тогда, когда gl.texImage2D () получает высоту и ширину, равные степени 2, хотя я ничего не вижу в документации. 32 была наибольшей шириной, с которой я мог создать изображение, а 16 была самой большой высотой, с которой я мог создать изображение. 1, 2, 4 и 8 тоже работают. (размер текстуры должен быть 27 на 20 для размера окна, с которым я тестирую)

Обратите внимание, что фрагментный шейдер все еще получает вектор uTileColorSampSize, который относится к размеру цветового массива , Мне только нужно, чтобы значения ширины и высоты gl.texImage2D () были жестко запрограммированы для создания изображения. Фактически, каждое значение, которое я пробовал для униформы, создавало изображение, хотя у каждого были разные цветовые узоры.

Я включил несколько упрощенную версию своего класса Gfx (оригинал довольно грязный, и включает в себя множество вещей, не относящихся к этой проблеме) ниже. Я мог бы предположить, что проблема выше, например 186 или около того, но я включил несколько дополнительных функций ниже, на случай, если они окажутся актуальными.

class Gfx {
    constructor() {
        this.canvas = document.getElementById("canvas");
        this.gl = canvas.getContext("webgl");

        //viewPos changes as you drag your cursor across the canvas
        this.x_viewPos = 0;
        this.y_viewPos = 0;
    }
    init() {
        this.resizeCanvas(window.innerWidth, window.innerHeight);

        const vsSource = `
            attribute vec4 aVertPos;

            uniform mat4 uMVMat;
            uniform mat4 uProjMat;

            void main() {
                gl_Position = uProjMat * uMVMat * aVertPos;
            }
        `;

        //my tiles get drawn in the frag shader below
        const fsSource = `
            precision mediump float;

            uniform vec2 uViewPos;
            uniform vec2 uTileColorSampSize;
            uniform sampler2D uTileColorSamp;

            void main() {
                //tile width and height are both 33px including a 1px border
                const float lineThickness = (1.0/33.0);

                //gridMult components will either be 0.0 or 1.0. This is used to place the grid lines
                vec2 gridMult = vec2(
                    ceil(max(0.0, fract((gl_FragCoord.x-uViewPos.x)/33.0) - lineThickness)),
                    ceil(max(0.0, fract((gl_FragCoord.y-uViewPos.y)/33.0) - lineThickness))
                );
                //tileIndex is used to pull color data from the sampler texture
                //add 0.5 due to pixel coords being off in gl
                vec2 tileIndex = vec2(
                    floor((gl_FragCoord.x-uViewPos.x)/33.0) + 0.5, 
                    floor((gl_FragCoord.y-uViewPos.y)/33.0) + 0.5
                );

                //divide by samp size as tex coords are 0.0 to 1.0
                vec4 tileColor = texture2D(uTileColorSamp, vec2(
                    tileIndex.x/uTileColorSampSize.x,
                    tileIndex.y/uTileColorSampSize.y
                ));

                gl_FragColor = vec4(
                    tileColor.x * gridMult.x * gridMult.y,
                    tileColor.y * gridMult.x * gridMult.y,
                    tileColor.z * gridMult.x * gridMult.y,
                    1.0 //the 4th rgba in our sampler is always 1.0 anyway
                );
            }
        `;

        const shader = this.buildShader(vsSource, fsSource);
        this.programInfo = {
            program: shader,
            attribLocs: {
                vertexPosition: this.gl.getAttribLocation(shader, 'aVertPos')
            },
            uniformLocs: {
                projMat: this.gl.getUniformLocation(shader, 'uProjMat'),
                MVMat: this.gl.getUniformLocation(shader, 'uMVMat'),
                viewPos: this.gl.getUniformLocation(shader, 'uViewPos'),
                tileColorSamp: this.gl.getUniformLocation(shader, 'uTileColorSamp'),
                tileColorSampSize: this.gl.getUniformLocation(shader, 'uTileColorSampSize')
            }
        };
        const buffers = this.initBuffers();

        //check and enable OES_texture_float to allow us to create our sampler tex
        if (!this.gl.getExtension("OES_texture_float")) {
            alert("Sorry, your browser/GPU/driver doesn't support floating point textures");
        }

        this.gl.clearColor(0.0, 0.0, 0.15, 1.0);
        this.gl.clearDepth(1.0);
        this.gl.enable(this.gl.DEPTH_TEST);
        this.gl.depthFunc(this.gl.LEQUAL);

        const FOV = 45 * Math.PI / 180;   // in radians
        const aspect = this.gl.canvas.width / this.gl.canvas.height;
        this.projMat = glMatrix.mat4.create();
        glMatrix.mat4.perspective(this.projMat, FOV, aspect, 0.0, 100.0);
        this.MVMat = glMatrix.mat4.create();
        glMatrix.mat4.translate(this.MVMat, this.MVMat, [-0.0, -0.0, -1.0]);

        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffers.position);
        this.gl.vertexAttribPointer(this.programInfo.attribLocs.vertPos, 2, this.gl.FLOAT, false, 0, 0);
        this.gl.enableVertexAttribArray(this.programInfo.attribLocs.vertPos);

        this.glDraw();
    }
    //glDraw() gets called once above, as well as in every frame of my render loop
    //(not included here as I have it in a seperate Timing class)
    glDraw() {
        this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
        this.gl.useProgram(this.programInfo.program);

        //X and Y TILE_COUNTs varrified to correspond to colorArray size in testing
        //(colorArray.length = rgbaLength * X_TILE_COUNT * Y_TILE_COUNT)
        //(colorArray.length = rgbaLength * widthInTiles * heightInTiles)
        //(colorArray.length = 4 * 27 * 20)
        let x_tileColorSampSize = X_TILE_COUNT; 
        let y_tileColorSampSize = Y_TILE_COUNT;

        //getTileColorArray() produces a flat array of floats between 0.0and 1.0
        //equal in length to rgbaLength * X_TILE_COUNT * Y_TILE_COUNT
        //every 4th value is 1.0, representing tile alpha
        let colorArray = this.getTileColorArray();

        let colorTex = this.colorMapTexFromArray(
            x_tileColorSampSize,
            y_tileColorSampSize,
            colorArray
        );
        //SO solution said to use anyting between 0 and 15 for texUnit, they used 3
        //I imagine this is just an arbitrary location in memory to hold a texture 
        let texUnit = 3;
        this.gl.activeTexture(this.gl.TEXTURE0 + texUnit);
        this.gl.bindTexture(this.gl.TEXTURE_2D, colorTex);
        this.gl.uniform1i(
            this.programInfo.uniformLocs.tileColorSamp,
            texUnit
        );
        this.gl.uniform2fv(
            this.programInfo.uniformLocs.tileColorSampSize,
            [x_tileColorSampSize, y_tileColorSampSize]
        );
        this.gl.uniform2fv(
            this.programInfo.uniformLocs.viewPos,
            [-this.x_viewPos, this.y_viewPos] //these change as you drag your cursor across the canvas
        );

        this.gl.uniformMatrix4fv(
            this.programInfo.uniformLocs.projMat,
            false,
            this.projMat
        );
        this.gl.uniformMatrix4fv(
            this.programInfo.uniformLocs.MVMat,
            false,
            this.MVMat
        );

        this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
    }
    colorMapTexFromArray(width, height, colorArray) {
        let float32Arr = Float32Array.from(colorArray);
        let oldActive = this.gl.getParameter(this.gl.ACTIVE_TEXTURE);
        //SO solution said "working register 31, thanks", next to next line
        //not sure what that means but I think they're just looking for any
        //arbitrary place to store the texture?
        this.gl.activeTexture(this.gl.TEXTURE15);  

        var texture = this.gl.createTexture();
        this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
        this.gl.texImage2D(
            this.gl.TEXTURE_2D, 0, this.gl.RGBA,
            //if I replace width and height with certain magic numbers
            //like 4 or 8 (all the way up to 32 for width and 16 for height)
            //I will see colored tiles, though obviously they don't map correctly.
            //I THINK I've only seen it work with a widths and heights that are
            //a power of 2... could the issue be that I need my texture to have
            //width and height equal to a power of 2?
            width, height, 0,
            this.gl.RGBA, this.gl.FLOAT, float32Arr
        );

        //use gl.NEAREST to prevent gl from blurring texture
        this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST);
        this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST);


        this.gl.bindTexture(this.gl.TEXTURE_2D, null);
        this.gl.activeTexture(oldActive);

        return texture;
    }

    //I don't think the issue would be in the functions below, but I included them anyway
    resizeCanvas(baseWidth, baseHeight) {
        let widthMod = 0;
        let heightMod = 0;
        //...some math is done here to account for some DOM elements that consume window space...
        this.canvas.width = baseWidth + widthMod;
        this.canvas.height = baseHeight + heightMod;
        this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);
    }
    initBuffers() {
        const posBuff = this.gl.createBuffer();
        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, posBuff);
        const positions = [
            -1.0,  1.0,
             1.0,  1.0,
            -1.0, -1.0,
             1.0, -1.0,
        ];
        this.gl.bufferData(
            this.gl.ARRAY_BUFFER,
            new Float32Array(positions),
            this.gl.STATIC_DRAW
        );
        return {
            position: posBuff
        };
    }
    buildShader(vsSource, fsSource) {
        const vertShader = this.loadShader(this.gl.VERTEX_SHADER, vsSource);
        const fragShader = this.loadShader(this.gl.FRAGMENT_SHADER, fsSource);

        const shaderProg = this.gl.createProgram();
        this.gl.attachShader(shaderProg, vertShader);
        this.gl.attachShader(shaderProg, fragShader);
        this.gl.linkProgram(shaderProg);

        if (!this.gl.getProgramParameter(shaderProg, this.gl.LINK_STATUS)) {
            console.error('Unable to initialize the shader program: ' + gl.getProgramInfoLog(shaderProg));
            return null;
        }

        return shaderProg;
    }
    loadShader(type, source) {
        const shader = this.gl.createShader(type);      
        this.gl.shaderSource(shader, source);
        this.gl.compileShader(shader);

        if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
            console.error('An error occurred compiling the shaders: ' + this.gl.getShaderInfoLog(shader));
            this.gl.deleteShader(shader);
            return null;
        }
        return shader;
    }

    //getTileColorArray as it appears in my code, in case you want to take a peak.
    //every tileGrid[i][j] has a color, which is an array of 4 values between 0.0 and 1.0
    //the fourth (last) value in tileGrid[i][j].color is always 1.0
    getTileColorArray() {
        let i_min = Math.max(0, Math.floor(this.x_pxPosToTilePos(this.x_viewPos)));
        let i_max = Math.min(GLOBAL.map.worldWidth-1, i_min + Math.ceil(this.x_pxPosToTilePos(this.canvas.width)) + 1);
        let j_min = Math.max(0, Math.floor(this.y_pxPosToTilePos(this.y_viewPos)));
        let j_max = Math.min(GLOBAL.map.worldHeight-1, j_min + Math.ceil(this.y_pxPosToTilePos(this.canvas.height)) + 1);

        let colorArray = [];
        for (let i=i_min; i <= i_max; i++) {
            for (let j=j_min; j <= j_max; j++) {
                colorArray = colorArray.concat(GLOBAL.map.tileGrid[i][j].color);
            }
        }
        return colorArray;
    }
}

Я также включил пастин из моего полный неизмененный класс Gfx на тот случай, если вы захотите взглянуть на это: https://pastebin.com/f0erR9qG

И вставка моего упрощенного кода для номеров строк: https://pastebin.com/iB1pUZJa

1 Ответ

0 голосов
/ 18 января 2020

WebGL 1.0 не поддерживает наложение текстур на текстуры с двумя степенями мощности. Есть два способа решить эту проблему, один из них заключается в том, чтобы буферизовать текстуру с достаточным количеством дополнительных данных, чтобы она имела мощность двух измерений, а другой - просто отключить наложение текстуры, например:

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

У меня все еще странное поведение в моем фрагментном шейдере, но теперь он по крайней мере показывает плитки. Я думаю, что дополнительное странное поведение является просто результатом того, что мой алгоритм шейдера не соответствует тому, что я предполагал.

...