WEBGL 2D遊戲引擎研發系列 第二章

WEBGL 2D遊戲引擎研發系列 第二章 <顯示圖片>

~\(≥▽≤)/~HTML5遊戲開發者社區(羣號:326492427)

轉載請註明出處:http://html5gamedev.org/

目錄

           HI,大家好,終於要迎來關鍵的一章了,本章內容將會是你的第一個挑戰,不過不用擔心,當你瞭解了第一章的內容本章的內容只是把理論代碼化而已,而且它將是你遊戲渲染的核心部分,也是你接觸最多的WEBGL獲得API的地方,好吧,我不想嚇唬,其實就是200多行代碼而已,而且不是算法哦,就是一些必須要執行的步驟而已,因爲WEBGL的教程非常少,所以我是根據以前的opengl es經驗來學習這門新技術,如果有遺漏和不明確的地方請海涵,那麼,現在就讓我們開始吧,我們不需你要導入任何3D插件,用自己的能力顯示出一副圖片吧.

        現在如果要顯示一張圖片可不像canvas那樣一句話搞定了,要明確一點的是,我們現在的渲染是面向過程式的,我列出我們需要做的步驟:

初始化

1.初始化WEBGL畫布

2.設置WEBGL狀態

3.載入圖片

4.利用載入的圖片創建紋理

3,把紋理送入到WEBGL的狀態機或者叫管理器,或者加工廠

6.設置紋理的一些選項

7.清空狀態機

8.創建頂點索引緩存

9.創建UV緩存

10.創建頂點座標緩存

11.創建頂點着色器

12.創建像素着色器

渲染

1.清空畫面

2.上傳頂點數據

3.上傳UV數據

4.上傳紋理

5.上傳着色器

6.上傳頂點索引開始繪圖

          以上的步驟是我們本章的步驟,並不是全部,在之後的學習中,我們會逐漸加入新的過程,現在來說,這些就已經足夠了,好吧,打開你的編輯器,創建一個新文件夾,開始新的旅程吧。

創建一個新的html

1
2
3
4
5
6
7
8
9
10
<html>
<head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  
<script type="text/javascript"  LANGUAGE="JavaScript" src="DisplayerObjectGL.js"></script>
</head>
<body onload="webGLStart();">
    <canvas id="myWebGL" style="border: none;" width="1024" height="768"></canvas>
</body>
  
</html>

           一個新的DisplayerObjectGL.js,一個啓動函數,一個新的畫布爲myWebGL,寬度爲1024,高度爲768,

現在我們看看這個DisplayerObjectGL.js,首先定義我們需要使用的數據

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//定義全局的GL句柄
var gl;
  
//定義頂點座標緩存數組
var vertexPositionBuffer;
  
//定義紋理UV信息緩存
var vertexTextureUvdBuffer;
  
//定義頂點索引緩存
var vertexIndexBuffer;
  
//定義紋理
var newTexture;

然後是啓動函數:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//啓動函數
function webGLStart() {
  
    //初始化WEBGL和畫布
    initGL();
  
    //初始化頂點數據緩存
    initBuffers();
  
    //初始化紋理
    initTexture();
  
    //初始化着色器
    initShaders();
  
    //遊戲循環渲染
    setTimeout(drawScene,0);
  
}

