本章節創建咱們基本上掌握瞭如何在2D或者3D平面中繪製圖形的基礎上(固然不瞭解2D和3D平面繪製圖片也能夠往下閱讀),咱們在2D平面中,能夠用webgl去繪製咱們所須要的圖案,但實際中咱們用到某個圖形時不可能所有一一的去繪製,紋理貼圖解決了這個問題,咱們能夠用已有的圖片來填充webgl中的圖形。html
- 紋理基礎
- 2D圖形的紋理貼圖
- 多紋理單元的紋理貼圖
- 不一樣紋理間實現轉場特效
原文的地址來自個人博客:https://github.com/forthealll...git
這個系列的源碼地址爲:源碼的地址爲: https://github.com/forthealll...github
在瞭解紋理貼圖以前,咱們先來了解一下預備知識,首先什麼是紋理貼圖或者說紋理映射呢?web
將一張圖像貼到一個幾何體的表面ajax
這就是紋理貼圖的含義,這張圖像咱們就成爲紋理,這個工做咱們就稱爲紋理貼圖,其本質就是提取圖像中的顏色,而後對應的賦予給幾何平面的某個位置,從而將圖像在幾何體表面完成渲染。chrome
這裏從紋理(圖像)到幾何體表面之間有一個映射,爲了瞭解這個映射是怎麼發生的,咱們必須瞭解一下紋理座標和裁剪座標。canvas
裁剪座標跨域
webgl中的裁剪面座標以下所示:瀏覽器
從上述裁剪座標系統的示意圖中咱們能夠看出,webgl中整個裁剪平面的中心座標是(0,0),對於二維的裁剪平面而言其水平方向從(-1,0)到(1,0),其豎直方向從(0,-1)到(0,1).緩存
也就是說其裁剪座標任何方向的值在區間[-1,1]內。
注意一點:裁剪平面是決定如何映射到畫布上,由於畫布是二維的,所以裁剪平面也是二維的。webgl中z軸的座標並不限制在(-1,1),能夠爲任何值。
紋理座標
紋理座標跟裁剪座標不同,其值不是從[-1,1]而是從[0,1]:
在貼圖過程當中,咱們須要使裁剪座標中的頂點座標,與紋理座標系統點一一對應。也就是如何截取紋理座標中的紋理,貼到幾何圖形中。
須要注意的是,圖片自己的座標與紋理的座標是左右相等,上下相反的,所以,存在一個上下方向的座標變換。在webgl中的紋理 中,經過:
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL,1)
來反轉Y軸上的方向。
禁用Chrome的安全性檢測
本文的例子中,直接本地經過file文件打開html,若是在這個html文件中有ajax跨域請求,那麼由於瀏覽器的安全性檢測,會提示以下信息的錯誤:
Cross origin requests are only supported for HTTP....
固然若是啓動一個本地server就不會有影響。這裏有種懶人解決方案,就是經過禁用安全性檢測的方法,以mac爲例,從命令行窗口中啓動chrome,啓動命令爲
open /Applications/Google\ Chrome.app --args --allow-file-access-from-files
此外,也能夠安裝http-server,快速在本地啓一個server.
下面咱們以2D幾何圖形的紋理貼圖來介紹一下,在webgl中如何實現紋理貼圖。
function createTexture (gl, filter, data, width, height) { let textureRef = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, textureRef); gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL,1) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter); 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); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, data); return textureRef; }
上述就是一個建立紋理緩存的例子,經過gl.createTexture()建立一個紋理緩存,並關聯繫統變量gl.TEXTURE_2D. 此外,由於在紋理的渲染中,在Y軸方向與圖片是徹底相反的,所以若是須要正相呈現紋理渲染結果,好比進行Y軸方向的反轉,即gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL,1)。
此外咱們能夠經過gl.texParameteri函數,指定當圖片紋理大於渲染區域,或者圖片紋理小於渲染區域時,如何去正確的渲染。
最後經過:
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, data);
提取data中的元素保存到紋理緩存中,data能夠是image,也能夠是video,甚至是另外一個canvas的渲染結果。也就是說,webgl的紋理貼圖的源,不只僅能夠是圖片,還能夠是視頻的一幀,甚至是另外一個cavans。
let image = new Image() image.src = './cubetexture.png' image.onload = function(){ let textureRef = createTexture(gl,gl.LINEAR,image1); gl.activeTexture(gl.TEXTURE0) gl.bindTexture(gl.TEXTURE_2D,textureRef) gl.uniform1i(u_Sampler, 0); // texture unit 0 gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); }
上述代碼加載image圖片,在onload的時候,經過以前定義的createTexture建立紋理,而後激活使用0號紋理單元,webgl能夠同時支持多個紋理單元,能夠同時將多個紋理渲染到同一個可視區。激活紋理單元后,咱們將0傳遞給着色器中的取樣器變量u_Sampler.
最後咱們在片元着色器中接受紋理單元編號,以及紋理座標,最後貼圖渲染到渲染區:
uniform sampler2D u_Sampler; //取色器變量 varying lowp vec2 v_TexCoord; //紋理座標 void main() { gl_FragColor = texture2D(u_Sampler,v_TexCoord); }
咱們最終獲得了在所指定的區域,渲染了正確的一張圖片,渲染結果以下所示:
前面說到webgl能夠同時支持多個紋理單元,webgl能夠同時處理多幅紋理,紋理單元就是爲了這個目的而設計的。在上述的例子中,咱們只用了一個紋理單元,將一張紋理渲染到了渲染區。接下來咱們嘗試使用兩個紋理單元,將兩張圖片,渲染到同一個區域。
<div align=center>
<img src="https://user-images.githubusercontent.com/17233651/80330488-df79ab00-8877-11ea-8f2c-8faac87835de.png" width=256 height=256 /><br/>
紋理圖片1
</div>
<div align=center>
<img src="https://user-images.githubusercontent.com/17233651/80331074-a6423a80-8879-11ea-9251-07a2b0323f1f.png" width=256 height=256 /><br/>
紋理圖片2
</div>
咱們使用webgl中國的兩個紋理單元,分別爲gl.TEXTURE0和gl.TEXTURE1,修改代碼以下。
首先是加載和渲染的邏輯:
let textures = [] image.onload = function(){ let textureRef = createTexture(gl,gl.LINEAR,image); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D,textureRef) textures.push(textureRef) } image.src = './cubetexture.png' let image1 = new Image(); image1.onload = function(){ let textureRef1 = createTexture(gl,gl.LINEAR,image1); gl.activeTexture(gl.TEXTURE1) gl.bindTexture(gl.TEXTURE_2D,textureRef1) textures.push(textureRef1) } image1.src = './img.png' //激活紋理單元而且繪製 gl.uniform1i(u_Sampler, 0); // texture unit 0 gl.uniform1i(u_Sampler1, 1); // texture unit 1 gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, textures[0]); gl.activeTexture(gl.TEXTURE1); gl.bindTexture(gl.TEXTURE_2D, textures[1]);
最後修改片元着色器:
uniform sampler2D u_Sampler; uniform sampler2D u_Sampler1; void main(){ gl_FragColor = texture2D(u_Sampler,v_TexCoord) + texture2D(u_Sampler1,v_TexCoord); }
最後的渲染結果爲:
<div align=center>
<img src="https://user-images.githubusercontent.com/17233651/80331830-ba873700-887b-11ea-9784-d01b9dc2274d.png" width=256 height=256 /><br/>
最後雙紋理單元合成渲染結果
</div>
完成的代碼 地址爲:https://github.com/forthealll...
經過使用多紋理單元,咱們能夠實現圖片間的轉場效果,首先咱們來看什麼是轉場效果。
轉場效果顧名思義,就是從圖片A切換到圖片B之間的動畫特效,相似與咱們在作PPT的時候,一些淡入淡出等等動效。在webgl中,渲染的紋理本質也是圖片,所以能夠在不一樣紋理間,按時間順序控制不一樣紋理的渲染結果,能夠實現轉場的效果。
<img src="https://camo.githubusercontent.com/c42ecc6197b0f51a106fb50723f9bc6d2e1f925c/687474703a2f2f692e696d6775722e636f6d2f74573331704a452e676966" /><img src="https://camo.githubusercontent.com/7e34cd12d5a9afa94f470395b04b0914c978ce01/687474703a2f2f692e696d6775722e636f6d2f555a5a727775552e676966" /><img src="https://camo.githubusercontent.com/0456d4ed8753fbce027f1174dc8b22da548eeade/687474703a2f2f692e696d6775722e636f6d2f654974426a33582e676966" />
如上所示的動圖就是列出了一些轉場特效。
咱們以一樣前面的兩張圖片爲例,研究一下圖片或者說紋理間的轉場效果。
直接看片元着色器中的代碼:
vec4 transition (vec2 uv) { float time = progress; float stime = sin(time * PI / 2.); float phase = time * PI * bounces; float y = (abs(cos(phase))) * (1.0 - stime); float d = uv.y - y; return mix( mix( getToColor(uv), shadow_colour, step(d, shadow_height) * (1. - mix( ((d / shadow_height) * shadow_colour.a) + (1.0 - shadow_colour.a), 1.0, smoothstep(0.95, 1., progress) // fade-out the shadow at the end )) ), getFromColor(vec2(uv.x, uv.y + (1.0 - y))), step(d, 0.0) ); }
咱們看到這個transition函數就是一個轉換函數,決定了隨着時間,如何切換紋理,從而進行渲染,咱們經過getFromColor和getToColor指定了兩個不一樣紋理單元的紋理:
vec4 getToColor(vec2 uv){ return texture2D(u_Sampler,uv); } vec4 getFromColor(vec2 uv){ return texture2D(u_Sampler1,uv); }
最後片元着色器的顏色就是調用transition函數後的結果,在調用transition的時候咱們傳入了紋理座標。
void main() { gl_FragColor = transition(v_TexCoord); }
最後要想要渲染結果動起來,就必須寫一個定時器,動態改變transition中的參數progress,是其從0到1的變化。
setInterval(()=>{ if(textures.length === 2){ if(i >= 1){ i = 0.01 } gl.uniform1i(u_Sampler, 0); // texture unit 0 gl.uniform1i(u_Sampler1, 1); // texture unit 1 gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, textures[0]); gl.activeTexture(gl.TEXTURE1); gl.bindTexture(gl.TEXTURE_2D, textures[1]); let progress = gl.getUniformLocation(shaderProgram,'progress') gl.uniform1f(progress,i) i += 0.05 gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); } },100)
最後的渲染結果咱們能夠看到的動畫結果以下:
完成的代碼 地址爲:https://github.com/forthealll...
此外,在https://github.com/gl-transit... 上收錄了各色各樣的轉場效果,轉場不只僅能夠應用於圖片,還能夠應用於視頻的不一樣幀間,下篇文章將具體講講如何實如今webgl中渲染video,以及video的幀間動畫。