《WebGL编程指南》读书笔记之入门

程序关键部分:1.WebGL如何获取<canvas>元素,如何在其上绘图;2.HTML文件如何引入WebGL JavaScript文件;3.简单的WebGL绘图函数;4.WebGL中的着色程序

  • Canvas:允许JavaScript动态地绘制图形

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Draw a blue rectangle</title>
    </head>
    <body onload="main()">
      <canvas id ='example' width="400" height="400">
          Please use a browser that supports "canvas"
      </canvas>
      <script type="text/javascript" src="DrawRectangle.js"></script>
    </body>
    </html>
    
    function main() {
      // 获取<canvas>元素
      var canvas = document.getElementById('example')
      if(!canvas){
          console.log('Failed to retrieve this <canvas> element!')
          return
      }
    
      // 获取绘制二位图形的绘图上下文
      var ctx = canvas.getContext('2d')
    
      // 绘制蓝色矩形
      ctx.fillStyle = 'rgba(0,0,255,1.0)'
      ctx.fillRect(120,10,150,150)
    }
    
  • 最短的WebGL程序:清空绘图区

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Clear "canvas"</title>
  </head>
 
  <body onload="main()">
    <canvas id="webgl" width="400" height="400">
    Please use a browser that supports "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="HelloCanvas.js"></script>
  </body>
</html>

function main() {
  // 获取<canvas>元素
  var canvas = document.getElementById('webgl');

  // 获取WebGL绘图上下文
  var gl = getWebGLContext(canvas);
  if (!gl) {
    console.log('Failed to get the rendering context for WebGL');
    return;
  }

  // 指定清空<canvas>的颜色
  gl.clearColor(0.0, 0.0, 0.0, 1.0);

  // 清空<canvas>
  gl.clear(gl.COLOR_BUFFER_BIT);
}
  • 绘制一个点1.0:此处用矩阵而不是圆
var VSHADER_SOURCE = 
  'void main() {\n' +
  '  gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n' + // 设置坐标
  '  gl_PointSize = 10.0;\n' +                    // 设置尺寸
  '}\n';

// 片元着色器程序
var FSHADER_SOURCE =
  'void main() {\n' +
  '  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' + // 设置颜色
  '}\n';

function main() {
  var canvas = document.getElementById('webgl');

  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 intialize shaders.');
    return;
  }

  gl.clearColor(0.0, 0.0, 0.0, 1.0);

  gl.clear(gl.COLOR_BUFFER_BIT);

  // 绘制一个点
  gl.drawArrays(gl.POINTS, 0, 1);
}
  • 着色器:是以字符串形式“嵌入”在js文件中的,在程序真正开始运行前就已经设置好了

    • 顶点着色器(Vertex shader):用来描述顶点特性(如位置、尺寸等)的程序
    • 片元着色器(Fragment shader):进行逐片元处理过程(如光照)的程序。片元是一个WebGL的术语,可以理解为像素。
  • 使用着色器的WebGL程序的结构:顶点着色器程序(GLSL ES语言)、片元着色器程序(GLSL ES语言)、主程序(JavaScript语言)

  • 初始化着色器:调用了辅助函数initShaders()。作用是将字符串形式的着色器代码从Js传给WebGL系统

  • 顶点着色器:gl_Position变量必须被赋值,变量必须指定类型,且赋的值要和类型一致。

  • 片元着色器:作用是处理片元,使其显示在屏幕上。

  • 绘制操作:gl.drawArrays()是一个强大的函数,可以用来绘制各种图形。当程序调用该函数时,顶点着色器将被执行count次,每次处理一个顶点。一旦顶点着色器执行完成后,片元着色器就会开始执行。

  • WebGL坐标系统:三维坐标系统(笛卡尔坐标系)

默认情况下:X轴是水平的(正方向为右),Y轴是垂直的(正方向为上),Z轴垂直于屏幕(正方向为外),即右手坐标系。

WebGL的坐标系和<canvas>绘图区的坐标系不同,需要将前者映射到后者。
绘制一个点2.0(在JavaScript和着色器之间传输数据,可扩展)

  • 使用attribute变量:传输与顶点相关的数据

