在我们的日常的生活的中看到的物体在视觉上并不是前后大小一样;至少他应该是前大后小的模式;也就是我们之前的所看到的正视投影应该不符合肉眼的观察习惯的;

想要实现近大远小的效果, 简单的做法就是将裁减空间中的 X 和 Y 值除以 Z 值。
你可以这么想:如果一个线段是 (10, 15) 到 (20,15), 它长度为十个单位,在当前的代码中它就是 10 个像素长, 但是如果我们将它除以 Z ,且 Z 值 为 1

10 / 1 = 10 20 / 1 = 20 abs(10-20) = 10 它将是 10 个像素长
10 / 2 = 5 20 / 2 = 10 abs(5-10) = 5 它将是 5 个像素长
10 / 3 = 3.333 20 / 3 = 6.666 abs(3.333 - 6.666) = 3.333 它将是 3.333 个像素长

一. fudgeFactor

你可以看出随着 Z 变大距离就变远了,画的也会小一点。 如果我们除以裁剪空间中的 Z ,值可能会变大,因为 Z 是一个较小的值(-1 到 +1)。但是我们可以提供一个 fudgeFactor 因子和 Z 相乘,这样就可以调整缩放的程度。
让我们来试试,首先修改顶点着色器,除以 Z 再乘以我们的 “fudgeFactor” 因子。

  1. <script id="vertex-shader-3d" type="x-shader/x-vertex">
  2. ...
  3. uniform float u_fudgeFactor;
  4. ...
  5. void main() {
  6. // 将位置和矩阵相乘
  7. vec4 position = u_matrix * a_position;
  8. // 调整除数
  9. float zToDivideBy = 1.0 + position.z * u_fudgeFactor;
  10. // x 和 y 除以调整后的除数
  11. gl_Position = vec4(position.xy / zToDivideBy, position.zw);
  12. }
  13. </script>