我們可以通過初始化函數看出我們大致要做什麼了,和第一章一樣,我們現在就是那兩個小夥伴,先來看看如何初始化WEBGL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//初始化WEBGL和畫布
function initGL() {
    //獲取畫布myWebGL是自定義名稱
    var canvas=document.getElementById("myWebGL");
    try {
        //獲取WEBGL的句柄,這個騷氣的名字ID不是我取的,你必須得用這個,我一開始也以爲是自定義的
        gl = canvas.getContext("experimental-webgl");
  
        //設置WEBGL的畫布,座標爲0,0,寬度和高度最好和原始畫布的一樣
        gl.viewport(0, 0, canvas.width, canvas.height);
  
    } catch (e) {
    }
    if (!gl) {
  
        //如果不支持,你可以回滾到canvas的渲染機制
        alert("Could not initialise WebGL, sorry <IMG class=wp-smiley alt=:-( src="http://html5gamedev.org/wp-content/themes/d-simple/img/smilies/icon_sad.gif"> ");
    }
}

這裏最核心的步驟就是獲取gl的句柄和設置WEBGL畫布的寬度高度,看到那騷氣的ID名字了嗎,那個不是自定義的,小心這裏的坑哦,接下來是初始化各頂點信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
//初始化頂點信息
function initBuffers() {
  
    /**********************************************初始化頂點座標信息*******************************************/
    //先從GL申請一個緩存數組
    vertexPositionBuffer = gl.createBuffer();
  
    //把這個緩存丟入到GL的狀態機裏
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionBuffer);
  
    //注意這裏的座標,在WEBGL中默認座標點是在屏幕的中心點,和我們之前使用canvas不一樣,X軸正號爲右,Y軸正號爲上,
    //我不太習慣這個座標系,不過沒有關係,我們先用默認的座標來處理圖像,後續我們可以利用矩陣
    //改變成自己的左上角座標系,現在我們通過4個頂點座標定義了一個四角形,所以我們的四角形的寬度和高度是2,這裏
    //是重點,不要看成是1拉,因爲-1和1表示的長度爲2,之後我們需要這個2來算出實際的圖像大小,所以,這裏的頂點循序是
    //左下角,右下角,右上角,左上角
    var vertices = [
        -1.0, -1.0,//左下角
        1.0, -1.0,//右下角
        1.0,  1.0,//右上角
        -1.0,  1.0//左上角
    ];
  
    /**********************************************初始化UV信息*******************************************/
  
    //上傳這個頂點數據到WEBGL的狀態機裏,這裏有點難理解,WBEGL是過程式的,因爲我們上面的操作是已經上傳了頂點的緩存數
    // 組到狀態機通過使用參數gl.STATIC_DRAW,告訴告訴狀態機,現在上傳的是這個緩存數組裏的具體參數,參數是浮點數
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
  
    //申請一個UV的緩存數組
    vertexTextureUvdBuffer = gl.createBuffer();
  
    //又上傳到WEBGL的狀態機裏,
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexTextureUvdBuffer);
  
    //這裏就是傳說中的UV,還記得0和1嗎,1代表原圖的最大采樣區域,如果我們要顯示一個完整的原圖,就需要設置各個頂點的UV座標
    //它對應的是頂點座標,通過設置UV信息着色器會幫我們計算插值座標
    var textureCoords = [
       0, 0.0,//左下角
       1.0, 0.0,//右下角
       1.0, 1.0,//右上角
       0.0, 1.0//左上角
    ];
  
    //再一次上傳到數據到狀態機裏,原理同上
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoords), gl.STATIC_DRAW);
  
    /**********************************************初始化頂點索引*******************************************/
  
    //申請一個頂點索引的緩存數組
    vertexIndexBuffer = gl.createBuffer();
  
    //上傳到WEBGL的狀態機裏
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, vertexIndexBuffer);
  
    //設置頂點繪製的循序,WBEGL會根據你的這個循序去渲染你的圖像,通常你可以利用這裏改變你的圖像的排序循序,這裏渲染的是
    //兩個三角形,因爲我們是做2D,兩個三角形是有兩個共享點的
    var vertexIndices = [
        0, 1, 2, 0, 2, 3
    ];
    //這裏的上傳類型改變爲長整形了,Uint16Array,這裏是一個坑,在其他語言裏你上傳錯誤的數據類型不會報錯,但是會顯示很奇怪,
    //以前我就被這個坑了一個下午,因爲索引ID沒有小數
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(vertexIndices), gl.STATIC_DRAW);
  
}

