Threejs 官网镜子demo源码分析

 

 

效果如下:

这个功能其实跟webgl 编程指南中的纹理demo一样的思路

思路是把相机放在镜子的位置,所形成材质,贴在镜子上面,这个有区别的是,它还计算了相机的位置,通过相机位置拿到与镜子镜像的相机位置,所形成的纹理;

demo 如下:

<!DOCTYPE html>
<html lang="en">
   <head>
      <title>镜像shader编写</title>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
      <style>
         body {
            color: #888888;
            font-family:Monospace;
            font-size:13px;

            background-color: #000;
            margin: 0px;
            overflow: hidden;
         }

         #info {
            position: absolute;
            top: 0px;
            width: 200px;
            left: calc(50% - 100px);
            text-align: center;
         }

         a {
            color: #00f;
         }
      </style>
   </head>
   <body>

      <div id="container"></div>
      <div id="info">
      </div>

      <script src="./js/three.min.js"></script>
      <script src="./js/Mirror.js"></script>
      <script src="./js/controls/OrbitControls.js"></script>

      <script>

         // scene size
         var WIDTH = window.innerWidth;
         var HEIGHT = window.innerHeight;

         // camera
         var VIEW_ANGLE = 45;
         var ASPECT = WIDTH / HEIGHT;
         var NEAR = 1;
         var FAR = 500;

         var camera, scane, renderer;

         var cameraControls;

         var verticalMirror, groundMirror;
         var sphereGroup, smallSphere;

         function init() {

            // renderer
            renderer = new THREE.WebGLRenderer();
            renderer.setSize( WIDTH, HEIGHT );

            renderer.autoClear = true;
            renderer.setClearColor( 0x000000, 1 );

            // scene
            scene = new THREE.Scene();

            // camera
            camera = new THREE.PerspectiveCamera(VIEW_ANGLE, ASPECT, NEAR, FAR);
            camera.position.set( 0, 75, 160 );

            cameraControls = new THREE.OrbitControls(camera, renderer.domElement);
            cameraControls.target.set( 0, 40, 0);
            cameraControls.maxDistance = 400;
            cameraControls.minDistance = 10;
            cameraControls.update();

            var container = document.getElementById( 'container' );
            container.appendChild( renderer.domElement );

         }

         function fillScene() {

            var planeGeo = new THREE.PlaneGeometry( 100, 100 );
            
            //MIRORR planes
            groundMirror = new THREE.Mirror( renderer, camera, { clipBias: 0.0, textureWidth: WIDTH, textureHeight: HEIGHT,debugMode:true} );
            
            var mirrorMesh = new THREE.Mesh( planeGeo, groundMirror.material );
            mirrorMesh.add( groundMirror );
            mirrorMesh.rotateX( - Math.PI / 2 );
            scene.add( mirrorMesh );

            verticalMirror = new THREE.Mirror( renderer, camera, { clipBias: 0.01, textureWidth: WIDTH, textureHeight: HEIGHT, color:0x889999 } );
            
            var verticalMirrorMesh = new THREE.Mesh( new THREE.PlaneGeometry( 60, 60 ), verticalMirror.material );
            verticalMirrorMesh.add( verticalMirror );
            verticalMirrorMesh.position.y = 35;
            verticalMirrorMesh.position.z = -45;
            scene.add( verticalMirrorMesh );

            sphereGroup = new THREE.Object3D();
            scene.add( sphereGroup );

            var geometry = new THREE.CylinderGeometry( 0.1, 15 * Math.cos( Math.PI / 180 * 30 ), 0.1, 24, 1 );
            var material = new THREE.MeshPhongMaterial( { color: 0xffffff, emissive: 0x444444 } );
            var sphereCap = new THREE.Mesh( geometry, material );
            sphereCap.position.y = -15 * Math.sin( Math.PI / 180 * 30 ) - 0.05;
            sphereCap.rotateX(-Math.PI);
            
            var geometry = new THREE.SphereGeometry( 15, 24, 24, Math.PI / 2, Math.PI * 2, 0, Math.PI / 180 * 120 );
            var halfSphere = new THREE.Mesh( geometry, material );
            halfSphere.add( sphereCap );
            halfSphere.rotateX( - Math.PI / 180 * 135 ); 
            halfSphere.rotateZ( - Math.PI / 180 * 20 ); 
            halfSphere.position.y = 7.5 + 15 * Math.sin( Math.PI / 180 * 30 );
            
            sphereGroup.add( halfSphere );
            
            var geometry = new THREE.IcosahedronGeometry( 5, 0 );
            var material = new THREE.MeshLambertMaterial( { color: 0xffffff, emissive: 0x333333, shading: THREE.FlatShading } );
            smallSphere = new THREE.Mesh( geometry, material );
            scene.add(smallSphere);    
            
            // walls
            var planeTop = new THREE.Mesh( planeGeo, new THREE.MeshPhongMaterial( { color: 0xffffff } ) );
            planeTop.position.y = 100;
            planeTop.rotateX( Math.PI / 2 );
            scene.add( planeTop );
            
            var planeBack = new THREE.Mesh( planeGeo, new THREE.MeshPhongMaterial( { color: 0xffffff } ) );
            planeBack.position.z = -50;
            planeBack.position.y = 50;
            scene.add( planeBack );
            
            var planeFront = new THREE.Mesh( planeGeo, new THREE.MeshPhongMaterial( { color: 0x7f7fff } ) );
            planeFront.position.z = 50;
            planeFront.position.y = 50;
            planeFront.rotateY( Math.PI );
            scene.add( planeFront );
            
            var planeRight = new THREE.Mesh( planeGeo, new THREE.MeshPhongMaterial( { color: 0x00ff00 } ) );
            planeRight.position.x = 50;
            planeRight.position.y = 50;
            planeRight.rotateY( - Math.PI / 2 );
            scene.add( planeRight );
            
            var planeLeft = new THREE.Mesh( planeGeo, new THREE.MeshPhongMaterial( { color: 0xff0000 } ) );
            planeLeft.position.x = -50;
            planeLeft.position.y = 50;
            planeLeft.rotateY( Math.PI / 2 );
            scene.add( planeLeft );
            
            // lights
            var mainLight = new THREE.PointLight( 0xcccccc, 1.5, 250 );
            mainLight.position.y = 60;
            scene.add( mainLight );

            var greenLight = new THREE.PointLight( 0x00ff00, 0.25, 1000 );
            greenLight.position.set( 550, 50, 0 );
            scene.add( greenLight );

            var redLight = new THREE.PointLight( 0xff0000, 0.25, 1000 );
            redLight.position.set( - 550, 50, 0 );
            scene.add( redLight );

            var blueLight = new THREE.PointLight( 0x7f7fff, 0.25, 1000 );
            blueLight.position.set( 0, 50, 550 );
            scene.add( blueLight );

         }

         function render() {

            // render (update) the mirrors
            groundMirror.renderWithMirror( verticalMirror );
            verticalMirror.renderWithMirror( groundMirror );
            //groundMirror.updateTextureMatrix();
            //groundMirror.render();
               //    verticalMirror.updateTextureMatrix();
            //verticalMirror.render();
            renderer.render(scene, camera);

         }

         function update() {
            
            requestAnimationFrame( update );

            var timer = Date.now() * 0.01;

            sphereGroup.rotation.y -= 0.002;

            smallSphere.position.set(
               Math.cos( timer * 0.1 ) * 30,
               Math.abs( Math.cos( timer * 0.2 ) ) * 20 + 5,
               Math.sin( timer * 0.1 ) * 30
            );
            smallSphere.rotation.y = ( Math.PI / 2 ) - timer * 0.1;
            smallSphere.rotation.z = timer * 0.8;

            cameraControls.update();
            
            render();
         }

         init();
         fillScene();
         update();

      </script>
   </body>
