【问题标题】:Must render buffer texture dimensions be power-of-two?渲染缓冲区纹理尺寸必须是二次方吗?
【发布时间】:2014-11-12 16:27:12
【问题描述】:

我们用于 WebGL 渲染缓冲区存储的纹理是否需要具有二次幂的维度?

背景资料

我正在追踪客户在此设置上报告的 FRAMEBUFFER_INCOMPLETE_ATTACHMENT:

Windows 7 企业版 32 位 火狐版本:33 显卡:Intel Q45/Q43 Express 芯片组驱动程序版本 8.13.10.2413

到目前为止,我不知道为什么会发生这种情况,所以猜测它可能与 NPOT 纹理有关。

这是我的渲染缓冲区实现,它还没有两个纹理的幂:

SceneJS._webgl.RenderBuffer = function (cfg) {

    /**
     * True as soon as this buffer is allocated and ready to go
     * @type {boolean}
     */
    this.allocated = false;

    this.canvas = cfg.canvas;
    this.gl = cfg.canvas.gl;
    this.buf = null;
    this.bound = false;
};

/**
 * Called after WebGL context is restored.
 */
SceneJS._webgl.RenderBuffer.prototype.webglRestored = function (_gl) {
    this.gl = _gl;
    this.buf = null;
};

/**
 * Binds this buffer
 */
SceneJS._webgl.RenderBuffer.prototype.bind = function () {
    this._touch();
    if (this.bound) {
        return;
    }
    this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.buf.framebuf);
    this.bound = true;
};


SceneJS._webgl.RenderBuffer.prototype._touch = function () {

    var width = this.canvas.canvas.width;
    var height = this.canvas.canvas.height;

    if (this.buf) { // Currently have a buffer
        if (this.buf.width == width && this.buf.height == height) { // Canvas size unchanged, buffer still good
            return;
        } else { // Buffer needs reallocation for new canvas size
            this.gl.deleteTexture(this.buf.texture);
            this.gl.deleteFramebuffer(this.buf.framebuf);
            this.gl.deleteRenderbuffer(this.buf.renderbuf);
        }
    }

    this.buf = {
        framebuf: this.gl.createFramebuffer(),
        renderbuf: this.gl.createRenderbuffer(),
        texture: this.gl.createTexture(),
        width: width,
        height: height
    };

    this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.buf.framebuf);
    this.gl.bindTexture(this.gl.TEXTURE_2D, this.buf.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.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);
    this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE);
   

    try {
        // Do it the way the spec requires
        this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, width, height, 0, this.gl.RGBA, this.gl.UNSIGNED_BYTE, null);
    } catch (exception) {
        // Workaround for what appears to be a Minefield bug.
        var textureStorage = new WebGLUnsignedByteArray(width * height * 3);
        this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, width, height, 0, this.gl.RGBA, this.gl.UNSIGNED_BYTE, textureStorage);
    }

    this.gl.bindRenderbuffer(this.gl.RENDERBUFFER, this.buf.renderbuf);
    this.gl.renderbufferStorage(this.gl.RENDERBUFFER, this.gl.DEPTH_COMPONENT16, width, height);
    this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, this.gl.TEXTURE_2D, this.buf.texture, 0);
    this.gl.framebufferRenderbuffer(this.gl.FRAMEBUFFER, this.gl.DEPTH_ATTACHMENT, this.gl.RENDERBUFFER, this.buf.renderbuf);
    this.gl.bindTexture(this.gl.TEXTURE_2D, null);
    this.gl.bindRenderbuffer(this.gl.RENDERBUFFER, null);
    this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
    // Verify framebuffer is OK
    this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.buf.framebuf);

    if (!this.gl.isFramebuffer(this.buf.framebuf)) {
        throw SceneJS_error.fatalError(SceneJS.errors.ERROR, "Invalid framebuffer");
    }

    var status = this.gl.checkFramebufferStatus(this.gl.FRAMEBUFFER);

    switch (status) {

        case this.gl.FRAMEBUFFER_COMPLETE:
            break;

        case this.gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
            throw SceneJS_error.fatalError(SceneJS.errors.ERROR, "Incomplete framebuffer: FRAMEBUFFER_INCOMPLETE_ATTACHMENT");

        case this.gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
            throw SceneJS_error.fatalError(SceneJS.errors.ERROR, "Incomplete framebuffer: FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT");

        case this.gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
            throw SceneJS_error.fatalError(SceneJS.errors.ERROR, "Incomplete framebuffer: FRAMEBUFFER_INCOMPLETE_DIMENSIONS");

        case this.gl.FRAMEBUFFER_UNSUPPORTED:
            throw SceneJS_error.fatalError(SceneJS.errors.ERROR, "Incomplete framebuffer: FRAMEBUFFER_UNSUPPORTED");

        default:
            throw SceneJS_error.fatalError(SceneJS.errors.ERROR, "Incomplete framebuffer: " + status);
    }

    this.bound = false;
};

/**
 * Clears this renderbuffer
 */
SceneJS._webgl.RenderBuffer.prototype.clear = function () {
    if (!this.bound) {
        throw "Render buffer not bound";
    }
    this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT);
    this.gl.disable(this.gl.BLEND);
};

/**
 * Reads buffer pixel at given coordinates
 */
SceneJS._webgl.RenderBuffer.prototype.read = function (pickX, pickY) {
    var x = pickX;
    var y = this.canvas.canvas.height - pickY;
    var pix = new Uint8Array(4);
    this.gl.readPixels(x, y, 1, 1, this.gl.RGBA, this.gl.UNSIGNED_BYTE, pix);
    return pix;
};

