【WebGL之巅】26-光照-光照原理

By yesmore on 2021-08-05
阅读时间 55 分钟
文章共 11.7k
阅读量

对应《WebGL编程指南》第八章 36-LightedCube、37-LightedCube_animation、38-LightedCube_ambient

要点:光源类型(平行光、点光源光)、反射类型(漫反射、环境反射)、根据光线和表面的方向计算入射角、法向量

知识点

一、光照原理

1.1 引入——着色与阴影

现实世界中的物体被光纤照射时,会反射一部分光。只有当反射光线进入你的眼睛时,你才能够看到物体并辨认出它的颜色。

在现实世界中,当光线照射到物体上时,发生了两个重要的现象:

  • 根据光源和光线方向,物体不同表面的明暗程度变得不一致。
  • 根据光源光线方向,物体向地面投下影子

明暗差异和阴影

明暗差异给了物体立体感。上图中的立方体是纯白的,但是它每个面受到光照程度不同所以能够辨认。

着色:根据光照条件重建“物体各表面明暗不一的效果”的过程。

阴影:物体向地面投下影子的现象,又被成为阴影。

在讨论着色过程之前,考虑两件事:

  • 发出光线的光源的类型

  • 物体表面如何反射光线

1.2 光源类型

真实世界的光主要有两种类型:

  • 平行光,类似于自然中的太阳光
  • 点光源光,类似于人造灯泡的光
  • 此外,我们还用环境光来模拟真实世界中的非直射光(也就是由光源发出后经过墙壁或其他物体反射后的光)

三维图形学还使用一些其他类型的光,比如用聚合灯光来模拟电筒,车前灯等。

平行光、点光源光、环境光

平行光

顾名思义,平行光的光线是互相平行的,平行光具有方向。平行光可以看做是无限远处的光源(比如太阳)发出的光。因为太阳距离地球很远,所以阳光到达地球时可以认为是平行的。平行光很简单,可以用一个方向和一个颜色来定义。

本章节提到的“光的颜色”,实际上已包含光的强度信息。比如标准的白光为(1, 1, 1),那么两倍于其强度的白光就表示为(2, 2, 2)。

点光源光

点光源光是从一个点向周围的所有方向发出的光。点光源光可以用来表示现实中的灯泡、火焰等。我们需要指定点光源的位置颜色。光线的方向将根据点光源的位置和被照射之处的位置计算出来,因为点光源的光线的方向在场景内的不同位置是不同的。

实际上点光源的光会衰减,本章教程中为了程序简单并未进行点光源光强的衰减。

环境光

环境光(间接光)是指那些经光源(点光源或平行光源)发出后,被墙壁等物体多次反射,然后照到物体表面上的光。环境光从各个角度照射物体,其强度都是一致的。比如说,在夜间打开冰箱的门,整个厨房都会有些微微亮,这就是环境光的作用。环境光不用指定位置和方向,只需要指定颜色即可

1.3 反射类型

物体向哪个方向反射光,反射的光是什么颜色,取决于以下两个因素:

  • 入射光
  • 物体表面的类型

入射光的信息包括入射光的方向和颜色,而物体表面的信息包括表面的固有颜色和反射特性。

物体表面反射光线的方式由两种:漫反射环境反射。本节的重点是如何很据上述两种信息(入射光和物体表面特性)来计算出反射光的颜色。本节会涉及一些简单的数学计算。

漫反射

漫反射是针对平行光点光源而言的。漫反射的反射光在各个方向上是均匀的,如下图所示。

漫反射

如果物体表面像镜子一样光滑,那么光线就会以特定的角度反射出去;但是现实中的大部分材质,比如纸张、岩石、塑料等,其表面都是粗糙的,在这种情况下反射光就会以不固定的角度反射出去。漫反射就是针对后一种情况而建立的理想反射模型。

在漫反射中,反射光的颜色取决于入射光的颜色表面的基底色入射光与表面形成的入射角

入射角定义:入射光与表面的法线形成的夹角,用 θ 表示。

漫反射光的颜色可以根据下式计算得到:

<漫反射光颜色> = <入射光颜色> x <表面基底色> x cosθ

式子中,<入射光颜色>指的是点光源平行光的颜色,乘法操作是在颜色矢量上逐分量(R、G、B)进行的。因为漫反射光在各个方向上都是”均匀“的,所以从任何角度看上去其强度都相等

漫反射光各方向均匀

环境反射

环境反射是针对环境光而言的。

在环境反射中,反射光的方向可以认为就是入射光的反方向。由于环境光照射物体的方式就是各方向均匀的、强度相等的,所以反射光也是各向均匀的,如下图所示。我们可以这样来描述它:

<环境反射颜色> = <入射光颜色> x <表面基底色>

这里的<入射光颜色>实际上也就是环境光的颜色。

环境反射

漫反射+环境反射

当漫反射和环境反射同时存在时,将两者加起来,就会得到物体最终被观察到的颜色:

<表面的反射颜色> = <漫反射光颜色> + <环境反射光颜色>

注意,两种反射光并不一定总是存在,也并不一定要按照上述公式来计算。渲染三维模型时,你可以修改这些公式以达到想要的效果。

下面来建立一个示例程序,在合适的位置放置一个光源,对场景进行着色。首先实现平行光下的漫反射。

二、 平行光下的漫反射

如前所述,漫反射的反射光,其颜色与入射光在入射点的入射角θ有关。平行光入射产生的漫反射光的颜色很容易计算,因为平行光的方向唯一的,对于同一个平面上的所有点,入射角是相同的,根据式子计算平行光入射的漫反射光颜色:

<漫反射光颜色> = <入射光颜色> x <表面基底色> x cosθ

