three.js的examples的ocean的水面镜面倒影笔记

本文详细解析了Three.js中实现水面反射效果的核心代码及原理,重点介绍了如何通过计算mirrorcamera的位置来渲染倒影,并讨论了在实际应用过程中遇到的问题及解决办法。

https://threejs.org/examples/?q=ocean#webgl_shaders_ocean

思路

从镜面上看到反射的物像是因为物体表面的光线经镜面反射到达了人眼(camera)。而根据初中物理,这可以想象为镜面的另一端也有一个相机,称为mirror camera,这两个camera的连线垂直于反射面(平行于法向量)。要得到反射面上的倒影,就是要找到这个mirror camera的位置,然后从它的视角渲染一幅图,将这个图作为texture贴到反射面上即可。
官方例子中还用到了法线贴图增强真实感,下次再学。。。

关键代码

https://github.com/mrdoob/three.js/blob/master/examples/js/objects/Water.js
shader中涉及倒影实现的不多,主要看这个函数onBeforeRender,它实现了根据物理原理计算mirror camera的3个基本参数:世界位置, 上向量, 目标位置(lookAt)。
three.js里面的camera是没有target属性的,.lookAt()方法只能用于设置target,不能获取。
下面简单解释:
(注意这个函数里rotationMatrix的值先是反射面的旋转变换,后来是camera的旋转变换)

scope.onBeforeRender = function ( renderer, scene, camera ) {

        mirrorWorldPosition.setFromMatrixPosition( scope.matrixWorld );
        // 反射面的世界位置
        cameraWorldPosition.setFromMatrixPosition( camera.matrixWorld );
        // camera的世界位置
        rotationMatrix.extractRotation( scope.matrixWorld );
        // scope就是反射面的mesh对象

        normal.set( 0, 0, 1 );
        normal.applyMatrix4( rotationMatrix );

这里是设置一些初始值。normal的计算我猜想是这样的:首先,这个海面是一个PlaneGeometry,初始存在xOy平面,法向量为(0,0,1),然后手动地根据该海面的mesh的旋转去旋转它。

        view.subVectors( mirrorWorldPosition, cameraWorldPosition );

view是mirror camera的世界位置,这里首先求了一个从camera指向反射面位置的向量。

        // Avoid rendering when mirror is facing away
        if ( view.dot( normal ) > 0 ) return;

如果此时的view和反射面法向量点乘>0即夹角小于90°,说明当前camera看向的方向和法向量同向,也就是说camera是看不到海面的,就不用渲染了。

        view.reflect( normal ).negate();
        view.add( mirrorWorldPosition );

这个画个图能明白,最后计算出来的view就会指向mirror camera的位置了。
然后计算target。

        rotationMatrix.extractRotation( camera.matrixWorld );

        lookAtPosition.set( 0, 0, - 1 );
        lookAtPosition.applyMatrix4( rotationMatrix );
        lookAtPosition.add( cameraWorldPosition );

        target.subVectors( mirrorWorldPosition, lookAtPosition );
        target.reflect( normal ).negate();
        target.add( mirrorWorldPosition );

lookAtPosition.set( 0, 0, - 1 ); 这一句我不太确定是不是因为camera的默认target是(0,0,-1)。 要加上cameraWorldPosition 的原因大概是three.js中的lookAt得接受世界坐标吧。这部分代码还不是太懂。。。

        mirrorCamera.position.copy( view );
        mirrorCamera.up.set( 0, 1, 0 );
        mirrorCamera.up.applyMatrix4( rotationMatrix );
        mirrorCamera.up.reflect( normal );
        mirrorCamera.lookAt( target );

这里计算了上向量,跟计算反射面法向量原理差不多,这里的rotationMatrix 是对应camera的rotation的。

        mirrorCamera.far = camera.far; // Used in WebGLBackground

        mirrorCamera.updateMatrixWorld();
        mirrorCamera.projectionMatrix.copy( camera.projectionMatrix );

textureMatrix 在shader中用于计算texture的坐标,目测是实现了把[-1,1]映射到[0,1]

        // Update the texture matrix
        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
        );
        textureMatrix.multiply( mirrorCamera.projectionMatrix );
        textureMatrix.multiply( mirrorCamera.matrixWorldInverse );

