1. 概述

在之前的教程中,都是通过物体的包围盒来设置模型视图投影矩阵(MVP矩阵),来确定物体合适的位置的。但是在很多情况下,使用包围盒并不方便计算,可以利用包围盒再生成一个包围球,利用包围球来设置MVP矩阵。

《WebGL简易教程(十):光照》中,给地形赋予了固定方向的平行光。这篇教程的例子就是想模拟在平行光的视角下地形的情况。对于点光源光,可以用透视投影来实现渲染的效果;而平行光就需要通过正射投影来模拟。并且,这种正射并不是垂直到达地面,而是附带一定角度[1]

在这种情况下使用包围盒来计算合适的位置有点难度,使用包围球来设置MVP矩阵更加方便。

2. 实现详解

包围球是利用包围盒生成的,所以首先需要定义一个球体对象:

  1. //定义一个球体
  2. function Sphere(cuboid) {
  3. this.centerX = cuboid.CenterX();
  4. this.centerY = cuboid.CenterY();
  5. this.centerZ = cuboid.CenterZ();
  6. this.radius = Math.max(Math.max(cuboid.LengthX(), cuboid.LengthY()), cuboid.LengthZ()) / 2.0;
  7. }
  8. Sphere.prototype = {
  9. constructor: Sphere
  10. }

这个球体对象的构造函数传入了一个包围盒对象,以包围盒的中心为球体的中心,包围盒长、宽、高的最大值作为包围球的直径。在构造出包围盒之后,利用包围盒参数构造出包围球,将其保存在自定义的Terrain对象中:

  1. var terrain = new Terrain();
  2. //....
  3. terrain.cuboid = new Cuboid(minX, maxX, minY, maxY, minZ, maxZ);
  4. terrain.sphere = new Sphere(terrain.cuboid);

接下来就是改进设置MVP矩阵的函数setMVPMatrix()了。如果仍然想像之前那样进行透视投影,几乎可以不用做改动:

  1. //设置MVP矩阵
  2. function setMVPMatrix(gl, canvas, sphere, lightDirection) {
  3. //...
  4. //投影矩阵
  5. var fovy = 60;
  6. var projMatrix = new Matrix4();
  7. projMatrix.setPerspective(fovy, canvas.width / canvas.height, 1, 10000);
  8. //计算lookAt()函数初始视点的高度
  9. var angle = fovy / 2 * Math.PI / 180.0;
  10. var eyeHight = (sphere.radius * 2 * 1.1) / 2.0 / angle;
  11. //视图矩阵
  12. var viewMatrix = new Matrix4(); // View matrix
  13. viewMatrix.lookAt(0, 0, eyeHight, 0, 0, 0, 0, 1, 0);
  14. //...
  15. }

之前是通过透视变换的张角和包围盒的Y方向长度来计算合适的视野高度,现在只不过将包围盒的Y方向长度换成包围球的直径。这样的写法兼容性更高,因为包围球的直径是包围盒XYZ三个方向的最大长度。这个时候的初始渲染状态为:



最后实现下特定角度平行光视角下的地形渲染情况。前面说到过这种情况下是需要设置正射投影的,具体设置过程如下:

  1. //设置MVP矩阵
  2. function setMVPMatrix(gl, canvas, sphere, lightDirection) {
  3. //...
  4. //模型矩阵
  5. var modelMatrix = new Matrix4();
  6. modelMatrix.scale(curScale, curScale, curScale);
  7. modelMatrix.rotate(currentAngle[0], 1.0, 0.0, 0.0); // Rotation around x-axis
  8. modelMatrix.rotate(currentAngle[1], 0.0, 1.0, 0.0); // Rotation around y-axis
  9. modelMatrix.translate(-sphere.centerX, -sphere.centerY, -sphere.centerZ);
  10. //视图矩阵
  11. var viewMatrix = new Matrix4();
  12. var r = sphere.radius + 10;
  13. viewMatrix.lookAt(lightDirection.elements[0] * r, lightDirection.elements[1] * r, lightDirection.elements[2] * r, 0, 0, 0, 0, 1, 0);
  14. //投影矩阵
  15. var projMatrix = new Matrix4();
  16. var diameter = sphere.radius * 2.1;
  17. var ratioWH = canvas.width / canvas.height;
  18. var nearHeight = diameter;
  19. var nearWidth = nearHeight * ratioWH;
  20. projMatrix.setOrtho(-nearWidth / 2, nearWidth / 2, -nearHeight / 2, nearHeight / 2, 1, 10000);
  21. //...
  22. }
  1. 通过模型变换,将世界坐标系的中心平移到包围球的中心。
  2. 设置视图矩阵的时候将观察点放到这个(0,0,0),也就是这个包围球中心;由于视野的方向也就是光线的方向知道,那么可以通过这个方向将视点位置设在与(0,0,0)相距比包围球半径远一点点的位置,就可以保证这个地形都能够被看见。
  3. 通过包围球的直径,来计算正射投影的盒装可视空间的最小范围。

