opengl-es opengl-es-2.0 clipping

Clipping-planes en OpenGL ES 2.0



opengl-es opengl-es-2.0 (4)

Necesito recortar algunos cientos de objetos debajo de un plano de recorte en OpenGL ES 2.0 y apreciaría las ideas de las personas con más experiencia con este subconjunto de OpenGL.

En OpenGL ES 1.x hay glClipPlane. En el escritorio tienes glClipPlane, o gl_ClipDistance en tu shader. Ninguno de estos dos están disponibles en OpenGL ES 2.0. Parece que este tipo de funcionalidad desapareció completamente con 2.0.

Parece que la única forma de hacer esto es A) ejecutando la ecuación del plano en el fragmento de sombreado, o B) escriba un sombreado de vértice muy complejo que coloca los vértices en el plano si están detrás de él.

(A) sería lento en comparación con glClipPlane, ya que el recorte "regular" se realiza después del sombreado de vértice y antes del sombreador de fragmentos, cada fragmento aún tendría que procesarse y descartarse parcialmente.

(B) sería muy difícil hacer compatibles entre los sombreadores, ya que no podemos descartar vértices, tenemos que alinearlos con el plano y ajustar los atributos para aquellos que están "cortados". No es posible interpolar entre vértices en el sombreador sin enviar todos los vértices en una textura y muestrearlo, lo que sería extremadamente costoso. A menudo, probablemente sería imposible interpolar los datos correctamente de todos modos.

También he pensado en alinear el plano cercano con el plano de recorte, lo que sería una solución eficiente.

Y dibujar un plano después de renderizar toda la escena y verificar la falla de profundidad tampoco funcionará (a menos que esté mirando cerca del plano perpendicular al plano).

Lo que funciona para un solo objeto es dibujar el plano en el búfer de profundidad y luego renderizarlo con glDepthFunc (GL_GREATER), pero como se esperaba, no funciona cuando uno de los objetos está detrás de otro. Intenté desarrollar este concepto, pero al final terminé con algo muy similar a los volúmenes ocultos y tan costoso.

Entonces, ¿qué me estoy perdiendo? ¿Cómo harías el recorte de planos en OpenGL ES 2.0?


Aparentemente, estaba en algo cuando intenté hacer un recorte utilizando la matriz de proyección. Exactamente cómo hacer esto se describe en este documento que encontré: http://www.terathon.com/lengyel/Lengyel-Oblique.pdf

Espero que esto ayude a otras personas que buscan hacer esto.


Aquí hay dos soluciones que he encontrado en los foros de Vuforia SDK .

  1. Usando shaders por 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. Usando quad en profundidad buffer por Alessandro Boccalatte:

    • deshabilitar la escritura de color (es decir, establecer la glColorMask(false, false, false, false); )
    • generar un quad que coincida con la forma del marcador (es decir, solo un quad con el mismo tamaño y posición / orientación del marcador); esto solo se representará en el búfer de profundidad (porque inhabilitamos la escritura del búfer de color en el paso anterior)
    • habilitar de nuevo la máscara de color ( glColorMask(true, true, true, true); )
    • renderiza tus modelos 3D

Dado que la extensión EXT_clip_cull_distance no está disponible en OpenGL ES 2.0 (porque para esta extensión es necesario OpenGL ES 3.0), se debe emular el recorte. Se puede emular en el sombreador de fragmentos, descartando fragmentos. Ver Fragment Shader - Operaciones especiales .

Véase también OpenGL ES Shading Language 1.00 Specification; 6.4 saltos; página 58 :

La palabra clave de descarte solo se permite dentro de los sombreadores de fragmentos Se puede utilizar dentro de un sombreador de fragmentos para abandonar la operación en el fragmento actual. Esta palabra clave hace que el fragmento se descarte y no se producirán actualizaciones de ningún búfer. Normalmente se usaría dentro de una declaración condicional, por ejemplo:

if (intensity < 0.0) discard;

Un programa de sombreado que emula gl_ClipDistance puede tener este aspecto:

Sombreador de vértices:

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); }

Sombreador de fragmentos:

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 ); }

El siguiente ejemplo de WebGL demuestra esto. Tenga en cuenta que el contexto WebGL 1.0 se ajusta a la API de OpenGL ES 2.0.

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>


No sé si esto se aplica a OpenGL ES, pero OpenGL tiene la salida variable gl_ClipDistance habilitada por glEnable (GL_CLIP_DISTANCE0). Una vez habilitado, la primitiva se recorta de manera que gl_ClipDistance [0]> = 0 después del vértice y los sombreadores de geometría.

La distancia del clip se puede especificar como un producto de puntos con una ecuación de plano del espacio-mundo:

http://github.prideout.net/clip-planes/