注意,由于裁减空间中的 Z 值是 -1 到 +1 的,所以 +1 是为了让 zToDivideBy 变成 0 到 +2 * fudgeFactor
还需要更新代码以设置 fudgeFactor。

  1. ...
  2. var fudgeLocation = gl.getUniformLocation(program, "u_fudgeFactor");
  3. ...
  4. var fudgeFactor = 1;
  5. ...
  6. function drawScene() {
  7. ...
  8. // 设置 fudgeFactor
  9. gl.uniform1f(fudgeLocation, fudgeFactor);
  10. // 绘制几何体
  11. var primitiveType = gl.TRIANGLES;
  12. var offset = 0;
  13. var count = 16 * 6;
  14. gl.drawArrays(primitiveType, offset, count);

image.png
事实上WebGL会将我们提供给 gl_Position 的 x,y,z,w 值自动除以 w 。
我们可以通过修改着色器来证明,用 zToDivideBy 代替 gl_Position.w

  1. ...
  2. var fudgeLocation = gl.getUniformLocation(program, "u_fudgeFactor");
  3. ...
  4. var fudgeFactor = 1;
  5. ...
  6. function drawScene() {
  7. ...
  8. // 设置 fudgeFactor
  9. gl.uniform1f(fudgeLocation, fudgeFactor);
  10. // 绘制几何体
  11. var primitiveType = gl.TRIANGLES;
  12. var offset = 0;
  13. var count = 16 * 6;
  14. gl.drawArrays(primitiveType, offset, count);

有惊人的同样的效果

二. 矩阵隐藏着线性代数

为什么WebGL会自动除以 W ?

因为使用矩阵的魔力,可以用把值从 z 传值到 w 。
一个这样的矩阵
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 1,
0, 0, 0, 0,
将会把 z 的值复制给 w , 你可以把每列看作
x_out = x_in 1 + y_in 0 + z_in 0 + w_in 0 ;
y_out = x_in 0 + y_in 1 + z_in 0 + w_in 0 ;
z_out = x_in 0 + y_in 0 + z_in 1 + w_in 0 ;
w_out = x_in 0 + y_in 0 + z_in 1 + w_in 0 ;
简化后得到
x_out = x_in;
y_out = y_in;
z_out = z_in;
w_out = z_in;
如果 w 原来就是 1.0 就会加 1
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 1,
0, 0, 0, 1,
他会将 W 的运算变为
w_out = x_in 0 + y_in 0 + z_in 1 + w_in 1 ;
因为 w_in = 1.0 是已知的
w_out = z_in + 1;
最后可以将 fudgeFactor 像这样放入矩阵中
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, fudgeFactor,
0, 0, 0, 1,
相当于
w_out = x_in 0 +y_in 0 +z_in fudgeFactor + w_in 1 ;
简化后为
w_out = z_in * fudgeFactor + 1;
我们来修改代码,使用这个矩阵。
首先将顶点着色器还原,又变成简单的样子

  1. <script id="vertex-shader-2d" type="x-shader/x-vertex">
  2. uniform mat4 u_matrix;
  3. void main() {
  4. // 位置和矩阵相乘
  5. gl_Position = u_matrix * a_position;
  6. ...
  7. }
  8. </script>
  9. //
  10. function makeZToWMatrix(fudgeFactor) {
  11. return [
  12. 1, 0, 0, 0,
  13. 0, 1, 0, 0,
  14. 0, 0, 1, fudgeFactor,
  15. 0, 0, 0, 1,
  16. ];
  17. }
  18. ...
  19. // 计算矩阵
  20. var matrix = makeZToWMatrix(fudgeFactor);
  21. matrix = m4.multiply(matrix, m4.projection(gl.canvas.clientWidth, gl.canvas.clientHeight, 400));
  22. matrix = m4.translate(matrix, translation[0], translation[1], translation[2]);
  23. matrix = m4.xRotate(matrix, rotation[0]);
  24. matrix = m4.yRotate(matrix, rotation[1]);
  25. matrix = m4.zRotate(matrix, rotation[2]);
  26. matrix = m4.scale(matrix, scale[0], scale[1], scale[2]);
  27. ...

这只是展示了除以 Z 值获可以实现透视投影,以及在WebGL中简单实现。

三. Z轴的空间的裁剪

但还有一些问题需要解决,比如将 Z 值设置为 -100 左右的时候会遇到下面的情形
23.gif

为什么会这样?为什么 F 提前消失了?WebGL裁剪空间中的 X 和 Y 会被 +1 和 -1 裁剪, Z也一样。我们看到的是 Z < -1 的情况。
我可以从数学方法深入探讨并寻找解决办法,但是你可以 联想 二维中的的解决方法。我们需要获取 Z 值,然后加上一些量, 缩放一些量,就可以将任意范围映射到 -1 到 +1 的范围内。
最有意思的是这件事可以在一个矩阵中完成,更方便的是, 我们可以定义一个 fieldOfView 代替 fudgeFactor , 计算出更合适的值。
这是创建矩阵的方法。

  1. var m4 = {
  2. perspective: function(fieldOfViewInRadians, aspect, near, far) {
  3. var f = Math.tan(Math.PI * 0.5 - 0.5 * fieldOfViewInRadians);
  4. var rangeInv = 1.0 / (near - far);
  5. return [
  6. f / aspect, 0, 0, 0,
  7. 0, f, 0, 0,
  8. 0, 0, (near + far) * rangeInv, -1,
  9. 0, 0, near * far * rangeInv * 2, 0
  10. ];
  11. },
  12. ...

这个矩阵会为我们完成所有转换。它可以调整单位以适应裁剪空间, 它可以自定义视场角,选择 Z-裁剪面。假设有一个眼睛或者摄像机 在原点(0, 0, 0),根据 zNear 和 fieldOfView 可以将 zNear 对应到 Z = -1 ,在 zNear 平面上一半的 fieldOfView 长度 对应画布中心到 Y = -1 或 Y = 1 的距离,X 的值通过乘以 aspect 获取,最后通过设置 zFar 对应 Z = 1 ,控制缩放的程度。
操作实景
image.png
正方体所在的有四个侧面的椎体叫做“视锥”,矩阵将视锥中的空间转换到裁剪空间中, zNear 决定了被正面切割的位置,zFar 决定被背面切割的位置。 将 zNear 设置为 23 就会看到正方体正面被切割, 将 zFar 设置为 24 就会看到正方体背面被切割。

还有一个问题,矩阵假定观察位置为 0,0,0 并且看向 Z 轴负方向, Y 轴为上方向。这和我们目前为止做法不同, 为了解决这个问题我们需要将物体放到视图范围内。
我们在 (45, 150, 0) 绘制的 F,可以将它移动到 (-150, 0, -360)
使用 m4.projection 方法代替之前的投影方法,可以调用 m4.perspective

  1. var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
  2. var zNear = 1;
  3. var zFar = 2000;
  4. var matrix = m4.perspective(fieldOfViewRadians, aspect, zNear, zFar);
  5. matrix = m4.translate(matrix, translation[0], translation[1], translation[2]);
  6. matrix = m4.xRotate(matrix, rotation[0]);
  7. matrix = m4.yRotate(matrix, rotation[1]);
  8. matrix = m4.zRotate(matrix, rotation[2]);
  9. matrix = m4.scale(matrix, scale[0], scale[1], scale[2]);

这一块的基本内容基本上以后webgl中关于3d都会涉及到;

四. 终极代码

  1. "use strict";
  2. function main() {
  3. // Get A WebGL context
  4. /** @type {HTMLCanvasElement} */
  5. var canvas = document.querySelector("#canvas");
  6. var gl = canvas.getContext("webgl");
  7. if (!gl) {
  8. return;
  9. }
  10. // setup GLSL program
  11. var program = webglUtils.createProgramFromScripts(gl, ["vertex-shader-3d", "fragment-shader-3d"]);
  12. // look up where the vertex data needs to go.
  13. var positionLocation = gl.getAttribLocation(program, "a_position");
  14. var colorLocation = gl.getAttribLocation(program, "a_color");
  15. // lookup uniforms
  16. var matrixLocation = gl.getUniformLocation(program, "u_matrix");
  17. // Create a buffer to put positions in
  18. var positionBuffer = gl.createBuffer();
  19. // Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
  20. gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  21. // Put geometry data into buffer
  22. setGeometry(gl);
  23. // Create a buffer to put colors in
  24. var colorBuffer = gl.createBuffer();
  25. // Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = colorBuffer)
  26. gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
  27. // Put geometry data into buffer
  28. setColors(gl);
  29. function radToDeg(r) {
  30. return r * 180 / Math.PI;
  31. }
  32. function degToRad(d) {
  33. return d * Math.PI / 180;
  34. }
  35. var translation = [-150, 0, -360];
  36. var rotation = [degToRad(190), degToRad(40), degToRad(320)];
  37. var scale = [1, 1, 1];
  38. var fieldOfViewRadians = degToRad(60);
  39. drawScene();
  40. // Setup a ui.
  41. webglLessonsUI.setupSlider("#fieldOfView", {value: radToDeg(fieldOfViewRadians), slide: updateFieldOfView, min: 1, max: 179});
  42. webglLessonsUI.setupSlider("#x", {value: translation[0], slide: updatePosition(0), min: -200, max: 200 });
  43. webglLessonsUI.setupSlider("#y", {value: translation[1], slide: updatePosition(1), min: -200, max: 200});
  44. webglLessonsUI.setupSlider("#z", {value: translation[2], slide: updatePosition(2), min: -1000, max: 0});
  45. webglLessonsUI.setupSlider("#angleX", {value: radToDeg(rotation[0]), slide: updateRotation(0), max: 360});
  46. webglLessonsUI.setupSlider("#angleY", {value: radToDeg(rotation[1]), slide: updateRotation(1), max: 360});
  47. webglLessonsUI.setupSlider("#angleZ", {value: radToDeg(rotation[2]), slide: updateRotation(2), max: 360});
  48. webglLessonsUI.setupSlider("#scaleX", {value: scale[0], slide: updateScale(0), min: -5, max: 5, step: 0.01, precision: 2});
  49. webglLessonsUI.setupSlider("#scaleY", {value: scale[1], slide: updateScale(1), min: -5, max: 5, step: 0.01, precision: 2});
  50. webglLessonsUI.setupSlider("#scaleZ", {value: scale[2], slide: updateScale(2), min: -5, max: 5, step: 0.01, precision: 2});
  51. function updateFieldOfView(event, ui) {
  52. fieldOfViewRadians = degToRad(ui.value);
  53. drawScene();
  54. }
  55. function updatePosition(index) {
  56. return function(event, ui) {
  57. translation[index] = ui.value;
  58. drawScene();
  59. };
  60. }
  61. function updateRotation(index) {
  62. return function(event, ui) {
  63. var angleInDegrees = ui.value;
  64. var angleInRadians = angleInDegrees * Math.PI / 180;
  65. rotation[index] = angleInRadians;
  66. drawScene();
  67. };
  68. }
  69. function updateScale(index) {
  70. return function(event, ui) {
  71. scale[index] = ui.value;
  72. drawScene();
  73. };
  74. }
  75. // Draw the scene.
  76. function drawScene() {
  77. webglUtils.resizeCanvasToDisplaySize(gl.canvas);
  78. // Tell WebGL how to convert from clip space to pixels
  79. gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
  80. // Clear the canvas AND the depth buffer.
  81. gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  82. // Turn on culling. By default backfacing triangles
  83. // will be culled.
  84. gl.enable(gl.CULL_FACE);
  85. // Enable the depth buffer
  86. gl.enable(gl.DEPTH_TEST);
  87. // Tell it to use our program (pair of shaders)
  88. gl.useProgram(program);
  89. // Turn on the position attribute
  90. gl.enableVertexAttribArray(positionLocation);
  91. // Bind the position buffer.
  92. gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  93. // Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
  94. var size = 3; // 3 components per iteration
  95. var type = gl.FLOAT; // the data is 32bit floats
  96. var normalize = false; // don't normalize the data
  97. var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
  98. var offset = 0; // start at the beginning of the buffer
  99. gl.vertexAttribPointer(
  100. positionLocation, size, type, normalize, stride, offset);
  101. // Turn on the color attribute
  102. gl.enableVertexAttribArray(colorLocation);
  103. // Bind the color buffer.
  104. gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
  105. // Tell the attribute how to get data out of colorBuffer (ARRAY_BUFFER)
  106. var size = 3; // 3 components per iteration
  107. var type = gl.UNSIGNED_BYTE; // the data is 8bit unsigned values
  108. var normalize = true; // normalize the data (convert from 0-255 to 0-1)
  109. var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
  110. var offset = 0; // start at the beginning of the buffer
  111. gl.vertexAttribPointer(
  112. colorLocation, size, type, normalize, stride, offset);
  113. // Compute the matrix
  114. var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
  115. var zNear = 1;
  116. var zFar = 2000;
  117. var matrix = m4.perspective(fieldOfViewRadians, aspect, zNear, zFar);
  118. matrix = m4.translate(matrix, translation[0], translation[1], translation[2]);
  119. matrix = m4.xRotate(matrix, rotation[0]);
  120. matrix = m4.yRotate(matrix, rotation[1]);
  121. matrix = m4.zRotate(matrix, rotation[2]);
  122. matrix = m4.scale(matrix, scale[0], scale[1], scale[2]);
  123. // Set the matrix.
  124. gl.uniformMatrix4fv(matrixLocation, false, matrix);
  125. // Draw the geometry.
  126. var primitiveType = gl.TRIANGLES;
  127. var offset = 0;
  128. var count = 16 * 6;
  129. gl.drawArrays(primitiveType, offset, count);
  130. }
  131. }
  132. var m4 = {
  133. perspective: function(fieldOfViewInRadians, aspect, near, far) {
  134. var f = Math.tan(Math.PI * 0.5 - 0.5 * fieldOfViewInRadians);
  135. var rangeInv = 1.0 / (near - far);
  136. return [
  137. f / aspect, 0, 0, 0,
  138. 0, f, 0, 0,
  139. 0, 0, (near + far) * rangeInv, -1,
  140. 0, 0, near * far * rangeInv * 2, 0
  141. ];
  142. },
  143. projection: function(width, height, depth) {
  144. // Note: This matrix flips the Y axis so 0 is at the top.
  145. return [
  146. 2 / width, 0, 0, 0,
  147. 0, -2 / height, 0, 0,
  148. 0, 0, 2 / depth, 0,
  149. -1, 1, 0, 1,
  150. ];
  151. },
  152. multiply: function(a, b) {
  153. var a00 = a[0 * 4 + 0];
  154. var a01 = a[0 * 4 + 1];
  155. var a02 = a[0 * 4 + 2];
  156. var a03 = a[0 * 4 + 3];
  157. var a10 = a[1 * 4 + 0];
  158. var a11 = a[1 * 4 + 1];
  159. var a12 = a[1 * 4 + 2];
  160. var a13 = a[1 * 4 + 3];
  161. var a20 = a[2 * 4 + 0];
  162. var a21 = a[2 * 4 + 1];
  163. var a22 = a[2 * 4 + 2];
  164. var a23 = a[2 * 4 + 3];
  165. var a30 = a[3 * 4 + 0];
  166. var a31 = a[3 * 4 + 1];
  167. var a32 = a[3 * 4 + 2];
  168. var a33 = a[3 * 4 + 3];
  169. var b00 = b[0 * 4 + 0];
  170. var b01 = b[0 * 4 + 1];
  171. var b02 = b[0 * 4 + 2];
  172. var b03 = b[0 * 4 + 3];
  173. var b10 = b[1 * 4 + 0];
  174. var b11 = b[1 * 4 + 1];
  175. var b12 = b[1 * 4 + 2];
  176. var b13 = b[1 * 4 + 3];
  177. var b20 = b[2 * 4 + 0];
  178. var b21 = b[2 * 4 + 1];
  179. var b22 = b[2 * 4 + 2];
  180. var b23 = b[2 * 4 + 3];
  181. var b30 = b[3 * 4 + 0];
  182. var b31 = b[3 * 4 + 1];
  183. var b32 = b[3 * 4 + 2];
  184. var b33 = b[3 * 4 + 3];
  185. return [
  186. b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30,
  187. b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31,
  188. b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32,
  189. b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33,
  190. b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30,
  191. b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31,
  192. b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32,
  193. b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33,
  194. b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30,
  195. b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31,
  196. b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32,
  197. b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33,
  198. b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30,
  199. b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31,
  200. b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32,
  201. b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33,
  202. ];
  203. },
  204. translation: function(tx, ty, tz) {
  205. return [
  206. 1, 0, 0, 0,
  207. 0, 1, 0, 0,
  208. 0, 0, 1, 0,
  209. tx, ty, tz, 1,
  210. ];
  211. },
  212. xRotation: function(angleInRadians) {
  213. var c = Math.cos(angleInRadians);
  214. var s = Math.sin(angleInRadians);
  215. return [
  216. 1, 0, 0, 0,
  217. 0, c, s, 0,
  218. 0, -s, c, 0,
  219. 0, 0, 0, 1,
  220. ];
  221. },
  222. yRotation: function(angleInRadians) {
  223. var c = Math.cos(angleInRadians);
  224. var s = Math.sin(angleInRadians);
  225. return [
  226. c, 0, -s, 0,
  227. 0, 1, 0, 0,
  228. s, 0, c, 0,
  229. 0, 0, 0, 1,
  230. ];
  231. },
  232. zRotation: function(angleInRadians) {
  233. var c = Math.cos(angleInRadians);
  234. var s = Math.sin(angleInRadians);
  235. return [
  236. c, s, 0, 0,
  237. -s, c, 0, 0,
  238. 0, 0, 1, 0,
  239. 0, 0, 0, 1,
  240. ];
  241. },
  242. scaling: function(sx, sy, sz) {
  243. return [
  244. sx, 0, 0, 0,
  245. 0, sy, 0, 0,
  246. 0, 0, sz, 0,
  247. 0, 0, 0, 1,
  248. ];
  249. },
  250. translate: function(m, tx, ty, tz) {
  251. return m4.multiply(m, m4.translation(tx, ty, tz));
  252. },
  253. xRotate: function(m, angleInRadians) {
  254. return m4.multiply(m, m4.xRotation(angleInRadians));
  255. },
  256. yRotate: function(m, angleInRadians) {
  257. return m4.multiply(m, m4.yRotation(angleInRadians));
  258. },
  259. zRotate: function(m, angleInRadians) {
  260. return m4.multiply(m, m4.zRotation(angleInRadians));
  261. },
  262. scale: function(m, sx, sy, sz) {
  263. return m4.multiply(m, m4.scaling(sx, sy, sz));
  264. },
  265. };
  266. // Fill the buffer with the values that define a letter 'F'.
  267. function setGeometry(gl) {
  268. gl.bufferData(
  269. gl.ARRAY_BUFFER,
  270. new Float32Array([
  271. // left column front
  272. 0, 0, 0,
  273. 0, 150, 0,
  274. 30, 0, 0,
  275. 0, 150, 0,
  276. 30, 150, 0,
  277. 30, 0, 0,
  278. // top rung front
  279. 30, 0, 0,
  280. 30, 30, 0,
  281. 100, 0, 0,
  282. 30, 30, 0,
  283. 100, 30, 0,
  284. 100, 0, 0,
  285. // middle rung front
  286. 30, 60, 0,
  287. 30, 90, 0,
  288. 67, 60, 0,
  289. 30, 90, 0,
  290. 67, 90, 0,
  291. 67, 60, 0,
  292. // left column back
  293. 0, 0, 30,
  294. 30, 0, 30,
  295. 0, 150, 30,
  296. 0, 150, 30,
  297. 30, 0, 30,
  298. 30, 150, 30,
  299. // top rung back
  300. 30, 0, 30,
  301. 100, 0, 30,
  302. 30, 30, 30,
  303. 30, 30, 30,
  304. 100, 0, 30,
  305. 100, 30, 30,
  306. // middle rung back
  307. 30, 60, 30,
  308. 67, 60, 30,
  309. 30, 90, 30,
  310. 30, 90, 30,
  311. 67, 60, 30,
  312. 67, 90, 30,
  313. // top
  314. 0, 0, 0,
  315. 100, 0, 0,
  316. 100, 0, 30,
  317. 0, 0, 0,
  318. 100, 0, 30,
  319. 0, 0, 30,
  320. // top rung right
  321. 100, 0, 0,
  322. 100, 30, 0,
  323. 100, 30, 30,
  324. 100, 0, 0,
  325. 100, 30, 30,
  326. 100, 0, 30,
  327. // under top rung
  328. 30, 30, 0,
  329. 30, 30, 30,
  330. 100, 30, 30,
  331. 30, 30, 0,
  332. 100, 30, 30,
  333. 100, 30, 0,
  334. // between top rung and middle
  335. 30, 30, 0,
  336. 30, 60, 30,
  337. 30, 30, 30,
  338. 30, 30, 0,
  339. 30, 60, 0,
  340. 30, 60, 30,
  341. // top of middle rung
  342. 30, 60, 0,
  343. 67, 60, 30,
  344. 30, 60, 30,
  345. 30, 60, 0,
  346. 67, 60, 0,
  347. 67, 60, 30,
  348. // right of middle rung
  349. 67, 60, 0,
  350. 67, 90, 30,
  351. 67, 60, 30,
  352. 67, 60, 0,
  353. 67, 90, 0,
  354. 67, 90, 30,
  355. // bottom of middle rung.
  356. 30, 90, 0,
  357. 30, 90, 30,
  358. 67, 90, 30,
  359. 30, 90, 0,
  360. 67, 90, 30,
  361. 67, 90, 0,
  362. // right of bottom
  363. 30, 90, 0,
  364. 30, 150, 30,
  365. 30, 90, 30,
  366. 30, 90, 0,
  367. 30, 150, 0,
  368. 30, 150, 30,
  369. // bottom
  370. 0, 150, 0,
  371. 0, 150, 30,
  372. 30, 150, 30,
  373. 0, 150, 0,
  374. 30, 150, 30,
  375. 30, 150, 0,
  376. // left side
  377. 0, 0, 0,
  378. 0, 0, 30,
  379. 0, 150, 30,
  380. 0, 0, 0,
  381. 0, 150, 30,
  382. 0, 150, 0]),
  383. gl.STATIC_DRAW);
  384. }
  385. // Fill the buffer with colors for the 'F'.
  386. function setColors(gl) {
  387. gl.bufferData(
  388. gl.ARRAY_BUFFER,
  389. new Uint8Array([
  390. // left column front
  391. 200, 70, 120,
  392. 200, 70, 120,
  393. 200, 70, 120,
  394. 200, 70, 120,
  395. 200, 70, 120,
  396. 200, 70, 120,
  397. // top rung front
  398. 200, 70, 120,
  399. 200, 70, 120,
  400. 200, 70, 120,
  401. 200, 70, 120,
  402. 200, 70, 120,
  403. 200, 70, 120,
  404. // middle rung front
  405. 200, 70, 120,
  406. 200, 70, 120,
  407. 200, 70, 120,
  408. 200, 70, 120,
  409. 200, 70, 120,
  410. 200, 70, 120,
  411. // left column back
  412. 80, 70, 200,
  413. 80, 70, 200,
  414. 80, 70, 200,
  415. 80, 70, 200,
  416. 80, 70, 200,
  417. 80, 70, 200,
  418. // top rung back
  419. 80, 70, 200,
  420. 80, 70, 200,
  421. 80, 70, 200,
  422. 80, 70, 200,
  423. 80, 70, 200,
  424. 80, 70, 200,
  425. // middle rung back
  426. 80, 70, 200,
  427. 80, 70, 200,
  428. 80, 70, 200,
  429. 80, 70, 200,
  430. 80, 70, 200,
  431. 80, 70, 200,
  432. // top
  433. 70, 200, 210,
  434. 70, 200, 210,
  435. 70, 200, 210,
  436. 70, 200, 210,
  437. 70, 200, 210,
  438. 70, 200, 210,
  439. // top rung right
  440. 200, 200, 70,
  441. 200, 200, 70,
  442. 200, 200, 70,
  443. 200, 200, 70,
  444. 200, 200, 70,
  445. 200, 200, 70,
  446. // under top rung
  447. 210, 100, 70,
  448. 210, 100, 70,
  449. 210, 100, 70,
  450. 210, 100, 70,
  451. 210, 100, 70,
  452. 210, 100, 70,
  453. // between top rung and middle
  454. 210, 160, 70,
  455. 210, 160, 70,
  456. 210, 160, 70,
  457. 210, 160, 70,
  458. 210, 160, 70,
  459. 210, 160, 70,
  460. // top of middle rung
  461. 70, 180, 210,
  462. 70, 180, 210,
  463. 70, 180, 210,
  464. 70, 180, 210,
  465. 70, 180, 210,
  466. 70, 180, 210,
  467. // right of middle rung
  468. 100, 70, 210,
  469. 100, 70, 210,
  470. 100, 70, 210,
  471. 100, 70, 210,
  472. 100, 70, 210,
  473. 100, 70, 210,
  474. // bottom of middle rung.
  475. 76, 210, 100,
  476. 76, 210, 100,
  477. 76, 210, 100,
  478. 76, 210, 100,
  479. 76, 210, 100,
  480. 76, 210, 100,
  481. // right of bottom
  482. 140, 210, 80,
  483. 140, 210, 80,
  484. 140, 210, 80,
  485. 140, 210, 80,
  486. 140, 210, 80,
  487. 140, 210, 80,
  488. // bottom
  489. 90, 130, 110,
  490. 90, 130, 110,
  491. 90, 130, 110,
  492. 90, 130, 110,
  493. 90, 130, 110,
  494. 90, 130, 110,
  495. // left side
  496. 160, 160, 220,
  497. 160, 160, 220,
  498. 160, 160, 220,
  499. 160, 160, 220,
  500. 160, 160, 220,
  501. 160, 160, 220]),
  502. gl.STATIC_DRAW);
  503. }
  504. main();
  1. <canvas id="canvas"></canvas>
  2. <div id="uiContainer">
  3. <div id="ui">
  4. <div id="fieldOfView"></div>
  5. <div id="x"></div>
  6. <div id="y"></div>
  7. <div id="z"></div>
  8. <div id="angleX"></div>
  9. <div id="angleY"></div>
  10. <div id="angleZ"></div>
  11. </div>
  12. </div>
  13. <!-- vertex shader -->
  14. <script id="vertex-shader-3d" type="x-shader/x-vertex">
  15. attribute vec4 a_position;
  16. attribute vec4 a_color;
  17. uniform mat4 u_matrix;
  18. varying vec4 v_color;
  19. void main() {
  20. // Multiply the position by the matrix.
  21. gl_Position = u_matrix * a_position;
  22. // Pass the color to the fragment shader.
  23. v_color = a_color;
  24. }
  25. </script>
  26. <!-- fragment shader -->
  27. <script id="fragment-shader-3d" type="x-shader/x-fragment">
  28. precision mediump float;
  29. // Passed in from the vertex shader.
  30. varying vec4 v_color;
  31. void main() {
  32. gl_FragColor = v_color;
  33. }
  34. </script>
  35. <script src="https://webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
  36. <script src="https://webglfundamentals.org/webgl/resources/webgl-lessons-ui.js"></script>