Date: 2018-4-03
Author: kagula
Description:
实现对象的拾取比较复杂,所以打算分两步来理解, 先做二维坐标里的对象拾取。
这里演示WebGL NDC(Native Device Coordinator)坐标系统下,对象拾取功能。
*一个红色和绿色距形代表被拾取对象。
*蓝色距形表示光标位置。
*被拾取对象在光标下面,就会被render成白色。
Author: kagula
Description:
实现对象的拾取比较复杂,所以打算分两步来理解, 先做二维坐标里的对象拾取。
这里演示WebGL NDC(Native Device Coordinator)坐标系统下,对象拾取功能。
*一个红色和绿色距形代表被拾取对象。
*蓝色距形表示光标位置。
*被拾取对象在光标下面,就会被render成白色。
这个演示由index.html shader.js shape.js webgl_helper.js四个文件组成
index.html
<html>
<head>
<!--
Title: JavaScript-WebGL2学习笔记六 - 设备坐标下拾取物体
Date: 2018-4-03
Author: kagula
Description:
实现对象的拾取比较复杂,所以打算分两步来做, 先做二维坐标里的对象拾取。
演示WebGL NDC(Native Device Coordinator)坐标系统下,对象拾取功能。
*一个红色和绿色距形代表被拾取对象。
*蓝色距形表示光标位置。
*被拾取对象在光标下面,就会被render成白色。
拾取原理:
采用了frame buffer object的办法, 把object id当作pixel, render到看不见的屏幕上,
然后在这个屏幕上检查当前光标下是否存在object id, 如果有, 就说明光标在这个object id所代表的对象上.
Reference:
[1]《WebGL(陆) texParameteri参数说明》
https://www.web-tinker.com/article/20163.html
Run environment
[1]Chrome 65.0.3325.162
[2]nginx 1.12.2
Remark
-->
<title>JavaScript-WebGL2学习笔记六 - 设备坐标下拾取物体</title>
<meta charset="utf-8">
<!-- gl-matrix version 2.4.0 from http://glmatrix.net/ -->
<script type="text/javascript" src="gl-matrix-min.js"></script>
<script type="text/javascript" src="webgl_helper.js"></script>
<script type="text/javascript" src="shader.js"></script>
<script type="text/javascript" src="shape.js"></script>
</head>
<body>
<canvas id="glCanvas" width="320" height="200"></canvas>
</body>
</html>
<script>
const g_canvas = document.querySelector("#glCanvas");
var gl;
main();
function main() {
gl = g_canvas.getContext("webgl2", {stencil: true});
if (!gl) {
alert("Unable to initialize WebGL2. Your browser or machine may not support it.");
return;
}
var contextAttributes = gl.getContextAttributes();
if (!contextAttributes.stencil) {
alert("Unable to support stencil.");
return;
}
initProgram(gl);
initProgram2(gl);
initScene(gl);
initTextureFramebuffer(g_canvas.width, g_canvas.height)
drawPickObj(gl);
}//main
function handleMouseMove(event) {
//client坐标转NDC(native device coordinate)坐标
var newX = event.clientX;
var newY = event.clientY;
var x = ( newX / g_canvas.width ) * 2 - 1;
var y = - ( newY / g_canvas.height ) * 2 + 1;
//console.log("x=" + x + ",y=" + y);
//
drawCursorLocation(gl ,x ,y ,newX ,newY);
}
g_canvas.onmousemove = handleMouseMove;
</script>
shader.js
var programInfo;
var programInfo2;
function initShaderProgram(gl, vsSource, fsSource) {
const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
// Create the shader program
const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
// If creating the shader program failed, alert
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
alert('Unable to initialize the shader program: ' + gl.getProgramInfoLog(shaderProgram));
return null;
}
return shaderProgram;
}
function setShaderProgramArrayArg(gl, destPositionInShader, srcArray, elementSize)
{
gl.bindBuffer(gl.ARRAY_BUFFER, srcArray);
gl.vertexAttribPointer(
destPositionInShader,
elementSize,// pull out 2 values per iteration //Must be 1, 2, 3, or 4.
gl.FLOAT,// the data in the buffer is 32bit floats
false,// don't normalize
0,//stride, how many bytes to get from one set of values to the next
0);//how many bytes inside the buffer to start from
gl.enableVertexAttribArray(destPositionInShader);
}
function loadShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
alert('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
function initProgram(gl)
{
const vsSource = `
attribute vec4 aVertexPosition;
attribute vec4 aVertexColor;
varying lowp vec4 vColor;
void main() {
gl_Position = aVertexPosition;
vColor = aVertexColor;
}
`;
const fsSource = `
precision highp float;
varying lowp vec4 vColor;
uniform float isReturnWhite;
void main() {
if(isReturnWhite > .0)
gl_FragColor = vec4(isReturnWhite, isReturnWhite, isReturnWhite, 1);
else
gl_FragColor = vColor;
}
`;
const shaderProgram = initShaderProgram(gl, vsSource, fsSource);
programInfo = {
program: shaderProgram,
attribLocations: {
vertexPosition: gl.getAttribLocation(shaderProgram, 'aVertexPosition'),
vertexColor: gl.getAttribLocation(shaderProgram, 'aVertexColor'),
},
uniformLocations: {
isReturnWhite: gl.getUniformLocation(shaderProgram, 'isReturnWhite')
}
};
}
function initProgram2(gl)
{
const vsSource = `
attribute vec4 aVertexPosition;
void main() {
gl_Position = aVertexPosition;
}
`;
const fsSource = `
precision highp float;
uniform float highByte;
uniform float lowByte;
void main() {
gl_FragColor = vec4(0, 0, highByte, lowByte );
}
`;
const shaderProgram = initShaderProgram(gl, vsSource, fsSource);
programInfo2 = {
program: shaderProgram,
attribLocations: {
vertexPosition: gl.getAttribLocation(shaderProgram, 'aVertexPosition')
},
uniformLocations: {
highByte: gl.getUniformLocation(shaderProgram, 'highByte'),
lowByte: gl.getUniformLocation(shaderProgram, 'lowByte')
}
};
}
shape.js
function createRedShape(gl) {
const positionBuffer = gl.createBuffer();//不用的时候可以通过gl.deleteBuffer(buffer);删除
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
const positions = [
0.5, 0.5,
-0.5, 0.5,
0.5, -0.5,
-0.5, -0.5,
];
gl.bufferData(gl.ARRAY_BUFFER,
new Float32Array(positions),
gl.STATIC_DRAW);
positionBuffer.itemSize = 2;
const colors = [
1.0, 0.0, 0.0, 1.0, //
1.0, 0.0, 0.0, 1.0, // red
1.0, 0.0, 0.0, 1.0, //
1.0, 0.0, 0.0, 1.0, //
];
const colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
colorBuffer.itemSize = 4;
return {
position: positionBuffer,
color: colorBuffer,
objectId:1001
};
}
function createGreenShape(gl) {
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
const positions = [
0.6, 0.5,
0, 0.5,
0.6, -0.5,
0, -0.5,
];
gl.bufferData(gl.ARRAY_BUFFER,
new Float32Array(positions),
gl.STATIC_DRAW);
positionBuffer.itemSize = 2;
const colors = [
0.0, 1.0, 0.0, 1.0, //
0.0, 1.0, 0.0, 1.0, //
0.0, 1.0, 0.0, 1.0, //
0.0, 1.0, 0.0, 1.0, //
];
const colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
colorBuffer.itemSize = 4;
return {
position: positionBuffer,
color: colorBuffer,
objectId:1002
};
}
function createCursorShape(gl, x, y, cursorSize)
{
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
let left = x - cursorSize;
let right = x + cursorSize;
let top = y + cursorSize;
let bottom = y - cursorSize;
const positions = [
right, top,
left, top,
right, bottom,
left, bottom
];
gl.bufferData(gl.ARRAY_BUFFER,
new Float32Array(positions),
gl.STATIC_DRAW);
positionBuffer.itemSize = 2;
const colors = [
0.0, 0.0, 1.0, 1.0, //
0.0, 0.0, 1.0, 1.0, //
0.0, 0.0, 1.0, 1.0, //
0.0, 0.0, 1.0, 1.0, //
];
const colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
colorBuffer.itemSize = 4;
return {
position: positionBuffer,
color: colorBuffer,
};
}
webgl_helper.js
var g_redShape;
var g_greenShape;
var g_cursorShape;
function initScene(gl)
{
g_redShape = createRedShape(gl);
g_greenShape = createGreenShape(gl);
}
function drawPickObj(gl) {
drawBackground(gl);
drawRedShape(gl,0);
drawGreenShape(gl,0);
}
function drawCursor(gl, x, y)
{
if(g_cursorShape!=null)
{
gl.deleteBuffer(g_cursorShape.positionBuffer);
gl.deleteBuffer(g_cursorShape.colorBuffer);
}
var cursorSize = 0.06;
g_cursorShape = createCursorShape(gl, x, y, cursorSize);
setShaderProgramArrayArg(gl,
programInfo.attribLocations.vertexPosition,
g_cursorShape.position, g_cursorShape.position.itemSize);
setShaderProgramArrayArg(gl,
programInfo.attribLocations.vertexColor,
g_cursorShape.color, g_cursorShape.color.itemSize);
gl.useProgram(programInfo.program);
gl.uniform1f(programInfo.uniformLocations.isReturnWhite, 0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
function getObjectIdUnderCursor(gl, screenX, screenY, cursorSize)
{
//bind to FBO
gl.bindFramebuffer(gl.FRAMEBUFFER, rttFramebuffer);
//
gl.clearColor(0.0, 0.0, 0.0, 0.0);
gl.clearDepth(1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.depthFunc(gl.LEQUAL);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
gl.disable(gl.BLEND);
//red shape
{
var lowByte = g_redShape.objectId & 255;
var highByte = (g_redShape.objectId >>> 8) & 255;
lowByte = lowByte/255;//值域转为[0,1]
highByte = highByte/255;//值域转为[0,1]
setShaderProgramArrayArg(gl,
programInfo2.attribLocations.vertexPosition,
g_redShape.position, g_redShape.position.itemSize);
gl.useProgram(programInfo2.program);
gl.uniform1f(programInfo2.uniformLocations.lowByte, lowByte);
gl.uniform1f(programInfo2.uniformLocations.highByte, highByte);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
//green shape
{
var lowByte = g_greenShape.objectId & 255;
var highByte = (g_greenShape.objectId >>> 8) & 255;
lowByte = lowByte/255;//值域转为[0,1]
highByte = highByte/255;//值域转为[0,1]
setShaderProgramArrayArg(gl,
programInfo2.attribLocations.vertexPosition,
g_greenShape.position, g_greenShape.position.itemSize);
gl.uniform1f(programInfo2.uniformLocations.lowByte, lowByte);
gl.uniform1f(programInfo2.uniformLocations.highByte, highByte);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
//gl.flush();
//
var objId = 0;
var pixelData = new Uint8Array(4 * (cursorSize*2) * (cursorSize*2));
gl.readPixels(screenX - cursorSize, screenY - cursorSize,
cursorSize*2, cursorSize*2, gl.RGBA, gl.UNSIGNED_BYTE, pixelData);
if (pixelData && pixelData.length) {
var length = (cursorSize*2) * (cursorSize*2);
for(var index = 0; index < length; index += 4)
{
objId = pixelData[index + 3];//shader中的定义域[0,1],取出来的值域[0,255],所以不需要再multiply 255.
objId += 256 * pixelData[index + 2];
if(objId!=0)
{
//console.log("objId = " + objId);
break;
}//if
}//for
}//if
//unbind
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
return objId;
}//getObjectIdUnderCursor
function drawBackground(gl)
{
gl.clearColor(0.0, 0.0, 0.0, 1.0); // Clear to black, fully opaque
gl.clearDepth(1.0); // Clear everything
gl.clearStencil(0); // 用0填充 stencil buffer
gl.enable(gl.DEPTH_TEST); // Enable depth testing
gl.depthFunc(gl.LEQUAL); // Near things obscure far things
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
}
function drawGreenShape(gl, isHighlight)
{
setShaderProgramArrayArg(gl,
programInfo.attribLocations.vertexPosition,
g_greenShape.position, g_greenShape.position.itemSize);
setShaderProgramArrayArg(gl,
programInfo.attribLocations.vertexColor,
g_greenShape.color, g_greenShape.color.itemSize);
gl.useProgram(programInfo.program);
gl.uniform1f(programInfo.uniformLocations.isReturnWhite, isHighlight);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
function drawRedShape(gl, isHighlight)
{
setShaderProgramArrayArg(gl,
programInfo.attribLocations.vertexPosition,
g_redShape.position, g_redShape.position.itemSize);
setShaderProgramArrayArg(gl,
programInfo.attribLocations.vertexColor,
g_redShape.color, g_redShape.color.itemSize);
gl.useProgram(programInfo.program);
gl.uniform1f(programInfo.uniformLocations.isReturnWhite, isHighlight);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
//这里draw layout设定不合理, 虽然不影响演示pick object的实现,等有时间,再完善demo.
function drawCursorLocation(gl, x, y, screenX, screenY)
{
drawPickObj(gl);
var objectId = getObjectIdUnderCursor(gl,screenX, screenY, 4);
if( objectId != 0 )
{
if(objectId == g_redShape.objectId)
{
drawBackground(gl);
drawRedShape(gl,1);
drawGreenShape(gl,0);
} else if(objectId == g_greenShape.objectId){
drawBackground(gl);
drawRedShape(gl,0);
drawGreenShape(gl,1);
}
}
//这里cursor size的设定不是很好, 虽然不影响演示pick object的实现,等有时间,再完善demo.
drawCursor(gl,x,y);
}
var rttFramebuffer;
var rttTexture;
function initTextureFramebuffer(canvasWidth,canvasHeight) {
//create frame buffer
rttFramebuffer = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, rttFramebuffer);
rttFramebuffer.width = canvasWidth;
rttFramebuffer.height = canvasHeight;
//create texture
rttTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, rttTexture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
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.generateMipmap(gl.TEXTURE_2D);//如果texture的width和height,不符合width=height=2^n等式generate mip map会失败。
//把texture的图片数据指针注销(交给frame buffer管理)
gl.texImage2D(gl.TEXTURE_2D, //指定目标纹理,这个值必须是gl.TEXTURE_2D
0, // 执行细节级别。0是最基本的图像级别,n表示第N级贴图细化级别
gl.RGBA, //internalFormat, 指定纹理中的颜色组件。可选的值有GL_ALPHA,GL_RGB,GL_RGBA,GL_LUMINANCE, GL_LUMINANCE_ALPHA 等几种。
rttFramebuffer.width, rttFramebuffer.height, //纹理图像的宽、高度,必须是2的n次方。纹理图片至少要支持64个材质元素的宽、高度
0, //边框的宽度。必须为0。
gl.RGBA, //源数据的颜色格式, 不需要和internalFormat取值必须相同。
gl.UNSIGNED_BYTE, //源数据分量的数据类型。
null);//内存中指向图像数据的指针
//create render buffer
var renderbuffer = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
//设置当前工作的渲染缓冲的存储大小
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, //这两个参数是固定的
rttFramebuffer.width, rttFramebuffer.height);
//texture绑定到frame buffer中
//https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/framebufferTexture2D
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D, //target
rttTexture, //source, A WebGLTexture object whose image to attach.
0);//A GLint specifying the mipmap level of the texture image to be attached. Must be 0.
//把render buffer绑定到frame buffer上
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT,
gl.RENDERBUFFER, //renderBufferTarget, A GLenum specifying the binding point (target) for the render buffer.
renderbuffer);//A WebGLRenderbuffer object to attach.
//unbind
gl.bindTexture(gl.TEXTURE_2D, null);
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
}