WebGLの簡単なチュートリアル(XIII):フレームバッファオブジェクト(オフスクリーンレンダリング)

1.概要

物事は、一般的にリンクされています。多くの場合、他の状態の途中でレンダリング結果にレンダリングされるオブジェクトを使用する必要がより現実的な結果を達成するために、最後のシーンをレンダリングするプロセスが表示しました。それは、フレーム・バッファ・オブジェクト(フレームバッファオブジェクト、FBOをいう)に格納され、この中間描画結果は、緩衝液は、色やデプスバッファを置き換えるために使用されます。結果が直接表示されないので、このような技術はまた、(描画オフスクリーン)オフスクリーン描画として知られています。

前の例の過程で、地形からの色情報は、頂点バッファオブジェクトです。このチュートリアルでは、例えば書き込みを調製する:色情報が地形でプロットされ、フレームバッファ及びカラーバッファの色バッファは、頂点を介してバッファ・フレーム・バッファを取得することによって取得されません。この単純な例では、実際の意味を指定しませんが、より良いFBOを理解するために、FBOは、その後、より高度な技術の基礎となっています。

2.例

ここでは主にそれの重要な部分に、完全なコードサンプルは、アドレスが尾をダウンロードするための記事で提供することができる彼らはここに差し出す、長すぎます。

2.1。シェーダ部

これは、二つのシェーダはフレームバッファに描画集合で定義されています。

// 顶点着色器程序-绘制到帧缓存
var FRAME_VSHADER_SOURCE =
  'attribute vec4 a_Position;\n' +  //位置
  'attribute vec4 a_Color;\n' + //颜色
  'uniform mat4 u_MvpMatrix;\n' +
  'varying vec4 v_Color;\n' +
  'void main() {\n' +
  '  gl_Position = u_MvpMatrix * a_Position;\n' + // 设置顶点坐标
  '  v_Color = a_Color;\n' +
  '}\n';

// 片元着色器程序-绘制到帧缓存
var FRAME_FSHADER_SOURCE =
  'precision mediump float;\n' +
  'varying vec4 v_Color;\n' +
  'void main() {\n' +  
  '  gl_FragColor = v_Color;\n' +   //将深度保存在FBO中
  '}\n';

これは、カラーバッファシェーダに差を描画しないためのシェーダープログラムを見ることができます。通常のカラーバッファに描画された別のグループ:

// 顶点着色器程序
var VSHADER_SOURCE =
  'attribute vec4 a_Position;\n' +  //位置
  'attribute vec4 a_Color;\n' + //颜色
  'attribute vec4 a_Normal;\n' + //法向量
  'uniform mat4 u_MvpMatrix;\n' +     
  'varying vec4 v_PositionFromLight;\n' +
  'void main() {\n' +
  '  gl_Position = u_MvpMatrix * a_Position;\n' +
  '  v_PositionFromLight = gl_Position;\n' +
  '}\n';

// 片元着色器程序
var FSHADER_SOURCE =
  '#ifdef GL_ES\n' +
  'precision mediump float;\n' +
  '#endif\n' +
  'uniform sampler2D u_Sampler;\n' +  //颜色贴图
  'varying vec4 v_PositionFromLight;\n' +
  'void main() {\n' +
  //获取颜色贴图中的值
  '  vec3 shadowCoord = (v_PositionFromLight.xyz/v_PositionFromLight.w)/2.0 + 0.5;\n' +
  '  gl_FragColor = texture2D(u_Sampler, shadowCoord.xy);\n' +
  '}\n';

ここでは、まだ頂点配列から最終的な位置を見ることができ、色補間は、テクスチャの対象外です。テクスチャオブジェクトは、オブジェクトを描画するフレームバッファの後に渡されるテクスチャオブジェクトに関連付けられたフレームバッファです。