上式用到了三项数据:

  • 平行入射光的颜色
  • 表面的基底色
  • 入射光与表面形成的入射角 θ

颜色可以用RGB值来表示,比如标准强度的白光颜色就是(1.0, 1.0, 1.0)。物体表面的基底色其实就是”物体本来的颜色“(或者说是”物体在标准白光下的颜色“),按照式子计算反射光颜色时,我们对 RGB 值的三个分量逐个相乘。

假设入射光是白色(1.0, 1.0, 1.0),而物体表面的基底色是红色(1.0, 0.0, 0.0),而入射角 θ 为 0.0(即入射光垂直入射),根据式子,入射光的红色分量 R 为 1.0,基底色的红色分量 R 为1.0,入射角余弦值 cos θ 为 1.0,那么反射光的红色分量 R 就可以由如下计算得到:

R = 1.0 * 1.0 * 1.0 = 1.0

类似的,我们可以算出绿色分量 G 和蓝色分量 B:

G = 1.0 * 0.0 * 1.0 = 0.0

B = 1.0 * 0.0 * 1.0 = 0.0

根据上面的计算,当白光垂直入射到红色物体的表面时,漫反射光的颜色就变成了红色(1.0, 0.0, 0.0)。而如果是红光垂直入射到白色物体的表面时,漫反射光的颜色也会是红色。这两种情况下,物体在观察者看来就是红色的。

那么如果入射角 θ 是 90 度,也就是说入射光与表面平行,一点都没有照射到表面上,在这种情况下会怎样呢?根据我们在现实世界中的经验,物体表面应该完全不反光,看上去是黑的。验证一下:当 θ 是 90 度,cos θ 的值是0,那么根据上面的式子,不管入射光的颜色和物体表面基底色是什么,最后得到的漫反射光颜色都为(0.0, 0.0, 0.0),也就是黑色。

同样,如果 θ 是60度,也就是斜射平行光斜射到物体表面上,那么该表面应该还是红色的,只不过比垂直入射时暗一些。根据上式,cos θ 是0.5,漫反射光颜色为 (0.5, 0.0, 0.0),即暗红色。

但是我们并不知道入射光 θ 是多少,只知道光线的方向。下面我们就来通过光线和物体表面的方向来计算入射角 θ,将式子中的 θ 换成我们更加熟悉的东西。

1.5 根据光线和表面的方向计算入射角

原理:根据入射光的方向和物体表面的朝向,即法线方向来计算出入射角。

在创建三维建模的时候,我们无法预先确定光线将以怎样的角度照射到每个表面上。但是,我们可以确定每个表面的朝向。在指定光源的时候,再确定光的方向,就可以用这两项信息来计算处入射角了。

点积运算

通过计算两个矢量的点积,来计算这两个矢量的夹角余弦值 cosθ。点积运算的使用非常频繁,GLSL ES 内置了点积运算函数。在公式中,我们使用点符号 . 来表示点积运算。这样,cosθ 就可以通过下式计算出来:

cosθ = <光线方向> · <法线方向>

进而计算反射光颜色:

<漫反射光颜色> = <入射光颜色> x <表面基底色> x (<光线方向> · <法线方向>)

注意:

  • 光线方向矢量和表面法线矢量的长度必须为1,否则反射光的颜色就会过暗或过亮将一个矢量的长度调整为1,同时保持方向不变的过程称之为归一化。GLSL ES 提供了内置的归一化函数,你可以直接使用。

  • 这里所谓的“光线方向”,实际上是入射方向的反方向,即从入射点指向光源方向,如下图所示。

光线方向

归一化

设矢量n为$(nx, ny, nz)$,则其长度为 $|n| = \sqrt {n_x^2 + n_y^2 + n_z^2}$​ ;

对矢量n进行归一化后的结果是 $(nx/m, ny/m, nz/m)$​​,式中mn的长度。如矢量(2, 2, 1)的长度为 |n| = sqrt(3),那么归一化后就是(2/3, 2/3, 1/3);

补充:矢量的点乘与叉乘

向量的点乘,也叫向量的内积、数量积,对两个向量执行点乘运算,就是对这两个向量对应位一一相乘之后求和的操作,点乘的结果是一个标量

点乘公式:

设$a =
\begin{bmatrix}
a_1, & a_2, & a_3
\end{bmatrix}
$,$b =
\begin{bmatrix}
b_1, & b_2, & b_3
\end{bmatrix}
$,则 $a · b =
\begin{bmatrix}
a_1b_1, & a_2b_2, & a_3b_3
\end{bmatrix}
$​​

(要求一维向量a和向量b的行列数相同。)

点乘几何意义:

点乘的几何意义是可以用来表征或计算两个向量之间的夹角,以及在b向量在a向量方向上的投影,有公式:$a · b = |a| |b| cosθ$​;​​​

两个向量的叉乘,又叫向量积、外积、叉积,叉乘的运算结果是一个向量而不是一个标量。并且两个向量的叉积与这两个向量组成的坐标平面垂直。

叉乘几何意义:

在三维几何中,向量a和向量b的叉乘结果是一个向量,更为熟知的叫法是法向量,该向量垂直于a和b向量构成的平面。

一般点乘用来判断两个向量是否垂直,也可以用来计算一个向量在某个方向上的投影长度,就像定义一样。

叉乘更多的是判断某个平面的方向。从这个平面上选两个不共线的向量,叉乘的结果就是这个平面的法向量。

更多关于叉乘与点乘知识点,请自行复习数学教材或百度。

1.6 法线:表面的朝向

定义

物体表面的朝向,即垂直于表面的方向,又称法线法向量

