WebGL编程指南笔记(一)概念及入门

WebGL编程指南笔记(一)概念及入门

第一章 WebGL概述

介绍

  • WebGL被设计出来的目的, 就是在网页上创建三维的应用和用户体验---------从传统意义上来说, 为了显示三维图形, 开发者需要使用C或C++语言, 辅以专门的计算机图形库, 如OpenGL或Direct3D, 来开发一个独立的应用程序。 现在有了WebGL,我们只需要向已经熟悉的(如果你是一名Web前端开发者) HTML和avaScript中添加一些额外的三维图形学的代码, 就可以在网页上显示三维图形了。
  • WebGL采用HTML5 中新引入的<canvas>标签,它定义了网页上的绘图区域。如果没有 WebGL,JavaScript只能在<canvas>上绘制二维图形, 有了 WebGL, 就可以在上面绘制三维图形了,开发三维的客户界面、运行三维的网页游戏、对互联网上的海量数据进行三维可视化都成了可能。
  • WebGL是内嵌在浏览器中的, 你不必安装插件和库就可以直接使用它。 而且, 因为它是基于浏览器(而不是基于操作系统) 的, 你可以在多种平台上运行WebGL程序,如高配置的个人计算机, 或消费类电子产品(如平板电脑和智能手机)。

技术起源

WebGL的技术规范继承自免费和开源的OpenGL标准。 从2.0版本开始,OpenGL支持了一项非常重要的特性---------可编程着色器方法 。 编写着色器的语言又称为着色器语言(shading language),OpenGL ES 2.0基于OpenGL着色器语言(GLSL), 因此后者又被称为OpenGLES着色器语言(GLSL ES)。 WebGL基于OpenGL ES 2.0, 也使用GLSL ES编写着色器。

程序结构

WebGL程序使用三种语言开发:HTML、JavaScript和GLSL ES。由于着色器代码GLSL ES内嵌在JavaScript中,所以WebGL网页的文件结构和传统网页一样。

第二章 WebGL入门

2.1 熟悉canvas

在HTML5出现之前,如果你想在网页上显示图像,只能使用HTML提供的原生方案<img>标签。用这个标签显示图像虽然简单,但只能显示静态的图片,不能进行实时绘制和渲染。因此,后来出现了一些第三方解决方案,如FlashPlayer等。 HTML5的出现改变了一切,它引入了<canvas>标签,允许JavaScript动态地绘制图形。

DrawRectangle.html
html 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Draw a blue rectangle (canvas version)</title>
  </head>
    
  <!-- 二、为 <body>元素指定 onload 属性
  告诉浏览器 <body>元素加载完成后执行 main() 函数,并作为JavaScript程序的入口 -->
  <body onload="main()">
      
    <!-- 一、定义canvas标签-->
    <canvas id="example" width="400" height="400">
      Please use a browser that supports "canvas"
    </canvas>
      		
    <!-- main函数就在其中 -->
    <script src="DrawRectangle.js"></script>
      
  </body>
</html>
DrawRectangle.js
js 复制代码
function main() {  
  // 获取 <canvas> 元素
  var canvas = document.getElementById('example');  
  if (!canvas) { 
    console.log('获取canvas元素失败');
    return false; 
  } 

  // 获取绘制二维图形的绘图上下文
  var ctx = canvas.getContext('2d');

  // 设置填充颜色为蓝色
  ctx.fillStyle = 'rgba(0, 0, 255, 1.0)'; 
  // 给矩形填充颜色 fillRect(矩形的左上顶点在<canvas>中的x坐标,y坐标,宽度,高度)
  ctx.fillRect(120, 10, 150, 150);
}
效果图

2.2 清空绘图区

HelloCanvas.html
html 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Clear "canvas"</title>
  </head>
  <body onload="main()">
    <!-- WebGL将使用的<canvas>绘制图形 -->
    <canvas id="webgl" width="400" height="400">
    Please use a browser that supports "canvas"
    </canvas>

    <!-- 引入一些转为WebGL准备的、事先定义好的函数库-->
    <script src="../lib/webgl-utils.js"></script>
    <script src="../lib/webgl-debug.js"></script>
    <script src="../lib/cuon-utils.js"></script>
    <!-- js文件,在<canvas>中绘制图形-->
    <script src="HelloCanvas.js"></script>
  </body>
