在three.js
中是通过射线检测THREE.Raycaster()
这个方法,来获取点击位置与射线相交的对象,这个webgl
示例走了个捷径,通过对比物体的颜色和点击处的像素值,来检测是否点击了物体,下面是示例效果
主要示例代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>webgl-example</title>
</head>
<body>
<canvas id="webgl" width="512" height="512" style="position: absolute; z-index: 0;margin: 0 auto;"></canvas>
<canvas id="hud" width="512" height="512" style="position: absolute;z-index: 1;"></canvas>
</body>
<script src="../lib/webgl-utils.js"></script>
<script src="../lib/webgl-debug.js"></script>
<script src="../lib/cuon-utils.js"></script>
<script src="../lib/cuon-matrix.js"></script>
<script>
// 顶点着色器
var v_shader_source = '' +
'attribute vec4 a_Position;' +
'attribute vec4 a_Color;' +
'uniform mat4 u_MvpMatrix;' +
'uniform bool u_Clicked;' +
'varying vec4 v_Color;' +
'void main(){' +
' gl_Position = u_MvpMatrix * a_Position;' +
' if(u_Clicked){' + // 如果鼠标点击到就把颜色绘制为红色
' v_Color = vec4(1.0, 0.0, 0.0, 1.0);' +
' }else{' +
' v_Color = a_Color;' +
' }' +
'}';
// 片元着色器
var f_shader_source = '' +
'precision mediump float;' +
'varying vec4 v_Color;' +
'void main(){' +
' gl_FragColor = v_Color;' +
'}';
// 获取当前角度
var ANGLE_STEP = 30.0; // 旋转角度
var g_last = Date.now(); // 方法最后被调用的时间
function getCurrentAngle(angle) {
var now = Date.now();
var elapsed = now - g_last;
g_last = now;
var newAngle = angle + (ANGLE_STEP * elapsed) / 1000.0;
return newAngle %= 360;
}
(function () {
var canvas = document.getElementById('webgl');
var hud = document.getElementById('hud');
var gl = getWebGLContext(canvas);
var ctx = hud.getContext('2d');
if (!gl || !ctx) {
console.log('不能获取上下文对象');
return false;
}
// 初始化渲染器
if (!initShaders(gl, v_shader_source, f_shader_source)) {
console.log('初始化着色器失败');
return false;
}
// 设置顶点位置
var n = initVertexBuffers(gl);
if (n < 0) {
console.log('初始化顶点信息失败');
return false;
}
// 设置清楚颜色
gl.clearColor(0.0, 0.5, 0.5, 1.0);
// 开启深度检测
gl.enable(gl.DEPTH_TEST);
var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');
var u_Clicked = gl.getUniformLocation(gl.program, 'u_Clicked');
if (!u_MvpMatrix || !u_Clicked) {
console.log('获取 uniform 变量存储位置失败');
return false;
}
// 投影视图矩阵设置
var viewProjMatrix = new Matrix4();
viewProjMatrix.setPerspective(30.0, canvas.width / canvas.height, 1.0, 100);
// 视点观察点和上方向
viewProjMatrix.lookAt(0.0, 0.0, 9.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
gl.uniform1i(u_Clicked, 0);
// 模型视图投影矩阵
var g_MvpMatrix = new Matrix4();
var currentAngle = 0.0;
// 循环渲染
function animate() {
requestAnimationFrame(animate);
currentAngle = getCurrentAngle(currentAngle);
draw(gl, n, currentAngle, viewProjMatrix, u_MvpMatrix);
draw2D(ctx, currentAngle);
}
animate();
function draw2D(ctx, currentAngle) {
ctx.clearRect(0, 0, 512, 512); // Clear <hud>
// Draw triangle with white lines
ctx.beginPath(); // Start drawing
ctx.moveTo(60, 5);
ctx.lineTo(100, 75);
ctx.lineTo(20, 75);
ctx.closePath();
ctx.strokeStyle = 'rgba(100, 255, 255, 1)'; // Set white to color of lines
ctx.stroke(); // Draw Triangle with white lines
// Draw white letters
ctx.font = '15px "Times New Roman"';
ctx.fillStyle = 'rgba(100, 255, 255, 1)'; // Set white to the color of letters
ctx.fillText('HUD: Head Up Display', 15, 100);
ctx.fillText('Current Angle: ' + Math.floor(currentAngle), 15, 120);
}
// 鼠标点击事件
hud.onmousedown = function (event) {
var x = event.clientX;
var y = event.clientY;
var rect = event.target.getBoundingClientRect();
var x_in_canvas = x - rect.left;
var y_in_canvas = rect.bottom - y;
var picked = check(gl, n, x_in_canvas, y_in_canvas, currentAngle, u_Clicked, viewProjMatrix);
if (picked) {
console.log('选中模型');
// 设置清楚颜色
gl.clearColor(Math.random(), Math.random(), Math.random(), Math.random());
}
}
function check(gl, n, x, y, currentAngle, u_Clicked, viewProjMatrix, u_MvpMatrix) {
var picked = false;
gl.uniform1i(u_Clicked, 1);
draw(gl, n, currentAngle, viewProjMatrix, u_MvpMatrix);
// 获取点击位置的像素
var 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);
draw(gl, n, currentAngle, viewProjMatrix, u_MvpMatrix);
return picked;
}
function draw(gl, n, currentAngle, viewProjMatrix, u_MvpMatrix) {
// 将viewProjMatrix设置为g_MvpMatrix
g_MvpMatrix.set(viewProjMatrix);
g_MvpMatrix.rotate(currentAngle, 1.0, 0.0, 0.0);
g_MvpMatrix.rotate(currentAngle, 0.0, 1.0, 0.0);
g_MvpMatrix.rotate(currentAngle, 0.0, 0.0, 1.0);
gl.uniformMatrix4fv(u_MvpMatrix, false, g_MvpMatrix.elements);
// 清空缓存重新绘制
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);
}
function initVertexBuffers(gl) {
// Create a cube
// v6----- v5
// /| /|
// v1------v0|
// | | | |
// | |v7---|-|v4
// |/ |/
// v2------v3
// 顶点坐标
var vertices = new Float32Array([ // Vertex coordinates
1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, // v0-v1-v2-v3 front
1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, // v0-v3-v4-v5 right
1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, // v0-v5-v6-v1 up
-1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, // v1-v6-v7-v2 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, // v7-v4-v3-v2 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 // v4-v7-v6-v5 back
]);
// 顶点颜色
var colors = new Float32Array([ // Colors
0.2, 0.58, 0.82, 0.2, 0.58, 0.82, 0.2, 0.58, 0.82, 0.2, 0.58, 0.82, // v0-v1-v2-v3 front
0.5, 0.41, 0.69, 0.5, 0.41, 0.69, 0.5, 0.41, 0.69, 0.5, 0.41, 0.69, // v0-v3-v4-v5 right
0.0, 0.32, 0.61, 0.0, 0.32, 0.61, 0.0, 0.32, 0.61, 0.0, 0.32, 0.61, // v0-v5-v6-v1 up
0.78, 0.69, 0.84, 0.78, 0.69, 0.84, 0.78, 0.69, 0.84, 0.78, 0.69, 0.84, // v1-v6-v7-v2 left
0.32, 0.18, 0.56, 0.32, 0.18, 0.56, 0.32, 0.18, 0.56, 0.32, 0.18, 0.56, // v7-v4-v3-v2 down
0.73, 0.82, 0.93, 0.73, 0.82, 0.93, 0.73, 0.82, 0.93, 0.73, 0.82, 0.93, // v4-v7-v6-v5 back
]);
// 法向量
var normals = new Float32Array([ // Normal
0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, // v0-v1-v2-v3 front
1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, // v0-v3-v4-v5 right
0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, // v0-v5-v6-v1 up
-1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, // v1-v6-v7-v2 left
0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, // v7-v4-v3-v2 down
0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0 // v4-v7-v6-v5 back
]);
// 顶点索引
var indices = new Uint8Array([ // Indices of the vertices
0, 1, 2, 0, 2, 3, // front
4, 5, 6, 4, 6, 7, // right
8, 9, 10, 8, 10, 11, // up
12, 13, 14, 12, 14, 15, // left
16, 17, 18, 16, 18, 19, // down
20, 21, 22, 20, 22, 23 // back
]);
// 创建缓冲区
var indexBuffer = gl.createBuffer();
if (!indexBuffer) return -1;
// 将顶点和颜色数据写入缓冲区对象
var v_num = initArrayBuffer(gl, vertices, 3, gl.FLOAT, 'a_Position');
var c_num = initArrayBuffer(gl, colors, 3, gl.FLOAT, 'a_Color');
if (!v_num || !c_num) {
console.log('初始化缓冲区失败!');
return -1;
}
// Unbind the buffer object
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
return indices.length;
}
// 初始化缓冲区对象
function initArrayBuffer(gl, data, num, type, attribute) {
// 创建缓冲区
var buffer = gl.createBuffer();
if (!buffer) {
console.log('创建缓冲区失败!');
return false;
}
// 写入数据到缓冲区
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
// 将缓冲区对象分配给属性变量
var a_attribute = gl.getAttribLocation(gl.program, attribute);
if (a_attribute < 0) {
console.log('获取变量存储地址失败' + attribute);
return false;
}
gl.vertexAttribPointer(a_attribute, num, type, false, 0, 0);
// 启用缓冲区属性变量
gl.enableVertexAttribArray(a_attribute);
return true;
}
}())
</script>
</html>