搭建WebGL开发环境

前言

本篇文章介绍如何搭建WebGL开发环境

WebGL

WebGL的技术规范继承自免费和开源的OpenGL ES标准,从某种意义上说,WebGL就是Web版的OpenGL ES,而OpenGL ES是从OpenGL中派生出来的。他们的应用环境有区别,一般来说:

  • OpenGL:一般应用于桌面级别的三维图形渲染,同类的还有微软的DirectX和苹果公司的Metal技术
  • OpenGL ES:OpenGL的子集,删除了很多用处不大的接口和特性,但仍然能进行三维渲染,应用于智能手机、嵌入式计算机、家用游戏机等
  • WebGL:从OpenGL ES派生出来的。需要浏览器内核的支持。WebGL广泛应用于所有的网页渲染。因为需要运行在浏览器上,所以浏览器在背后做了很多工作,包括跨平台的特性。

版本

既然OpenGL、OpenGL ES和WebGL存在继承关系,那必然涉及到版本信息

先看一下OpenGL的版本信息:

  • OpenGL 1.5:这个版本的OpenGL不支持着色器编程,采用固定渲染管线
  • OpenGL 2.0:这个版本加入GLSL着色器编程语言
  • OpenGL 3.3:2010年发布
  • OpenGL 4.3:2013年发布

看一下OpenGL ES的版本信息:

  • OpenGL ES 1.1:OpenGL 1.5的子集,应用范围有限
  • OpenGL ES 2.0:OpenGL 2.0的子集,应用最广泛的版本
  • OpenGL ES 3.0 :OpenGL 3.3的子集,增加了很多新功能,包括:
    • 遮挡查询
    • 变缓反馈(Transform Feedback)
    • 实例渲染(Instanced Rendering)
    • 四个或更多渲染目标支持
    • 着色语言全面支持整数和32位浮点操作
    • 纹理功能大幅增强,支持浮点纹理、3D纹理、深度纹理、顶点纹理、NPOT纹理、R/RG单双通道纹理、不可变纹理、2D阵列纹理、无二次幂限制纹理、阴影对比、调配(swizzle)、LOD与mip level clamps、无缝立方体贴图、采样对象、纹理MSAA抗锯齿渲染器
    • 一系列广泛的精确尺寸纹理和渲染缓冲格式

看一下WebGL的版本信息:

  • WebGL 1.0:基于OpenGL ES 2.0,并提供了3D图形的API,它使用HTML5Canvas并允许利用文档对象模型接口。
  • WebGL 2.0:WebGL 2.0基于OpenGL ES 3.0,确保了提供许多选择性的WebGL 1.0扩展,并引入新的API。可利用部分Javascript实现自动存储器管理

WebGL程序组成

一个最简单的WebGL程序之需要一个html文件和几个js文件就可以了,下面详细介绍

html部分

html主要是为了放置canvas元素,因为WebGL的渲染都是在canvas进行的。

下面是一个html部分:

复制代码
<!DOCTYPE html>
<html lang="cn">
<head>
    <meta charset="utf-8">
    <title>wgl render ins</title>
</head>
<body onload="main()">
    <canvas id="wglCanvas" width="960" height="540">
        当前浏览器不支持canvas
    </canvas>
	<script src="sk_init.js"></script>
	<script src="sk_shader.js"></script>
	<script src="sk_utils.js"></script>
</body>
</html>

这个简单的不能再简单的html就已经够用了。

上面的html我们命名为main.html。从上面的html中,可以发现我们需要一个main函数,这个函数一般来说在js中实现,这也是我们WebGL程序正式开始的入口函数,我们添加一个js文件sk_init.js,并实现该函数,当然我们也需要在html中加入引用js的代码,为了保持代码的简洁,已经在上面的html中加入了该代码。

js部分

下面是sk_init.js的代码:

复制代码
var gl;
function main()
{
    var canvas = document.getElementById("wglCanvas");
    gl = create3DContext(canvas);
    if(!gl)
    {
        console.log("获取webgl渲染上下文失败");
        return;
    }
    gl.clearColor(0.0,0.0,0.0,1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);
}

var create3DContext = function(canvas) {
	var names = ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"];
	var context = null;
	for (var ii = 0; ii < names.length; ++ii) {
		try {
			context = canvas.getContext(names[ii]);
		} catch(e) {}
		if (context) {
			break;
		}
	}
	return context;
}

上面这个代码分为几部分:

  • 获取canvas对象
  • 获取webgl上下文
  • 设置canvas画布背景色
  • 使用背景色清空绘图区