然後是初始化紋理信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//初始化紋理
function initTexture() {
  
    //申請一個紋理
    newTexture = gl.createTexture();
  
    //JS居然可以這樣寫,添加動態屬性,這是我網上學的辦法,嗯,先這麼用着吧
    //創建一個圖片
    newTexture.image = new Image();
  
    //如果圖片讀取完畢就執行初始化過程,在之後的操作裏,你可以把這裏優化到你的結構裏,我現在寫在一起方便你的查看
    newTexture.image.onload = function () {
  
        //開始WEBGL紋理功能,這是一個坑,如果你的程序沒有報錯,但是不顯示圖片看看這裏有沒有開啓
        gl.activeTexture(gl.TEXTURE0);
  
        //和上傳頂點的過程是一樣一樣的,把這個紋理對象上傳到WEBGL的狀態機裏
        gl.bindTexture(gl.TEXTURE_2D,newTexture);
  
        //這個函數之前沒見過,看樣子你不這樣子設置畫面會反轉,那就這樣設置吧
        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
  
        //核心函數,利用newTexture.image生成紋理,我們實際渲染的不是load進來的圖片而是一個紋理,後面的0參數看起來是紋理等級
        //的意思,在3D中會有多級紋理,比如1024*1024 512*512 256*256 ...一直到最小,這個操作是方便在遠處的貼圖以小精度也就是
        //等級顯示,這樣就不需要利用大圖縮放而損失畫面質量了,不過我們的2D遊戲不會用到這個功能,後面的參數看起來是設置圖像
        //的一些顏色信息,默認吧,默認吧
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, newTexture.image);
  
        //縮放的採樣模式,這裏是設置圖像被放大時採樣模式,爲臨近採樣模式,這個參數很常用你最好把它封裝起來,初始化時方便你
        //選擇
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
  
        //這裏是設置縮小時的採樣模式,原理同上,
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
  
        //清空狀態機裏的紋理,這裏只是清除引用而已,不是清楚紋理,紋理我們已經經過狀態機加工過了
        gl.bindTexture(gl.TEXTURE_2D, null)
    }
  
    //設置圖片的讀取地址,最好是2的次方圖片,如果不是會出現UV混亂或者報錯的情況,如果非要上傳任意尺寸的圖片呢?可以在前端加工成最接近的2的次方的圖片再上傳
    newTexture.image.src = "lufei.png";
}

最後初始化頂點着色器和像素着色器,這裏會有一點點複雜,因爲你要接觸到一個新的語言,叫GLSL,它是一個着色器語言,在WEBGL裏它是一串字符串承載的,現在爲了方便,我們把它寫到外部的html裏,以後我們的遊戲特效濾鏡很多時候都實在這裏寫着色器程序,不懂?沒關係,之後的章節會有GLSL的案例介紹還有特效源碼.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<script id="shader-vs" type="x-shader/x-vertex">
    //定義一個頂點屬性,因爲我們是2D遊戲,所以只有XY,所以我們定義了vec2,後面是2,
    //的這樣一個寄存器,當然你也可以定義vec3,vec4,等等,aVertexPosition是這個寄存器的名稱和外部的JS對應的,
    //一會你會看到代碼
    attribute vec2 aVertexPosition;
  
    //原理同上定一個2維的UV信息的寄存器,這裏也是外部JS傳遞過來的數據
    attribute vec2 aTextureUv;
  
    //插值共享寄存器,看到前面的定義的類型了嗎varying這和上面的attribute不同,attribute一般用於本地數據傳遞,
    //varying用於頂點着色器像像素着色器傳值,一般就是傳UV的插值信息
    varying vec2 vTextureCoord;
  
    //執行的代碼片段放這裏
    void main(void) {
        //gl_Position是內置寄存器,它是一個4維的寄存器,但實際上我們的2D遊戲只用到了2維,所以我們可以強制轉換類型,
        //把前面2維的寄存器丟進去,因爲還有剩下的2維,所以你可以填入默認值,1,1
        gl_Position =  vec4(aVertexPosition,1.0,1.0);
        //這是一個神奇的過程,你看不到插值計算你只要把UV信息給這個寄存去,它傳遞到像素着色器時再獲取就是插值後的座標了
        vTextureCoord = aTextureUv;
    }
</script>
  
