如何用threejs实现实时多边形折射


Posted in Javascript onMay 07, 2021

前言

在本教程中,您将学习如何使用Three.js在三个步骤中使对象看起来像玻璃。

渲染3D对象时,无论使用某种3D软件还是使用WebGL进行实时显示,始终都必须为其分配材料以使其可见并具有所需的外观。

可以使用Three.js之类的库中的现成程序来模仿许多类型的材料,但是在本教程中,我将向您展示如何使用三个对象(三个步骤)使对象看起来像玻璃一样。

步骤1:设定和正面折射

在本演示中,我将使用菱形几何图形,但是您可以跟随一个简单的盒子或任何其他几何图形。

让我们建立我们的项目。我们需要一个渲染器,一个场景,一个透视相机和我们的几何图形。为了渲染我们的几何图形,我们需要为其分配材质。创建此材料将是本教程的主要重点。因此,继续创建具有基本顶点和片段着色器的新ShaderMaterial。

与您期望的相反,我们的材料将不是透明的,实际上,我们将对钻石后面的任何东西进行采样和变形。为此,我们需要将场景(没有菱形)渲染为纹理。我只是使用正交摄影机渲染全屏平面,但这也可能是充满其他对象的场景。在Three.js中从菱形分割背景几何图形的最简单方法是使用“图层”。

this.orthoCamera = new THREE.OrthographicCamera( width / - 2,width / 2, height / 2, height / - 2, 1, 1000 );
// assign the camera to layer 1 (layer 0 is default)
this.orthoCamera.layers.set(1);

const tex = await loadTexture('texture.jpg');
this.quad = new THREE.Mesh(new THREE.PlaneBufferGeometry(), new THREE.MeshBasicMaterial({map: tex}));

this.quad.scale.set(width, height, 1);
// also move the plane to layer 1
this.quad.layers.set(1);
this.scene.add(this.quad);

我们的渲染循环如下所示:

this.envFBO = new THREE.WebGLRenderTarget(width, height);

this.renderer.autoClear = false;

render() {
    requestAnimationFrame( this.render );

    this.renderer.clear();

    // render background to fbo
    this.renderer.setRenderTarget(this.envFbo);
    this.renderer.render( this.scene, this.orthoCamera );

    // render background to screen
    this.renderer.setRenderTarget(null);
    this.renderer.render( this.scene, this.orthoCamera );
    this.renderer.clearDepth();

    // render geometry to screen
    this.renderer.render( this.scene, this.camera );
};

好吧,现在该花一点点理论了。透明材料(如玻璃)可以弯曲,因此可见。那是因为光在玻璃中的传播要比空气中的传播慢,因此当光波以一定角度撞击玻璃物体时,这种速度变化会导致光波改变方向。波浪方向的这种变化描述了折射现象。

如何用threejs实现实时多边形折射

为了在代码中复制这一点,我们将需要知道我们的眼睛向量与世界空间中钻石表面(法线)向量之间的角度。让我们更新顶点着色器以计算这些向量。

varying vec3 eyeVector;
varying vec3 worldNormal;