</html>

 

主要的代码在Mirror.js中,如下:

/**
 * @author Slayvin / http://slayvin.net
 */
//自定义着色器,这里定义一个镜像着色器
THREE.ShaderLib['mirror'] = {

    uniforms: {
        "mirrorColor": {type: "c", value: new THREE.Color(0x7F7F7F)},
        "mirrorSampler": {type: "t", value: null},
        "textureMatrix": {type: "m4", value: new THREE.Matrix4()}
    },

    vertexShader: [

        "uniform mat4 textureMatrix;",

        "varying vec4 mirrorCoord;",

        "void main() {",

        "vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );",
        "vec4 worldPosition = modelMatrix * vec4( position, 1.0 );",
        //镜子所在的位置乘以纹理矩阵,得到纹理坐标
        "mirrorCoord = textureMatrix * worldPosition;",
        //这个给每个顶点gl_Position的赋值并没有什么特别,和普通着色器一样
        "gl_Position = projectionMatrix * mvPosition;",

        "}"

    ].join("\n"),

    fragmentShader: [

        "uniform vec3 mirrorColor;",
        "uniform sampler2D mirrorSampler;",

        "varying vec4 mirrorCoord;",

        "float blendOverlay(float base, float blend) {",
        "return( base < 0.5 ? ( 2.0 * base * blend ) : (1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) );",
        "}",

        "void main() {",
        //得到纹理的颜色如果texture2DProj的 第二个参数(比如说是coord)是vec3类型,刚做如下变换coord.s /= coord.p 和coord.t/=coord.p
        //如果是vec4类型,则是coord.s /=coord.q 和 coord.t /= coord.q ;好像就是将它归一化再执行纹理查询;
        //texture2DProj:就是把纹理投射到场景的物体上,就像一个投影机把幻灯片投影到其他物体上;textureProj函数是用来访问纹理的(传入的纹理坐标是投影坐标系下的坐标),在平移缩放投影矩阵后,我们需要除以其坐标中的w项
        //而textureProj会为我们做这些;(https://blog.csdn.net/u012419410/article/details/41994117)
        //projtextcoord = 偏移矩阵 * 光源投影矩阵 * 光源观察矩阵 * 建模矩阵
        "vec4 color = texture2DProj(mirrorSampler, mirrorCoord);",
        //纹理的颜色和镜子本来的颜色进行混合,得到最终的纹理颜色
        "color = vec4(blendOverlay(mirrorColor.r, color.r), blendOverlay(mirrorColor.g, color.g), blendOverlay(mirrorColor.b, color.b), 1.0);",

        "gl_FragColor = color;",

        "}"

    ].join("\n")

};