使用步骤:

  1. 在顶点着色器中,声明attribute变量
  2. 将attribute变量赋值给gl_Position变量
  3. 向attribute变量传输数据

关键代码:

// 声明attribute变量
var VSHADER_SOURCE = 
  //<存储限定符> <类型> <变量名>
  'attribute vec4 a_Position;\n' + // attribute变量必须声明成全局变量
  'void main() {\n' +
  '  gl_Position = a_Position;\n' +
  '  gl_PointSize = 10.0;\n' +
  '}\n'; 

// 获取attribute变量的存储位置
var a_Position = gl.getAttribLocation(gl.program, 'a_Position'); // 必须在initShader()之后访问gl.program

// 将顶点位置传输给attribute变量
gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);
  • 通过鼠标点击绘制点
    关键代码:
// 注册鼠标点击事件相应函数
canvas.onmousedown = function(ev){ click(ev, gl, canvas, a_Position); };

var g_points = []; // 鼠标点击位置数组
function click(ev, gl, canvas, a_Position) {
  var x = ev.clientX; // 鼠标点击处的x坐标
  var y = ev.clientY; // 鼠标点击处的y坐标
  var rect = ev.target.getBoundingClientRect() ;

  // 坐标转换
  x = ((x - rect.left) - canvas.width/2)/(canvas.width/2);
  y = (canvas.height/2 - (y - rect.top))/(canvas.height/2);
  // 将坐标存储到g_points数组中
  g_points.push(x); g_points.push(y);

  // 清空<canvas>
  gl.clear(gl.COLOR_BUFFER_BIT);

  var len = g_points.length;
  for(var i = 0; i < len; i += 2) {
    // 将点的位置传递到变量中
    gl.vertexAttrib3f(a_Position, g_points[i], g_points[i+1], 0.0);

    // 绘制
    gl.drawArrays(gl.POINTS, 0, 1);
  }
}

uniform变量:将颜色值传给着色器

  • 使用步骤:
  1. 在片元着色器中准备uniform变量
  2. 用这个uniform变量向gl_FragColor赋值
  3. 将颜色数据从JavaScript传给该uniform变量

关键代码:

// 片元着色器
var FSHADER_SOURCE =
  // 精度限定词
  'precision mediump float;\n' +
  // <存储限定符> <类型> <变量名>
  'uniform vec4 u_FragColor;\n' +  // uniform変量
  'void main() {\n' +
  '  gl_FragColor = u_FragColor;\n' +
  '}\n';

// 获取u_FragColor变量的存储位置
  var u_FragColor = gl.getUniformLocation(gl.program, 'u_FragColor');

// 注册鼠标点击时的事件响应事件
canvas.onmousedown = function(ev){ click(ev, gl, canvas, a_Position, u_FragColor) };

var g_colors = [];  // 存储点颜色的数组

function click(ev, gl, canvas, a_Position, u_FragColor) {
......
  // 将点的颜色存储到g_colors数组中
  if (x >= 0.0 && y >= 0.0) {      // 第一象限
    g_colors.push([1.0, 0.0, 0.0, 1.0]);  // Red
  } else if (x < 0.0 && y < 0.0) { // 第三象限
    g_colors.push([0.0, 1.0, 0.0, 1.0]);  // Green
  } else {                         // 其他
    g_colors.push([1.0, 1.0, 1.0, 1.0]);  // White
  }

......

  var len = g_points.length;
  for(var i = 0; i < len; i++) {
    var xy = g_points[i];
    var rgba = g_colors[i];

    // 将点的位置传输到a_Position中
    gl.vertexAttrib3f(a_Position, xy[0], xy[1], 0.0);
    // 将点的颜色传输到u_FragColor中
    gl.uniform4f(u_FragColor, rgba[0], rgba[1], rgba[2], rgba[3]);
    // 绘制
    gl.drawArrays(gl.POINTS, 0, 1);
  }
}
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354

推荐阅读更多精彩内容