注記に基づいて計算したテクスチャ座標のことを「簡単なチュートリアルはWebGLの(V):グラフの変換(モデル、ビュー、射影変換)」は、このチュートリアルに記載された、頂点シェーダを通過した後に、頂点座標を正規化します1 -1の間、およびテクスチャ座標0と1の間であるので、変換にそれを調整することが必要です。

2.2。初期化/準備

最初は、まだいくつかの初期化操作です。コンテキストを取得した後にシェーダを作成し、オブジェクト(FBO)バッファフレームを初期化します。

  // 获取 <canvas> 元素
  var canvas = document.getElementById('webgl');

  // 获取WebGL渲染上下文
  var gl = getWebGLContext(canvas);
  if (!gl) {
    console.log('Failed to get the rendering context for WebGL');
    return;
  }

  //初始化两个着色器,drawProgram绘制到界面,frameProgram绘制到帧缓存
  var drawProgram = createProgram(gl, VSHADER_SOURCE, FSHADER_SOURCE);
  var frameProgram = createProgram(gl, FRAME_VSHADER_SOURCE, FRAME_FSHADER_SOURCE);
  if (!drawProgram || !frameProgram) {
    console.log('Failed to intialize shaders.');
    return;
  }

  //从着色器中获取地址,保存到对应的变量中
  GetProgramLocation(gl, drawProgram, frameProgram);

  // 初始化帧缓冲区对象 (FBO)
  var fbo = initFramebufferObject(gl);
  if (!fbo) {
    console.log('Failed to intialize the framebuffer object (FBO)');
    return;
  }

  // 开启深度测试
  gl.enable(gl.DEPTH_TEST);

  // 指定清空<canvas>的颜色
  gl.clearColor(0.0, 0.0, 0.0, 1.0);

  //清空颜色和深度缓冲区
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

ここで、変数便利に格納されているいくつかのスイッチングシェーダ再分配データの操作を伴うので、アップ取得シェーダからのアドレスデータを格納された機能的な機能GetProgramLocationです。

//从着色器中获取地址,保存到对应的变量中
function GetProgramLocation(gl, drawProgram, frameProgram) {
  // Get the storage location of attribute variables and uniform variables 
  drawProgram.a_Position = gl.getAttribLocation(drawProgram, 'a_Position');
  drawProgram.u_MvpMatrix = gl.getUniformLocation(drawProgram, 'u_MvpMatrix');
  if (drawProgram.a_Position < 0 || !drawProgram.u_MvpMatrix) {
    console.log('Failed to get the storage location of a_Position, u_MvpMatrix');
    //return;
  }

  frameProgram.a_Position = gl.getAttribLocation(frameProgram, 'a_Position');
  frameProgram.a_Color = gl.getAttribLocation(frameProgram, 'a_Color');
  frameProgram.u_MvpMatrix = gl.getUniformLocation(frameProgram, 'u_MvpMatrix');
  if (frameProgram.a_Position < 0 || frameProgram.a_TexCoord < 0 || !frameProgram.u_MvpMatrix) {
    console.log('Failed to get the storage location of a_Position, a_Color, u_MvpMatrix');
    //return;
  }
}

2.2.1。シェーダスイッチ

例2実描画動作では、再びフレームバッファ及びカラーバッファに描画されています。このように、二つの異なるシェーダを使用する必要があります。しかし、同じ描画作業時間は、シェーダによって設定することができ、ここではシェーダスイッチの問題を伴います。

2.2.1.1。初期化

関数initShadersによって初期化される以前の実施例にcuon-utilsの組立シェーダのWebGL。この関数は、実際には、シェーダプログラムがcreateProgramを()関数を作成し、現在のシェーダ機能gl.useProgram()を設定するための機能が含まれています。

function initShaders(gl, vshader, fshader) {
  var program = createProgram(gl, vshader, fshader);
  if (!program) {
    console.log('Failed to create program');
    return false;
  }

  gl.useProgram(program);
  gl.program = program;

  return true;
}

