WebGL第一步
什么是WebGL?
WebGL使用了GLSL ES(OpenGL ES)着色器语言,通过配合调用js相关的绘制接口来实现3D效果。
采用页面中的<canvas>元素来定义绘图区域,canvas支持三维图形的绘制,但它不直接提供绘图方法,而是提供一种叫上下文(context)机制来绘制图形。
绘制流程
获取画布canvas和3D绘制上下文
var canvas = document.getElementById('canvas');
var gl = canvas.getContext('webgl');
具体的参数可以参考这里:https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLCanvasElement/getContext
编写顶点着色器和片段着色器
// 着色器是字符串形式的glsl代码
var VSHADER_SOURCE = "...";
var FSHADER_SOURCE = "...";
编译着色器并提交到GPU
1.创建Shader对象
createShader方法,需要传递参数指明是顶点着色器还是片段着色器;
2.将写好的着色器附加到Shader上
shaderSource方法,可以指定着色器代码添加到指定的Shader上;
3.编译着色器
compileShader方法,可以将附加了着色器的shader对象进行编译;
4.创建程序对象
createProgram方法,可以创建一个程序对象,一个可用的WebGLProgram对象由两个编译过后的WebGLShader组成,即顶点着色器和片段着色器;
5.添加着色器
attachShader方法,可以把一个编译好的着色器对象添加到一个程序对象上;
6.连接程序对象
linkProgram方法,连接一个程序对象,连接之后的程序对象可以使用;
7.使用程序对象
useProgram方法,设定一个程序对象为当前使用的程序对象;
8.调用绘制方法继续绘制
根据需要调用对应的方法进行绘制;
示例代码点击这里;
绘制三角形
绘制图像需要向GPU传递多个顶点,WebGL提供了一种很方便的机制,缓冲区对象(buffer object),它是WebGL系统中的一块内存区域,它可以一次性向着色器传入多个顶点的数据,然后将这些数据保存在其中,供顶点着色器使用。
绘制图像除了要完成上面说的步骤之外,还有下面的步骤需要完成:
1.使用缓冲区对象向GPU传入多个顶点数据
创建缓冲区对象
createBuffer方法,创建缓冲区对象;
将缓冲区对象绑定到目标
bindBuffer方法,绑定缓冲区对象,同时表明缓冲区的类型,如下:
- gl.ARRAY_BUFFER: 包含顶点属性的Buffer,如顶点坐标,纹理坐标数据或顶点颜色数据。
- gl.ELEMENT_ARRAY_BUFFER: 用于元素索引的Buffer。
向缓冲区写入数据
bufferData方法,需要表明缓冲数据的类型,同上,还要表明数据是否会变动,如下:
- gl.STATIC_DRAW: 缓冲区的内容可能经常使用,而不会经常更改。内容被写入缓冲区,但不被读取。
- gl.DYNAMIC_DRAW: 缓冲区的内容可能经常被使用,并且经常更改。内容被写入缓冲区,但不被读取。
- gl.STREAM_DRAW: 缓冲区的内容可能不会经常使用。内容被写入缓冲区,但不被读取。
获取属性下标位置
getAttribLocation方法,获取指定属性的下标;
将缓冲区对象分配给指定的名称变量
vertexAttribPointer方法,会告知到GPU的属性怎么获取传入的数据,参数分别如下:
- 第一个参数:指定待分配attribute变量的存储位置
- 第二个参数:指定缓存区中每个顶点的分量个数(1~4)
- 第三个参数:类型有,gl.UNSIGNED_BYTE无符号字节,gl.SHORT短整数,gl.UNSIGNED_SHORT无符号短整数,gl.INT整型,gl.UNSIGNED_INT无符号整型,gl.FLOAT浮点型。
- 第四个参数:表示是否将非浮点型的数据归到[0,1][-1,1]区间
- 第五个参数:相邻两个顶点的字节数。默认为0
- 第六个参数:表示缓存区对象的偏移量(以字节为单位),就是attribute变量从缓冲区中的何处开始存储。
开启attribute变量
enableVertexAttribArray方法,就是开启指定的变量,使缓存区对attribute变量分配生效,以便顶点着色器能够访问缓冲区内的数据。
2.着色器获取外部传入的数据
使用attribute定义的属性都是可以接收外部传入的数据的属性,接收到的数据可以直接在着色器中使用;
attribute vec4 a_Position;
void main() {
gl_Position = a_Position;
}
3.绘制图像
drawArrays方法,用来绘制图像,参数分别如下:
第一个参数:绘制类型
- 点 gl.POINTS 一系列点,绘制v0,v1……。
- 线段 gl.LINES 一系列单独的线段,绘制(v0,v1),(v2,v3)……如果是奇数,最后一个省略。
- 线条 gl.LINE_STRIP 一系列连接的线段,绘制(v0,v1),(v1,v2),(v2,v3)……除了第一个和最后一个,其他的点点即是起点又是终点。
- 回路 gl.LINE_LOOP 一系列连接的线段,绘制(v0,v1),(v1,v2),(v2,v3)……(vn,v0),最后一个点会连接起点。
三角形 gl.TRIANGLES 一系列单独的三角形,绘制(v0,v1,v2),(v3,v4,v5)……如果不是3的倍数,剩下的将会被忽略。
三角带 gl.TRIANGLES_STRIP 一系列连接的三角形,绘制(v0,v1,v2),(v2,v1,v3),(v2,v3,v4)……以此类推,第二个是(v2,v1,v3)而不是(v1,v2,v3)是为了保持绘制按照逆时针绘制
第二个参数:开始绘制的顶点索引
第三个参数:绘制的顶点数量
示例代码点击这里;
矩阵转换
我们希望修改传输到GPU中的顶点的位置时(比如放大缩小旋转等),需要从外部传入一个矩阵来进行运算,外部传入的矩阵是一个包含16个数字的带数据类型的数组对象;
关于矩阵的相关教程可以看这里;
1.先在代码中定义好需要参与运算的矩阵:
// 控制缩放的矩阵
var Sx = 1.5; Sy = 1.5; Sz = 1.0;
var xformMatrix = new Float32Array([
Sx, 0.0, 0.0, 0.0,
0.0, Sy, 0.0, 0.0,
0.0, 0.0, Sz, 0.0,
0.0, 0.0, 0.0, 1.0
]);
2.将矩阵数据传入GPU:
uniformMatrix4fv方法,传输到GPU,参数如下:
- 第一个参数:代表uniform变量的存储位置;
- 第二个参数:在WebGL中指定为false;
- 第三个参数:待传输的类型化数组;
代码如下:
// 获取矩阵变量的下标
var u_xformMatarix = gl.getUniformLocation(shaderProgram, 'u_xformMatarix');
// 提交矩阵数据到GPU
gl.uniformMatrix4fv(u_xformMatarix, false, xformMatrix);
3.在着色器中获取矩阵并进行运算
attribute vec4 a_Position;
uniform mat4 u_xformMatarix; void main() {
// 设置坐标
gl_Position = u_xformMatarix * a_Position;
}
示例代码点击这里;
设定颜色
我们可以通过顶点数据向着色器添加颜色信息。
1.为顶点数据添加颜色的信息
//顶点坐标和颜色
var vertices = new Float32Array([
0.0, 0.5, 1.0, 0.0, 0.0,
-0.5, -0.5, 0.0, 1.0, 0.0,
0.5, -0.5, 0.0, 0.0, 1.0,
]);
2.编写着色器
颜色是在片段着色器中处理的,但是顶点数据只在顶点着色器中可以使用,所以我们需要将顶点着色器中的部分数据传递到片段着色器中,使用varying定义的属性可以将顶点着色器中的数据传递到片段着色器中;
// 顶点着色器
attribute vec4 a_Position;
attribute vec4 a_Color;
// 声明需要传递到片段着色器的属性
varying vec4 v_Color; void main() {
// 设置坐标
gl_Position = a_Position;
// 传递颜色数据
v_Color = a_Color;
} // 片段着色器
// 必须在片段着色器中为float指定一个默认精度
precision mediump float;
// 声明从顶点着色器接收数据的属性
varying vec4 v_Color; void main() {
gl_FragColor = v_Color;
}
3.设置传递的数据规则
//获取单个字节
var FSIZE = vertices.BYTES_PER_ELEMENT; //获取坐标点
var a_Position = gl.getAttribLocation(shaderProgram, 'a_Position'); //将缓冲区对象分配给a_Position变量
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 5, 0); //连接a_Position变量与分配给它的缓冲区对象
gl.enableVertexAttribArray(a_Position); //获取Color坐标点
var a_Color = gl.getAttribLocation(shaderProgram, 'a_Color'); //将缓冲区对象分配给a_Position变量
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 5, FSIZE * 2); //连接a_Position变量与分配给它的缓冲区对象
gl.enableVertexAttribArray(a_Color);
示例代码点击这里;
绘制图片
这里我们看看怎么使用webgl来绘制图片;
1.加载图片
通过创建Image对象,可以动态加载指定的图片,保存这个Image对象,后期直接使用这个对象提交到GPU即可;
2.提交到GPU
//对纹理图像进行Y轴反转,因为WebGL纹理坐标系统的t轴(分为t轴和s轴)的方向和图片的坐标系统Y轴方向相反。因此将Y轴进行反转。
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1); //开启0号纹理单元,WebGL通过纹理单元的机制来同时使用多个纹理,gl.TEXTURE0~gl.TEXTURE7是管理纹理图像的8个纹理单元
gl.activeTexture(gl.TEXTURE0); //向target绑定纹理对象
gl.bindTexture(gl.TEXTURE_2D, texture); //配置纹理参数
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); //配置纹理图像
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image); //获取u_Sampler的存储位置
var u_Sampler = gl.getUniformLocation(shaderProgram, 'u_Sampler'); //将0号纹理图像传递给着色器
gl.uniform1i(u_Sampler, 0); // 清空 <canvas>
gl.clear(gl.COLOR_BUFFER_BIT); // 绘制矩形
gl.drawArrays(gl.TRIANGLE_STRIP, 0, n);
3.UV坐标
Webgl中,是使用UV坐标来标记图像的采样点,UV的详情点击这里;
4.着色器修改
// 顶点着色器
attribute vec4 a_Position;
attribute vec2 a_TextCoord;
varying vec2 v_TexCoord; void main() {
// 设置坐标
gl_Position = a_Position;
// 传递uv坐标
v_TexCoord = a_TextCoord;
} // 片段着色器
precision mediump float;
// 声明采样器
uniform sampler2D u_Sampler;
varying vec2 v_TexCoord; void main() {
// 设置颜色,通过采样获得指定坐标的颜色
gl_FragColor = texture2D(u_Sampler, v_TexCoord);
}
示例代码点击这里;
drawArrays和drawElements
我们之前是提交顶点数据,然后通过drawArrays来绘制三角形,我们知道一个矩形是由两个三角形组合而成的,所以可以用下面的两种方式来进行绘制:
// 3个顶点为一组进行绘制,绘制一个矩形需要6个点
gl.drawArrays(gl.TRIANGLES, 0, 6); // 传入4个顶点,按照(v0,v1,v2),(v2,v1,v3),(v2,v3,v4)...的顺序进行绘制
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
这里,第一种方法,会导致大量的重复顶点数据存在,第二种方法,虽然可以节省顶点数据,但是是规定好的顺序,不够自由;
为了解决这个问题,WebGL还引入了另外一种绘制方法drawElements,这种方法需要传递两种数据,一种是顶点数据,一种是索引数据;
顾名思义,顶点数据定义一堆不同的顶点,而索引数据定义这些顶点的绘制顺序;
1.定义索引数组
//定义索引数组
var indexDatas =
[
0, 1, 2,
2, 1, 3,
];
2.提交到GPU
//创建缓冲区对象
indexBuffer = gl.createBuffer();
//将缓冲区对象绑定到目标,并设定该缓冲区是索引缓冲
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
//向缓冲区写入数据
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indexDatas), gl.STATIC_DRAW);
3.着色器代码
不需要做任何的修改;
4.提交绘制
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
- 第一个参数:绘制模式,可选:gl.LINE_LOOP、gl.LINES、gl.TRIANGLES等
- 第二个参数:绘制顶点个数
- 第三个参数:索引数据类型,gl.UNSIGNED_BYTE对应Uint8Array,gl.UNSIGNED_SHORT对应Uint16Array
- 第四个参数:从第几个点开始绘制
示例代码点击这里;
WebGL第一步的更多相关文章
- 开发thinkphp的第一步就是给Application目录(不包括其下的文件)777权限, 关闭selinux
开发thinkphp的时候, 总是会出现各种个样 的奇怪的毛病, 比如: 说什么Application目录不可写, 比如: 说什么 _STORAGE_WRITE_ERROR, 不能生成 Runtime ...
- ElasticSearch第一步-环境配置
ElasticSearch第一步-环境配置 ElasticSearch第二步-CRUD之Sense ElasticSearch第三步-中文分词 ElasticSearch第四步-查询详解 Elasti ...
- Scala的第一步
第一步:学习使用Scala解释器 开始Scala最简单的方法是使用Scala解释器,它是一个编写Scala表达式和程序的交互式“shell”.在使用Scala之前需要安装Scala,可以参考 Firs ...
- UE4蓝图编程的第一步
认识UE4蓝图中颜色与变量类型: UE4中各个颜色对应着不同的变量,连接点和连线的颜色都在表示此处是什么类型的变量.对于初学者来说一开始看到那么多连接点, 可能会很茫然,搞不清还怎么连,如果知道了颜色 ...
- STM32F407第一步之点亮LED
STM32F407第一步之点亮LED. 要点亮LED,首先了解一下F4的GPIO模块.首先看一下STM32F4数据手册,GPIO模块的内部结构图 看上去有点复杂,不要怕,慢慢理解就可以了.对外引脚那里 ...
- 重制AdvanceWars第一步 -- 搞定地图
首先来聊下高级战争吧Advance Wars,由任天堂旗下的Intelligent Systems开发的战棋游戏.初作诞生于GBA上,后来继续跟进了高战2黑洞崛,而后在下一代掌机DS上也出了三代续作高 ...
- 高德携手阿里云发布“LBS云”,账户打通只是第一步
位置.游戏.视频,是公认的基于云计算的三大移动端应用方向.而今,LBS云有了更多进展,在高价值应用与云平台之间实现了资源打通和融合,高德迈出了实质性的一步. 高德地图副总裁郄建军(左)与阿里云业务总经 ...
- 005.TCP--拼接TCP头部IP头部,实现TCP三次握手的第一步(Linux,原始套接字)
一.目的: 自己拼接IP头,TCP头,计算效验和,将生成的报文用原始套接字发送出去. 若使用tcpdump能监听有对方服务器的包回应,则证明TCP报文是正确的! 二.数据结构: TCP首部结构图: s ...
- Theano2.1.2-基础知识之第一步:代数
来自:http://deeplearning.net/software/theano/tutorial/adding.html Baby Steps - Algebra 一.两个标量相加 在学习the ...
随机推荐
- LINUX下查看日志信息
Linux下grep显示多行信息标准unix/linux下的grep通过以下参数控制上下文 grep -C 5 foo file 显示file文件中匹配foo字串那行以及上下5行 例如 grep -C ...
- mysql的innodb数据存储结构
数据库磁盘读取与系统磁盘读取 1,系统从磁盘中读取数据到内存时是以磁盘块(block)为基本单位,位于同一个磁盘块中的数据会被一次性读取出来. 2,innodb存储引擎中有页(Page)的概念,页 ...
- C语言强、弱符号,强、弱引用
C语言强.弱符号,强.弱引用 符号定义 在编程中我们经常碰到符号重复定义的情况,当我们在同一个作用域内重复定义同一个变量时,有时是因为误写,有时是文件之间的冲突,编译器的处理方式就是报错: redef ...
- Python_正则表达式语法
1.正则表达式中的操作符: 2.re库的使用: import re #search方法要求只要待匹配的字符串中包含正则表达式中的字符串就可以 match = re.search('python+',' ...
- 哈希长度拓展攻击之De1CTF - SSRF Me
题目考查:python代码审计.hash长度拓展攻击 0x01 题目源码: #! /usr/bin/env python #encoding=utf-8 from flask import Flask ...
- SQL进阶系列之3三值逻辑与NULL
写在前面 普通编程语言里的布尔型只有true和false两个值,这种逻辑体系被称为二值逻辑,而SQL语言里,还有第三个值unknown,因此SQL的逻辑体系被称为三值逻辑. Why SQL存在三值逻辑 ...
- 微信小程序~下拉刷新真机测试不弹回的处理办法
问题描述: 下拉刷新在手机上不会自动回弹,开发工具可以 解决办法: 主动调用wx.stopPullDownRefresh /** * 页面相关事件处理函数--监听用户下拉动作 */ onPullDow ...
- 项目Alpha冲刺(10/10)
1.项目燃尽图 2.今日进度描述 项目进展 完成测试 问题困难 测试用例的设计 心得体会 目标快要完成,队员士气较高 3.会议照片 4.各成员情况 221600106 今日进展 根据测试结果修改代码 ...
- axure快速上手
Axure RP是一个专业的快速原型设计工具.Axure(发音:Ack-sure),代表美国Axure公司:RP则是Rapid Prototyping(快速原型)的缩写.Axure RP是美国Axur ...
- 使用Patroni和HAProxy创建高可用的PostgreSQL集群
操作系统:CentOS Linux release 7.6.1810 (Core) node1:192.168.216.130 master node2:192.168.216.132 slave n ...