WebGL-学习笔记(一)

WebGL学习笔记(一).png

反正不管你信不信,我觉得WebGL是接下来一个时代的流量入口啦,谁不喜欢酷炫的东西,所以本着跟着时代步伐的精神,终于开始进行WebGL的学习啦(饶过我JS的学习还没有结束,JS学习笔记的剩余内容会在后期更新啦!)

1. WebGL历史

WebGL是基于OpenGL ES进行开发的,WebGL1.0版本基于OpenGL ES2.0,而WebGL2.0会基于OpenGL ES3.0。
那么OpenGL ES是撒?OpenGL ES是OpenGL的一个子库,主要是针对嵌入式计算机,智能手机和游戏设备等的子库。
最后OpenGL又是什么呢?OpenGL是计算机三维图形渲染的两大技术之一,另一个技术是大家都很熟悉了Directive3D(微软DirectiveX的一部分,反正经常会报错),Directive主要是针对Window系统的渲染,而OpenGL则被设计在跨平台的操作上了。
总的来说借鉴一下书上的OpenGL世代图,大致也就是这样:

OpenGL 1.0 ——> OpenGL ES 1.1
OpenGL 2.0 ——> OpenGL ES 2.0 ——> WebGL 1.0
OpenGL 3.3 ——> OpenGL ES 3.0 ——> WebGL 2.0
OpenGL 4.3

最后补充一点,在OpenGL2.0版本以后我们才可以对着色器(Shader)进行操作,操作着色器的语言是GLSL语言/GLSL ES语言(和C语言一样)

2. WebGL程序组成

先来看一个最简单的例子(在画布上画一个红色对点):

html部分:
<body>
    <canvas width="400" height="400" id="myCanvas">不支持Canvas</canvas>
</body>
script部分:
<script>
main();
// WebGL主程序部分
function main() {
    // ---- Canvas ----
    // 获取canvas节点
    let canvas = document.querySelector('#myCanvas');
    // 获取canvas上下文
    let gl = canvas.getContext('webgl');
    // ----GLSL程序部分----
    // Vertex Shader(顶点着色器)
    const vertex_source = `
        void main() {
           gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
           gl_PointSize = 10.0;
        }
    `
    // Fragment Shader(片元着色器)
    const fragment_source = `
        void main() {
          gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
        }
    `
    // ----WebGL API调用部分----
    program = initProgram(gl, vertex_source, fragment_source);
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.drawArray(gl.Point, 0, 1.0);
}

// 辅助方法:初始化着色器程序
function initProgram(gl, vsource, fsource) {
    let vShader = initShader(gl, gl.VERTEX_SHADER, vsource);
    let fShader = initShader(gl, gl.FRAGMENT_SHADER, fsource);
    // 创建WebGL程序
    let program = gl.createProgram();
    gl.attachShader(program, vShader);
    gl.attachShader(program, fShader);
    gl.linkProgram(program);
    // 判断是否创建成功
    if(!gl.getProgramParameter(program, gl.LINK_STATUS)) {
        alert('unable to initialize!');
        return;
    }
    gl.useProgram(program);
    return program;
}
// 辅助方法:初始化着色器
function initShader(gl, type, source) {
    let shader = gl.createShader(type);
    gl.shaderSource(shader, source);
    gl.compileShader(shader);
    if(!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        alert('error occured compiling the shaders:' + gl.getShaderInfoLog(shader));
        return null;
    }
    return shader;
}
</script>

根据这个例子,我将整个WebGL程序分成了以下对几个部分

2.1 Javascript主程序

包含两个主要部分Canvas和WebGL API,WebGL利用了HTML5的Canvas来绘制3D图形

2.1.1 Canvas