表示

法向量有三个分量,向量(nx, ny, nz)表示从原点(0, 0, 0)指向点(nx, ny, nz)的方向。比如,向量(1, 0, 0)表示 x 轴正方向,向量(0, 0, 1)表示 z 轴正方向。设计到表面和法向量的问题时,必须考虑以下两点:

  • 一个表面具有两个法向量
  • 平面的法向量唯一
一个表面具有两个法向量

每个表面都有两个面,”正面“和”背面“。两个面各自具有一个法向量。比如,垂直与z 轴的 xy平面,其正面的法向量为z负半轴,即(0, 0, -1),而正面的法向量为 x 负半轴,即(0, 0, 1)。

法向量

在三维图形学中,表面的正面和背面取决与绘制表面时的顶点顺序。当你按照v0, v1, v2, v3 的顶点顺序绘制了一个平面(该平面由两个三角形组成,各自绘制顺序是v0,v1,v2和v2,v3,v0),那么当你从正面观察这个表示时,这4个顶点是顺时针的,而你从背面观察该表面,这4个顶点就是逆时针的。如上图,该平面正面的法向量是(0, 0, -1)。

平面的法向量唯一

由于法向量表示的是方向,与位置无关,所以一个平面只有一个法向量。换句话说,平面的任意一点都具有相同的法向量。

进一步来说,即使有两个不同的平面,只要其朝向相同(也就是两个平面平行),法向量也相同。比如说,有一个经过点(10, 98, 9)的平面,只要垂直与 Z 轴,它的法向量仍然是(0, 0, 1)和(0, 0, -1),和经过原点并垂直与z轴的平面一样。

法向量与位置无关

下图左显示了示例程序中的立方体及每个表面的法向量。比如立方体表面上的法向量表示为 n(0, 1, 0)

立方体各表面法向量

一旦计算好每个平面的法向量,接下来的任务就是将数据传给着色器程序。以前程序把颜色作为“逐顶点数据”存储在缓冲区中,并传给着色器。对法向量数据也可以这样做。如上图右所示,每个顶点对应3个法向量,就像之前每个顶点都对应3个颜色值一样。

由于立方体各表面垂直相交,所以每个顶点对3个法向量(同时在缓冲区中被拆成3个顶点)。但是,一些表面光滑的物体,通常其每个顶点只对应1个法向量。

1.7 程序分析

1.7.1 顶点着色器

顶点着色器实现了:

<漫反射光颜色> = <入射光颜色> x <表面基底色> x (<光线方向> · <法线方向>)

