從零開始手擼WebGL3D引擎3 基本渲染流程介紹

2021-10-02 02:46:00 字數 4265 閱讀 4473

雖然乙個渲染框架可能很複雜,但是如果我們一開始就把事情想的很複雜往往難以下手。另一方面,現在有很多開源引擎,你直接去看也會感覺難以入手。因為複雜性是由於需求而不斷增加的,甚至有的複雜性是因為歷史原因。本系列文章的初衷,是記錄乙個可用的,簡單的webgl渲染框架的發展過程,在這個過程中加深對webgl和圖形技術的理解,並且通過實踐讓對知識的理解不再浮於表面。也許mini3d.js以後會變的很複雜,但是現在他是簡單的,是易於理解的,可作為乙個參考物件。如果我等到把mini3d.js做的功能很多很複雜,再回頭來看,一方面記憶已經模糊,另一方面設計可能已經幾易其稿而丟失了設計修改的抉擇過程。而現在,唯一要考慮的是能不能堅持下去。

第乙個里程碑的目標,是乙個可以用滑鼠拖動旋轉的彩色立方體。

該立方體每個面有乙個顏色(並不是很多opengl教程裡面那種漸變色的立方體),滑鼠拖動可上下左右旋轉,並且我還順便測試了一下萬向節死鎖的效果。

在搭建專案框架時,講到應用開始要呼叫mini3d.init('webgl')。這個init方法獲取了webgl的上下文物件webglrendringcontext,並進行一些初始化操作。**位於src/gl.js中。mini3d.js採用了es6的模組系統。gl.js匯出了3個物件:export ;其中gl是我們獲得的webgl上下文物件,canvas是執行webgl的canvas物件,而init則是初始化方法。

這個檔案比較短就都貼出來:

let gl =

null

;let canvas =

null

;function

init

(canvasid)

}else

canvas.width = math.

floor

(canvas.clientwidth * window.devicepixelratio)

; canvas.height = math.

floor

(canvas.clientheight * window.devicepixelratio)

;let names =

["webgl"

,"experimental-webgl"

,"webkit-3d"

,"moz-webgl"];

let context =

null

;for

(let i=

0; i

++i)

catch

(e)if

(context)

} gl = context;

gl.viewport(0

,0, canvas.width, canvas.height);}

;export

;

init方法的引數為canvas的id,如果不填寫,則會在document上建立乙個canvas物件,否則會根據id獲取到canvas物件。之後會計算出canvas的畫素為單位的寬和高。然後使用canvas.getcontext獲取webgl上下文,並儲存到gl物件中。最後呼叫gl.viewport設定視口。其實這兒沒什麼說的,都是常規化的操作。

由於是第乙個demo,所以先大概介紹一下渲染流程。這之後會逐個講述mini3d.js封裝的各個物件。

demo的入口在examples/src/main.js中,所有的內容都在example()方法內。

建立shader並對映到頂點屬性

首先,我們建立了乙個mini3d.shader物件:

let shader =

newmini3d.shader()

;if(!shader.

create

(vshader_source

,fshader_source))

其中vshader_source和fshader_source是預定義好的shader**,暫時還是直接用字串表示,後面會從檔案載入。mini3d.shader是對webgl shader&program的封裝,後面會詳細說明。

由於我們使用shader渲染模型時,要知道模型的頂點屬性和shader中的attribute的對應關係,所以下面呼叫:

shader.

mapattributesemantic

(mini3d.vertexsemantic.

position

,'a_position');

shader.

mapattributesemantic

(mini3d.vertexsemantic.

color

,'a_color'

);

將a_position對映到模型的position屬性上,a_color對映到color屬性,後面會詳細說明屬性和vertexsemantic。

使用shader

shader.

use(

);

使用哪個shader其實是乙個webgl的狀態,由於本例只有乙個shader不需要切換,所以在初始化之後直接use他。

建立mesh

createmesh()方法建立了乙個立方體模型,在mini3d中用mesh物件表示。模型由頂點組成,而每個頂點包含了很多資料,比如在這個例子裡面需要位置和顏色屬性。頂點由哪些資料組成,這些資料的長度以及在頂點資料buffer中的位置,由vertexformat定義。min3d.mesh建立時必須傳入乙個建立好的vertexformat。

let format =

newmini3d.vertexformat()

;format.

addattrib

(mini3d.vertexsemantic.

position,3

);format.

addattrib

(mini3d.vertexsemantic.

color,3

);let mesh =

newmini3d.mesh

(format)

;

這個例子裡面定義了兩個屬性,屬性的類別使用vertexsemantic中的預定義常量,其實mini3d.js支援自定義屬性,只要不和vertexsemantic中定義的常量衝突。

然後向mesh傳入頂點的各個屬性資料,並設定三角形索引,然後執行upload將資料上傳到視訊記憶體。

mesh.

setvertexdata

(mini3d.vertexsemantic.

position

, position_data)

;

mesh.

setvertexdata

(mini3d.vertexsemantic.

color

, color_data)

;

mesh.

set********s

(triangels)

;mesh.

upload()

;

建立矩陣

為什麼需要矩陣,其實不是webgl需要,而是我們為了在shader裡面變換頂點,將頂點從模型空間轉換到裁剪空間,而需要使用矩陣。我們需要最終在shader中使用mvp矩陣。在這個例子中,由於立方體是旋轉的,所以需要儲存立方體在世界空間下的旋轉,因此建立了乙個modelmatrix。由於camera不動,所以合併view和projection,建立了乙個viewprojmatrix。然後就是最終合成的mvpmatrix。

設定滑鼠操作事件

setupinput中設定了滑鼠操作事件,將滑鼠的移動量轉化為尤拉角的增量,然後改變尤拉角,並且重新繪製

設定全域性狀態

這個例子需要設定的全域性狀態只有clear color和depth,以及開啟depth_test。

繪製立方體

function

draw

(mesh, shader)

基於旋轉後的尤拉角,重新建立了modelmatrix,然後重新合併出mvpmatrix。將矩陣傳入shader,然後clear螢幕,對mesh呼叫render。

這個例子中,不是每幀都繪製,只會在滑鼠拖到時重新整理。可以看到本例中不變的是mesh, shader和viewprojmatrix。每次繪製需要重新計算modelmatrix以及mvpmatrix,需要清除螢幕。

從零開始擼乙個ajax框架

寫這篇文章的初衷 寫了5年 發現每天的工作都是在像拼積木一樣,拼湊著乙個又乙個功能,但是基本的實現原理可能就那幾把刷子,想著想著就乾脆整理成乙個自己風格的公共庫,既然說了,那就幹唄 框架的封裝是乙個開發者綜合能力的乙個體現,因為框架的封裝不僅關係到怎麼讓使用者更方便的去使用你封裝的這個方法,更需要乙...

從零開始擼乙個Fresco之總結

fresco是乙個比較大的開源專案,類的總數超過450個。經過乙個來月的翻譯,終於將絕大部分fresco的原始碼翻譯完畢了,也寫了幾篇fresco各個模組的分析部落格。fresco的源 是乙個巨大的寶庫,讓我能夠一窺android高階開發者開發乙個專案的方式與思路。之後如果有時間我還會帶來更多的an...

從零開始學編碼3

從零開始學編碼1中我們對編碼這個概念進行了了解。在從零開始學編碼2中學習了基本的電路知識。這篇我們說一說進製數。在遠古時期,人類便有了計數的需求,看著自己的手指,大拇指代表乙個蘋果,食指代表第二個蘋果,中指代表的第三個蘋果 好吧人類只有十根手指,可能只能做十以內的計數,後來人的思維能力發散,有了邏輯...