袋鼠云研发手记 | Easy[V]数据可视化揭秘:Threejs倒影解析

Posted 袋鼠云技术团队

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了袋鼠云研发手记 | Easy[V]数据可视化揭秘:Threejs倒影解析相关的知识,希望对你有一定的参考价值。

袋鼠云研发手记

作为一家创新驱动的科技公司,袋鼠云每年研发投入达数千万,公司80%员工都是技术人员,)、等产品也在不断迭代。在进行产品研发的过程中,技术小哥哥们能文能武,不断提升产品性能和体验的同时,也把这些提升和优化过程记录下来,现录入“袋鼠云研发手记”专栏中,以和业内童鞋们分享交流。


袋鼠云前端团队

知乎专栏@DTUX

袋鼠云UX团队拥有十多名专家级别、经验丰富的前端开发工程师,支撑公司大数栈产品线的不同子项目的开发需求,具体包括数据中台产品「数栈」与数据可视化产品Easy[V]两大块。


在长期的项目实践与产品迭代过程中,团队成员在 React 技术栈、数据可视化技术、前端工程化等细分领域上不断深耕探索,积累了丰富的经验与最佳实践,并分享在知乎专栏@DTUXhttps://zhuanlan.zhihu.com/c_109929958




第六期

Easy[V]数据可视化揭秘:Threejs倒影解析

文 |  围墙   袋鼠云高级前端开发工程师


倒影是在自然界中非常常见的一种现象,例如水面倒影、镜子。我们都知道,眼睛之所以能够看到某个物体,是因为物体本身能够发光或者物体能够反射其它的物体所发的光,这些光进入到我们的眼里就形成了该物体影像。倒影形成也是一种光学的现象,其原理是物体发射或者反射的光经过倒影平面的反射后进入到我们的眼里,我们所看到的在倒影平面形成的虚像就是该物体的倒影。
不管是做数据可视化还是游戏,我们经常需要在3D场景中来实现这种自然现象,给水面、镜面等物体增加倒影的效果,来提高视觉效果。那么在3D渲染中这种效果是怎么实现的呢?
WebGL的渲染引擎threejs给我们提供了一个很好的倒影实现的封装,通过对threejs提供的代码进行分析,希望能够和大家一起学习一下。
大概的思路是: 构建一个虚拟的相机对需要倒影的物体进行渲染,然后将渲染的结果当作纹理映射到倒影平面上,这样就可以实现倒影的效果了 。这里面我们需要解决两个问题,第一个是如何构建这个虚拟的相机,第二个是怎么将通过纹理相机渲染出来的结果正确的映射到倒影平面上。
使用threejs来实现倒影我们只需要引入threejs的倒影引擎(该文件位于threejs项目example/js/objects目录下),然后创建一个接受倒影的几何体,其他的交给threejs来完成就可以来,这里我们创建了一个圆形来接收阴影。
 
   
   
 
<script src="js/objects/Reflector.js"></script>

var geometry = new THREE.CircleBufferGeometry(40, 64);
var groundMirror = new THREE.Reflector(geometry, {
textureWidth: WIDTH * window.devicePixelRatio,
textureHeight: HEIGHT * window.devicePixelRatio
});


袋鼠云研发手记 | Easy[V]数据可视化揭秘:Threejs倒影解析 (效果图)

1

构建虚拟相机

得到倒影面和真实相机的位置:
 
   
   
 
reflectorWorldPosition.setFromMatrixPosition( scope.matrixWorld );
cameraWorldPosition.setFromMatrixPosition( camera.matrixWorld );
得到反射面的旋转矩阵:
 
   
   
 
rotationMatrix.extractRotation( scope.matrixWorld );
先定义一个默认的法向量,乘以上一步得到的反射面的旋转向量得到反射面现在的法向量:
 
   
   
 
normal.set( 0, 0, 1 );
normal.applyMatrix4( rotationMatrix );
计算相机位置:
 
   
   
 
// 计算相机位置到反射平面位置到向量
view.subVectors( reflectorWorldPosition, cameraWorldPosition );
// 当向量与反射面当法向量夹角说明相机在反射面的背面,则直接返回不进行倒影的渲染
if ( view.dot( normal ) > 0 ) return;
// 得到反射向量的反向量
view.reflect( normal ).negate();
// 投影面位置加上该向量得到虚拟相机的位置
view.add( reflectorWorldPosition );
与计算相机位置类似的步骤计算相机的视点位置:
 
   
   
 
rotationMatrix.extractRotation( camera.matrixWorld );
lookAtPosition.set( 0, 0, - 1 );
lookAtPosition.applyMatrix4( rotationMatrix );
lookAtPosition.add( cameraWorldPosition );