THREE.Mirror = function (renderer, camera, options) {

    THREE.Object3D.call(this);//继承Threejs 的Object3D,得到相关函数

    //生成一个名字
    this.name = 'mirror_' + this.id;

    //判断value值是不是2的次方,纹理的宽高最好是2的次方;有些硬件不支持,也便于运算速度
    function isPowerOfTwo(value) {
        return ( value & ( value - 1 ) ) === 0;
    };
    //如果没有传入参数,那么option为{}
    options = options || {};
    //是否更新矩阵
    this.matrixNeedsUpdate = true;
    //纹理的高度和宽度,默认为512
    var width = options.textureWidth !== undefined ? options.textureWidth : 512;
    var height = options.textureHeight !== undefined ? options.textureHeight : 512;
    //剪切
    this.clipBias = options.clipBias !== undefined ? options.clipBias : 0.0;
    //镜像颜色
    var mirrorColor = options.color !== undefined ? new THREE.Color(options.color) : new THREE.Color(0x7F7F7F);

    this.renderer = renderer;
    this.mirrorPlane = new THREE.Plane();//平面
    this.normal = new THREE.Vector3(0, 0, 1);
    this.mirrorWorldPosition = new THREE.Vector3();//镜面位置
    this.cameraWorldPosition = new THREE.Vector3();//相机位置
    this.rotationMatrix = new THREE.Matrix4();//旋转矩阵
    this.lookAtPosition = new THREE.Vector3(0, 0, -1);
    this.clipPlane = new THREE.Vector4();//剪切平面

    // 如果调试,将显示一个调试的plane在场景中
    var debugMode = options.debugMode !== undefined ? options.debugMode : false;
    //调试用的平面和箭头
    if (debugMode) {

        var arrow = new THREE.ArrowHelper(new THREE.Vector3(0, 0, 1), new THREE.Vector3(0, 0, 0), 10, 0xffff80);
        var planeGeometry = new THREE.Geometry();
        planeGeometry.vertices.push(new THREE.Vector3(-10, -10, 0));
        planeGeometry.vertices.push(new THREE.Vector3(10, -10, 0));
        planeGeometry.vertices.push(new THREE.Vector3(10, 10, 0));
        planeGeometry.vertices.push(new THREE.Vector3(-10, 10, 0));
        planeGeometry.vertices.push(planeGeometry.vertices[0]);
        var plane = new THREE.Line(planeGeometry, new THREE.LineBasicMaterial({color: 0xffff80}));

        this.add(arrow);
        this.add(plane);

    }
    //是否使用透视相机,没有就创建一个
    if (camera instanceof THREE.PerspectiveCamera) {

        this.camera = camera;

    } else {

        this.camera = new THREE.PerspectiveCamera();
        console.log(this.name + ': camera is not a Perspective Camera!');

    }
    //初始化纹理矩阵
    this.textureMatrix = new THREE.Matrix4();
    //复制一个相机,主要是得到相机的一些参数
    this.mirrorCamera = this.camera.clone();
    //WebGLRenderTarget对应的是帧缓存,就是屏幕中显示的一帧在内存中的表示,这里声明两个帧缓存,主要是用来加快动画的切换速度
    this.texture = new THREE.WebGLRenderTarget(width, height);
    this.tempTexture = new THREE.WebGLRenderTarget(width, height);

    //没有一种内置的着色器能够完成光线最终的效果,所以,这里我们必须自己定义一个着色器,
    var mirrorShader = THREE.ShaderLib["mirror"];
    var mirrorUniforms = THREE.UniformsUtils.clone(mirrorShader.uniforms);

    //定义一个着色器材质,传入顶点和片元着色器
    this.material = new THREE.ShaderMaterial({

        fragmentShader: mirrorShader.fragmentShader,
        vertexShader: mirrorShader.vertexShader,
        uniforms: mirrorUniforms

    });
    //向材质着色器传入变量,将纹理传入着色器中
    this.material.uniforms.mirrorSampler.value = this.texture;
    //镜子的颜色
    this.material.uniforms.mirrorColor.value = mirrorColor;
    //传入纹理矩阵
    this.material.uniforms.textureMatrix.value = this.textureMatrix;

    //如果纹理的宽高不是2的次方,那么久不生成mipmap贴图
    if (!isPowerOfTwo(width) || !isPowerOfTwo(height)) {

        this.texture.generateMipmaps = false;
        this.tempTexture.generateMipmaps = false;

    }
    //更新纹理矩阵
    this.updateTextureMatrix();
    //渲染纹理,将纹理渲染到texture 或temTexture中
    this.render();

};

