游戏引擎渲染基础之WebGl

使用TS开发环境搭建

  • parcel网址

https://parceljs.org/

  • 安装命令
1
npm install -g parcel-bundler
  • 新建一个index.html文件
1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<canvas id="webgl" width="400" height="400"></canvas>
<script src="./main.ts"></script>
</body>
</html>
  • 同目录下新建一个main.ts的文件
1
console.log('hello ts')
  • 构建环境
1
parcel index.html
  • 打开网址

http://localhost:1234/

在console里面可以看到打印的”hello ts”, 接下来可以使用ts进行web的开发

  • 源码地址

https://gitee.com/limo/basic_webgl/tree/master/basic01

显示一个webgl的黑色屏幕

  • 工具方法用于不同浏览器获取WebGl的句柄
1
2
3
4
5
6
7
8
9
10
/**
* 获取当前的webgl句柄
*
* @param {HTMLCanvasElement} canvas
* @returns {WebGLRenderingContext}
*/
function getWebGLContext(canvas: HTMLCanvasElement) : WebGLRenderingContext {
let gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl')
return gl
}
  • 渲染的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 渲染的方法
*
*/
function render() {
// 清除颜色
gl.clearColor(0, 0, 0, 1)
// 使用深度测试
gl.enable(gl.DEPTH_TEST)
// 清除模式
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
// 启动混合模式(后面图片会用到)
gl.enable(gl.BLEND);
// 设置混合模式
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
// 设置视界
gl.viewport(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT)
// 动画帧方法(多次调用)
requestAnimationFrame(render)
}
  • 当前的源码
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
39
40
41
42
43
44
// 当前的gl句柄
let gl: WebGLRenderingContext = null
let CANVAS_WIDTH: number = 0
let CANVAS_HEIGHT: number = 0

function main() {
let canvas: HTMLCanvasElement = document.getElementById("webgl") as HTMLCanvasElement
CANVAS_WIDTH = canvas.width
CANVAS_HEIGHT = canvas.height
gl = getWebGLContext(canvas)
// 调用渲染
render()
}

main()

/**
* 渲染的方法
*
*/
function render() {
// 清除颜色
gl.clearColor(0, 0, 0, 1)
gl.enable(gl.DEPTH_TEST)
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
// 设置视界
gl.viewport(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT)
// gl.drawArrays(gl.TRIANGLES, 0, count)
requestAnimationFrame(render)
}

/**
* 获取当前的webgl句柄
*
* @param {HTMLCanvasElement} canvas
* @returns {WebGLRenderingContext}
*/
function getWebGLContext(canvas: HTMLCanvasElement) : WebGLRenderingContext {
let gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl')
return gl
}

  • 显示的内容

  • 源码地址

https://gitee.com/limo/basic_webgl/tree/master/basic02

画一个三角形

创建shader,顶点着色器和片面着色器

  • 创建并编译shader的工具方法
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
/**
* 创建shader程序的方法
*
* @param {WebGLRenderingContext} gl
* @param {number} type
* @param {string} source
*/
function createShader(gl: WebGLRenderingContext, type: number, source: string) {
// 通过类型创建着色器对象
const shader = gl.createShader(type)
// 将着色器代码装载到着色器对象内
gl.shaderSource(shader, source)
// 编译着色器
gl.compileShader(shader)
// 检测着色器的编译情况
const compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS)
if(!compiled){
// 获取当前的错误数据
const err = gl.getShaderInfoLog(shader)
console.error('Failed to compile shader: ' + err)
// 删除着色器
gl.deleteShader(shader)
return null
}
return shader
}
  • 基础的GLSL源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 顶点着色器的源码
const V_SHADER_SOURCE: string =`
attribute vec3 a_Position;
attribute vec3 a_Color;
varying vec3 v_Color;
void main() {
gl_Position = vec4(a_Position.x, a_Position.y, a_Position.z, 1.0);
v_Color = a_Color;
}`

