1. 程式人生 > >WebGL2系列之多采樣渲染緩衝物件

WebGL2系列之多采樣渲染緩衝物件

在很久很久以前,盤古開闢了天地,他的頭頂著天,腳踩著地,最後他掛了。他的毛髮變成了森林,他的血液變成了河流,他的肌肉變成了大地。。。。。。
卡!
哦,不對,在很久很久以前,你屬於我,我擁有你。
你還有沒有程式設計師的自我修養啦。
不好意思,串戲了,下面進入。。。主題
本文適合對webgl、計算機圖形學、前端視覺化感興趣的讀者。

在很久很久以前,使用WebGL1的時候,只能在預設的繪製的緩衝區上面使用MSAA,而不能在幀緩衝區上面實現,更加形象的說就是:MSAA不能用於離屏渲染。
如果需要在幀緩衝區(離屏渲染)上面實現去鋸齒效果,需要在貼圖內容上使用自己實現的post -process的AA,比如:

  • FXAA: https://github.com/mattdesl/glsl-fxaa
  • SMAA http://threejs.org/examples/#webgl_postprocessing_smaa
    而且在WebGL1中,不能通過上下文來改變MSAA的取樣數量,這對於WebGL1下的去鋸齒效果有很大影響。

多采樣渲染緩衝物件

在WebGL2中,有了一個新的特性,叫做Multisampled Renderbuffer,恩,中文呢就叫做: 多采樣渲染緩衝物件吧;通過多采樣渲染緩衝物件,可以在幀緩衝區的渲染緩衝物件上實現MSAA(multisampled antialiasing), 然後通過下面的流程實現最終實現渲染的去鋸齒:

pre-z pass –> rendering pass to FBO –> postprocessing pass 
–> render to window

函式renderbufferStorageMultisample

和多采樣渲染緩衝物件相關的一個重要的函式就是gl.renderbufferStorageMultisample,下面是函式的簽名:

gl.renderbufferStorageMultisample(target, samples, internalFormat, width, height);

該函式的第一個target是渲染緩衝物件的“目標”,samples表示取樣數,internalFormat表示資料格式,width、height表示渲染緩衝物件的寬高。

下面是使用該函式的簡單程式碼片段:

var frameBuffer = gl.createFrameBuffer();
var colorRenderbuffer = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, colorRenderbuffer);
gl.renderbufferStorageMultisample(gl.RENDERBUFFER, 4, gl.RGBA8, FRAMEBUFFER_SIZE.x, FRAMEBUFFER_SIZE.y);

gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, colorRenderbuffer);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);

這和webgl1 中建立幀緩衝區的程式碼類似,並沒有太大差別,不同的是如下一行:

gl.renderbufferStorageMultisample(gl.RENDERBUFFER, 4, gl.RGBA8, FRAMEBUFFER_SIZE.x, FRAMEBUFFER_SIZE.y);

通過gl.renderbufferStorageMultisample方法指定了渲染緩衝物件的多重取樣,取樣數是4。

多采樣紋理附件

多采樣紋理附件又是什麼東西呢,好吧,其實在WebGL2中,沒有這個多采樣紋理附件,在OPENGL才有,為什麼提到這個多采樣紋理附件,大部分時間,我們的離屏渲染都需要渲染到一個紋理物件上面,才能進一步使用。
在沒有多采樣紋理附件,只有多采樣渲染緩衝物件的情況下,要實現MSAA,只能渲染到渲染緩衝物件上,但是渲染緩衝物件的內容不能直接傳遞給紋理物件。
那麼應該怎麼做呢? 需要使用另外一個重要的函式:

gl.blitFramebuffer函式

通過gl.blitFramebuffer函式,可以把多采樣渲染緩衝物件的內容傳遞給紋理物件。下面是該函式的簽名:

gl.blitFramebuffer(srcX0, srcY0, srcX1, srcY1,
                        dstX0, dstY0, dstX1, dstY1,
                        mask, filter);

該函式的作用就是,把一個幀緩衝區(read framebuffer)上的指定區域畫素轉移給另外一個幀緩衝區(draw framebuffer)上的指定區域。
其中引數srcX0, srcY0, srcX1, srcY1指定read framebuffer上的區域;
dstX0, dstY0, dstX1, dstY1 指定draw framebuffer上的區域; mask指定那個buffer的內容會被copy,可選值:

  • gl.COLOR_BUFFER_BIT
  • gl.DEPTH_BUFFER_BIT
  • gl.STENCIL_BUFFER_BIT
    filter 表示當兩個區域大小不同的時候,插值的方式,可以是以下值:
  • gl.NEAREST
  • gl.LINEAR

下面是程式碼片段:

var renderableFramebuffer = gl.createFramebuffer();
......
var colorFramebuffer = gl.createFramebuffer();
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, FRAMEBUFFER_SIZE.x, FRAMEBUFFER_SIZE.y, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
gl.bindTexture(gl.TEXTURE_2D, null);

gl.bindFramebuffer(gl.FRAMEBUFFER, colorFramebuffer);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);

// ...

// After drawing to the multisampled renderbuffers
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, renderableFramebuffer);
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, colorFramebuffer);
gl.clearBufferfv(gl.COLOR, 0, [0.0, 0.0, 0.0, 1.0]);
gl.blitFramebuffer(
    0, 0, FRAMEBUFFER_SIZE.x, FRAMEBUFFER_SIZE.y,
    0, 0, FRAMEBUFFER_SIZE.x, FRAMEBUFFER_SIZE.y,
    gl.COLOR_BUFFER_BIT, gl.NEAREST
);

程式碼中,首先把場景渲染到renderableFramebuffer中,然後把renderableFramebuffer繫結到目標gl.READ_FRAMEBUFFER,把colorFramebuffer繫結到目標gl.DRAW_FRAMEBUFFER,之後清空DRAW_FRAMEBUFFER上面的顏色關聯物件,然後呼叫gl.blitFramebuffer方法把renderableFramebuffer的顏色關聯物件上的資料複製到colorFramebuffer的顏色管理物件,colorFramebuffer的顏色關聯物件是一個紋理物件,這樣就把資料從渲染緩衝物件複製到紋理物件上面了。

READ_FRAMEBUFFER和DRAW_FRAMEBUFFER

在webgl1中,幀緩衝區的物件的目標只能是gl.FRAMEBUFFER,而在WebGL2中,增加兩種目標:

  • gl.READ_FRAMEBUFFER
  • gl.DRAW_FRAMEBUFFER
    以上兩種目標分別表示FBO可以分別進行讀操作和寫操作;這在FBO複製到FBO的時候很有用,就像前文中所敘述的,可以把READ_FRAMEBUFFER上的資料複製到DRAW_FRAMEBUFFER上。

參考

https://github.com/mrdoob/three.js/pull/8120
https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext/blitFramebuffer
https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext/blitFramebuffer
http://www.realtimerendering.com/blog/webgl-2-new-features/
https://www.khronos.org/registry/webgl/specs/latest/2.0/#2.2

歡迎關注公眾號“ITman彪叔”。彪叔,擁有10多年開發經驗,現任公司系統架構師、技術總監、技術培訓師、職業規劃師。熟悉Java、JavaScript、Python語言,熟悉資料庫。熟悉java、nodejs應用系統架構,大資料高併發、高可用、分散式架構。在計算機圖形學、WebGL、前端視覺化方面有深入研究。對程式設計師思維能力訓練和培訓、程式設計師職業規劃有濃厚興趣。

ITman彪叔公