无法渲染到帧缓冲区(纹理)

Unable to render to framebuffer (texture)

我正在尝试使用阴影贴图实现阴影,因此我需要将场景渲染到单独的帧缓冲区(纹理)。我无法让它正常工作,所以在剥离我的代码库后,我只剩下一组相对简单的指令,这些指令应该将场景渲染为纹理,然后简单地渲染纹理。

该程序由两个程序组成:

  1. 地面程序
  2. 茶壶计划

第一个应该渲染一个具有特定纹理的矩形。第二个应该渲染一个茶壶(根据其位置使用颜色)。 Eech 渲染步骤执行以下操作(好吧,无论如何就是这个想法):

  1. 切换到帧缓冲区
  2. 渲染茶壶
  3. 切换到普通缓冲区
  4. 渲染茶壶
  5. 渲染地面

现在,地面片段着色器看起来像:

gl_FragColor = texture2D(shadowMap, fTexCoord);

'shadowMap' 是我在第 2 步中渲染的纹理。我 期望 看到一个漂浮的茶壶,下面画了一个矩形。这确实有效。现在,我还希望 'ground' 包含一个茶壶。毕竟,我们在没有地面的情况下渲染了我们正在看的场景到 framebuffer/texture.

代码

var UNSIGNED_SHORT_SIZE = 2;

// Variables filled by setup()
var glCanvas;
var gl, teapotProgram, groundProgram;
var vBuffer, iBuffer, fBuffer;
var vertices, indices, textures;

var teapot = null;
var model;
var view;
var light;
var projection;

var BASE_URL = "https://hmbastiaan.nl/martijn/webgl/W08P02_SO/";

var WIDTH = 150, HEIGHT = 150;

function makeTeapot(){
    var drawingInfo = teapot.getDrawingInfoObjects();
    var indices = drawingInfo.indices;

    for(var i=0; i < indices.length; i++){
        indices[i] += 4; // Add offset for 'ground'
    }

    return {
        indices: drawingInfo.indices,
        vertices: drawingInfo.vertices
    }
}

function makeRectangle(x1, x2, y1, y2, z1, z2){
    var x1 = -2,
        x2 =  2,
        y1 = -1,
        y2 = -1,
        z1 = -1,
        z2 = -5;

    var vertices = [
        vec4(x1, y2, z1, 1),
        vec4(x2, y1, z1, 1),
        vec4(x2, y1, z2, 1),
        vec4(x1, y2, z2, 1)
    ];

    var textures = [
        vec2(-1.0, -1.0),
        vec2( 1.0, -1.0),
        vec2( 1.0,  1.0),
        vec2(-1.0,  1.0)
    ];

    var indices = [
        0, 1, 2,
        0, 2, 3
    ];

    return {
        indices: indices,
        vertices: vertices,
        textures: textures
    }

}

function resetBuffers(){
    vertices = [];
    indices = [];
    textures = [];

    // Add rectangle
    var rectangle = makeRectangle();
    Array.prototype.push.apply(vertices, rectangle.vertices);
    Array.prototype.push.apply(indices, rectangle.indices);
    Array.prototype.push.apply(textures, rectangle.textures);

    // Add teapot
    var teapot = makeTeapot();
    Array.prototype.push.apply(vertices, teapot.vertices);
    Array.prototype.push.apply(indices, teapot.indices);

    console.log(vertices);
    console.log(indices);
    console.log(textures);

    // Send to GPU
    gl.bindBuffer(gl.ARRAY_BUFFER, vBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, flatten(vertices), gl.STATIC_DRAW);

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, iBuffer);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
}

function setup(){
    $.get(BASE_URL + "teapot.obj", function(teapot_obj_data){
        teapot = new OBJDoc(BASE_URL + "teapot.obj");

        if(!teapot.parse(teapot_obj_data, 1)){
            alert("Parsing teapot.obj failed.");
            return;
        }

        setup2();
    }).fail(function(){
        alert("Getting teapot.obj failed.");
    });
}