// 片段着色器
const F_SHADER_SOURCE: string =`
precision mediump float;
varying vec3 v_Color;
void main() {
gl_FragColor = vec4(v_Color.r, v_Color.g, v_Color.b, 1.0);;
}`

创建Program 着色器程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 创建着色程序
*
* @param {WebGLRenderingContext} gl
* @param {string} vShaderSrc
* @param {string} fShaderSrc
* @returns {WebGLProgram}
*/
function createProgram(gl: WebGLRenderingContext, vShaderSrc?: string, fShaderSrc?: string) : WebGLProgram {
// 加载顶点和片段着色器
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vShaderSrc || V_SHADER_SOURCE)
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fShaderSrc || F_SHADER_SOURCE)
// 创建着色程序
const program = gl.createProgram()
// 将编译过的着色器附加到着色程序上
gl.attachShader(program, vertexShader)
gl.attachShader(program, fragmentShader)
// 链接着色程序
gl.linkProgram(program)
// 使用着色程序
gl.useProgram(program)
return program
}

初始化顶点数据和颜色数据的buffer

  • 初始化顶点和颜色
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
/**
* 初始化数据
*
* @param {WebGLRenderingContext} gl
*/
function initBuffer(gl: WebGLRenderingContext) {
// 定义顶点数据
const vertices = new Float32Array([
-0.5, 0.5, 0.5,
-0.5, -0.5, 0.5,
0.5, 0.5, 0.5,
])
// 写入顶点
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
const a_Position = gl.getAttribLocation(program, 'a_Position');
gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false,0, 0);
gl.enableVertexAttribArray(a_Position);

// 定义颜色数据
const colors = new Float32Array([
1.0, 0, 0,
1.0, 0, 0,
1.0, 0, 0,
])
// 写入颜色
const colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
const a_Color = gl.getAttribLocation(program, 'a_Color');
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_Color);

count = vertices.length / 3
console.log(count)
}
  • 画出缓存的方法 渲染的模式为三角形
  • count表示为顶点的数量(3个一组为顶点)
1
gl.drawArrays(gl.TRIANGLES, 0, count)
  • 当前的源码
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
console.log('hello ts')

// 顶点着色器的源码
const V_SHADER_SOURCE: string =`
attribute vec3 a_Position;
attribute vec3 a_Color;
varying vec3 v_Color;
void main() {
gl_Position = vec4(a_Position.x, a_Position.y, a_Position.z, 1.0);
v_Color = a_Color;
}`

// 片段着色器
const F_SHADER_SOURCE: string =`
precision mediump float;
varying vec3 v_Color;
void main() {
gl_FragColor = vec4(v_Color.r, v_Color.g, v_Color.b, 1.0);;
}`



// 当前的gl句柄
let gl: WebGLRenderingContext = null
let program: WebGLProgram = null
let CANVAS_WIDTH: number = 0
let CANVAS_HEIGHT: number = 0
let count = 0

/**
* 初始化数据
*
* @param {WebGLRenderingContext} gl
*/
function initBuffer(gl: WebGLRenderingContext) {
// 定义顶点数据
const vertices = new Float32Array([
-0.5, 0.5, 0.5,
-0.5, -0.5, 0.5,
0.5, 0.5, 0.5,
])
// 写入顶点
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
const a_Position = gl.getAttribLocation(program, 'a_Position');
gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false,0, 0);
gl.enableVertexAttribArray(a_Position);

// 定义颜色数据
const colors = new Float32Array([
1.0, 0, 0,
1.0, 0, 0,
1.0, 0, 0,
])
// 写入颜色
const colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
const a_Color = gl.getAttribLocation(program, 'a_Color');
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_Color);

count = vertices.length / 3
console.log(count)
}

