javascript
webgl编程指南源码_ThreeJS 源码剖析之 Renderer(一)
引子?
最近,忽然想起曾在 WebGL 基礎系列 文章中立下 flag:“后續還打算出 《ThreeJS 源碼剖析》 系列”(特意翻出原話?),項目忙了一陣后,便決定開始寫此系列,更新周期不固定,畢竟項目排期“天曉得”。此系列與其說是分享,倒不如說是共同學習罷了。作為最出名的 WebGL 的開源庫,ThreeJS 已受到了廣泛認可和好評,同時其自身也在不斷更新,讓開發者用的舒心放心。
說回自己,本初入圖形學世界,僅憑個人興趣撞的鼻青臉腫,但也樂在其中。自認為技術本不該有距離感,以有趣且通俗易懂的方式傳播技術也是一件趣事。任憑其吹的天花亂墜,何等高大上,但其本質是服務于我們的,歸根結底還是要我們接受才可。故之前的文章也盡量保持著基礎、有趣、易懂的風格(偶爾會開開車?),未來亦會嘗試更多元的文章風格。但其宗旨都是為了讓各位更容易接受。
以上僅個人觀點,如與各位觀念有相悖之處,可相互交流,以彼之長補己之短。
下面正式開始 ThreeJS 源碼剖析 系列?
前言?
當學習一種新技術時,大部分情況都會首選其官方文檔,而文檔中能最快讓我們上手的章節便是 Getting Started。ThreeJS 也不例外,我們進入到其對應的 Getting Started 頁面后,便能看到 Creating the scene 的代碼僅有下面寥寥幾行:
var?scene?=?new?THREE.Scene();var?camera?=?new?THREE.PerspectiveCamera(?75,?window.innerWidth?/?window.innerHeight,?0.1,?1000?);
var?renderer?=?new?THREE.WebGLRenderer();
renderer.setSize(?window.innerWidth,?window.innerHeight?);
document.body.appendChild(?renderer.domElement?);
再滾動至最下方,各位會看到完整的 Demo:
//?...var?animate?=?function?()?{
??requestAnimationFrame(?animate?);
??cube.rotation.x?+=?0.01;
??cube.rotation.y?+=?0.01;
??renderer.render(?scene,?camera?);
};
animate();
閱讀完簡單的十幾行代碼之后,各位可能會發現這個 Demo 中在一直調用 animate() 方法,而此方法中我們修改了立方體的旋轉角度,并執行了這一行代碼:renderer.render( scene, camera )。假如你沒有接觸過圖形學或 WebGL,僅憑語義會翻譯成:“渲染器渲染場景和攝像機”。沒錯,這一行的作用正是如此!
“我看你骨骼驚奇,理解力非凡,一定是個學圖形學的天才”(這也體現了命名規范合理是多么重要)?
假如我們將上面那句話拆解一下:“渲染器 - 渲染 - 場景和攝像機”,這句話正符合了我們的 “主 - 謂 - 賓”的結構。那么就可見主語(即渲染器)是個很重要的存在!所以此系列的第一篇文章我們就從渲染器 —— Renderer 說起。
Renderer??
將 ThreeJS 文檔目錄滾動至 Renderers 一欄,可以看到有 WebGLMultisampleRenderTarget 、WebGLRenderer 、WebGL1Renderer 、WebGLRenderTarget 和 WebGLCubeRenderTarget 五類渲染器,而我們就聚焦于 WebGLRenderer 這個渲染器即可,稍后會給大家介紹一下為何會有 WebGL1Renderer,至于其他三種渲染器,我們就不先討論(其實我也沒用過,后續了解了咱們再聊)?
WebGL1Renderer
假如你對 WebGL 稍有了解,那么就會知道其實 WebGL 有 1.0 和 2.0 兩個版本,那么兩個版本分別是基于 OpenGL ES 2.0 和 OpenGL ES 3.0 的,至于不基于 OpenGL ES 1.0 是因為 1.0 是固定管線的,2.0 和 3.0 是可編程管線。所謂固定管線就是我們給管線配置相應參數和開關即可,可編程管線的可編程部分就是我們所熟知的 頂點著色器 和 片元著色器。
扯遠了,再說回 WebGL,這倆版本有什么差別呢?大家可以看本文 WebGL What's New,簡單概括一下就是多了更多紋理格式、內置函數、3D 紋理貼圖,同時還支持了非2的整數次方大小的圖片。同時,WebGL 2.0 與 WebGL 1.0 在對瀏覽器的兼容性上有很大的差異,以 Chrome 為例,WebGL 1.0 兼容 9 及以上的 Chrome 版本,而 WebGL 2.0 則兼容 56 及以上的 Chrome 版本,對瀏覽器的兼容性的巨大差異就有極大的可能讓一些陳舊的 WebGL 1.0 的系統崩潰,故 ThreeJS 提供了 WebGL1Renderer 來進行適配兼容。ThreeJS 已經從 r118 版本就全面升級到 WebGL 2.0 了,所以如果你的系統使用的 ThreeJS 版本低于 r118,并且準備升級到最新的 r120 版本,為了避免程序掛掉你需要將 Renderer 替換成 WebGL1Renderer!如何查看最新 ThreeJS 版本請見下圖:
而查看當前項目使用的 ThreeJS 版本請見 package.json 中的 ThreeJS 依賴版本的 MINOR 版本號即可,比如:three: 0.120.0 就表明是 r120 版本,npm 語義化版本請見官方文檔。
WebGLRenderer
說回我們的主角 WebGLRenderer,進入到源碼(src/renderers/WebGLRenderer.js)后可以看到頭部有很多參數初始化的操作,在次就不一一介紹,關于參數含義大家可參考文檔:了解參數含義請閱讀 Constructor 部分。其中需要提一下的是,大家可能會發現這條語句:
const?_canvas?=?parameters.canvas?!==?undefined???parameters.canvas?:?document.createElementNS(?'http://www.w3.org/1999/xhtml',?'canvas'?);這里使用的是 createElementNS 并非 createElement,二者區別及用途請閱讀 Document.createElementNS: What's the difference and why we need it。
getContext
當我們 new 一個 Renderer 時,其內部會最先進入這下面的語句:
if?(?_gl?===?null?)?{??const?contextNames?=?[?'webgl2',?'webgl',?'experimental-webgl'?];
??if?(?_this.isWebGL1Renderer?===?true?)?{
????contextNames.shift();
??}
??_gl?=?getContext(?contextNames,?contextAttributes?);
??if?(?_gl?===?null?)?{
????if?(?getContext(?contextNames?)?)?{
??????throw?new?Error(?'Error?creating?WebGL?context?with?your?selected?attributes.'?);
????}?else?{
??????throw?new?Error(?'Error?creating?WebGL?context.'?);
????}
??}
}
首先當 WebGL 上下文不存在時,Renderer 內部列出了瀏覽器所提供的所有 WebGL 上下文名稱,如若是 WebGL1Renderer,則刪去 WebGL 2.0 的上下文名稱,然后調用 getContext 方法獲取上下文。getContext 方法實現也十分簡單:
function?getContext(?contextNames,?contextAttributes?)?{??for?(?let?i?=?0;?i?????const?contextName?=?contextNames[?i?];
????const?context?=?_canvas.getContext(?contextName,?contextAttributes?);
????if?(?context?!==?null?)?return?context;
??}
??return?null;
}
遍歷上方列舉的上下文名稱,如果獲取上下文則返回,否則返回 null,這么簡單的代碼聰明的你也能寫出來你,四舍五入你和 ThreeJS 貢獻者的水平就是一樣的?
initContext
當獲取到上下文后,緊接著調用的就是 initGLContext 方法了:
function?initGLContext()?{??extensions?=?new?WebGLExtensions(?_gl?);
??capabilities?=?new?WebGLCapabilities(?_gl,?extensions,?parameters?);
??//?若不是?WebGL?2.0,則需額外獲取以下?extensions
??if?(?capabilities.isWebGL2?===?false?)?{
????//?...
??}
??extensions.get(?'OES_texture_float_linear'?);
??
??utils?=?new?WebGLUtils(?_gl,?extensions,?capabilities?);
??state?=?new?WebGLState(?_gl,?extensions,?capabilities?);
??state.scissor(?_currentScissor.copy(?_scissor?).multiplyScalar(?_pixelRatio?).floor()?);
??state.viewport(?_currentViewport.copy(?_viewport?).multiplyScalar(?_pixelRatio?).floor()?);
??//?...
}
用導游的話來講:“首先映入我們眼簾的是 new WebGLExtensions 和 new WebGLCapabilities 兩條語句”,WebGLExtensions 的作用是判斷某個 extension 是否存在,以及獲取指定的 extension 并 全量返回,而 WebGLCapabilities 則是獲取當前 WebGL 系統的一些屬性,如:支持的最大精度、是否是 WebGL 2.0 以及所支持的最大貼圖大小等。而下面的 WebGLUtils 中只有一個 convert 方法,該方法主要是 將自定義的類型轉換成 WebGL 內置的類型。
緊隨其后的就是 WebGLState,別看它名字很短,但是它卻很重要!用描述蔡總(我們項目組的翹楚)的一句話就是 “短,但有用!” 通過這個 state,我們可以設置視口大小、設置混合、設置材質、綁定紋理等。其余的內容就先省略,后續有必要再講,先讓各位知道 Renderer 內部的工作流程。
調用完 initGLContext 方法后就有一句:
const?xr?=?new?WebXRManager(?_this,?_gl?);WebXR 是一組支持將渲染3D場景用來呈現虛擬世界(虛擬現實,也稱作 VR)或將圖形圖像添加到現實世界(增強現實,也稱作 AR)的標準,詳情請了解MDN文檔。
animation
調用完 initContext 后,就來到了 animation 環節:
const?animation?=?new?WebGLAnimation();animation.setAnimationLoop(?onAnimationFrame?);
if?(?typeof?window?!==?'undefined'?)?animation.setContext(?window?);
animation 的調用更是只有這簡單的 3 行語句,WebGLAnimation 的類型如下:
class?WebGLAnimation?{?start():?void;
?stop():?void;
?setAnimationLoop(?callback:?Function?):?void;
?setContext(?value:?Window?):?void;
}
當調用 setAnimationLoop 時,其實就是將傳入的 callback 賦給 WenGLAnimation 內部的 animationLoop 變量,當調用 start 方法時,就調用 window 的 requestAnimationFrame 方法。所以 ThreeJS 內部的動畫也是不斷調用 requestAnimationFrame 來實現的,呵!凡人!
而 setContext 則是將 context 設為 window,至于 stop 方法大家應該也能猜到里面如何停止動畫的了吧!
結束語?
這么快就結束語了?你是不是給我偷工減料了!?
其實 WebGLRenderer 大約 2000 行代碼,但其中絕大部分是函數聲明供我們后續的使用。回顧本文,大家就會發現當我們 new 一個 Renderer 時 ThreeJS 只不過是幫我們初始化了 context,并設置了一下 animation,其余的函數都是后續才會用到。故初始化一個 Renderer 也沒什么故弄玄虛的!
既然內部有這么多的函數讓我們后續使用,那么下篇文章就來講講我們常用的 Renderer 中的方法!
感謝閱讀!
總結
以上是生活随笔為你收集整理的webgl编程指南源码_ThreeJS 源码剖析之 Renderer(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: opencv Mat push_back
- 下一篇: 搭建webUI自动化及问题解决:Mess