經(jīng)過這一個月來欲仙欲死的摸索,總算在摸索出了一些入門webGl的門道抬闷。關(guān)于webGl的學(xué)習(xí)妇蛀,我建議大家去入手一本《webGl編程指南》和《線性代數(shù)》,里面的內(nèi)容非常詳細(xì)笤成,這里也不需要在多說了评架。還有,編程指南那本書的代碼結(jié)構(gòu)寫得還是值得人吐槽的炕泳,所以纵诞,遇到問題也請多多善用搜索引擎,或者:https://stackoverflow.com/search?q=你的問題
培遵。
本文不做關(guān)于webgl的任何教程內(nèi)容浙芙,本文旨在分享一下我在摸索webgl中的一些姿勢和一些坑,幫助一些初學(xué)者學(xué)習(xí)得更舒服一點籽腕。
第一嗡呼,webGL!==web 3D
我不知道多少人最開始學(xué)習(xí)webGL是把它當(dāng)web 3d方面去學(xué)習(xí)的,至少最開始我以為webGL就是用來繪制3D模型的皇耗。
Naive南窗!
webGL是比canvas.getContext('2d')
更加底層的圖形繪制接口。而它的工作原理,實際上就是遍歷每一個像素點万伤,然后給各個像素點填充顏色窒悔,然后才構(gòu)成一幅2d或者3d的圖像。至于你想直接先搞3d方面的東西壕翩, 使用three.js
比直接擼webGL舒服多了蛉迹。
而且,如果你愿意放妈,webGL更適合去做圖像處理北救。
第二,shaders
webGL工作的基本單位是shaders芜抒,中文喚作著色器珍策。
而我們親愛的js在這個環(huán)境里能做的就只有跑跑腿傳傳值,并不能像ctx.stroke()
那樣親自上陣宅倒。而著色器攘宙,完蛋了,根本就是一門新語言拐迁,叫glsl
蹭劈。
我們的js,是跑在瀏覽器里的語言线召。而glsl铺韧,它是跑在顯卡里面的,它需要手動使用js去調(diào)用WebGL編譯它的方法缓淹,然后變成二進(jìn)制包哈打,然后讓瀏覽器把它塞入顯卡里,最后才能夠使用讯壶。
所以webGL繪制比js去繪制的好處在于料仗,webGL占用的是顯卡里的資源,并不過多占用內(nèi)存伏蚊,性能比起canvas2D來立轧,那是不知道高到哪里去了。
開始學(xué)習(xí)shaders語言的時候躏吊,建議跟著編程指南的例子去敲肺孵,不過這里有一個坑,是我學(xué)習(xí)的時候遇到的颜阐。我跟著書里的例子去敲,卻發(fā)現(xiàn)書里的例子無論如何也無法通過編譯吓肋,它會報一行這樣的錯:
經(jīng)過谷歌凳怨、百度、stackoverflow等多方詢問,最終的解決辦法也非常簡單肤舞,在你每一個著色器程序頭部加上這樣一行:
precision mediump float;
這句話的意思是紫新,設(shè)定中等精度為float型。顯卡程序里面有三種精度:
- 高精度
highp
- 中等精度
mediump
- 低精度
lowp
那這些精度是干嘛用的呢李剖?當(dāng)然是用來精確計算的芒率。(隔壁連0.1+0.2都算不準(zhǔn)的js醬躲在墻角默默哭泣)。比如說一個3d模型篙顺,它每個點的位置最好使用highp精度去計算偶芍,這樣定位準(zhǔn)確。而這個3d模型的貼圖紋理德玫,其實都是圖片匪蟀,對于圖片像素位置的計算,使用中等精度的mediump就行了宰僧。最后的lowp材彪,適合去計算像素的顏色值。
然后說了這么多琴儿,還只是科普一下而已段化,因為不同設(shè)備對這三種精度的支持不一致(前端人深有體會,萬惡的兼容)造成,對三種精度的默認(rèn)設(shè)置也不一致显熏,比如某些垃圾的設(shè)備就把mediump這個級別設(shè)定為int型整數(shù),這個計算精度一下子就下降了谜疤。
所以在webGL里面要加上這句佃延,統(tǒng)一設(shè)置mediump的默認(rèn)值。這樣夷磕,程序就可以通過編譯了履肃。
第三,shaders坐桩,著色器程序glsl的加載
就目前看到的大部分教程來看尺棋,加載shaders程序的方法無非以下幾種:
- 寫在html里面,在html里面插入一個
<script type="text/plain">
绵跷,然后把glsl寫在里面膘螟。而js這邊就需要寫一個獲取這個script的innerHTML的方法,讀取到glsl的源碼碾局,再去編譯荆残。
不過這樣有個缺點,當(dāng)你的代碼編輯器净当,比如vscode内斯,存在html代碼格式優(yōu)化這種功能的時候蕴潦,會傻逼傻逼地將glsl源碼壓縮成一行。俘闯。潭苞。 - 直接使用字符串拼接,就是
var vShaderSource='precision mediump float;'+
'attribute vec4 a_Position;'+
...
就跟我們使用 jquery拼接html一樣去拼接glsl的源碼真朗。不得不說此疹,很煩。
- ajax加載遮婶,這個就可以舒舒服服把glsl的源碼寫在
.glsl
文件里蝗碎,然后通過ajax加載進(jìn)來。如果你的代碼編輯器可以的話蹭睡,甚至有.glsl
文件的語法高亮衍菱,就像vscode安裝了高亮插件之后:
不過呢缕坎,作為新時代的前端人,掌握了webpack工程化開發(fā)習(xí)慣的我們怎么能忍受上面幾種類似jq時代的寫法呢篡悟?
什么谜叹?配置babel然后使用es6的字符串模板寫源碼?
Naive搬葬!
webpack連css都能讀進(jìn)來荷腊,區(qū)區(qū)glsl!急凰?這里我直接是使用了
row-loader
這個加載器去加載.glsl
文件女仰,然后既不用考慮ajax的異步同步問題,還能夠保持.glsl
文件語法高亮抡锈,通過一句var vGlsl=require('./xxx/xxx/xx.glsl')
就能夠?qū)⒃创a引入到j(luò)s中疾忍,十分方便。
懶得配置webpack的同學(xué)床三,這里我給你寫好了一個簡單的webpack模板了:
直接去我的github里面clone一下就好了一罩,里面還有一個我寫的小demo:
https://github.com/Char-Ten/webGl-Webpack-Template
第四 紋理加載的一些小問題
如果你參照《webGL編程指南》的demo去寫添加紋理,如果你是在網(wǎng)上隨便找一張自己的圖片的話撇簿,你可能會發(fā)現(xiàn)紋理渲染不出來擒抛,即便你的代碼和例子一摸一樣推汽。它會報這樣一行錯:
這里的解決辦法非常簡單,最簡單的解決辦法歧沪,是先檢查你使用的貼圖尺寸。如果長和寬的大小都不是2的n次冪(即錯誤信息里面所說的non-power-of-2
)莲组,那么請用PS等圖像處理軟件把它的長和寬分別處理為2的n次冪诊胞,如:1x1 2x2 4x4 8x8 16x16 32x32 64x64 128x128 256x256 512x512.....
一般來說這樣就能夠解決了,然后參考一下stackoverflow一位dalao給的代碼锹杈,你可以這樣寫一個創(chuàng)建紋理的函數(shù):
/**創(chuàng)建紋理貼圖
* @param {WebGLRenderingContext} webgl - 使用webgl的上下文
* @param {Canvas||Image} image - 要作為紋理的圖片對象
* @return {WebglTexture} texture對象
*/
function createTexByImage(webgl, image) {
var texture = webgl.createTexture();
webgl.bindTexture(webgl.TEXTURE_2D, texture);
webgl.texImage2D(webgl.TEXTURE_2D, 0, webgl.RGBA, webgl.RGBA, webgl.UNSIGNED_BYTE, image);
if (isPowerOf2(image.width) && isPowerOf2(image.height)) {
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
return texture
}
webgl.texParameteri(webgl.TEXTURE_2D, webgl.TEXTURE_MIN_FILTER, webgl.NEAREST);
webgl.texParameteri(webgl.TEXTURE_2D, webgl.TEXTURE_MAG_FILTER, webgl.NEAREST);
webgl.texParameteri(webgl.TEXTURE_2D, webgl.TEXTURE_WRAP_S, webgl.CLAMP_TO_EDGE);
webgl.texParameteri(webgl.TEXTURE_2D, webgl.TEXTURE_WRAP_T, webgl.CLAMP_TO_EDGE);
return texture
}
/**檢查數(shù)字是否為2的指數(shù)
* @param {Number} value - 要檢查的值
* @return {Boolean}
*/
function isPowerOf2(value) {
return !(value & (value - 1));
}
當(dāng)圖片的尺寸不滿足2的指數(shù)的時候撵孤,你要寫滿四個texParameteri
方法。
這個方法是用來設(shè)定紋理貼圖參數(shù)的竭望,有四個值可以設(shè)定邪码,分別是
- TEXTURE_MAG_FILTER 設(shè)定圖片放大后像素點的取值方式
- TEXTURE_MIN_FILTER 設(shè)定圖片縮小后像素點的取值方式
- TEXTURE_WRAP_S 設(shè)定圖片橫向平鋪樣式
- TEXTURE_WRAP_T 設(shè)定圖片垂直平鋪樣式
默認(rèn)貼圖在webgl中是平鋪的,只有設(shè)定為不平鋪時(webgl.CLAMP_TO_EDGE
)咬清,才能夠渲染出來闭专。
至于這個原因呢,很簡單旧烧,對于尺寸不是2的指數(shù)的圖片影钉,GPU對其遍歷是十分消耗性能的。所以掘剪,你想做一個平鋪重復(fù)的紋理平委,就必須使用符合規(guī)則的圖片。
第五夺谁,關(guān)于glsl語言的debug
glsl這門語言不像js那樣有console
打印或者瀏覽器斷點調(diào)試那樣方便去調(diào)試一個程序廉赔。換句話說,當(dāng)js以buffer的形式丟一個值給glsl匾鸥,你沒辦法在glsl里面打印這個值是否正確蜡塌。
更何況glsl是靜態(tài)類型語言,有時候忘記寫類型聲明扫腺,或者不同類型的值賦值的時候就會報錯岗照,甚至你后面少寫了個分號都會報錯,都會導(dǎo)致編譯不通過笆环。
對于上面兩種情況攒至,首先是打印這個問題,沒辦法躁劣,glsl不能打印的時候你只能去猜這個變量到底是個什么值迫吐,然后給每個像素的顏色RGB設(shè)定為這個值,然后觀察繪制的結(jié)果账忘,通過顏色去驗證數(shù)值正不正確志膀,只是我目前能夠用到的debug方法熙宇。。溉浙。期待有更好的方法出現(xiàn)烫止。
第二種情況,這個一方面依賴于自己對于glsl語言的學(xué)習(xí)戳稽,同時你也可以通過你的代碼編輯器去檢查是否有語法錯誤馆蠕,或者,如果你在chrome調(diào)試的話惊奇,你可以去下載這些個chrome插件:
它們可以更好的幫助你檢查程序錯誤已經(jīng)其他問題互躬。
這就是我目前學(xué)習(xí)的過程中踩到的一些坑或者解決的一些小問題吧,而且學(xué)了一個月都還只在2d繪制上搞來搞去颂郎,想往3d方向走吼渡,需要的數(shù)學(xué)知識要更多,這些都只是基礎(chǔ)而已乓序。
最后以一張作品圖作為這篇文章的結(jié)尾吧(當(dāng)然glsl的內(nèi)容都是從網(wǎng)絡(luò)上“移植”下來的寺酪。。竭缝。法線貼圖生成算法移植自某位lua的dalao手筆)房维,今后學(xué)習(xí)如果遇到新坑會繼續(xù)寫文章回填的。