效果如下:
这个功能其实跟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