JavaScript-WebGL2學習筆記六 - 裝置座標下拾取物體
阿新 • • 發佈:2018-11-19
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);
}