target.subVectors( reflectorWorldPosition, lookAtPosition );
target.reflect( normal ).negate();
target.add( reflectorWorldPosition );
根据计算的相机位置和相机视点位置构建虚拟相机:
 
   
   
 
virtualCamera.position.copy( view );
virtualCamera.up.set( 0, 1, 0 );
virtualCamera.up.applyMatrix4( rotationMatrix );
virtualCamera.up.reflect( normal );
virtualCamera.lookAt( target );
virtualCamera.far = camera.far;
virtualCamera.updateMatrixWorld();
virtualCamera.projectionMatrix.copy( camera.projectionMatrix );

2

将虚拟相机的渲染结果映射到投影面上

初始化一个默认矩阵,这是初始化的矩阵主要是为了把屏幕坐标和[-1, 1]映射到[0, 1]的纹理坐标:
 
   
   
 
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( virtualCamera.projectionMatrix );
textureMatrix.multiply( virtualCamera.matrixWorldInverse );
textureMatrix.multiply( scope.matrixWorld );
将该矩阵在着色器中使用,可以得到倒影面各坐标点对应的纹理坐标,这样就可以把渲染结果正确的映射到投影平面上:
 
   
   
 
vUv = textureMatrix * vec4( position, 1.0 );

3

调试完善

调整虚拟相机渲染时的投影矩阵,将相机的近裁剪面重置为投影面,避免对倒影面下方对物体进行投影,clipBias参数是对裁剪面进行了一个偏移,具体算法请参考:
Oblique View Frustum Near-Plane Clipping
terathon.com/lengyel/Le
 
   
   
 
reflectorPlane.setFromNormalAndCoplanarPoint( normal, reflectorWorldPosition );
reflectorPlane.applyMatrix4( virtualCamera.matrixWorldInverse );

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

var projectionMatrix = virtualCamera.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 ];

clipPlane.multiplyScalar( 2.0 / clipPlane.dot( q ) );

projectionMatrix.elements[ 2 ] = clipPlane.x;
projectionMatrix.elements[ 6 ] = clipPlane.y;
projectionMatrix.elements[ 10 ] = clipPlane.z + 1.0 - clipBias;
projectionMatrix.elements[ 14 ] = clipPlane.w;
最主要的几个步骤已经完成了,下面进行的就是通过构建对虚拟相机和新的投影矩阵的整个场景进行渲染,将渲染结果当作纹理映射到投影平面上就完成了。

4

可视化工具

讲完怎么用threejs渲染引擎制作倒影效果,摞完了一堆代码。接下来,小妹我要给大家推荐一款神器——Easy[V]( 交互式 数据可视化开发平台

即使是没有设计经验或技术背景,通过组件拖拽、图层、画布等可视化操作方式,也可快速创造出美观酷炫的数据大屏。


袋鼠云数据可视化团队在Easy[V]—3D地图组件中集成了倒影特效,用户仅拖、拉、拽等简单操作,不用一行行码代码、调参数,即可轻松实现倒影效果,以下为操作视频:



欢迎了解袋鼠云数栈


或直接点击“阅读原文”查看


感兴趣的童鞋

可以微信后台留言和我们联系哦~


400-002-1024

了解数据中台解决方案&数栈





以上是关于袋鼠云研发手记 | Easy[V]数据可视化揭秘:Threejs倒影解析的主要内容,如果未能解决你的问题,请参考以下文章

袋鼠云春季生长大会最新议程来啦!4月20日我们云上见

腾讯云TDSQL审计原理揭秘

GaussDB(for MySQL)如何快速创建索引?华为云数据库资深架构师为您揭秘

今日直播 | 揭秘数据可视化

数栈技术分享:到底什么是数据中台?终于有人说清楚了!

袋鼠云技术荟 | 数据安全:混合云环境数据库备份容灾实现