/**
 * Unbinds this renderbuffer
 */
SceneJS._webgl.RenderBuffer.prototype.unbind = function () {
    this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
    this.bound = false;
};

/** Returns the texture
 */
SceneJS._webgl.RenderBuffer.prototype.getTexture = function () {
    var self = this;
    return {
        bind: function (unit) {
            if (self.buf && self.buf.texture) {
                self.gl.activeTexture(self.gl["TEXTURE" + unit]);
                self.gl.bindTexture(self.gl.TEXTURE_2D, self.buf.texture);
                return true;
            }
            return false;
        },
        unbind: function (unit) {
            if (self.buf && self.buf.texture) {
                self.gl.activeTexture(self.gl["TEXTURE" + unit]);
                self.gl.bindTexture(self.gl.TEXTURE_2D, null);
            }
        }
    };
};

/** Destroys this buffer
 */
SceneJS._webgl.RenderBuffer.prototype.destroy = function () {
    if (this.buf) {
        this.gl.deleteTexture(this.buf.texture);
        this.gl.deleteFramebuffer(this.buf.framebuf);
        this.gl.deleteRenderbuffer(this.buf.renderbuf);
        this.buf = null;
        this.bound = false;
    }
};

【问题讨论】:

  • 只要你的 textureWRAPmode 设置为 CLAMP_TO_EDGE 就没有限制
  • 您是否尝试过运行 WebGL 一致性测试。这些特别是test1test2。如果它们失败,则需要更新用户的驱动程序或浏览器需要将其列入黑名单。如果他们通过了,那么查看您的代码和测试之间的差异可能会发现问题。
  • 太棒了,谢谢gman,我会把这些测试发给客户试试。
  • 顺便说一句。 Ken Russell 刚刚建议也分配一个 DEPTH_STENCIL 渲染缓冲区,以防驱动程序需要它,我也会尝试。客户在不同的大陆,我没有那个设置,所以......需要一点时间来确认,我会回到 yiz。

标签: webgl


【解决方案1】:

据我所知(我不使用 WebGL),WebGL 规范在这些 FBO 相关调用上委托给 OpenGL ES 2.0 规范。每个组件 8 位的 RGBA 不是 ES 2.0 中支持作为渲染目标的格式。许多设备都支持它(通过 OES_rgb8_rgba8 扩展进行宣传),但它不是标准的一部分。

您作为COLOR_ATTACHMENT0 使用的纹理是具有 8 位分量的 RGBA:

this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, width, height, 0,
                   this.gl.RGBA, this.gl.UNSIGNED_BYTE, textureStorage);

尝试将其指定为 RGB565,它是可彩色渲染的:

this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, width, height, 0,
                   this.gl.RGB, this.gl.UNSIGNED_SHORT_5_6_5, textureStorage);

如果您确实需要纹理中的 alpha 组件,RGBA4444 或 RGB5_A1 是您唯一的可移植选项:

this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, width, height, 0,
                   this.gl.RGBA, this.gl.UNSIGNED_SHORT_4_4_4_4, textureStorage);
this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, width, height, 0,
                   this.gl.RGBA, this.gl.UNSIGNED_SHORT_5_5_5_1, textureStorage);

在我看来,规范实际上有些矛盾。在“WebGL 和 OpenGL ES 2.0 之间的差异”下,它说:

以下帧缓冲区对象附件的组合,当所有附件都是帧缓冲区附件完整、非零且具有相同的宽度和高度时,必须导致帧缓冲区完整:
COLOR_ATTACHMENT0 = RGBA/UNSIGNED_BYTE 纹理

乍一看表明支持RGBA/UNSIGNED_BYTE。但这是在“当所有附件都是帧缓冲附件完成时”的条件下,并且根据 ES 2.0 规范,这种格式的附件不是附件完整。 WebGL 规范中没有覆盖“附件完成”的含义。

【讨论】:

  • WebGL 需要 RGBA,每个组件有 8 位。这就是你引用的部分的重点。 ES 2.0 不需要,但它并没有禁止它。 WebGL 要求在最低 ES 2.0 之上支持其他几个最低要求,这是其中之一。事实上,ES 不需要任何可渲染的格式 RGB565、RGBA444、RGB5_A1 等,但 WebGL 确实需要规范中指定的 3 种在任何地方都可以工作。
  • @gman 我不确定我理解你最后一部分的意思。 ES 2.0 明确指定 RGBA4、RGBA5_A1 和 RGB565 为可渲染颜色。请参阅规范文档中的表 4.5。
  • RGBA4、RGBA5_A1 和 RGB565 是渲染缓冲区格式。上面的代码使用的是纹理,而不是其颜色附件的渲染缓冲区。表 4.5 是关于渲染缓冲区,而不是纹理。此外,不管第 4.5 节的那部分只是向下翻页,如果 附加图像的内部格式的组合不违反与实现相关的一组限制,则它表示帧缓冲区是不完整的。换句话说,ES 表示驱动程序可以拒绝任何附件组合,并且仍然符合规范。另一方面,WebGL 需要 3 种特定的组合才能工作。其他人可能会工作。
猜你喜欢
  • 2013-09-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-07
  • 2023-03-06
  • 1970-01-01
  • 1970-01-01
  • 2016-09-27
相关资源
最近更新 更多