WebGL教程http://www.webgl3d.cn/WebGL/在学习《WebGL教程》的过程中,经常会遇到有的章节中缺失源码的问题(后来才知道源码需要购买),比较影响学习效率,本人结合教程内容、网络搜索和自己的推导,尽最大努力补充了部分缺失的源码,并与教程的章节同步展示。
以下代码都基于HTML + Vue3实现。
第一章 WebGL零基础快速入门
5.绘制一个立方体—WebGL旋转变换
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="utf-8" />
<meta name="viewport" id="viewport" content="width=device-width,initial-scale=1,minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
<title>绘制一个立方体—WebGL旋转变换</title>
<style>
html {
box-sizing: border-box;
scroll-behavior: smooth !important;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
*,
*:before,
*:after {
box-sizing: inherit;
margin: 0;
padding: 0;
}
html,
body {
width: 100%;
height: 100%;
background-color: #fff;
counter-reset: boxnum;
}
body:before {
content: "";
position: fixed;
top: -10px;
left: 0;
z-index: 100;
width: 100%;
height: 10px;
-webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
-moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
}
.wrapper {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
overflow: hidden;
}
.container {
flex: 1;
display: flex;
justify-content: center;
}
</style>
</head>
<body>
<!-- 1920*1080 -->
<div id="wrap" class="wrapper">
<div class="container">
<canvas id="webgl" width="500" height="500" style="background-color: blue"></canvas>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.15/vue.global.min.js"></script>
<script>
const { createApp } = Vue
createApp({
setup() {
const init = () => {
// 通过getElementById()方法获取canvas画布
const canvas = document.getElementById('webgl')
// 通过方法getContext()获取WebGL上下文
const gl = canvas.getContext('webgl')
// 顶点着色器源码
const vertexShaderSource = `
// attribute声明vec4类型变量apos
attribute vec4 apos;
void main() {
// 设置几何体旋转角度为30度,并把角度值转化为弧度值
float radian = radians(30.0);
// 求解y轴旋转角度余弦值
float cos = cos(radian);
// 求解y轴旋转角度正弦值
float sin = sin(radian);
// y轴顺时针旋转矩阵
mat4 yMatrix = mat4(
sin, 0, cos, 0,
0, 1, 0, 0,
cos, 0, -sin, 0,
0, 0, 0, 1
);
// x轴旋转矩阵
mat4 xMatrix = mat4(
1, 0, 0, 0,
0, cos, -sin, 0,
0, sin, cos, 0,
0, 0, 0, 1
);
gl_Position = xMatrix * (yMatrix * apos); // 顶点位置
}
`
// 片元着色器源码
const fragShaderSource = `
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 定义片元颜色
}
`
// 初始化着色器
const program = initShader(gl, vertexShaderSource, fragShaderSource)
// 数组里48个元素,每间隔3个为一组,分别代表xyz轴上的坐标值
const data = new Float32Array([
// 前面的4个点
-0.5, 0.5, 0.5,
0.5, 0.5, 0.5,
0.5, -0.5, 0.5,
-0.5, -0.5, 0.5,
// 后面的4个点
-0.5, -0.5, -0.5,
-0.5, 0.5, -0.5,
0.5, 0.5, -0.5,
0.5, -0.5, -0.5,
// 用于绘制前后两个面连线的8个点
-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,
-0.5, -0.5, -0.5,
-0.5, -0.5, 0.5,
])
// 获取顶点着色器的位置变量apos,即aposLocation指向apos变量
const aposLocation = gl.getAttribLocation(program, 'apos')
// 创建缓冲区对象
const buffer = gl.createBuffer()
// 绑定缓冲区对象,激活buffer
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
// 顶点数组data数据传入缓冲区
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
// 缓冲区中的数据按照一定的规律传递给位置变量apos
gl.vertexAttribPointer(aposLocation, 3, gl.FLOAT, false, 0, 0)
// 允许数据传递
gl.enableVertexAttribArray(aposLocation)
// LINE_LOOP模式绘制前四个点
gl.drawArrays(gl.LINE_LOOP, 0, 4)
// LINE_LOOP模式从第五个点开始绘制四个点
gl.drawArrays(gl.LINE_LOOP, 4, 4)
// LINES模式绘制后8个点
gl.drawArrays(gl.LINES, 8, 8)
}
// 初始化着色器函数
// initShader()函数可以完成着色器代码的编译,然后在GPU上执行
const initShader = (gl, vertexShaderSource, fragmentShaderSource) => {
// 创建顶点着色器对象
const vertexShader = gl.createShader(gl.VERTEX_SHADER)
// 创建片元着色器对象
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
// 引入顶点、片元着色器源代码
gl.shaderSource(vertexShader, vertexShaderSource)
gl.shaderSource(fragmentShader, fragmentShaderSource)
// 编译顶点、片元着色器
gl.compileShader(vertexShader)
gl.compileShader(fragmentShader)
// 创建程序对象program
const program = gl.createProgram()
// 附着顶点着色器和片元着色器到program
gl.attachShader(program, vertexShader)
gl.attachShader(program, fragmentShader)
// 链接program
gl.linkProgram(program)
// 使用program
gl.useProgram(program)
// 返回程序program对象
return program
}
Vue.onMounted(init)
return { init }
}
}).mount('#wrap')
</script>
</body>
</html>
若想让立方体逆时针旋转则将顶点着色器源码替换为以下代码即可:
// 顶点着色器源码
const vertexShaderSource = `
// attribute声明vec4类型变量apos
attribute vec4 apos;
void main() {
// 设置几何体旋转角度为30度,并把角度值转化为弧度值
float radian = radians(30.0);
// 求解y轴旋转角度余弦值
float cos = cos(radian);
// 求解y轴旋转角度正弦值
float sin = sin(radian);
// y轴逆时针旋转矩阵
mat4 yMatrix = mat4(
cos, 0, sin, 0,
0, 1, 0, 0,
-sin, 0, cos, 0,
0, 0, 0, 1
);
// x轴旋转矩阵
mat4 xMatrix = mat4(
1, 0, 0, 0,
0, cos, -sin, 0,
0, sin, cos, 0,
0, 0, 0, 1
);
gl_Position = xMatrix * (yMatrix * apos); // 顶点位置
}
`
6.WebGL顶点索引绘制
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="utf-8" />
<meta name="viewport" id="viewport" content="width=device-width,initial-scale=1,minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
<title>WebGL顶点索引绘制</title>
<style>
html {
box-sizing: border-box;
scroll-behavior: smooth !important;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
*,
*:before,
*:after {
box-sizing: inherit;
margin: 0;
padding: 0;
}
html,
body {
width: 100%;
height: 100%;
background-color: #fff;
counter-reset: boxnum;
}
body:before {
content: "";
position: fixed;
top: -10px;
left: 0;
z-index: 100;
width: 100%;
height: 10px;
-webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
-moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
}
.wrapper {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
overflow: hidden;
}
.container {
flex: 1;
display: flex;
justify-content: center;
}
</style>
</head>
<body>
<!-- 1920*1080 -->
<div id="wrap" class="wrapper">
<div class="container">
<canvas id="webgl" width="500" height="500" style="background-color: blue"></canvas>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.15/vue.global.min.js"></script>
<script>
const { createApp } = Vue
createApp({
setup() {
const init = () => {
// 通过getElementById()方法获取canvas画布
const canvas = document.getElementById('webgl')
// 通过方法getContext()获取WebGL上下文
const gl = canvas.getContext('webgl')
// 顶点着色器源码
const vertexShaderSource = `
// attribute声明vec4类型变量apos
attribute vec4 apos;
void main() {
// 设置几何体旋转角度为30度,并把角度值转化为弧度值
float radian = radians(30.0);
// 求解y轴旋转角度余弦值
float cos = cos(radian);
// 求解y轴旋转角度正弦值
float sin = sin(radian);
// y轴逆时针旋转矩阵
// mat4 yMatrix = mat4(
// cos, 0, sin, 0,
// 0, 1, 0, 0,
// -sin, 0, cos, 0,
// 0, 0, 0, 1
// );
// y轴顺时针旋转矩阵
mat4 yMatrix = mat4(
sin, 0, cos, 0,
0, 1, 0, 0,
cos, 0, -sin, 0,
0, 0, 0, 1
);
// x轴旋转矩阵
mat4 xMatrix = mat4(
1, 0, 0, 0,
0, cos, -sin, 0,
0, sin, cos, 0,
0, 0, 0, 1
);
gl_Position = xMatrix * (yMatrix * apos); // 顶点位置
}
`
// 片元着色器源码
const fragShaderSource = `
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // 定义片元颜色
}
`
// 初始化着色器
const program = initShader(gl, vertexShaderSource, fragShaderSource)
// 数组里48个元素,每间隔3个为一组,分别代表xyz轴上的坐标值
const data = new Float32Array([
// 前面的4个点
0.5, 0.5, 0.5, // 顶点0
-0.5, 0.5, 0.5, // 顶点1
-0.5, -0.5, 0.5, // 顶点2
0.5, -0.5, 0.5, // 顶点3
// 后面的4个点
0.5, 0.5, -0.5, // 顶点4
-0.5, 0.5, -0.5, // 顶点5
-0.5, -0.5, -0.5, // 顶点6
0.5, -0.5, -0.5 // 顶点7
])
// 创建顶点缓冲区对象
const buffer = gl.createBuffer()
// 绑定顶点缓冲区对象,激活buffer
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
// 顶点数组data数据传入缓冲区
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
// 顶点索引数组
const indexes = new Uint8Array([
// 前四个点对应索引值
0, 1, 2, 3, // gl.LINE_LOOP模式四个点绘制一个矩形框
//后四个顶点对应索引值
4, 5, 6, 7, // gl.LINE_LOOP模式四个点绘制一个矩形框
// 前后对应点对应索引值
0, 4, // 两个点绘制一条直线
1, 5, // 两个点绘制一条直线
2, 6, // 两个点绘制一条直线
3, 7 // 两个点绘制一条直线
])
// 创建顶点索引缓冲区对象
const indexesBuffer = gl.createBuffer()
// 绑定顶点索引缓冲区对象,激活indexesBuffer
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexesBuffer)
// 索引数组indexes数据传入缓冲区
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexes, gl.STATIC_DRAW)
// 获取顶点着色器的位置变量apos,即aposLocation指向apos变量
const aposLocation = gl.getAttribLocation(program, 'apos')
// 缓冲区中的数据按照一定的规律传递给位置变量apos
gl.vertexAttribPointer(aposLocation, 3, gl.FLOAT, false, 0, 0)
// 允许数据传递
gl.enableVertexAttribArray(aposLocation)
// LINE_LOOP模式绘制前四个点
gl.drawElements(gl.LINE_LOOP, 4, gl.UNSIGNED_BYTE, 0)
// LINE_LOOP模式从第五个点开始绘制四个点
gl.drawElements(gl.LINE_LOOP, 4, gl.UNSIGNED_BYTE, 4)
// LINES模式绘制后8个点
gl.drawElements(gl.LINES, 8, gl.UNSIGNED_BYTE, 8)
}
// 初始化着色器函数
// initShader()函数可以完成着色器代码的编译,然后在GPU上执行
const initShader = (gl, vertexShaderSource, fragmentShaderSource) => {
// 创建顶点着色器对象
const vertexShader = gl.createShader(gl.VERTEX_SHADER)
// 创建片元着色器对象
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
// 引入顶点、片元着色器源代码
gl.shaderSource(vertexShader, vertexShaderSource)
gl.shaderSource(fragmentShader, fragmentShaderSource)
// 编译顶点、片元着色器
gl.compileShader(vertexShader)
gl.compileShader(fragmentShader)
// 创建程序对象program
const program = gl.createProgram()
// 附着顶点着色器和片元着色器到program
gl.attachShader(program, vertexShader)
gl.attachShader(program, fragmentShader)
// 链接program
gl.linkProgram(program)
// 使用program
gl.useProgram(program)
// 返回程序program对象
return program
}
Vue.onMounted(init)
return { init }
}
}).mount('#wrap')
</script>
</body>
</html>
7.varying变量和颜色插值
课程源码
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="utf-8" />
<meta name="viewport" id="viewport" content="width=device-width,initial-scale=1,minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
<titlevarying变量和颜色插值课程源码</title>
<style>
html {
box-sizing: border-box;
scroll-behavior: smooth !important;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
*,
*:before,
*:after {
box-sizing: inherit;
margin: 0;
padding: 0;
}
html,
body {
width: 100%;
height: 100%;
background-color: #fff;
counter-reset: boxnum;
}
body:before {
content: "";
position: fixed;
top: -10px;
left: 0;
z-index: 100;
width: 100%;
height: 10px;
-webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
-moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
}
.wrapper {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
overflow: hidden;
}
.container {
flex: 1;
display: flex;
justify-content: center;
}
</style>
</head>
<body>
<!-- 1920*1080 -->
<div id="wrap" class="wrapper">
<div class="container">
<canvas id="webgl" width="500" height="500"></canvas>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.15/vue.global.min.js"></script>
<script>
const { createApp } = Vue
createApp({
setup() {
const init = () => {
// 通过getElementById()方法获取canvas画布
const canvas = document.getElementById('webgl')
// 通过方法getContext()获取WebGL上下文
const gl = canvas.getContext('webgl')
// 顶点着色器源码
const vertexShaderSource = `
// attribute声明vec4类型变量apos
attribute vec4 apos;
// attribute声明顶点颜色变量
attribute vec4 a_color;
// varying声明顶点颜色插值后变量
varying vec4 v_color;
void main() {
gl_Position = apos; // 顶点位置
// 顶点颜色插值计算
v_color = a_color;
}
`
// 片元着色器源码
const fragShaderSource = `
// 所有float类型数据的精度是lowp
precision lowp float;
// 接收顶点着色器中v_color数据
varying vec4 v_color;
void main() {
gl_FragColor = v_color; // 插值后颜色数据赋值给对应的片元
}
`
// 初始化着色器
const program = initShader(gl, vertexShaderSource, fragShaderSource)
// 创建顶点位置数据数组data,存储两个顶点(-0.5, 0.5)、(0.5, 0.5)
const data = new Float32Array([-0.5, 0.5, 0.5, 0.5])
// 创建顶点缓冲区对象
const buffer = gl.createBuffer()
// 绑定顶点缓冲区对象,激活buffer
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
// 顶点数组data数据传入缓冲区
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
// 获取顶点着色器的位置变量apos,即aposLocation指向apos变量
const aposLocation = gl.getAttribLocation(program, 'apos')
// 缓冲区中的数据按照一定的规律传递给位置变量apos
gl.vertexAttribPointer(aposLocation, 2, gl.FLOAT, false, 0, 0)
// 允许数据传递
gl.enableVertexAttribArray(aposLocation)
// 创建顶点颜色数组colorData,存储两个顶点对应RGB颜色值(0, 0, 1)、(1, 0, 0)
const colorData = new Float32Array([0, 0, 1, 1, 0, 0])
// 创建缓冲区colorBuffer
const colorBuffer = gl.createBuffer()
// 绑定顶点颜色缓冲区对象
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer)
// 传入顶点颜色数据colorData
gl.bufferData(gl.ARRAY_BUFFER, colorData, gl.STATIC_DRAW)
// 获取顶点着色器的颜色变量a_color
const a_color = gl.getAttribLocation(program, 'a_color')
// 缓冲区中的数据按照一定的规律传递给颜色变量a_color
gl.vertexAttribPointer(a_color, 3, gl.FLOAT, false, 0, 0)
// 允许数据传递
gl.enableVertexAttribArray(a_color)
// 绘制线条插值效果
gl.drawArrays(gl.LINES, 0, 2)
}
// 初始化着色器函数
// initShader()函数可以完成着色器代码的编译,然后在GPU上执行
const initShader = (gl, vertexShaderSource, fragmentShaderSource) => {
// 创建顶点着色器对象
const vertexShader = gl.createShader(gl.VERTEX_SHADER)
// 创建片元着色器对象
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
// 引入顶点、片元着色器源代码
gl.shaderSource(vertexShader, vertexShaderSource)
gl.shaderSource(fragmentShader, fragmentShaderSource)
// 编译顶点、片元着色器
gl.compileShader(vertexShader)
gl.compileShader(fragmentShader)
// 创建程序对象program
const program = gl.createProgram()
// 附着顶点着色器和片元着色器到program
gl.attachShader(program, vertexShader)
gl.attachShader(program, fragmentShader)
// 链接program
gl.linkProgram(program)
// 使用program
gl.useProgram(program)
// 返回程序program对象
return program
}
Vue.onMounted(init)
return { init }
}
}).mount('#wrap')
</script>
</body>
</html>
课后练习1.创建一个彩色三角形
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="utf-8" />
<meta name="viewport" id="viewport" content="width=device-width,initial-scale=1,minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
<title>varying变量和颜色插值课后练习1</title>
<style>
html {
box-sizing: border-box;
scroll-behavior: smooth !important;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
*,
*:before,
*:after {
box-sizing: inherit;
margin: 0;
padding: 0;
}
html,
body {
width: 100%;
height: 100%;
background-color: #fff;
counter-reset: boxnum;
}
body:before {
content: "";
position: fixed;
top: -10px;
left: 0;
z-index: 100;
width: 100%;
height: 10px;
-webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
-moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
}
.wrapper {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
overflow: hidden;
}
.container {
flex: 1;
display: flex;
justify-content: center;
}
</style>
</head>
<body>
<!-- 1920*1080 -->
<div id="wrap" class="wrapper">
<div class="container">
<canvas id="webgl" width="500" height="500"></canvas>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.15/vue.global.min.js"></script>
<script>
const { createApp } = Vue
createApp({
setup() {
const init = () => {
// 通过getElementById()方法获取canvas画布
const canvas = document.getElementById('webgl')
// 通过方法getContext()获取WebGL上下文
const gl = canvas.getContext('webgl')
// 顶点着色器源码
const vertexShaderSource = `
// attribute声明vec4类型变量apos
attribute vec4 apos;
// attribute声明顶点颜色变量
attribute vec4 a_color;
// varying声明顶点颜色插值后变量
varying vec4 v_color;
void main() {
gl_Position = apos; // 顶点位置
// 顶点颜色插值计算
v_color = a_color;
}
`
// 片元着色器源码
const fragShaderSource = `
// 所有float类型数据的精度是lowp
precision lowp float;
// 接收顶点着色器中v_color数据
varying vec4 v_color;
void main() {
gl_FragColor = v_color; // 插值后颜色数据赋值给对应的片元
}
`
// 初始化着色器
const program = initShader(gl, vertexShaderSource, fragShaderSource)
// 创建顶点位置数据数组data,存储3个顶点(-0.5, 0.5)、(0.5, 0.5)、(0.5, -0.5)
const data = new Float32Array([-0.5, 0.5, 0.5, 0.5, 0.5, -0.5])
// 创建顶点缓冲区对象
const buffer = gl.createBuffer()
// 绑定顶点缓冲区对象,激活buffer
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
// 顶点数组data数据传入缓冲区
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
// 获取顶点着色器的位置变量apos,即aposLocation指向apos变量
const aposLocation = gl.getAttribLocation(program, 'apos')
// 缓冲区中的数据按照一定的规律传递给位置变量apos
gl.vertexAttribPointer(aposLocation, 2, gl.FLOAT, false, 0, 0)
// 允许数据传递
gl.enableVertexAttribArray(aposLocation)
// 创建顶点颜色数组colorData,存储3个顶点对应RGB颜色值(0, 0, 1)、(1, 0, 0)、(0, 0, 1)
const colorData = new Float32Array([0, 0, 1, 1, 0, 0, 0, 0, 1])
// 创建缓冲区colorBuffer
const colorBuffer = gl.createBuffer()
// 绑定顶点颜色缓冲区对象
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer)
// 传入顶点颜色数据colorData
gl.bufferData(gl.ARRAY_BUFFER, colorData, gl.STATIC_DRAW)
// 获取顶点着色器的颜色变量a_color
const a_color = gl.getAttribLocation(program, 'a_color')
// 缓冲区中的数据按照一定的规律传递给颜色变量a_color
gl.vertexAttribPointer(a_color, 3, gl.FLOAT, false, 0, 0)
// 允许数据传递
gl.enableVertexAttribArray(a_color)
// 执行绘制三角形命令
gl.drawArrays(gl.TRIANGLES, 0, 3)
}
// 初始化着色器函数
// initShader()函数可以完成着色器代码的编译,然后在GPU上执行
const initShader = (gl, vertexShaderSource, fragmentShaderSource) => {
// 创建顶点着色器对象
const vertexShader = gl.createShader(gl.VERTEX_SHADER)
// 创建片元着色器对象
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
// 引入顶点、片元着色器源代码
gl.shaderSource(vertexShader, vertexShaderSource)
gl.shaderSource(fragmentShader, fragmentShaderSource)
// 编译顶点、片元着色器
gl.compileShader(vertexShader)
gl.compileShader(fragmentShader)
// 创建程序对象program
const program = gl.createProgram()
// 附着顶点着色器和片元着色器到program
gl.attachShader(program, vertexShader)
gl.attachShader(program, fragmentShader)
// 链接program
gl.linkProgram(program)
// 使用program
gl.useProgram(program)
// 返回程序program对象
return program
}
Vue.onMounted(init)
return { init }
}
}).mount('#wrap')
</script>
</body>
</html>
课后练习2.两个单色三角面(不同颜色)
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="utf-8" />
<meta name="viewport" id="viewport" content="width=device-width,initial-scale=1,minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
<title>varying变量和颜色插值课后练习2</title>
<style>
html {
box-sizing: border-box;
scroll-behavior: smooth !important;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
*,
*:before,
*:after {
box-sizing: inherit;
margin: 0;
padding: 0;
}
html,
body {
width: 100%;
height: 100%;
background-color: #fff;
counter-reset: boxnum;
}
body:before {
content: "";
position: fixed;
top: -10px;
left: 0;
z-index: 100;
width: 100%;
height: 10px;
-webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
-moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
}
.wrapper {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
overflow: hidden;
}
.container {
flex: 1;
display: flex;
justify-content: center;
}
</style>
</head>
<body>
<!-- 1920*1080 -->
<div id="wrap" class="wrapper">
<div class="container">
<canvas id="webgl" width="500" height="500"></canvas>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.15/vue.global.min.js"></script>
<script>
const { createApp } = Vue
createApp({
setup() {
const init = () => {
// 通过getElementById()方法获取canvas画布
const canvas = document.getElementById('webgl')
// 通过方法getContext()获取WebGL上下文
const gl = canvas.getContext('webgl')
// 顶点着色器源码
const vertexShaderSource = `
// attribute声明vec4类型变量apos
attribute vec4 apos;
// attribute声明顶点颜色变量
attribute vec4 a_color;
// varying声明顶点颜色插值后变量
varying vec4 v_color;
void main() {
gl_Position = apos; // 顶点位置
// 顶点颜色插值计算
v_color = a_color;
}
`
// 片元着色器源码
const fragShaderSource = `
// 所有float类型数据的精度是lowp
precision lowp float;
// 接收顶点着色器中v_color数据
varying vec4 v_color;
void main() {
gl_FragColor = v_color; // 插值后颜色数据赋值给对应的片元
}
`
// 初始化着色器
const program = initShader(gl, vertexShaderSource, fragShaderSource)
// 创建顶点位置数据数组data,存储6个顶点
const data = 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 // 第二个三角形的三个点
])
// 创建顶点缓冲区对象
const buffer = gl.createBuffer()
// 绑定顶点缓冲区对象,激活buffer
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
// 顶点数组data数据传入缓冲区
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
// 获取顶点着色器的位置变量apos,即aposLocation指向apos变量
const aposLocation = gl.getAttribLocation(program, 'apos')
// 缓冲区中的数据按照一定的规律传递给位置变量apos
gl.vertexAttribPointer(aposLocation, 2, gl.FLOAT, false, 0, 0)
// 允许数据传递
gl.enableVertexAttribArray(aposLocation)
// 创建顶点颜色数组colorData,存储6个顶点对应RGB颜色值
const colorData = new Float32Array([
// 三个红色点
1, 0, 0,
1, 0, 0,
1, 0, 0,
// 三个蓝色点
0, 0, 1,
0, 0, 1,
0, 0, 1
])
// 创建缓冲区colorBuffer
const colorBuffer = gl.createBuffer()
// 绑定顶点颜色缓冲区对象
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer)
// 传入顶点颜色数据colorData
gl.bufferData(gl.ARRAY_BUFFER, colorData, gl.STATIC_DRAW)
// 获取顶点着色器的颜色变量a_color
const a_color = gl.getAttribLocation(program, 'a_color')
// 缓冲区中的数据按照一定的规律传递给颜色变量a_color
gl.vertexAttribPointer(a_color, 3, gl.FLOAT, false, 0, 0)
// 允许数据传递
gl.enableVertexAttribArray(a_color)
// 执行绘制三角形命令
gl.drawArrays(gl.TRIANGLES, 0, 3)
gl.drawArrays(gl.TRIANGLES, 3, 3)
}
// 初始化着色器函数
// initShader()函数可以完成着色器代码的编译,然后在GPU上执行
const initShader = (gl, vertexShaderSource, fragmentShaderSource) => {
// 创建顶点着色器对象
const vertexShader = gl.createShader(gl.VERTEX_SHADER)
// 创建片元着色器对象
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
// 引入顶点、片元着色器源代码
gl.shaderSource(vertexShader, vertexShaderSource)
gl.shaderSource(fragmentShader, fragmentShaderSource)
// 编译顶点、片元着色器
gl.compileShader(vertexShader)
gl.compileShader(fragmentShader)
// 创建程序对象program
const program = gl.createProgram()
// 附着顶点着色器和片元着色器到program
gl.attachShader(program, vertexShader)
gl.attachShader(program, fragmentShader)
// 链接program
gl.linkProgram(program)
// 使用program
gl.useProgram(program)
// 返回程序program对象
return program
}
Vue.onMounted(init)
return { init }
}
}).mount('#wrap')
</script>
</body>
</html>
课后练习3.颜色插值(顶点位置、颜色使用一个缓冲区存储)
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="utf-8" />
<meta name="viewport" id="viewport" content="width=device-width,initial-scale=1,minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
<title>varying变量和颜色插值课后练习3</title>
<style>
html {
box-sizing: border-box;
scroll-behavior: smooth !important;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
*,
*:before,
*:after {
box-sizing: inherit;
margin: 0;
padding: 0;
}
html,
body {
width: 100%;
height: 100%;
background-color: #fff;
counter-reset: boxnum;
}
body:before {
content: "";
position: fixed;
top: -10px;
left: 0;
z-index: 100;
width: 100%;
height: 10px;
-webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
-moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
}
.wrapper {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
overflow: hidden;
}
.container {
flex: 1;
display: flex;
justify-content: center;
}
</style>
</head>
<body>
<!-- 1920*1080 -->
<div id="wrap" class="wrapper">
<div class="container">
<canvas id="webgl" width="500" height="500"></canvas>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.15/vue.global.min.js"></script>
<script>
const { createApp } = Vue
createApp({
setup() {
const init = () => {
// 通过getElementById()方法获取canvas画布
const canvas = document.getElementById('webgl')
// 通过方法getContext()获取WebGL上下文
const gl = canvas.getContext('webgl')
// 顶点着色器源码
const vertexShaderSource = `
// attribute声明vec4类型变量apos
attribute vec4 apos;
// attribute声明顶点颜色变量
attribute vec4 a_color;
// varying声明顶点颜色插值后变量
varying vec4 v_color;
void main() {
gl_Position = apos; // 顶点位置
// 顶点颜色插值计算
v_color = a_color;
}
`
// 片元着色器源码
const fragShaderSource = `
// 所有float类型数据的精度是lowp
precision lowp float;
// 接收顶点着色器中v_color数据
varying vec4 v_color;
void main() {
gl_FragColor = v_color; // 插值后颜色数据赋值给对应的片元
}
`
// 初始化着色器
const program = initShader(gl, vertexShaderSource, fragShaderSource)
// 创建顶点位置数据数组data,存储两个顶点(-0.5, 0.5)、(0.5, 0.5)
// 存储两个顶点对应RGB颜色值(0, 0, 1)、(1, 0, 0)
const data = new Float32Array([
-0.5, 0.5,
0, 0, 1,
0.5, 0.5,
1, 0, 0
])
// 创建顶点缓冲区对象,传入顶点颜色、位置数据
const buffer = gl.createBuffer()
// 绑定顶点缓冲区对象,激活buffer
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
// 顶点数组data数据传入缓冲区
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
// 获取顶点着色器的位置变量apos,即aposLocation指向apos变量
const aposLocation = gl.getAttribLocation(program, 'apos')
// 4表示data数组一个元素占据的字节数
// 倒数第二个参数4*5表示每5个元素是一个选择单元
// 第2个参数2表示从5元素组成的一个选择单元中选择前2个作为顶点位置数据
gl.vertexAttribPointer(aposLocation, 2, gl.FLOAT, false, 4 * 5, 4 * 2)
// 允许数据传递
gl.enableVertexAttribArray(aposLocation)
// 获取顶点着色器的颜色变量a_color
const a_color = gl.getAttribLocation(program, 'a_color')
// 最后一个参数4*2表示5元素组成的一个选择单元中偏移2个元素
// 第2个参数3表示从5元素组成的一个选择单元中选择后三个作为顶点颜色数据
gl.vertexAttribPointer(a_color, 3, gl.FLOAT, false, 4 * 5, 4 * 2)
// 允许数据传递
gl.enableVertexAttribArray(a_color)
// 绘制线条
gl.drawArrays(gl.LINES, 0, 2)
}
// 初始化着色器函数
// initShader()函数可以完成着色器代码的编译,然后在GPU上执行
const initShader = (gl, vertexShaderSource, fragmentShaderSource) => {
// 创建顶点着色器对象
const vertexShader = gl.createShader(gl.VERTEX_SHADER)
// 创建片元着色器对象
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
// 引入顶点、片元着色器源代码
gl.shaderSource(vertexShader, vertexShaderSource)
gl.shaderSource(fragmentShader, fragmentShaderSource)
// 编译顶点、片元着色器
gl.compileShader(vertexShader)
gl.compileShader(fragmentShader)
// 创建程序对象program
const program = gl.createProgram()
// 附着顶点着色器和片元着色器到program
gl.attachShader(program, vertexShader)
gl.attachShader(program, fragmentShader)
// 链接program
gl.linkProgram(program)
// 使用program
gl.useProgram(program)
// 返回程序program对象
return program
}
Vue.onMounted(init)
return { init }
}
}).mount('#wrap')
</script>
</body>
</html>
8.立方体-每个面一种颜色
课程源码
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="utf-8" />
<meta name="viewport" id="viewport" content="width=device-width,initial-scale=1,minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
<title>立方体-每个面一种颜色</title>
<style>
html {
box-sizing: border-box;
scroll-behavior: smooth !important;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
*,
*:before,
*:after {
box-sizing: inherit;
margin: 0;
padding: 0;
}
html,
body {
width: 100%;
height: 100%;
background-color: #fff;
counter-reset: boxnum;
}
body:before {
content: "";
position: fixed;
top: -10px;
left: 0;
z-index: 100;
width: 100%;
height: 10px;
-webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
-moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
}
.wrapper {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
overflow: hidden;
}
.container {
flex: 1;
display: flex;
justify-content: center;
}
</style>
</head>
<body>
<!-- 1920*1080 -->
<div id="wrap" class="wrapper">
<div class="container">
<canvas id="webgl" width="500" height="500" style="background-color: blue"></canvas>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.15/vue.global.min.js"></script>
<script>
const { createApp } = Vue
createApp({
setup() {
const init = () => {
// 通过getElementById()方法获取canvas画布
const canvas = document.getElementById('webgl')
// 通过方法getContext()获取WebGL上下文
const gl = canvas.getContext('webgl')
// 顶点着色器源码
const vertexShaderSource = `
// attribute声明vec4类型变量apos
attribute vec4 apos;
// attribute声明顶点颜色变量
attribute vec4 a_color;
// varying声明顶点颜色插值后变量
varying vec4 v_color;
void main() {
// 设置几何体旋转角度为30度,并把角度值转化为弧度值
float radian = radians(30.0);
// 求解y轴旋转角度余弦值
float cos = cos(radian);
// 求解y轴旋转角度正弦值
float sin = sin(radian);
// y轴逆时针旋转矩阵
// mat4 yMatrix = mat4(
// cos, 0, sin, 0,
// 0, 1, 0, 0,
// -sin, 0, cos, 0,
// 0, 0, 0, 1
// );
// y轴顺时针旋转矩阵
mat4 yMatrix = mat4(
sin, 0, cos, 0,
0, 1, 0, 0,
cos, 0, -sin, 0,
0, 0, 0, 1
);
// x轴旋转矩阵
mat4 xMatrix = mat4(
1, 0, 0, 0,
0, cos, -sin, 0,
0, sin, cos, 0,
0, 0, 0, 1
);
gl_Position = xMatrix * (yMatrix * apos); // 顶点位置
// 顶点颜色插值计算
v_color = a_color;
}
`
// 片元着色器源码
const fragShaderSource = `
// 所有float类型数据的精度是lowp
precision lowp float;
// 接收顶点着色器中v_color数据
varying vec4 v_color;
void main() {
gl_FragColor = v_color; // 插值后颜色数据赋值给对应的片元
}
`
// 初始化着色器
const program = initShader(gl, vertexShaderSource, fragShaderSource)
// 绘制立方体线框
drawCubeLine(gl, program)
// 绘制立方体6个面
drawCubeSide(gl, program)
}
// 绘制立方体线框
const drawCubeLine = (gl, program) => {
// 数组里48个元素,每间隔3个为一组,分别代表xyz轴上的坐标值
const data = new Float32Array([
// 前面的4个点
0.5, 0.5, 0.5, // 顶点0
-0.5, 0.5, 0.5, // 顶点1
-0.5, -0.5, 0.5, // 顶点2
0.5, -0.5, 0.5, // 顶点3
// 后面的4个点
0.5, 0.5, -0.5, // 顶点4
-0.5, 0.5, -0.5, // 顶点5
-0.5, -0.5, -0.5, // 顶点6
0.5, -0.5, -0.5 // 顶点7
])
// 创建顶点缓冲区对象
const buffer = gl.createBuffer()
// 绑定顶点缓冲区对象,激活buffer
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
// 顶点数组data数据传入缓冲区
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
// 顶点索引数组
const indexes = new Uint8Array([
// 前四个点对应索引值
0, 1, 2, 3, // gl.LINE_LOOP模式四个点绘制一个矩形框
//后四个顶点对应索引值
4, 5, 6, 7, // gl.LINE_LOOP模式四个点绘制一个矩形框
// 前后对应点对应索引值
0, 4, // 两个点绘制一条直线
1, 5, // 两个点绘制一条直线
2, 6, // 两个点绘制一条直线
3, 7 // 两个点绘制一条直线
])
// 创建顶点索引缓冲区对象
const indexesBuffer = gl.createBuffer()
// 绑定顶点索引缓冲区对象,激活indexesBuffer
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexesBuffer)
// 索引数组indexes数据传入缓冲区
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexes, gl.STATIC_DRAW)
// 获取顶点着色器的位置变量apos,即aposLocation指向apos变量
const aposLocation = gl.getAttribLocation(program, 'apos')
// 缓冲区中的数据按照一定的规律传递给位置变量apos
gl.vertexAttribPointer(aposLocation, 3, gl.FLOAT, false, 0, 0)
// 允许数据传递
gl.enableVertexAttribArray(aposLocation)
// LINE_LOOP模式绘制前四个点
gl.drawElements(gl.LINE_LOOP, 4, gl.UNSIGNED_BYTE, 0)
// LINE_LOOP模式从第五个点开始绘制四个点
gl.drawElements(gl.LINE_LOOP, 4, gl.UNSIGNED_BYTE, 4)
// LINES模式绘制后8个点
gl.drawElements(gl.LINES, 8, gl.UNSIGNED_BYTE, 8)
}
// 绘制立方体6个面
const drawCubeSide = (gl, program) => {
// 创建顶点位置数据数组data,Javascript中小数点前面的0可以省略
const data = new Float32Array([
// 面1
.5, .5, .5,
-.5, .5, .5,
-.5, -.5, .5,
.5, .5, .5,
-.5, -.5, .5,
.5, -.5, .5,
// 面2
.5, .5, .5,
.5, -.5, .5,
.5, -.5, -.5,
.5, .5, .5,
.5, -.5, -.5,
.5, .5, -.5,
// 面3
.5, .5, .5,
.5, .5, -.5,
-.5, .5, -.5,
.5, .5, .5,
-.5, .5, -.5,
-.5, .5, .5,
// 面4
-.5, .5, .5,
-.5, .5, -.5,
-.5, -.5, -.5,
-.5, .5, .5,
-.5, -.5, -.5,
-.5, -.5, .5,
// 面5
-.5, -.5, -.5,
.5, -.5, -.5,
.5, -.5, .5,
-.5, -.5, -.5,
.5, -.5, .5,
-.5, -.5, .5,
// 面6
.5, -.5, -.5,
-.5, -.5, -.5,
-.5, .5, -.5,
.5, -.5, -.5,
-.5, .5, -.5,
.5, .5, -.5
])
// 创建顶点缓冲区对象
const buffer = gl.createBuffer()
// 绑定顶点缓冲区对象,激活buffer
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
// 顶点数组data数据传入缓冲区
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
// 获取顶点着色器的位置变量apos,即aposLocation指向apos变量
const aposLocation = gl.getAttribLocation(program, 'apos')
// 缓冲区中的数据按照一定的规律传递给位置变量apos
gl.vertexAttribPointer(aposLocation, 3, gl.FLOAT, false, 0, 0)
// 允许数据传递
gl.enableVertexAttribArray(aposLocation)
// 创建顶点颜色数组colorData
const colorData = new Float32Array([
1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, // 面1红色
0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, // 面2绿色
0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, // 面3蓝色
1,1,0, 1,1,0, 1,1,0, 1,1,0, 1,1,0, 1,1,0, // 面4黄色
0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, // 面5黑色
1,1,1, 1,1,1, 1,1,1, 1,1,1, 1,1,1, 1,1,1 // 面6白色
])
// 创建缓冲区colorBuffer
const colorBuffer = gl.createBuffer()
// 绑定顶点颜色缓冲区对象
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer)
// 传入顶点颜色数据colorData
gl.bufferData(gl.ARRAY_BUFFER, colorData, gl.STATIC_DRAW)
// 获取顶点着色器的颜色变量a_color
const a_color = gl.getAttribLocation(program, 'a_color')
// 缓冲区中的数据按照一定的规律传递给颜色变量a_color
gl.vertexAttribPointer(a_color, 3, gl.FLOAT, false, 0, 0)
// 允许数据传递
gl.enableVertexAttribArray(a_color)
/**执行绘制之前,一定要开启深度测试,以免颜色混乱**/
gl.enable(gl.DEPTH_TEST)
// 执行绘制三角形命令
gl.drawArrays(gl.TRIANGLES, 0, 36)
}
// 初始化着色器函数
// initShader()函数可以完成着色器代码的编译,然后在GPU上执行
const initShader = (gl, vertexShaderSource, fragmentShaderSource) => {
// 创建顶点着色器对象
const vertexShader = gl.createShader(gl.VERTEX_SHADER)
// 创建片元着色器对象
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
// 引入顶点、片元着色器源代码
gl.shaderSource(vertexShader, vertexShaderSource)
gl.shaderSource(fragmentShader, fragmentShaderSource)
// 编译顶点、片元着色器
gl.compileShader(vertexShader)
gl.compileShader(fragmentShader)
// 创建程序对象program
const program = gl.createProgram()
// 附着顶点着色器和片元着色器到program
gl.attachShader(program, vertexShader)
gl.attachShader(program, fragmentShader)
// 链接program
gl.linkProgram(program)
// 使用program
gl.useProgram(program)
// 返回程序program对象
return program
}
Vue.onMounted(init)
return { init }
}
}).mount('#wrap')
</script>
</body>
</html>
测试
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="utf-8" />
<meta name="viewport" id="viewport" content="width=device-width,initial-scale=1,minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
<title>立方体-每个面一种颜色-测试</title>
<style>
html {
box-sizing: border-box;
scroll-behavior: smooth !important;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
*,
*:before,
*:after {
box-sizing: inherit;
margin: 0;
padding: 0;
}
html,
body {
width: 100%;
height: 100%;
background-color: #fff;
counter-reset: boxnum;
}
body:before {
content: "";
position: fixed;
top: -10px;
left: 0;
z-index: 100;
width: 100%;
height: 10px;
-webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
-moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
}
.wrapper {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
overflow: hidden;
}
.container {
flex: 1;
display: flex;
justify-content: center;
}
</style>
</head>
<body>
<!-- 1920*1080 -->
<div id="wrap" class="wrapper">
<div class="container">
<canvas id="webgl" width="500" height="500" style="background-color: blue"></canvas>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.15/vue.global.min.js"></script>
<script>
const { createApp } = Vue
createApp({
setup() {
const init = () => {
// 通过getElementById()方法获取canvas画布
const canvas = document.getElementById('webgl')
// 通过方法getContext()获取WebGL上下文
const gl = canvas.getContext('webgl')
// 顶点着色器源码
const vertexShaderSource = `
// attribute声明vec4类型变量apos
attribute vec4 apos;
// attribute声明顶点颜色变量
attribute vec4 a_color;
// varying声明顶点颜色插值后变量
varying vec4 v_color;
void main() {
// 设置几何体旋转角度为30度,并把角度值转化为弧度值
float radian = radians(30.0);
// 求解y轴旋转角度余弦值
float cos = cos(radian);
// 求解y轴旋转角度正弦值
float sin = sin(radian);
// y轴逆时针旋转矩阵
// mat4 yMatrix = mat4(
// cos, 0, sin, 0,
// 0, 1, 0, 0,
// -sin, 0, cos, 0,
// 0, 0, 0, 1
// );
// y轴顺时针旋转矩阵
mat4 yMatrix = mat4(
sin, 0, cos, 0,
0, 1, 0, 0,
cos, 0, -sin, 0,
0, 0, 0, 1
);
// x轴旋转矩阵
mat4 xMatrix = mat4(
1, 0, 0, 0,
0, cos, -sin, 0,
0, sin, cos, 0,
0, 0, 0, 1
);
gl_Position = xMatrix * (yMatrix * apos); // 顶点位置
// 顶点颜色插值计算
v_color = a_color;
}
`
// 片元着色器源码
const fragShaderSource = `
// 所有float类型数据的精度是lowp
precision lowp float;
// 接收顶点着色器中v_color数据
varying vec4 v_color;
void main() {
gl_FragColor = v_color; // 插值后颜色数据赋值给对应的片元
}
`
// 初始化着色器
const program = initShader(gl, vertexShaderSource, fragShaderSource)
// 绘制立方体线框
drawCubeLine(gl, program)
// 绘制立方体6个面
drawCubeSide(gl, program)
}
// 绘制立方体线框
const drawCubeLine = (gl, program) => {
// 数组里48个元素,每间隔3个为一组,分别代表xyz轴上的坐标值
const data = new Float32Array([
// 前面的4个点
0.5, 0.5, 0.5, // 顶点0
-0.5, 0.5, 0.5, // 顶点1
-0.5, -0.5, 0.5, // 顶点2
0.5, -0.5, 0.5, // 顶点3
// 后面的4个点
0.5, 0.5, -0.5, // 顶点4
-0.5, 0.5, -0.5, // 顶点5
-0.5, -0.5, -0.5, // 顶点6
0.5, -0.5, -0.5 // 顶点7
])
// 创建顶点缓冲区对象
const buffer = gl.createBuffer()
// 绑定顶点缓冲区对象,激活buffer
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
// 顶点数组data数据传入缓冲区
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
// 顶点索引数组
const indexes = new Uint8Array([
// 前四个点对应索引值
0, 1, 2, 3, // gl.LINE_LOOP模式四个点绘制一个矩形框
//后四个顶点对应索引值
4, 5, 6, 7, // gl.LINE_LOOP模式四个点绘制一个矩形框
// 前后对应点对应索引值
0, 4, // 两个点绘制一条直线
1, 5, // 两个点绘制一条直线
2, 6, // 两个点绘制一条直线
3, 7 // 两个点绘制一条直线
])
// 创建顶点索引缓冲区对象
const indexesBuffer = gl.createBuffer()
// 绑定顶点索引缓冲区对象,激活indexesBuffer
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexesBuffer)
// 索引数组indexes数据传入缓冲区
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexes, gl.STATIC_DRAW)
// 获取顶点着色器的位置变量apos,即aposLocation指向apos变量
const aposLocation = gl.getAttribLocation(program, 'apos')
// 缓冲区中的数据按照一定的规律传递给位置变量apos
gl.vertexAttribPointer(aposLocation, 3, gl.FLOAT, false, 0, 0)
// 允许数据传递
gl.enableVertexAttribArray(aposLocation)
// LINE_LOOP模式绘制前四个点
gl.drawElements(gl.LINE_LOOP, 4, gl.UNSIGNED_BYTE, 0)
// LINE_LOOP模式从第五个点开始绘制四个点
gl.drawElements(gl.LINE_LOOP, 4, gl.UNSIGNED_BYTE, 4)
// LINES模式绘制后8个点
gl.drawElements(gl.LINES, 8, gl.UNSIGNED_BYTE, 8)
}
// 绘制立方体6个面
const drawCubeSide = (gl, program) => {
// 创建顶点位置数据数组data,Javascript中小数点前面的0可以省略
const data = new Float32Array([
// 面1
.5, .5, .5,
-.5, .5, .5,
-.5, -.5, .5,
.5, .5, .5,
-.5, -.5, .5,
.5, -.5, .5,
// 面2
.5, .5, .5,
.5, -.5, .5,
.5, -.5, -.5,
.5, .5, .5,
.5, -.5, -.5,
.5, .5, -.5,
// 面3
.5, .5, .5,
.5, .5, -.5,
-.5, .5, -.5,
.5, .5, .5,
-.5, .5, -.5,
-.5, .5, .5,
// 面4
-.5, .5, .5,
-.5, .5, -.5,
-.5, -.5, -.5,
-.5, .5, .5,
-.5, -.5, -.5,
-.5, -.5, .5,
// 面5
-.5, -.5, -.5,
.5, -.5, -.5,
.5, -.5, .5,
-.5, -.5, -.5,
.5, -.5, .5,
-.5, -.5, .5,
// 面6
.5, -.5, -.5,
-.5, -.5, -.5,
-.5, .5, -.5,
.5, -.5, -.5,
-.5, .5, -.5,
.5, .5, -.5
])
// 创建顶点缓冲区对象
const buffer = gl.createBuffer()
// 绑定顶点缓冲区对象,激活buffer
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
// 顶点数组data数据传入缓冲区
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
// 获取顶点着色器的位置变量apos,即aposLocation指向apos变量
const aposLocation = gl.getAttribLocation(program, 'apos')
// 缓冲区中的数据按照一定的规律传递给位置变量apos
gl.vertexAttribPointer(aposLocation, 3, gl.FLOAT, false, 0, 0)
// 允许数据传递
gl.enableVertexAttribArray(aposLocation)
// 创建顶点颜色数组colorData
const colorData = new Float32Array([
1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, // 面1红色
.9,0,0, .9,0,0, .9,0,0, .9,0,0, .9,0,0, .9,0,0, // 面2绿色 R=0.9
.8,0,0, .8,0,0, .8,0,0, .8,0,0, .8,0,0, .8,0,0, // 面3蓝色 R=0.8
1,1,0, 1,1,0, 1,1,0, 1,1,0, 1,1,0, 1,1,0, // 面4黄色
0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, // 面5黑色
1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0 // 面6白色
])
// 创建缓冲区colorBuffer
const colorBuffer = gl.createBuffer()
// 绑定顶点颜色缓冲区对象
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer)
// 传入顶点颜色数据colorData
gl.bufferData(gl.ARRAY_BUFFER, colorData, gl.STATIC_DRAW)
// 获取顶点着色器的颜色变量a_color
const a_color = gl.getAttribLocation(program, 'a_color')
// 缓冲区中的数据按照一定的规律传递给颜色变量a_color
gl.vertexAttribPointer(a_color, 3, gl.FLOAT, false, 0, 0)
// 允许数据传递
gl.enableVertexAttribArray(a_color)
/**执行绘制之前,一定要开启深度测试,以免颜色混乱**/
gl.enable(gl.DEPTH_TEST)
// 执行绘制三角形命令
gl.drawArrays(gl.TRIANGLES, 0, 36)
}
// 初始化着色器函数
// initShader()函数可以完成着色器代码的编译,然后在GPU上执行
const initShader = (gl, vertexShaderSource, fragmentShaderSource) => {
// 创建顶点着色器对象
const vertexShader = gl.createShader(gl.VERTEX_SHADER)
// 创建片元着色器对象
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
// 引入顶点、片元着色器源代码
gl.shaderSource(vertexShader, vertexShaderSource)
gl.shaderSource(fragmentShader, fragmentShaderSource)
// 编译顶点、片元着色器
gl.compileShader(vertexShader)
gl.compileShader(fragmentShader)
// 创建程序对象program
const program = gl.createProgram()
// 附着顶点着色器和片元着色器到program
gl.attachShader(program, vertexShader)
gl.attachShader(program, fragmentShader)
// 链接program
gl.linkProgram(program)
// 使用program
gl.useProgram(program)
// 返回程序program对象
return program
}
Vue.onMounted(init)
return { init }
}
}).mount('#wrap')
</script>
</body>
</html>
9.WebGL光照渲染立方体
前置数学概念及计算公式
- 向量的大小,也就是向量的长度(或称模)。向量a的模记作|a|。
- 向量的模是非负实数,向量的模是可以比较大小的。向量a=(x,y) ,向量a的模=√x²+y²。
- 因为方向不能比较大小,所以向量也就不能比较大小。对于向量来说“大于”和“小于”的概念是没有意义的。例如向量AB>向量CD是没有意义的。
9.1 光照模型
平行光漫反射简单数学模型:
漫反射光的颜色 = 几何体表面基色 x 光线颜色 x 光线入射角余弦值
漫反射数学模型RGB分量表示:
(R2,G2,B2) = (R1,G1,B1) x (R0,G0,B0) x cosθ
漫反射数学模型WebGL着色器语言表示:
// 角度值转化为弧度值
float radian = radians(60.0);
// reflectedLight的结果是(0.5, 0, 0)
vec3 reflectedLight = vec3(1.0, 0.0, 0.0) * vec3(1.0, 0.0, 0.0) * cos(radian)
平行光镜面反射数学模型:
镜面反射光的颜色 = 几何体表面基色 x 光线颜色 x 视线与反射光线的夹角余弦值n
环境光照数学模型:
环境反射光颜色 = 几何体表面基色 x 环境光颜色
复合光照数学模型:
总反射光线 = 漫反射光线 + 镜面反射光线 + 环境反射光线
9.2立方体添加平行光
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="utf-8" />
<meta name="viewport" id="viewport" content="width=device-width,initial-scale=1,minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
<title>WebGL光照渲染立方体-立方体添加平行光</title>
<style>
html {
box-sizing: border-box;
scroll-behavior: smooth !important;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
*,
*:before,
*:after {
box-sizing: inherit;
margin: 0;
padding: 0;
}
html,
body {
width: 100%;
height: 100%;
background-color: #fff;
counter-reset: boxnum;
}
body:before {
content: "";
position: fixed;
top: -10px;
left: 0;
z-index: 100;
width: 100%;
height: 10px;
-webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
-moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
}
.wrapper {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
overflow: hidden;
}
.container {
flex: 1;
display: flex;
justify-content: center;
}
</style>
</head>
<body>
<!-- 1920*1080 -->
<div id="wrap" class="wrapper">
<div class="container">
<canvas id="webgl" width="500" height="500" style="background-color: blue"></canvas>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.15/vue.global.min.js"></script>
<script>
const { createApp } = Vue
createApp({
setup() {
const init = () => {
// 通过getElementById()方法获取canvas画布
const canvas = document.getElementById('webgl')
// 通过方法getContext()获取WebGL上下文
const gl = canvas.getContext('webgl')
// 顶点着色器源码
const vertexShaderSource = `
// attribute声明vec4类型变量apos
attribute vec4 apos;
// attribute声明顶点颜色变量
attribute vec4 a_color;
// 顶点法向量变量
attribute vec4 a_normal;
// uniform声明平行光颜色变量
uniform vec3 u_lightColor;
// uniform声明平行光传入方向变量
uniform vec3 u_lightDirection;
// varying声明顶点颜色插值后变量
varying vec4 v_color;
void main() {
// 设置几何体旋转角度为30度,并把角度值转化为弧度值
float radian = radians(30.0);
// 求解y轴旋转角度余弦值
float cos = cos(radian);
// 求解y轴旋转角度正弦值
float sin = sin(radian);
// y轴逆时针旋转矩阵
// mat4 yMatrix = mat4(
// cos, 0, sin, 0,
// 0, 1, 0, 0,
// -sin, 0, cos, 0,
// 0, 0, 0, 1
// );
// y轴顺时针旋转矩阵
mat4 yMatrix = mat4(
sin, 0, cos, 0,
0, 1, 0, 0,
cos, 0, -sin, 0,
0, 0, 0, 1
);
// x轴旋转矩阵
mat4 xMatrix = mat4(
1, 0, 0, 0,
0, cos, -sin, 0,
0, sin, cos, 0,
0, 0, 0, 1
);
gl_Position = xMatrix * yMatrix * apos; // 顶点位置
// 顶点法向量进行矩阵变换,然后归一化
vec3 normal = normalize((xMatrix * yMatrix * a_normal).xyz);
// 点光源照射顶点的方向归一化
vec3 lightDirection = normalize(u_lightDirection);
// 计算平行光方向向量和顶点法向量的点积
float dot = max(dot(lightDirection, normal), 0.0);
// 计算反射后的颜色
vec3 reflectedLight = u_lightColor * a_color.rgb * dot;
// 顶点颜色插值计算
v_color = vec4(reflectedLight, a_color.a);
}
`
// 片元着色器源码
const fragShaderSource = `
// 所有float类型数据的精度是lowp
precision lowp float;
// 接收顶点着色器中v_color数据
varying vec4 v_color;
void main() {
gl_FragColor = v_color; // 插值后颜色数据赋值给对应的片元
}
`
// 初始化着色器
const program = initShader(gl, vertexShaderSource, fragShaderSource)
// 绘制立方体线框
drawCubeLine(gl, program)
// 绘制立方体6个面
drawCubeSide(gl, program)
}
// 绘制立方体线框
const drawCubeLine = (gl, program) => {
// 数组里48个元素,每间隔3个为一组,分别代表xyz轴上的坐标值
const data = new Float32Array([
// 前面的4个点
0.5, 0.5, 0.5, // 顶点0
-0.5, 0.5, 0.5, // 顶点1
-0.5, -0.5, 0.5, // 顶点2
0.5, -0.5, 0.5, // 顶点3
// 后面的4个点
0.5, 0.5, -0.5, // 顶点4
-0.5, 0.5, -0.5, // 顶点5
-0.5, -0.5, -0.5, // 顶点6
0.5, -0.5, -0.5 // 顶点7
])
// 创建顶点缓冲区对象
const buffer = gl.createBuffer()
// 绑定顶点缓冲区对象,激活buffer
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
// 顶点数组data数据传入缓冲区
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
// 顶点索引数组
const indexes = new Uint8Array([
// 前四个点对应索引值
0, 1, 2, 3, // gl.LINE_LOOP模式四个点绘制一个矩形框
//后四个顶点对应索引值
4, 5, 6, 7, // gl.LINE_LOOP模式四个点绘制一个矩形框
// 前后对应点对应索引值
0, 4, // 两个点绘制一条直线
1, 5, // 两个点绘制一条直线
2, 6, // 两个点绘制一条直线
3, 7 // 两个点绘制一条直线
])
// 创建顶点索引缓冲区对象
const indexesBuffer = gl.createBuffer()
// 绑定顶点索引缓冲区对象,激活indexesBuffer
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexesBuffer)
// 索引数组indexes数据传入缓冲区
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexes, gl.STATIC_DRAW)
// 获取顶点着色器的位置变量apos,即aposLocation指向apos变量
const aposLocation = gl.getAttribLocation(program, 'apos')
// 缓冲区中的数据按照一定的规律传递给位置变量apos
gl.vertexAttribPointer(aposLocation, 3, gl.FLOAT, false, 0, 0)
// 允许数据传递
gl.enableVertexAttribArray(aposLocation)
// LINE_LOOP模式绘制前四个点
gl.drawElements(gl.LINE_LOOP, 4, gl.UNSIGNED_BYTE, 0)
// LINE_LOOP模式从第五个点开始绘制四个点
gl.drawElements(gl.LINE_LOOP, 4, gl.UNSIGNED_BYTE, 4)
// LINES模式绘制后8个点
gl.drawElements(gl.LINES, 8, gl.UNSIGNED_BYTE, 8)
}
// 绘制立方体6个面
const drawCubeSide = (gl, program) => {
// 创建顶点位置数据数组data,Javascript中小数点前面的0可以省略
const data = new Float32Array([
// 面1
.5, .5, .5,
-.5, .5, .5,
-.5, -.5, .5,
.5, .5, .5,
-.5, -.5, .5,
.5, -.5, .5,
// 面2
.5, .5, .5,
.5, -.5, .5,
.5, -.5, -.5,
.5, .5, .5,
.5, -.5, -.5,
.5, .5, -.5,
// 面3
.5, .5, .5,
.5, .5, -.5,
-.5, .5, -.5,
.5, .5, .5,
-.5, .5, -.5,
-.5, .5, .5,
// 面4
-.5, .5, .5,
-.5, .5, -.5,
-.5, -.5, -.5,
-.5, .5, .5,
-.5, -.5, -.5,
-.5, -.5, .5,
// 面5
-.5, -.5, -.5,
.5, -.5, -.5,
.5, -.5, .5,
-.5, -.5, -.5,
.5, -.5, .5,
-.5, -.5, .5,
// 面6
.5, -.5, -.5,
-.5, -.5, -.5,
-.5, .5, -.5,
.5, -.5, -.5,
-.5, .5, -.5,
.5, .5, -.5
])
// 创建顶点缓冲区对象
const buffer = gl.createBuffer()
// 绑定顶点缓冲区对象,激活buffer
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
// 顶点数组data数据传入缓冲区
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
// 获取顶点着色器的位置变量apos,即aposLocation指向apos变量
const aposLocation = gl.getAttribLocation(program, 'apos')
// 缓冲区中的数据按照一定的规律传递给位置变量apos
gl.vertexAttribPointer(aposLocation, 3, gl.FLOAT, false, 0, 0)
// 允许数据传递
gl.enableVertexAttribArray(aposLocation)
// 创建顶点颜色数组colorData
const colorData = new Float32Array([
1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, // 面1红色
0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, // 面2绿色
0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, // 面3蓝色
1,1,0, 1,1,0, 1,1,0, 1,1,0, 1,1,0, 1,1,0, // 面4黄色
0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, // 面5黑色
1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0 // 面6白色
])
// 创建缓冲区colorBuffer
const colorBuffer = gl.createBuffer()
// 绑定顶点颜色缓冲区对象
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer)
// 传入顶点颜色数据colorData
gl.bufferData(gl.ARRAY_BUFFER, colorData, gl.STATIC_DRAW)
// 获取顶点着色器的颜色变量a_color
const a_color = gl.getAttribLocation(program, 'a_color')
// 缓冲区中的数据按照一定的规律传递给颜色变量a_color
gl.vertexAttribPointer(a_color, 3, gl.FLOAT, false, 0, 0)
// 允许数据传递
gl.enableVertexAttribArray(a_color)
/**
* 从program对象获取相关的变量
* attribute变量声明的方法使用getAttribLocation()方法
* uniform变量声明的方法使用getUniformLocation()方法
**/
const a_normal = gl.getAttribLocation(program, 'a_normal')
const u_lightColor = gl.getUniformLocation(program, 'u_lightColor')
const u_lightDirection = gl.getUniformLocation(program, 'u_lightDirection')
// 顶点法向量数组normalData
const normalData = new Float32Array([
0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, // z轴正方向——面1
1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, // x轴正方向——面2
0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, // y轴正方向——面3
-1,0,0, -1,0,0, -1,0,0, -1,0,0, -1,0,0, -1,0,0, // x轴负方向——面4
0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0, // y轴负方向——面5
0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1 // z轴负方向——面6
])
const normalBuffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer)
gl.bufferData(gl.ARRAY_BUFFER, normalData, gl.STATIC_DRAW)
gl.vertexAttribPointer(a_normal, 3, gl.FLOAT, false, 0, 0)
gl.enableVertexAttribArray(a_normal)
/**
* 给平行光传入颜色和方向数据,RGB(1,1,1),单位向量(x,y,z)
**/
gl.uniform3f(u_lightColor, 1.0, 1.0, 1.0)
// 保证向量(x,y,-z)的长度为1,即单位向量
// 如果不是单位向量,也可以再来着色器代码中进行归一化
const x = 1 / Math.sqrt(15), y = 2 / Math.sqrt(15), z = 3 / Math.sqrt(15)
gl.uniform3f(u_lightDirection, x, y, -z) // 改变光传入方向数据可以观察到立方体表面颜色的变化
/**执行绘制之前,一定要开启深度测试,以免颜色混乱**/
gl.enable(gl.DEPTH_TEST)
// 执行绘制三角形命令
gl.drawArrays(gl.TRIANGLES, 0, 36)
}
// 初始化着色器函数
// initShader()函数可以完成着色器代码的编译,然后在GPU上执行
const initShader = (gl, vertexShaderSource, fragmentShaderSource) => {
// 创建顶点着色器对象
const vertexShader = gl.createShader(gl.VERTEX_SHADER)
// 创建片元着色器对象
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
// 引入顶点、片元着色器源代码
gl.shaderSource(vertexShader, vertexShaderSource)
gl.shaderSource(fragmentShader, fragmentShaderSource)
// 编译顶点、片元着色器
gl.compileShader(vertexShader)
gl.compileShader(fragmentShader)
// 创建程序对象program
const program = gl.createProgram()
// 附着顶点着色器和片元着色器到program
gl.attachShader(program, vertexShader)
gl.attachShader(program, fragmentShader)
// 链接program
gl.linkProgram(program)
// 如有报错则在控制台显示
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
const info = gl.getProgramInfoLog(program)
throw new Error('Could not compile WebGL program. \n\n' + info)
}
// 使用program
gl.useProgram(program)
// 返回程序program对象
return program
}
Vue.onMounted(init)
return { init }
}
}).mount('#wrap')
</script>
</body>
</html>
改变光传入方向数据后
const x = 1 / Math.sqrt(15), y = 2 / Math.sqrt(15), z = 3 / Math.sqrt(15)
gl.uniform3f(u_lightDirection, x, y, z) // 改变光传入方向数据可以观察到立方体表面颜色的变化
9.3立方体添加点光源
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="utf-8" />
<meta name="viewport" id="viewport" content="width=device-width,initial-scale=1,minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
<title>WebGL光照渲染立方体-立方体添加点光源</title>
<style>
html {
box-sizing: border-box;
scroll-behavior: smooth !important;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
*,
*:before,
*:after {
box-sizing: inherit;
margin: 0;
padding: 0;
}
html,
body {
width: 100%;
height: 100%;
background-color: #fff;
counter-reset: boxnum;
}
body:before {
content: "";
position: fixed;
top: -10px;
left: 0;
z-index: 100;
width: 100%;
height: 10px;
-webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
-moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
}
.wrapper {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
overflow: hidden;
}
.container {
flex: 1;
display: flex;
justify-content: center;
}
</style>
</head>
<body>
<!-- 1920*1080 -->
<div id="wrap" class="wrapper">
<div class="container">
<canvas id="webgl" width="500" height="500" style="background-color: blue"></canvas>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.15/vue.global.min.js"></script>
<script>
const { createApp } = Vue
createApp({
setup() {
const init = () => {
// 通过getElementById()方法获取canvas画布
const canvas = document.getElementById('webgl')
// 通过方法getContext()获取WebGL上下文
const gl = canvas.getContext('webgl')
// 顶点着色器源码
const vertexShaderSource = `
// attribute声明vec4类型变量apos
attribute vec4 apos;
// attribute声明顶点颜色变量
attribute vec4 a_color;
// 顶点法向量变量
attribute vec4 a_normal;
// uniform声明点光源颜色变量
uniform vec3 u_lightColor;
// uniform声明点光源位置变量
uniform vec3 u_lightPosition;
// varying声明顶点颜色插值后变量
varying vec4 v_color;
void main() {
// 设置几何体旋转角度为30度,并把角度值转化为弧度值
float radian = radians(30.0);
// 求解y轴旋转角度余弦值
float cos = cos(radian);
// 求解y轴旋转角度正弦值
float sin = sin(radian);
// y轴逆时针旋转矩阵
// mat4 yMatrix = mat4(
// cos, 0, sin, 0,
// 0, 1, 0, 0,
// -sin, 0, cos, 0,
// 0, 0, 0, 1
// );
// y轴顺时针旋转矩阵
mat4 yMatrix = mat4(
sin, 0, cos, 0,
0, 1, 0, 0,
cos, 0, -sin, 0,
0, 0, 0, 1
);
// x轴旋转矩阵
mat4 xMatrix = mat4(
1, 0, 0, 0,
0, cos, -sin, 0,
0, sin, cos, 0,
0, 0, 0, 1
);
gl_Position = xMatrix * yMatrix * apos; // 顶点位置
// 顶点法向量进行矩阵变换,然后归一化
vec3 normal = normalize((xMatrix * yMatrix * a_normal).xyz);
// 计算点光源照射顶点的方向并归一化
vec3 lightDirection = normalize(vec3(gl_Position) - u_lightPosition);
// 计算点光源方向向量和顶点法向量的点积
float dot = max(dot(lightDirection, normal), 0.0);
// 计算反射后的颜色
vec3 reflectedLight = u_lightColor * a_color.rgb * dot;
// 顶点颜色插值计算
v_color = vec4(reflectedLight, a_color.a);
}
`
// 片元着色器源码
const fragShaderSource = `
// 所有float类型数据的精度是lowp
precision lowp float;
// 接收顶点着色器中v_color数据
varying vec4 v_color;
void main() {
gl_FragColor = v_color; // 插值后颜色数据赋值给对应的片元
}
`
// 初始化着色器
const program = initShader(gl, vertexShaderSource, fragShaderSource)
// 绘制立方体6个面
drawCubeSide(gl, program)
}
// 绘制立方体6个面
const drawCubeSide = (gl, program) => {
// 创建顶点位置数据数组data,Javascript中小数点前面的0可以省略
const data = new Float32Array([
// 面1
.5, .5, .5,
-.5, .5, .5,
-.5, -.5, .5,
.5, .5, .5,
-.5, -.5, .5,
.5, -.5, .5,
// 面2
.5, .5, .5,
.5, -.5, .5,
.5, -.5, -.5,
.5, .5, .5,
.5, -.5, -.5,
.5, .5, -.5,
// 面3
.5, .5, .5,
.5, .5, -.5,
-.5, .5, -.5,
.5, .5, .5,
-.5, .5, -.5,
-.5, .5, .5,
// 面4
-.5, .5, .5,
-.5, .5, -.5,
-.5, -.5, -.5,
-.5, .5, .5,
-.5, -.5, -.5,
-.5, -.5, .5,
// 面5
-.5, -.5, -.5,
.5, -.5, -.5,
.5, -.5, .5,
-.5, -.5, -.5,
.5, -.5, .5,
-.5, -.5, .5,
// 面6
.5, -.5, -.5,
-.5, -.5, -.5,
-.5, .5, -.5,
.5, -.5, -.5,
-.5, .5, -.5,
.5, .5, -.5
])
// 创建顶点缓冲区对象
const buffer = gl.createBuffer()
// 绑定顶点缓冲区对象,激活buffer
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
// 顶点数组data数据传入缓冲区
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
// 获取顶点着色器的位置变量apos,即aposLocation指向apos变量
const aposLocation = gl.getAttribLocation(program, 'apos')
// 缓冲区中的数据按照一定的规律传递给位置变量apos
gl.vertexAttribPointer(aposLocation, 3, gl.FLOAT, false, 0, 0)
// 允许数据传递
gl.enableVertexAttribArray(aposLocation)
// 创建顶点颜色数组colorData
const colorData = new Float32Array([
1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, // 面1红色
0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, // 面2绿色
0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, // 面3蓝色
1,1,0, 1,1,0, 1,1,0, 1,1,0, 1,1,0, 1,1,0, // 面4黄色
0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, // 面5黑色
1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0 // 面6白色
])
// 创建缓冲区colorBuffer
const colorBuffer = gl.createBuffer()
// 绑定顶点颜色缓冲区对象
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer)
// 传入顶点颜色数据colorData
gl.bufferData(gl.ARRAY_BUFFER, colorData, gl.STATIC_DRAW)
// 获取顶点着色器的颜色变量a_color
const a_color = gl.getAttribLocation(program, 'a_color')
// 缓冲区中的数据按照一定的规律传递给颜色变量a_color
gl.vertexAttribPointer(a_color, 3, gl.FLOAT, false, 0, 0)
// 允许数据传递
gl.enableVertexAttribArray(a_color)
/**
* 从program对象获取相关的变量
* attribute变量声明的方法使用getAttribLocation()方法
* uniform变量声明的方法使用getUniformLocation()方法
**/
const a_normal = gl.getAttribLocation(program, 'a_normal')
const u_lightColor = gl.getUniformLocation(program, 'u_lightColor')
const u_lightPosition = gl.getUniformLocation(program, 'u_lightPosition')
// 顶点法向量数组normalData
const normalData = new Float32Array([
0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, // z轴正方向——面1
1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, // x轴正方向——面2
0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, // y轴正方向——面3
-1,0,0, -1,0,0, -1,0,0, -1,0,0, -1,0,0, -1,0,0, // x轴负方向——面4
0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0, // y轴负方向——面5
0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1 // z轴负方向——面6
])
const normalBuffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer)
gl.bufferData(gl.ARRAY_BUFFER, normalData, gl.STATIC_DRAW)
gl.vertexAttribPointer(a_normal, 3, gl.FLOAT, false, 0, 0)
gl.enableVertexAttribArray(a_normal)
/**
* 给点光源传入颜色和方向数据,RGB(1,1,1),单位向量(x,y,z)
**/
gl.uniform3f(u_lightColor, 1.0, 1.0, 1.0)
gl.uniform3f(u_lightPosition, 2.0, 3.0, 4.0) // 改变点光源位置数据可以观察到立方体表面颜色的变化
/**执行绘制之前,一定要开启深度测试,以免颜色混乱**/
gl.enable(gl.DEPTH_TEST)
// 执行绘制三角形命令
gl.drawArrays(gl.TRIANGLES, 0, 36)
}
// 初始化着色器函数
// initShader()函数可以完成着色器代码的编译,然后在GPU上执行
const initShader = (gl, vertexShaderSource, fragmentShaderSource) => {
// 创建顶点着色器对象
const vertexShader = gl.createShader(gl.VERTEX_SHADER)
// 创建片元着色器对象
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
// 引入顶点、片元着色器源代码
gl.shaderSource(vertexShader, vertexShaderSource)
gl.shaderSource(fragmentShader, fragmentShaderSource)
// 编译顶点、片元着色器
gl.compileShader(vertexShader)
gl.compileShader(fragmentShader)
// 创建程序对象program
const program = gl.createProgram()
// 附着顶点着色器和片元着色器到program
gl.attachShader(program, vertexShader)
gl.attachShader(program, fragmentShader)
// 链接program
gl.linkProgram(program)
// 如有报错则在控制台显示
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
const info = gl.getProgramInfoLog(program)
throw new Error('Could not compile WebGL program. \n\n' + info)
}
// 使用program
gl.useProgram(program)
// 返回程序program对象
return program
}
Vue.onMounted(init)
return { init }
}
}).mount('#wrap')
</script>
</body>
</html>
改变点光源位置数据后
gl.uniform3f(u_lightPosition, -2, 0, 3) // 改变点光源位置数据可以观察到立方体表面颜色的变化
10.立方体旋转动画
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="utf-8" />
<meta name="viewport" id="viewport" content="width=device-width,initial-scale=1,minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
<title>立方体旋转动画</title>
<style>
html {
box-sizing: border-box;
scroll-behavior: smooth !important;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
*,
*:before,
*:after {
box-sizing: inherit;
margin: 0;
padding: 0;
}
html,
body {
width: 100%;
height: 100%;
background-color: #fff;
counter-reset: boxnum;
}
body:before {
content: "";
position: fixed;
top: -10px;
left: 0;
z-index: 100;
width: 100%;
height: 10px;
-webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
-moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
}
.wrapper {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
overflow: hidden;
}
.container {
flex: 1;
display: flex;
justify-content: center;
}
</style>
</head>
<body>
<!-- 1920*1080 -->
<div id="wrap" class="wrapper">
<div class="container">
<canvas id="webgl" width="500" height="500" style="background-color: blue"></canvas>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.15/vue.global.min.js"></script>
<script>
const { createApp } = Vue
createApp({
setup() {
const init = () => {
// 通过getElementById()方法获取canvas画布
const canvas = document.getElementById('webgl')
// 通过方法getContext()获取WebGL上下文
const gl = canvas.getContext('webgl')
// 顶点着色器源码
const vertexShaderSource = `
// attribute声明vec4类型变量apos
attribute vec4 apos;
// attribute声明顶点颜色变量
attribute vec4 a_color;
// 顶点法向量变量
attribute vec4 a_normal;
// uniform声明点光源颜色变量
uniform vec3 u_lightColor;
// uniform声明点光源位置变量
uniform vec3 u_lightPosition;
/* uniform声明旋转矩阵变量mx、my */
uniform mat4 mx; // 绕x轴旋转矩阵
uniform mat4 my; // 绕y轴旋转矩阵
// varying声明顶点颜色插值后变量
varying vec4 v_color;
void main() {
gl_Position = mx * my * apos; // 顶点位置
// 顶点法向量进行矩阵变换,然后归一化
vec3 normal = normalize((mx * my * a_normal).xyz);
// 计算点光源照射顶点的方向并归一化
vec3 lightDirection = normalize(vec3(gl_Position) - u_lightPosition);
// 计算点光源方向向量和顶点法向量的点积
float dot = max(dot(lightDirection, normal), 0.0);
// 计算反射后的颜色
vec3 reflectedLight = u_lightColor * a_color.rgb * dot;
// 顶点颜色插值计算
v_color = vec4(reflectedLight, a_color.a);
}
`
// 片元着色器源码
const fragShaderSource = `
// 所有float类型数据的精度是lowp
precision lowp float;
// 接收顶点着色器中v_color数据
varying vec4 v_color;
void main() {
gl_FragColor = v_color; // 插值后颜色数据赋值给对应的片元
}
`
// 初始化着色器
const program = initShader(gl, vertexShaderSource, fragShaderSource)
// 绘制立方体6个面
drawCubeSide(gl, program)
}
// 绘制立方体6个面
const drawCubeSide = (gl, program) => {
// 创建顶点位置数据数组data,Javascript中小数点前面的0可以省略
const data = new Float32Array([
// 面1
.5, .5, .5,
-.5, .5, .5,
-.5, -.5, .5,
.5, .5, .5,
-.5, -.5, .5,
.5, -.5, .5,
// 面2
.5, .5, .5,
.5, -.5, .5,
.5, -.5, -.5,
.5, .5, .5,
.5, -.5, -.5,
.5, .5, -.5,
// 面3
.5, .5, .5,
.5, .5, -.5,
-.5, .5, -.5,
.5, .5, .5,
-.5, .5, -.5,
-.5, .5, .5,
// 面4
-.5, .5, .5,
-.5, .5, -.5,
-.5, -.5, -.5,
-.5, .5, .5,
-.5, -.5, -.5,
-.5, -.5, .5,
// 面5
-.5, -.5, -.5,
.5, -.5, -.5,
.5, -.5, .5,
-.5, -.5, -.5,
.5, -.5, .5,
-.5, -.5, .5,
// 面6
.5, -.5, -.5,
-.5, -.5, -.5,
-.5, .5, -.5,
.5, -.5, -.5,
-.5, .5, -.5,
.5, .5, -.5
])
// 创建顶点缓冲区对象
const buffer = gl.createBuffer()
// 绑定顶点缓冲区对象,激活buffer
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
// 顶点数组data数据传入缓冲区
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
// 获取顶点着色器的位置变量apos,即aposLocation指向apos变量
const aposLocation = gl.getAttribLocation(program, 'apos')
// 缓冲区中的数据按照一定的规律传递给位置变量apos
gl.vertexAttribPointer(aposLocation, 3, gl.FLOAT, false, 0, 0)
// 允许数据传递
gl.enableVertexAttribArray(aposLocation)
// 创建顶点颜色数组colorData
const colorData = new Float32Array([
1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, // 面1红色
0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, // 面2绿色
0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, // 面3蓝色
1,1,0, 1,1,0, 1,1,0, 1,1,0, 1,1,0, 1,1,0, // 面4黄色
0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, // 面5黑色
1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0 // 面6白色
])
// 创建缓冲区colorBuffer
const colorBuffer = gl.createBuffer()
// 绑定顶点颜色缓冲区对象
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer)
// 传入顶点颜色数据colorData
gl.bufferData(gl.ARRAY_BUFFER, colorData, gl.STATIC_DRAW)
// 获取顶点着色器的颜色变量a_color
const a_color = gl.getAttribLocation(program, 'a_color')
// 缓冲区中的数据按照一定的规律传递给颜色变量a_color
gl.vertexAttribPointer(a_color, 3, gl.FLOAT, false, 0, 0)
// 允许数据传递
gl.enableVertexAttribArray(a_color)
/**
* 从program对象获取相关的变量
* attribute变量声明的方法使用getAttribLocation()方法
* uniform变量声明的方法使用getUniformLocation()方法
**/
const a_normal = gl.getAttribLocation(program, 'a_normal')
const u_lightColor = gl.getUniformLocation(program, 'u_lightColor')
const u_lightPosition = gl.getUniformLocation(program, 'u_lightPosition')
// 顶点法向量数组normalData
const normalData = new Float32Array([
0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, // z轴正方向——面1
1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, // x轴正方向——面2
0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, // y轴正方向——面3
-1,0,0, -1,0,0, -1,0,0, -1,0,0, -1,0,0, -1,0,0, // x轴负方向——面4
0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0, // y轴负方向——面5
0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1 // z轴负方向——面6
])
const normalBuffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer)
gl.bufferData(gl.ARRAY_BUFFER, normalData, gl.STATIC_DRAW)
gl.vertexAttribPointer(a_normal, 3, gl.FLOAT, false, 0, 0)
gl.enableVertexAttribArray(a_normal)
/**
* 给点光源传入颜色和方向数据,RGB(1,1,1),单位向量(x,y,z)
**/
gl.uniform3f(u_lightColor, 1.0, 1.0, 1.0)
gl.uniform3f(u_lightPosition, 2.0, 3.0, 4.0) // 改变点光源位置数据可以观察到立方体表面颜色的变化
/* 从program对象获得旋转矩阵变量mx、my地址 */
const mx = gl.getUniformLocation(program, 'mx')
const my = gl.getUniformLocation(program, 'my')
const PI = Math.PI
/* 绕x轴旋转45度 */
let angle = Math.PI / 4 // 起始角度
const mxArr = new Float32Array([
1, 0, 0, 0,
0, Math.cos(angle), -Math.sin(angle), 0,
0, Math.sin(angle), Math.cos(angle), 0,
0, 0, 0, 1
]);
// 把数据mxArr传递给着色器旋转矩阵变量mx
gl.uniformMatrix4fv(mx, false, mxArr)
/* 绕轴旋转30度 */
const myRadian = PI / 6
const myArr = new Float32Array([
Math.sin(myRadian), 0, Math.cos(myRadian), 0,
0, 1, 0, 0,
Math.cos(myRadian), 0, -Math.sin(myRadian), 0,
0, 0, 0, 1
])
// 把数据myArr传递给着色器旋转矩阵变量my
gl.uniformMatrix4fv(my, false, myArr)
/**执行绘制之前,一定要开启深度测试,以免颜色混乱**/
gl.enable(gl.DEPTH_TEST)
// 定义绘制函数draw(),定时更新旋转矩阵数据,并调用WebGL绘制API
const draw = () => {
// gl.clear(gl.COLOR_BUFFER_BIT) // 清空画布上一帧图像
/*
* 立方体绕y轴旋转
*/
angle += 0.01 // 每次渲染角度递增,每次渲染不同的角度
const sin = Math.sin(angle) //旋转角度正弦值
const cos = Math.cos(angle) // 旋转角度余弦值
const myArr = new Float32Array([
cos, 0, -sin, 0,
0, 1, 0, 0,
sin, 0, cos, 0,
0, 0, 0, 1
])
gl.uniformMatrix4fv(my, false, myArr)
requestAnimationFrame(draw)
/**执行绘制命令**/
gl.drawArrays(gl.TRIANGLES, 0, 36)
}
draw()
}
// 初始化着色器函数
// initShader()函数可以完成着色器代码的编译,然后在GPU上执行
const initShader = (gl, vertexShaderSource, fragmentShaderSource) => {
// 创建顶点着色器对象
const vertexShader = gl.createShader(gl.VERTEX_SHADER)
// 创建片元着色器对象
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
// 引入顶点、片元着色器源代码
gl.shaderSource(vertexShader, vertexShaderSource)
gl.shaderSource(fragmentShader, fragmentShaderSource)
// 编译顶点、片元着色器
gl.compileShader(vertexShader)
gl.compileShader(fragmentShader)
// 创建程序对象program
const program = gl.createProgram()
// 附着顶点着色器和片元着色器到program
gl.attachShader(program, vertexShader)
gl.attachShader(program, fragmentShader)
// 链接program
gl.linkProgram(program)
// 如有报错则在控制台显示
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
const info = gl.getProgramInfoLog(program)
throw new Error('Could not compile WebGL program. \n\n' + info)
}
// 使用program
gl.useProgram(program)
// 返回程序program对象
return program
}
Vue.onMounted(init)
return { init }
}
}).mount('#wrap')
</script>
</body>
</html>
11.WebGL绘制多个几何体
方式二(重用数据)
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="utf-8" />
<meta name="viewport" id="viewport" content="width=device-width,initial-scale=1,minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
<title>WebGL绘制多个几何体-方式二(重用数据)</title>
<style>
html {
box-sizing: border-box;
scroll-behavior: smooth !important;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
*,
*:before,
*:after {
box-sizing: inherit;
margin: 0;
padding: 0;
}
html,
body {
width: 100%;
height: 100%;
background-color: #fff;
counter-reset: boxnum;
}
body:before {
content: "";
position: fixed;
top: -10px;
left: 0;
z-index: 100;
width: 100%;
height: 10px;
-webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
-moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
}
.wrapper {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
overflow: hidden;
}
.container {
flex: 1;
display: flex;
justify-content: center;
}
</style>
</head>
<body>
<!-- 1920*1080 -->
<div id="wrap" class="wrapper">
<div class="container">
<canvas id="webgl" width="500" height="500" style="background-color: blue"></canvas>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.15/vue.global.min.js"></script>
<script>
const { createApp } = Vue
createApp({
setup() {
const init = () => {
// 通过getElementById()方法获取canvas画布
const canvas = document.getElementById('webgl')
// 通过方法getContext()获取WebGL上下文
const gl = canvas.getContext('webgl')
// 顶点着色器源码
const vertexShaderSource = `
// attribute声明vec4类型变量apos
attribute vec4 apos;
// attribute声明顶点颜色变量
attribute vec4 a_color;
// 顶点法向量变量
attribute vec4 a_normal;
// uniform声明点光源颜色变量
uniform vec3 u_lightColor;
// uniform声明点光源位置变量
uniform vec3 u_lightPosition;
/**uniform声明旋转矩阵变量mx、my,平移矩阵Tx**/
uniform mat4 mx; // 绕x轴旋转矩阵
uniform mat4 my; // 绕y轴旋转矩阵
uniform mat4 Tx; // 沿着x轴平移矩阵
// varying声明顶点颜色插值后变量
varying vec4 v_color;
void main() {
// 设置几何体旋转角度为30度,并把角度值转化为弧度值
float radian = radians(30.0);
// 求解y轴旋转角度余弦值
float cos = cos(radian);
// 求解y轴旋转角度正弦值
float sin = sin(radian);
// 顶点位置
gl_Position = Tx * mx * my * apos;
// gl_Position = mx * my * Tx * apos; // 连乘顺序改变,运算结果也会改变
// 顶点法向量进行矩阵变换,然后归一化
vec3 normal = normalize((mx * my * a_normal).xyz);
// 计算点光源照射顶点的方向并归一化
vec3 lightDirection = normalize(vec3(gl_Position) - u_lightPosition);
// 计算点光源方向向量和顶点法向量的点积
float dot = max(dot(lightDirection, normal), 0.0);
// 计算反射后的颜色
vec3 reflectedLight = u_lightColor * a_color.rgb * dot;
// 顶点颜色插值计算
v_color = vec4(reflectedLight, a_color.a);
}
`
// 片元着色器源码
const fragShaderSource = `
// 所有float类型数据的精度是lowp
precision lowp float;
// 接收顶点着色器中v_color数据
varying vec4 v_color;
void main() {
gl_FragColor = v_color; // 插值后颜色数据赋值给对应的片元
}
`
// 初始化着色器
const program = initShader(gl, vertexShaderSource, fragShaderSource)
// 绘制立方体6个面
drawCubeSide(gl, program)
}
// 绘制立方体6个面
const drawCubeSide = (gl, program) => {
// 创建顶点位置数据数组data,Javascript中小数点前面的0可以省略
const data = new Float32Array([
// 面1
.3, .3, .3,
-.3, .3, .3,
-.3, -.3, .3,
.3, .3, .3,
-.3, -.3, .3,
.3, -.3, .3,
// 面2
.3, .3, .3,
.3, -.3, .3,
.3, -.3, -.3,
.3, .3, .3,
.3, -.3, -.3,
.3, .3, -.3,
// 面3
.3, .3, .3,
.3, .3, -.3,
-.3, .3, -.3,
.3, .3, .3,
-.3, .3, -.3,
-.3, .3, .3,
// 面4
-.3, .3, .3,
-.3, .3, -.3,
-.3, -.3, -.3,
-.3, .3, .3,
-.3, -.3, -.3,
-.3, -.3, .3,
// 面5
-.3, -.3, -.3,
.3, -.3, -.3,
.3, -.3, .3,
-.3, -.3, -.3,
.3, -.3, .3,
-.3, -.3, .3,
// 面6
.3, -.3, -.3,
-.3, -.3, -.3,
-.3, .3, -.3,
.3, -.3, -.3,
-.3, .3, -.3,
.3, .3, -.3,
// 立方体2的顶点坐标数据
])
// 创建顶点缓冲区对象
const buffer = gl.createBuffer()
// 绑定顶点缓冲区对象,激活buffer
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
// 顶点数组data数据传入缓冲区
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
// 获取顶点着色器的位置变量apos,即aposLocation指向apos变量
const aposLocation = gl.getAttribLocation(program, 'apos')
// 缓冲区中的数据按照一定的规律传递给位置变量apos
gl.vertexAttribPointer(aposLocation, 3, gl.FLOAT, false, 0, 0)
// 允许数据传递
gl.enableVertexAttribArray(aposLocation)
// 创建顶点颜色数组colorData
const colorData = new Float32Array([
1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, // 面1红色
0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, // 面2绿色
0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, // 面3蓝色
1,1,0, 1,1,0, 1,1,0, 1,1,0, 1,1,0, 1,1,0, // 面4黄色
0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, 0,0,0, // 面5黑色
1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0 // 面6白色
])
// 创建缓冲区colorBuffer
const colorBuffer = gl.createBuffer()
// 绑定顶点颜色缓冲区对象
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer)
// 传入顶点颜色数据colorData
gl.bufferData(gl.ARRAY_BUFFER, colorData, gl.STATIC_DRAW)
// 获取顶点着色器的颜色变量a_color
const a_color = gl.getAttribLocation(program, 'a_color')
// 缓冲区中的数据按照一定的规律传递给颜色变量a_color
gl.vertexAttribPointer(a_color, 3, gl.FLOAT, false, 0, 0)
// 允许数据传递
gl.enableVertexAttribArray(a_color)
/**
* 从program对象获取相关的变量
* attribute变量声明的方法使用getAttribLocation()方法
* uniform变量声明的方法使用getUniformLocation()方法
**/
const a_normal = gl.getAttribLocation(program, 'a_normal')
const u_lightColor = gl.getUniformLocation(program, 'u_lightColor')
const u_lightPosition = gl.getUniformLocation(program, 'u_lightPosition')
// 顶点法向量数组normalData
const normalData = new Float32Array([
0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, 0,0,1, // z轴正方向——面1
1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,0,0, // x轴正方向——面2
0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, // y轴正方向——面3
-1,0,0, -1,0,0, -1,0,0, -1,0,0, -1,0,0, -1,0,0, // x轴负方向——面4
0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0, // y轴负方向——面5
0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1, 0,0,-1 // z轴负方向——面6
])
const normalBuffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer)
gl.bufferData(gl.ARRAY_BUFFER, normalData, gl.STATIC_DRAW)
gl.vertexAttribPointer(a_normal, 3, gl.FLOAT, false, 0, 0)
gl.enableVertexAttribArray(a_normal)
/**
* 给点光源传入颜色和方向数据,RGB(1,1,1),单位向量(x,y,z)
**/
gl.uniform3f(u_lightColor, 1.0, 1.0, 1.0)
gl.uniform3f(u_lightPosition, 2.0, 3.0, 4.0) // 改变点光源位置数据可以观察到立方体表面颜色的变化
/* 从program对象获得旋转矩阵变量mx、my地址 */
const mx = gl.getUniformLocation(program, 'mx')
const my = gl.getUniformLocation(program, 'my')
const Tx = gl.getUniformLocation(program, 'Tx')
// 旋转矩阵的旋转角度数据
const angle = Math.PI / 4 // 旋转角度
const sin = Math.sin(angle)
const cos = Math.cos(angle)
// 旋转矩阵数据
const mxArr = new Float32Array([1,0,0,0, 0,cos,-sin,0, 0,sin,cos,0, 0,0,0,1])
const myArr = new Float32Array([cos,0,-sin,0, 0,1,0,0, sin,0,cos,0, 0,0,0,1])
// 类型数组传入旋转矩阵
gl.uniformMatrix4fv(mx, false, mxArr)
gl.uniformMatrix4fv(my, false, myArr)
draw(gl, Tx, 0.5) // 绘制第一个正方体
// gl.clear(gl.COLOR_BUFFER_BIT) // 清除帧缓冲区中颜色缓冲区存储的颜色数据,执行后会清除第一个正方体
draw(gl, Tx, -0.5) // 绘制第二个正方体
}
// 绘制立方体函数
const draw = (gl, Tx, x) => {
// 平移矩阵数据
const TxArr = new Float32Array([1,0,0,0, 0,1,0,0, 0,0,1,0, x,0,0,1])
// 类型数组传入平移矩阵
gl.uniformMatrix4fv(Tx, false, TxArr)
/* 执行绘制之前,一定要开启深度测试,以免颜色混乱 */
gl.enable(gl.DEPTH_TEST)
// 执行绘制三角形命令
gl.drawArrays(gl.TRIANGLES, 0, 36)
}
// 初始化着色器函数
// initShader()函数可以完成着色器代码的编译,然后在GPU上执行
const initShader = (gl, vertexShaderSource, fragmentShaderSource) => {
// 创建顶点着色器对象
const vertexShader = gl.createShader(gl.VERTEX_SHADER)
// 创建片元着色器对象
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
// 引入顶点、片元着色器源代码
gl.shaderSource(vertexShader, vertexShaderSource)
gl.shaderSource(fragmentShader, fragmentShaderSource)
// 编译顶点、片元着色器
gl.compileShader(vertexShader)
gl.compileShader(fragmentShader)
// 创建程序对象program
const program = gl.createProgram()
// 附着顶点着色器和片元着色器到program
gl.attachShader(program, vertexShader)
gl.attachShader(program, fragmentShader)
// 链接program
gl.linkProgram(program)
// 如有报错则在控制台显示
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
const info = gl.getProgramInfoLog(program)
throw new Error('Could not compile WebGL program. \n\n' + info)
}
// 使用program
gl.useProgram(program)
// 返回程序program对象
return program
}
Vue.onMounted(init)
return { init }
}
}).mount('#wrap')
</script>
</body>
</html>
12.WebGL纹理贴图
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="utf-8" />
<meta name="viewport" id="viewport" content="width=device-width,initial-scale=1,minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
<title>WebGL纹理贴图</title>
<style>
html {
box-sizing: border-box;
scroll-behavior: smooth !important;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
*,
*:before,
*:after {
box-sizing: inherit;
margin: 0;
padding: 0;
}
html,
body {
width: 100%;
height: 100%;
background-color: #fff;
counter-reset: boxnum;
}
body:before {
content: "";
position: fixed;
top: -10px;
left: 0;
z-index: 100;
width: 100%;
height: 10px;
-webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
-moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
}
.wrapper {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
overflow: hidden;
}
.container {
flex: 1;
display: flex;
justify-content: center;
}
</style>
</head>
<body>
<!-- 1920*1080 -->
<div id="wrap" class="wrapper">
<div class="container">
<canvas id="webgl" width="500" height="500" style="background-color: blue"></canvas>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.15/vue.global.min.js"></script>
<script>
const { createApp } = Vue
createApp({
setup() {
const init = () => {
// 通过getElementById()方法获取canvas画布
const canvas = document.getElementById('webgl')
// 通过方法getContext()获取WebGL上下文
const gl = canvas.getContext('webgl')
// 顶点着色器源码
const vertexShaderSource = `
attribute vec4 a_Position; // 顶点位置坐标
attribute vec2 a_TexCoord; // 纹理坐标
varying vec2 v_TexCoord; // 插值后纹理坐标
void main() {
// 顶点坐标赋值给内置变量gl_Position
gl_Position = a_Position;
// 纹理坐标插值计算
v_TexCoord = a_TexCoord;
}
`
// 片元着色器源码
const fragShaderSource = `
// 所有float类型数据的精度是highp
precision highp float;
// 接收插值后的纹理坐标
varying vec2 v_TexCoord;
// 纹理图片像素数据
uniform sampler2D u_Sampler;
void main() {
gl_FragColor = texture2D(u_Sampler, v_TexCoord); // 采集纹素,逐片元赋值像素值
}
`
// 初始化着色器
const program = initShader(gl, vertexShaderSource, fragShaderSource)
/*
* 四个顶点坐标数据data,z轴为零
* 定义纹理贴图在WebGL坐标系中位置
*/
const data = new Float32Array([
-0.5, 0.5, // 左上角——v0
-0.5, -0.5, // 左下角——v1
0.5, 0.5, // 右上角——v2
0.5, -0.5 // 右下角——v3
])
// 获取顶点着色器的位置变量a_Position
const a_Position = gl.getAttribLocation(program, 'a_Position')
// 创建缓冲区对象
const buffer = gl.createBuffer()
// 绑定缓冲区对象,激活buffer
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
// 顶点数组data数据传入缓冲区
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
// 缓冲区中的数据按照一定的规律传递给位置变量a_Position
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0)
// 允许数据传递
gl.enableVertexAttribArray(a_Position)
/*
* 创建UV纹理坐标数据textureData
*/
const textureData = new Float32Array([
0, 1, // 左上角——uv0
0, 0, // 左下角——uv1
1, 1, // 右上角——uv2
1, 0 // 右下角——uv3
])
/*
* 从program对象获取相关的变量
* attribute变量声明的方法使用getAttribLocation()方法
* uniform变量声明的方法使用getUniformLocation()方法
*/
const a_TexCoord = gl.getAttribLocation(program, 'a_TexCoord')
const u_Sampler = gl.getUniformLocation(program, 'u_Sampler')
// 创建缓冲区对象
const textureBuffer = gl.createBuffer()
// 绑定缓冲区对象,激活textureBuffer
gl.bindBuffer(gl.ARRAY_BUFFER, textureBuffer)
// UV纹理坐标数据textureData数据传入缓冲区
gl.bufferData(gl.ARRAY_BUFFER, textureData, gl.STATIC_DRAW)
// 缓冲区中的数据按照一定的规律传递给位置变量a_TexCoord
gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, 0, 0)
// 允许数据传递
gl.enableVertexAttribArray(a_TexCoord)
/*
* 加载纹理图像像素数据
*/
const image = new Image()
image.src = '../../img/test/panda.jpg' // 设置图片路径,必须是分辨率为2的n次幂的图片,否则只显示黑色
image.onload = () => {
// 传入图片纹理数据,然后执行绘制方法drawArrays()
const texture = gl.createTexture() // 创建纹理图像缓冲区
// 因为纹理的坐标原点位于左下角,和我们通常的左上角坐标原点刚好相反,所以要将它按Y轴进行反转,方便我们设置坐标
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true) // 纹理图片上下反转
gl.activeTexture(gl.TEXTURE0) // 激活0号纹理单元TEXTURE0
gl.bindTexture(gl.TEXTURE_2D, texture) // 绑定纹理缓冲区
// 设置纹理贴图填充方式(纹理贴图像素尺寸大于顶点绘制区域像素尺寸)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
// 设置纹理贴图填充方式(纹理贴图像素尺寸小于顶点绘制区域像素尺寸)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
// 设置纹素格式,jpg格式对应gl.RGB,png格式对应gl.RGBA
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image)
gl.uniform1i(u_Sampler, 0) // 纹理缓冲区单元TEXTURE0中的颜色数据传入片元着色器
// gl.clear(gl.COLOR_BUFFER_BIT)
// 进行绘制
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4)
}
}
// 初始化着色器函数
// initShader()函数可以完成着色器代码的编译,然后在GPU上执行
const initShader = (gl, vertexShaderSource, fragmentShaderSource) => {
// 创建顶点着色器对象
const vertexShader = gl.createShader(gl.VERTEX_SHADER)
// 创建片元着色器对象
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
// 引入顶点、片元着色器源代码
gl.shaderSource(vertexShader, vertexShaderSource)
gl.shaderSource(fragmentShader, fragmentShaderSource)
// 编译顶点、片元着色器
gl.compileShader(vertexShader)
gl.compileShader(fragmentShader)
// 创建程序对象program
const program = gl.createProgram()
// 附着顶点着色器和片元着色器到program
gl.attachShader(program, vertexShader)
gl.attachShader(program, fragmentShader)
// 链接program
gl.linkProgram(program)
// 使用program
gl.useProgram(program)
// 返回程序program对象
return program
}
Vue.onMounted(init)
return { init }
}
}).mount('#wrap')
</script>
</body>
</html>
注意:贴图的图片必须是分辨率为2的n次幂的图片,否则只显示黑色。
2的N次幂是指:1 2 4 8 16 32 64 128 256 512 1024 2048……
13.彩色图转灰度图
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="utf-8" />
<meta name="viewport" id="viewport" content="width=device-width,initial-scale=1,minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
<title>彩色图转灰度图</title>
<style>
html {
box-sizing: border-box;
scroll-behavior: smooth !important;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
*,
*:before,
*:after {
box-sizing: inherit;
margin: 0;
padding: 0;
}
html,
body {
width: 100%;
height: 100%;
background-color: #fff;
counter-reset: boxnum;
}
body:before {
content: "";
position: fixed;
top: -10px;
left: 0;
z-index: 100;
width: 100%;
height: 10px;
-webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
-moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.8);
}
.wrapper {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
overflow: hidden;
}
.container {
flex: 1;
display: flex;
justify-content: center;
}
</style>
</head>
<body>
<!-- 1920*1080 -->
<div id="wrap" class="wrapper">
<div class="container">
<canvas id="webgl" width="500" height="500" style="background-color: blue"></canvas>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.15/vue.global.min.js"></script>
<script>
const { createApp } = Vue
createApp({
setup() {
const init = () => {
// 通过getElementById()方法获取canvas画布
const canvas = document.getElementById('webgl')
// 通过方法getContext()获取WebGL上下文
const gl = canvas.getContext('webgl')
// 顶点着色器源码
const vertexShaderSource = `
attribute vec4 a_Position; // 顶点位置坐标
attribute vec2 a_TexCoord; // 纹理坐标
varying vec2 v_TexCoord; // 插值后纹理坐标
void main() {
// 顶点坐标赋值给内置变量gl_Position
gl_Position = a_Position;
// 纹理坐标插值计算
v_TexCoord = a_TexCoord;
}
`
// 片元着色器源码
const fragShaderSource = `
// 所有float类型数据的精度是highp
precision highp float;
// 接收插值后的纹理坐标
varying vec2 v_TexCoord;
// 纹理图片像素数据
uniform sampler2D u_Sampler;
void main() {
// 采集纹素
vec4 texture = texture2D(u_Sampler,v_TexCoord);
// 计算RGB三个分量光能量之和,也就是亮度
float luminance = 0.299 * texture.r + 0.587 * texture.g + 0.114 * texture.b;
// 逐片元赋值,RGB相同均为亮度值,用黑白两色表达图片的明暗变化
gl_FragColor = vec4(luminance, luminance, luminance, 1.0);
}
`
// 初始化着色器
const program = initShader(gl, vertexShaderSource, fragShaderSource)
/*
* 四个顶点坐标数据data,z轴为零
* 定义纹理贴图在WebGL坐标系中位置
*/
const data = new Float32Array([
-0.5, 0.5, // 左上角——v0
-0.5, -0.5, // 左下角——v1
0.5, 0.5, // 右上角——v2
0.5, -0.5 // 右下角——v3
])
// 获取顶点着色器的位置变量a_Position
const a_Position = gl.getAttribLocation(program, 'a_Position')
// 创建缓冲区对象
const buffer = gl.createBuffer()
// 绑定缓冲区对象,激活buffer
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
// 顶点数组data数据传入缓冲区
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW)
// 缓冲区中的数据按照一定的规律传递给位置变量a_Position
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0)
// 允许数据传递
gl.enableVertexAttribArray(a_Position)
/*
* 创建UV纹理坐标数据textureData
*/
const textureData = new Float32Array([
0, 1, // 左上角——uv0
0, 0, // 左下角——uv1
1, 1, // 右上角——uv2
1, 0 // 右下角——uv3
])
/*
* 从program对象获取相关的变量
* attribute变量声明的方法使用getAttribLocation()方法
* uniform变量声明的方法使用getUniformLocation()方法
*/
const a_TexCoord = gl.getAttribLocation(program, 'a_TexCoord')
const u_Sampler = gl.getUniformLocation(program, 'u_Sampler')
// 创建缓冲区对象
const textureBuffer = gl.createBuffer()
// 绑定缓冲区对象,激活textureBuffer
gl.bindBuffer(gl.ARRAY_BUFFER, textureBuffer)
// UV纹理坐标数据textureData数据传入缓冲区
gl.bufferData(gl.ARRAY_BUFFER, textureData, gl.STATIC_DRAW)
// 缓冲区中的数据按照一定的规律传递给位置变量a_TexCoord
gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, 0, 0)
// 允许数据传递
gl.enableVertexAttribArray(a_TexCoord)
/*
* 加载纹理图像像素数据
*/
const image = new Image()
image.src = '../../img/test/panda1.jpg' // 设置图片路径,必须是分辨率为2的n次幂的图片,否则只显示黑色
image.onload = () => {
// 传入图片纹理数据,然后执行绘制方法drawArrays()
const texture = gl.createTexture() // 创建纹理图像缓冲区
// 因为纹理的坐标原点位于左下角,和我们通常的左上角坐标原点刚好相反,所以要将它按Y轴进行反转,方便我们设置坐标
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true) // 纹理图片上下反转
gl.activeTexture(gl.TEXTURE0) // 激活0号纹理单元TEXTURE0
gl.bindTexture(gl.TEXTURE_2D, texture) // 绑定纹理缓冲区
// 设置纹理贴图填充方式(纹理贴图像素尺寸大于顶点绘制区域像素尺寸)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
// 设置纹理贴图填充方式(纹理贴图像素尺寸小于顶点绘制区域像素尺寸)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
// 设置纹素格式,jpg格式对应gl.RGB,png格式对应gl.RGBA
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image)
gl.uniform1i(u_Sampler, 0) // 纹理缓冲区单元TEXTURE0中的颜色数据传入片元着色器
// gl.clear(gl.COLOR_BUFFER_BIT)
// 进行绘制
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4)
}
}
// 初始化着色器函数
// initShader()函数可以完成着色器代码的编译,然后在GPU上执行
const initShader = (gl, vertexShaderSource, fragmentShaderSource) => {
// 创建顶点着色器对象
const vertexShader = gl.createShader(gl.VERTEX_SHADER)
// 创建片元着色器对象
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)
// 引入顶点、片元着色器源代码
gl.shaderSource(vertexShader, vertexShaderSource)
gl.shaderSource(fragmentShader, fragmentShaderSource)
// 编译顶点、片元着色器
gl.compileShader(vertexShader)
gl.compileShader(fragmentShader)
// 创建程序对象program
const program = gl.createProgram()
// 附着顶点着色器和片元着色器到program
gl.attachShader(program, vertexShader)
gl.attachShader(program, fragmentShader)
// 链接program
gl.linkProgram(program)
// 使用program
gl.useProgram(program)
// 返回程序program对象
return program
}
Vue.onMounted(init)
return { init }
}
}).mount('#wrap')
</script>
</body>
</html>