</html>
HelloCanvas.js
js 复制代码
function main() {
  // 荻取<canvas>元素
  var canvas = document.getElementById('webgl');

  // 获取WebGL绘图上下文
  var gl = getWebGLContext(canvas);
  if (!gl) {
    console.log('获取WebGL绘图上下文失败');
    return;
  }

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

  // 清空<canvas>
  gl.clear(gl.COLOR_BUFFER_BIT);
}
  • getWebGLContext(canvas):
    • 该辅助函数来自../lib/cuon-utils.js。在获取WebGL绘图上下文时,canvas.getContext()函数接收的参数,在不同浏览器中会不同,所以写了一个函数getWebGLConTex()来隐藏不同浏览器之间的差异。
  • gl.clearColor( res, green, blue, alpha):
    • 指定背景色,对应着OpenGL ES 2.0或OpenGL中的glClearColor()函数。
    • 取值不再是0-255,而是0.0-1.0。
  • gl.clear(gl.COLOR_BUFFER_BIT)
    • WebGL中的gl.clear()方法实际上继承自OpenGL, 它基于多基本缓冲区模型。 清空绘图区域,实际上是在清空颜色缓冲区(color buffer), 传递参数gl.COLOR_BUFFER_BIT就是在告诉WebGL清空颜色缓冲区。
    • 参数可能值:
      • gl.COLOR_BUFFER_BIT 指定颜色缓存
      • gl.DEPTH_BUFFER_BIT 指定深度缓冲区
      • gl.STENCIL BUFFER BIT 指定模板缓冲区
    • 清空缓冲区的默认颜色及其相关函数
效果图

2.3绘制一个点

HelloPoint1.html

HTML文件同HelloCanvas.html,此处省略

HelloPoint1.js
js 复制代码
// Vertex shader program 顶点着色器程序
var VSHADER_SOURCE = 
  'void main() {\n' +
  '  gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n' + // 设置坐标
  '  gl_PointSize = 10.0;\n' +                    // 设置尺寸
  '}\n';

// Fragment shader program 片元着色器程序
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('获取WebGL绘图上下文失败');
    return;
  }

  // 初始化着色器
  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
    console.log('初始化着色器失败');
    return;
  }

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

  // 绘制一个点
  gl.drawArrays(gl.POINTS, 0, 1);
}
  • initShaders

    • 该辅助函数对字符串形式的着色器进行了初始化。 该函数被定义在../lib/cuon-utils.js中的, 是专为本书编写的。
    • 在初始化着色器之前,顶点着色器和片元着色器都是空白的,我们需要将字符串形式的着色器代码从JavaScript传给WebGL系统,并建立着色器,这就是initShaders()所做的事情。
    • 顶点着色器先执行,它对gl_Position变量和gl_PointSize变量进行赋值,并将它们传入片元着色器,然后片元着色器再执行。实际上,片元着色器接收到的是经过光栅化处理后的片元值。
    • WebGL程序包括【运行在浏览器中的JavaScript】和【运行在WebGL系统的着色器程序】这两个部分。着色器运行在WebGL系统中,而不是JavaScript程序中
  • 顶点着色器程序

    和C语言程序一样,必须包含一个main()函数。 main()前面的关键字void表示这个函数不会有返回值。不能为main()指定参数。

    • vec4 gl_Position
      • 表示顶点的位置。
      • gl_Position变量必须被赋值, 否则着色器就无法正常工作。
      • 内置函数vec4()---------帮助你创建vec4 类型的变量,传入4个浮点型分量,创建vec4类型对象
        • 注意, 我们添加了1.0 作为第4 个分量。 由 4 个分量组成的矢星被称为齐次坐标, 因为它能够提高处理三维数据的效率, 所以在三维图形系统中被大量使用。 虽然齐次坐标是四维的, 但是如果其最后一个 分量是1.0, 那么这个齐次坐标就可以表示 "前三个分量为坐标值" 的那个点。 所以, 当你需要用齐次坐标表示顶点坐标的时候, 只要将最后一个分量赋为1.0 就可以了。
    • float gl_PointSize
      • 表示点的尺寸
      • 如果不赋值,着色器就会为其取默认值1.0。
  • 片元着色器程序

    片元就是显示在屏幕上的一个像素(严格意义上来说, 片元包括这个像素的位置、 颜色和其他信息)。片元着色器的作用是处理片元, 使其显示在屏幕上。

    • vec4 gl_FragColor
      • 指定片元颜色 (RGBA 格式)
      • 是片元着色器唯一的内置变量
  • gl.drawArrays(mode,first, count)

    • 参数说明
      1. 因为我们绘制的是单独的点, 所以设置第1个参数为gl. POINTS
      2. 设置第2个参数为0,表示从第1个顶点(虽然只有1个顶点)开始画起的 ;
      3. 第3个参数 count为1' 表示在这个简单的程序中仅绘制了1个点。
    • 函数规范
    • 处理流程
      1. 当程序调用gl.drawArrays() 时, 顶点着色器将被执行 count次, 每次处理一个顶点
      2. 在着色器执行的时候, 将调用并逐行执行内部的 main() 函数, 将值(0.0, 0.0, 0.0, 1.0)赋给gl_Position, 将值 10.0赋给gl_PointSize。
      3. 一旦顶点着色器执行完后,片元着色器就会开始执行,调用 main() 函数,将颜色值赋给gl_FragColor(第 12 行)
      4. 最后,一个红色的 10 个像素大的点就被绘制在了 (0.0, 0.0, 0.0, 1.0) 处,也就是绘制区域的中心位置