ただ、その上にプログラムの初期化シェーダー機能createProgram()を作成し、あなたがデータを転送して描画する必要がある場合、現在のシェーダgl.useProgramを()に設定してください。

2.2.1.2。頂点バッファ

加えて、頂点バッファの使用は、変更されています。チュートリアルの前に:「(オブジェクトをバッファリング)三角形を描く簡単なチュートリアルWebGLの(c)は、」頂点バッファ内の5つのステップを紹介しました:

  1. バッファオブジェクトを作成します(gl.createBuffer())
  2. 結合バッファオブジェクト(gl.bindBuffer())
  3. ライトデータバッファオブジェクト(gl.bufferData())
  4. 属性変数のバッファオブジェクトに割り当てられた(gl.vertexAttribPointer())
  5. オープン属性変数(gl.enableVertexAttribArray())

しかし、スペースを節約するために、二つの異なるシェーダは、必要なときに割り当てデータを切り替え、これを用いたデータバッファの頂点です。従って、2つの機能に以上の5つのステップがあるかもしれない - 初期化時に、ステップ1-3に進み左分布を用いてプロットしたときに、頂点バッファにデータを書き込みます。

//向顶点缓冲区写入数据,留待以后分配
function initArrayBufferForLaterUse(gl, data, num, type) {
  // Create a buffer object
  var buffer = gl.createBuffer();
  if (!buffer) {
    console.log('Failed to create the buffer object');
    return null;
  }
  // Write date into the buffer object
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);

  // Store the necessary information to assign the object to the attribute variable later
  buffer.num = num;
  buffer.type = type;

  return buffer;
}

4〜5のステップについて、対応する描画シェーダに切り替える場合:バッファオブジェクトを割り当て、接続を開始します。

//分配缓冲区对象并开启连接
function initAttributeVariable(gl, a_attribute, buffer) {
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  gl.vertexAttribPointer(a_attribute, buffer.num, buffer.type, false, 0, 0);
  gl.enableVertexAttribArray(a_attribute);
}

オブジェクトバッファにバインドするために必要な頂点バッファに割り当てられた頂点データのインデックス、またもちろん、:

//向顶点缓冲区写入索引,留待以后分配
function initElementArrayBufferForLaterUse(gl, data, type) {
  // Create a buffer object
  var buffer = gl.createBuffer();
  if (!buffer) {
    console.log('Failed to create the buffer object');
    return null;
  }
  // Write date into the buffer object
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer);
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, data, gl.STATIC_DRAW);

  buffer.type = type;

  return buffer;
}

2.2.2フレームバッファ

オブジェクトがレンダリング中間結果バッファフレームに格納され、従って3つのにそれぞれ関連するオブジェクトが存在している - カラーバッファの代わりに使用し、関連するオブジェクトの色(色アタッチメント)、関連するオブジェクト(深さアタッチメント)と関連付けられたオブジェクトテンプレート(ステンシルアタッチメント)の深さ、面積、深度バッファとステンシルバッファ。テクスチャ・オブジェクトとオブジェクト(レンダオブジェクト)バッファレンダリング:関連の被験者は、2つのタイプに分けました。テクスチャ・オブジェクトが関連付けられているオブジェクトのカラーフレームバッファとして定義することができ、一般的に、レンダーバッファオブジェクトは、関連するオブジェクトが達成されるオフスクリーン描画バッファフレームの深さとして定義されます。

画像
図2-1:フレームバッファオブジェクト、テクスチャオブジェクト、バッファオブジェクトをレンダリング

フレームバッファはinitFramebufferObjectで仕事()関数内で初期化されます。具体的には、フレーム・バッファは、特定の設定ステップ8に細分することができます。

2.2.2.1。フレーム・バッファ・オブジェクト(gl.createFramebuffer())を作成します

()オブジェクトを初期化するには、gl.createFramebufferによって作成されます。

