Как создать гистограмму в WebGL2 с использованием 16-битных данных? - PullRequest
0 голосов
/ 19 сентября 2019

var gl, utils, pseudoImg, vertices;
var img = null;
document.addEventListener('DOMContentLoaded', () => {
    utils = new WebGLUtils();
    vertices = utils.prepareVec2({x1 : -1.0, y1 : -1.0, x2 : 1.0, y2 : 1.0});
    gl = utils.getGLContext(document.getElementById('canvas'));
    var program = utils.getProgram(gl, 'render-vs', 'render16bit-fs');
    var histogramProgram = utils.getProgram(gl, 'histogram-vs', 'histogram-fs');
    var sortProgram = utils.getProgram(gl, 'sorting-vs', 'sorting-fs');
    var showProgram = utils.getProgram(gl, 'showhistogram-vs', 'showhistogram-fs');
    utils.activateTextureByIndex(gl, showProgram, 'histTex', 3);
    utils.activateTextureByIndex(gl, showProgram, 'maxTex', 4);
    utils.activateTextureByIndex(gl, sortProgram, 'tex3', 2);
    utils.activateTextureByIndex(gl, histogramProgram, 'tex2', 1);
    utils.activateTextureByIndex(gl, program, 'u_texture', 0);
    
    var vertexBuffer = utils.createAndBindBuffer(gl, vertices);
    var imageTexture;
    computeHistogram = (AR, myFB) => {
        gl.useProgram(histogramProgram);
        var width = AR.width;
        var height = AR.height;
        var numOfPixels = width * height;
        var pixelIds = new Float32Array(numOfPixels);
        for (var i = 0; i < numOfPixels; i++) {
            pixelIds[i] = i;
        }
        var histogramFbObj = utils.createTextureAndFramebuffer(gl, {
            format : gl.RED,
            internalFormat : gl.R32F,
            filter : gl.NEAREST,
            dataType : gl.FLOAT,
            mipMapST : gl.CLAMP_TO_EDGE,
            width : 256,
            height : 256
        });
        gl.bindFramebuffer(gl.FRAMEBUFFER, histogramFbObj.fb);
        gl.viewport(0, 0, 256, 256);
        var pixelBuffer = utils.createAndBindBuffer(gl, pixelIds, true);
        gl.blendFunc(gl.ONE, gl.ONE);
        gl.enable(gl.BLEND);
        utils.linkAndSendDataToGPU(gl, histogramProgram, 'pixelIds', pixelBuffer, 1);
        gl.uniform2fv(gl.getUniformLocation(histogramProgram, 'imageDimensions'), [width, height]);
        utils.sendTextureToGPU(gl, myFB.tex, 1);
        gl.drawArrays(gl.POINTS, 0, numOfPixels);

        gl.blendFunc(gl.ONE, gl.ZERO);
        gl.disable(gl.BLEND);
        return histogramFbObj;
    };

    sortHistogram = (histogramFbObj) => {
        gl.useProgram(sortProgram);
        utils.linkAndSendDataToGPU(gl, sortProgram, 'vertices', vertexBuffer, 2);
        var sortFbObj = utils.createTextureAndFramebuffer(gl, {
            format : gl.RED,
            internalFormat : gl.R32F,
            filter : gl.NEAREST,
            dataType : gl.FLOAT,
            mipMapST : gl.CLAMP_TO_EDGE,
            width : 1,
            height : 1
        });
        gl.bindFramebuffer(gl.FRAMEBUFFER, sortFbObj.fb);
        gl.viewport(0, 0, 1, 1);
        utils.sendTextureToGPU(gl, histogramFbObj.tex, 2);
        gl.drawArrays(gl.TRIANGLES, 0, 6);
        return sortFbObj;
    };

    showHistogram = (histFb, sortFb) => {
        gl.useProgram(showProgram);
        utils.linkAndSendDataToGPU(gl, showProgram, 'vertices', vertexBuffer, 2);
        utils.sendTextureToGPU(gl, histFb.tex, 3);
        utils.sendTextureToGPU(gl, sortFb.tex, 4);
        gl.uniform2fv(gl.getUniformLocation(showProgram, 'imageDimensions'), [gl.canvas.width, gl.canvas.height]);
        gl.drawArrays(gl.TRIANGLES, 0, 6);
    };

    showTexture = (AR) => {
        imageTexture = utils.createAndBindTexture(gl, {
            filter : gl.NEAREST,
            mipMapST : gl.CLAMP_TO_EDGE,
            dataType : gl.UNSIGNED_SHORT,
            format : gl.RGBA_INTEGER,
            internalFormat : gl.RGBA16UI,
            img : AR.img,
            width : AR.width,
            height : AR.height
        });

        gl.useProgram(program);
        var myFB = utils.createTextureAndFramebuffer(gl, {
            filter : gl.NEAREST,
            mipMapST : gl.CLAMP_TO_EDGE,
            dataType : gl.UNSIGNED_BYTE,
            format : gl.RGBA,
            internalFormat : gl.RGBA,
            width : AR.width,
            height : AR.height,
        });
        gl.bindFramebuffer(gl.FRAMEBUFFER, myFB.fb);
        gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
        utils.linkAndSendDataToGPU(gl, program, 'vertices', vertexBuffer, 2);
        gl.uniform1f(gl.getUniformLocation(program, 'flipY'), 1.0);
        utils.sendTextureToGPU(gl, imageTexture, 0);
        gl.drawArrays(gl.TRIANGLES, 0, 6);

        var fb1 = computeHistogram(AR, myFB);
        var fb2 = sortHistogram(fb1);
        gl.bindFramebuffer(gl.FRAMEBUFFER, null);
        gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
        showHistogram(fb1, fb2);
    };

    var w = 128;
    var h = 128;
    var size = w * h * 4;
    var img = new Uint16Array(size); // need Uint16Array
    for (var i = 0; i < img.length; i += 4) {
        img[i + 0] = 65535; // r
        img[i + 1] = i/64 * 256; // g
        img[i + 2] = 0; // b
        img[i + 3] = 65535; // a
    }
    showTexture({
        img : img,
        width : w,
        height : h
    });
});
<script id="render16bit-fs" type="not-js">
            #version 300 es
            precision highp float; 
            uniform highp usampler2D tex;
            in vec2 texcoord; // receive pixel position from vertex shader
            out vec4 fooColor;
            void main() {
                uvec4 unsignedIntValues = texture(tex, texcoord);
                vec4 floatValues0To65535 = vec4(unsignedIntValues);
                vec4 colorValues0To1 = floatValues0To65535;
                fooColor = colorValues0To1;
            }          
        </script>

        <script type="not-js" id="render-vs">
            #version 300 es
            in vec2 vertices;
            out vec2 texcoord;
            uniform float flipY;
            void main() {
                texcoord = vertices.xy * 0.5 + 0.5;
                gl_Position = vec4(vertices.x, vertices.y * flipY, 0.0, 1.0);
            }
        </script>

        <script type="not-js" id="histogram-vs">
            #version 300 es
            in float pixelIds; //0,1,2,3,4,.......width*height
            uniform sampler2D tex2;
            uniform vec2 imageDimensions;
            void main () {
                vec2 pixel = vec2(mod(pixelIds, imageDimensions.x), floor(pixelIds / imageDimensions.x)); 
                vec2 xy = pixel/imageDimensions;
                float pixelValue = texture(tex2, xy).r;//Pick Pixel value from GPU texture ranges from 0-65535
                float xDim = mod(pixelValue, 255.0)/256.0;
                float yDim = floor(pixelValue / 255.0)/256.0;
                float xVertex = (xDim*2.0) - 1.0;//convert 0.0 to 1.0 -> -1.0 -> 1.0, it will increment because we have used gl.blendFunc
                float yVertex = 1.0 - (yDim*2.0);
                gl_Position = vec4(xVertex, yVertex, 0.0, 1.0);
                gl_PointSize = 1.0;
            }
        </script>
        <script type="not-js" id="histogram-fs">
            #version 300 es
            precision mediump float;
            out vec4 fcolor;
            void main() {
                fcolor = vec4(1.0, 1.0, 1.0, 1.0);
            }
        </script>

        <script type="not-js" id="sorting-vs">
            #version 300 es
            in vec2 vertices;
            void main () {
                gl_Position = vec4(vertices, 0.0, 1.0);
            }
        </script>
        <script type="not-js" id="sorting-fs">
            #version 300 es
            precision mediump float;
            out vec4 fcolor;
            uniform sampler2D tex3;
            const int MAX_WIDTH = 65536;
            void main() {
                vec4 maxColor = vec4(0.0);
                for (int i = 0; i < MAX_WIDTH; i++) {
                    float xDim = mod(float(i), 256.0)/256.0;
                    float yDim = floor(float(i) / 256.0)/256.0;
                    vec2 xy = vec2(xDim, yDim);
                    vec4 currPixel = texture(tex3, xy).rrra;
                    maxColor = max(maxColor, currPixel);
                }
                fcolor = vec4(maxColor);
            }
        </script>

        <script type="not-js" id="showhistogram-vs">
            #version 300 es
            in vec2 vertices;
            void main () {
                gl_Position = vec4(vertices, 0.0, 1.0);
            }
        </script>
        <script type="not-js" id="showhistogram-fs">
            #version 300 es
            precision mediump float;
            uniform sampler2D histTex, maxTex;
            uniform vec2 imageDimensions;
            out vec4 fcolor;
            void main () {
                // get the max color constants
                vec4 maxColor = texture(maxTex, vec2(0));
                // compute our current UV position
                vec2 uv = gl_FragCoord.xy / imageDimensions;
                vec2 uv2 = gl_FragCoord.xy / vec2(256.0, 256.0);
                // Get the history for this color
                vec4 hist = texture(histTex, uv2);
                // scale by maxColor so scaled goes from 0 to 1 with 1 = maxColor
                vec4 scaled = hist / maxColor;
                // 1 > maxColor, 0 otherwise
                vec4 color = step(uv2.yyyy, scaled);
                fcolor = vec4(color.rgb, 1);
            }
        </script>
        
        <canvas id="canvas"></canvas>
        <script type="text/javascript">
        class WebGLUtils {
    getGLContext = (canvas, version) => {
        canvas.width = window.innerWidth * 0.99;
        canvas.height = window.innerHeight * 0.85;
        var gl = canvas.getContext(version ? 'webgl' : 'webgl2');
        const ext = gl.getExtension("EXT_color_buffer_float");
        if (!ext) {
            console.log("sorry, can't render to floating point textures");
        }
        gl.clearColor(0, 0, 0, 1.0);
        gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT);
        gl.lineWidth(0.5);
        return gl;
    };

    clear = (gl) => {
        gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT);
    };

    getShader = (gl, type, shaderText) => {
        var vfs = gl.createShader(type);
        gl.shaderSource(vfs, shaderText);
        gl.compileShader(vfs);
        if (!gl.getShaderParameter(vfs, gl.COMPILE_STATUS)) {
            console.error(gl.getShaderInfoLog(vfs));
        }
        return vfs;
    };

    getProgram = (gl, vertexShader, fragmentShader) => {
        var program = gl.createProgram();
        gl.attachShader(program, this.getShader(gl, gl.VERTEX_SHADER, document.getElementById(vertexShader).text.trim()));
        gl.attachShader(program, this.getShader(gl, gl.FRAGMENT_SHADER, document.getElementById(fragmentShader).text.trim()));
        gl.linkProgram(program);
        if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
            console.error(gl.getProgramInfoLog(program));
        }
        return program;
    };

    createAndBindBuffer = (gl, relatedVertices, isNotJSArray) => {
        var buffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
        gl.bufferData(gl.ARRAY_BUFFER, isNotJSArray ? relatedVertices : new Float32Array(relatedVertices), gl.STATIC_DRAW);
        gl.bindBuffer(gl.ARRAY_BUFFER, null);
        return buffer;
    };

    createAndBindTexture = (gl, _) => {
        var texBuffer = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, texBuffer);
        if (_.img.width) {
            gl.texImage2D(gl.TEXTURE_2D, 0, _.internalFormat, _.format, _.dataType, _.img);
        } else {
            gl.texImage2D(gl.TEXTURE_2D, 0, _.internalFormat, _.width, _.height, 0, _.format, _.dataType, _.img);
        }
        // set the filtering so we don't need mips
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, _.filter);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, _.filter);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, _.mipMapST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, _.mipMapST);
        gl.bindTexture(gl.TEXTURE_2D, null);
        return texBuffer;
    };

    createTextureAndFramebuffer = (gl, _) => {
        const tex = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, tex);
        gl.texImage2D(gl.TEXTURE_2D, 0, _.internalFormat, _.width, _.height, 0, _.format, _.dataType, null);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, _.filter);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, _.filter);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, _.mipMapST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, _.mipMapST);
        const fb = gl.createFramebuffer();
        gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
        const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
        console.log(`can ${status === gl.FRAMEBUFFER_COMPLETE ? "" : "NOT "}render to R32`);
        return {tex: tex, fb: fb};
    };

    linkAndSendDataToGPU = (gl, program, linkedVariable, buffer, dimensions) => {
        var vertices = gl.getAttribLocation(program, linkedVariable);
        gl.enableVertexAttribArray(vertices);
        gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
        gl.vertexAttribPointer(vertices, dimensions, gl.FLOAT, gl.FALSE, 0, 0);
        return vertices;
    };

    sendDataToGPU = (gl, buffer, vertices, dimensions) => {
        gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
        gl.vertexAttribPointer(vertices, dimensions, gl.FLOAT, gl.FALSE, 0, 0);
    };

    sendTextureToGPU = (gl, tex, index) => {
        gl.activeTexture(gl.TEXTURE0 + index);
        gl.bindTexture(gl.TEXTURE_2D, tex);
    };

    calculateAspectRatio = (img, gl) => {
        var cols = img.width;
        var rows = img.height; 
        var imageAspectRatio = cols / rows;
        var ele = gl.canvas;
        var windowWidth = ele.width;
        var windowHeight = ele.height;
        var canvasAspectRatio = windowWidth / windowHeight;
        var renderableHeight, renderableWidth;
        var xStart, yStart;
        /// If image's aspect ratio is less than canvas's we fit on height
        /// and place the image centrally along width
        if(imageAspectRatio < canvasAspectRatio) {
            renderableHeight = windowHeight;
            renderableWidth = cols * (renderableHeight / rows);
            xStart = (windowWidth - renderableWidth) / 2;
            yStart = 0;
        }
    
        /// If image's aspect ratio is greater than canvas's we fit on width
        /// and place the image centrally along height
        else if(imageAspectRatio > canvasAspectRatio) {
            renderableWidth = windowWidth;
            renderableHeight = rows * (renderableWidth / cols);
            xStart = 0;
            yStart = ( windowHeight  - renderableHeight) / 2;
        }
    
        ///keep aspect ratio
        else {
            renderableHeight =  windowHeight ;
            renderableWidth = windowWidth;
            xStart = 0;
            yStart = 0;
        }
        return {
            y2 : yStart + renderableHeight,
            x2 : xStart + renderableWidth,
            x1 : xStart,
            y1 : yStart
        };
    };

    convertCanvasCoordsToGPUCoords = (canvas, AR) => {
        //GPU -> -1, -1, 1, 1
        //convert to 0 -> 1
        var _0To1 = {
            y2 : AR.y2/canvas.height,
            x2 : AR.x2/canvas.width,
            x1 : AR.x1/canvas.width,
            y1 : AR.y1/canvas.height
        };
        //Convert -1 -> 1
        return {
            y2 : -1 + _0To1.y2 * 2.0,
            x2 : -1 + _0To1.x2 * 2.0,
            x1 : -1 + _0To1.x1 * 2.0,
            y1 : -1 + _0To1.y1 * 2.0
        };
    };
    
    //convert -1->+1 to 0.0->1.0
    convertVertexToTexCoords = (x1, y1, x2, y2) => {
        return {
            y2 : (y2 + 1.0)/2.0,
            x2 : (x2 + 1.0)/2.0,
            x1 : (x1 + 1.0)/2.0,
            y1 : (y1 + 1.0)/2.0
        };
    };

    activateTextureByIndex = (gl, program, gpuRef, gpuTextureIndex) => {
        gl.useProgram(program);
        gl.uniform1i(gl.getUniformLocation(program, gpuRef), gpuTextureIndex);
    };

    prepareVec4 = (_) => {
        return [_.x1, _.y1, 0.0, 1.0,
            _.x2, _.y1, 0.0, 1.0,
            _.x1, _.y2, 0.0, 1.0,
            _.x2, _.y1, 0.0, 1.0,
            _.x1, _.y2, 0.0, 1.0,
            _.x2, _.y2, 0.0, 1.0];
    };
    
    prepareVec2 = (_) => {
        return [_.x1, _.y1,
            _.x2, _.y1, 
            _.x1, _.y2, 
            _.x2, _.y1,
            _.x1, _.y2,
            _.x2, _.y2];
    };
};
        </script>

