JavaScript-WebGL2学习笔记六 - 设备坐标下拾取物体

Date: 2018-4-03  
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);  
}  

猜你喜欢

转载自blog.csdn.net/lee353086/article/details/79803811
今日推荐