如何用threejs实现实时多边形折射
前言
在本教程中,您将学习如何使用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 ); };
好吧,现在该花一点点理论了。透明材料(如玻璃)可以弯曲,因此可见。那是因为光在玻璃中的传播要比空气中的传播慢,因此当光波以一定角度撞击玻璃物体时,这种速度变化会导致光波改变方向。波浪方向的这种变化描述了折射现象。
为了在代码中复制这一点,我们将需要知道我们的眼睛向量与世界空间中钻石表面(法线)向量之间的角度。让我们更新顶点着色器以计算这些向量。
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); }
真好!我们成功编写了折射着色器。但是我们的钻石几乎看不见……部分原因是我们只处理了玻璃的一种视觉特性。并非所有的光都会穿过要折射的材料,实际上,一部分光会被反射。让我们看看如何实现它!
步骤2:反射和菲涅耳方程
为了简单起见,在本教程中,我们将不计算适当的反射,而仅将白色用作反射光。现在,我们怎么知道什么时候该反思,什么时候该折射?理论上,这取决于材料的折射率,当入射矢量和表面法线之间的角度大于临界角时,光波将被反射。
在片段着色器中,我们将使用菲涅耳方程来计算反射光线与折射光线之间的比率。不幸的是,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); }
看起来已经好多了,但是还有一些不足之处……嗯,我们看不到透明对象的另一面。让我们解决这个问题!
步骤3:多边折射
到目前为止,我们已经了解了有关反射和折射的知识,我们可以理解,光在离开对象之前可以在对象内部来回反弹几次。
为了获得物理上正确的结果,我们将必须跟踪每条光线,但是不幸的是,这种计算量太大,无法实时渲染。因此,我将向您展示一个简单的近似值,至少可以直观地看到我们钻石的背面。
在一个片段着色器中,我们需要几何图形的正面和背面的世界法线。由于我们不能同时渲染两侧,因此需要首先将背面法线渲染为纹理。
让我们像在步骤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只是一个标量值,指示应应用背面法线的数量。
我们做到了!我们可以看到钻石的所有侧面,仅是因为我们对钻石的材质进行了折射和反射。
局限性
正如我已经解释的那样,用这种方法实时渲染物理上正确的透明材料是不可能的。当在彼此前面渲染多个玻璃对象时会发生另一个问题。由于我们仅对环境采样一次,因此无法看透一连串的对象。最后,我在这里演示的屏幕空间折射在画布的边缘附近效果不佳,因为光线可能会折射到其边界之外的值,并且在将背景场景渲染到渲染目标时我们没有捕获到该数据。
当然,有多种方法可以克服这些限制,但是对于您在WebGL中进行实时渲染,它们可能并不是全部很好的解决方案。
以上就是如何用threejs实现实时多边形折射的详细内容,更多关于JS库的资料请关注猪先飞其它相关文章!
相关文章
- 本篇文章主要分享了通过window.navigator来判断浏览器及其版本信息的实例代码。具有一定的参考价值,下面跟着小编一起来看下吧...2017-01-23
- 这篇文章主要介绍了js如何实现浏览器打印功能,文中示例代码非常详细,帮助大家更好的理解和学习,感兴趣的朋友可以了解下...2020-07-15
- 下面小编就为大家带来一篇利用JS实现点击按钮后图片自动切换的简单方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2016-10-25
- 作为前端,一直以来都知道HTTP劫持与XSS跨站脚本、CSRF跨站请求伪造。防御这些劫持最好的方法是从后端入手,前端能做的太少。而且由于源码的暴露,攻击者很容易绕过防御手段。但这不代表我们去了解这块的相关知识是没意义的,本文的许多方法,用在其他方面也是大有作用。...2021-05-24
- 那么今天我就用JavaScript代码来实现这个效果吧,那么首先介绍一下整个的思路,首先我们先将确定输入密码的位数,我的需求是5位,那么就用一个div标签包住5个input标签...2016-01-02
- 这篇文章主要给大家介绍了关于Nest.js参数校验和自定义返回数据格式的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-28
- 这篇文章主要为大家详细介绍了js实现上传图片及时预览的相关资料,具有一定的参考价值,感兴趣的朋友可以参考一下...2016-05-09
- 这篇文章主要为大家详细介绍了js+css实现回到顶部按钮back to top回到顶部按钮,感兴趣的小伙伴们可以参考一下...2016-03-03
- 这篇文章主要介绍了如何使用JavaScript实现“无缝滚动 自动播放”轮播图效果,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-08-20
- 这篇文章主要给大家介绍了一个关于JS正则匹配的踩坑记录,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-04-13
- 有时候我们需要屏蔽客户端的F12,以防菜鸟也可以随意修改我们的代码,也处于源码的保护等操作,这里就为大家分享一下常见的代码...2020-10-03
- 这篇文章主要介绍了js实现调用网络摄像头及常见错误处理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-07
- 这篇文章主要为大家详细介绍了JS实现随机生成验证码,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-09-06
- 这篇文章主要介绍了js组件SlotMachine实现图片切换效果制作抽奖系统的相关资料,需要的朋友可以参考下...2016-04-19
- 这篇文章主要为大家详细介绍了js实现列表按字母排序,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-08-11
- 这篇文章主要介绍了基于JavaScript实现文字超出部分隐藏 的相关资料,需要的朋友可以参考下...2016-03-01
- 这篇文章主要介绍了JS创建Tag标签的方法,结合具体实例形式分析了javascript动态操作页面HTML元素实现tag标签功能的步骤与相关操作技巧,需要的朋友可以参考下...2017-06-15
- Vue.js通过简洁的API提供高效的数据绑定和灵活的组件系统.这篇文章主要介绍了vue.js 表格分页ajax 异步加载数据的相关资料,需要的朋友可以参考下...2016-10-20
- 这次文章要给大家介绍的是node.JS md5加密中文与php结果不一致怎么办,不知道具体解决办法的下面跟小编一起来看看。 因项目需要,需要Node.js与PHP做接口调用,发现nod...2017-07-06
- 为了网站的安全性,很多朋友都把密码设的比较复杂,但是如何密码不能明显示,不知道输的是对是错,为了安全起见可以把密码显示的,那么基于js代码如何实现的呢?下面通过本文给大家介绍JavaScript实现表单密码的隐藏和显示,需要的朋友参考下...2016-03-03