THREE.Mirror.prototype = Object.create(THREE.Object3D.prototype);
//镜子渲染
THREE.Mirror.prototype.renderWithMirror = function (otherMirror) {

    // 更新镜像矩阵以镜像当前视图为纹理
    this.updateTextureMatrix();
    this.matrixNeedsUpdate = false;

    // 设置另一面镜子的相机,这样镜像视图就是参考视图
    var tempCamera = otherMirror.camera;
    otherMirror.camera = this.mirrorCamera;

    // 用临时纹理渲染另一面镜子
    otherMirror.renderTemp();
    otherMirror.material.uniforms.mirrorSampler.value = otherMirror.tempTexture;

    // render the current mirror
    this.render();
    this.matrixNeedsUpdate = true;

    // restore material and camera of other mirror
    otherMirror.material.uniforms.mirrorSampler.value = otherMirror.texture;
    otherMirror.camera = tempCamera;

    // restore texture matrix of other mirror
    otherMirror.updateTextureMatrix();
};
//更新纹理函数
THREE.Mirror.prototype.updateTextureMatrix = function () {
    //Three.math.sign 这个函数用来返回一个数的符号,
    var sign = THREE.Math.sign;
    //更新世界矩阵,也就是更新THREE.Mirror 的位置,THREE.Mirror中提供了镜子的纹理和调试时的显示的平面和箭头,
    //所以需要对其位置进行实时更新,那么平面和箭头的位置才能够实时更新;
    this.updateMatrixWorld();
    //更新相机的位置,这个相机是全局的相机
    this.camera.updateMatrixWorld();
    //从世界矩阵中得到目前镜子所在的位置
    this.mirrorWorldPosition.getPositionFromMatrix(this.matrixWorld);
    //得到相机目前所在的位置
    this.cameraWorldPosition.getPositionFromMatrix(this.camera.matrixWorld);
    //从镜子的世界矩阵中计算出其旋转矩阵出来。世界矩阵能够表达位移,旋转,缩放,所以能够单独的提取出旋转矩阵出来
    this.rotationMatrix.extractRotation(this.matrixWorld);
    //设置镜子的法向量
    this.normal.set(0, 0, 1);
    //法向量乘上旋转矩阵;(物体的旋转和放大缩小,位移,它们的法向量只与旋转矩阵有关)
    this.normal.applyMatrix4(this.rotationMatrix);
    //计算摄像机指向镜子的向量
    var view = this.mirrorWorldPosition.clone().sub(this.cameraWorldPosition);
    //根据法线,计算出view的反射向量
    var reflectView = view.reflect(this.normal);
    //计算出反射向量,并根据镜子的位置得出向量的结果,最终得到摄像机反射后应该所在的位置
    reflectView.add(this.mirrorWorldPosition);

    //从相机的世界矩阵得到相机的旋转矩阵
    this.rotationMatrix.extractRotation(this.camera.matrixWorld);
    //对相机的目标点向量也进行旋转,并加到相机目前的位置,即可得相机的目标点应该所在的位置
    this.lookAtPosition.set(0, 0, -1);
    this.lookAtPosition.applyMatrix4(this.rotationMatrix);
    this.lookAtPosition.add(this.cameraWorldPosition);

    //对目标进行镜像反射,得到位置
    var target = this.mirrorWorldPosition.clone().sub(this.lookAtPosition);
    var reflectTarget = target.reflect(this.normal);
    reflectTarget.add(this.mirrorWorldPosition);

    this.up.set(0, -1, 0);
    this.up.applyMatrix4(this.rotationMatrix);
    var reflectUp = this.up.reflect(this.normal);

    //镜子中的场景是一个单独的摄像机来实现的,这个相机的位置是reflectView
    this.mirrorCamera.position.copy(reflectView);
    //相机的上方向
    this.mirrorCamera.up = reflectUp;
    //相机所看到的位置
    this.mirrorCamera.lookAt(reflectTarget);
    //更新相机的投影矩阵
    this.mirrorCamera.updateProjectionMatrix();
    //更新相机的世界矩阵
    this.mirrorCamera.updateMatrixWorld();
    //相机的投影视图矩阵的逆矩阵
    this.mirrorCamera.matrixWorldInverse.getInverse(this.mirrorCamera.matrixWorld);

    // 将纹理缩小0.5,这样就不会将这个场景映射到镜子中了;
    this.textureMatrix.set(0.5, 0.0, 0.0, 0.5,
        0.0, 0.5, 0.0, 0.5,
        0.0, 0.0, 0.5, 0.5,
        0.0, 0.0, 0.0, 1.0);
    //纹理矩阵乘以投影矩阵,最后得到在屏幕中显示的矩阵,及投影视图矩阵,
    this.textureMatrix.multiply(this.mirrorCamera.projectionMatrix);
    this.textureMatrix.multiply(this.mirrorCamera.matrixWorldInverse);

    //用现在新的剪切算法更新矩阵, 可以看这个: http://www.terathon.com/code/oblique.html
    //相关论文: http://www.terathon.com/lengyel/Lengyel-Oblique.pdf
    //以下代码去掉都不会影响最终的效果,这里只是让剪切效果更好一些
    this.mirrorPlane.setFromNormalAndCoplanarPoint(this.normal, this.mirrorWorldPosition);
    this.mirrorPlane.applyMatrix4(this.mirrorCamera.matrixWorldInverse);

    this.clipPlane.set(this.mirrorPlane.normal.x, this.mirrorPlane.normal.y, this.mirrorPlane.normal.z, this.mirrorPlane.constant);

    var q = new THREE.Vector4();
    var projectionMatrix = this.mirrorCamera.projectionMatrix;

    q.x = ( sign(this.clipPlane.x) + projectionMatrix.elements[8] ) / projectionMatrix.elements[0];
    q.y = ( sign(this.clipPlane.y) + projectionMatrix.elements[9] ) / projectionMatrix.elements[5];
    q.z = -1.0;
    q.w = ( 1.0 + projectionMatrix.elements[10] ) / projectionMatrix.elements[14];

    // Calculate the scaled plane vector
    var c = new THREE.Vector4();
    c = this.clipPlane.multiplyScalar(2.0 / this.clipPlane.dot(q));

    // Replacing the third row of the projection matrix
    projectionMatrix.elements[2] = c.x;
    projectionMatrix.elements[6] = c.y;
    projectionMatrix.elements[10] = c.z + 1.0 - this.clipBias;
    projectionMatrix.elements[14] = c.w;

};