虽然代码很简单,但是这几步是所有webgl程序都需要做的事情,当前main.html已经可以运行了。不过只会显示一个黑色背景。下面我们开始加入些东西

shader

想编写一个真正可以使用的webgl程序,首先需要写shader,由于本篇文章我们着重介绍WebGL框架,所以我们只是简单说明一下,关于shader语法,会有专门文章介绍。

  • WebGL程序的shader一般有两个,顶点着色器片元着色器
  • 着色器是以字符串的形式嵌入到js文件中的

下面是两个简单的着色器代码,我们创建一个sk_shader.js存放这部分代码:

顶点着色器:

复制代码
var VERTEX_SHADER=`
attribute vec4 a_Position;
uniform float u_PointSize;
void main() {
  gl_Position = a_Position;
  gl_PointSize = u_PointSize;
}`;

片元着色器:

复制代码
var FRAG_SHADER=`
void main() {
  gl_FragColor = vec4(1.0,0.0,0.0,1.0);
}`;

初始化着色器

创建好了着色器以后,下一步就要初始化着色器了

加载着色器代码

我们新建一个sk_util.js文件,里面封装一些公共的方法,首先我们要添加的方法就是加载着色器代码的方法:

复制代码
// gl: webgl上下文
// type: 着色器类型
// source: 着色器代码
function loadShader(gl, type, source) {
  var shader = gl.createShader(type);
  if (shader == null) {
    console.log('unable to create shader');
    return null;
  }
  gl.shaderSource(shader, source);
  gl.compileShader(shader);
  var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
  if (!compiled) {
    var error = gl.getShaderInfoLog(shader);
    console.log('Failed to compile shader: ' + error);
    gl.deleteShader(shader);
    return null;
  }
  return shader;
}

加载一个着色器需要以下步骤:

  • 创建着色器对象 :使用gl.createShader接口,参数为着色器类型,着色器类型一般有两类:
    • gl.VERTEX_SHADER:顶点着色器
    • gl.FRAGMENT_SHADER:片元着色器
  • 加载着色器代码 :使用gl.shaderSource接口,第一个参数为第一步创建的着色器对象,第二个参数为着色器的字符串表示
  • 编译着色器代码 :使用gl.compileShader接口,参数为第一步创建的着色器对象
  • 获取着色器编译状态,如果编译失败,获取错误信息

创建着色器程序

下面是创建着色器程序的代码:

复制代码
function createProgram(gl, vshader, fshader) {
  var vertexShader = loadShader(gl, gl.VERTEX_SHADER, vshader);
  var fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fshader);
  if (!vertexShader || !fragmentShader) {
    return null;
  }
  var program = gl.createProgram();
  if (!program) {
    return null;
  }
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);
  gl.linkProgram(program);
  var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
  if (!linked) {
    var error = gl.getProgramInfoLog(program);
    console.log('Failed to link program: ' + error);
    gl.deleteProgram(program);
    gl.deleteShader(fragmentShader);
    gl.deleteShader(vertexShader);
    return null;
  }
  return program;
}

创建着色器程序的步骤如下:

  • 创建着色器程序 :使用gl.createProgram接口,返回着色器程序对象
  • 关联着色器对象 :使用gl.attachShader接口,第一个参数是第一步创建的着色器程序对象,第二个参数是关联的着色器对象
  • 链接着色器程序 :使用gl.linkProgram接口链接程序,参数是第一步创建的着色器程序对象
  • 获取着色器程序链接状态,如果链接失败,获取错误信息

使用着色器对象

  • 使用gl.useProgram接口使用我们上一步创建的着色器程序对象。
  • 将着色器程序对象赋给gl.program,方便我们后面传递数据的时候使用

代码如下:

复制代码
function initShaders(gl, vshader, fshader) {
    var program = createProgram(gl, vshader, fshader);
    if (!program) {
      console.log('Failed to create program');
      return false;
    }
    gl.useProgram(program);
    gl.program = program;
    return true;
}

经过这三步以后,着色器程序已经初始化完成了,接下来就是向着色器传递数据

传递数据

所谓的传递数据就是将内存的数据传递给显存

一般情况下shader中使用的数据有三种:

  • 使用attribute标记的数据,这个也叫做顶点数据,就是每个顶点都会有自己的数值
  • 使用uniform标记的数据,这个也叫做统一数据,就是所有顶点共用的数据
  • 使用varying标记的数据,这个是在着色器之间传递的数据