function main() {
let canvas: HTMLCanvasElement = document.getElementById("webgl") as HTMLCanvasElement
CANVAS_WIDTH = canvas.width
CANVAS_HEIGHT = canvas.height
gl = getWebGLContext(canvas)
program = createProgram(gl)
initBuffer(gl)
// 调用渲染
render()
}

main()

/**
* 渲染的方法
*
*/
function render() {
// 清除颜色
gl.clearColor(0, 0, 0, 1)
gl.enable(gl.DEPTH_TEST)
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
// 设置视界
gl.viewport(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT)
gl.drawArrays(gl.TRIANGLES, 0, count)
requestAnimationFrame(render)
}

/********* 工具方法 ***********/

/**
* 获取当前的webgl句柄
*
* @param {HTMLCanvasElement} canvas
* @returns {WebGLRenderingContext}
*/
function getWebGLContext(canvas: HTMLCanvasElement) : WebGLRenderingContext {
let gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl')
return gl
}

/**
* 创建shader程序的方法
*
* @param {WebGLRenderingContext} gl
* @param {number} type
* @param {string} source
*/
function createShader(gl: WebGLRenderingContext, type: number, source: string) {
// 通过类型创建着色器对象
const shader = gl.createShader(type)
// 将着色器代码装载到着色器对象内
gl.shaderSource(shader, source)
// 编译着色器
gl.compileShader(shader)
// 检测着色器的编译情况
const compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS)
if(!compiled){
// 获取当前的错误数据
const err = gl.getShaderInfoLog(shader)
console.error('Failed to compile shader: ' + err)
// 删除着色器
gl.deleteShader(shader)
return null
}
return shader
}

/**
* 创建着色程序
*
* @param {WebGLRenderingContext} gl
* @param {string} vShaderSrc
* @param {string} fShaderSrc
* @returns {WebGLProgram}
*/
function createProgram(gl: WebGLRenderingContext, vShaderSrc?: string, fShaderSrc?: string) : WebGLProgram {
// 加载顶点和片段着色器
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vShaderSrc || V_SHADER_SOURCE)
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fShaderSrc || F_SHADER_SOURCE)
// 创建着色程序
const program = gl.createProgram()
// 将编译过的着色器附加到着色程序上
gl.attachShader(program, vertexShader)
gl.attachShader(program, fragmentShader)
// 链接着色程序
gl.linkProgram(program)
// 使用着色程序
gl.useProgram(program)
return program
}

  • 显示的内容

修改顶点颜色

渲染的颜色为RGB值,3个一组,表示3个顶点的颜色,每个顶点需要单独设置颜色

1
2
3
4
5
6
// 定义颜色数据
const colors = new Float32Array([
1.0, 0, 0,
0, 1.0, 0,
0, 0, 1.0,
])
  • 显示的内容

画出一个矩形

使用两个三角形拼成一个矩形

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 定义顶点数据
const vertices = new Float32Array([
-0.5, 0.5, 0.5,
-0.5, -0.5, 0.5,
0.5, 0.5, 0.5,
0.5, 0.5, 0.5,
-0.5, -0.5, 0.5,
0.5, -0.5, 0.5,
])
// 定义颜色数据
const colors = new Float32Array([
1.0, 0, 0,
1.0, 0, 0,
1.0, 0, 0,
1.0, 0, 0,
1.0, 0, 0,
1.0, 0, 0,
])
  • 显示的内容

矩形的构造方式

  • 顶点使用逆时针方式排序
  • 由两个三角形拼凑而成
  • x0 -> x1 -> x2
  • y0 -> y1 -> y2
  • 每个顶点对应一个颜色

使用TRIANGLE_STRIP方式构造三角形带

  • 使用共用顶点的方式构造多个三角形
  • 第二个三角形可以使用第一个三角形的两个顶点
  • 使用此方式可以减少顶点的数据量
  • 但是因为共用顶点,所以顶点颜色也是共用的,只能设置4个顶点颜色,适合创建单身模型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const vertices = new Float32Array([
-0.5, 0.5, 0.5,
-0.5, -0.5, 0.5,
0.5, 0.5, 0.5,
// 0.5, 0.5, 0.5,
// -0.5, -0.5, 0.5,
0.5, -0.5, 0.5,
])