// 初始化帧缓冲区对象 (FBO)
function initFramebufferObject(gl) {
  //...

  // 创建帧缓冲区对象 (FBO)
  framebuffer = gl.createFramebuffer();
  if (!framebuffer) {
    console.log('Failed to create frame buffer object');
    return error();
  }

  //...
}

2.2.2.2。テクスチャオブジェクトを作成し、その寸法やパラメータを設定

チュートリアルでは、「WebGLの簡単なチュートリアル(XI):テクスチャ」すでに、我々はテクスチャオブジェクトを作成し、テクスチャオブジェクトのパラメータを設定する方法を説明しました。ここでの作成プロセスは同じですが、少しだけ違うの詳細:

  1. ここでは、キャンバスの長さと幅と同じことはできませんが、あなたは速いスピードをしたい長さと幅の質感を設定より小さい、あなたは良い結果をしたい、それが大きくなることがあります。
  2. 最後のパラメータgl.texImage2D機能は、新たなフレームバッファを描画するために空白領域を示す、nullに設定する必要があります。
function initFramebufferObject(gl) {
  //...

  // 创建纹理对象并设置其尺寸和参数
  texture = gl.createTexture(); // 创建纹理对象
  if (!texture) {
    console.log('Failed to create texture object');
    return error();
  }
  gl.bindTexture(gl.TEXTURE_2D, texture); // Bind the object to target
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);  
  // 设置纹理参数
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
  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);
  framebuffer.texture = texture; // 保存纹理对象

  //...
}

2.2.2.3。バッファオブジェクト(gl.createRenderbuffer())レンダリングの作成

()gl.createRenderbufferによってレンダリングバッファオブジェクトを作成する機能は、描画バッファオブジェクトは、関連するオブジェクトの深さに割り当てられます。

function initFramebufferObject(gl) {
  //...

  // 创建渲染缓冲区对象并设置其尺寸和参数
  depthBuffer = gl.createRenderbuffer(); //创建渲染缓冲区
  if (!depthBuffer) {
    console.log('Failed to create renderbuffer object');
    return error();
  }

  //...
}

2.2.2.4。レンダ結合およびセットサイズ(gl.bindRenderbuffer()、gl.renderbufferStorage())

ターゲットへのレンダバインド、レンダリングパラメータのバッファサイズにより設定された目標。

function initFramebufferObject(gl) {
  //...

  gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer); // Bind the object to target
  gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT);

    //...
}

WebGLの/ OpenGLのために、任意のバッファ・オブジェクトがターゲット、操作の標的に結合することが必要です。gl.bindRenderbufferは()である結合機能を定義します。

画像

結合、gl.renderbufferStorageによって()関数後レンダフォーマット、幅、および高さを設定します。幅および高さ関連するレンダリング・バッファの深さは、オブジェクトのテクスチャに関連付けられたカラーバッファと同じでなければならないことに留意されたいです。その機能は次のように定義されています。

画像

2.2.2.5。オブジェクトに関連付けられているフレームバッファ(gl.bindFramebuffer()、gl.framebufferTexture2D)にテクスチャオブジェクト

機能gl.bindFramebuffer()バインドを使用して、まだターゲットに結合する第1のフレームバッファであります:

画像

テクスチャオブジェクト関連するオブジェクトは、カラーフレームバッファとして指定され、標的結合を作成し使用して、定義された関数gl.framebufferTexture2D()を次のように
画像

次のように関連するコードの例は以下のとおりです。

function initFramebufferObject(gl) {
  //...
  // 将纹理和渲染缓冲区对象关联到帧缓冲区对象上
  gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
  gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);   //关联颜色
  
  //...
}

WebGLの異なるとOpenGLこのパラメータアタッチメントgl.COLOR_ATTACHMENT0の値は、WebGLの一色のみ関連するオブジェクトは、OpenGLの複数を可能にすることに留意されたいです。

2.2.2.6。オブジェクトに関連付けられているフレームバッファにオブジェクトバッファレンダリング(gl.framebufferRenderbuffer())