这个时候的初始渲染状态为:

3. 具体代码

具体实现代码如下:

  1. // 顶点着色器程序
  2. var VSHADER_SOURCE =
  3. 'attribute vec4 a_Position;\n' + //位置
  4. 'attribute vec4 a_Color;\n' + //颜色
  5. 'attribute vec4 a_Normal;\n' + //法向量
  6. 'uniform mat4 u_MvpMatrix;\n' +
  7. 'varying vec4 v_Color;\n' +
  8. 'varying vec4 v_Normal;\n' +
  9. 'void main() {\n' +
  10. ' gl_Position = u_MvpMatrix * a_Position;\n' + //设置顶点的坐标
  11. ' v_Color = a_Color;\n' +
  12. ' v_Normal = a_Normal;\n' +
  13. '}\n';
  14. // 片元着色器程序
  15. var FSHADER_SOURCE =
  16. 'precision mediump float;\n' +
  17. 'uniform vec3 u_DiffuseLight;\n' + // 漫反射光颜色
  18. 'uniform vec3 u_LightDirection;\n' + // 漫反射光的方向
  19. 'uniform vec3 u_AmbientLight;\n' + // 环境光颜色
  20. 'varying vec4 v_Color;\n' +
  21. 'varying vec4 v_Normal;\n' +
  22. 'void main() {\n' +
  23. //对法向量归一化
  24. ' vec3 normal = normalize(v_Normal.xyz);\n' +
  25. //计算光线向量与法向量的点积
  26. ' float nDotL = max(dot(u_LightDirection, normal), 0.0);\n' +
  27. //计算漫发射光的颜色
  28. ' vec3 diffuse = u_DiffuseLight * v_Color.rgb * nDotL;\n' +
  29. //计算环境光的颜色
  30. ' vec3 ambient = u_AmbientLight * v_Color.rgb;\n' +
  31. ' gl_FragColor = vec4(diffuse+ambient, v_Color.a);\n' +
  32. '}\n';
  33. //定义一个矩形体:混合构造函数原型模式
  34. function Cuboid(minX, maxX, minY, maxY, minZ, maxZ) {
  35. this.minX = minX;
  36. this.maxX = maxX;
  37. this.minY = minY;
  38. this.maxY = maxY;
  39. this.minZ = minZ;
  40. this.maxZ = maxZ;
  41. }
  42. Cuboid.prototype = {
  43. constructor: Cuboid,
  44. CenterX: function () {
  45. return (this.minX + this.maxX) / 2.0;
  46. },
  47. CenterY: function () {
  48. return (this.minY + this.maxY) / 2.0;
  49. },
  50. CenterZ: function () {
  51. return (this.minZ + this.maxZ) / 2.0;
  52. },
  53. LengthX: function () {
  54. return (this.maxX - this.minX);
  55. },
  56. LengthY: function () {
  57. return (this.maxY - this.minY);
  58. },
  59. LengthZ: function () {
  60. return (this.maxZ - this.minZ);
  61. }
  62. }
  63. //定义一个球体
  64. function Sphere(cuboid) {
  65. this.centerX = cuboid.CenterX();
  66. this.centerY = cuboid.CenterY();
  67. this.centerZ = cuboid.CenterZ();
  68. this.radius = Math.max(Math.max(cuboid.LengthX(), cuboid.LengthY()), cuboid.LengthZ()) / 2.0;
  69. }
  70. Sphere.prototype = {
  71. constructor: Sphere
  72. }
  73. //定义DEM
  74. function Terrain() { }
  75. Terrain.prototype = {
  76. constructor: Terrain,
  77. setWH: function (col, row) {
  78. this.col = col;
  79. this.row = row;
  80. }
  81. }
  82. var currentAngle = [0.0, 0.0]; // 绕X轴Y轴的旋转角度 ([x-axis, y-axis])
  83. var curScale = 1.0; //当前的缩放比例
  84. function main() {
  85. var demFile = document.getElementById('demFile');
  86. if (!demFile) {
  87. console.log("Failed to get demFile element!");
  88. return;
  89. }
  90. demFile.addEventListener("change", function (event) {
  91. //判断浏览器是否支持FileReader接口
  92. if (typeof FileReader == 'undefined') {
  93. console.log("你的浏览器不支持FileReader接口!");
  94. return;
  95. }
  96. var input = event.target;
  97. var reader = new FileReader();
  98. reader.onload = function () {
  99. if (reader.result) {
  100. //读取
  101. var terrain = new Terrain();
  102. if (!readDEMFile(reader.result, terrain)) {
  103. console.log("文件格式有误,不能读取该文件!");
  104. }
  105. //绘制
  106. onDraw(gl, canvas, terrain);
  107. }
  108. }
  109. reader.readAsText(input.files[0]);
  110. });
  111. // 获取 <canvas> 元素
  112. var canvas = document.getElementById('webgl');
  113. // 获取WebGL渲染上下文
  114. var gl = getWebGLContext(canvas);
  115. if (!gl) {
  116. console.log('Failed to get the rendering context for WebGL');
  117. return;
  118. }
  119. // 初始化着色器
  120. if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
  121. console.log('Failed to intialize shaders.');
  122. return;
  123. }
  124. // 指定清空<canvas>的颜色
  125. gl.clearColor(0.0, 0.0, 0.0, 1.0);
  126. // 开启深度测试
  127. gl.enable(gl.DEPTH_TEST);
  128. //清空颜色和深度缓冲区
  129. gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  130. }
  131. //绘制函数
  132. function onDraw(gl, canvas, terrain) {
  133. // 设置顶点位置
  134. var n = initVertexBuffers(gl, terrain);
  135. if (n < 0) {
  136. console.log('Failed to set the positions of the vertices');
  137. return;
  138. }
  139. //注册鼠标事件
  140. initEventHandlers(canvas);
  141. //设置灯光
  142. var lightDirection = setLight(gl);
  143. //绘制函数
  144. var tick = function () {
  145. //设置MVP矩阵
  146. setMVPMatrix(gl, canvas, terrain.sphere, lightDirection);
  147. //清空颜色和深度缓冲区
  148. gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  149. //绘制矩形体
  150. gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_SHORT, 0);
  151. //请求浏览器调用tick
  152. requestAnimationFrame(tick);
  153. };
  154. //开始绘制
  155. tick();
  156. }
  157. //设置灯光
  158. function setLight(gl) {
  159. var u_AmbientLight = gl.getUniformLocation(gl.program, 'u_AmbientLight');
  160. var u_DiffuseLight = gl.getUniformLocation(gl.program, 'u_DiffuseLight');
  161. var u_LightDirection = gl.getUniformLocation(gl.program, 'u_LightDirection');
  162. if (!u_DiffuseLight || !u_LightDirection || !u_AmbientLight) {
  163. console.log('Failed to get the storage location');
  164. return;
  165. }
  166. //设置漫反射光
  167. gl.uniform3f(u_DiffuseLight, 1.0, 1.0, 1.0);
  168. // 设置光线方向(世界坐标系下的)
  169. var solarAltitude = 45.0;
  170. var solarAzimuth = 315.0;
  171. var fAltitude = solarAltitude * Math.PI / 180; //光源高度角
  172. var fAzimuth = solarAzimuth * Math.PI / 180; //光源方位角
  173. var arrayvectorX = Math.cos(fAltitude) * Math.cos(fAzimuth);
  174. var arrayvectorY = Math.cos(fAltitude) * Math.sin(fAzimuth);
  175. var arrayvectorZ = Math.sin(fAltitude);
  176. var lightDirection = new Vector3([arrayvectorX, arrayvectorY, arrayvectorZ]);
  177. lightDirection.normalize(); // Normalize
  178. gl.uniform3fv(u_LightDirection, lightDirection.elements);
  179. //设置环境光
  180. gl.uniform3f(u_AmbientLight, 0.2, 0.2, 0.2);
  181. return lightDirection;
  182. }
  183. //读取DEM函数
  184. function readDEMFile(result, terrain) {
  185. var stringlines = result.split("\n");
  186. if (!stringlines || stringlines.length <= 0) {
  187. return false;
  188. }
  189. //读取头信息
  190. var subline = stringlines[0].split("\t");
  191. if (subline.length != 6) {
  192. return false;
  193. }
  194. var col = parseInt(subline[4]); //DEM宽
  195. var row = parseInt(subline[5]); //DEM高
  196. var verticeNum = col * row;
  197. if (verticeNum + 1 > stringlines.length) {
  198. return false;
  199. }
  200. terrain.setWH(col, row);
  201. //读取点信息
  202. var ci = 0;
  203. var pSize = 9;
  204. terrain.verticesColors = new Float32Array(verticeNum * pSize);
  205. for (var i = 1; i < stringlines.length; i++) {
  206. if (!stringlines[i]) {
  207. continue;
  208. }
  209. var subline = stringlines[i].split(',');
  210. if (subline.length != pSize) {
  211. continue;
  212. }
  213. for (var j = 0; j < pSize; j++) {
  214. terrain.verticesColors[ci] = parseFloat(subline[j]);
  215. ci++;
  216. }
  217. }
  218. if (ci !== verticeNum * pSize) {
  219. return false;
  220. }
  221. //包围盒
  222. var minX = terrain.verticesColors[0];
  223. var maxX = terrain.verticesColors[0];
  224. var minY = terrain.verticesColors[1];
  225. var maxY = terrain.verticesColors[1];
  226. var minZ = terrain.verticesColors[2];
  227. var maxZ = terrain.verticesColors[2];
  228. for (var i = 0; i < verticeNum; i++) {
  229. minX = Math.min(minX, terrain.verticesColors[i * pSize]);
  230. maxX = Math.max(maxX, terrain.verticesColors[i * pSize]);
  231. minY = Math.min(minY, terrain.verticesColors[i * pSize + 1]);
  232. maxY = Math.max(maxY, terrain.verticesColors[i * pSize + 1]);
  233. minZ = Math.min(minZ, terrain.verticesColors[i * pSize + 2]);
  234. maxZ = Math.max(maxZ, terrain.verticesColors[i * pSize + 2]);
  235. }
  236. terrain.cuboid = new Cuboid(minX, maxX, minY, maxY, minZ, maxZ);
  237. terrain.sphere = new Sphere(terrain.cuboid);
  238. return true;
  239. }
  240. //注册鼠标事件
  241. function initEventHandlers(canvas) {
  242. var dragging = false; // Dragging or not
  243. var lastX = -1,
  244. lastY = -1; // Last position of the mouse
  245. //鼠标按下
  246. canvas.onmousedown = function (ev) {
  247. var x = ev.clientX;
  248. var y = ev.clientY;
  249. // Start dragging if a moue is in <canvas>
  250. var rect = ev.target.getBoundingClientRect();
  251. if (rect.left <= x && x < rect.right && rect.top <= y && y < rect.bottom) {
  252. lastX = x;
  253. lastY = y;
  254. dragging = true;
  255. }
  256. };
  257. //鼠标离开时
  258. canvas.onmouseleave = function (ev) {
  259. dragging = false;
  260. };
  261. //鼠标释放
  262. canvas.onmouseup = function (ev) {
  263. dragging = false;
  264. };
  265. //鼠标移动
  266. canvas.onmousemove = function (ev) {
  267. var x = ev.clientX;
  268. var y = ev.clientY;
  269. if (dragging) {
  270. var factor = 100 / canvas.height; // The rotation ratio
  271. var dx = factor * (x - lastX);
  272. var dy = factor * (y - lastY);
  273. currentAngle[0] = currentAngle[0] + dy;
  274. currentAngle[1] = currentAngle[1] + dx;
  275. }
  276. lastX = x, lastY = y;
  277. };
  278. //鼠标缩放
  279. canvas.onmousewheel = function (event) {
  280. if (event.wheelDelta > 0) {
  281. curScale = curScale * 1.1;
  282. } else {
  283. curScale = curScale * 0.9;
  284. }
  285. };
  286. }
  287. //设置MVP矩阵
  288. function setMVPMatrix(gl, canvas, sphere, lightDirection) {
  289. // Get the storage location of u_MvpMatrix
  290. var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');
  291. if (!u_MvpMatrix) {
  292. console.log('Failed to get the storage location of u_MvpMatrix');
  293. return;
  294. }
  295. //模型矩阵
  296. var modelMatrix = new Matrix4();
  297. modelMatrix.scale(curScale, curScale, curScale);
  298. modelMatrix.rotate(currentAngle[0], 1.0, 0.0, 0.0); // Rotation around x-axis
  299. modelMatrix.rotate(currentAngle[1], 0.0, 1.0, 0.0); // Rotation around y-axis
  300. modelMatrix.translate(-sphere.centerX, -sphere.centerY, -sphere.centerZ);
  301. /*
  302. //----------------------透视---------------------
  303. //投影矩阵
  304. var fovy = 60;
  305. var projMatrix = new Matrix4();
  306. projMatrix.setPerspective(fovy, canvas.width / canvas.height, 1, 10000);
  307. //计算lookAt()函数初始视点的高度
  308. var angle = fovy / 2 * Math.PI / 180.0;
  309. var eyeHight = (sphere.radius * 2 * 1.1) / 2.0 / angle;
  310. //视图矩阵
  311. var viewMatrix = new Matrix4(); // View matrix
  312. viewMatrix.lookAt(0, 0, eyeHight, 0, 0, 0, 0, 1, 0);
  313. //----------------------透视---------------------
  314. */
  315. //----------------------正射---------------------
  316. //视图矩阵
  317. var viewMatrix = new Matrix4();
  318. var r = sphere.radius + 10;
  319. viewMatrix.lookAt(lightDirection.elements[0] * r, lightDirection.elements[1] * r, lightDirection.elements[2] * r, 0, 0, 0, 0, 1, 0);
  320. //投影矩阵
  321. var projMatrix = new Matrix4();
  322. var diameter = sphere.radius * 2.1;
  323. var ratioWH = canvas.width / canvas.height;
  324. var nearHeight = diameter;
  325. var nearWidth = nearHeight * ratioWH;
  326. projMatrix.setOrtho(-nearWidth / 2, nearWidth / 2, -nearHeight / 2, nearHeight / 2, 1, 10000);
  327. //----------------------正射---------------------
  328. //MVP矩阵
  329. var mvpMatrix = new Matrix4();
  330. mvpMatrix.set(projMatrix).multiply(viewMatrix).multiply(modelMatrix);
  331. //将MVP矩阵传输到着色器的uniform变量u_MvpMatrix
  332. gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);
  333. }
  334. //
  335. function initVertexBuffers(gl, terrain) {
  336. //DEM的一个网格是由两个三角形组成的
  337. // 0------1 1
  338. // | |
  339. // | |
  340. // col col------col+1
  341. var col = terrain.col;
  342. var row = terrain.row;
  343. var indices = new Uint16Array((row - 1) * (col - 1) * 6);
  344. var ci = 0;
  345. for (var yi = 0; yi < row - 1; yi++) {
  346. //for (var yi = 0; yi < 10; yi++) {
  347. for (var xi = 0; xi < col - 1; xi++) {
  348. indices[ci * 6] = yi * col + xi;
  349. indices[ci * 6 + 1] = (yi + 1) * col + xi;
  350. indices[ci * 6 + 2] = yi * col + xi + 1;
  351. indices[ci * 6 + 3] = (yi + 1) * col + xi;
  352. indices[ci * 6 + 4] = (yi + 1) * col + xi + 1;
  353. indices[ci * 6 + 5] = yi * col + xi + 1;
  354. ci++;
  355. }
  356. }
  357. //
  358. var verticesColors = terrain.verticesColors;
  359. var FSIZE = verticesColors.BYTES_PER_ELEMENT; //数组中每个元素的字节数
  360. // 创建缓冲区对象
  361. var vertexColorBuffer = gl.createBuffer();
  362. var indexBuffer = gl.createBuffer();
  363. if (!vertexColorBuffer || !indexBuffer) {
  364. console.log('Failed to create the buffer object');
  365. return -1;
  366. }
  367. // 将缓冲区对象绑定到目标
  368. gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorBuffer);
  369. // 向缓冲区对象写入数据
  370. gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);
  371. //获取着色器中attribute变量a_Position的地址
  372. var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  373. if (a_Position < 0) {
  374. console.log('Failed to get the storage location of a_Position');
  375. return -1;
  376. }
  377. // 将缓冲区对象分配给a_Position变量
  378. gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE * 9, 0);
  379. // 连接a_Position变量与分配给它的缓冲区对象
  380. gl.enableVertexAttribArray(a_Position);
  381. //获取着色器中attribute变量a_Color的地址
  382. var a_Color = gl.getAttribLocation(gl.program, 'a_Color');
  383. if (a_Color < 0) {
  384. console.log('Failed to get the storage location of a_Color');
  385. return -1;
  386. }
  387. // 将缓冲区对象分配给a_Color变量
  388. gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 9, FSIZE * 3);
  389. // 连接a_Color变量与分配给它的缓冲区对象
  390. gl.enableVertexAttribArray(a_Color);
  391. // 向缓冲区对象分配a_Normal变量,传入的这个变量要在着色器使用才行
  392. var a_Normal = gl.getAttribLocation(gl.program, 'a_Normal');
  393. if (a_Normal < 0) {
  394. console.log('Failed to get the storage location of a_Normal');
  395. return -1;
  396. }
  397. gl.vertexAttribPointer(a_Normal, 3, gl.FLOAT, false, FSIZE * 9, FSIZE * 6);
  398. //开启a_Normal变量
  399. gl.enableVertexAttribArray(a_Normal);
  400. // 将顶点索引写入到缓冲区对象
  401. gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
  402. gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
  403. return indices.length;
  404. }

