文章目录
目标
- 使用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 canvasElement
let gl
function 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').innerText
const 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.program
gl.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 canvasElement
let gl
const vertexData = new Float32Array([0.5, 0.5, -0.5, -0.5])
function init() {
initWebGL()
initShaders()
initBuffers()
draw()
}
function initWebGL() {
// 获取canvas
canvasElement = document.getElementById('canvasId')
// 设置canvas
canvasElement.width = 600
canvasElement.height = 600
// 获取webgl上下文
gl = canvasElement.getContext('webgl')
}
function initShaders() {
// 获取着色器源代码
const vsSource = document.getElementById('vertexShaderId').innerText
const 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 canvasElement
let gl
const vertexData = new Float32Array([0.5, 0.5, -0.5, -0.5])
function init() {
initWebGL()
initShaders()
initBuffers()
draw()
}
function initWebGL() {
// 获取canvas
canvasElement = document.getElementById('canvasId')
// 设置canvas
canvasElement.width = 600
canvasElement.height = 600
// 获取webgl上下文
gl = canvasElement.getContext('webgl')
}
function initShaders() {
// 获取着色器源代码
const vsSource = document.getElementById('vertexShaderId').innerText
const 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 canvasElement
let gl
const vertexData = new Float32Array([0.5, 0.5, -0.5, -0.5, 0.5, -0.5])
function init() {
initWebGL()
initShaders()
initBuffers()
draw()
}
function initWebGL() {
// 获取canvas
canvasElement = document.getElementById('canvasId')
// 设置canvas
canvasElement.width = 600
canvasElement.height = 600
// 获取webgl上下文
gl = canvasElement.getContext('webgl')
}
function initShaders() {
// 获取着色器源代码
const vsSource = document.getElementById('vertexShaderId').innerText
const 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,添加了点的数量,绘制了多个点,以及更改绘制方式和点的数量来绘制了线和面,给出了多种绘制点与面的方式。具体细节,大家可以尝试多增加几个点,用多种方式来绘制,作为文章记录,基本到这里就可以了。致此,我们将每个代码走一遍的任务也就完成了,之后主要是思路以及变换上的事情多一些了。下次我们来说,三维变换。
浙公网安备 33010602011771号