Canvas是HTML5引入,用于构建2D,3D的图形,可以在<canvas>标签中增加文本,来判断浏览器是否支持<canvas>,因为不支持的浏览器将显示这些文本信息,而支持的将默认显示一个空的画布。
要使用WebGL,首先要利用Canvas获取到上下文,主要按照三步进行操作

  1. 获取页面的Canvas元素
  2. 利用Canvas元素获取上下文(WebGL现在可以直接使用getContext('webgl')来获取,对于2d的我们常用getContext('2d')方法获取Canvas上下文)
  3. 利用WebGL API来操作绘制图形
2.2.2 WebGL API

WebGL API是WebGL编程的最主要部分,因为包含的API太多了,我也是新入门,所以目前暂时提及几个在例子中用到的主要函数:

// 清空缓冲区相关
gl.clearColor: 设置webgl颜色缓冲区清除后画布颜色
gl.clear: 清除webgl的缓冲区,可以有三个参数,分别为颜色缓冲(COLOR_BUFFER_BIT),深度缓冲区(DEPTH_BUFFER_BIT),模板缓冲区(STENCIL_BUFFER_BIT)
gl.drawArrays:利用缓冲区数据绘制图形
// 构建程序相关
gl.createProgram: 创建一个program对象
gl.attachShader: 将程序对象和shader进行绑定
gl.linkProgram: 编译程序?
gl.useProgram:设置使用的webgl program对象
// Shader构建相关
gl.createShader: 根据类型构建Shader
gl.shaderSource: 加载Shader的GLSL资源
gl.compileShader: 编译Shader返回shader对象
// 操作GLSL中变量相关,通常命名为:<方法名><变量个数><变量类型>
gl.getAttribLocation: 获取程序中的attribute变量的位置
gl.vertex[1,2,3,4]f: 设置attribute的值的函数族
gl.getUniformLocation: 获取程序中uniform变量的位置
gl.uniform[1,2,3,4]f: 设置uniform的值的函数族

2.2 GLSL

构成WebGL的第二部分是GLSL程序,WebGL构建需要两个着色器Vertex Shader(顶点着色器)和Fragment Shader(片元着色器)
注:我其实比较喜欢用英文而不喜欢用中文来表述这两个着色器,因为现在英文文档较多,知道英文名比中文名要好。

2.2.1 Vertex Shader

中文译名顶点着色器,顾名思义,主要是用来描述顶点信息(位置,尺寸等)可以看到例子中描述了gl_Position(顶点位置)和gl_PointSize(顶点大小,颜色?),这里的gl_Positiongl_PointSize是GLSL中的内置变量。

2.2.2 Fragment Shader

中文译名片元着色器,用途是用来描述绘制的图形过程和光照等信息,例子中指定了整个片段的颜色信息,gl_FragColor也是GLSL中的内置变量。

2.2.3 GLSL中的变量

按照前面例子的写法,我们绘制的点的位置和大小,颜色,硬编码写到GLSL程序中,而没办法通过页面操作进行改变了,那么如果我们想要GLSL和web页面惊醒交互,按么我们就要使用GLSL变量。
GLSL中变量的声明包含三部分:

<存储限定符><变量类型><变量名>

其中存储限定符有三个attribute(只用于vertex source中),uniform(可用于vertex和fragment source中),以及varying(用于vertex和fragment之间的数据传递);
变量类型有floatvec[1,2,3,4](分别代表有多少个矢量组成的数据结构);
变量名通常根据存储限定符来进行定义attribute就是a_uniform就是u_
于是代码就可以根据以下进行调整,来设置变量:

let vertex_source = `
    attribute vec4 a_Position;
    void main() {
        gl_Position = a_Position;
    }
`
let fragment_source = `
    precision mediump float; // 必须要,指定精度
    uniform vec4 u_FragColor;
    void main() {
        gl_FragColor = u_FragColor;
    }
`

3. WebGL其他

3.1 坐标系转换

为了完成接下来的例子,我们还需要说明关于WebGL中的坐标系转换。

首先,WebGL使用的是笛卡尔坐标系,也就是说右手坐标系。就是x轴的正向是大拇指方向,y轴正向是四指方向,z轴方向为穿过掌背到掌心;
其次,WebGL中的坐标系的值是从[-1.0, 1.0]的范围,原点为canvas图形中心点;
最后,对于我们使用的web页面坐标系,最左上角为原点位置,面对屏幕时,x正方向往右,y轴正方向往下

