一,前言
1,绘制一个正方体的数据,我们以前,上,右逆时针绘制,对面的用顺时针绘制。
2, 数据准备 cubeModel.js
/**
* 获得正方体所有顶点位置
* @param sideLength 边长
*/
window.getCubeVertexesPosition = (sideLength) => {
//前
const FRONT = [
0.0, 0.0, 0.0,
sideLength, 0.0, 0.0,
0.0, sideLength, 0.0,
0.0, sideLength, 0.0,
sideLength, 0.0, 0.0,
sideLength, sideLength, 0.0
];
//上
const TOP = [
0.0, sideLength, 0.0,
sideLength, sideLength, 0.0,
0.0, sideLength, sideLength,
0.0, sideLength, sideLength,
sideLength, sideLength, 0.0,
sideLength, sideLength, sideLength
];
//右
const RIGHT = [
sideLength, 0.0, 0.0,
sideLength, 0.0, sideLength,
sideLength, sideLength, 0.0,
sideLength, sideLength, 0.0,
sideLength, 0.0, sideLength,
sideLength, sideLength, sideLength
];
//后
const REAR = [
0.0, 0.0, sideLength,
0.0, sideLength, sideLength,
sideLength, 0.0, sideLength,
sideLength, 0.0, sideLength,
0.0, sideLength, sideLength,
sideLength, sideLength, sideLength
];
//左
const LEFT = [
0.0, 0.0, 0.0,
0.0, sideLength, 0.0,
0.0, 0.0, sideLength,
0.0, 0.0, sideLength,
0.0, sideLength, 0.0,
0.0, sideLength, sideLength
];
//下
const BOTTOM = [
0.0, 0.0, 0.0,
0.0, 0.0, sideLength,
sideLength, 0.0, 0.0,
sideLength, 0.0, 0.0,
0.0, 0.0, sideLength,
sideLength, 0.0, sideLength
];
let data = [];
return data.concat(FRONT, TOP, RIGHT, REAR, BOTTOM, LEFT);
};
/**
* 获得cube的纹理坐标
*/
window.getCubeTexcoord = () => {
const TEXCOORD = [
0.0, 0.0, 0.0,
1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
0.0, 1.0, 0.0,
1.0, 0.0, 0.0,
1.0, 1.0, 0.0
];
let data = [];
return data.concat(TEXCOORD, TEXCOORD, TEXCOORD, TEXCOORD, TEXCOORD, TEXCOORD);//6x6个点的纹理贴图坐标
};
//红橙黄绿蓝靛 (每个面的颜色值) RGBA
window.getCubeColors = () => {
const FRONT = [1.0, 0.0, 0.0, 0.9];//红
const TOP = [1.0, 0.55, 0.0, 0.9];//橙
const RIGHT = [1.0, 1.0, 0.0, 0.9];//黄
const REAR = [0.0, 1.0, 0.0, 0.9];//绿
const BOTTOM = [0.0, 0.0, 1.0, 0.9];//蓝
const LEFT = [0.0, 1.0, 1.0, 0.9];//靛
let data = [];
data = data.concat(FRONT, FRONT, FRONT, FRONT, FRONT, FRONT);
data = data.concat(TOP, TOP, TOP, TOP, TOP, TOP);
data = data.concat(RIGHT, RIGHT, RIGHT, RIGHT, RIGHT, RIGHT);
data = data.concat(REAR, REAR, REAR, REAR, REAR, REAR);
data = data.concat(BOTTOM, BOTTOM, BOTTOM, BOTTOM, BOTTOM, BOTTOM);
data = data.concat(LEFT, LEFT, LEFT, LEFT, LEFT, LEFT);
return data;//6X6个点的颜色值
}
二, index.html
<body>
<script src="js/model/cubeModel.js"></script>
<script src="js/common/shaderUtil.js"></script>
<script id="vertex-shader-2d" type="notjs">
attribute vec4 a_position;
attribute vec2 a_texCoord;
attribute vec4 a_color;
uniform mat4 u_matrix;//2D变换矩阵
varying vec2 v_texCoord;
varying vec4 v_color;//颜色值,每个面用不同的颜色值
void main(){
v_texCoord = a_texCoord;
v_color = a_color;
gl_Position = u_matrix * a_position;
}
</script>
<script id="fragment-shader-2d" type="notjs">
precision mediump float;
uniform sampler2D u_image;
varying vec2 v_texCoord;
varying vec4 v_color;
void main(){
gl_FragColor = texture2D(u_image, v_texCoord) * v_color;
}
</script>
<script src="js/shader7.js"></script>
</body>
三, shaderUtil.js
window.angle2Radian = (angle) => {
const angleInRadians = angle * Math.PI / 180;
return angleInRadians;
}
window.radian2Angle = (radian) => {
const angle = radian * 180 / Math.PI;
return angle;
}
window.getCS = (radian) => {
return {
cos: Math.cos(radian),
sin: Math.sin(radian)
};
}
/**
* 2D变换
*/
window.m3 = {
projection: (width, height) => {
// 注意:这个矩阵翻转了 Y 轴,所以 0 在上方
return [
2 / width, 0, 0,
0, -2 / height, 0,
-1, 1, 1
];
},
translation: (tx, ty) => {
return [
1, 0, 0,
0, 1, 0,
tx, ty, 1,
];
},
rotation: (angle) => {
const data = getCS(angle2Radian(angle));
return [
data.cos, -data.sin, 0,
data.sin, data.cos, 0,
0, 0, 1,
];
},
scaling: (sx, sy) => {
return [
sx, 0, 0,
0, sy, 0,
0, 0, 1,
];
},
translate: (m, tx, ty) => {
return m3.multiply(m, m3.translation(tx, ty));
},
rotate: (m, angle) => {
return m3.multiply(m, m3.rotation(angle));
},
scale: (m, sx, sy) => {
return m3.multiply(m, m3.scaling(sx, sy));
},
multiply: (a, b) => {
const a00 = a[0 * 3 + 0];
const a01 = a[0 * 3 + 1];
const a02 = a[0 * 3 + 2];
const a10 = a[1 * 3 + 0];
const a11 = a[1 * 3 + 1];
const a12 = a[1 * 3 + 2];
const a20 = a[2 * 3 + 0];
const a21 = a[2 * 3 + 1];
const a22 = a[2 * 3 + 2];
const b00 = b[0 * 3 + 0];
const b01 = b[0 * 3 + 1];
const b02 = b[0 * 3 + 2];
const b10 = b[1 * 3 + 0];
const b11 = b[1 * 3 + 1];
const b12 = b[1 * 3 + 2];
const b20 = b[2 * 3 + 0];
const b21 = b[2 * 3 + 1];
const b22 = b[2 * 3 + 2];
return [
b00 * a00 + b01 * a10 + b02 * a20,
b00 * a01 + b01 * a11 + b02 * a21,
b00 * a02 + b01 * a12 + b02 * a22,
b10 * a00 + b11 * a10 + b12 * a20,
b10 * a01 + b11 * a11 + b12 * a21,
b10 * a02 + b11 * a12 + b12 * a22,
b20 * a00 + b21 * a10 + b22 * a20,
b20 * a01 + b21 * a11 + b22 * a21,
b20 * a02 + b21 * a12 + b22 * a22,
];
},
};
/**
* 3D变换
*/
window.m4 = {
projection: (width, height, depth) => {
// 注意:这个矩阵翻转了 Y 轴,所以 0 在上方
return [
2 / width, 0, 0, 0,
0, -2 / height, 0, 0,
0, 0, 2 / depth, 0,
-1, 1, 0, 1,
];
},
translation: (tx, ty, tz) => {
return [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
tx, ty, tz, 1,
];
},
xRotation: (angle) => {
const data = getCS(angle2Radian(angle));
return [
1, 0, 0, 0,
0, data.cos, data.sin, 0,
0, -data.sin, data.cos, 0,
0, 0, 0, 1,
];
},
yRotation: (angle) => {
const data = getCS(angle2Radian(angle));
return [
data.cos, 0, -data.sin, 0,
0, 1, 0, 0,
data.sin, 0, data.cos, 0,
0, 0, 0, 1,
];
},
zRotation: (angle) => {
const data = getCS(angle2Radian(angle));
return [
data.cos, data.sin, 0, 0,
-data.sin, data.cos, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
];
},
scaling: (sx, sy, sz) => {
return [
sx, 0, 0, 0,
0, sy, 0, 0,
0, 0, sz, 0,
0, 0, 0, 1
];
},
translate: (m, tx, ty, tz) => {
return m4.multiply(m, m4.translation(tx, ty, tz));
},
xRotate: (m, angle) => {
return m4.multiply(m, m4.xRotation(angle));
},
yRotate: (m, angle) => {
return m4.multiply(m, m4.yRotation(angle));
},
zRotate: (m, angle) => {
return m4.multiply(m, m4.zRotation(angle));
},
scale: (m, sx, sy, sz) => {
return m4.multiply(m, m4.scaling(sx, sy, sz));
},
multiply: (a, b) => {
const a00 = a[0 * 4 + 0];
const a01 = a[0 * 4 + 1];
const a02 = a[0 * 4 + 2];
const a03 = a[0 * 4 + 3];
const a10 = a[1 * 4 + 0];
const a11 = a[1 * 4 + 1];
const a12 = a[1 * 4 + 2];
const a13 = a[1 * 4 + 3];
const a20 = a[2 * 4 + 0];
const a21 = a[2 * 4 + 1];
const a22 = a[2 * 4 + 2];
const a23 = a[2 * 4 + 3];
const a30 = a[3 * 4 + 0];
const a31 = a[3 * 4 + 1];
const a32 = a[3 * 4 + 2];
const a33 = a[3 * 4 + 3];
const b00 = b[0 * 4 + 0];
const b01 = b[0 * 4 + 1];
const b02 = b[0 * 4 + 2];
const b03 = b[0 * 4 + 3];
const b10 = b[1 * 4 + 0];
const b11 = b[1 * 4 + 1];
const b12 = b[1 * 4 + 2];
const b13 = b[1 * 4 + 3];
const b20 = b[2 * 4 + 0];
const b21 = b[2 * 4 + 1];
const b22 = b[2 * 4 + 2];
const b23 = b[2 * 4 + 3];
const b30 = b[3 * 4 + 0];
const b31 = b[3 * 4 + 1];
const b32 = b[3 * 4 + 2];
const b33 = b[3 * 4 + 3];
return [
b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30,
b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31,
b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32,
b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33,
b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30,
b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31,
b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32,
b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33,
b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30,
b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31,
b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32,
b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33,
b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30,
b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31,
b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32,
b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33,
];
},
};
四, shader.js
/**
* 加载图片
* @param imageName
* @param pork
* @param callback
*/
function loadImage(imageName, pork, callback) {
const image = new Image();
image.src = "http://127.0.0.1:" + pork + "/WebGLDemo/textures/" + imageName;
image.onload = () => {
callback(image);
};
}
function setTexture(gl, image) {
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
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.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); //gl.LINEAR
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); //gl.NEAREST
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
}
function setCube(gl, sideLength) {
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(getCubeVertexesPosition(sideLength)), gl.STATIC_DRAW);
}
/**
* 获得绘图上下文gl WebGLRenderingContext
* @param {Object} width
* @param {Object} height
*/
function getWebGLRenderingContext(width, height) {
const canvas = document.createElement("canvas");
document.getElementsByTagName("body")[0].appendChild(canvas);
canvas.width = width;
canvas.height = height;
const gl = canvas.getContext("webgl");
if (!gl) {
console.log("%c不支持webgl", "color:#F00");
return null;
}
return gl;
}
function getShaderSource(isVertex) {
let source;
if (isVertex) {
source = document.querySelector("#vertex-shader-2d").text;
} else {
source = document.querySelector("#fragment-shader-2d").text;
}
return source;
}
/**
* @param {Object} gl WebGLReanderingContext
* @param {Object} type gl.VERTEX_SHADER/gl.FRAGMENT_SHADER
* @param {Object} source source string
*/
function createShader(gl, type, source) {
const shader = gl.createShader(type); //创建相关类型的shader
gl.shaderSource(shader, source); //提供shader的资源
gl.compileShader(shader); //编译shader
const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (success) {
return shader;
}
console.log(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
}
/**
* 链接(link)2个shader,得到着色程序program
* @param {Object} gl WebGLReanderingContext
* @param {Object} vertexShader 顶点着色器
* @param {Object} fragmentShader 片段着色器
*/
function createProgram(gl, vertexShader, fragmentShader) {
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
const success = gl.getProgramParameter(program, gl.LINK_STATUS);
if (success) {
return program;
}
console.log(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
}
function render(gl, program, image) {
const positionAttributeLocation = gl.getAttribLocation(program, "a_position");
const texCoordAttributeLocation = gl.getAttribLocation(program, "a_texCoord");
const colorAttributeLocation = gl.getAttribLocation(program, "a_color");
const matrixUniformLocation = gl.getUniformLocation(program, "u_matrix");
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
const MAX_SIDE_LENGTH = Math.max(image.width, image.height);
setCube(gl, MAX_SIDE_LENGTH); //正方体
const texCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(getCubeTexcoord()), gl.STATIC_DRAW);
const colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(getCubeColors()), gl.STATIC_DRAW);
//开始渲染
gl.viewport(0, 0, gl.canvas.clientWidth, gl.canvas.clientHeight);
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.enable(gl.CULL_FACE);//剔除背面
gl.enable(gl.DEPTH_TEST);//开启深度测试
gl.useProgram(program);
gl.enableVertexAttribArray(positionAttributeLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
const size = 3; //每次迭代运行提取3个单位数据
const type = gl.FLOAT; //每个单位数据是32位的浮点数
const normaliza = false; //不需要归一化数据
const stride = 0; //0 = 移动单位数量 * 每个单位占用的内存(sizeof(type))
const offset = 0; //每次迭代运行运动多少内存到下一个数据开始点,从缓冲起始位置开始读取
gl.vertexAttribPointer(positionAttributeLocation, size, type, normaliza, stride, offset);
gl.enableVertexAttribArray(texCoordAttributeLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
gl.vertexAttribPointer(texCoordAttributeLocation, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(colorAttributeLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.vertexAttribPointer(colorAttributeLocation, 4, gl.FLOAT, false, 0, 0);//RGBA 4个数据
setTexture(gl, image); //设置贴图
const primitiveType = gl.TRIANGLES; //绘制三角形
const pOffset = 0; //从第一个点开始绘制
const count = 6 * 6; //一个面绘制6次,总共有6个面 36个点
let matrix;
let scale = 0.7;
let angle = 0;
let isScaleAdd = true;
setInterval(() => {
matrix = m4.projection(gl.canvas.clientWidth, gl.canvas.clientHeight, 400);
matrix = m4.translate(matrix, 150, 200, 0);
angle += 1;
if (angle >= 360) {
angle = 360 - angle;
}
matrix = m4.yRotate(matrix, angle);
matrix = m4.zRotate(matrix, angle);
if (isScaleAdd) {
scale += 0.01;
if (scale >= 1.4) {
isScaleAdd = false;
}
} else {
scale -= 0.01;
if (scale <= 0.7) {
isScaleAdd = true;
}
}
matrix = m4.scale(matrix, scale, scale, scale);
matrix = m4.translate(matrix, -MAX_SIDE_LENGTH / 2, -MAX_SIDE_LENGTH / 2, -MAX_SIDE_LENGTH / 2);
gl.uniformMatrix4fv(matrixUniformLocation, false, matrix);
gl.drawArrays(primitiveType, pOffset, count);
}, 15);
}
function main() {
const WIDTH = 400;
const HEIGHT = 300;
const gl = getWebGLRenderingContext(WIDTH, HEIGHT);
if (!gl) return;
//#region 构建2个shader
const vertexShader = createShader(gl, gl.VERTEX_SHADER, getShaderSource(true));
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, getShaderSource(false));
//#endregion
//#region link2个shader
const program = createProgram(gl, vertexShader, fragmentShader);
//#endregiopn
loadImage("head.png", 8848, (image) => {
render(gl, program, image);
});
}
main();