由[1.5](#1.5 根据光线和表面的方向计算入射角)可知计算漫反射光颜色需要:

  • 入射光颜色
  • 表面基底色
  • 入射光方向(归一化)
  • 表面法线方向(归一化)

主要代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'attribute vec4 a_Color;\n' + //表面基底色
'attribute vec4 a_Normal;\n' + //法向量
'uniform mat4 u_MvpMatrix;\n' + //光线颜色
'uniform vec3 u_LightColor;\n' + //归一化的世界坐标(入射光方向)
'uniform vec3 u_LightDirection;\n' +
'varying vec4 v_Color;\n' +
'void main() {\n' +
' gl_Position = u_MvpMatrix * a_Position;\n' +
' vec3 normal = normalize(vec3(a_Normal));\n' + //对法向量进行归一化
' float nDotL = max(dot(u_LightDirection, normal), 0.0);\n' + //计算光线方向和法向量的点积
' vec3 diffuse = u_LightColor * vec3(a_Color) * nDotL;\n' + //计算漫反射光的颜色
' v_Color = vec4(diffuse, a_Color.a);\n' +
'}\n';
变量分析
变量名 描述
a_Color 表面基底色
a_Normal 表面法线方向
u_LightColor 入射光颜色
u_LightDirection 归一化的世界坐标(入射光方向)

注意,入射光方向 u_LightDirection 是在世界坐标下的,而且在传入着色器前已经在 JS 中归一化了。这样,我们就可以避免在顶点着色器每次执行时都对它进行归一化。

关于世界坐标系和本地坐标系参考。

本章节中,光照效果是在世界坐标系下计算的。

计算a_Normal

有了这些信息,就可以开始在顶点着色器中进行计算了。

首先对 a_Normal 进行归一化。严格地说,本例通过缓冲区传入的法向量都是已经归一化过的,所以实际上这一步可以略去。但是顶点着色器可不知道传入的矢量是否经过了归一化,而且这里没有节省开销的理由,所以,有这一步总比没有要好:

1
2
// 对法向量进行归一化
' vec3 normal = normalize(vec3(a_Normal));\n' +

a_Normal 变量是 vec4 类型的,使用前三个分量x、y和 z 表示法线法相,所以我们将这三个分量提取出来进行归一化。对vec3 类型的变量进行归一化不必这样做。本例使用 vec4 类型的 a_Normal 变量是为了方便对下一个示例程序进行扩展。GLSL ES 提供了内置函数 normalize()对矢量参数进行归一化。归一化的结果赋给了 vec3类型的 normal 变量,供之后使用。

计算点积

接下来,计算点积。光线方向存储在 u_LightDirection变量中,而且已经被归一化了,可以直接使用。法线方向存储在之前进行归一化后的结果 normal变量中。使用 GLSL ES 提供的内置函数 dot()计算两个矢量的点积,该函数接受两个矢量作为参数,返回它们的点积。

1
2
// 计算光线方向和法向量的点积
' float nDotL = max(dot(u_LightDirection, normal), 0.0);\n' +

如果点积大于0,就将点积赋值给 nDotL 变量,如果其小于0,就将0赋给该变量。使用内置函数 max()完成这个任务,将点积和0两者中的较大者赋值给 nDotL。

点积值小于0,意味着 cosθ 中的 θ 大于90度θ是入射角,也就是入射反方向(光线方向)表面法向量的夹角 。 θ大于90度说明光线照射在表面的背面上,此时,将nDotL 赋为 0.0。如下图:

入射角大于90度

计算漫反射颜色

注意 a_Color 变量即顶点的颜色,被从vec4对象转成了 vec3 对象,因为其第4个分量与式子无关。

实际上,物体表面的透明度确实会影响物体的外观。但这时光照的计算较为复杂,现在暂时认为物体都是不透明的,这样就计算出了漫反射光的颜色 diffuse

1
2
//计算漫反射光的颜色
' vec3 diffuse = u_LightColor * vec3(a_Color) * nDotL;\n' +

然后,将 diffuse 的值赋给 v_Color变量v_Colorvec4 对象,而 diffusevec3 对象,需要将第4个分量补上为1.0

1
'   v_Color = vec4(diffuse, a_Color.a);\n' +

顶点着色器运行的结果就是计算出了 v_Color变量,其值取决于顶点的颜色法线方向平行光的颜色和方向。v_Color 变量将被传入片元着色器并赋值给 gl_FragColor变量

本例中的光是平行光,所以立方体上同一个面的颜色是一致的,没有之前出现的颜色渐变效果。

1.7.2 JS 程序流程

JS 将光的颜色 u_LightColor 和方向 u_LightDirection 传给顶点着色器。首先用 gl.uniform3f()函数将 u_LightColor 赋值为(1.0, 1.0, 1.0),表示入射光是白光

1
gl.uniform3f(u_LightColor, 1.0, 1.0, 1.0); // 设置光线颜色为白色

下一步是设置光线方向,注意光线方向必须被归一化。cuon-matrix.js 为 vector3 类型提供了 normalize()函数,以实现归一化。该函数的用法非常简单:在你想要进行归一化的 Vector3 对象上调用即可。

1
2
3
var lightDirection = new Vector3([0.5, 3.0, 4.0]);//设置光线方向(世界坐标系下)
lightDirection.normalize();//归一化
gl.uniform3fv(u_LightDirection, lightDirection.elements);

注意 JS 和 GLSL ES 对矢量进行归一化的不同之处。

归一化后的光线方向以 Float32Array 类型的形式存储在 lightDirection 对象的 elements属性中,使用gl.uniform3fv()将其分配给着色器中的 u_LightDirection 变量

最后,在initVertexBuffers()函数中为每个顶点定义法向量。法向量数据存储在 normals 数组中,然后被 initArrayBuffer()函数传给了顶点着色器的 a_Normal 变量。

1
2
3
4
5
6
7
8
9
10
11
12
var normals = new Float32Array([    // Normal
0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, // v0-v1-v2-v3 front
1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, // v0-v3-v4-v5 right
0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, // v0-v5-v6-v1 up
-1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, // v1-v6-v7-v2 left
0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, // v7-v4-v3-v2 down
0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0 // v4-v7-v6-v5 back
]);

...

if (!initArrayBuffer(gl, 'a_Normal', normals, 3, gl.FLOAT)) return -1;

initArrayBuffer()函数的作用是将第3个参数指定的数组分配给第2个参数指定的着色器中的变量。

三、环境光下的漫反射

3.1 平行光下漫反射的缺陷

现在,我们已经成功实现了平行光下的漫反射光。但是结果图和显示中的立方体还是有点不大一样,特别是右侧表面是全黑的,仿佛不存在一样。如果这个立方体动起来,你也许就能看的更清楚一些,试着运行程序 Lighted_Cube_animation,如图所示:

LookAtTriangles Please use the browser supporting "canvas".

虽然程序是严格按照式子对场景进行光照的,但经验告诉我们肯定有什么对方不对劲。在现实世界中,光照下物体的各表面的差异不会如此分明:那些背光的面虽然会暗一些,但决不至于黑到看不见的程度。

3.2 环境光颜色计算

实际上,那些背光的面是被非直射光照亮的(即其他物体,如墙壁的反光等),前面提到的环境光就起到了这部分非直射光的作用,它使场景更加逼真。因为环境光均匀地从各个角度照在物体表面,所以由环境光反射产生的颜色只去取决与光的颜色表面基底色,使用式子计算后我们再来看一下:

<环境反射光颜色> = <入射光颜色> x <表面基底色>

接下来,向示例程序中加入上式中的环境光所产生的反射光颜色:

<表面的反射颜色> = <漫反射光颜色> + <环境反射光颜色>

环境光是由墙壁等其物体反射产生的,所以环境光的强度通常比较弱。假设环境光是较弱的白光(0.2, 0.2, 0.2),而物体表面是红色(1.0, 1.0, 1.0)。根据式子,由环境光反射的光颜色就是暗红色(0.2, 0.0, 0.0)。同样,在蓝色的空间中,环境光为(0.0, 0.0, 0.2),有一个白色的物体,即表面基底色为(1.0, 1.0, 1.0),那么由环境光产生的漫反射光颜色就是淡蓝色(0.0, 0.0, 0.2)。

3.3 程序分析

示例程序 LightedCube_ambient 实现了环境光漫反射的效果,如下图左所见。可见,完全没有被平行光照到的表面也不是全黑,而是呈现较暗的颜色,与真实世界更加相符。

LightedCube_ambient LightedCube
顶点着色器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'attribute vec4 a_Color;\n' + //表面基底色
'attribute vec4 a_Normal;\n' + //法向量
'uniform mat4 u_MvpMatrix;\n' +
'uniform vec3 u_LightColor;\n' + //光线颜色
'uniform vec3 u_LightDirection;\n' + //归一化的世界坐标(入射光方向)
'uniform vec3 u_AmbientLight;\n' + //环境光颜色
'varying vec4 v_Color;\n' +
'void main() {\n' +
' gl_Position = u_MvpMatrix * a_Position;\n' +
' vec3 normal = normalize(vec3(a_Normal));\n' + //对法向量进行归一化
' float nDotL = max(dot(u_LightDirection, normal), 0.0);\n' + //计算光线方向和法向量的点积
' vec3 diffuse = u_LightColor * vec3(a_Color) * nDotL;\n' + //计算漫反射光的颜色
' vec3 ambient = u_AmbientLight * vec3(a_Color);\n' + //计算环境光产生的反射光颜色
' v_Color = vec4(diffuse + ambient, a_Color.a);\n' +
'}\n';

顶点着色器中新增加了 u_AmbientLight变量用来接收环境光的颜色。使用该变量和表面的基底色 a_Color 计算出反射光的颜色,将其存储在 ambient 变量中。这样我们就即由环境光反射产生的颜色 ambient,又有了由平行光漫反射产生的颜色 diffuse。最后根据式子计算物体的最终颜色并存储在 v_Color 变量中,作为物体表面最终显示处的颜色,和 LightCube 一样。

1
2
var u_AmbientLight = gl.getUniformLocation(gl.program, 'u_AmbientLight');
gl.uniform3f(u_AmbientLight, 0.2, 0.2, 0.2); //设置环境光颜色

实例

代码1-LightedCube

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
//LightedCube.js
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'attribute vec4 a_Color;\n' + //表面基底色
'attribute vec4 a_Normal;\n' + //法向量
'uniform mat4 u_MvpMatrix;\n' + //光线颜色
'uniform vec3 u_LightColor;\n' + //归一化的世界坐标(入射光方向)
'uniform vec3 u_LightDirection;\n' +
'varying vec4 v_Color;\n' +
'void main() {\n' +
' gl_Position = u_MvpMatrix * a_Position;\n' +
' vec3 normal = normalize(vec3(a_Normal));\n' + //对法向量进行归一化
' float nDotL = max(dot(u_LightDirection, normal), 0.0);\n' + //计算光线方向和法向量的点积
' vec3 diffuse = u_LightColor * vec3(a_Color) * nDotL;\n' + //计算漫反射光的颜色
' v_Color = vec4(diffuse, a_Color.a);\n' +
'}\n';

var FSHADER_SOURCE=
'#ifdef GL_ES\n' +
'precision mediump float;\n' +
'#endif\n' +
'varying vec4 v_Color;\n' +
'void main(){\n'+
' gl_FragColor = v_Color;\n'+
'}\n';

function main() {

var canvas = document.getElementById("webgl");
if (!canvas) {
console.log("Failed to retrieve the <canvas> element");
return;
}

var gl = getWebGLContext(canvas);
if (!gl) {
console.log("Failed to get the rendering context for WebGL");
return;
}

if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log("Failed to initialize shaders.");
return;
}

//设置顶点位置
var n = initVertexBuffers(gl);
if (n < 0) {
console.log('Failed to set the positions of the vertices');
return;
}

// Set the clear color and enable the depth test
// gl.clearColor(0, 0, 0, 1);
gl.enable(gl.DEPTH_TEST);


var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');//模型视图投影矩阵
var u_LightColor = gl.getUniformLocation(gl.program, 'u_LightColor');
var u_LightDirection = gl.getUniformLocation(gl.program, 'u_LightDirection');
if (!u_MvpMatrix || !u_LightColor || !u_LightDirection) {
console.log('Failed to get the storage location');
return;
}

gl.uniform3f(u_LightColor, 1.0, 1.0, 1.0); //设置光线颜色为白色

var lightDirection = new Vector3([0.5, 3.0, 4.0]);//设置光线方向(世界坐标系下)
lightDirection.normalize();//归一化
gl.uniform3fv(u_LightDirection, lightDirection.elements);

var mvpMatrix = new Matrix4();
mvpMatrix.setPerspective(30, canvas.width/canvas.height, 1 ,100);
mvpMatrix.lookAt(3, 3, 7, 0, 0, 0, 0, 1, 0);
gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);

gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);
}

function initVertexBuffers(gl) {
// Create a cube
// v6----- v5
// /| /|
// v1------v0|
// | | | |
// | |v7---|-|v4
// |/ |/
// v2------v3
var vertices = new Float32Array([ // Vertex coordinates
1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0,-1.0, 1.0, 1.0,-1.0, 1.0, // v0-v1-v2-v3 front
1.0, 1.0, 1.0, 1.0,-1.0, 1.0, 1.0,-1.0,-1.0, 1.0, 1.0,-1.0, // v0-v3-v4-v5 right
1.0, 1.0, 1.0, 1.0, 1.0,-1.0, -1.0, 1.0,-1.0, -1.0, 1.0, 1.0, // v0-v5-v6-v1 up
-1.0, 1.0, 1.0, -1.0, 1.0,-1.0, -1.0,-1.0,-1.0, -1.0,-1.0, 1.0, // v1-v6-v7-v2 left
-1.0,-1.0,-1.0, 1.0,-1.0,-1.0, 1.0,-1.0, 1.0, -1.0,-1.0, 1.0, // v7-v4-v3-v2 down
1.0,-1.0,-1.0, -1.0,-1.0,-1.0, -1.0, 1.0,-1.0, 1.0, 1.0,-1.0 // v4-v7-v6-v5 back
]);

var colors = new Float32Array([ // Colors
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v0-v1-v2-v3 front
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v0-v3-v4-v5 right
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v0-v5-v6-v1 up
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v1-v6-v7-v2 left
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v7-v4-v3-v2 down
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0  // v4-v7-v6-v5 back
]);

var normals = new Float32Array([ // Normal
0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, // v0-v1-v2-v3 front
1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, // v0-v3-v4-v5 right
0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, // v0-v5-v6-v1 up
-1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, // v1-v6-v7-v2 left
0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, // v7-v4-v3-v2 down
0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0 // v4-v7-v6-v5 back
]);

var indices = new Uint8Array([ // Indices of the vertices
0, 1, 2, 0, 2, 3, // front
4, 5, 6, 4, 6, 7, // right
8, 9,10, 8,10,11, // up
12,13,14, 12,14,15, // left
16,17,18, 16,18,19, // down
20,21,22, 20,22,23 // back
]);

// Write the vertex coordinates and color to the buffer object
if (!initArrayBuffer(gl, vertices, 3, gl.FLOAT, 'a_Position'))
return -1;

if (!initArrayBuffer(gl, colors, 3, gl.FLOAT, 'a_Color'))
return -1;

if (!initArrayBuffer(gl, normals, 3, gl.FLOAT, 'a_Normal'))
return -1;

// Create a buffer object
var indexBuffer = gl.createBuffer();
if (!indexBuffer) {
console.log('Failed to create the buffer object');
return false;
}

// Write the indices to the buffer object
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);