使用gl.framebufferRenderbuffer()関数は、フレームバッファへのオブジェクト関連するオブジェクト関連付けバッファ深度をレンダリングします。

function initFramebufferObject(gl) {
  //...
  gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);    //关联深度
  //...
}

次のようにその機能が定義されています。

画像

2.2.2.7。フレームバッファ(gl.checkFramebufferStatus())の構成を確認

フレームバッファ構成プロセスは複雑で、WebGLのチェック機能は、(gl.checkFramebufferStatusを提供します)。

画像

次のように関連するコードは次のとおりです。

function initFramebufferObject(gl) {
  //...
  // 检查帧缓冲区是否被正确设置
  var e = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
  if (gl.FRAMEBUFFER_COMPLETE !== e) {
    console.log('Frame buffer object is incomplete: ' + e.toString());
    return error();
  }
  //...
}

2.2.2.8。()(gl.bindFramebuffer)フレームバッファに描画し

あなたはバインディングフレームバッファオブジェクトを描画バッファフレーム内で呼び出す必要がある場合には、必要性は時にカラーバッファレンダリングを結合到達します。()関数は、特に次のセクションを参照する、gl.bindFramebufferによって達成されます。

2.3。描画機能

初期化の準備が完了した後、データをロードした後の動作をレンダリング次のグラフィックスは、描画機能DrawDEM()を呼び出します。

  demFile.addEventListener("change", function (event) {
    //...
    reader.onload = function () {
      if (reader.result) {

        //读取
        var terrain = new Terrain();
        if (!readDEMFile(reader.result, terrain)) {
          console.log("文件格式有误,不能读取该文件!");
        }

        //绘制
        DrawDEM(gl, canvas, fbo, frameProgram, drawProgram, terrain);
      }
    }

readDEMFile()是读取解析DEM文件的函数,并保存到自定义的Terrain对象中,通过这个Terrain对象,调用DrawDEM()进行绘制:

//绘制
function DrawDEM(gl, canvas, fbo, frameProgram, drawProgram, terrain) {
  // 设置顶点位置
  var demBufferObject = initVertexBuffersForDrawDEM(gl, terrain);
  if (!demBufferObject) {
    console.log('Failed to set the positions of the vertices');
    return;
  }

  //获取光线:平行光
  var lightDirection = getLight();

  //预先给着色器传递一些不变的量
  {
    //使用帧缓冲区着色器
    gl.useProgram(frameProgram);
    //设置MVP矩阵
    setMVPMatrix(gl, canvas, terrain.sphere, lightDirection, frameProgram);

    //使用颜色缓冲区着色器
    gl.useProgram(drawProgram);
    //设置MVP矩阵
    setMVPMatrix(gl, canvas, terrain.sphere, lightDirection, drawProgram);
    //将绘制在帧缓冲区的纹理传递给颜色缓冲区着色器的0号纹理单元
    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, fbo.texture);
    gl.uniform1i(drawProgram.u_Sampler, 0);

    gl.useProgram(null);
  }

  //开始绘制
  var tick = function () {
    //帧缓存绘制
    gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); //将绘制目标切换为帧缓冲区对象FBO
    gl.viewport(0, 0, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT); // 为FBO设置一个视口

    gl.clearColor(0.2, 0.2, 0.4, 1.0); // Set clear color (the color is slightly changed)
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // Clear FBO
    gl.useProgram(frameProgram); //准备生成纹理贴图

    //分配缓冲区对象并开启连接
    initAttributeVariable(gl, frameProgram.a_Position, demBufferObject.vertexBuffer); // 顶点坐标
    initAttributeVariable(gl, frameProgram.a_Color, demBufferObject.colorBuffer); // 颜色

    //分配索引并绘制
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, demBufferObject.indexBuffer);
    gl.drawElements(gl.TRIANGLES, demBufferObject.numIndices, demBufferObject.indexBuffer.type, 0);

    //颜色缓存绘制
    gl.bindFramebuffer(gl.FRAMEBUFFER, null); //将绘制目标切换为颜色缓冲区
    gl.viewport(0, 0, canvas.width, canvas.height); // 设置视口为当前画布的大小

    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // Clear the color buffer
    gl.useProgram(drawProgram); // 准备进行绘制

    //分配缓冲区对象并开启连接
    initAttributeVariable(gl, drawProgram.a_Position, demBufferObject.vertexBuffer); // Vertex coordinat

    //分配索引并绘制
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, demBufferObject.indexBuffer);
    gl.drawElements(gl.TRIANGLES, demBufferObject.numIndices, demBufferObject.indexBuffer.type, 0);

    window.requestAnimationFrame(tick, canvas);
  };
  tick();
}