4. 参考

本来部分代码和插图来自《WebGL编程指南》,源代码链接:地址 。会在此共享目录中持续更新后续的内容。

[1] Directx11教程三十一之ShadowMap(阴影贴图)之平行光成影

WebGL简易教程(十二):包围球与投影的更多相关文章

  1. WebGL简易教程(十四):阴影

    目录 1. 概述 2. 示例 2.1. 着色器部分 2.1.1. 帧缓存着色器 2.1.2. 颜色缓存着色器 2.2. 绘制部分 2.2.1. 整体结构 2.2.2. 具体改动 3. 结果 4. 参考 ...

  2. WebGL简易教程(十):光照

    目录 1. 概述 2. 原理 2.1. 光源类型 2.2. 反射类型 2.2.1. 环境反射(enviroment/ambient reflection) 2.2.2. 漫反射(diffuse ref ...

  3. WebGL简易教程(十五):加载gltf模型

    目录 1. 概述 2. 实例 2.1. 数据 2.2. 程序 2.2.1. 文件读取 2.2.2. glTF格式解析 2.2.3. 初始化顶点缓冲区 2.2.4. 其他 3. 结果 4. 参考 5. ...

  4. WebGL简易教程——目录

    目录 1. 绪论 2. 目录 3. 资源 1. 绪论 最近研究WebGL,看了<WebGL编程指南>这本书,结合自己的专业知识写的一系列教程.之前在看OpenGL/WebGL的时候总是感觉 ...

  5. WebGL简易教程(二):向着色器传输数据

    目录 1. 概述 2. 示例:绘制一个点(改进版) 1) attribute变量 2) uniform变量 3) varying变量 3. 结果 4. 参考 1. 概述 在上一篇教程<WebGL ...

  6. WebGL简易教程(三):绘制一个三角形(缓冲区对象)

    目录 1. 概述 2. 示例:绘制三角形 1) HelloTriangle.html 2) HelloTriangle.js 3) 缓冲区对象 (1) 创建缓冲区对象(gl.createBuffer( ...

  7. WebGL简易教程(四):颜色

    目录 1. 概述 2. 示例:绘制三角形 1) 数据的组织 2) varying变量 3. 结果 4. 理解 1) 图形装配和光栅化 2) 内插过程 5. 参考 1. 概述 在上一篇教程<Web ...

  8. CRL快速开发框架系列教程十二(MongoDB支持)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

  9. 无废话ExtJs 入门教程十二[下拉列表联动:Combobox_Two]

    无废话ExtJs 入门教程十二[下拉列表联动:Combobox_Two] extjs技术交流,欢迎加群(201926085) 不管是几级下拉列表的联动实现本质上都是根据某个下拉列表的变化,去动态加载其 ...