function setup2(){
    glCanvas = document.getElementById("gl-canvas");

    gl = WebGLUtils.setupWebGL(glCanvas, {stencil: true, alpha: false});
    gl.viewport(0, 0, WIDTH, HEIGHT);

    teapotProgram = initShaders(gl, BASE_URL + "vshader-teapot.glsl", BASE_URL + "fshader-teapot.glsl");
    groundProgram = initShaders(gl, BASE_URL + "vshader-ground.glsl", BASE_URL + "fshader-ground.glsl");

    light = vec3(0.0, 2.0, -2.0);
    view = lookAt(vec3(0, 0, 3), vec3(0,0,0), vec3(0,1,0));
    projection = perspective(45, 1.0, 1, 100.0);

    // Get teapot uniforms
    gl.useProgram(teapotProgram);
    teapotProgram.modelLoc      = gl.getUniformLocation(teapotProgram, "Model");
    teapotProgram.viewLoc       = gl.getUniformLocation(teapotProgram, "View");
    teapotProgram.projectionLoc = gl.getUniformLocation(teapotProgram, "Projection");

    // Upload uniforms
    gl.uniformMatrix4fv(teapotProgram.projectionLoc, false, flatten(projection));
    gl.uniformMatrix4fv(teapotProgram.viewLoc, false, flatten(view));
    gl.uniformMatrix4fv(teapotProgram.modelLoc, false, flatten(scalem(0.25, 0.25, 0.25)));

    // Get teapot attributes
    teapotProgram.vPosition = gl.getAttribLocation(teapotProgram, "vPosition");

    // Get ground uniforms
    gl.useProgram(groundProgram);
    groundProgram.modelLoc      = gl.getUniformLocation(groundProgram, "Model");
    groundProgram.viewLoc       = gl.getUniformLocation(groundProgram, "View");
    groundProgram.projectionLoc = gl.getUniformLocation(groundProgram, "Projection");
    groundProgram.shadowMap     = gl.getUniformLocation(groundProgram, "shadowMap");

    // Get ground attributes
    groundProgram.vTexCoord = gl.getAttribLocation(groundProgram, "vTexCoord");
    groundProgram.vPosition = gl.getAttribLocation(groundProgram, "vPosition");

    // Allocate and fill vertices buffer
    vBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vBuffer);

    gl.vertexAttribPointer(teapotProgram.vPosition, 4, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(teapotProgram.vPosition);

    gl.vertexAttribPointer(groundProgram.vPosition, 4, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(groundProgram.vPosition);

    // Allocate indices buffer
    iBuffer = gl.createBuffer();

    // Setup FBO
    fBuffer = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, fBuffer);

    fBuffer.renderbuffer = gl.createRenderbuffer();
    gl.bindRenderbuffer(gl.RENDERBUFFER, fBuffer.renderbuffer);
    gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, 512, 512);

    fBuffer.texture = gl.createTexture();
    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, fBuffer.texture);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 512, 512, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
    gl.generateMipmap(gl.TEXTURE_2D);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST_MIPMAP_LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);

    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, fBuffer.texture, 0);
    gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, fBuffer.renderbuffer);

    // Sanity checking: framebuffer seems to throw now errors
    if (!gl.isFramebuffer(fBuffer)) {
        throw("Invalid framebuffer");
    }

    var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
    switch (status) {
        case gl.FRAMEBUFFER_COMPLETE:
            break;
        case gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
            throw("Incomplete framebuffer: FRAMEBUFFER_INCOMPLETE_ATTACHMENT");
            break;
        case gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
            throw("Incomplete framebuffer: FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT");
            break;
        case gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
            throw("Incomplete framebuffer: FRAMEBUFFER_INCOMPLETE_DIMENSIONS");
            break;
        case gl.FRAMEBUFFER_UNSUPPORTED:
            throw("Incomplete framebuffer: FRAMEBUFFER_UNSUPPORTED");
            break;
        default:
            throw("Incomplete framebuffer: " + status);
    }

    // Set ground textures
    gl.uniform1i(groundProgram.shadowMap, 0);

    // Upload uniforms
    gl.uniformMatrix4fv(groundProgram.projectionLoc, false, flatten(projection));
    gl.uniformMatrix4fv(groundProgram.viewLoc, false, flatten(view));
    gl.uniformMatrix4fv(groundProgram.modelLoc, false, flatten(mat4()));

    // Restore default buffers
    gl.bindTexture(gl.TEXTURE_2D, null);
    gl.bindRenderbuffer(gl.RENDERBUFFER, null);
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);

    // Set background colour
    gl.clearColor(0.3921, 0.5843, 0.9294, 1.0);

    gl.enable(gl.DEPTH_TEST);
    gl.enable(gl.CULL_FACE);

    resetBuffers();

    window.requestAnimationFrame(render);
}

