【问题标题】:Compose an image with floating point layers in webgl在 webgl 中用浮点层合成图像
【发布时间】:2015-07-26 16:50:58
【问题描述】:

我试图在这样构建的浏览器中渲染图像:

  • 一堆矩形每个都用径向渐变填充(理想情况下是高斯,但可以用几个停止点来近似)
  • 每个矩形在被放置到绘图区域之前都经过旋转和平移

  • 通过对矩形的所有强度求和来展平图像(并裁剪为绘图区域的尺寸)

  • 重新调整强度,使最高强度为 255,最低为 0(理想情况下,我也可以应用某种伽玛校正)

  • 最后绘制了一张图像,其中每个像素的颜色取自 256 种颜色的调色板。

我不能用画布对象轻松做到这一点的原因是我需要在浮点数中工作,否则我会失去精度。我事先不知道最大强度和最小强度会是多少,所以我不能只画透明的矩形并寄希望于最好的结果。

有没有办法在 webgl 中做到这一点?如果是这样,我会怎么做?

【问题讨论】:

    标签: html5-canvas webgl webcl


    【解决方案1】:

    您可以使用常规画布来执行此任务:

    1) 检查矩形的最小值/最大值,因此您可以构建一个映射函数 double -> [0-255] 超出该范围。

    2) 在 'lighter' 模式下绘制矩形 == 添加组件值。

    3) 当多个矩形重叠时,您可能会出现饱和:如果是这样,请将映射范围加倍并转到 2)。
    现在,如果您没有饱和度,只需调整范围以使用画布的整个 [0-255] 范围,您就完成了。

    由于此算法使用getImageData,因此它可能无法在所有浏览器/设备上达到 60 fps。但桌面/Chrome 上超过 10fps 似乎完全有可能。

    希望下面的代码能澄清我的描述:

    //noprotect
    // boilerplate
    var cv = document.getElementById('cv');
    var ctx = cv.getContext('2d');
    
    // rectangle collection
    var rectCount = 30;
    var rects = buildRandRects(rectCount);
    
    
    iterateToMax();
    
    
    // --------------------------------------------
    
    function iterateToMax() {
        var limit = 10; // loop protection
        // initialize min/max mapping based on rects min/max
        updateMapping(rects);
        //
        while (true) {
            // draw the scene using current mapping
            drawScene();
            // get the max int value from the canvas
            var max = getMax();
            if (max == 255) {
                // saturation ?? double the min-max interval
                globalMax = globalMin + 2 * (globalMax - globalMin);
            } else {
                // no sauration ? Just adjust the min-max interval
                globalMax = globalMin + (max / 255) * (globalMax - globalMin);
                drawScene();
                return;
            }
            limit--;
            if (limit <= 0) return;
        }
    }
    
    // --------------------------------------------
    // --------------------------------------------
    
    // Oriented rectangle Class.
    function Rect(x, y, w, h, rotation, min, max) {
        this.min = min;
        this.max = max;
        this.draw = function () {
            ctx.save();
            ctx.fillStyle = createRadialGradient(min, max);
            ctx.translate(x, y);
            ctx.rotate(rotation);
            ctx.scale(w, h);
            ctx.fillRect(-1, -1, 2, 2);
            ctx.restore();
        };
        var that = this;
    
        function createRadialGradient(min, max) {
            var gd = ctx.createRadialGradient(0, 0, 0, 0, 0, 1);
            var start = map(that.min);
            var end = map(that.max);
            gd.addColorStop(0, 'rgb(' + start + ',' + start + ',' + start + ')');
            gd.addColorStop(1, 'rgb(' + end + ',' + end + ',' + end + ')');
            return gd;
        }
    }
    
    // Mapping : float value -> 0-255 value
    var globalMin = 0;
    var globalMax = 0;
    
    function map(value) {
        return 0 | (255 * (value - globalMin) / (globalMax - globalMin));
    }
    
    // create initial mapping 
    function updateMapping(rects) {
        globalMin = rects[0].min;
        globalMax = rects[0].max;
        for (var i = 1; i < rects.length; i++) {
            var thisRect = rects[i];
            if (thisRect.min < globalMin) globalMin = thisRect.min;
            if (thisRect.max > globalMax) globalMax = thisRect.max;
        }
    }
    
    // Random rect collection
    function buildRandRects(rectCount) {
        var rects = [];
        for (var i = 0; i < rectCount; i++) {
            var thisMin = Math.random() * 1000;
            var newRect = new Rect(Math.random() * 400, Math.random() * 400, 10 + Math.random() * 50, 10 + Math.random() * 50, Math.random() * 2 * Math.PI, thisMin, thisMin + Math.random() * 1000);
            rects.push(newRect);
        }
        return rects;
    }
    
    // draw all rects in 'lighter' mode (=sum values)
    function drawScene() {
        ctx.save();
        ctx.globalCompositeOperation = 'source-over';
        ctx.clearRect(0, 0, cv.width, cv.height);
        ctx.globalCompositeOperation = 'lighter';
        for (var i = 0; i < rectCount; i++) {
            var thisRect = rects[i];
            thisRect.draw();
        }
        ctx.restore();
    }
    
    
    // get maximum value for r for this canvas 
    //   ( == max r, g, b value for a gray-only drawing. )
    function getMax() {
        var data = ctx.getImageData(0, 0, cv.width, cv.height).data;
        var max = 0;
        for (var i = 0; i < data.length; i += 4) {
            if (data[i] > max) max = data[i];
            if (max == 255) return 255;
        }
        return max;
    }
    &lt;canvas id='cv' width = 400 height = 400&gt;&lt;/canvas&gt;

    【讨论】:

    • 感谢您的回答和代码,但是您如何处理数值稳定性?如果我有 1000 个重叠的矩形,每个矩形的贡献很小,该怎么办?
    • 不客气。这只是学术上的“假设”,还是真的有可能? (换句话说:您的应用程序应该具有的最大矩形数/动态范围是多少?)
    • 不,我确实希望在每个像素上都有几千个重叠的矩形
    • 然后我很想删除我的答案......你应该a)使用浮动webGL缓冲区(哎哟!为了显示第一个矩形而产生的开销!更不用说浏览器支持问题.) 或 b) 在 Float32 数组中手动​​绘制渐变矩形 => 转换为 ImageData => 在画布上绘制。也许是因为我在 Context2D 方面更流利,但我敢打赌我会在 4 小时内完成 b),并且 a) 在 4... 天内完成!!!
    • 对了,我在 JS 中逐个像素地绘制所有内容,但我需要大约 300 毫秒才能用统一的颜色填充 512x512 图像...
    猜你喜欢
    • 2016-02-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多