return indices.length;
}

function initArrayBuffer(gl, data, num, type, attribute) {
var buffer = gl.createBuffer(); // Create a buffer object
if (!buffer) {
console.log('Failed to create the buffer object');
return false;
}
// Write date into the buffer object
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
// Assign the buffer object to the attribute variable
var a_attribute = gl.getAttribLocation(gl.program, attribute);
if (a_attribute < 0) {
console.log('Failed to get the storage location of ' + attribute);
return false;
}
gl.vertexAttribPointer(a_attribute, num, type, false, 0, 0);
// Enable the assignment of the buffer object to the attribute variable
gl.enableVertexAttribArray(a_attribute);

gl.bindBuffer(gl.ARRAY_BUFFER, null);

return true;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>LightedCube</title>
</head>
<body onload="main()">
<canvas id="webgl" width="400" height="400">
Please use the browser supporting "canvas".
</canvas>

<script src="../lib/webgl-utils.js"></script>
<script src="../lib/webgl-debug.js"></script>
<script src="../lib/cuon-utils.js"></script>
<script src="../lib/cuon-matrix.js"></script>
<script src="LightedCube.js"></script>
</body>
</html>
效果

LightedCube

代码2-LightedCube_animation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
//LightedCube_animation.js
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'attribute vec4 a_Color;\n' +
'attribute vec4 a_Normal;\n' +
'uniform mat4 u_MvpMatrix;\n' +
'uniform mat4 u_NormalMatrix;\n' +
'uniform vec3 u_LightDirection;\n' +
'varying vec4 v_Color;\n' +
'void main() {\n' +
' gl_Position = u_MvpMatrix * a_Position;\n' +
' vec4 normal = u_NormalMatrix * a_Normal;\n' +
' float nDotL = max(dot(u_LightDirection, normalize(normal.xyz)), 0.0);\n' +
' v_Color = vec4(a_Color.xyz * nDotL, a_Color.a);\n' +
'}\n';

var FSHADER_SOURCE=
'#ifdef GL_ES\n' +
'precision mediump float;\n' +
'#endif\n' +
'varying vec4 v_Color;\n' +
'void main(){\n'+
'gl_FragColor = v_Color;\n'+
'}\n';

function main() {

var canvas = document.getElementById("webgl");
var gl = getWebGLContext(canvas);
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log("Failed to initialize shaders.");
return;
}

//设置顶点位置
var n = initVertexBuffers(gl);

// Set the clear color and enable the depth test
// gl.clearColor(0, 0, 0, 1);
gl.enable(gl.DEPTH_TEST);

var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');//模型视图投影矩阵
var u_NormalMatrix = gl.getUniformLocation(gl.program, 'u_NormalMatrix');
var u_LightDirection = gl.getUniformLocation(gl.program, 'u_LightDirection');

var lightDirection = new Vector3([0.5, 3.0, 4.0]);//设置光线方向(世界坐标系下)
lightDirection.normalize();//归一化
gl.uniform3fv(u_LightDirection, lightDirection.elements);

var mvpMatrix = new Matrix4();//视图投影矩阵
mvpMatrix.setPerspective(30, canvas.width / canvas.height, 1, 100);
mvpMatrix.lookAt(3, 3, 7, 0, 0, 0, 0, 1, 0);

var currentAngle = 0.0; //目前的旋转角度
var modelMatrix = new Matrix4(); //模型矩阵
var vpMatrix = new Matrix4();//模型视图投影矩阵
var normalMatrix = new Matrix4();//法线变换矩阵

var tick = function () {
currentAngle = animate(currentAngle);//更新当前旋转角度

//计算模型矩阵
modelMatrix.setRotate(currentAngle, 0, 1, 0); //围绕y轴旋转
vpMatrix.set(mvpMatrix).multiply(modelMatrix);
gl.uniformMatrix4fv(u_MvpMatrix, false, vpMatrix.elements);

// Pass the matrix to transform the normal based on the model matrix to u_NormalMatrix
normalMatrix.setInverseOf(modelMatrix);
normalMatrix.transpose();
gl.uniformMatrix4fv(u_NormalMatrix, false, normalMatrix.elements);

// Clear color and depth buffer
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

// Draw the cube
gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);

requestAnimationFrame(tick, canvas); // Request that the browser ?calls tick
};
tick();
}

