WebGL 绘制一个点

优质
小牛编辑
126浏览
2023-12-01

通过WebGL可以渲染出来各种各样酷炫的3D效果,但是考虑到WebGL复杂性,为了大家降低初学难度,下面的代码仅仅在Canvas画布上绘制了一个点,对WebGL的整个绘图渲染流程进行了完整的演示,麻雀虽小,五脏俱全。

学习建议

本节课你不需要刻意去记忆具体知识,对于暂时不理解的小细节可以跳过,先有个整体印象即可,可以把全部代码复制到编辑器中,根据课程对相关知识模块的介绍,尝试更改几个参数,体验一下渲染效果。

在后面的学习中,都可以在本节课源码的基础上进行增删代码即可,初学WebGL没必要每一行代码都要自己去敲出来。

本课程风格和大多数课程风格不同,注意一定要结合案例代码学习,在案例代码的基础上调试体验总结,就像做化学实验一样,不要仅仅阅读文字。

关键词

  • 超文本语言HTML
  • 脚本语言Javascript
  • Canvas画布
  • 着色器语言GLSL ES
  • WebGL API

关于HTML/CSS/JavaScript不再进行过多的介绍,默认你有一定的前端基础,下面结合WebGL绘制点的代码,对Canvas画布、着色器语言GLSL ES和WebGL API相关知识进行简单介绍。

WebGL绘制一个点(.html文件完整源码)

1    <!DOCTYPE html>
2    <html lang="en">
3    <head>
4        <meta charset="UTF-8">
5        <title>使用WebGL绘制一个点</title>
6    </head>
7    <body>
8    <!--canvas标签创建一个宽高均为500像素,背景为蓝色的矩形画布-->
9    <canvas id="webgl" width="500" height="500" style="background-color: blue"></canvas>
10   
11   
12   <script>
13       //通过getElementById()方法获取canvas画布
14       var canvas=document.getElementById('webgl');
15       //通过方法getContext()获取WebGL上下文
16       var gl=canvas.getContext('webgl');
17   
18       //顶点着色器源码
19       var vertexShaderSource = '' +
20           'void main(){' +
21           //给内置变量gl_PointSize赋值像素大小
22           '   gl_PointSize=20.0;' +
23           //顶点位置,位于坐标原点
24           '   gl_Position =vec4(0.0,0.0,0.0,1.0);' +
25           '}';
26   
27       //片元着色器源码
28       var fragShaderSource = '' +
29           'void main(){' +
30           //定义片元颜色
31           '   gl_FragColor = vec4(1.0,0.0,0.0,1.0);' +
32           '}';
33   
34       //初始化着色器
35       var program = initShader(gl,vertexShaderSource,fragShaderSource);
36       //开始绘制,显示器显示结果
37       gl.drawArrays(gl.POINTS,0,1);
38   
39       //声明初始化着色器函数
40       function initShader(gl,vertexShaderSource,fragmentShaderSource){
41           //创建顶点着色器对象
42           var vertexShader = gl.createShader(gl.VERTEX_SHADER);
43           //创建片元着色器对象
44           var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
45           //引入顶点、片元着色器源代码
46           gl.shaderSource(vertexShader,vertexShaderSource);
47           gl.shaderSource(fragmentShader,fragmentShaderSource);
48           //编译顶点、片元着色器
49           gl.compileShader(vertexShader);
50           gl.compileShader(fragmentShader);
51   
52           //创建程序对象program
53           var program = gl.createProgram();
54           //附着顶点着色器和片元着色器到program
55           gl.attachShader(program,vertexShader);
56           gl.attachShader(program,fragmentShader);
57           //链接program
58           gl.linkProgram(program);
59           //使用program
60           gl.useProgram(program);
61           //返回程序program对象
62           return program;
63       }
64   </script>
65   </body>
66   </html>

创建Canvas画布

创建一个Canvas画布,用于显示WebGL的渲染结果,canvas元素和div等元素一样是HTML的一个元素,只是Canvas画布具有2D和3D绘图功能。

<!--canvas标签创建一个宽高均为500像素,背景为蓝色的矩形画布-->
<canvas id="webgl" width="500" height="500" style="background-color: blue"></canvas>

通过JavaScript获取上面创建的Canvas元素返回一个Canvas对象。

//通过getElementById()方法获取canvas画布对象
var canvas= document.getElementById('webgl')

canvas对象也可以不通过<canvas>标签创建,然后id方式获取,也可以通过DOM直接创建