Я могу отобразить 8-битную гистограмму как в WebGL1, так и в WebGL2, используя этот код .Но мне нужно сгенерировать 16-битную гистограмму, используя 16-битную текстуру.

Вот как я посылаю текстуру в графический процессор:

var tex = gl.createTexture(); // create empty texture
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texImage2D(
    gl.TEXTURE_2D, // target
    0, // mip level
    gl.RGBA16UI, // internal format -> gl.RGBA16UI
    w, h, // width and height
    0, // border
    gl.RGBA_INTEGER, //format -> gl.RGBA_INTEGER
    gl.UNSIGNED_SHORT, // type -> gl.UNSIGNED_SHORT
    img // texture data
);

Итак, возьмем работающий пример в уме, я застрял в нескольких вещах:

1) Как создать кадровый буфер / текстуру 65536 X 1, чтобы сохранить 16-битную гистограмму, как четко говорит WebGL: WebGL: INVALID_VALUE: texImage2D: width or height out of range.Можем ли мы попробовать 256 x 256 кадровый буфер?Я пытался, но застрял в точке нет.2 ниже.

2) Как считывать пиксели внутри вершинного шейдера в случае 16-битного кода, приведенного ниже, для 8-битных данных, будет ли он работать и для 16-битного?Как я не могу отладить, так и не могу сказать, работает ли он или нет:

<script id="hist-vs" type="not-js">
attribute float pixelId;

uniform vec2 u_resolution;
uniform sampler2D u_texture;
uniform vec4 u_colorMult;

void main() {
// based on an id (0, 1, 2, 3 ...) compute the pixel x, y for the source image
vec2 pixel = vec2(mod(pixelId, u_resolution.x), floor(pixelId / u_resolution.x));

// compute corresponding uv center of that pixel
vec2 uv = (pixel + 0.5) / u_resolution;

// get the pixels but 0 out channels we don't want
vec4 color = texture2D(u_texture, uv) * u_colorMult;

// add up all the channels. Since 3 are zeroed out we'll get just one channel
float colorSum = color.r + color.g + color.b + color.a;

// set the position to be over a single pixel in the 256x1 destination texture
gl_Position = vec4((colorSum * 255.0 + 0.5) / 256.0 * 2.0 - 1.0, 0.5, 0, 1);

gl_PointSize = 1.0;
}
</script>

1 Ответ

0 голосов
/ 21 сентября 2019

Если вы просто хотите получить ответы на свои 2 вопроса, тогда

1) Как создать рамочный буфер / текстуру 65536 X 1, чтобы сохранить 16-битную гистограмму, как четко говорит WebGL: WebGL: INVALID_VALUE: texImage2D: ширина или высота вне диапазона.Можем ли мы попробовать 256 x 256 кадровый буфер?