<script id="shader-fs" type="x-shader/x-fragment">
        //先聲明一下寄存去可以使用浮點數
        precision mediump float;
  
        //就是這裏接受插值計算座標,和頂點着色器定義一模一樣的寄存器
        varying vec2 vTextureCoord;
  
        //這裏是JS傳遞過來的紋理數據,也就是原始數據,也就是採樣數據
        uniform sampler2D uSampler;
  
        //執行的代碼片段放這裏
        void main(void) {
        //我們不是用傳遞過來的紋理數據直接顯示,而是把紋理數據當作採樣參數,和UV插值座標來採樣,最終輸出到gl_FragColor
        //這個內置的寄存器,這樣像素就最終顯示到了我們的畫面上
        gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
        }
</script>

然後回到DisplayerObjectGL.js裏,創建一個解析的函數

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//獲取着色器
function getShader(gl, id) {
  
    //這裏是一系列的JS解析過程,實際上你不這麼做直接上傳字符串也可以
    var shaderScript = document.getElementById(id);
    if (!shaderScript) {
        return null;
    }
  
    var str = "";
    var k = shaderScript.firstChild;
    while (k) {
        if (k.nodeType == 3) {
            str += k.textContent;
        }
        k = k.nextSibling;
    }
  
    var shader;
    if (shaderScript.type == "x-shader/x-fragment") {
  
        //根據參數定義不同的着色器類型,這裏定義的是像素着色器類型
        shader = gl.createShader(gl.FRAGMENT_SHADER);
    } else if (shaderScript.type == "x-shader/x-vertex") {
  
        //這裏定義的是一個頂點着色器類型
        shader = gl.createShader(gl.VERTEX_SHADER);
    } else {
        return null;
    }
  
    //綁定着色器字符串到到着色器裏
    gl.shaderSource(shader, str);
  
    //編譯這個着色器,就是生成這段程序
    gl.compileShader(shader);
  
    //如果創建不成功,那就是你寫錯代碼了
    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        alert(gl.getShaderInfoLog(shader));
        return null;
    }
  
    //最後返回出這個着色器
    return shader;
}

然後是初始化着色器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//初始化着色器
function initShaders() {
  
    //獲取頂點着色器
    var vertexShader = getShader(gl, "shader-vs");
  
    //獲取像素着色器
    var fragmentShader = getShader(gl, "shader-fs");
  
    //創建一個着色器程序
    shaderProgram = gl.createProgram();
  
    //把頂點着色器上傳到這個着色器程序裏
    gl.attachShader(shaderProgram, vertexShader);
  
    //把像素着色器上傳到這個着色器程序裏
    gl.attachShader(shaderProgram, fragmentShader);
  
    //鏈接這個着色器
    gl.linkProgram(shaderProgram);
  
    //如果你創建失敗了,那你又寫錯代碼了
    if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
        alert("Could not initialise shaders");
    }
  
    //把這個着色器上傳到GPU
    gl.useProgram(shaderProgram);
  
    //還記得aVertexPosition個寄存器的名稱麼,這是對應到頂點着色器的,getAttribLocation這句話的意思是,從這個着色器程序裏
    //獲得一個叫aVertexPosition的寄存器名稱,然後賦值給shaderProgram.vertexPositionAttribute
    shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
  
    //綁定這個寄存器屬性
    gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
  
    //原理同上,名稱級的要一一對飲
    shaderProgram.textureCoordAttribute = gl.getAttribLocation(shaderProgram, "aTextureUv");
    gl.enableVertexAttribArray(shaderProgram.textureCoordAttribute);
  
}

