three.js模版缓冲能干啥?“任意门”了解下!

您所在的位置:网站首页 谁有任意门 three.js模版缓冲能干啥?“任意门”了解下!

three.js模版缓冲能干啥?“任意门”了解下!

2023-03-23 19:29| 来源: 网络整理| 查看: 265

最终效果

在这个例子中,森林中有一个石门从背面看是一个正常的石门,但正面却能通向一片海岛。

https://www.zhihu.com/video/1620605349916033024灵感来源

此前在网上看到过一个错误的示范:

https://www.zhihu.com/video/1620609169471565824原理剖析

思路就是创建两台相机,一台拍摄门内世界(camera0),一台拍摄现实世界(camera1),然后把拍摄到的门内世界作为纹理贴图,贴在门上。

renderer.setRenderTarget(renderTarget); renderer.render(secondaryScene, secondaryCamera); renderer.setRenderTarget(null); // render the scene to the canvas renderer.render(primaryScene, primaryCamera);

为了使camera0拍摄的纹理能正好贴合在门上,camera0的相机前平面会选者和门同一个尺寸。但是稍微动一动就会发现门内世界的透视实际是错的。这是因为camera1和camera0的相机参数,不一致,使得门中世界和门外世界使用的P矩阵不一致。

其实用上面的思路也是能做到透视一致的,只要把门中世界的物体和门外世界的物体放在不同的层,用同一个相机拍摄即可:

// 门中物体放在 layer0,门外物体放在 layer1 // pass1 相机只渲染门,把它作为遮罩模版 // pass2 相机拍门中门中世界,使用pass1的纹理作为遮罩 // pass3 正常渲染门外世界,最终结果叠加pass2的纹理

three.js官网有一个例子就是使用这样的遮罩原理实现的

const renderTarget = new THREE.WebGLRenderTarget( window.innerWidth, window.innerHeight, parameters ); composer = new EffectComposer( renderer, renderTarget ); composer.addPass( clearPass ); composer.addPass( maskPass1 ); composer.addPass( texturePass1 ); composer.addPass( clearMaskPass ); composer.addPass( maskPass2 ); composer.addPass( texturePass2 ); composer.addPass( clearMaskPass ); composer.addPass( outputPass );

但是如果我们有n个异次元门,难道每帧渲染就多加2n个pass?这显然很消耗性能,能不能只使用一个pass解决问题?

模版测试原理剖析

上面的问题答案是肯定的,我们可以使用模版测试实现这一效果。关于模版测试,读者可以自行翻阅OpenGL。简单说来模版测试可以让程序以一定的规则筛选参与测试的片源。

比如图中的门,我们就可以在它渲染时写入模版,然后在火山渲染时进行模版测试。

如上图所示,其实火山一直都在场景之中,只是绘制石门时,生成了图中红色的模版,在火山渲染时,会进行模版测试那些不通过的值就直接被丢弃了。

代码实现

在webgl中通过

gl.enable(gl.STENCIL_TEST);

来开启模版测试。还可通过gl.stencilMask设置位掩码来开放/禁止写入模板缓冲:

gl.stencilMask(0xFF); // 每一位写入模板缓冲时都保持原样 gl.stencilMask(0x00); // 每一位在写入模板缓冲时都会变成0(禁用写入)

在three.js中通过material的属性来开启模版写入与模版测试

// 门的material new MeshPhongMaterial({ color: new Color('red'), stencilWrite: true, // 开启模版写入 stencilRef: 1, //模版基准值设为1 stencilPass: ReplaceStencilOp, // 写入模版缓存 }) // 火山的material new MeshPhongMaterial({ stencilFunc: EqualStencilFunc, //当模版基准与模版缓冲上的值一致时才通过 stencilRef: 1, //模版基准值设为1 stencilWriteMask: 0x00, // 只做模版测试,不写入模版 stencilWrite: true, // 开启模版测试 })

这样实现之后火山就消失了,但也没有在门内出现:

原因是火山虽然通过了模版测试,但由于火山本来就比门距离相机更远,所以没能通过深度测试,因此需要在渲染门中世界之前清除深度缓冲:

door.onAfterRender = function () { renderer.clearDepth() }

但这样还有出现另一个问题,就是深度缓冲被清空了,如果有门外世界的物体位于门前面,如何实现遮挡?这里我们需要调整渲染顺序,在three.js中每一个mesh都可以设置renderOrder,渲染队列按renderOrder按从小到大顺序进行。

因此,我们需要先渲染门外世界,再渲染门,清空深度缓冲,再渲染门中世界的物体。

此时也不是stencilPass: ReplaceStencilOp而是stencilZPass: ReplaceStencilOp。需要同时通过深度测试才写入模版。

https://www.zhihu.com/video/1620729114167611392再多想一想怎么实现多个门?

我们说过,在之前的方案中,多加一个门需要多两个pass,那stencil这套方案,需要吗?答案是否定的!

对于不同的门我们只需要使用不同的基准值即可:

// 门1的material new MeshPhongMaterial({ color: new Color('red'), stencilWrite: true, // 开启模版写入 stencilRef: 1, //模版基准值设为1 stencilPass: ReplaceStencilOp, // 写入模版缓存 }) // 世界1的material new MeshPhongMaterial({ stencilFunc: EqualStencilFunc, //当模版基准与模版缓冲上的值一致时才通过 stencilRef: 1, //模版基准值设为1 stencilWriteMask: 0x00, // 只做模版测试,不写入模版 stencilWrite: true, // 开启模版测试 }) // 门2的material new MeshPhongMaterial({ color: new Color('red'), stencilWrite: true, // 开启模版写入 stencilRef: 2, //模版基准值设为2 stencilPass: ReplaceStencilOp, // 写入模版缓存 }) // 世界2的material new MeshPhongMaterial({ stencilFunc: EqualStencilFunc, //当模版基准与模版缓冲上的值一致时才通过 stencilRef: 2, //模版基准值设为2 stencilWriteMask: 0x00, // 只做模版测试,不写入模版 stencilWrite: true, // 开启模版测试 })

在最后一扇门渲染后,清空深度缓冲即可

性能如何优化?

我们知道里世界可能很大,但门通常不大,管中窥豹。如上图,其实这时很多里世界的物体根本看不到,就不用去做光栅化,因此我们可以在每帧渲染之前,做一次类似视锥体剔除的剔除操作。从相机位置,向门的4个顶点连线,加上门所在的平面,与camera的远平面,一共有六个面,当门内世界的某个物体的球包围盒,完全不在这六个面围成的区域内时,我们就不再渲染该物体。这样它后续的shader操作,光栅化,各种测试都不会进行,从而提高性能!

本篇没有讨论的问题:如果需要实现透明物体?是怎样的顺序?

我们知道渲染透明物体是需要知道深度信息的,每个里世界是有深度信息的,但表世界的深度信息,在最后一扇门被渲染之后就被清空了,渲染外世界的透明物体需要深度,但渲染里世界的物体需要清空深度。那外世界的透明物体是不是应该在里世界的物体前被渲染?但是此时如果有一个透明物体刚好挡住里世界的门,但门内物体还没有被渲染完成,该如何进行blend?

如何实现多光源和多阴影?

如果有多个里世界,里世界之间的光源,里世界和表世界的光源能否相互独立?表世界的shadow会影响到里世界?

以上两个问题可以在留言区讨论!



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3