下面一部分是对这个mirror texture进行裁剪的,在官方例子中如果不执行这一段,在球体靠近水面时, 倒影就像会浮出水面一样。(还没有仔细看这段。。

        // Now update projection matrix with new clip plane, implementing code from: http://www.terathon.com/code/oblique.html
        // Paper explaining this technique: http://www.terathon.com/lengyel/Lengyel-Oblique.pdf
        mirrorPlane.setFromNormalAndCoplanarPoint( normal, mirrorWorldPosition );
        mirrorPlane.applyMatrix4( mirrorCamera.matrixWorldInverse );

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

        var projectionMatrix = mirrorCamera.projectionMatrix;

        q.x = ( Math.sign( clipPlane.x ) + projectionMatrix.elements[ 8 ] ) / projectionMatrix.elements[ 0 ];
        q.y = ( Math.sign( 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
        clipPlane.multiplyScalar( 2.0 / clipPlane.dot( q ) );

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

        eye.setFromMatrixPosition( camera.matrixWorld );

下面就是对render的一些设置,关键执行renderer.render( scene, mirrorCamera, renderTarget, true ); 这一句即可。

        var currentRenderTarget = renderer.getRenderTarget();

        var currentVrEnabled = renderer.vr.enabled;
        var currentShadowAutoUpdate = renderer.shadowMap.autoUpdate;

        scope.visible = false;

        renderer.vr.enabled = false; // Avoid camera modification and recursion
        renderer.shadowMap.autoUpdate = false; // Avoid re-computing shadows

        renderer.render( scene, mirrorCamera, renderTarget, true );

        scope.visible = true;

        renderer.vr.enabled = currentVrEnabled;
        renderer.shadowMap.autoUpdate = currentShadowAutoUpdate;

        renderer.setRenderTarget( currentRenderTarget );

    };

学习心得(tucao)

这两天想把这个效果用到期末proj上,我确定我已经基本看懂了反射部分的代码,于是把它抽了出来,但发现我做的效果是错的,目测主要是texture的变换不正确:
这里写图片描述

百思不得其解,看了十几遍关键部分的代码,我觉得我已经和例子做的一模一样了。还是不行,于是找了原作者之一的一个简化版来看:
http://stemkoski.github.io/Three.js/FlatMirror-Water.html

差别并不大,不过这个版本感觉应该正确一些,第一个版本里面有些地方不知道为什么要调用negate()。于是我把它下载到本地运行,打算看看到底是哪里出错了。

经过一轮鼓捣之后跑起来了,但居然运行出来的投影也是错的:
这里写图片描述

黑人问号脸??

后来想到可能是three.js版本问题,把作者网站上的拷贝过来就。。好了。。:

这里写图片描述

猜想问题可能出在这两行代码:

this.mirrorWorldPosition.getPositionFromMatrix( this.matrixWorld );
this.cameraWorldPosition.getPositionFromMatrix( this.camera.matrixWorld );

getPositionFromMatrix在新版本中调用会提示你使用另一个函数setFromMatrixPosition。但提示说只是进行了renamed,也不确定是不是它的锅。

而且官方的ocean运行是没有问题的。可能那几个negate()就是修复这个问题的吧(虽然并不知道是什么问题

所以还是模仿官网的例子吧(:з」∠)..

今天终于发现了错误,出在mirror camera的位置计算上。本来我写的是:

   var seedir = seaworldpos.clone().sub(camworldpos); 
    if (seedir.dot(seanorm) <= 0) {
        mirworldpos = seedir.reflect(seanorm);   
        mirworldpos.add( seaworldpos );

正确的是:

   var seedir = seaworldpos.clone().sub(camworldpos); 
    if (seedir.dot(seanorm) <= 0) {
        mirworldpos = seedir.reflect(seanorm).negate(); // 要反过来   
        mirworldpos.add( seaworldpos );

正确效果:
这里写图片描述

### 使用 Three.js 创建简单 3D 飞机 为了创建一个简单的 3D 飞机模型,可以利用 Three.js 这一简化了 WebGL 的库来实现浏览器中的 3D 效果[^1]。下面是一个基础的例子,展示了如何设置场景、相机以及渲染器,并加入了一个基本几何体作为飞机的代表。 ```javascript // 导入Three.js核心功能 import * as THREE from 'three'; // 初始化场景 const scene = new THREE.Scene(); // 设置透视摄像机 const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); // 定义WebGLRenderer用于绘制图形到页面上 const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // 添加灯光以便能够看到物体颜色 const light = new THREE.DirectionalLight(0xffffff, 1); light.position.set(5, 5, 5).normalize(); scene.add(light); // 创建一个立方体网格表示飞机主体部分 const geometry = new THREE.BoxGeometry(); const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 }); const cube = new THREE.Mesh(geometry, material); scene.add(cube); camera.position.z = 5; // 动画循环函数不断更新画面帧数 function animate() { requestAnimationFrame(animate); // 让立方体旋转起来模拟飞行效果 cube.rotation.x += 0.01; cube.rotation.y += 0.01; renderer.render(scene, camera); } animate(); ``` 此段代码构建了一个非常基础的三维环境,在其中放置了一架由绿色方块组成的简易“飞机”,并通过持续改变其角度来模仿飞行运动的效果。对于更复杂的项目来说,可能还需要引入额外的对象如翅膀或其他细节特征,这可以通过调整 `geometry` 和 `material` 参数完成。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值