webGL clipping plane和doubleside

来源:互联网 发布:linux shell 创建文件 编辑:程序博客网 时间:2024/06/05 22:33

完整的例子 clippingplane
1.几何基础知识
1)已知平面上一点和此点处法向量可以确定平面。不妨设这一点是r0,法向量是n,对平面任一点r有
这里写图片描述
计算得
这里写图片描述
整理得
这里写图片描述 这里写图片描述
可见一个平面可以用[a,b,c,d]表示。
2)点到平面的距离公式是
这里写图片描述
标准化法向量则
这里写图片描述
整理得
这里写图片描述
再去掉绝对值,则当D<0时点在平面法向量所指一边,D>0时在另一边,D=0时在平面上。

2.利用openGL的内建变量
openGL在vertexShader里有一个内建数组变量 gl_ClipDistance。可以用它存放当前点到平面的距离。因为可能有多个平面,所以它是数组变量。vertexShader处理当前点时,计算出它距平面A的距离可以存放在 gl_ClipDistance[0]里,与平面B的距离存在gl_ClipDistance[1]里,如此类推。这些值在光栅化时会线性插值,小于0的fragment不会传给fragmentShader,从而实现clipping。 这里openGL认为与平面距离小于0的点会被cull,它认为法线方向为正方向。这是人为的规定,不过控制权在流水线不可编程的图元装配和光栅化部分,所以不可以改变。下面将看到,在webGL里,在fragmentShader里编程实现对fragment的选取,这样既可以选择距离大于0的点,也可以相反。不过为了统一,选取法线方向为正方向是一个好选择。

//opengl的可选做法#version 330 core//vec4(a,b,c,d)表示一个平面,为了计算方便(a,b,c)一般已经标准化,注意对一个平面法向量进行标准化时,d也要相应变化float gl_ClipDistance[1];//声明内建数组的长度为1,表示只有一个clipping planeuniform vec4 Plane;{     //计算出当前点距离平面的距离存在数组第0位置     gl_ClipDistance[0]=计算出来的距离}

2.webGL的做法
因为webGL不支持这个内建变量,所以不能用这个方法。可以在vertexShader里计算点到平面的距离,再声明自己的varying变量来记录距离,这个变量会插值赋给每一个fragment。在fragmentShader里根据这个值来决定是否clipping当前fragment。没有内建变量就声明varying变量,只不过对fragment的选取不在光栅化部分,无论这个值的大小所有fragment都将传给fragmentShader处理,所以要在fragmengShader里编程实现。
计算距离也可以放到fragmentShader里,这样vertexShader只要将点的位置传给fragmentShader。此时插值的是点的位置不再是距离。例子里将选取这一做法。
要注意的是计算距离时平面和点要在同一的坐标系下,这里不妨选取view坐标系。传给fragmentShader用来表示平面的uniform变量vec4(a,b,c,d)为了方便不妨选取model坐标系,然后在fragmentShader里对它进行转变。为了得到view坐标系下的平面表示,我们需要知道在model坐标系下,平面上一点的坐标和这一点处的法向量,再将这一点和法向量转为view坐标系下的表示。就像将在model坐标系里的点转为view坐标系里的点是用ViewMatrix*ModelMatrix*P一样。ViewMatrix*ModelMatrix*r0得到r0在view坐标系下的坐标,而根据ViewMatrix*ModelMatrix矩阵的左上3*3矩阵的逆的转置得到的矩阵可以将法向量(a,b,c)转为view坐标系下的表示,这叫法向量转换矩阵,具体的数学推导差不多每一本计算机图像入门书籍都有,网上也随处可见。
现在问题是,根据传进来的vec4(a,b,c,d),可以知道法向量是vec3(a,b,c),因为平面上任何点处的法向量都一样,所以r0处法向量也是vec3(a,b,c),但是怎么找出一个r0?这里根据法向量已经标准化即a*a+b*b+c*c=1和这里写图片描述这两个事实,可以得知点vec3(-ad,-bd,-cd)在平面上。

