Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clipping-planes in OpenGL ES 2.0

Tags:

I need to clip a few hundred objects under a clipping plane in OpenGL ES 2.0 and would appreciate ideas from people more experienced with this subset of OpenGL.

In OpenGL ES 1.x there is glClipPlane. On the desktop you have glClipPlane, or gl_ClipDistance in your shader. Neither of these two are available in OpenGL ES 2.0. It seems that this kind of functionality disappeared completely with 2.0.

It seems the only way to do this is either A) run the plane equation in the fragment shader, or B) write a very complex vertex shader that positions vertices on the plane if they are behind it.

(A) would be slow compared to glClipPlane, since "regular" clipping is done after the vertex shader and before the fragment shader, each fragment would still have to be partially processed and discarded.

(B) would be very hard to make compatible between shaders, since we can't discard vertices we have to align them with the plane and adjust attributes for those that are "cut". It's not possible to interpolate between vertices in the shader without sending all vertices in a texture and sample it, which would be extremely expensive. Often it would probably be impossible to interpolate the data correcly anyway.

I've also thought of aligning the near plane with the clipping plane which would be an efficient solution.

And drawing a plane after rendering the whole scene and checking for depth-fail will not work either (unless you are looking close to perpendicular to the plane).

What works for a single object is to draw the plane to the depth buffer and then render the object with glDepthFunc(GL_GREATER), but as expected it does not work when one of the objects is behind another. I tried to build on to this concept but eventually ended up with something very similar to shadow volumes and just as expensive.

So what am I missing? How would you do plane clipping in OpenGL ES 2.0?

like image 257
Emil Romanus Avatar asked Sep 13 '11 21:09

Emil Romanus


People also ask

What is the clipping plane?

Near and far clipping planes are imaginary planes located at two particular distances from the camera along the camera's sight line. They determine how much of a scene is seen by the camera in the viewport. Only objects between a camera's two clipping planes are rendered in that camera's view.

What is clipping in the context of OpenGL?

Computer Graphics. As you are learning OpenGL, you may come in contact with the term Clipping. Clipping is a stage in the graphics pipeline that determines which primitives to discard and which ones to pass through to the next stage.


2 Answers