THREE.Mirror.prototype.render = function () {
    //更新纹理矩阵
    if (this.matrixNeedsUpdate) this.updateTextureMatrix();
    //是否更新矩阵
    this.matrixNeedsUpdate = true;

    // 将当前场景的镜像渲染到目标纹理texture中
    var scene = this;
    //找到父场景,这里是主场景中可以添加子场景
    while (scene.parent !== undefined) {

        scene = scene.parent;

    }

    if (scene !== undefined && scene instanceof THREE.Scene) {
        //通过相机和场景,将帧渲染到texture中
        /*render(scene,camera,renderTarget,forceClear)
         * scene :场景对象
         * camera : 相机对象
         * renderTarget : 渲染目标
         * forceClear :是否强制清屏
          * */

        this.renderer.render(scene, this.mirrorCamera, this.texture, true);

    }

};
//临时渲染
THREE.Mirror.prototype.renderTemp = function () {

    if (this.matrixNeedsUpdate) this.updateTextureMatrix();

    this.matrixNeedsUpdate = true;

    // 将当前场景的镜像视图渲染为目标纹理
    var scene = this;

    while (scene.parent !== undefined) {

        scene = scene.parent;

    }

    if (scene !== undefined && scene instanceof THREE.Scene) {

        this.renderer.render(scene, this.mirrorCamera, this.tempTexture, true);

    }

};

 

转载请说明来源:https://mp.csdn.net/postedit

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值