// 定义颜色数据
const colors = new Float32Array([
1.0, 1.0, 0,
1.0, 1.0, 0,
1.0, 1.0, 0,
1.0, 1.0, 0,
])

  • 源码地址

https://gitee.com/limo/basic_webgl/tree/master/basic03

贴图渲染

  • 贴图渲染的GLSL源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 带图片的顶点着色器的源码
const V_IMAGE_SHADER_SOURCE: string =`
attribute vec2 a_Position;
attribute vec2 a_TexCoord;
varying vec2 v_TexCoord;
void main() {
gl_Position = vec4(a_Position.x, a_Position.y, 0.5, 1.0);
v_TexCoord = a_TexCoord;
}`

// 带图片的片段着色器
const F_IMAGE_SHADER_SOURCE: string =`
precision mediump float;
uniform sampler2D u_Sampler;
varying vec2 v_TexCoord;
void main() {
vec4 c = texture2D(u_Sampler,v_TexCoord);
gl_FragColor = c;
}`
  • 初始化顶点和uv缓存数据
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
/**
* 初始化带图片的buffer
*
* @param {WebGLRenderingContext} gl
*/
function initBufferWithImage(gl: WebGLRenderingContext) {
// 定义顶点数据
const vertices = new Float32Array([
-0.5, 0.5, 0.0, 1.0,
-0.5, -0.5, 0.0, 0.0,
0.5, 0.5, 1.0, 1.0,
0.5, -0.5, 1.0, 0.0,
])
// 写入顶点
const vertexBuffer = gl.createBuffer()
// 获取当前数据的比特长度
const bSize = vertices.BYTES_PER_ELEMENT
// 绑定缓冲区
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)
// 写入缓冲区
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)
// 获得着色程序中的参数的数据位置seek
const a_Position = gl.getAttribLocation(program, 'a_Position')
const a_TexCoord = gl.getAttribLocation(program, 'a_TexCoord')

gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, bSize * 4, 0)
gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, bSize * 4, bSize * 2)
gl.enableVertexAttribArray(a_Position)
gl.enableVertexAttribArray(a_TexCoord)

count = vertices.length / 4
console.log(count)
}
  • 初始化纹理加载(使用image加载图片)
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
/**
* 初始化纹理资源
*
* @param {WebGLRenderingContext} gl
* @param {WebGLProgram} program
*/
function initTextures(gl: WebGLRenderingContext, program: WebGLProgram) {
const texture = gl.createTexture()
const u_Sampler = gl.getUniformLocation(program, 'u_Sampler');
const image = new Image()
image.src = "./head.jpeg"
image.onload = () => {
// 对图形进行Y轴的反转
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL,1)
// 开启0号纹理单元
gl.activeTexture(gl.TEXTURE0)
// 绑定纹理对象
gl.bindTexture(gl.TEXTURE_2D, texture)
// 配置纹理参数 可以不用使用2的N次幂
gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
// 配置纹理图像
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image)
// 将0号纹理传递给着色器
gl.uniform1i(u_Sampler, 0)
render()
}
}
  • 修改main的方法
1
2
3
4
5
6
7
8
9
function main() {
let canvas: HTMLCanvasElement = document.getElementById("webgl") as HTMLCanvasElement
CANVAS_WIDTH = canvas.width
CANVAS_HEIGHT = canvas.height
gl = getWebGLContext(canvas)
program = createProgram(gl, V_IMAGE_SHADER_SOURCE, F_IMAGE_SHADER_SOURCE)
initBufferWithImage(gl)
initTextures(gl, program)
}

  • 源码地址

https://gitee.com/limo/basic_webgl/tree/master/basic04