如何用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 相关文章推荐
js表格分页实现代码
Sep 18 Javascript
js防止表单重复提交实现代码
Sep 05 Javascript
如何用JavaScript动态呼叫函数(两种方式)
May 03 Javascript
JS来动态的修改url实现对url的增删查改
Sep 05 Javascript
javascript实现省市区三级联动下拉框菜单
Nov 17 Javascript
jquery在ie7下选择器的问题导致append失效的解决方法
Jan 10 Javascript
Javascript 事件冒泡机制详细介绍
Oct 10 Javascript
JQuery 获取多个select标签option的text内容(实例)
Sep 07 jQuery
小程序兼容安卓和IOS数据处理问题及坑
Sep 18 Javascript
jquery.pagination.js分页使用教程
Oct 23 jQuery
利用不到200行代码写一款属于你自己的js类库
Jul 08 Javascript
vue实现分页的三种效果
Jun 23 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横向重复区域显示二法
2008/09/25 PHP
CodeIgniter CLI模式简介
2014/06/17 PHP
Yii框架登录流程分析
2014/12/03 PHP
PHP实现数组和对象的相互转换操作示例
2019/03/20 PHP
csdn 博客的css样式 v3
2009/02/24 Javascript
讨论javascript(一)工厂方式 js面象对象的定义方法
2009/12/15 Javascript
基于jQuery的输入框无值自动显示指定数据的实现代码
2011/01/24 Javascript
JS分页控件 可用于无刷新分页
2013/07/23 Javascript
方便实用的jQuery checkbox复选框全选功能简单实例
2013/10/09 Javascript
javascript计算星座属相(十二生肖属相)示例代码
2014/01/09 Javascript
jquery制作 随机弹跳的小球特效
2015/02/01 Javascript
AngularJS入门教程之多视图切换用法示例
2016/11/02 Javascript
React Native中的RefreshContorl下拉刷新使用
2017/10/09 Javascript
angularjs数组判断是否含有某个元素的实例
2018/02/27 Javascript
vue.js过滤器+ajax实现事件监听及后台php数据交互实例
2018/05/22 Javascript
Vue多系统切换实现方案
2018/06/05 Javascript
vue-content-loader内容加载器的使用方法
2018/08/05 Javascript
Vue form表单动态添加组件实战案例
2019/09/02 Javascript
JQuery事件委托(适用于给动态生成的脚本元素添加事件)
2020/02/01 jQuery
Openlayers显示瓦片网格信息的方法
2020/09/28 Javascript
H5 js点击按钮复制文本到粘贴板
2020/11/19 Javascript
Django中reverse反转并且传递参数的方法
2019/08/06 Python
python:批量统计xml中各类目标的数量案例
2020/03/10 Python
Django调用支付宝接口代码实例详解
2020/04/04 Python
解决python 执行sql语句时所传参数含有单引号的问题
2020/06/06 Python
Python容器类型公共方法总结
2020/08/19 Python
python switch 实现多分支选择功能
2020/12/21 Python
Notino芬兰:购买香水和化妆品
2019/04/15 全球购物
运动会广播稿100字
2014/01/11 职场文书
2014年五四青年节活动方案
2014/03/29 职场文书
安全横幅标语
2014/06/09 职场文书
2014最新离职证明范本
2014/09/12 职场文书
卖车协议书范本4篇
2014/10/01 职场文书
想要创业,那么你做好准备了吗?
2019/07/01 职场文书
基于Python实现对比Exce的工具
2022/04/07 Python
el-table-column 内容不自动换行的解决方法
2022/08/14 Vue.js