1.三维物体选中原理与实现步骤
选中三维物体的原理就是先将几何体设置为唯一的颜色,也就是说场景中的几何体颜色都不同,我们都知道gl_Color的类型是vec4类型的,也就是占4个字节,那么有256256256*256种可能,也可以理解位颜色就是各个几何体的ID,当鼠标点击屏幕时取当前位置的像素颜色,然后根据颜色找到对应几何体。最后再还原几何体本身的颜色。
2.demo效果
如上图当我们点击立方体时,会alert出提示。
3.demo代码
const VSHADER_SOURCE = `
attribute vec4 a_Position;
uniform vec4 u_Color;
varying vec4 v_Color;
uniform mat4 u_MvpMatrix4;
uniform bool u_Clicked;
void main() {
gl_Position=u_MvpMatrix4*a_Position;
if(u_Clicked) {
v_Color = vec4(1.0,0.0,0.0,1.0);
}else {
v_Color=u_Color;
}
}
`;
const FSHADER_SOURCE = `
precision mediump float;
varying vec4 v_Color;
void main() {
gl_FragColor=v_Color;
}
`;
const initArrayBuffer = (gl, data, name, num, type) => {
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
const vertexLocation = gl.getAttribLocation(gl.program, name);
gl.vertexAttribPointer(vertexLocation, num, type, false, 0, 0);
gl.enableVertexAttribArray(vertexLocation);
return true;
};
const check = (
gl,
n,
x,
y,
currentAngle,
u_Clicked,
viewProjMatrix,
u_MvpMatrix4
) => {
let picked = false;
gl.uniform1i(u_Clicked, 1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.drawElements(gl.TRIANGLES, 24, gl.UNSIGNED_BYTE, 0);
const pixels = new Uint8Array(4);
gl.readPixels(x, y, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
if(pixels[0] ===255) {
picked = true
}
gl.uniform1i(u_Clicked,0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.drawElements(gl.TRIANGLES, 24, gl.UNSIGNED_BYTE, 0);
return picked
};
const initIndexBuffer = (gl, indexData) => {
const indicesBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indicesBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexData, gl.STATIC_DRAW);
};
const main = () => {
const canvas = document.getElementById("webgl");
const gl = canvas.getContext("webgl");
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
return console.error("着色器初始化失败");
}
canvas.onmousedown = (ev) => {
const x = ev.clientX,
y = ev.clientY;
const rect = ev.target.getBoundingClientRect();
if (rect.left <= x && x < rect.right && rect.top <= y && y < rect.bottom) {
const x_in_canvas=x-rect.left;
const y_in_canvas=rect.bottom-y;
const u_Clicked = gl.getUniformLocation(gl.program,'u_Clicked')
const picked = check(gl,0,x_in_canvas,y_in_canvas,0,u_Clicked);
if(picked) {
alert('the cube was selected!')
}
}
};
const modelMatrix4 = new Matrix4().setRotate(60, 0, 1, 0);
const projMatrix4 = new Matrix4().setPerspective(
90,
canvas.width / canvas.clientHeight,
0.5,
100
);
const viewMatrix4 = new Matrix4().setLookAt(
3,
3,
5,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0
);
const mvpMatrix4 = new Matrix4()
.setIdentity()
.multiply(projMatrix4)
.multiply(viewMatrix4)
.multiply(modelMatrix4);
const u_MvpMatrix4 = gl.getUniformLocation(gl.program, "u_MvpMatrix4");
gl.uniformMatrix4fv(u_MvpMatrix4, false, mvpMatrix4.elements);
const vertexBuffer = new Float32Array([
1.0,
1.0,
1.0, //v0 white
-1.0,
1.0,
1.0, //v1 品红
-1.0,
-1.0,
1.0, //v2 红色
1.0,
-1.0,
1.0, //v3 黄色
1.0,
-1.0,
-1.0, //v4
1.0,
1.0,
-1.0, //v5
-1.0,
1.0,
-1.0, //v6
-1.0,
-1.0,
-1.0, //v7
]);
const indicesData = new Uint8Array([
0,
1,
2,
0,
2,
3, //front
0,
3,
4,
0,
4,
5, //right
0,
5,
6,
0,
6,
1, //up
1,
6,
7,
1,
7,
2, //left
7,
4,
3,
7,
3,
2, //bottom
4,
7,
6,
4,
6,
5, //behind
]);
initArrayBuffer(gl, vertexBuffer, "a_Position", 3, gl.FLOAT);
initIndexBuffer(gl, indicesData);
const u_Clicked = gl.getUniformLocation(gl.program, "u_Clicked");
//初始化为false
gl.uniform1i(u_Clicked, 0);
const u_Color = gl.getUniformLocation(gl.program, "u_Color");
gl.uniform4f(u_Color, 0.0, 1.0, 0.0, 1.0);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.enable(gl.DEPTH_TEST);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.drawElements(gl.TRIANGLES, indicesData.length, gl.UNSIGNED_BYTE, 0);
};
main();
4.相关API介绍
以上代码有一个gl.readPixels() 用来读取指定区域的像素颜色
调用示例:gl.readPixels(x, y, width, height, format, type, pixels)
--------------------------------------------------------------------------
函数功能:从颜色缓冲区中读取由x,y,width,height参数确定的矩形中的所有像素值,
并保存在pixels指定的数组中
--------------------------------------------------------------------------
参数
x, y 指定颜色缓冲区中矩形块左上角的坐标
width, height 指定矩形的宽度和高度,以像素为单位
format 指定像素值的颜色格式,必须为gl.RGBA
type 指定像素值的数据格式,必须为gl.UNSIGNED_BYTE
pixels 指定用来接收像素数据的Uint8Array类型化数组
--------------------------------------------------------------------------
返回值 无
--------------------------------------------------------------------------
错误 INVALID_VALUE pixels为null,或者,width或height是负值
INVALID_OPERATION pexels的长度不够存储所有像素值数据
INVALID_ENUM format或type值无效
4.鼠标点击选中立方体一个表面
4.1demo效果
如上图所示,若点击立方体的一个面,则这个面变成白色,点击立方体以外的区域,立方体恢复原有颜色
逻辑与选中立方体逻辑基本一致,只不过之前颜色相当于立方体的ID,现在颜色相当于面的ID,每个面的颜色不同
4.2 demo代码
const VSHADER_SOURCE = `
attribute vec4 a_Position;
attribute float a_Face;
uniform vec4 u_Color;
varying vec4 v_Color;
uniform mat4 u_MvpMatrix4;
uniform bool u_Clicked;
uniform int u_PickedFace;
void main() {
gl_Position=u_MvpMatrix4*a_Position;
int face = int(a_Face);
vec3 color = (face == u_PickedFace)?vec3(1.0):u_Color.rgb;
if(u_PickedFace == 0) {
v_Color = vec4(color,a_Face/255.0);
}else {
v_Color=vec4(color,u_Color.a);
}
}
`;
const FSHADER_SOURCE = `
precision mediump float;
varying vec4 v_Color;
void main() {
gl_FragColor=v_Color;
}
`;
const initArrayBuffer = (gl, data, name, num, type) => {
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
const vertexLocation = gl.getAttribLocation(gl.program, name);
gl.vertexAttribPointer(vertexLocation, num, type, false, 0, 0);
gl.enableVertexAttribArray(vertexLocation);
return true;
};
const check = (
gl,
n,
x,
y,
currentAngle,
u_PickedFace,
viewProjMatrix,
u_MvpMatrix4
) => {
const pixels = new Uint8Array(4);
gl.uniform1i(u_PickedFace,0);//将表面编号写入分量
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.drawElements(gl.TRIANGLES, 24, gl.UNSIGNED_BYTE, 0);
gl.readPixels(x, y, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
return pixels[3]
};
const initIndexBuffer = (gl, indexData) => {
const indicesBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indicesBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexData, gl.STATIC_DRAW);
};
const main = () => {
const canvas = document.getElementById("webgl");
const gl = canvas.getContext("webgl");
if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
return console.error("着色器初始化失败");
}
canvas.onmousedown = (ev) => {
const x = ev.clientX,
y = ev.clientY;
const rect = ev.target.getBoundingClientRect();
if (rect.left <= x && x < rect.right && rect.top <= y && y < rect.bottom) {
const x_in_canvas=x-rect.left;
const y_in_canvas=rect.bottom-y;
const u_PickedFace = gl.getUniformLocation(gl.program,'u_PickedFace')
const face = check(gl,0,x_in_canvas,y_in_canvas,0,u_PickedFace);
console.log(face,face)
gl.uniform1i(u_PickedFace,face);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.drawElements(gl.TRIANGLES, 24, gl.UNSIGNED_BYTE, 0);
}
};
const u_PickedFace = gl.getUniformLocation(gl.program,'u_PickedFace');
gl.uniform1i(u_PickedFace,-1);
const modelMatrix4 = new Matrix4().setRotate(60, 0, 1, 0);
const projMatrix4 = new Matrix4().setPerspective(
90,
canvas.width / canvas.clientHeight,
0.5,
100
);
const viewMatrix4 = new Matrix4().setLookAt(
3,
3,
5,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0
);
const mvpMatrix4 = new Matrix4()
.setIdentity()
.multiply(projMatrix4)
.multiply(viewMatrix4)
.multiply(modelMatrix4);
const u_MvpMatrix4 = gl.getUniformLocation(gl.program, "u_MvpMatrix4");
gl.uniformMatrix4fv(u_MvpMatrix4, false, mvpMatrix4.elements);
const vertexBuffer =new Float32Array([
1.0,
1.0,
1.0,
-1.0,
1.0,
1.0,
-1.0,
-1.0,
1.0,
1.0,
-1.0,
1.0, //front面 v0-4
1.0,
1.0,
1.0,
1.0,
-1.0,
1.0,
1.0,
-1.0,
-1.0,
1.0,
1.0,
-1.0, //right v0345
1.0,
1.0,
1.0,
1.0,
1.0,
-1.0,
-1.0,
1.0,
-1.0,
-1.0,
1.0,
1.0, //up v0561
-1.0,
1.0,
1.0,
-1.0,
-1.0,
1.0,
-1.0,
-1.0,
-1.0,
-1.0,
1.0,
-1.0, //left
-1.0,
-1.0,
1.0,
1.0,
-1.0,
1.0,
1.0,
-1.0,
-1.0,
-1.0,
-1.0,
-1.0, //down
1.0,
-1.0,
-1.0,
1.0,
1.0,
-1.0,
-1.0,
1.0,
-1.0,
-1.0,
-1.0,
-1.0, //back
]);
const indicesData =new Uint8Array([
0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 8, 9, 10, 8, 10, 11, 12, 13, 14, 12, 14,
15, 16, 17, 18, 16, 18, 19, 20, 21, 22, 20, 22, 23,
]);
const faces = new Uint8Array([
1,1,1,1,
2,2,2,2,
3,3,3,3,
4,4,4,4,
5,5,5,5,
6,6,6,6
])
initArrayBuffer(gl,faces,"a_Face",1,gl.BYTE)
initArrayBuffer(gl, vertexBuffer, "a_Position", 3, gl.FLOAT);
initIndexBuffer(gl, indicesData);
const u_Clicked = gl.getUniformLocation(gl.program, "u_Clicked");
//初始化为false
gl.uniform1i(u_Clicked, 0);
const u_Color = gl.getUniformLocation(gl.program, "u_Color");
gl.uniform4f(u_Color, 0.0, 1.0, 0.0, 1.0);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.enable(gl.DEPTH_TEST);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.drawElements(gl.TRIANGLES, indicesData.length, gl.UNSIGNED_BYTE, 0);
};
main();