2.3.1. 初始化顶点数组

首先第一步仍然是初始化顶点缓冲区数组,但是与之前不同的是这个只传输顶点数据到顶点缓冲区,并不连接顶点着色器,因为两组着色器是公用顶点数据的,所以需要在切换着色器的时候分配着色器并连接:

function initVertexBuffersForDrawDEM(gl, terrain) {
  //DEM的一个网格是由两个三角形组成的
  //      0------1            1
  //      |                   |
  //      |                   |
  //      col       col------col+1    
  var col = terrain.col;
  var row = terrain.row;

  var indices = new Uint16Array((row - 1) * (col - 1) * 6);
  var ci = 0;
  for (var yi = 0; yi < row - 1; yi++) {
    //for (var yi = 0; yi < 10; yi++) {
    for (var xi = 0; xi < col - 1; xi++) {
      indices[ci * 6] = yi * col + xi;
      indices[ci * 6 + 1] = (yi + 1) * col + xi;
      indices[ci * 6 + 2] = yi * col + xi + 1;
      indices[ci * 6 + 3] = (yi + 1) * col + xi;
      indices[ci * 6 + 4] = (yi + 1) * col + xi + 1;
      indices[ci * 6 + 5] = yi * col + xi + 1;
      ci++;
    }
  }

  var dem = new Object(); // Create the "Object" object to return multiple objects.

  // Write vertex information to buffer object
  dem.vertexBuffer = initArrayBufferForLaterUse(gl, terrain.vertices, 3, gl.FLOAT);
  dem.colorBuffer = initArrayBufferForLaterUse(gl, terrain.colors, 3, gl.FLOAT);
  dem.normalBuffer = initArrayBufferForLaterUse(gl, terrain.normals, 3, gl.FLOAT);
  dem.indexBuffer = initElementArrayBufferForLaterUse(gl, indices, gl.UNSIGNED_SHORT);
  if (!dem.vertexBuffer || !dem.colorBuffer || !dem.indexBuffer || !dem.normalBuffer) {
    return null;
  }

  dem.numIndices = indices.length;

  // Unbind the buffer object
  gl.bindBuffer(gl.ARRAY_BUFFER, null);
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);

  return dem;
}

2.3.2. 传递非公用随帧不变的数据

为了满足交互需求,绘制函数仍然是通过刷新页面函数requestAnimationFrame()实现的,有的数据是固定随帧不变的,这样的数据可以提前传输好。当然,这些数据不包含共用的顶点缓冲区数据:

  //获取光线:平行光
  var lightDirection = getLight();

  //预先给着色器传递一些不变的量
  {
    //使用帧缓冲区着色器
    gl.useProgram(frameProgram);
    //设置MVP矩阵
    setMVPMatrix(gl, canvas, terrain.sphere, lightDirection, frameProgram);

    //使用颜色缓冲区着色器
    gl.useProgram(drawProgram);
    //设置MVP矩阵
    setMVPMatrix(gl, canvas, terrain.sphere, lightDirection, drawProgram);
    //将绘制在帧缓冲区的纹理传递给颜色缓冲区着色器的0号纹理单元
    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, fbo.texture);
    gl.uniform1i(drawProgram.u_Sampler, 0);

    gl.useProgram(null);
  }