随机推荐

  1. HBase学习与实践

    Photo by bealach verse on Unsplash 参考书籍:<HBase 权威指南> -- Lars George著. 文章为个人从零开始学习记录,如有错误,还请不吝赐 ...

  2. Knative 实战:基于 Knative Serverless 技术实现天气服务-下篇

    上一期我们介绍了如何基于 Knative Serverless 技术实现天气服务-上篇,首先我们先来回顾一下上篇介绍的内容: 通过高德天气 API 接口,每隔 3 个小时定时发送定时事件,将国内城市未 ...

  3. 经典面试题golang实现方式(一)

    以下所有题目的关键信息都会用[]括起来,我们不对题目进行分析,只给出题目的解决方案:如有疑问请不吝赐教. 题目: 请实现一个算法,确定一个字符串的所有字符[是否全都不同].这里我们要求[不允许使用额外 ...

  4. C# 常见面试问题汇总

    1.c#垃圾回收机制 从以下方面入手展开:  1.压缩合并算法   2.代的机制  3.GC调用终结器 Garbage Collector . NET采用了和Java类似的方法由CLR(Common ...

  5. 利用sed将xml报文转换为分隔符形式报文

    原始xml文本如下 <?xml version="1.0" encoding="utf-8"?> <Message> <Heade ...

  6. 最新打赏正版V15微信视频打赏源码 带(百倍)暗雷 N秒试看 自动切换域名 自动防封

    免签支付域名防封随机跳转盒子推广设置试看N秒百倍 平台搭建:乌龟-源码科技QQ:64430146 全新版本 V15打赏版本功能介绍: 特别注意: 新增加功能!!!! 1.包括V14所有功能(除个别因优 ...

  7. 2-SAT问题学习笔记+例题[洛谷P4792]

    一个不错的2-SAT文章:传送门 问题初入 什么是2-SAT SAT是适定性(Satisfiability)问题的简称 .一般形式为k-适定性问题,简称 k-SAT. 首先,把「2」和「SAT」拆开. ...

  8. 掌握git基本功

    前言 最近想把代码传到GitHub上,结果我发现的demo的npm全是本地安装,上穿到GitHub要死要死,几百M,然后我就搜了下怎么不上传node_modules弄了半天也没成功,于是准备静下心学一 ...

  9. JavaScript中For循环以及For循环嵌套实例

    JavaScript中For循环实例 1.打印出所有的 "水仙花数 ",所谓 "水仙花数 "是指一个三位数,其各位数字立方和等于该数本身. 例如:153是一个 ...

  10. ‎Cocos2d-x 学习笔记(18) Label

    1. 简介 Label直接继承了Node LabelProtocol BlendProtocol,用于渲染文本,让文本呈现的效果丰富. Label有4种类型,: enum class LabelTyp ...