随着三维地图的越来越流行,作为一个giser
不会WebGL
都不好意思说自己是做webgis
的了。实现一些基本的图形可以用canvas
的API
来完成,但如果数据量一多,canvas
的API
就力不从心了,所以需要学习一下WebGL
的知识。WebGL
(Web图形库) 是一种JavaScript API
,用于在任何兼容的Web浏览器中呈现交互式3D和2D图形,而无需使用插件。WebGL
通过引入一个与OpenGL ES 2.0
紧密相符合的API
,canvas
元素可被用来通过脚本(通常是JavaScript
)绘制图形。比如,它可以被用来绘制图形,制作图片集合,甚至用来实现动画效果。目前支持 WebGL
的浏览器有:Firefox4+
,Google Chrome9+
, Opera12+
, Safari 5.1+
和 Internet Explorer 11+
;然而, WebGL一些特性也需要用户的硬件设备支持。
1.创建WebGL
上下文
动手之前先介绍两个学习网站,对学习WebGL
很有帮忙,WebGL教程和MDN
首先合建一个DIV
用于WebGL
的容器,然后再通过getContext
来获取WebGL
的上下文。具体代码如下所示:
function main(){
//获取canvas
const canvas = document.querySelector("#glcanvas");
//获取webgl上下文
const gl = canvas.getContext('webgl');
if(!gl){
alert('该浏览器不支持webgl');
}
//设置颜色
gl.clearColor(0.0, 0.0, 0.0, 1.0);
//将设置的颜色作为背景色
gl.clear(gl.COLOR_BUFFER_BIT);
}
2.渲染场景
开始学习的时候,先从简单的正文形开始绘制,并且开始绘制的是二维图形,图形虽然是二维的正方形,但是会将二维图形绘制在三维场景中,在三维场景中绘制图形比直接在canvas
上调用二维图形的API
要复杂得多,在三维场景中需要创建着色器,通过它来渲染简单场景并画出正方形。
2.1着色器介绍
着色器是使用GLSL语言来编写的程序,它记录着像素点的位置和颜色。他有两种不同类型的着色器,分别为顶点着色器和片段着色器
GLSL是什么?
下面引用百度百科上的说明什么是GLSL。
OpenGL着色语言(OpenGL Shading Language)是用来在OpenGL中着色编程的语言,也即开发人员写的短小的自定义程序,他们是在图形卡的GPU (Graphic Processor Unit图形处理单元)上执行的,代替了固定的渲染管线的一部分,使渲染管线中不同层次具有可编程性。比如:视图转换、投影转换等。GLSL(GL Shading Language)的着色器代码分成2个部分:Vertex Shader(顶点着色器)和Fragment(片断着色器),有时还会有Geometry Shader(几何着色器)。负责运行顶点着色的是顶点着色器。它可以得到当前OpenGL 中的状态,GLSL内置变量进行传递。GLSL其使用C语言作为基础高阶着色语言,避免了使用汇编语言或硬件规格语言的复杂性。
下面就来介绍一下什么是顶点着色器
和片段着色器
。
2.1.1顶点着色器
顶点着色器是一种作用于顶点的着色器,它的工作是将输入顶点从原始坐标系转换到WebGL
所使用的缩放空间坐标系。每个轴的坐标范围是从-1.0
到1.0
之间。并且不考虑纵横比,实际尺寸或任何其他因素。以下的顶点着色器接收一个我们定义的属性(aVertexPosition)
的顶点位置值。之后这个值与两个4x4
的矩阵(uProjectionMatrix和uModelMatrix)
相乘; 乘积赋值给gl_Position
。
const vsSource = `
attribute vec4 aVertexPosition;
uniform mat4 uModelViewMatrix;
uniform mat4 uProjectionMatrix;
void main() {
gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
}
`;
2.1.2片段着色器
片段着色器是指在顶点着色器处理完成图形的顶点后,会被需要绘制的图形的每个像素点调用一次。它的职责是确定像素的颜色,在获取到颜色值后,将该颜色绘制到图形对应像素的对应位置。如下所示为创建了一个片段着色器
。
const fsSource = `
void main() {
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
`;
2.2着色器初始化
在创建完顶点颜色器和片段着色器后,需要将它们传递给WebGL
,通过编译,并将他们连接在一起。
着色器初始化流程:
- 使用
gl.createShader
创建着色器- 使用
gl.shaderSource
将源代码发送到着色器- 编译源码
- 返回编译好的着色器
export class WebGLSource {
constructor(){
}
//初始化着色器程序,让WebGL知道如何绘制我们的数据
initShaderProgram(gl, vsSource, fsSource) {
const vertexShader = this.loadShader(gl, gl.VERTEX_SHADER, vsSource);
const fragmentShader = this.loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
// 创建着色器程序
const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
// 创建失败, alert
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
alert('创建失败 ' + gl.getProgramInfoLog(shaderProgram));
return null;
}
return shaderProgram;
}
//创建指定类型的着色器,上传source源码并编译
loadShader(gl, type, source) {
//创建一个新的着色器。
const shader = gl.createShader(type);
//将源代码发送到着色器。
gl.shaderSource(shader, source);
//编译源码
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert('编译失败!');
gl.deleteShader(shader);
return null;
}
//返回编译的着色器
return shader;
}
}
在创建着色器程序之后,我们需要查找WebGL
返回分配的输入位置。在上述情况下,我们有一个属性和两个uniforms
。属性从缓冲区接收值。顶点着色器的每次迭代都从分配给该属性的缓冲区接收下一个值。uniforms
类似于`JavaScript``全局变量。它们在着色器的所有迭代中保持相同的值。由于属性和统一的位置是特定于单个着色器程序的,因此我们将它们存储在一起以使它们易于传递。
2.3创建对象
在画正方形前,我们需要创建一个缓冲器来存储它的顶点,如下:
//创建缓冲器来存储顶点
initBuffers(gl) {
//创建缓冲器
const positionBuffer = gl.createBuffer();
//绑定上下文。
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
const positions = [
1.0, 1.0,
-1.0, 1.0,
1.0, -1.0,
-1.0, -1.0,
];
gl.bufferData(gl.ARRAY_BUFFER,
new Float32Array(positions),
gl.STATIC_DRAW);
return {
position: positionBuffer,
};
}
2.4 绘制场景
当着色器和物体都创建好后,可以开始渲染这个场景了。因为这个例子不会产生动画,所以 drawScene() 方法非常简单,如下所示:
//绘制场景
drawScene(gl, programInfo, buffers) {
//用背景色擦除画布
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clearDepth(1.0);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
//建立摄像机透视矩阵
const fieldOfView = 45 * Math.PI / 180; //设置45度的视图角度
//设置一个适合实际图像的宽高比
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
//指定在摄像机距离0.1到100单位长度的范围内的物体可见。
const zNear = 0.1;
const zFar = 100.0;
const projectionMatrix = mat4.create();
mat4.perspective(projectionMatrix,
fieldOfView,
aspect,
zNear,
zFar);
const modelViewMatrix = mat4.create();
//加载特定位置,并把正方形放在距离摄像机6个单位的的位置
mat4.translate(modelViewMatrix,
modelViewMatrix,
[-0.0, 0.0, -6.0]);
{
const numComponents = 2;
const type = gl.FLOAT;
const normalize = false;
const stride = 0;
const offset = 0;
gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position);
gl.vertexAttribPointer(
programInfo.attribLocations.vertexPosition,
numComponents,
type,
normalize,
stride,
offset);
gl.enableVertexAttribArray(
programInfo.attribLocations.vertexPosition);
}
gl.useProgram(programInfo.program);
gl.uniformMatrix4fv(
programInfo.uniformLocations.projectionMatrix,
false,
projectionMatrix);
gl.uniformMatrix4fv(
programInfo.uniformLocations.modelViewMatrix,
false,
modelViewMatrix);
{
const offset = 0;
const vertexCount = 4;
gl.drawArrays(gl.TRIANGLE_STRIP, offset, vertexCount);
}
}
绘制后的正文形如下所示:
整个流程下来,绘制一个正方形就需要那么多的代码,可见比起
canvas
的基础图形绘制要复杂的多,得多花时间来加强学习。个人博客