да, вы бы сделали текстуру 256x256, если хотите узнать итоговые значения для каждого из 65536 возможных значений

2) Как прочитать пиксели внутриВершинный шейдер в случае 16-битного кода ниже для 8-битных данных, будет ли он работать и для 16-битного?Как я не могу отладить, так и не могу сказать, работает ли он или нет:

Конечно, вы можете отлаживать.Попробуйте и посмотрите, верны ли результаты.Если это не так, вы смотрите на свой код и / или сообщения об ошибках и пытаетесь выяснить, почему.Это называется отладкой.Создайте текстуру 1x1, вызовите свою функцию, проверьте, имеет ли гистограмма правильный счет 1 для этого пиксельного ввода 1x1, вызвав gl.readPixels.Затем попробуйте 2x1 или 2x2.

В любом случае вы не можете прочитать gl.RGBA16UI текстур с GLSL 1.0 es.Вы должны использовать версию 300, поэтому, если вы действительно хотите создать отдельную корзину для всех 65536 значений, тогда

Вот несколько шейдеров WebGL2 GLSL 3.00 ES, которые будут заполнять итоги для значений от 0 до 65535 в256x256 текстура

#version 300 es

uniform usampler2D u_texture;
uniform uvec4 u_colorMult;

void main() {
  const int mipLevel = 0;

  ivec2 size = textureSize(u_texture, mipLevel);

  // based on an id (0, 1, 2, 3 ...) compute the pixel x, y for the source image
  vec2 pixel = vec2(
     gl_VertexID % size.x,
     gl_VertexID / size.x);

  // get the pixels but 0 out channels we don't want
  uvec4 color = texelFetch(u_texture, pixel, mipLevel) * u_colorMult;

  // add up all the channels. Since 3 are zeroed out we'll get just one channel
  uint colorSum = color.r + color.g + color.b + color.a;

  vec2 outputPixel = vec2(
     colorSum % 256u,
     colorSum / 256u);

  // set the position to be over a single pixel in the 256x256 destination texture
  gl_Position = vec4((outputPixel + 0.5) / 256.0 * 2.0 - 1.0, 0, 1);

  gl_PointSize = 1.0;
}

Пример

Примечания:

  • В WebGL2 вам не нужен pixelID, вы можете использоватьgl_VertexID, поэтому нет необходимости устанавливать какие-либо буферы или атрибуты.Просто позвоните

    const numPixels = img.width * img.height;
    gl.drawArrays(gl.POINTS, 0, numPixels);
    
  • Вы можете использовать textureSize, чтобы получить размер текстуры, поэтому нет необходимости передавать ее.

  • Вы можете использовать texelFetch, чтобы получить один тексель (пиксель) из текстуры.Требуются целочисленные пиксельные координаты.

  • Чтобы прочитать целочисленный текстурный формат без знака, такой как RGBA16UI, вы должны использовать usampler2D, иначе вы получите ошибку при рисовании во время рисования, чтобы использовать RGBA16UI текстура на sampler2D (вот как я знаю, что вы на самом деле не использовали текстуру RGBA16UI, потому что вы получили бы ошибку в консоли JavaScript, говорящую вам и ведущую вас к смене шейдера.

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

...