//通过getElementById()方法获取canvas画布对象
var canvas = document.createElement('canvas');

Canvas对象方法.getContext()

HTML的Canvas元素提供了2D和3D绘图两种功能,平时程序员之间交流所说的Canvas一词就是指Canvas的2D绘图功能,通过Canvas元素实现的3D绘图功能,也就是所谓的WebGL,或者说WebGL依赖于Canvas元素实现。

执行canvas.getContext('2d')返回对象具有一系列绘制二维图形的方法,比如绘制直线、圆弧等API。关于canvas 2D绘图相关内容,本课程不介绍,可以百度关键词Canvas,

//通过方法getContext()获取Canvas 2D绘图上下文
var gl=canvas.getContext('2d');
c.moveTo(0,0);//直线起点坐标
c.lineTo(50,50);//直线第2个点坐标
c.lineTo(0,100);//直线第3个点坐标
c.stroke();//把点连成直线绘制出来

执行canvas.getContext('webgl');返回对象具有一系列绘制渲染三维场景的方法,也就是WebGL API。

//通过方法getContext()获取WebGL上下文
var gl=canvas.getContext('webgl');
...
//调用WebGL API绘制渲染方法drawArrays
gl.drawArrays(gl.POINTS,0,1);
...

着色器语言GLSL ES

下面代码中的两个字符串vertexShaderSourcefragShaderSource是WebGL的着色器代码,着色器代码通过着色器语言GLSL ES编写,对于前端工程来说学习WebGL,还需要学习一门新的语言着色器语言GLSL ES。关于着色器语言的学习,可以跟着课程一边写案例,一边去学习,这样更容易理解。

着色器语言用于计算机图形编程,运行在GPU中,平时所说的大多数语言编写的程序都是运行在CPU中。 与OpenGL API相配合的是着色器语言GLSL,与OpenGL ES API、WebGL API相互配合的是着色器语言GLSL ES。OpenGL标准应用的是客户端 OpenGL ES应用的是移动端,WebGL标准应用的是浏览器平台。

顶点着色器和片元着色器经过WebGL编译处理后,会在GPU的顶点着色器单元和片元着色器单元上执行。

顶点着色器定义了顶点的渲染位置和点的渲染像素大小

//顶点着色器源码
var vertexShaderSource = '' +
    'void main(){' +
    //给内置变量gl_PointSize赋值像素大小
    '   gl_PointSize=20.0;' +
    //顶点位置,位于坐标原点
    '   gl_Position =vec4(0.0,0.0,0.0,1.0);' +
    '}';

片元着色器定义了点的渲染结果像素的颜色值

//片元着色器源码
var fragShaderSource = '' +
    'void main(){' +
    //定义片元颜色
    '   gl_FragColor = vec4(1.0,0.0,0.0,1.0);' +
    '}';

gl_PointSize、gl_Position、gl_FragColor都是内置变量,也就是说不需要声明,这一点与多数编程语言不同,这主要是由GPU的特殊性决定,感兴趣的话 可以研究显卡的硬件结构,渲染管线等概念。

通过程序可以看出来顶点着色器源码vertexShaderSource、片元着色器源码fragShaderSource,都是只有一个主函数main,也就是入口函数。

给内置变量gl_Position赋值vec4(0.0,0.0,0.0,1.0),也就是设置顶点位置坐标,vec4代表的是一种数据类型, 在这里可以理解为vec4()是一个可以构造出vec4类型数据的构造函数,前三个参数表示顶点坐标值xyz。

给内置变量gl_FragColor赋值vec4(1.0,0.0,0.0,1.0),也就是设置会在屏幕上显示的像素的颜色,vec4()构造函数 前三个参数,表示颜色RGB值,最后一个参数是透明度A。在WebGL着色器中颜色值使用[0,1]区间表示。

你可以通过改变WebGL着色器代码内置变量gl_PointSizegl_Positiongl_FragColor测试WebGL渲染效果的变化。

  • gl_PointSize=20.0改为gl_PointSize=10.0,观察屏幕点的大小变化

  • gl_Position =vec4(0.5,0.5,0.0,1.0)改为gl_Position =vec4(0.5,0.5,0.0,1.0),观察屏幕点的位置变化

gl_FragColor=vec4(0.0,0.0,1.0,1.0)更改为gl_FragColor = vec4(0.0,0.0,1.0,1.0),观察屏幕点的颜色变化

