@
- 目标
- WebGL原理示意图
- 着色器代码
- 顶点着色器代码解析
- 片元着色器代码解析
- js代码
- 初始化WebGL代码
- 初始化shaders代码
- 创建着色器对象
- 创建程序对象
- 初始化buffers代码
- 绘制代码
- 代码总结
- 绘制多个点
- 完整代码
- 初始化缓冲区
- 绘制线
- 完整代码
- 绘制面
- 完整代码
目标
- 使用WebgL绘制两个点、一条线、一个面
- 了解整个绘制的编写流程并进行梳理,并深入进入代码里面
WebGL原理示意图
从上面这个图依然展示出来,从上图中,可以清晰的看出来,我们在js这边需要准备数据,并传入着色器
- 顶点数据:我们需要描绘物体的位置信息,用于来确定这个物体,也就是由这些点来组成物体。
- 矩阵:矩阵我们后面解释,这里简单介绍是用于物体的平移、缩放、旋转等三维变换。
- 颜色:颜色是在物体对光的反应,也是可以进行运算的,暂时不考虑,每个数都是0-1。
而上面右侧,主要是顶点着色器与片元着色器,不能说其他的不重要,而是,只有这两个,我们编程接触的多,其他的基本不由我们编程完成,最多是调用api。而顶点和片元着色器,我们在shader程序中有写它的源代码,也就是可以编程来让它怎么做。
- 顶点着色器:在脚本中type="x-shader/x-vertex",id是可以自定义的,这里用id="vertexShader"。下面具体介绍代码。
- 片元着色器:在脚本中type="x-shader/x-fragment",id是可以自定义的,这里用id="fragmentShader"。下面具体介绍代码。
着色器代码
顶点着色器代码解析
<!-- 顶点着色器源码 -->
<script id="vertexShader" type="x-shader/x-vertex">attribute vec2 a_position;void main() {gl_Position = vec4(a_position, 0.0, 1.0);gl_PointSize = 10.0;}
</script>
- 属性attribute:通过属性变量对点的位置进行设置。
- vec2:这里暂时没有涉及到三维,所以用两个标量来确定点的平面位置即可。
- gl_Position: 在主函数中确定点的位置,也就是把attribute的值给gl_Position。
- gl_PointSize:在主函数中确定点的大小,这里写死一个值给gl_Position。当然后期是变量赋值,同位置的赋值,之后不再赘述。
片元着色器代码解析
<!-- 片元着色器源码 -->
<script id="fragmentShader" type="x-shader/x-fragment">void main() {gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);}
</script>
- gl_FragColor:在主函数中确定点的颜色,这里写死一个值给gl_FragColor。之后会两个着色器之间的通信,进行变量赋值。
js代码
初始化WebGL代码
let canvasElementlet glfunction initWebGL() {// 获取canvas元素canvasElement = document.getElementById('canvasId')// 获取WebGL上下文gl = canvasElement.getContext('webgl')}
- 获取canvas元素:原生js写法,这个几乎不用解释,主要是,初始化时,需要获取,或者创建出来。
- 获取WebGL上下文:调用getContext这个函数,从canvas元素身上获取webgl。
初始化shaders代码
function initShaders() {// 获取着色器源码const vsSource = document.getElementById('vertexShader').innerTextconst fsSource = document.getElementById('fragmentShader').innerText// 初始化着色器initShader(gl, vsSource, fsSource)}// -----------------这些代码之后需要单独封装,因此gl需要传递过来---------------------function initShader(gl, vsSource, fsSource) {// 创建着色器对象const vShader = loadShader(gl, gl.VERTEX_SHADER, vsSource)const fShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource)// 创建程序对象const program = gl.createProgram()// 将着色器对象分配给程序对象gl.attachShader(program, vShader)gl.attachShader(program, fShader)// 链接程序对象gl.linkProgram(program)// 使用程序对象gl.useProgram(program)// 将程序对象分配给gl.programgl.program = program}function loadShader(gl, type, source) {// 创建着色器const shader = gl.createShader(type)// 加载着色器源码gl.shaderSource(shader, source)// 编译着色器gl.compileShader(shader)return shader}
- 获取着色器源码:有的人在这里直接用模板字符串的写法,也是可以的,我们使用了分离写法,所以获取下。
- 初始化着色器:调用初始化函数,传入gl, vsSource, fsSource,具体过程分为创建着色器对象与创建程序对象。
- 创建着色器对象:创建具体的着色器,编译着色器。
- 创建程序对象:创建WebGL程序对象,连接并使用。
创建着色器对象
- 创建着色器:根据类型创建具体的着色器,是片元着色器还是顶点着色器。
- 加载着色器源码:将传过来的源码与着色器绑定。
- 编译着色器:对着色器代码进行编译。
流程图
创建程序对象
- 创建程序对象:创建WebGL程序对象。
- 加载具体的着色器对象:将具体的着色器分配给WebGL程序对象。
- 连接程序对象:连接WebGL程序对象。
- 使用程序对象:使用WebGL程序对象。
- 将程序对象挂载到gl上:将WebGL程序对象挂载到gl身上,以备后用。
流程图
初始化buffers代码
function initBuffers() {setPointPosition(0.5, 0.5)}function setPointPosition(x, y) {// 获取位置属性const a_position = gl.getAttribLocation(gl.program, 'a_position')// 设置位置属性gl.vertexAttrib2f(a_position, x, y)}
- 初始化buffers:这里用来初始化各种buffer,绘制一个点没有用到,我们在这里调用了设置点位置的函数,充当了顶点缓冲区。
- 获取位置属性:使用gl身上获取本地attribute变量的方法获取位置属性。
- 设置位置属性:使用gl身上设置设置顶点属性的方法进行设置,这里有很多同族函数,之后会说到。
绘制代码
function draw() {// 清空画布颜色gl.clearColor(0.0, 0.0, 0.0, 1.0)// 清空画布gl.clear(gl.COLOR_BUFFER_BIT)// 绘制gl.drawArrays(gl.POINTS, 0, 1)}
- 清空画布颜色:设置用什么颜色来清空画布。
- 清空画布:使用上面的颜色来清空画布,或者说是初始化画布颜色。
- 绘制:使用gl身上的绘制方法drawArrays进行绘制,参数分别代表绘制的类型、从哪个点开始、画几个点。
代码总结
以上两个板块,着色器代码与js代码板块,基本上把现有的代码就走了一遍,采取的是,打码+下面解释的方式,之后我们会逐步进入细节。大的流程方面,参考前三期的示意图、流程图等地方。如有需要,之后在具体的地方,再去画出来具体的流程,供日后参考与修正。
绘制多个点
接下来我们来绘制多个点,其实有很多细节,我们捡主要的说,绘制多个点,从画画的角度,就是再多画一个点,但是在这里不行了。
我们需要同时描绘出来多个点的信息,进行绘制,否则就会覆盖前面的点。
有两种方案,第一种是使用同步绘制方案,第二种就是使用顶点缓冲区。我们采用第二种。
完整代码
<!doctype html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>绘制多个点</title><!-- 顶点着色器源码 --><script type="x-shader/x-vertex" id="vertexShaderId">attribute vec2 a_position;void main() {gl_Position = vec4(a_position, 0.0, 1.0);gl_PointSize = 10.0;}</script><!-- 片元着色器源码 --><script type="x-shader/x-fragment" id="fragmentShaderId">void main() {gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);}</script><script>let canvasElementlet glconst vertexData = new Float32Array([0.5, 0.5, -0.5, -0.5])function init() {initWebGL()initShaders()initBuffers()draw()}function initWebGL() {// 获取canvascanvasElement = document.getElementById('canvasId')// 设置canvascanvasElement.width = 600canvasElement.height = 600// 获取webgl上下文gl = canvasElement.getContext('webgl')}function initShaders() {// 获取着色器源代码const vsSource = document.getElementById('vertexShaderId').innerTextconst fsSource = document.getElementById('fragmentShaderId').innerText// 初始化着色器程序initShader(gl, vsSource, fsSource)}function initBuffers() {// 创建顶点位置缓冲区const positionBuffer = gl.createBuffer()// 将缓冲区绑定到目标gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer)// 为缓冲区提供数据positionArray,STATIC_DRAW代表数据为静态的gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW)// 获取顶点位置const a_position = gl.getAttribLocation(gl.program, 'a_position')// 指定如何解析顶点数据数组gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 0, 0)// 启用顶点属性数组gl.enableVertexAttribArray(a_position)}function draw() {// 设置清屏颜色gl.clearColor(0.0, 0.0, 0.0, 1.0)// 清屏gl.clear(gl.COLOR_BUFFER_BIT)// 绘制gl.drawArrays(gl.POINTS, 0, vertexData.length / 2)}// -------------------------------------------------function initShader(gl, vsSource, fsSource) {// 创建着色器对象const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource)const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource)// 创建着色器程序const program = gl.createProgram()// 添加着色器对象gl.attachShader(program, vertexShader)gl.attachShader(program, fragmentShader)// 连接着色器程序gl.linkProgram(program)// 使用着色器程序gl.useProgram(program)// 存储着色器程序gl.program = program}function loadShader(gl, type, source) {// 创建着色器const shader = gl.createShader(type)// 绑定着色器源代码gl.shaderSource(shader, source)// 编译着色器gl.compileShader(shader)return shader}</script></head><body onload="init()"><canvas id="canvasId"></canvas></body>
</html>
初始化缓冲区
function initBuffers() {// 创建顶点位置缓冲区const positionBuffer = gl.createBuffer()// 将缓冲区绑定到目标gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer)// 为缓冲区提供数据顶点数据,STATIC_DRAW代表数据为静态的gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW)// 获取顶点位置const a_position = gl.getAttribLocation(gl.program, 'a_position')// 指定如何解析顶点数据数组gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 0, 0)// 启用顶点属性数组gl.enableVertexAttribArray(a_position)}
- 创建顶点位置缓冲区:使用gl身上创建buffer的方法创建顶点位置缓冲区。
- 将缓冲区绑定到目标:使用gl身上的bindBuffer对缓冲区进行绑定。
- 为缓冲区设置数据:使用gl身上的bufferData的方法为缓冲区设置数据。
- 获取顶点位置:这个在设置一个点的时候也用到,获取位置属性。
- 指定如何解析顶点数据:参数含义依次是:属性位置、每个属性的元素数量、数据类型、是否标准化、跨度、偏移量。
- 启用顶点属性数组:开启顶点属性数组。
const vertexData = new Float32Array([0.5, 0.5, -0.5, -0.5])
- 另外是给了两个点的坐标,由于我们只在平面上画,所以采取了二维向量,因此是四个数据。其他的地方基本没办,参看完整代码即可。
绘制线
接下来我们来绘制线,其实只需要改下绘制方式即可
完整代码
<!doctype html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>绘制一个点</title><!-- 顶点着色器源码 --><script type="x-shader/x-vertex" id="vertexShaderId">attribute vec2 a_position;void main() {gl_Position = vec4(a_position, 0.0, 1.0);gl_PointSize = 10.0;}</script><!-- 片元着色器源码 --><script type="x-shader/x-fragment" id="fragmentShaderId">void main() {gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);}</script><script>let canvasElementlet glconst vertexData = new Float32Array([0.5, 0.5, -0.5, -0.5])function init() {initWebGL()initShaders()initBuffers()draw()}function initWebGL() {// 获取canvascanvasElement = document.getElementById('canvasId')// 设置canvascanvasElement.width = 600canvasElement.height = 600// 获取webgl上下文gl = canvasElement.getContext('webgl')}function initShaders() {// 获取着色器源代码const vsSource = document.getElementById('vertexShaderId').innerTextconst fsSource = document.getElementById('fragmentShaderId').innerText// 初始化着色器程序initShader(gl, vsSource, fsSource)}function initBuffers() {// 创建顶点位置缓冲区const positionBuffer = gl.createBuffer()// 将缓冲区绑定到目标gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer)// 为缓冲区提供数据positionArray,STATIC_DRAW代表数据为静态的gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW)// 获取顶点位置const a_position = gl.getAttribLocation(gl.program, 'a_position')// 指定如何解析顶点数据数组gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 0, 0)// 启用顶点属性数组gl.enableVertexAttribArray(a_position)}function draw() {// 设置清屏颜色gl.clearColor(0.0, 0.0, 0.0, 1.0)// 清屏gl.clear(gl.COLOR_BUFFER_BIT)// 绘制gl.drawArrays(gl.LINES, 0, vertexData.length / 2)}// -------------------------------------------------function initShader(gl, vsSource, fsSource) {// 创建着色器对象const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource)const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource)// 创建着色器程序const program = gl.createProgram()// 添加着色器对象gl.attachShader(program, vertexShader)gl.attachShader(program, fragmentShader)// 连接着色器程序gl.linkProgram(program)// 使用着色器程序gl.useProgram(program)// 存储着色器程序gl.program = program}function loadShader(gl, type, source) {// 创建着色器const shader = gl.createShader(type)// 绑定着色器源代码gl.shaderSource(shader, source)// 编译着色器gl.compileShader(shader)return shader}</script></head><body onload="init()"><canvas id="canvasId"></canvas></body>
</html>
这里只需要将绘制的方式改为LINES,绘制数量取vertexData.length / 2,其他的地方没有改变。线还有其他绘制方式,如首尾连接等,下面代码有给出并注释。
gl.drawArrays(gl.LINES, 0, vertexData.length / 2) // 绘制线段: 两两顶点为一条线
// gl.drawArrays(gl.LINE_STRIP, 0, vertexData.length / 2) // 绘制线段: 连续连接为线
// gl.drawArrays(gl.LINE_LOOP, 0, vertexData.length / 2) // 绘制线段: 连接后自动闭合
绘制面
接下来我们来绘制线,其实只需要改下绘制方式即可
完整代码
<!doctype html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>绘制一个点</title><!-- 顶点着色器源码 --><script type="x-shader/x-vertex" id="vertexShaderId">attribute vec2 a_position;void main() {gl_Position = vec4(a_position, 0.0, 1.0);gl_PointSize = 10.0;}</script><!-- 片元着色器源码 --><script type="x-shader/x-fragment" id="fragmentShaderId">void main() {gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);}</script><script>let canvasElementlet glconst vertexData = new Float32Array([0.5, 0.5, -0.5, -0.5, 0.5, -0.5])function init() {initWebGL()initShaders()initBuffers()draw()}function initWebGL() {// 获取canvascanvasElement = document.getElementById('canvasId')// 设置canvascanvasElement.width = 600canvasElement.height = 600// 获取webgl上下文gl = canvasElement.getContext('webgl')}function initShaders() {// 获取着色器源代码const vsSource = document.getElementById('vertexShaderId').innerTextconst fsSource = document.getElementById('fragmentShaderId').innerText// 初始化着色器程序initShader(gl, vsSource, fsSource)}function initBuffers() {// 创建顶点位置缓冲区const positionBuffer = gl.createBuffer()// 将缓冲区绑定到目标gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer)// 为缓冲区提供数据positionArray,STATIC_DRAW代表数据为静态的gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW)// 获取顶点位置const a_position = gl.getAttribLocation(gl.program, 'a_position')// 指定如何解析顶点数据数组gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 0, 0)// 启用顶点属性数组gl.enableVertexAttribArray(a_position)}function draw() {// 设置清屏颜色gl.clearColor(0.0, 0.0, 0.0, 1.0)// 清屏gl.clear(gl.COLOR_BUFFER_BIT)// 绘制gl.drawArrays(gl.TRIANGLES, 0, vertexData.length / 2)}// -------------------------------------------------function initShader(gl, vsSource, fsSource) {// 创建着色器对象const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource)const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource)// 创建着色器程序const program = gl.createProgram()// 添加着色器对象gl.attachShader(program, vertexShader)gl.attachShader(program, fragmentShader)// 连接着色器程序gl.linkProgram(program)// 使用着色器程序gl.useProgram(program)// 存储着色器程序gl.program = program}function loadShader(gl, type, source) {// 创建着色器const shader = gl.createShader(type)// 绑定着色器源代码gl.shaderSource(shader, source)// 编译着色器gl.compileShader(shader)return shader}</script></head><body onload="init()"><canvas id="canvasId"></canvas></body>
</html>
这里只需要将绘制的方式改为TRIANGLES,由于组基本的面就是三角形,因此是TRIANGLES,其他的地方就是点的数量上有变化。面还有其他绘制方式,如连续连接为三角形等,下面代码有给出并注释。
gl.drawArrays(gl.TRIANGLES, 0, vertexData.length / 2) // 绘制三角形: 三个顶点为三角形
// gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertexData.length / 2) // 绘制三角形: 连续连接为三角形
// gl.drawArrays(gl.TRIANGLE_FAN, 0, vertexData.length / 2) // 绘制三角形: 扇形
总结:本文主要梳理了在Webgl中如何绘制一个点的所有具体代码,解释了具体的功能。然后基于此,引入了buffer,添加了点的数量,绘制了多个点,以及更改绘制方式和点的数量来绘制了线和面,给出了多种绘制点与面的方式。具体细节,大家可以尝试多增加几个点,用多种方式来绘制,作为文章记录,基本到这里就可以了。致此,我们将每个代码走一遍的任务也就完成了,之后主要是思路以及变换上的事情多一些了。下次我们来说,三维变换。
上一篇:WebGL学习及项目实战(第02期:绘制一个点)
下一篇:WebGL学习及项目实战(第04期:三维变换)
返回系列首页