經過webgl中的紋理貼圖來自定義圖片間的轉場效果

經過webgl中的紋理貼圖來自定義圖片間的轉場效果


    本章節創建咱們基本上掌握瞭如何在2D或者3D平面中繪製圖形的基礎上(固然不瞭解2D和3D平面繪製圖片也能夠往下閱讀),咱們在2D平面中,能夠用webgl去繪製咱們所須要的圖案,但實際中咱們用到某個圖形時不可能所有一一的去繪製,紋理貼圖解決了這個問題,咱們能夠用已有的圖片來填充webgl中的圖形。html

  • 紋理基礎
  • 2D圖形的紋理貼圖
  • 多紋理單元的紋理貼圖
  • 不一樣紋理間實現轉場特效

原文的地址來自個人博客:https://github.com/forthealll...git

這個系列的源碼地址爲:源碼的地址爲: https://github.com/forthealll...github

1、紋理基礎

    在瞭解紋理貼圖以前,咱們先來了解一下預備知識,首先什麼是紋理貼圖或者說紋理映射呢?web

    將一張圖像貼到一個幾何體的表面ajax

    這就是紋理貼圖的含義,這張圖像咱們就成爲紋理,這個工做咱們就稱爲紋理貼圖,其本質就是提取圖像中的顏色,而後對應的賦予給幾何平面的某個位置,從而將圖像在幾何體表面完成渲染。chrome

    這裏從紋理(圖像)到幾何體表面之間有一個映射,爲了瞭解這個映射是怎麼發生的,咱們必須瞭解一下紋理座標和裁剪座標。canvas

裁剪座標跨域

    webgl中的裁剪面座標以下所示:瀏覽器

Lark20191216-181530

    從上述裁剪座標系統的示意圖中咱們能夠看出,webgl中整個裁剪平面的中心座標是(0,0),對於二維的裁剪平面而言其水平方向從(-1,0)到(1,0),其豎直方向從(0,-1)到(0,1).緩存

也就是說其裁剪座標任何方向的值在區間[-1,1]內。

注意一點:裁剪平面是決定如何映射到畫布上,由於畫布是二維的,所以裁剪平面也是二維的。webgl中z軸的座標並不限制在(-1,1),能夠爲任何值

紋理座標

    紋理座標跟裁剪座標不同,其值不是從[-1,1]而是從[0,1]:

Lark20191230-195608

在貼圖過程當中,咱們須要使裁剪座標中的頂點座標,與紋理座標系統點一一對應。也就是如何截取紋理座標中的紋理,貼到幾何圖形中。

須要注意的是,圖片自己的座標與紋理的座標是左右相等,上下相反的,所以,存在一個上下方向的座標變換。在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.

2、2D圖形的紋理貼圖

    下面咱們以2D幾何圖形的紋理貼圖來介紹一下,在webgl中如何實現紋理貼圖。

(1).首先建立一個紋理緩衝

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

(2).加載圖片使用紋理緩衝

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); 
    }

咱們最終獲得了在所指定的區域,渲染了正確的一張圖片,渲染結果以下所示:

Lark20200426-211044

3、多紋理單元的紋理貼圖

    前面說到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...

4、不一樣紋理間實現轉場效果

    經過使用多紋理單元,咱們能夠實現圖片間的轉場效果,首先咱們來看什麼是轉場效果。

    轉場效果顧名思義,就是從圖片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)

最後的渲染結果咱們能夠看到的動畫結果以下:

Untitled1

完成的代碼 地址爲:https://github.com/forthealll...

此外,在https://github.com/gl-transit... 上收錄了各色各樣的轉場效果,轉場不只僅能夠應用於圖片,還能夠應用於視頻的不一樣幀間,下篇文章將具體講講如何實如今webgl中渲染video,以及video的幀間動畫。

相關文章
相關標籤/搜索