最後,真的是最後了,創建循環刷新函數

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//渲染函數,這裏是循環調用的
function drawScene() {
  
    //清理畫面
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  
    //上傳頂點數據到WEBGL的狀態機
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionBuffer);
  
    //設置頂點着色器接受緩存的數組並且上傳到着色器,我們只用了二維,所以是2,類型爲浮點數,flase是不需要轉換爲單位向量,這個
    //矩陣會用到,或者是法線貼圖的數據,現在用不到,後面是開始位置和間隔位置,實際上你可以在一個緩存數組裏放所有的信息
    //這樣理論上會節省空間和提升效率,但我在其他平臺上測試,分開的優勢比較大,WEBGL的還沒有測試過,現在默認是0,0
    gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute,2, gl.FLOAT, false, 0, 0);
  
    //同上理,上傳UV信息到WEBGL狀態機
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexTextureUvdBuffer);
    //同上理
    gl.vertexAttribPointer(shaderProgram.textureCoordAttribute,2, gl.FLOAT, false, 0, 0);
  
    //上傳紋理信息到WBEGL狀態機
    gl.bindTexture(gl.TEXTURE_2D, newTexture);
  
    //這裏是一個坑,因爲是面向過程的,循序不能錯,把紋理上傳到WEBGL狀態機後,要緊接着上傳到着色器,uSampler是和像素着色器對應
    //好的寄存器名稱,後面的參數,沒見過,默認吧,默認吧,
    gl.uniform1i(gl.getUniformLocation(shaderProgram,"uSampler"), 0);
  
    //上傳頂點索引到WBEGL狀態機
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, vertexIndexBuffer);
    //通過剛上傳的頂點索引繪製三角形,一共有6個頂點,類型爲整形,間隔爲0
    gl.drawElements(gl.TRIANGLES,6, gl.UNSIGNED_SHORT, 0);
  
    //循環調用
    setTimeout(drawScene,0);
}

現在,所有的代碼都寫完了,就剩下圖片了,如果你沒有的話,可以用我提供的,最好是2的次方圖

lufei

 

爲了方便你的觀看,下面是完整的代碼:

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<html>
<head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  
<script id="shader-vs" type="x-shader/x-vertex">
    //定義一個頂點屬性,因爲我們是2D遊戲,所以只有XY,所以我們定義了vec2,後面是2,
    //的這樣一個寄存器,當然你也可以定義vec3,vec4,等等,aVertexPosition是這個寄存器的名稱和外部的JS對應的,
    //一會你會看到代碼
    attribute vec2 aVertexPosition;
  
    //原理同上定一個2維的UV信息的寄存器,這裏也是外部JS傳遞過來的數據
    attribute vec2 aTextureUv;
  
    //插值共享寄存器寄存器,看到前面的定義的類型了嗎varying這和上面的attribute不同,attribute一般用於本地數據傳遞,
    //varying用於頂點着色器像像素着色器傳值,一般就是傳UV的插值信息
    varying vec2 vTextureCoord;
  
    //執行的代碼片段放這裏
    void main(void) {
        //gl_Position是內置寄存器,它是一個4維的寄存器,但實際上我們的2D遊戲只用到了2維,所以我們可以強制轉換類型,
        //把前面2維的寄存器丟進去,因爲還有剩下的2維,所以你可以填入默認值,1,1
        gl_Position =  vec4(aVertexPosition,1.0,1.0);
        //這是一個神奇的過程,你看不到插值計算你只要把UV信息給這個寄存去,它傳遞到像素着色器時再獲取就是插值後的座標了
        vTextureCoord = aTextureUv;
    }
</script>
  
<script id="shader-fs" type="x-shader/x-fragment">
        //先聲明一下寄存去可以使用浮點數
        precision mediump float;
  
        //就是這裏接受插值計算座標,和頂點着色器定義一模一樣的寄存器
        varying vec2 vTextureCoord;
  
        //這裏是JS傳遞過來的紋理數據,也就是原始數據,也就是採樣數據
        uniform sampler2D uSampler;
  
        //執行的代碼片段放這裏
        void main(void) {
        //我們不是用傳遞過來的紋理數據直接顯示,而是把紋理數據當作採樣參數,和UV插值座標來採樣,最終輸出到gl_FragColor
        //這個內置的寄存器,這樣像素就最終顯示到了我們的畫面上
        gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
        }
</script>
  
<script type="text/javascript"  LANGUAGE="JavaScript" src="DisplayerObjectGL.js"></script>
</head>
<body onload="webGLStart();">
    <canvas id="myWebGL" style="border: none;" width="1024" height="768"></canvas>
</body>
  
</html>

DisplayerObjectGL.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
//HTML5遊戲開發者社區 AND 9TECH
//作者 白澤
  
//定義全局的GL句柄
var gl;
  
//定義頂點座標緩存數組
var vertexPositionBuffer;
  
//定義紋理UV信息緩存
var vertexTextureUvdBuffer;
  
