In the last , We introduced JS Operator overloading scheme in , It's like React Use in JSX Instead of React.createElement like that . We can optimize our code , Make it more concise and intuitive .
However , It's just grammar sugar , It doesn't solve the performance problem . The huge amount of computation of ray tracing algorithm , Other optimization techniques are needed . Today, let's talk about these techniques .
Solution ：Time Slicing
Time is divided , Or asynchronous rendering , Or concurrent mode , Whatever the name is , It's about a long-term task , Divide it into small pieces , Every execution period of time , Stop to let the main thread backlog of other tasks （ For example, rendering ） Get released .
React and Vue It's been shown before . In our scene , You can also use this idea , And there's no need to implement a Concurrency frame . use async/await and generator function You can simply meet the needs .
So let's see , How to divide the calculation task of ray tracing into blocks .
The idea of ray tracing algorithm is , Calculate the color of this pixel position pixel by pixel . So there are two for loop ,i = 0
width Add j = 0height, Each group i, j For the location of the pixels .
For each pixel , We start with the eyes （ Observation points ） Launch in 100 A light , Simulate the reverse path of the light path from the object to the eye , To sample the light source . The calculation of each pixel is shown in the figure below ：
Light for sampling , Every time you hit an object , According to the material characteristics of the object itself , On reflection 、 refraction 、 Scattering, etc . It's equivalent to using the impact point as the light source , Send light back to other places . The light is in collision , Gradual loss of energy （ Total loss of energy , It's black ; Like shadows , The shadow is usually at the angle of the object , High frequency collisions of light in narrow places , It's partially absorbed with each impact , Partial reflection , Partial refraction , Multiple impacts , It's fully absorbed ）.
Considering that the theme of this series is , Take ray tracing, for example , Let's talk about the strategy of rendering optimization . therefore , A more detailed description of the ray tracing algorithm , Beyond the scope of ; Interested students , You can search 《Ray Tracing in a Weekend》 Wait for reading materials .
After we implement the ray tracing algorithm , Want to put it in the browser to run , You need to use the optimization measures described in this series of articles .
The present , Suppose we've implemented the ray tracing algorithm . Its JS The code expresses , As shown in the figure below ：
The first two layers for loop , To determine the position of pixels ; The third level for The loop is the number of times the sample light is emitted , Add a little Math.random() Random disturbance , Let the sampling points be emitted at different points in a small square , Can detect a wider environment .
color(ray, world) Function to calculate recursively ray Light and world The collision relationship between the objects in , Value a color .
Finally, we get the color value of the sampled light , Add up , Take the average again . It's the actual color of the pixel . Because it is normalized to 0
1 The value range of , So finally zoom in to 0255 Of RGB In interval . Before that, I did a radical , It's a simple simulation of gamma correction , It doesn't matter here , Press the table .
thus , We know how our ray tracing algorithm works . How can I cut it ？
First step , The color value is the average value after accumulation . We can't emit light so many times in one traversal , It's just one shot . You can get a rough image . Store the pixel values of this image . Then initiate another traversal , Each pixel emits light again , Get another rough image , Two images are superimposed and averaged , More detailed images .
We've eliminated the third layer for loop . Instead, it's a way of executing functions over and over again , Get more than one content, combined , We can get the same detailed image as ray tracing .
We did . But it's amazing , Take a detailed image , Divided into a number of rough images , It doesn't solve the problem completely . Just a dozen minutes , It's a dozen seconds . For web pages , More than ten seconds , Still unacceptable .
How else can we divide the task ？ Every calculation n Stop once per pixel ？ It can . But how to write our code , Two layers of for The loop can go well xy Axis width height positioning . We don't want to use messy code , To forcibly meet the needs of fragmentation .
There's a great way to , Change our function to generator function, It can pass. yiled The keyword stops many times .
Now? , We don't collect it inside the function content 了 , We put them 4 In groups yield get out , Collect from the outside .
Outside through data Array to accumulate color values , use innerCount To accumulate rendering times , It's convenient to average ; use duration To track the execution time , every other 100 millisecond , Just await delay() once （ For internal use setTimeout(f, 0)）, Make room for UI The main thread .
such , Just by generator function and async/await, We've made it easy Time Slicing. Although the drawing time is still 10 Seconds （ In a computer or mobile phone with poor performance ）, But at least the interface has to move , At least it can be constantly rendered dom To read the seconds and time .
Advanced program ：Streaming Rendering
thus , We split high-definition images into multiple blurred images . And the generation of a blurred image , according to 100ms It's broken down into sections , Give Way UI Other rendering tasks in the main thread （ such as DOM）, Have the opportunity to execute . The interface is no longer stuck .
Our ray tracing is available in browsers , But that's not the limit we can do . We can go further , Make images show faster .
Take a look back. , In use React do SSR when , If it's rendered HTML Too complicated , Waiting for the whole thing to be done , And send it to the browser . Users will always see a white screen . How did we optimize it at that time ？
We will adopt renderToNodeStreaming, Render into Node.js Stream, Let the browser receive one by one HTML character string , Achieve progressive rendering . In our ray tracing scene , This optimization strategy is also feasible .
because , Even if not all the pixels of an image are collected , It's still renderable（ Renderable ）, Keep the rest transparent （ We don't even have to deal with ,cxt.createImageData Generated data structure , The default is transparency ）.
As shown above , Compared to 100ms There's no brain delay once , Give Way UI The possible backlog of tasks in the thread is released ; This time we , Launch it directly once requestAnimationFrame Rendering of , To render canvas Pictures are also added to UI In the main route . When all the pixels are collected , We'll do an extra overall rendering .
such , And that's what happened Streaming Rendering, We don't have to wait any longer 10 Second , The first time I saw the whole picture . We can see part of the image in the first second . As shown below ,1.6 You can see part of the image in seconds .
We successfully solved the problem of first rendering , But we can do better . Except for the first rendering , In the update phase, we can also add some optimization measures .
such as , Many images are mostly simple backgrounds , Only a few objects , We do the calculation equally , It's a waste . We should put our valuable computing resources , Put something more critical , Especially on objects in the center of vision . Make them clear first .
Next time , I'll show you how to use React Upcoming Schedule The idea of priority strategy , Perfect our ray tracing .