void main() {
    vec4 worldPosition = modelMatrix * vec4( position, 1.0);
    eyeVector = normalize(worldPos.xyz - cameraPosition);
    worldNormal = normalize( modelViewMatrix * vec4(normal, 0.0)).xyz;
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

在片段着色器中,我们现在可以将eyeVector和worldNormal用作glsl内置折射函数的前两个参数。第三个参数是折射率的比率,即我们的快速介质(空气)的折射率(IOR)除以我们的慢速介质(玻璃)的IOR。在这种情况下,该值为1.0 / 1.5,但是您可以调整该值以获得所需的结果。例如,水的IOR为1.33,钻石的IOR为2.42。

uniform sampler2D envMap;
uniform vec2 resolution;

varying vec3 worldNormal;
varying vec3 viewDirection;

void main() {
    // get screen coordinates
    vec2 uv = gl_FragCoord.xy / resolution;

    vec3 normal = worldNormal;
    // calculate refraction and add to the screen coordinates
    vec3 refracted = refract(eyeVector, normal, 1.0/ior);
    uv += refracted.xy;
    
    // sample the background texture
    vec4 tex = texture2D(envMap, uv);

    vec4 output = tex;
    gl_FragColor = vec4(output.rgb, 1.0);
}

如何用threejs实现实时多边形折射

真好!我们成功编写了折射着色器。但是我们的钻石几乎看不见……部分原因是我们只处理了玻璃的一种视觉特性。并非所有的光都会穿过要折射的材料,实际上,一部分光会被反射。让我们看看如何实现它!

步骤2:反射和菲涅耳方程

为了简单起见,在本教程中,我们将不计算适当的反射,而仅将白色用作反射光。现在,我们怎么知道什么时候该反思,什么时候该折射?理论上,这取决于材料的折射率,当入射矢量和表面法线之间的角度大于临界角时,光波将被反射。

如何用threejs实现实时多边形折射

在片段着色器中,我们将使用菲涅耳方程来计算反射光线与折射光线之间的比率。不幸的是,glsl也没有内置此方程式,但是您可以从这里复制它:

float Fresnel(vec3 eyeVector, vec3 worldNormal) {
    return pow( 1.0 + dot( eyeVector, worldNormal), 3.0 );
}

现在,我们可以根据刚计算出的菲涅耳比,简单地将折射纹理颜色与白色反射颜色混合。

uniform sampler2D envMap;
uniform vec2 resolution;

varying vec3 worldNormal;
varying vec3 viewDirection;

float Fresnel(vec3 eyeVector, vec3 worldNormal) {
    return pow( 1.0 + dot( eyeVector, worldNormal), 3.0 );
}

void main() {
    // get screen coordinates
    vec2 uv = gl_FragCoord.xy / resolution;

    vec3 normal = worldNormal;
    // calculate refraction and add to the screen coordinates
    vec3 refracted = refract(eyeVector, normal, 1.0/ior);
    uv += refracted.xy;

    // sample the background texture
    vec4 tex = texture2D(envMap, uv);

    vec4 output = tex;

    // calculate the Fresnel ratio
    float f = Fresnel(eyeVector, normal);

    // mix the refraction color and reflection color
    output.rgb = mix(output.rgb, vec3(1.0), f);

    gl_FragColor = vec4(output.rgb, 1.0);
}

如何用threejs实现实时多边形折射

看起来已经好多了,但是还有一些不足之处……嗯,我们看不到透明对象的另一面。让我们解决这个问题!

步骤3:多边折射

到目前为止,我们已经了解了有关反射和折射的知识,我们可以理解,光在离开对象之前可以在对象内部来回反弹几次。

为了获得物理上正确的结果,我们将必须跟踪每条光线,但是不幸的是,这种计算量太大,无法实时渲染。因此,我将向您展示一个简单的近似值,至少可以直观地看到我们钻石的背面。

在一个片段着色器中,我们需要几何图形的正面和背面的世界法线。由于我们不能同时渲染两侧,因此需要首先将背面法线渲染为纹理。

如何用threejs实现实时多边形折射

让我们像在步骤1中一样制作一个新的ShaderMaterial,但是这次我们将世界法线渲染为gl_FragColor。

varying vec3 worldNormal;

void main() {
    gl_FragColor = vec4(worldNormal, 1.0);
}

接下来,我们将更新渲染循环以包括背面通道。

this.backfaceFbo = new THREE.WebGLRenderTarget(width, height);

...

render() {
    requestAnimationFrame( this.render );

    this.renderer.clear();

    // render background to fbo
    this.renderer.setRenderTarget(this.envFbo);
    this.renderer.render( this.scene, this.orthoCamera );

    // render diamond back faces to fbo
    this.mesh.material = this.backfaceMaterial;
    this.renderer.setRenderTarget(this.backfaceFbo);
    this.renderer.clearDepth();
    this.renderer.render( this.scene, this.camera );

    // render background to screen
    this.renderer.setRenderTarget(null);
    this.renderer.render( this.scene, this.orthoCamera );
    this.renderer.clearDepth();

    // render diamond with refraction material to screen
    this.mesh.material = this.refractionMaterial;
    this.renderer.render( this.scene, this.camera );
};

现在,我们在折射材料中采样背面法线纹理。

vec3 backfaceNormal = texture2D(backfaceMap, uv).rgb;

最后,我们结合了正面和背面法线。

float a = 0.33;
vec3 normal = worldNormal * (1.0 - a) - backfaceNormal * a;

在此等式中,a只是一个标量值,指示应应用背面法线的数量。

如何用threejs实现实时多边形折射

我们做到了!我们可以看到钻石的所有侧面,仅是因为我们对钻石的材质进行了折射和反射。

局限性

正如我已经解释的那样,用这种方法实时渲染物理上正确的透明材料是不可能的。当在彼此前面渲染多个玻璃对象时会发生另一个问题。由于我们仅对环境采样一次,因此无法看透一连串的对象。最后,我在这里演示的屏幕空间折射在画布的边缘附近效果不佳,因为光线可能会折射到其边界之外的值,并且在将背景场景渲染到渲染目标时我们没有捕获到该数据。

当然,有多种方法可以克服这些限制,但是对于您在WebGL中进行实时渲染,它们可能并不是全部很好的解决方案。

以上就是如何用threejs实现实时多边形折射的详细内容,更多关于JS库的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
脚本安需导入(装载)的三种模式的对比
Jun 24 Javascript
JavaScript prototype对象的属性说明
Mar 13 Javascript
jquery tab插件制作实现代码
Jun 22 Javascript
jquery判断元素的子元素是否存在的示例代码
Feb 04 Javascript
JS 获取鼠标左右键的键值方法
Oct 11 Javascript
JavaScript数组去重的两种方法推荐
Apr 05 Javascript
ajax图片上传,图片异步上传,更新实例
Dec 30 Javascript
angular多语言配置详解
May 16 Javascript
微信小程序云开发修改云数据库中的数据方法
May 18 Javascript
vue实现抖音时间转盘
Sep 08 Javascript
使用xampp将angular项目运行在web服务器的教程
Sep 16 Javascript
ES5新增数组的实现方法
May 12 Javascript
详解JS ES6编码规范
May 07 #Javascript
html5中sharedWorker实现多页面通信的示例代码
May 07 #Javascript
详解如何使用Node.js实现热重载页面
May 06 #Javascript
关于Vue Router的10条高级技巧总结
May 06 #Vue.js
在JavaScript中如何使用宏详解
May 06 #Javascript
如何用JS实现简单的数据监听
May 06 #Javascript
详解TS数字分隔符和更严格的类属性检查
May 06 #Javascript
You might like
PHP实现多文件上传的方法
2015/07/08 PHP
php的RSA加密解密算法原理与用法分析
2020/01/23 PHP
jquery实现居中弹出层代码
2010/08/25 Javascript
滚动图片效果 jquery实现回旋滚动效果
2013/01/08 Javascript
js时间日期和毫秒的相互转换
2013/02/22 Javascript
JavaScript数组迭代器实例分析
2015/06/09 Javascript
JavaScript 实现的 zip 压缩和解压缩工具包Zip.js使用详解
2015/12/14 Javascript
谈谈JavaScript中的几种借用方法
2016/08/09 Javascript
JS中用childNodes获取子元素换行会产生一个子元素
2016/12/08 Javascript
详解Vue 2.0封装axios笔记
2017/06/22 Javascript
原生JS实现多个小球碰撞反弹效果示例
2018/01/31 Javascript
在vue项目中使用sass的配置方法
2018/03/20 Javascript
微信小程序之侧边栏滑动实现过程解析(附完整源码)
2019/08/23 Javascript
[48:44]2014 DOTA2国际邀请赛中国区预选赛5.21 TongFu VS HGT
2014/05/22 DOTA
[02:28]PWL开团时刻DAY3——Ink Ice与DeMonsTer之间的勾心斗角
2020/11/03 DOTA
Python自动化测试工具Splinter简介和使用实例
2014/05/13 Python
Python中除法使用的注意事项
2014/08/21 Python
TensorFlow搭建神经网络最佳实践
2018/03/09 Python
python3实现域名查询和whois查询功能
2018/06/21 Python
使用Python的SymPy库解决数学运算问题的方法
2019/03/27 Python
Django用户认证系统 组与权限解析
2019/08/02 Python
一篇文章弄懂Python中的可迭代对象、迭代器和生成器
2019/08/12 Python
将Pytorch模型从CPU转换成GPU的实现方法
2019/08/19 Python
使用Rasterio读取栅格数据的实例讲解
2019/11/26 Python
tensorflow实现从.ckpt文件中读取任意变量
2020/05/26 Python
python中加背景音乐如何操作
2020/07/19 Python
Python调用C/C++的方法解析
2020/08/05 Python
印度在线杂货店:bigbasket
2018/08/23 全球购物
美国名牌香水折扣网站:Hottperfume
2021/02/10 全球购物
什么是Smart Navigation?
2016/07/03 面试题
信息管理专业学生自荐信格式
2013/09/22 职场文书
社区植树节活动总结
2015/02/06 职场文书
目标责任书格式范文
2015/05/11 职场文书
2015暑期社会实践个人总结
2015/07/13 职场文书
JavaScript 防篡改对象的用法示例
2021/04/24 Javascript
Mysql中有关Datetime和Timestamp的使用总结
2021/12/06 MySQL