注意这里通过函数gl.useProgram()切换了着色器,然后再分别给着色器传输数据。在这个例子只是通过帧缓冲区做颜色中转,所以帧缓冲区和颜色缓冲区绘制的MVP矩阵是相同且固定的,所以可以提前传输好。并且,将帧缓冲区关联着颜色关联对象的纹理对象,分配给颜色缓冲区的片元着色器。

2.3.3. 逐帧绘制

刷新页面函数requestAnimationFrame()的回调函数tick()中进行绘制,页面每隔一段时间就会调用这个绘制函数。

2.3.3.1. 绘制到帧缓存

为了声明当前是绘制到帧缓存的,首先将要绑定帧缓冲区对象gl.bindFramebuffer()。然后调用gl.viewport()函数定义一个绘图的视口:

画像

接下来还是通过gl.useProgram()切换到对应的着色器,分配并连接顶点缓冲区的顶点数据;最后调用gl.drawElements()进行绘制即可。

相关的代码如下:

//开始绘制
  var tick = function () {
    //帧缓存绘制
    gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); //将绘制目标切换为帧缓冲区对象FBO
    gl.viewport(0, 0, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT); // 为FBO设置一个视口

    gl.clearColor(0.2, 0.2, 0.4, 1.0); // Set clear color (the color is slightly changed)
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // Clear FBO
    gl.useProgram(frameProgram); //准备生成纹理贴图

    //分配缓冲区对象并开启连接
    initAttributeVariable(gl, frameProgram.a_Position, demBufferObject.vertexBuffer); // 顶点坐标
    initAttributeVariable(gl, frameProgram.a_Color, demBufferObject.colorBuffer); // 颜色

    //分配索引并绘制
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, demBufferObject.indexBuffer);
    gl.drawElements(gl.TRIANGLES, demBufferObject.numIndices, demBufferObject.indexBuffer.type, 0);

    //...

    window.requestAnimationFrame(tick, canvas);
  };
  tick();
}

2.3.3.2. 绘制到颜色缓存

绘制到颜色缓冲区的步骤也是一致的,只不过在绘制之前需要调用gl.bindFramebuffer(gl.FRAMEBUFFER, null)解除帧缓冲区绑定,将绘制目标切换到当前的颜色缓冲区。当然,设置视口和切换着色器操作都是必须的。相关代码如下:

//开始绘制
  var tick = function () {
    //...

    //颜色缓存绘制
    gl.bindFramebuffer(gl.FRAMEBUFFER, null); //将绘制目标切换为颜色缓冲区
    gl.viewport(0, 0, canvas.width, canvas.height); // 设置视口为当前画布的大小

    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // Clear the color buffer
    gl.useProgram(drawProgram); // 准备进行绘制

    //分配缓冲区对象并开启连接
    initAttributeVariable(gl, drawProgram.a_Position, demBufferObject.vertexBuffer); // Vertex coordinat

    //分配索引并绘制
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, demBufferObject.indexBuffer);
    gl.drawElements(gl.TRIANGLES, demBufferObject.numIndices, demBufferObject.indexBuffer.type, 0);

    window.requestAnimationFrame(tick, canvas);
  };
  tick();
}

3. 结果

最后运行的结果如下,显示的是一个特定角度的地形:

画像

前のチュートリアルと比較すると、例では、特殊ないないようです。シャドウ:例えば、次の技術で議論される - この例のキーポイントは、より詳細な技術的な準備のために、フレームバッファを介して、このレンダリング・トランジットです。

4.リファレンス

:「プログラミングガイドのWebGL、」ソースリンクからコードやイラストのもともと一部のアドレスフォローアップこの共有ディレクトリの内容を更新していきます。

おすすめ

転載: www.cnblogs.com/charlee44/p/11965462.html