效果图
WebGL坐标系统
  • 可以认为是右手坐标系
  • WebGL的坐标系和<canvas>绘图区的坐标系不同,需要将前者映射到后者。
    1. <canvas>的中心点(0.0, 0.0, 0.0)
    2. <canvas>的上边缘和下边缘:(-1.0, 0.0, 0.0)和(1.0,0.0, 0.0)
    3. <canvas>的左边缘和右边缘:(0.0, -1.0, 0.0)和(0.0,1.0, 0.0)

着色器

在代码中,着色器程序是以字符串的形式"嵌入" 在JavaScript文件中的。在三维场景中,仅仅用线条和颜色把图形画出来是远远不够的。你必须考虑,比如,光线照上去之后,或者观察者的视角发生变化,对场景会有些什么影响。 着色器可以高度灵活地完成这些工作, 提供各种渲染效果.

  • 顶点着色器(Vertex shader): 顶点着色器是用来描述顶点特性(如位置、 颜色等)的程序。 顶点 (vertex) 是指二维或三维空间中的一个点, 比如二维或三维图形的端点或交点。
  • 片元着色器(Fragnment shader): 进行逐片元处理过程如光照(见第8章 "光照" ) 的程序。片元(fragment)是一个WebGL术语,你可以将其理解为像素(图像的单元)。
  • 从执行JavaScript程序到在浏览器中显示结果的过程

2.4 绘制一个点(版本2)

WebGL程序可以将顶点的位置坐标从JavaScript传到着色器程序中,然后在对应位置上将点绘制出来。

HelloPoint2.html

HTML文件同HelloCanvas.html,此处省略

HelloPoint2.js
js 复制代码
var VSHADER_SOURCE = 
  'attribute vec4 a_Position;\n' + // attribute 变量
  'void main() {\n' +
  '  gl_Position = a_Position;\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('获取WebGL绘图上下文失败');
    return;
  }

  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
    console.log('初始化着色器失败');
    return;
  }

  // 获取attribute变量的存储位置
  var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  if (a_Position < 0) {
    console.log('获取attribute变量的存储位置失败');
    return;
  }

  // 将顶点位置传输给attribute变量
  gl.vertexAttrib3f(a_Position, 0.0, 0.0, 0.0);

  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  gl.clear(gl.COLOR_BUFFER_BIT);
  
  gl.drawArrays(gl.POINTS, 0, 1);
}
  • attribute变量: 传输与顶点相关的数据。attribute变量是一种GLSLES变量, 被用来从外部向顶点着色器内传输数据, 只有顶点着色器能使用它。
  • uniform变量: 传输与顶点无关的数据