获取shader的数据标记

  • 使用gl.getAttribLocation接口获取attribute数据的标记,第一个参数是着色器程序对象,第二个参数是数据在shader中的名称的字符串表示
  • 使用gl.getUniformLocation接口获取uniform数据的标记,第一个参数是着色器程序对象,第二个参数是数据在shader中的名称的字符串表示

下面我们获取前面着色器程序的a_Position和u_PointSize的标记:

复制代码
var u_PointSize = gl.getUniformLocation(gl.program, "u_PointSize");
if (u_PointSize < 0) {
	console.log('Failed to get the storage location of u_PointSize');
	return -1;
}
var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
if (a_Position < 0) {
	console.log('Failed to get the storage location of a_Position');
	return -1;
}

从内存传递attribute数据到显存

  • 创建缓冲区,使用gl.createBuffer()创建缓冲区

  • 绑定缓冲区,使用gl.bindBuffer接口绑定缓冲区,第一个参数是缓冲区类型,这里我们使用gl.ARRAY_BUFFER,第二个参数是上一步创建的缓冲区

  • 传递数据,使用gl.bufferData接口传递数据,第一参数是缓冲区类型,这里我们使用gl.ARRAY_BUFFER ,第二个参数是内存数据指针,第三个参数是数据的使用方式,这里我们使用gl.STATIC_DRAW
    代码如下:

    var verticesTexCoords = new Float32Array([
    -0.5, 0.5, 0.0, 1.0,
    -0.5, -0.5, 0.0, 1.0,
    0.5, 0.5, 0.0, 1.0,
    0.5, -0.5, 0.0, 1.0,
    ]);
    var FSIZE = verticesTexCoords.BYTES_PER_ELEMENT;
    var vertexTexCoordBuffer = gl.createBuffer();
    if (!vertexTexCoordBuffer) {
    console.log('Failed to create the buffer object');
    return -1;
    }
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexTexCoordBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, verticesTexCoords, gl.STATIC_DRAW);

传递uniform数据到显存

使用gl.uniform1f接口传递单个float数值到显存,在当前程序中用来传递u_PointSize的值

代码如下:

复制代码
gl.uniform1f(u_PointSize, 10);

绑定缓冲区与attribute顶点属性

  • 使用gl.vertexAttribPointer接口绑定缓冲区与attribute顶点属性

  • 使用gl.enableVertexAttribArray接口开启顶点属性数组,如果不开启的话,默认会使用静态属性,就是所有顶点数据都使用一个默认的值
    代码如下:

    gl.vertexAttribPointer(a_Position, 4, gl.FLOAT, false, FSIZE * 4, 0);
    gl.enableVertexAttribArray(a_Position);

绘制

当前程序使用gl.drawArrays来开始绘制操作

gl.drawArrays的第一个参数是绘制的类型,我们当前程序就使用gl.POINTS方式

gl.drawArrays的第二个参数是绘制顶点的偏移,当前为0

gl.drawArrays的第三个参数是绘制顶点的数量,当前为4

代码如下:

复制代码
gl.clearColor(0.0,0.0,0.0,1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.POINTS, 0, 4);

代码结构调整

  • 我们把创建WebGL上下文和初始化着色器的方法放进sk_utils.js
  • 我们把shader字符串放进sk_shader.js
  • 我们把传递数据的部分放进sk_init.js
  • main.html中需要添加对这3个js的引用,我们已经添加好了

完整的代码已经上传gitlab:
https://gitlab.com/lingyanTools/sk_webgl.git

该例子绘制了四个顶点

相关推荐
小猪努力学前端1 天前
基于PixiJS的小游戏广告开发
前端·webgl·游戏开发
光影少年2 天前
WebGIS 和GIS学习路线图
学习·前端框架·webgl
DBBH2 天前
Cesium源码分析之渲染3DTile的一点思考
图形渲染·webgl·cesium.js
Robet3 天前
TS2d渲染引擎
webgl
Robet3 天前
WebGL2D渲染引擎
webgl
goodName4 天前
如何实现精准操控?Cesium模型移动旋转控件实现
webgl·cesium
丫丫7237347 天前
Three.js 模型树结构与节点查询学习笔记
javascript·webgl
allenjiao9 天前
WebGPU vs WebGL:WebGPU什么时候能完全替代WebGL?Web 图形渲染的迭代与未来
前端·图形渲染·webgl·threejs·cesium·webgpu·babylonjs
mapvthree10 天前
mapvthree Engine 设计分析——二三维一体化的架构设计
webgl·数字孪生·mapvthree·jsapi2d·jsapigl·引擎对比
GISer_Jing11 天前
3D Cesium渲染架剖析
javascript·3d·webgl