function draw(gl, n, angle, vpMatrix, u_MvpMatrix, u_NormalMatrix) {
}

function initVertexBuffers(gl) {
// Create a cube
// v6----- v5
// /| /|
// v1------v0|
// | | | |
// | |v7---|-|v4
// |/ |/
// v2------v3
var vertices = new Float32Array([ // Vertex coordinates
1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0,-1.0, 1.0, 1.0,-1.0, 1.0, // v0-v1-v2-v3 front
1.0, 1.0, 1.0, 1.0,-1.0, 1.0, 1.0,-1.0,-1.0, 1.0, 1.0,-1.0, // v0-v3-v4-v5 right
1.0, 1.0, 1.0, 1.0, 1.0,-1.0, -1.0, 1.0,-1.0, -1.0, 1.0, 1.0, // v0-v5-v6-v1 up
-1.0, 1.0, 1.0, -1.0, 1.0,-1.0, -1.0,-1.0,-1.0, -1.0,-1.0, 1.0, // v1-v6-v7-v2 left
-1.0,-1.0,-1.0, 1.0,-1.0,-1.0, 1.0,-1.0, 1.0, -1.0,-1.0, 1.0, // v7-v4-v3-v2 down
1.0,-1.0,-1.0, -1.0,-1.0,-1.0, -1.0, 1.0,-1.0, 1.0, 1.0,-1.0 // v4-v7-v6-v5 back
]);

var colors = new Float32Array([ // Colors
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v0-v1-v2-v3 front
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v0-v3-v4-v5 right
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v0-v5-v6-v1 up
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v1-v6-v7-v2 left
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v7-v4-v3-v2 down
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0  // v4-v7-v6-v5 back
]);

var normals = new Float32Array([ // Normal
0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, // v0-v1-v2-v3 front
1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, // v0-v3-v4-v5 right
0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, // v0-v5-v6-v1 up
-1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, // v1-v6-v7-v2 left
0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, // v7-v4-v3-v2 down
0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0 // v4-v7-v6-v5 back
]);

var indices = new Uint8Array([ // Indices of the vertices
0, 1, 2, 0, 2, 3, // front
4, 5, 6, 4, 6, 7, // right
8, 9,10, 8,10,11, // up
12,13,14, 12,14,15, // left
16,17,18, 16,18,19, // down
20,21,22, 20,22,23 // back
]);

// Write the vertex coordinates and color to the buffer object
if (!initArrayBuffer(gl, vertices, 3, gl.FLOAT, 'a_Position'))
return -1;

if (!initArrayBuffer(gl, colors, 3, gl.FLOAT, 'a_Color'))
return -1;

if (!initArrayBuffer(gl, normals, 3, gl.FLOAT, 'a_Normal'))
return -1;

// Create a buffer object
var indexBuffer = gl.createBuffer();
if (!indexBuffer) {
console.log('Failed to create the buffer object');
return false;
}

// Write the indices to the buffer object
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);

return indices.length;
}

function initArrayBuffer(gl, data, num, type, attribute) {
var buffer = gl.createBuffer(); // Create a buffer object
if (!buffer) {
console.log('Failed to create the buffer object');
return false;
}
// Write date into the buffer object
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
// Assign the buffer object to the attribute variable
var a_attribute = gl.getAttribLocation(gl.program, attribute);
if (a_attribute < 0) {
console.log('Failed to get the storage location of ' + attribute);
return false;
}
gl.vertexAttribPointer(a_attribute, num, type, false, 0, 0);
// Enable the assignment of the buffer object to the attribute variable
gl.enableVertexAttribArray(a_attribute);

gl.bindBuffer(gl.ARRAY_BUFFER, null);

return true;
}