变量的声明必须按照以下的格式: < 存储限 定符><类型><变量名> 如:attribute vec4 a_Position(数据类型vec4 表示由四个浮点数组成的矢量)

  • gl.getAttribLocation(program, name):获取attribute变量的存储位置

  • gl.vertexAttrib3f(location, v0,v1,v2):

    • gl.vertexAttrib3f()是一系列同族函数中的一个,该系列函数的任务就是从JavaScript向顶点着色器中的attribute变量传值
      • gl.vertexAttrib1f()传输1个单精度值(v0),
      • gl.vertexAttrib2f()传输2个值(v0和v1), 而
      • gl.vertexAttrib4f()传输4个值(v0、v1,v2和v3)。
    • 如果你省略了第4个参数,这个方法就会默认地将第4个分量设置为了1.0

    WebGL相关函数的命名规范------OpenGL中的函数名由三个部分组成:<基础函数名><参数个数><参数类型>,WebGL的函数命名使用同样的结构。<参数类型>中"f"表示浮点数,"i"表示整数。

    • 矢量版本:如果函数名后面跟着一个v, 就表示函数也可以接收数组作为参数。 在这种情况下, 函数名中的数字表示数组中的元素个数。
    js 复制代码
    var position= new Float32Array([l.O, 2.0, 3.0, 1.0]);
    //函数名中4表示数组长度为4
    gl.vertexAttrib4fv(a_Position, position);
效果图

同HelloPoint1,此处省略

2.5 通过鼠标点击绘点

ClickedPoints.html

HTML文件同HelloCanvas.html,此处省略

ClickedPoints.js
js 复制代码
var VSHADER_SOURCE =
  'attribute vec4 a_Position;\n' +
  'void main() {\n' +
  '  gl_Position = a_Position;\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('获取WebGL绘图上下文失败');
    return;
  }

  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
    console.log('初始化着色器失败');
    return;
  }

  var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  if (a_Position < 0) {
    console.log('Failed to get the storage location of a_Position');
    return;
  }

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

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

  gl.clear(gl.COLOR_BUFFER_BIT);
}

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

  // 获取WebGL坐标系下的x、y轴坐标
  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,y])

  // 清除 <canvas> 因为在绘制点之后 颜色缓冲区就被WebGL重置为了默认的颜色 (0.0, 0.0, 0.0, 0.0)
  gl.clear(gl.COLOR_BUFFER_BIT);
  
  g_points.forEach(item => {
    // 将点的位置传递到变量中a_Position
    gl.vertexAttrib2f(a_Position, item[0], item[1]);
    // 绘制
    gl.drawArrays(gl.POINTS, 0, 1);
  })
}
  • canvas.onmoousedown: 将事件响应函数注册在<canvas>的onmousedown事件上
  • event.clientXevent.clientY: 鼠标点击位置坐标。是在 "浏览器客户区" (client area) 中的坐标,而不是在<canvas>中的坐标
  • event.target.getBoundingClientRect: 代码中的rect.leftrect.top<canvas> 的原点在浏览器客户区中的坐标。

计算鼠标点的位置:

  1. event.clientXevent.clientY获取鼠标点击位置在 "浏览器客户区" 中的坐标。

  2. 将客户区坐标系下的坐标(x,y)转化为<canvas>坐标系下的坐标。

  3. <canvas>坐标系下的坐标转化为WebGL坐标系下的坐标 WebGL坐标系统中,中心点的坐标是(canvas.height /2, canvas.width/2)

  4. 最终结果 x = ((x - rect.left) - canvas.width/2)/(canvas.width/2); y = (canvas.height/2 - (y - rect.top))/(canvas.height/2);

效果图.gif

2.6 改变点的颜色

ColoredPoint.html

