最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • React 优化技巧在 Web 版光线追踪里的应用(中)

    正文概述 掘金(工业聚)   2020-12-24   342

    在上篇中,我们介绍了 JS 里的操作符重载方案,它就像 React 里使用 JSX 代替 React.createElement 那样。可以优化我们的代码,使之更加简洁和直观。

    工业聚:React 优化技巧在 Web 版光线追踪里的应用(上)

    然而,它仅仅是语法糖,并不能解决性能问题。光线追踪算法的巨大运算量,需要别的优化技巧。今天我们就来讲一下这些技巧。

    解决方案:Time Slicing

    时间分片,或叫异步渲染,或叫并发模式,不管叫什么名字,大概描述的都是将一个长时间执行的任务,分成一小块一小块,每执行一段时间,就停下来让主线程里积压的其它任务(比如渲染)得到释放。

    React 和 Vue 都曾展示过这种效果。在我们的场景里,也能用这个思路,并且并不需要先实现一个 Concurrency 框架。用 async/await 和 generator function 就可以简单地满足需求。

    我们先来看一下,如何把光线追踪的计算任务进行分块。

    光线追踪算法的思路是,逐个像素地计算这个像素位置的颜色。因此是两个 for 循环,i = 0width 加 j = 0height,每组 i, j 对应该像素的位置。

    对于每个像素,我们从眼睛(观察点)里发射 100 条光线,模拟光路从物体到眼睛的反向路径,去采样光源。每个像素的计算方式如下图所示:

    React 优化技巧在 Web 版光线追踪里的应用(中)

    采样用的光线,每撞击到一个物体,就会根据物体本身的材质特征,进行反射、折射、散射等。相当于以撞击点为光源,向其它地方再次发射光线。光线在碰撞中,逐渐丧失能量(完全丧失能量,就呈现黑色;比如阴影,阴影通常在物体的夹角处,光线在狭隘的地方高频碰撞,每撞击一次就被部分吸收,部分反射,部分折射,多次撞击,就被充分吸收了)。

    考虑到本系列内容的主旨是,以光线追踪为例,讲渲染优化的策略。因此,更详尽的光线追踪算法的描述,超出了范畴;感兴趣的同学,可以搜索《Ray Tracing in a Weekend》等阅读材料。

    我们实现了光线追踪算法后,想把它放到浏览器里运行,需要用到本系列文章里介绍的优化措施。

    当下,假设我们已经实现了光线追踪算法。其 JS 代码表达起来,如下图所示:

    React 优化技巧在 Web 版光线追踪里的应用(中)

    前两层 for 循环,用以确定像素点的位置;第三层 for 循环是发射采样光线的次数,加一点 Math.random() 随机扰动,让采样点在一个小方格的不同点发射出去,能探测到更广一点的环境。

    color(ray, world) 函数里面递归地去计算 ray 光线跟 world 里的各个物体的碰撞关系,得出一个颜色值。

    最后我们把采样光线获取到的颜色值,累加起来,再取平均值。就是这个像素点的实际颜色。由于它在计算时都被归一化到 01 的值区间,因此最后要放大到 0255 的 RGB 区间中。在此之前做了一次开根号,是对伽马校正的一个简单模拟,在这里并不重要,按下不表。

    至此,我们知道了我们的光线追踪算法的任务处理方式。可以如何切块呢?

    第一步,颜色值是累加后取平均值得出的。我们可以不在一次遍历里发射光线那么多次,改为只发射一次。就可以得到一个粗糙的图像。把这个图像的像素值储存起来。然后发起再次遍历,每个像素点再发射一次光线,得到另一个粗糙图像,两个图像一迭加取平均,就得到了细节更丰富的图像。

    React 优化技巧在 Web 版光线追踪里的应用(中)

    我们消掉了第三层 for 循环。转而以反复执行函数的方式,拿到多个 content,合并起来,我们最终能得到一样细致的光线追踪图像。

    我们确实做到了。但令人惊讶的是,把一张细致的图像,分为多个粗糙的图像,并不能彻底解决问题。只是把十几分钟,变成十几秒钟。对于网页来说,卡十几秒钟,还是不可接受。

    我们还能怎么分块任务?每计算 n 个像素就停下来一次吗?确实可以。不过我们的代码要怎么写,两层 for 循环可以很好地进行 xy 轴宽高定位。我们不想用很杂乱的代码,来强行满足分片的需求。

    有个很好的方式是,把我们的函数改成 generator function,它能通过 yiled 关键字多次停下来。

    React 优化技巧在 Web 版光线追踪里的应用(中)

    现在,我们不在函数内部去收集 content 了,我们把它们 4 个为一组地 yield 出去,由外面去收集。

    React 优化技巧在 Web 版光线追踪里的应用(中)

    外部通过 data 数组去累计颜色值,用 innerCount 去累积渲染次数,方便取平均值;用 duration 去追踪执行时间,每隔 100 毫秒,就 await delay() 一次(内部用 setTimeout(f, 0)),腾出机会给 UI 主线程。

    如此,仅仅靠 generator function 和 async/await,我们就实现了简易的 Time Slicing。尽管出图的时间还是 10 几秒(在性能不佳的电脑或手机里),但起码界面点得动了,起码能不断地渲染 dom 去读秒和计时了。

    React 优化技巧在 Web 版光线追踪里的应用(中)

    进阶方案:Streaming Rendering

    至此,我们把高清图片分割成多个模糊图片的叠加。又把一个模糊图片的生成,按照 100ms 分成多段,让 UI 主线程里的其它渲染任务(比如DOM),有机会执行。界面变得不再卡顿。

    我们的光线追踪在浏览器里可用了,但这不是我们能做到的极限。我们还可以更进一步,让图像更快地展示。

    回顾一下,在用 React 做 SSR 时,如果渲染的 HTML 太复杂,要等待全部内容完成,再发送给浏览器。用户就会一直看到白屏。当时我们是怎么优化的呢?

    我们会采用 renderToNodeStreaming,渲染成 Node.js Stream,让浏览器一份一份的接收 HTML 字符串,实现渐进式地渲染。在我们的光线追踪场景里,这个优化策略一样可行。

    因为,即便没有收集到一张图片的所有像素,它依然是 renderable(可渲染的),让其它部分保持透明即可(我们甚至无需处理,cxt.createImageData 生成的数据结构,默认就是透明)。

    React 优化技巧在 Web 版光线追踪里的应用(中)

    如上所示,相比每隔 100ms 就无脑的 delay 一下,让 UI 线程里可能积压的任务得到释放;我们这次,直接发起一次 requestAnimationFrame 的渲染,把渲染 canvas 图片也加入到 UI 主线程里。等全部像素收集完毕,我们会额外进行一次整体渲染。

    如此,就实现了 Streaming Rendering,我们不用再等待 10 秒钟,才第一次看见完整图像。我们可以在第一秒钟就看到部分图像。如下所示,1.6 秒时已经能看到部分图像。

    React 优化技巧在 Web 版光线追踪里的应用(中)

    我们成功地解决了首次渲染的问题,但我们还能做得更好。除了首次渲染,在更新阶段我们也可以添加一些优化措施。

    比如,很多图像大部分内容都是简单的背景,只有少部分物体,我们一视同仁地去计算,是一种浪费。我们应当把宝贵的计算资源,放到更关键的物体,特别是视觉中心里的物体上。让它们优先得到清晰化。

    下一回,我将介绍如何采用 React 即将发布的 Schedule 优先级策略的思路,完善我们的光线追踪。


    起源地下载网 » React 优化技巧在 Web 版光线追踪里的应用(中)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元