# 一、从绘制一个点开始

绘制一个点有以下几个步骤

  1. 获取 canvas 以及上下文。webgl 需要使用 canvas 展示。
1
2
3
4
5
<canvas id="canvas"></canvas>
<script>
let canvas = document.getElementById('canvas')
let gl = canvas.getContext('webgl')
</script>
  1. 创建着色器

着色器有两类分别为顶点着色器和片元着色器。众所周知,每一个 3d 模型都是由一个个三角形构成的,而三角形由三个顶点构成。顶点着色器就是记录顶点的数据,比如位置,大小,颜色。当处理完顶点就会进入光栅化,简单的讲就是将 3d 模型转化为 2d 的像素点,模型所占用的像素点就是片元着色器,记录着每一个像素点的颜色,用 rgba 表示,取值范围在 0 到 1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//glsl的代码使用字符串储存,然后再通过shaderSource传递到创建的着色器,通过compileShader编译。
const vertexCode = `
//注意glsl的每一行的结尾都要写;
void main(){
//顶点坐标
gl_Postion = vec4(0.0,0.0,0.0,1.0);
//点的大小
gl_PointSion = 10.0;
}
const fragmentCode = `
void main(){
//片元颜色设置成红色
gl_FragColor = vec4(1.0,0.0,0.0,1.0);
};
`

const vertexShader = gl.creatShader(gl_VERTEX_SHADER) //创建顶点着色器
gl.shaderSource(vertexShader,vertexCode) //将上面写的顶点着色器glsl代码,放到创建好的顶点着色器上。
gl.compileShader(vertexShader) //编译顶点着色器
//创建片元着色器与顶点着色器一致
const fragmentShader = gl.creatShader(gl_FRAGMENT_SHADER)
gl.shaderSource(fragmentShader,fragmentCode)
gl.compileShader(fragmentShader)
  1. 创建着色器程序
1
2
3
4
5
const program = gl.createProgram() //创建着色器程序
gl.attachShader(program,vertexShader) //将顶点着色器传入着色器程序
gl.attachShader(program,fragmentShader) //将片元着色器传入着色器程序
gl.linkProgram(program) //连接着色器程序
gl.useProgram(program) //使用这个着色器程序
  1. 将点绘制出来
1
gl.drawArrays(gl.POINTS,0,1)
  1. 完整代码,此外还可以设置背景颜色,canvas1 默认的背景颜色是透明的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
let canvas = document.getElementById('canvas')
const gl = canvas.getContext('webgl')

const vertexCode = `
void main(){
gl_Position = vec4(0.0,0.0,0.0,1.0);
gl_PointSize = 10.0;
}
`
const fragmentCode = `
void main(){
gl_FragColor = vec4(1.0,0.0,0.0,1.0);
}
`

gl.clearColor(0.0,0.0,0.0,1.0)//设置背景颜色,设置一次之后每次调用clear()前无需重复设置除非颜色发送改变
gl.clear(gl.COLOR_BUFFER_BIT)//清除颜色缓冲区

const vertexShader = gl.createShader(gl.VERTEX_SHADER) //创建顶点着色器
gl.shaderSource(vertexShader,vertexCode) //将上面写的顶点着色器glsl代码,放到创建好的顶点着色器上。
gl.compileShader(vertexShader) //编译顶点着色器
//创建片元着色器与顶点着色器一致
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
gl.shaderSource(fragmentShader,fragmentCode)
gl.compileShader(fragmentShader)

const program = gl.createProgram(); //创建着色器程序
gl.attachShader(program,vertexShader) //将顶点着色器传入着色器程序
gl.attachShader(program,fragmentShader) //将片元着色器传入着色器程序
gl.linkProgram(program) //连接着色器程序
gl.useProgram(program) //使用这个着色器程序

gl.drawArrays(gl.POINTS,0,1)

# 二、修改点的数据

​ 之前点的坐标,大小,片元的颜色是通过硬编码的形式设置好的无法修改。现在试着使用 JavaScript 的变量储存点的数据然后传递给 webgl。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const vertexCode =`
//attribute为储存限定符只能用于储存顶点着色器的数据。
attribute vec4 a_Position;
attribute float a_PointSize;
void main(){
gl_Position = a_Postion;
gl_PointSize = a_PointSize;
}
`
//获取顶点着色器的坐标以及大小地址
const a_Postion = gl.getAttribLocation(program,'a_Position')
const a_PointSize = gl.getAttribLocation(program,'a_PointSize')
//修改顶点着色器的坐标和大小的值,坐标有四个值,但是如果传入的值少于四个,w默认1.0,其他默认0.0
//vertexAttrib[1,2,3,4]f,表示传入1,2,3,4个参数为浮点数
gl.vertexAttrib2f(a_Postion,0.5,0.0)
gl.vertexAttrib1f(a_PointSize,30.0)

# 三、使用鼠标绘制点

​ 在此基础上,使用鼠标监听事件监听用户的鼠标坐标然后传入顶点就行了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
canvas.addEventListener('click',(e)=>{
const width = canvas.width / 2
const height = canvas.height / 2
const glX = (e.offsetX-width) / width
const glY = (height-e.offsetY) / height
//获取顶点着色器的坐标以及大小地址
const a_Postion = gl.getAttribLocation(program,'a_Position')
const a_PointSize = gl.getAttribLocation(program,'a_PointSize')
//修改顶点着色器的坐标和大小的值,坐标有四个值,但是如果传入的值少于四个,w默认1.0,其他默认0.0
//vertexAttrib[1,2,3,4]f,表示传入1,2,3,4个参数为浮点数
gl.vertexAttrib2f(a_Postion,glX,glY)
gl.vertexAttrib1f(a_PointSize,30.0)
gl.drawArrays(gl.POINTS,0,1)
})

​ 可以发现每次绘制点的时候,不仅上个点没了,背景也变成默认的透明了,如果想要每次绘制的时候都有上个点,同时不用每次都重新绘制背景颜色的话。

1
2
//在获取上下文的时候传入 { preserveDrawingBuffer: true }这个参数即可
gl = canvas.getContext('WebGL', { preserveDrawingBuffer: true })

# 四、绘制有颜色的点

​ 可以使用给片元着色器上颜色改变点的颜色。与片元着色器相关的储存限定符有两个,一个是 uniform,另一个是 varying。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
1.使用uniform
const fragmentCode = `
//指定颜色的精度,如果不指定的话,会有问题。lowp为低精度,mediump为中精度,highp为高精度。
precision mediump float;
uniform vec4 u_FrageColor;
void main (){
gl_FragColor = u_FrageColor;
}
`
//在鼠标点击事件上加上这两行,注意使用uniform的时候如果第四个参数不传默认是0.0,也即透明。
const u_FrageColor = gl.getUniformLocation(program,'u_FrageColor')
gl.uniform4f(u_FrageColor,Math.random(),Math.random(),Math.random(),1.0)

2.使用varying
const vertexCode = `
//attribute为储存限定符只能用于储存顶点着色器的数据。
attribute vec4 a_Position;
attribute float a_PointSize;
//v_Color这个变量顶点着色器和片元着色器同名,给这个传a_Color,然后修改a_Color的值,片元着色器的v_Color也会跟着改变
attribute vec4 a_Color;
varying vec4 v_Color;
void main (){
gl_Position = a_Position;
gl_PointSize = a_PointSize;
v_Color = a_Color;
}
`
const fragmentCode = `
precision mediump float;
// uniform vec4 u_FrageColor;
varying vec4 v_Color;
void main (){
gl_FragColor = v_Color;
}
`

const a_Color = gl.getAttribLocation(program,'a_Color')
gl.vertexAttrib4f(a_Color,Math.random(),Math.random(),Math.random(),1.0)

​ 参考资料:

[2. WebGL 绘制点 | iceWebGL (ice-webgl.netlify.app)](https://ice-webgl.netlify.app/content/ 二、WebGL 基础 / 2. WebGL 绘制点.html)