HTML文件同HelloCanvas.html,此处省略

ColoredPoint.js
js 复制代码
var VSHADER_SOURCE =
  'attribute vec4 a_Position;\n' +
  'void main() {\n' +
  '  gl_Position = a_Position;\n' +
  '  gl_PointSize = 10.0;\n' +
  '}\n';

var FSHADER_SOURCE =
  'precision mediump float;\n' +//使用精度限定词(precision qualifier) 来指定变量的范围(最大值与最小值)和精度, 本例中为中等精度。
  'uniform vec4 u_FragColor;\n' +  // uniform変数
  'void main() {\n' +
  '  gl_FragColor = u_FragColor;\n' +
  '}\n';

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

  var gl = getWebGLContext(canvas);
  if (!gl) {
    console.log('获取WebGL绘图上下文失败');
    return;
  }

  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
    console.log('初始化着色器失败');
    return;
  }

  var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  if (a_Position < 0) {
    console.log('获取attribute变量的存储位置失败');
    return;
  }

  // 获取uniform变量的存储地址
  var u_FragColor = gl.getUniformLocation(gl.program, 'u_FragColor');
  if (!u_FragColor) {
    console.log('获取uniform变量的存储位置失败');
    return;
  }

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

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

  gl.clear(gl.COLOR_BUFFER_BIT);
}


var g_points = []; 
function click(event, gl, canvas, a_Position, u_color) {
    var x = event.clientX; 
    var y = event.clientY;
    var rect = event.target.getBoundingClientRect() ;

    x = ((x - rect.left) - canvas.width/2)/(canvas.width/2);
    y = (canvas.height/2 - (y - rect.top))/(canvas.height/2);

    g_points.push([x,y])

    gl.clear(gl.COLOR_BUFFER_BIT);

    g_points.forEach(item => {
        gl.vertexAttrib2f(a_Position, item[0], item[1]);
        
		// 不同象限的点,给uniform变量赋不同的值
        if (item[0] > 0){
            if (item[1] > 0) {
                gl.uniform4f(u_color, 0.0,1.0,0.0, 1.0)     
            }else {
                gl.uniform4f(u_color, 0.0,0.0,1.0, 1.0)
            }
        }else{
            if (item[1] > 0) {
                gl.uniform4f(u_color, 1.0,0.0,0.0, 1.0)  
            }else {
                gl.uniform4f(u_color, 1.0,1.0,0.0, 1.0)
            }
        }

        // 绘制
        gl.drawArrays(gl.POINTS, 0, 1);
    })
}
  • gl.getUniformLocation(program, name):获取uniform变量的存储地址
  • gl.uniform4f(location,v0,v1,v2,v3):向uniform变量赋值
    • gl.uniform1f()函数用来传输1个值(v0)
    • gl.uniform2f()传输2个值(v0和v1)
    • gl.un]iform3f()传输3个值(v0,v1和v2)
效果图.gif
相关推荐
霸王蟹6 分钟前
React 19 中的useRef得到了进一步加强。
前端·javascript·笔记·学习·react.js·ts
霸王蟹6 分钟前
React 19版本refs也支持清理函数了。
前端·javascript·笔记·react.js·前端框架·ts
繁依Fanyi11 分钟前
ColorAid —— 一个面向设计师的色盲模拟工具开发记
开发语言·前端·vue.js·编辑器·codebuddy首席试玩官
明似水1 小时前
Flutter 开发入门:从一个简单的计数器应用开始
前端·javascript·flutter
沐土Arvin1 小时前
前端图片上传组件实战:从动态销毁Input到全屏预览的全功能实现
开发语言·前端·javascript
爱编程的鱼2 小时前
C#接口(Interface)全方位讲解:定义、特性、应用与实践
java·前端·c#
sunbyte2 小时前
50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | 页面布局 与 Vue Router 路由配置
前端·javascript·vue.js·tailwindcss
saadiya~3 小时前
Vue 3 实现后端 Excel 文件流导出功能(Blob 下载详解)
前端·vue.js·excel
摇摇奶昔x4 小时前
webpack 学习
前端·学习·webpack