//定義頂點索引緩存
var vertexIndexBuffer;
  
//定義紋理
var newTexture;
  
//初始化紋理
function initTexture() {
  
    //申請一個紋理
    newTexture = gl.createTexture();
  
    //JS居然可以這樣寫,添加動態屬性,這是我網上學的辦法,嗯,先這麼用着吧
    //創建一個圖片
    newTexture.image = new Image();
  
    //如果圖片讀取完畢就執行初始化過程,在之後的操作裏,你可以把這裏優化到你的結構裏,我現在寫在一起方便你的查看
    newTexture.image.onload = function () {
  
        //開始WEBGL紋理功能,這是一個坑,如果你的程序沒有報錯,但是不顯示圖片看看這裏有沒有開啓
        gl.activeTexture(gl.TEXTURE0);
  
        //和上傳頂點的過程是一樣一樣的,把這個紋理對象上傳到WEBGL的狀態機裏
        gl.bindTexture(gl.TEXTURE_2D,newTexture);
  
        //這個函數之前沒見過,看樣子你不這樣子設置畫面會反轉,那就這樣設置吧
        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
  
        //核心函數,利用newTexture.image生成紋理,我們實際渲染的不是load進來的圖片而是一個紋理,後面的0參數看起來是紋理等級
        //的意思,在3D中會有多級紋理,比如1024*1024 512*512 256*256 ...一直到最小,這個操作是方便在遠處的貼圖以小精度也就是
        //等級顯示,這樣就不需要利用大圖縮放而損失畫面質量了,不過我們的2D遊戲不會用到這個功能,後面的參數看起來是設置圖像
        //的一些顏色信息,默認吧,默認吧
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, newTexture.image);
  
        //縮放的採樣模式,這裏是設置圖像被放大時採樣模式,爲臨近採樣模式,這個參數很常用你最好把它封裝起來,初始化時方便你
        //選擇
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
  
        //這裏是設置縮小時的採樣模式,原理同上,
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
  
        //清空狀態機裏的紋理,這裏只是清除引用而已,不是清除紋理,紋理我們已經經過狀態機加工過了
        gl.bindTexture(gl.TEXTURE_2D, null)
    }
  
    //讀取的圖片最好爲2的次方,如果不是則會出現紋理UV錯亂和報錯的情況,如果非要上傳任意尺寸的圖像呢,可以在前端加工成最
    //接近的2的次方圖片
    newTexture.image.src = "lufei.png";
}
  
//初始化WEBGL和畫布
function initGL() {
    //獲取畫布myWebGL是自定義名稱
    var canvas=document.getElementById("myWebGL");
    try {
        //獲取WEBGL的句柄,這個騷氣的名字ID不是我取的,你必須得用這個,我一開始也以爲是自定義的
        gl = canvas.getContext("experimental-webgl");
  
        //設置WEBGL的畫布,座標爲0,0,寬度和高度最好和原始畫布的一樣
        gl.viewport(0, 0, canvas.width, canvas.height);
  
    } catch (e) {
    }
    if (!gl) {
  
        //如果不支持,你可以回滾到canvas的渲染機制
        alert("Could not initialise WebGL, sorry <IMG class=wp-smiley alt=:-( src="http://html5gamedev.org/wp-content/themes/d-simple/img/smilies/icon_sad.gif"> ");
    }
}
  
//獲取着色器
function getShader(gl, id) {
  
    //這裏是一系列的JS解析過程,實際上你不這麼做直接上傳字符串也可以
    var shaderScript = document.getElementById(id);
    if (!shaderScript) {
        return null;
    }
  
    var str = "";
    var k = shaderScript.firstChild;
    while (k) {
        if (k.nodeType == 3) {
            str += k.textContent;
        }
        k = k.nextSibling;
    }
  
    var shader;
    if (shaderScript.type == "x-shader/x-fragment") {
  
        //根據參數定義不同的着色器類型,這裏定義的是像素着色器類型
        shader = gl.createShader(gl.FRAGMENT_SHADER);
    } else if (shaderScript.type == "x-shader/x-vertex") {
  
        //這裏定義的是一個頂點着色器類型
        shader = gl.createShader(gl.VERTEX_SHADER);
    } else {
        return null;
    }
  
    //綁定着色器字符串到到着色器裏
    gl.shaderSource(shader, str);
  
    //編譯這個着色器,就是生成這段程序
    gl.compileShader(shader);
  
    //如果創建不成功,那就是你寫錯代碼了
    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        alert(gl.getShaderInfoLog(shader));
        return null;
    }
  
    //最後返回出這個着色器
    return shader;
}
  