function render(){
    var teapot = makeTeapot();

    gl.useProgram(teapotProgram);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);

    // Switch to framebuffer
    gl.bindFramebuffer(gl.FRAMEBUFFER, fBuffer);

    // Draw teapot
    teapot = makeTeapot();
    gl.drawElements(gl.TRIANGLES, teapot.indices.length, gl.UNSIGNED_SHORT, 6 * UNSIGNED_SHORT_SIZE);

    // Set framebuffer to defualt buffer (in-browser output)
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);

    // Draw ground
    gl.useProgram(groundProgram);
    gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);

    // Render teapot
    gl.useProgram(teapotProgram);
    gl.drawElements(gl.TRIANGLES, teapot.indices.length, gl.UNSIGNED_SHORT, 6 * UNSIGNED_SHORT_SIZE);
}

setup();
<div>
    <br/>
    <canvas width="150" height="150" id="gl-canvas">Sorry :|</canvas>
</div>
<script type='text/javascript' src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
<script type='text/javascript' src="https://hmbastiaan.nl/martijn/webgl/angel/webgl-utils.js"></script>
<script type='text/javascript' src="https://hmbastiaan.nl/martijn/webgl/angel/initShaders2.js"></script>
<script type='text/javascript' src="https://hmbastiaan.nl/martijn/webgl/angel/MV.js"></script>
<script type='text/javascript' src="https://hmbastiaan.nl/martijn/webgl/angel/objParser.js"></script>

感兴趣的函数:

  1. setup2(): 设置所有缓冲区和制服。
  2. render(): 渲染场景。

免责声明:这是一个作业,尽管这段代码已经足够简化,看起来根本不像原来的作业:)。

一眼看去有几个问题

  1. 纹理绑定是全局的。由于在 setup2 中您取消绑定 1 纹理,这意味着它从未被使用过。

    您需要在每次绘制调用之前绑定所需的任何纹理。换句话说,当你绘制地面时,你需要绑定茶壶纹理,如

    gl.bindTexture(gl.TEXTURE_2D, fBuffer.texture);
    

    注意:这是对真正需要的东西的过度简化。你真的需要

    1. 选择一个纹理单元来绑定纹理

      var unit = 5;
      gl.activeTexture(gl.TEXTURE0 + unit);
      
    2. 将纹理绑定到该单元。

      gl.bindTexture(gl.TEXTURE_2D, fBuffer.texture);
      
    3. 将统一采样器设置为该纹理单元

      gl.uniform1i(groundProgram.shadowMap, unit);
      

    您不需要这些额外步骤的原因是 (a) 您只 有 1 个纹理,所以您使用纹理单元 #0,默认值和 (b) 因为 制服默认为 0,因此 shadowMap 正在查看纹理单元 #0。

  2. 因为您制作了一个 mipmapped 纹理,只是渲染到级别 0 不会更新 mips。

    换句话说,在渲染茶壶后,您将拥有一个 mip 级别 0 的茶壶,但 mip 级别 1、2、3、4、5 等仍然没有任何内容。您需要致电

    gl.generateMipmap(gl.TEXTURE_2D)
    

    在将茶壶渲染到该纹理后的纹理。或者停止使用 mips

  3. 每次调用都需要设置viewport gl.bindFramebuffer.

    gl.bindFramebuffer 之后几乎总是会调用 gl.viewport 以使视口与您要渲染的对象的大小相匹配

    gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
    // set to size of fb
    gl.viewport(0, 0, widthOfFb, heightOfFb);
    
    renderSomething();
    
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
    // set to size of canvas's drawingBuffer
    gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
    
  4. 属性设置是全局的

    您设置了茶壶属性。然后你在纹理上画一个茶壶。然后绘制地面,但您仍在使用茶壶属性。

    就像纹理一样,您需要在每次绘制调用之前设置属性。

我猜你真的不应该在你的渲染函数中调用 makeTeapot 而应该在设置中调用它。

您可能会发现 this article useful

您还应该考虑 not putting properties on WebGL objects as it's arguably an anti-pattern

同步 XHR 请求也不酷。您在 JavaScript 控制台

中收到此消息

Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience. For more help, check http://xhr.spec.whatwg.org/.