// Rotation angle (degrees/second)
var ANGLE_STEP = 30.0;
// Last time that this function was called
var g_last = Date.now();
function animate(angle) {
// Calculate the elapsed time
var now = Date.now();
var elapsed = now - g_last;
g_last = now;
// Update the current rotation angle (adjusted by the elapsed time)
var newAngle = angle + (ANGLE_STEP * elapsed) / 1000.0;
return newAngle %= 360;
}
[效果:3.1 平行光下漫反射的缺陷](#3.1 平行光下漫反射的缺陷)

代码3-LightedCube_ambient

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
//LightedCube_ambient.js
var VSHADER_SOURCE =
'attribute vec4 a_Position;\n' +
'attribute vec4 a_Color;\n' + //表面基底色
'attribute vec4 a_Normal;\n' + //法向量
'uniform mat4 u_MvpMatrix;\n' +
'uniform vec3 u_LightColor;\n' + //光线颜色
'uniform vec3 u_LightDirection;\n' + //归一化的世界坐标(入射光方向)
'uniform vec3 u_AmbientLight;\n' + //环境光颜色
'varying vec4 v_Color;\n' +
'void main() {\n' +
' gl_Position = u_MvpMatrix * a_Position;\n' +
' vec3 normal = normalize(vec3(a_Normal));\n' + //对法向量进行归一化
' float nDotL = max(dot(u_LightDirection, normal), 0.0);\n' + //计算光线方向和法向量的点积
' vec3 diffuse = u_LightColor * vec3(a_Color) * nDotL;\n' + //计算漫反射光的颜色
' vec3 ambient = u_AmbientLight * vec3(a_Color);\n' + //计算环境光产生的反射光颜色
' v_Color = vec4(diffuse + ambient, a_Color.a);\n' +
'}\n';

var FSHADER_SOURCE=
'#ifdef GL_ES\n' +
'precision mediump float;\n' +
'#endif\n' +
'varying vec4 v_Color;\n' +
'void main(){\n'+
' gl_FragColor = v_Color;\n'+
'}\n';

function main() {

var canvas = document.getElementById("webgl");
if (!canvas) {
console.log("Failed to retrieve the <canvas> element");
return;
}

var gl = getWebGLContext(canvas);
if (!gl) {
console.log("Failed to get the rendering context for WebGL");
return;
}

if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
console.log("Failed to initialize shaders.");
return;
}

//设置顶点位置
var n = initVertexBuffers(gl);
if (n < 0) {
console.log('Failed to set the positions of the vertices');
return;
}

// Set the clear color and enable the depth test
// gl.clearColor(0, 0, 0, 1);
gl.enable(gl.DEPTH_TEST);


var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');//模型视图投影矩阵
var u_LightColor = gl.getUniformLocation(gl.program, 'u_LightColor');
var u_LightDirection = gl.getUniformLocation(gl.program, 'u_LightDirection');
var u_AmbientLight = gl.getUniformLocation(gl.program, 'u_AmbientLight');
if (!u_MvpMatrix || !u_LightColor || !u_LightDirection || !u_AmbientLight) {
console.log('Failed to get the storage location');
return;
}

gl.uniform3f(u_LightColor, 1.0, 1.0, 1.0); //设置光线颜色为白色
gl.uniform3f(u_AmbientLight, 0.2, 0.2, 0.2); //设置环境光颜色

var lightDirection = new Vector3([0.5, 3.0, 4.0]);//设置光线方向(世界坐标系下)
lightDirection.normalize();//归一化
gl.uniform3fv(u_LightDirection, lightDirection.elements);

var mvpMatrix = new Matrix4();
mvpMatrix.setPerspective(30, canvas.width/canvas.height, 1 ,100);
mvpMatrix.lookAt(3, 3, 7, 0, 0, 0, 0, 1, 0);
gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);

gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);
}

function initVertexBuffers(gl) {
// Create a cube
// v6----- v5
// /| /|
// v1------v0|
// | | | |
// | |v7---|-|v4
// |/ |/
// v2------v3
var vertices = new Float32Array([ // Vertex coordinates
1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0,-1.0, 1.0, 1.0,-1.0, 1.0, // v0-v1-v2-v3 front
1.0, 1.0, 1.0, 1.0,-1.0, 1.0, 1.0,-1.0,-1.0, 1.0, 1.0,-1.0, // v0-v3-v4-v5 right
1.0, 1.0, 1.0, 1.0, 1.0,-1.0, -1.0, 1.0,-1.0, -1.0, 1.0, 1.0, // v0-v5-v6-v1 up
-1.0, 1.0, 1.0, -1.0, 1.0,-1.0, -1.0,-1.0,-1.0, -1.0,-1.0, 1.0, // v1-v6-v7-v2 left
-1.0,-1.0,-1.0, 1.0,-1.0,-1.0, 1.0,-1.0, 1.0, -1.0,-1.0, 1.0, // v7-v4-v3-v2 down
1.0,-1.0,-1.0, -1.0,-1.0,-1.0, -1.0, 1.0,-1.0, 1.0, 1.0,-1.0 // v4-v7-v6-v5 back
]);

var colors = new Float32Array([ // Colors
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v0-v1-v2-v3 front
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v0-v3-v4-v5 right
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v0-v5-v6-v1 up
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v1-v6-v7-v2 left
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // v7-v4-v3-v2 down
1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0  // v4-v7-v6-v5 back
]);

var normals = new Float32Array([ // Normal
0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, // v0-v1-v2-v3 front
1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, // v0-v3-v4-v5 right
0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, // v0-v5-v6-v1 up
-1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, // v1-v6-v7-v2 left
0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, // v7-v4-v3-v2 down
0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0 // v4-v7-v6-v5 back
]);

var indices = new Uint8Array([ // Indices of the vertices
0, 1, 2, 0, 2, 3, // front
4, 5, 6, 4, 6, 7, // right
8, 9,10, 8,10,11, // up
12,13,14, 12,14,15, // left
16,17,18, 16,18,19, // down
20,21,22, 20,22,23 // back
]);

// Write the vertex coordinates and color to the buffer object
if (!initArrayBuffer(gl, vertices, 3, gl.FLOAT, 'a_Position'))
return -1;

if (!initArrayBuffer(gl, colors, 3, gl.FLOAT, 'a_Color'))
return -1;

if (!initArrayBuffer(gl, normals, 3, gl.FLOAT, 'a_Normal'))
return -1;

// Create a buffer object
var indexBuffer = gl.createBuffer();
if (!indexBuffer) {
console.log('Failed to create the buffer object');
return false;
}

// Write the indices to the buffer object
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);

return indices.length;
}

function initArrayBuffer(gl, data, num, type, attribute) {
var buffer = gl.createBuffer(); // Create a buffer object
if (!buffer) {
console.log('Failed to create the buffer object');
return false;
}
// Write date into the buffer object
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
// Assign the buffer object to the attribute variable
var a_attribute = gl.getAttribLocation(gl.program, attribute);
if (a_attribute < 0) {
console.log('Failed to get the storage location of ' + attribute);
return false;
}
gl.vertexAttribPointer(a_attribute, num, type, false, 0, 0);
// Enable the assignment of the buffer object to the attribute variable
gl.enableVertexAttribArray(a_attribute);

gl.bindBuffer(gl.ARRAY_BUFFER, null);

return true;
}

效果


Tips: Please indicate the source and original author when reprinting or quoting this article.