我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情
什么是WebGL
WebGL(全写 Web Graphics Library)是一种 3D 绘图标准,并允许用户与之交互。这种绘图技术标准允许把 JavaScript 和 OpenGL ES 2.0 结合在一起,通过增加 OpenGL ES 2.0 的一个 JavaScript 绑定,WebGL 可以为 HTML5 Canvas 提供硬件 3D 加速渲染,这样 Web 开发人员就可以借助系统显卡来在浏览器里更流畅地展示 3D 场景和模型了,还能创建复杂的导航和数据视觉化。WebGL 1.0 是以 OpenGL ES 2.0 为蓝本设计的,OpenGL ES 2.0 则是以 OpenGL 2.0 为蓝本设计的。 WebGL 在 GPU 中运行,因此你需要使用能够在 GPU 上运行的代码,WebGL 使用 GLSL 来编写用于 GPU 渲染的着色器,JavaScript 主要负责整个渲染流程的控制和 GPU 与 CPU 之间的数据交互,繁重的坐标变换计算全部交给 GPU 来处理。
着色器
WebGL 在电脑的 GPU 中运行。因此你需要使用能够在 GPU 上运行的代码。这样的代码需要提供成对的方法。每对方法中一个叫顶点着色器( Vertex Shader ),另一个叫片段着色器 ( Fragment Shader ),并且使用一种和 C 或 C++ 类似的强类型的语言 GLSL (GL着色语言)。每一对组合起来可被编译为一个着色程序( Shader Program )。
顶点着色器的作用是计算顶点的位置。根据计算出的一系列顶点位置,WebGL 可以对点,线和三角形在内的一些图元进行光栅化处理。当对这些图元进行光栅化处理时需要使用片段着色器方法,片段着色器的作用是计算出当前绘制图元中每个像素的颜色值。
几乎所有的 WebGL API 都是关于如何设置这些成对方法的状态值以及运行它们。对于想要绘制的每一个对象,都需要先设置一系列状态值,然后通过调用 gl.drawArrays 或 gl.drawElements ( 这里仅指 WebGL 1.0 )运行一个着色方法对,使得你的着色程序能够在 GPU 上运行。
这些方法对所需的任何数据都需要发送到GPU,这里有着色器获取数据的4种方法。
- 属性( Attribute )和缓冲( Buffer )
缓冲是发送到 GPU 的一些二进制数据序列,通常情况下缓冲数据包括顶点位置,法向量,纹理坐标,颜色值等。你可以存储任何数据。
属性用来指明怎么从缓冲中获取所需数据并将它提供给顶点着色器。例如你可能在缓冲中用三个32位的浮点型数据存储一个位置值。对于一个确切的属性你需要告诉它从哪个缓冲中获取数据,获取什么类型的数据,起始偏移值是多少,到下一个位置的字节数是多少。
缓冲不是随意读取的。事实上顶点着色器运行的次数是一个指定的确切数字,每一次运行属性会从指定的缓冲中按照指定规则依次获取下一个值。
- 全局变量( Uniform )
全局变量在着色程序运行前赋值,在运行过程中全局有效。
- 纹理( Texture )
纹理是一个数据序列,可以在着色程序运行中随意读取其中的数据。大多数情况存放的是图像数据,但是纹理仅仅是数据序列,你也可以随意存放除了颜色数据以外的其它数据。
- 可变量( Varying )
可变量是一种顶点着色器给片段着色器传值的方式,依照渲染的图元是点,线还是三角形,顶点着色器中设置的可变量会在片断着色器中获取不同的插值。
现在有了两个着色器方法,让我们开始使用 WebGL。
首先需要一个 HTML 中的 canvas 对象:
<canvas id="webgl-canvas"></canvas>
然后可以用 JavaScript 获取它:
var canvas = document.getElementById("webgl-canvas");
创建一个 WebGL 渲染上下文(WebGLRenderingContext):
var gl = canvas.getContext("webgl");
if (!gl) {
// 你不能使用WebGL!
...
现在需要编译着色器对,提交到 GPU。你可以利用 JavaScript 中创建字符串的方式创建 GLSL 字符串:用串联的方式(concatenating),用AJAX下载,用多行模板数据。或者在这个例子里,将它们放在非 JavaScript 类型的标签中。
<script id="2d-vertex-shader" type="notjs">
// 一个属性变量,将会从缓冲中获取数据
attribute vec4 a_position;
// 所有着色器都有一个main方法
void main() {
// gl_Position 是一个顶点着色器主要设置的变量
gl_Position = a_position;
}
</script>
<script id="2d-fragment-shader" type="notjs">
// 片断着色器没有默认精度,所以我们需要设置一个精度
// mediump是一个不错的默认值,代表“medium precision”(中等精度)
precision mediump float;
void main() {
// gl_FragColor是一个片断着色器主要设置的变量
gl_FragColor = vec4(1, 0, 0.5, 1); // 返回“瑞迪施紫色”
}
</script>
事实上,大多数三维引擎在运行时利用模板,串联等方式创建GLSL。对于这个教程中的例子来说,没有复杂到要在运行时创建 GLSL 的程度。
接下来我们使用的方法将会创建一个着色器,只需要上传 GLSL 数据,编译成着色器。你可能注意到这段代码没有任何注释,因为可以从方法名很清楚的了解方法的作用(这里作为翻译版本我还是稍微注释一下)。
// 创建着色器方法,输入参数:渲染上下文,着色器类型,数据源
function createShader(gl, type, source) {
var shader = gl.createShader(type); // 创建着色器对象
gl.shaderSource(shader, source); // 提供数据源
gl.compileShader(shader); // 编译 -> 生成着色器
var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (success) {
return shader;
}
console.log(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
}
现在可以使用以上方法创建两个着色器:
var vertexShaderSource = document.getElementById("2d-vertex-shader").text;
var fragmentShaderSource = document.getElementById("2d-fragment-shader").text;
var vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
var fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
然后我们将这两个着色器 link(链接)到一个 program(着色程序):
function createProgram(gl, vertexShader, fragmentShader) {
var program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
var success = gl.getProgramParameter(program, gl.LINK_STATUS);
if (success) {
return program;
}
console.log(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
}
然后调用它:
var program = createProgram(gl, vertexShader, fragmentShader);
到此为止我们已经创建了一个着色器程序
我们需要告诉 WebGL 运行哪个着色程序:
// 告诉它用我们之前写好的着色程序(一个着色器对)
gl.useProgram(program);
显然我们需要告诉 WebGL 要运行着色器来画图
// 绘制
gl.drawArrays(primitiveType, offset, count);
webgl3d库主要使用:Three.js、Babylon.js等等
什么是Three.js
- Three.js是一个3D JavaScript库。
- 一 个 典 型 的 Three.js 程 序 至 少 要 包 括 渲 染 器( Renderer)、 场 景( Scene)、 照 相 机( Camera), 以 及 你 在 场 景 中 创 建 的 物 体。
Cameras( 照 相 机, 控 制 投 影 方 式)
Three.js
- OrthographicCamera(正交)、PerspectiveCamera(透视)
- 透视投影近大远小,更加真实
var camera = new THREE.OrthographicCamera(-2, 2, 1.5, -1.5, 1, 10)
var camera = new THREE.PerspectiveCamera(60, 400 / 300, 1, 10);
Webgl
- 正射投影矩阵、透视投影矩阵
- <投影矩阵(正射、透视)>x<视图矩阵>x<顶点坐标>
attribute vec4 a_Position;
attribute vec4 a_Color;
uniform mat4 u_ViewMatrix;
uniform mat4 u_ProjMatrix;
varying vec4 v_Color;
void main() {
gl_Position = u_ProjMatrix * u_ViewMatrix * a_Position;
v_Color = a_Color;
}
// 创建指定可视空间的矩阵并传递给u_ProjMatrix
var projMatrix = new Matrix4();
projMatrix.setOrtho(-1.0, 1.0, -1.0, 1.0, 0.0, 2.0);
gl.uniformMatrix4fv(u_ProjMatrix, false, projMatrix.elements);
// 创建视图矩阵
var viewMatrix = new Matrix4();
// 使用矩阵设置摄像机视图矩阵
viewMatrix.setLookAt(g_EyeX, g_EyeY, g_EyeZ, 0, 0, 0, 0, 1, 0);
// 传递视图投影矩阵
gl.uniformMatrix4fv(u_ViewMatrix, false, viewMatrix.elements);
attribute vec4 a_Position;
attribute vec4 a_Color;
uniform mat4 u_ViewMatrix;
uniform mat4 u_ProjMatrix;
varying vec4 v_Color;
void main() {
gl_Position = u_ProjMatrix * u_ViewMatrix * a_Position;;
v_Color = a_Color;
}
var viewMatrix = new Matrix4({}); // 视图矩阵
var projMatrix = new Matrix4({}); // 投影矩阵
// 计算视图矩阵和投影矩阵
viewMatrix.setLookAt(0, 0, 5, 0, 0, -100, 0, 1, 0);
projMatrix.setPerspective(30, canvasRef.current.width/canvasRef.current.height, 1, 100);
// 将视图矩阵和投影矩阵传递给u_ViewMatrix和u_ProjMatrix
glRef.current.uniformMatrix4fv(u_ViewMatrix, false, viewMatrix.elements);
glRef.current.uniformMatrix4fv(u_ProjMatrix, false, projMatrix.elements);
几何形状
Three.js
**
- 立 方 体( CubeGeometry)、平 面( PlaneGeometry)、球 体( SphereGeometry)、圆 形( CircleGeometry)、圆 柱 体( CylinderGeometry)、正 四 面 体( TetrahedronGeometry)、正 八 面 体( OctahedronGeometry)、正 二 十 面 体( IcosahedronGeometry)、圆 环 面( TorusGeometry)、圆 环 结( TorusKnotGeometry)、文 字 形 状( TextGeometry)
var material = new THREE.MeshBasicMaterial({
color: 0xffff00,
wireframe: true
});
var cube = new THREE.Mesh(new THREE.CubeGeometry(1, 2, 3, 2, 2, 3), material);
scene.add(cube);
Webgl
// Create a cube
// v6----- v5
// /| /|
// v1------v0|
// | | | |
// | |v7---|-|v4
// |/ |/
// v2------v3
var verticesColors = new Float32Array([
// Vertex coordinates and color
1.0, 1.0, 1.0, 1.0, 1.0, 1.0, // v0 White
-1.0, 1.0, 1.0, 1.0, 0.0, 1.0, // v1 Magenta
-1.0, -1.0, 1.0, 1.0, 0.0, 0.0, // v2 Red
1.0, -1.0, 1.0, 1.0, 1.0, 0.0, // v3 Yellow
1.0, -1.0, -1.0, 0.0, 1.0, 0.0, // v4 Green
1.0, 1.0, -1.0, 0.0, 1.0, 1.0, // v5 Cyan
-1.0, 1.0, -1.0, 0.0, 0.0, 1.0, // v6 Blue
-1.0, -1.0, -1.0, 0.0, 0.0, 0.0 // v7 Black
]);
生成物体
Three.js
- ◦几何体+材质组成
- ◦物体:Bone、Group、Line、LOD、Mesh( 网 格, 最 常 用 的 物 体)、MorphAnimMesh、Particle、ParticleSystem、Ribbon、SkinnedMesh、Sprite
var material = new THREE.MeshLambertMaterial({
color: 0xffff00
});
var geometry = new THREE.CubeGeometry(1, 2, 3);
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
Webgl
- Webgl是通过传入不同光照模型、不同的顶点颜色产生不同的效果,也就是不同的材质
光照
Three.js
- 环 境 光 (AmbientLight)、点 光 源(PointLight)、平 行 光(DirectionalLight)、聚 光 灯(SpotLight)
var light = new THREE.SpotLight(0xffff00, 1, 100, Math.PI / 6, 25);
light.position.set(2, 5, 3);
light.target = cube;
light.castShadow = true;
light.shadowCameraNear = 2;
light.shadowCameraFar = 10;
light.shadowCameraFov = 30;
light.shadowCameraVisible = true;
light.shadowMapWidth = 1024;
light.shadowMapHeight = 1024;
light.shadowDarkness = 0.3;
scene.add(light);
// ambient light
var ambient = new THREE.AmbientLight(0x666666);
scene.add(ambient);
Webgl
- 光照模型
- 漫反射:<漫反射光颜色>=<入射光颜色>x<表面基底色>x(<光线方向>x<法线方向>)
- 环境反射:<环境反射光颜色>=<入射光颜色>x<表面基底色>
- 表面的反射光:<表面的反射光颜色>=<漫反射光颜色>+<环境反射光颜色>
attribute vec4 a_Position;
attribute vec4 a_Color;
attribute vec4 a_Normal; // Normal
uniform mat4 u_MvpMatrix;
uniform vec3 u_DiffuseLight; // Diffuse light color 光线颜色
uniform vec3 u_LightDirection; // Diffuse light direction (in the world coordinate, normalized) 归一化的世界坐标
uniform vec3 u_AmbientLight; // Color of an ambient light 环境颜色
varying vec4 v_Color;
void main() {
gl_Position = u_MvpMatrix * a_Position;
// Make the length of the normal 1.0
vec3 normal = normalize(a_Normal.xyz);
// The dot product of the light direction and the normal (the orientation of a surface) 计算光线方向和法向量的点积
float nDotL = max(dot(u_LightDirection, normal), 0.0);
// Calculate the color due to diffuse reflection 计算漫反射光的颜色
vec3 diffuse = u_DiffuseLight * a_Color.rgb * nDotL;
// Calculate the color due to ambient reflection 计算环境光产生的反射光颜色
vec3 ambient = u_AmbientLight * a_Color.rgb;
// Add the surface colors due to diffuse reflection and ambient reflection 将以上两者相加得到物体最终颜色
v_Color = vec4(diffuse + ambient, a_Color.a);
}
// Get the storage locations of uniform variables and so on
var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');
var u_DiffuseLight = gl.getUniformLocation(gl.program, 'u_DiffuseLight');
var u_LightDirection = gl.getUniformLocation(gl.program, 'u_LightDirection');
var u_AmbientLight = gl.getUniformLocation(gl.program, 'u_AmbientLight');
if (!u_MvpMatrix || !u_DiffuseLight || !u_LightDirection || !u_AmbientLight) {
console.log('Failed to get the storage location');
return;
}
// Set the light color (white)
gl.uniform3f(u_DiffuseLight, 1.0, 1.0, 1.0);
// Set the light direction (in the world coordinate)
var lightDirection = new Vector3([0.5, 3.0, 4.0]);
lightDirection.normalize(); // Normalize
gl.uniform3fv(u_LightDirection, lightDirection.elements);
// Set the ambient light
gl.uniform3f(u_AmbientLight, 0.2, 0.2, 0.2);
// Calculate the view projection matrix
var mvpMatrix = new Matrix4({}); // Model view projection matrix
mvpMatrix.setPerspective(30, canvasRef.current.width/canvasRef.current.height, 1, 100);
mvpMatrix.lookAt(3, 3, 7, 0, 0, 0, 0, 1, 0);
// Pass the model view projection matrix to the variable u_MvpMatrix
gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);
位 置、 缩 放、 旋 转变换
Three.js
- 通过api:scale、rotateX、rotateY、rotateZ
- 通过属性:mesh.position.z=1
// ball
ballMesh = new THREE.Mesh(new THREE.SphereGeometry(ballRadius, 16, 8),
new THREE.MeshLambertMaterial({
color: 0xffff00
}));
ballMesh.position.y = ballRadius;
scene.add(ballMesh);
function draw() {
if (isMoving) {
ballMesh.position.y += v;
v += a;
if (ballMesh.position.y <= ballRadius) {
// hit plane
v = -v * 0.9;
}
if (Math.abs(v) < 0.001) {
// stop moving
isMoving = false;
ballMesh.position.y = ballRadius;
}
}
renderer.render(scene, camera);
id = requestAnimationFrame(draw);
}
Webgl
**
- <视图矩阵>x<模型矩阵>x<原始顶点坐标>
- 平移、缩放等基本变换矩阵或它们的组合,被称为模型矩阵
attribute vec4 a_Position;
uniform mat4 u_ModelMatrix;
void main() {
gl_Position = u_ModelMatrix * a_Position;
}
var modelMatrix = new Matrix4({});
// Set the rotation matrix
modelMatrix.setRotate(currentAngle, 0, 0, 1); // Rotation angle, rotation axis (0, 0, 1)
// Pass the rotation matrix to the vertex shader
gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);
var tick = function() {
currentAngle = animate(currentAngle); // Update the rotation angle
draw(gl, n, currentAngle, modelMatrix, u_ModelMatrix, mode); // Draw the triangle
requestAnimationFrame(tick); // Request that the browser calls tick
};
旋转矩阵
平移矩阵
缩放矩阵
纹理
Three.js
- 纹理在材质中的使用
var texture = THREE.ImageUtils.loadTexture('../img/chess.png', {}, function() {
renderer.render(scene, camera);
});
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(4, 4);
var material = new THREE.MeshLambertMaterial({
map: texture
});
var plane = new THREE.Mesh(new THREE.PlaneGeometry(12, 12), material);
scene.add(plane);
Webgl
**
- 设置纹理坐标(initVertexBuffers())
- 配置和加载纹理(initTextures())
- 为WebGL配置纹理(loadTexture())
- 激活纹理单元(gl.activeTexture())
- 绑定纹理对象(gl.bindTexture())
- 配置纹理对象的参数(gl.textParameteri())
- 将纹理图像分配给纹理对象(gl.texImage2D())
- 将纹理单元传递给片元着色器(gl.uniformli())
- 从顶点着色器向片元着色器传递纹理坐标
** 例
# 顶点着色器
attribute vec4 a_Position;
attribute vec2 a_TexCoord;
varying vec2 v_TexCoord;
void main() {
gl_Position = a_Position;
v_TexCoord = a_TexCoord;
}
# 片元着色器
#ifdef GL_ES
precision mediump float;
#endif
uniform sampler2D u_Sampler;
varying vec2 v_TexCoord;
void main() {
gl_FragColor = texture2D(u_Sampler, v_TexCoord);
}
var verticesTexCoords = new Float32Array([
// Vertex coordinates, texture coordinate
-0.5, 0.5, 0.0, 1.0,
-0.5, -0.5, 0.0, 0.0,
0.5, 0.5, 1.0, 1.0,
0.5, -0.5, 1.0, 0.0,
]);
外部模型
Three.js
提供各种loader threejs.org/examples/#w…
Webgl
rodger.global-linguist.com/webgl/ch10/…