<script id="vertexShader" type="x-shader/x-vertex">    // = object.matrixWorld    uniform mat4 modelMatrix;    // = camera.matrixWorldInverse * object.matrixWorld    uniform mat4 modelViewMatrix;    // = camera.projectionMatrix    uniform mat4 projectionMatrix;    // = camera.matrixWorldInverse    uniform mat4 viewMatrix;    // default vertex attributes provided by Geometry and BufferGeometry    attribute vec3 position;    //点在view坐标系下的坐标,将在光栅化部分插值赋给每一个fragment    varying vec3 modelViewPosition;    void main()    {        //计算点在view坐标系下的坐标        modelViewPosition=(viewMatrix*modelMatrix*vec4(position,1.0)).xyz;        //计算标准坐标系坐标        gl_Position=projectionMatrix*viewMatrix*modelMatrix*vec4(position,1.0);    }</script><script id="fragmentShader" type="x-shader/x-fragment">    precision highp float;    // = camera.matrixWorldInverse    uniform mat4 viewMatrix;    //平面的法向量变换矩阵    uniform mat3 viewNormalMatrix;    uniform vec4 plane;//[a,b,c,d],(a,b,c)is normal must be normalized, d is the constant    varying vec3 modelViewPosition;    //将传入的plane转换为view坐标系下的表示    vec4 planeToEC(vec4 plane,mat4 viewMatrix,mat3 viewNormalMatrix)    {        //(a,b,c)is normalized, a*a+b*b+c*c=1,(-ad,-bd,-cd) is on the plane ax+by+cz+d=0        vec3 normal=vec3(plane.x,plane.y,plane.z);        //计算model坐标系下的r0        vec3 pointInPlaneWC=normal*-plane.w;        //计算view坐标系下的r0        vec3 pointInPlaneEC=(viewMatrix*vec4(pointInPlaneWC.xyz,1.0)).xyz;        //计算view坐标系下的法向量        vec3 normalOfPlaneInEC=normalize(viewNormalMatrix*normal);        //返回view坐标系下的平面表示        //-dot(normalOfPlaneInEC,pointInPlaneEC)是根据view坐标系下的法向量和r0来计算d        return vec4(normalOfPlaneInEC,-dot(normalOfPlaneInEC,pointInPlaneEC));    }    //计算点到平面距离,此时平面和点都在view坐标系下    float distanceToPlane(vec4 plane,vec3 position)    {            //根据公式计算            float distance=dot(vec3(plane.x,plane.y,plane.z),position)+plane.w;            return distance;    }    void main()    {        vec4 planeInEC=planeToEC(plane,viewMatrix,viewNormalMatrix);                float distance=distanceToPlane(planeInEC,modelViewPosition);        if(distance<0.0)        //在平面法向量相反方向的fragment被丢弃,discard的fragment不影响color buffer也不影响depth buffer。如果不discard只是不赋值gl_FragColor的话,这些fragment只是默认黑色,还是会影响color buffer和depth buffer。因为它们总是更靠近视点,所以总是挡住内侧,你永远看不到一个球的内部。            discard;            //gl_FrontFacing==true表示fragment来自图元正面        if(gl_FrontFacing==true&&distance>=0.0)            //如果fragment来自正面,赋值蓝色            gl_FragColor=vec4(0.0,0.0,1.0,1.0);        if(gl_FrontFacing==false&&distance>0.0)            //负面,赋值绿色            gl_FragColor=vec4(0.0,1.0,0.0,1.0);    }</script>
<script>    if (!Detector.webgl)        Detector.addGetWebGLMessage();    var container = document.getElementById( "container" );    var scene, renderer, camera;    var mesh;    renderer = new THREE.WebGLRenderer({antialias:true});    renderer.setPixelRatio( window.devicePixelRatio );    renderer.setSize( window.innerWidth, window.innerHeight );    container.appendChild(renderer.domElement);    scene=new THREE.Scene();    var aspect = window.innerWidth / window.innerHeight;    camera=new THREE.PerspectiveCamera(45,aspect,1,1000);    camera.position.set(0,0,8);    var controls=new THREE.OrbitControls(camera,renderer.domElement);    controls.target.set(0,0,-0.0);    controls.autoRotate=false;    var rawShaderMaterial=new THREE.RawShaderMaterial(                 {                    uniforms:                     {                        plane:{value:new THREE.Vector4(0.0,0.0,-1.0,1.0)},                        viewNormalMatrix:{value:new THREE.Matrix3()}                    },                    vertexShader:document.getElementById('vertexShader').textContent,                    fragmentShader:document.getElementById('fragmentShader').textContent                } );    //如果material.side的值为THREE.DoubleSide,render()函数里会调用gl.disable(gl.CULL_FACE),确保反面的fragment也会传给fragmentShader。    //    rawShaderMaterial.side=THREE.DoubleSide;    ballGeometry=new THREE.SphereGeometry(1.8,100,100);    ballMaterial=rawShaderMaterial;    ballMesh=new THREE.Mesh(ballGeometry,ballMaterial);    scene.add(ballMesh);    animate();        function animate()        {                requestAnimationFrame(animate);                controls.update();                //获得平面的法向量转换矩阵                rawShaderMaterial.uniforms.viewNormalMatrix.value                .getNormalMatrix(camera.matrixWorldInverse);                renderer.render(scene,camera);        }</script>

对于多个平面,比如说平面A,B,C,将算出距离da,db,dc。我们可以discard掉所有(da<0.0||db<0.0||dc<0.0)的fragment,即被任一个平面cull掉的fragment都不要。也可以只cull掉(da<0.0&&db<0.0&&dc<0.0)的fragment。

3.THREE.js的做法
THREE.js包装了webGL的做法。

    //声明几个平面    var plane1=new THREE.Plane().setComponents(0,0,-1,1);    var plane2=new THREE.Plane().setComponents(-1,0,0,0);    var plane3=new THREE.Plane().setComponents(0,-1,0,0);    //放到数组里    var clippingPlanes=[];    clippingPlanes.push(plane1);    clippingPlanes.push(plane2);    clippingPlanes.push(plane3);    renderer = new THREE.WebGLRenderer({antialias:true});    renderer.setPixelRatio( window.devicePixelRatio );    renderer.setSize( window.innerWidth, window.innerHeight );    //数组赋值给WebGLRenderer的成员变量clippingPlanes,记得material.side=THREE.DoubleSide    renderer.clippingPlanes=clippingPlanes;

而对于(da<0.0||db<0.0||dc<0.0)或(da<0.0&&db<0.0&&dc<0.0)的选取是设置material的clipIntersection属性。

.clipIntersection
Changes the behavior of clipping planes so that only their intersection is clipped, rather than their union. Default is false.

原创粉丝点击