所以,我们要设置正确的WebGL点的坐标位置时,我们要将web页面的坐标信息转换为WebGL的坐标信息,转换过程如下:

let rect = canvas.getBoundingClientRect();
x = (x - rect.left - rect.width/2) / (rect.width/2);
y = (rect.height/2 -  (y - rect.top)) / (rect.height/2);

3.2 根据鼠标点击绘制点的完整例子

最后附上一个完整的入门例子:

<!DOCTYPE html>
<html>
<head>
    <script src="./shader-util.js"></script>
</head>
<body>
    <canvas id="glCanvas" width="640" height="480"></canvas>
</body>
<script>
    // 清空颜色缓冲区
    main();
    function main() {
        const canvas = document.querySelector('#glCanvas');
        const gl = canvas.getContext('webgl');
        // 着色器程序
        const VSHADER_SOURCE = `
            attribute vec4 a_Position;
            attribute float a_PointSize;
            void main() {
                gl_Position = a_Position;
                gl_PointSize = a_PointSize;
            }
        `;
        const FSHADER_SOURCE = `
            precision mediump float;
            uniform vec4 u_FragColor;
            void main() {
                gl_FragColor = u_FragColor;
            }
        `
        let program = initProgram(gl, VSHADER_SOURCE, FSHADER_SOURCE);
        let a_Position = gl.getAttribLocation(program, 'a_Position');
        if (a_Position < 0) {
            console.log('Cant find the position');
            return;
        }
        gl.vertexAttrib3f(a_Position, 0.9, 0.0, 0.0);
        let a_PointSize = gl.getAttribLocation(program, 'a_PointSize');
        if (a_PointSize < 0) {
            console.log('Cant find the pointsize');
            return;
        }
        gl.vertexAttrib1f(a_PointSize, 10.0);
        let u_FragColor = gl.getUniformLocation(program, 'u_FragColor');
        gl.uniform4f(u_FragColor, 1.0, 0.0, 0.0, 1.0);

        gl.clearColor(0.0, 0.0, 0.0, 1.0);
        gl.clear(gl.COLOR_BUFFER_BIT);
        gl.drawArrays(gl.POINTS, 0, 1);
        // 绑定canvas上的点击事件
        canvas.onmousedown = (event) => {
            click(gl, event, canvas, a_Position, u_FragColor)
        }
    }
    let g_points = [];
    let g_colors = [];
    function click(gl, event, canvas, a_Position, u_FragColor) {
        let x = event.clientX;
        let y = event.clientY;
        let rect = canvas.getBoundingClientRect();

        x = ((x - rect.left) - canvas.width/2 )/ (canvas.width/2);
        y = (canvas.height/2 - (y - rect.top)) / (canvas.height/2);
        g_points.push({x, y});
        if (x>0 && y>0) {
            g_colors.push([1.0, 0.0, 0.0, 1.0]);
        } else if (x<0 && y<0){
            g_colors.push([0.0, 1.0, 0.0, 1.0]);
        } else {
            g_colors.push([1.0, 1.0, 1.0, 1.0]);
        }

        gl.clear(gl.COLOR_BUFFER_BIT);
        g_points.forEach((point, index) => {
            gl.vertexAttrib3f(a_Position, point.x, point.y , 0.0);
            gl.uniform4f(u_FragColor, g_colors[index][0], g_colors[index][1], g_colors[index][2], g_colors[index][3]);
            gl.drawArrays(gl.POINTS, 0, 1);
        })

    }
</script>
</html>

4. 总结

因为还是入门,所以能做的东西很有限,只能绘制简单的二维图形,还完全没有发挥WebGL三维图形构建的技术,之后会持续更新学习进度的。

5. 参考

《WebGL编程指南》
MDN-WebGL-Start

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容