Here is two solutions I've found on Vuforia SDK forums.

  1. Using shaders by Harri Smatt:

    uniform mat4 uModelM; uniform mat4 uViewProjectionM; attribute vec3 aPosition; varying vec3 vPosition; void main() {   vec4 pos = uModelM * vec4(aPosition, 1.0);   gl_Position = uViewProjectionM * pos;   vPosition = pos.xyz / pos.w; } 

    precision mediump float; varying vec3 vPosition; void main() {   if (vPosition.z < 0.0) {     discard;   } else {     // Choose actual color for rendering..   } } 
  2. Using quad in depth buffer by Alessandro Boccalatte:

    • disable color writing (i.e. set the glColorMask(false, false, false, false);)
    • render a quad which matches the marker shape (i.e. just a quad with the same size and position/orientation of the marker); this will only be rendered into the depth buffer (because we disabled color buffer writing in the previous step)
    • enable back the color mask (glColorMask(true, true, true, true);)
    • render your 3D models
like image 186
Cfr Avatar answered Oct 04 '22 03:10

Cfr


Since the extension EXT_clip_cull_distance is not available in OpenGL ES 2.0 (because for this extension is OpenGL ES 3.0 is required), clipping has to be emulated. It can be emulated in the fragment shader, by discarding fragments. See Fragment Shader - Special operations.

See also OpenGL ES Shading Language 1.00 Specification; 6.4 Jumps; page 58:

The discard keyword is only allowed within fragment shaders. It can be used within a fragment shader to abandon the operation on the current fragment. This keyword causes the fragment to be discarded and no updates to any buffers will occur. It would typically be used within a conditional statement, for example:

if (intensity < 0.0)     discard; 

A shader program which emulates gl_ClipDistance may look like this:

Vertex shader:

attribute vec3 inPos; attribute vec3 inCol;  varying vec3  vertCol; varying float clip_distance;  uniform mat4 u_projectionMat44; uniform mat4 u_viewMat44; uniform mat4 u_modelMat44; uniform vec4 u_clipPlane;  void main() {        vertCol        = inCol;     vec4 modelPos  = u_modelMat44 * vec4( inPos, 1.0 );     gl_Position    = u_projectionMat44 * u_viewMat44 * viewPos;     clip_distance  = dot(modelPos, u_clipPlane); } 

Fragment shader:

varying vec3  vertPos; varying vec3  vertCol; varying float clip_distance;  void main() {     if ( clip_distance < 0.0 )         discard;     gl_FragColor = vec4( vertCol.rgb, 1.0 ); }  

The following WebGL example demonstrates this. Note, the WebGL 1.0 context conforms closely to the OpenGL ES 2.0 API.

var readInput = true;    function changeEventHandler(event){      readInput = true;    }        (function loadscene() {        var gl, progDraw, vp_size;    var bufCube = {};    var clip = 0.0;        function render(delteMS){          if ( readInput ) {            readInput = false;            clip = (document.getElementById( "clip" ).value - 50) / 50;        }          Camera.create();        Camera.vp = vp_size;                    gl.viewport( 0, 0, vp_size[0], vp_size[1] );        gl.enable( gl.DEPTH_TEST );        gl.clearColor( 0.0, 0.0, 0.0, 1.0 );        gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );          // set up draw shader        ShaderProgram.Use( progDraw );        ShaderProgram.SetUniformM44( progDraw, "u_projectionMat44", Camera.Perspective() );        ShaderProgram.SetUniformM44( progDraw, "u_viewMat44", Camera.LookAt() );        var modelMat = IdentityMat44()        modelMat = RotateAxis( modelMat, CalcAng( delteMS, 13.0 ), 0 );        modelMat = RotateAxis( modelMat, CalcAng( delteMS, 17.0 ), 1 );        ShaderProgram.SetUniformM44( progDraw, "u_modelMat44", modelMat );        ShaderProgram.SetUniformF4( progDraw, "u_clipPlane", [1.0,-1.0,0.0,clip*1.7321] );                // draw scene        VertexBuffer.Draw( bufCube );          requestAnimationFrame(render);    }        function resize() {        //vp_size = [gl.drawingBufferWidth, gl.drawingBufferHeight];        vp_size = [window.innerWidth, window.innerHeight]        canvas.width = vp_size[0];        canvas.height = vp_size[1];    }        function initScene() {            canvas = document.getElementById( "canvas");        gl = canvas.getContext( "experimental-webgl" );        //gl = canvas.getContext( "webgl2" );        if ( !gl )          return null;                /*        var ext_frag_depth = gl.getExtension( "EXT_clip_cull_distance" );  // gl_ClipDistance gl_CullDistance        if (!ext_frag_depth)            alert('no gl_ClipDistance and gl_CullDistance support');        */          progDraw = ShaderProgram.Create(           [ { source : "draw-shader-vs", stage : gl.VERTEX_SHADER },            { source : "draw-shader-fs", stage : gl.FRAGMENT_SHADER }          ] );        if ( !progDraw.progObj )            return null;        progDraw.inPos = ShaderProgram.AttributeIndex( progDraw, "inPos" );        progDraw.inNV  = ShaderProgram.AttributeIndex( progDraw, "inNV" );        progDraw.inCol = ShaderProgram.AttributeIndex( progDraw, "inCol" );                // create cube        var cubePos = [          -1.0, -1.0,  1.0,  1.0, -1.0,  1.0,  1.0,  1.0,  1.0, -1.0,  1.0,  1.0,          -1.0, -1.0, -1.0,  1.0, -1.0, -1.0,  1.0,  1.0, -1.0, -1.0,  1.0, -1.0 ];        var cubeCol = [ 1.0, 0.0, 0.0, 1.0, 0.5, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0 ];        var cubeHlpInx = [ 0, 1, 2, 3, 1, 5, 6, 2, 5, 4, 7, 6, 4, 0, 3, 7, 3, 2, 6, 7, 1, 0, 4, 5 ];          var cubePosData = [];        for ( var i = 0; i < cubeHlpInx.length; ++ i ) {          cubePosData.push( cubePos[cubeHlpInx[i]*3], cubePos[cubeHlpInx[i]*3+1], cubePos[cubeHlpInx[i]*3+2] );        }        var cubeNVData = [];        for ( var i1 = 0; i1 < cubeHlpInx.length; i1 += 4 ) {        var nv = [0, 0, 0];        for ( i2 = 0; i2 < 4; ++ i2 ) {            var i = i1 + i2;            nv[0] += cubePosData[i*3]; nv[1] += cubePosData[i*3+1]; nv[2] += cubePosData[i*3+2];        }        for ( i2 = 0; i2 < 4; ++ i2 )          cubeNVData.push( nv[0], nv[1], nv[2] );        }        var cubeColData = [];        for ( var is = 0; is < 6; ++ is ) {          for ( var ip = 0; ip < 4; ++ ip ) {           cubeColData.push( cubeCol[is*3], cubeCol[is*3+1], cubeCol[is*3+2] );           }        }        var cubeInxData = [];        for ( var i = 0; i < cubeHlpInx.length; i += 4 ) {          cubeInxData.push( i, i+1, i+2, i, i+2, i+3 );           }        bufCube = VertexBuffer.Create(        [ { data : cubePosData, attrSize : 3, attrLoc : progDraw.inPos },          { data : cubeNVData,  attrSize : 3, attrLoc : progDraw.inNV },          { data : cubeColData, attrSize : 3, attrLoc : progDraw.inCol } ],          cubeInxData );                  window.onresize = resize;        resize();        requestAnimationFrame(render);    }        function Fract( val ) {         return val - Math.trunc( val );    }    function CalcAng( deltaTime, intervall ) {        return Fract( deltaTime / (1000*intervall) ) * 2.0 * Math.PI;    }    function CalcMove( deltaTime, intervall, range ) {        var pos = self.Fract( deltaTime / (1000*intervall) ) * 2.0        var pos = pos < 1.0 ? pos : (2.0-pos)        return range[0] + (range[1] - range[0]) * pos;    }        function EllipticalPosition( a, b, angRag ) {        var a_b = a * a - b * b        var ea = (a_b <= 0) ? 0 : Math.sqrt( a_b );        var eb = (a_b >= 0) ? 0 : Math.sqrt( -a_b );        return [ a * Math.sin( angRag ) - ea, b * Math.cos( angRag ) - eb, 0 ];    }        glArrayType = typeof Float32Array !="undefined" ? Float32Array : ( typeof WebGLFloatArray != "undefined" ? WebGLFloatArray : Array );        function IdentityMat44() {      var m = new glArrayType(16);      m[0]  = 1; m[1]  = 0; m[2]  = 0; m[3]  = 0;      m[4]  = 0; m[5]  = 1; m[6]  = 0; m[7]  = 0;      m[8]  = 0; m[9]  = 0; m[10] = 1; m[11] = 0;      m[12] = 0; m[13] = 0; m[14] = 0; m[15] = 1;      return m;    };        function RotateAxis(matA, angRad, axis) {        var aMap = [ [1, 2], [2, 0], [0, 1] ];        var a0 = aMap[axis][0], a1 = aMap[axis][1];         var sinAng = Math.sin(angRad), cosAng = Math.cos(angRad);        var matB = new glArrayType(16);        for ( var i = 0; i < 16; ++ i ) matB[i] = matA[i];        for ( var i = 0; i < 3; ++ i ) {            matB[a0*4+i] = matA[a0*4+i] * cosAng + matA[a1*4+i] * sinAng;            matB[a1*4+i] = matA[a0*4+i] * -sinAng + matA[a1*4+i] * cosAng;        }        return matB;    }        function Cross( a, b ) { return [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0], 0.0 ]; }    function Dot( a, b ) { return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]; }    function Normalize( v ) {        var len = Math.sqrt( v[0] * v[0] + v[1] * v[1] + v[2] * v[2] );        return [ v[0] / len, v[1] / len, v[2] / len ];    }        var Camera = {};    Camera.create = function() {        this.pos    = [0, 3, 0.0];        this.target = [0, 0, 0];        this.up     = [0, 0, 1];        this.fov_y  = 90;        this.vp     = [800, 600];        this.near   = 0.5;        this.far    = 100.0;    }    Camera.Perspective = function() {        var fn = this.far + this.near;        var f_n = this.far - this.near;        var r = this.vp[0] / this.vp[1];        var t = 1 / Math.tan( Math.PI * this.fov_y / 360 );        var m = IdentityMat44();        m[0]  = t/r; m[1]  = 0; m[2]  =  0;                              m[3]  = 0;        m[4]  = 0;   m[5]  = t; m[6]  =  0;                              m[7]  = 0;        m[8]  = 0;   m[9]  = 0; m[10] = -fn / f_n;                       m[11] = -1;        m[12] = 0;   m[13] = 0; m[14] = -2 * this.far * this.near / f_n; m[15] =  0;        return m;    }    Camera.LookAt = function() {        var mz = Normalize( [ this.pos[0]-this.target[0], this.pos[1]-this.target[1], this.pos[2]-this.target[2] ] );        var mx = Normalize( Cross( this.up, mz ) );        var my = Normalize( Cross( mz, mx ) );        var tx = Dot( mx, this.pos );        var ty = Dot( my, this.pos );        var tz = Dot( [-mz[0], -mz[1], -mz[2]], this.pos );         var m = IdentityMat44();        m[0]  = mx[0]; m[1]  = my[0]; m[2]  = mz[0]; m[3]  = 0;        m[4]  = mx[1]; m[5]  = my[1]; m[6]  = mz[1]; m[7]  = 0;        m[8]  = mx[2]; m[9]  = my[2]; m[10] = mz[2]; m[11] = 0;        m[12] = tx;    m[13] = ty;    m[14] = tz;    m[15] = 1;         return m;    }         var ShaderProgram = {};    ShaderProgram.Create = function( shaderList ) {        var shaderObjs = [];        for ( var i_sh = 0; i_sh < shaderList.length; ++ i_sh ) {            var shderObj = this.CompileShader( shaderList[i_sh].source, shaderList[i_sh].stage );            if ( shderObj == 0 )                return 0;            shaderObjs.push( shderObj );        }        var prog = {}        prog.progObj = this.LinkProgram( shaderObjs )        if ( prog.progObj ) {            prog.attribIndex = {};            var noOfAttributes = gl.getProgramParameter( prog.progObj, gl.ACTIVE_ATTRIBUTES );            for ( var i_n = 0; i_n < noOfAttributes; ++ i_n ) {                var name = gl.getActiveAttrib( prog.progObj, i_n ).name;                prog.attribIndex[name] = gl.getAttribLocation( prog.progObj, name );            }            prog.unifomLocation = {};            var noOfUniforms = gl.getProgramParameter( prog.progObj, gl.ACTIVE_UNIFORMS );            for ( var i_n = 0; i_n < noOfUniforms; ++ i_n ) {                var name = gl.getActiveUniform( prog.progObj, i_n ).name;                prog.unifomLocation[name] = gl.getUniformLocation( prog.progObj, name );            }        }        return prog;    }    ShaderProgram.AttributeIndex = function( prog, name ) { return prog.attribIndex[name]; }     ShaderProgram.UniformLocation = function( prog, name ) { return prog.unifomLocation[name]; }     ShaderProgram.Use = function( prog ) { gl.useProgram( prog.progObj ); }     ShaderProgram.SetUniformI1  = function( prog, name, val ) { if(prog.unifomLocation[name]) gl.uniform1i( prog.unifomLocation[name], val ); }    ShaderProgram.SetUniformF1  = function( prog, name, val ) { if(prog.unifomLocation[name]) gl.uniform1f( prog.unifomLocation[name], val ); }    ShaderProgram.SetUniformF2  = function( prog, name, arr ) { if(prog.unifomLocation[name]) gl.uniform2fv( prog.unifomLocation[name], arr ); }    ShaderProgram.SetUniformF3  = function( prog, name, arr ) { if(prog.unifomLocation[name]) gl.uniform3fv( prog.unifomLocation[name], arr ); }    ShaderProgram.SetUniformF4  = function( prog, name, arr ) { if(prog.unifomLocation[name]) gl.uniform4fv( prog.unifomLocation[name], arr ); }    ShaderProgram.SetUniformM33 = function( prog, name, mat ) { if(prog.unifomLocation[name]) gl.uniformMatrix3fv( prog.unifomLocation[name], false, mat ); }    ShaderProgram.SetUniformM44 = function( prog, name, mat ) { if(prog.unifomLocation[name]) gl.uniformMatrix4fv( prog.unifomLocation[name], false, mat ); }    ShaderProgram.CompileShader = function( source, shaderStage ) {        var shaderScript = document.getElementById(source);        if (shaderScript)          source = shaderScript.text;        var shaderObj = gl.createShader( shaderStage );        gl.shaderSource( shaderObj, source );        gl.compileShader( shaderObj );        var status = gl.getShaderParameter( shaderObj, gl.COMPILE_STATUS );        if ( !status ) alert(gl.getShaderInfoLog(shaderObj));        return status ? shaderObj : null;    }     ShaderProgram.LinkProgram = function( shaderObjs ) {        var prog = gl.createProgram();        for ( var i_sh = 0; i_sh < shaderObjs.length; ++ i_sh )            gl.attachShader( prog, shaderObjs[i_sh] );        gl.linkProgram( prog );        status = gl.getProgramParameter( prog, gl.LINK_STATUS );        if ( !status ) alert("Could not initialise shaders");        gl.useProgram( null );        return status ? prog : null;    }        var VertexBuffer = {};    VertexBuffer.Create = function( attributes, indices ) {        var buffer = {};        buffer.buf = [];        buffer.attr = []        for ( var i = 0; i < attributes.length; ++ i ) {            buffer.buf.push( gl.createBuffer() );            buffer.attr.push( { size : attributes[i].attrSize, loc : attributes[i].attrLoc } );            gl.bindBuffer( gl.ARRAY_BUFFER, buffer.buf[i] );            gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( attributes[i].data ), gl.STATIC_DRAW );        }        buffer.inx = gl.createBuffer();        gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, buffer.inx );        gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( indices ), gl.STATIC_DRAW );        buffer.inxLen = indices.length;        gl.bindBuffer( gl.ARRAY_BUFFER, null );        gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, null );        return buffer;    }    VertexBuffer.Draw = function( bufObj ) {      for ( var i = 0; i < bufObj.buf.length; ++ i ) {            gl.bindBuffer( gl.ARRAY_BUFFER, bufObj.buf[i] );            gl.vertexAttribPointer( bufObj.attr[i].loc, bufObj.attr[i].size, gl.FLOAT, false, 0, 0 );            gl.enableVertexAttribArray( bufObj.attr[i].loc );        }        gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufObj.inx );        gl.drawElements( gl.TRIANGLES, bufObj.inxLen, gl.UNSIGNED_SHORT, 0 );        for ( var i = 0; i < bufObj.buf.length; ++ i )           gl.disableVertexAttribArray( bufObj.attr[i].loc );        gl.bindBuffer( gl.ARRAY_BUFFER, null );        gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, null );    }        initScene();        })();
html,body {      height: 100%;      width: 100%;      margin: 0;      overflow: hidden;  }    #gui {      position : absolute;      top : 0;      left : 0;  }
<script id="draw-shader-vs" type="x-shader/x-vertex">      precision highp float;            attribute vec3 inPos;      attribute vec3 inNV;      attribute vec3 inCol;          varying vec3  vertPos;      varying vec3  vertNV;      varying vec3  vertCol;      varying float clip_distance;            uniform mat4 u_projectionMat44;      uniform mat4 u_viewMat44;      uniform mat4 u_modelMat44;      uniform vec4 u_clipPlane;            void main()      {             mat4 mv       = u_viewMat44 * u_modelMat44;           vertCol       = inCol;          vertNV        = normalize(mat3(mv) * inNV);          vec4 viewPos  = mv * vec4( inPos, 1.0 );          vertPos       = viewPos.xyz;          gl_Position   = u_projectionMat44 * viewPos;            vec4 modelPos  = u_modelMat44 * vec4( inPos, 1.0 );          vec4 clipPlane = vec4(normalize(u_clipPlane.xyz), u_clipPlane.w);           clip_distance  = dot(modelPos, clipPlane);      }  </script>      <script id="draw-shader-fs" type="x-shader/x-fragment">      precision mediump float;        varying vec3  vertPos;      varying vec3  vertNV;      varying vec3  vertCol;      varying float clip_distance;            void main()      {          if ( clip_distance < 0.0 )              discard;          vec3 color   = vertCol;          gl_FragColor = vec4( color.rgb, 1.0 );      }   </script>    <div>      <form id="gui" name="inputs">          <table>              <tr> <td> <font color= #CCF>clipping</font> </td>                    <td> <input type="range" id="clip" min="0" max="100" value="50" onchange="changeEventHandler(event);"/></td> </tr>          </table>      </form>  </div>      <canvas id="canvas" style="border: none;" width="100%" height="100%"></canvas>
like image 30
Rabbid76 Avatar answered Oct 04 '22 01:10

Rabbid76