WebGL API

一句话来描述,WebGL API指的就是gl=canvas.getContext('webgl')返回对象gl的一系列绘制渲染方法,通过WebGL API可以把一个三维场景绘制渲染出来。比如上面代码中gl.createShader()gl.shaderSource()gl.drawArrays()等方法就是WebGl API。

WebGL API多数与GPU硬件相关,控制相关图形处理单元,比如说通过gl.createShader()gl.shaderSource()等方法可以对着色器代码进行编译,然后在GPU的着色器单元上执行;比如说drawArrays不执行,GPU渲染管线的顶点、片元着色器是不会把顶点坐标转化为显示器上的像素显示出来。

如果你有 数字电路的知识,可编程芯片不仅仅只有GPU,针对不同的应用情形,都有特定的可编程芯片,图形处理用到的是可编程GPU,也就是说可以运行程序。处理声音是声卡,处理图像是显卡,自然而然它们 都会以CPU为核心,接受CPU的调度。以上描述在有些地方可能不太严谨,大家也不必记忆,主要目的是让大家有基本的认识,可以更好的编写程序。GPU相比CPU最大的特点是并行计算,不过WebGL API都 进行了封装,如果你想学习并行计算可以关注CUDA或OpenCL。GPU硬件(渲染管线)、显卡驱动、操作系统、浏览器、WebGL API是逐层抽象的。每一层都会为上一层提供一个接口,这里可以看出WebGL API是 首先通过浏览器的的解析,才能够经过一系列层驱动GPU工作,生成像素缓存,显示器会按照一定的频率扫描像素缓存,最终显示出来。

初始化着色器函数initShader()

关于着色器函数initShader()中封装的WebGL API代码,案例代码中都进行了注释,可以先简单阅读一下,具体的细节可以先不去学习,当成一个黑箱处理,你只需要知道通过initShader()函数可以完成着色器代码的编译,然后在GPU上执行。

初始化着色器函数initShader(),主要是把顶点着色器源代码vertexShaderSource, 片元着色器源代码fragShaderSource,进行编译处理,然后顶点着色器代码在GPU的顶点着色器单元执行,片元着色器代码在GPU的片元着色器单元执行。

//声明初始化着色器函数
function initShader(gl,vertexShaderSource,fragmentShaderSource){
    //创建顶点着色器对象
    var vertexShader = gl.createShader(gl.VERTEX_SHADER);
    //创建片元着色器对象
    var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
    //引入顶点、片元着色器源代码
    gl.shaderSource(vertexShader,vertexShaderSource);
    gl.shaderSource(fragmentShader,fragmentShaderSource);
    //编译顶点、片元着色器
    gl.compileShader(vertexShader);
    gl.compileShader(fragmentShader);
    //创建程序对象program
    var program = gl.createProgram();
    //附着顶点着色器和片元着色器到program
    gl.attachShader(program,vertexShader);
    gl.attachShader(program,fragmentShader);
    //链接program
    gl.linkProgram(program);
    //使用program
    gl.useProgram(program);
    //返回程序program对象
    return program;
}

绘制方法gl.drawArrays()

gl.drawArrays()方法的作用就是通知GPU执行着色器代码,然后根据着色器代码在Canvas画布上进行渲染绘制。

着色器代码放在script标签中

WebGL着色器代码在JavaScript中以字符串的形式存在,编写代码比较麻烦,为了编写方便,可以把着色器代码写在script或者其他HTML元素标签中,然后通过元素.innerText属性获得元素中的字符串,也就是着色器的代码。

在原来WebGL绘制一个点的代码基础上进行更改

<!-- 顶点着色器源码 -->
<script id="vertexShader" type="x-shader/x-vertex">
  void main() {
    //给内置变量gl_PointSize赋值像素大小
    gl_PointSize=20.0;
    //顶点位置,位于坐标原点
    gl_Position =vec4(0.0,0.0,0.0,1.0);
  }
</script>
<!-- 片元着色器源码 -->
<script id="fragmentShader" type="x-shader/x-fragment">
  void main() {
    gl_FragColor = vec4(1.0,0.0,0.0,1.0);
  }
</script>
//顶点着色器源码
var vertexShaderSource = document.getElementById('vertexShader').innerText;
//片元着色器源码
var fragShaderSource = document.getElementById('fragmentShader').innerText;
//初始化着色器
var program = initShader(gl,vertexShaderSource,fragShaderSource);