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游戏引擎研发系列 第二章 <显示图片>

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