//初始化着色器
function initShaders() {
  
    //獲取頂點着色器
    var vertexShader = getShader(gl, "shader-vs");
  
    //獲取像素着色器
    var fragmentShader = getShader(gl, "shader-fs");
  
    //創建一個着色器程序
    shaderProgram = gl.createProgram();
  
    //把頂點着色器上傳到這個着色器程序裏
    gl.attachShader(shaderProgram, vertexShader);
  
    //把像素着色器上傳到這個着色器程序裏
    gl.attachShader(shaderProgram, fragmentShader);
  
    //鏈接這個着色器
    gl.linkProgram(shaderProgram);
  
    //如果你創建失敗了,那你又寫錯代碼了
    if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
        alert("Could not initialise shaders");
    }
  
    //把這個着色器上傳到GPU
    gl.useProgram(shaderProgram);
  
    //還記得aVertexPosition個寄存器的名稱麼,這是對應到頂點着色器的,getAttribLocation這句話的意思是,從這個着色器程序裏
    //獲得一個叫aVertexPosition的寄存器名稱,然後賦值給shaderProgram.vertexPositionAttribute
    shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
  
    //綁定這個寄存器屬性
    gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);
  
    //原理同上,名稱級的要一一對飲
    shaderProgram.textureCoordAttribute = gl.getAttribLocation(shaderProgram, "aTextureUv");
    gl.enableVertexAttribArray(shaderProgram.textureCoordAttribute);
  
}
  
//初始化頂點信息
function initBuffers() {
  
    /**********************************************初始化頂點座標信息*******************************************/
    //先從GL申請一個緩存數組
    vertexPositionBuffer = gl.createBuffer();
  
    //把這個緩存丟入到GL的狀態機裏
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionBuffer);
  
    //注意這裏的座標,在WEBGL中默認座標點是在屏幕的中心點,和我們之前使用canvas不一樣,X軸正號爲右,Y軸正號爲上,
    //我不太習慣這個座標系,不過沒有關係,我們先用默認的座標來處理圖像,後續我們可以利用矩陣
    //改變成自己的左上角座標系,現在我們通過4個頂點座標定義了一個四角形,所以我們的四角形的寬度和高度是2,這裏
    //是重點,不要看成是1拉,因爲-1和1表示的長度爲2,之後我們需要這個2來算出實際的圖像大小,所以,這裏的頂點循序是
    //左下角,右下角,右上角,左上角
    var vertices = [
        -1.0, -1.0,//左下角
        1.0, -1.0,//右下角
        1.0,  1.0,//右上角
        -1.0,  1.0//左上角
    ];
  
    /**********************************************初始化UV信息*******************************************/
  
    //上傳這個頂點數據到WEBGL的狀態機裏,這裏有點難理解,WBEGL是過程式的,因爲我們上面的操作是已經上傳了頂點的緩存數
    // 組到狀態機通過使用參數gl.STATIC_DRAW,告訴告訴狀態機,現在上傳的是這個緩存數組裏的具體參數,參數是浮點數
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
  
    //申請一個UV的緩存數組
    vertexTextureUvdBuffer = gl.createBuffer();
  
    //又上傳到WEBGL的狀態機裏,
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexTextureUvdBuffer);
  
    //這裏就是傳說中的UV,還記得0和1嗎,1代表原圖的最大采樣區域,如果我們要顯示一個完整的原圖,就需要設置各個頂點的UV座標
    //它對應的是頂點座標,通過設置UV信息着色器會幫我們計算插值座標
    var textureCoords = [
       0, 0.0,//左下角
       1.0, 0.0,//右下角
       1.0, 1.0,//右上角
       0.0, 1.0//左上角
    ];
  
    //再一次上傳到數據到狀態機裏,原理同上
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoords), gl.STATIC_DRAW);
  
    /**********************************************初始化頂點索引*******************************************/
  
    //申請一個頂點索引的緩存數組
    vertexIndexBuffer = gl.createBuffer();
  
    //上傳到WEBGL的狀態機裏
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, vertexIndexBuffer);
  
    //設置頂點繪製的循序,WBEGL會根據你的這個循序去渲染你的圖像,通常你可以利用這裏改變你的圖像的排序循序,這裏渲染的是
    //兩個三角形,因爲我們是做2D,兩個三角形是有兩個共享點的
    var vertexIndices = [
        0, 1, 2, 0, 2, 3
    ];
    //這裏的上傳類型改變爲長整形了,Uint16Array,這裏是一個坑,在其他語言裏你上傳錯誤的數據類型不會報錯,但是會顯示很奇怪,
    //以前我就被這個坑了一個下午,因爲索引ID沒有小數
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(vertexIndices), gl.STATIC_DRAW);
  
}
  
//渲染函數,這裏是循環調用的
function drawScene() {
  
    //清理畫面
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  
    //上傳頂點數據到WEBGL的狀態機
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionBuffer);
  
    //設置頂點着色器接受緩存的數組並且上傳到着色器,我們只用了二維,所以是2,類型爲浮點數,flase是不需要轉換爲單位向量,這個
    //矩陣會用到,或者是法線貼圖的數據,現在用不到,後面是開始位置和間隔位置,實際上你可以在一個緩存數組裏放所有的信息
    //這樣理論上會節省空間和提升效率,但我在其他平臺上測試,分開的優勢比較大,WEBGL的還沒有測試過,現在默認是0,0
    gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute,2, gl.FLOAT, false, 0, 0);
  
    //同上理,上傳UV信息到WEBGL狀態機
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexTextureUvdBuffer);
    //同上理
    gl.vertexAttribPointer(shaderProgram.textureCoordAttribute,2, gl.FLOAT, false, 0, 0);
  
    //上傳紋理信息到WBEGL狀態機
    gl.bindTexture(gl.TEXTURE_2D, newTexture);
  
    //這裏是一個坑,因爲是面向過程的,循序不能錯,把紋理上傳到WEBGL狀態機後,要緊接着上傳到着色器,uSampler是和像素着色器對應
    //好的寄存器名稱,後面的參數,沒見過,默認吧,默認吧,
    gl.uniform1i(gl.getUniformLocation(shaderProgram,"uSampler"), 0);
  
    //上傳頂點索引到WBEGL狀態機
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, vertexIndexBuffer);
    //通過剛上傳的頂點索引繪製三角形,一共有6個頂點,類型爲整形,間隔爲0
    gl.drawElements(gl.TRIANGLES,6, gl.UNSIGNED_SHORT, 0);
  
    //循環調用
    setTimeout(drawScene,0);
}
  
//啓動函數
function webGLStart() {
  
    //初始化WEBGL和畫布
    initGL();
  
    //初始化頂點數據緩存
    initBuffers();
  
    //初始化紋理
    initTexture();
  
    //初始化着色器
    initShaders();
  
    //遊戲循環渲染
    setTimeout(drawScene,0);
  
}

如果你的程序沒有出錯的畫,應該就是這樣了

QQ截圖20130928020736

 

怎麼?和上傳的圖片尺寸不一樣?沒錯拉,因爲我們上傳的圖片只是採樣圖,我沒還沒有加如任何頂點的邏輯去處理他們,現在的畫布是以0~1顯示的,如果你已經成功到這裏了,恭喜你了,這是你的第一個WBEGL程序,沒有用任何插件哦,你現在可以自己修改頂點或者UV信息去玩玩,下一章節我們將使用矩陣技術讓這個圖片顯示到實際的大小.

下面是DEMO地址和源碼,演示地址建議用谷歌瀏覽器打開.

在線演示 源碼下載

轉載請註明:HTML5遊戲開發者社區 » WEBGL 2D遊戲引擎研發系列 第二章 <顯示圖片>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章