Improving functionality and performance with Three.js custom shaders

26 July 2022

Software engineer Kyle Watson has been making improvements on SamsonVT's approach to CORE 3D rendering, and this is what he found using Three.js custom shaders.

Discovery 

Over the past couple of weeks I have been learning about GLSL shaders in Three.js and how we can use them at SamsonVT to improve functionality and performance. After watching this Livestream on using framebuffers to implement mouse picking, I was inspired to implement a similar approach to our platform. We had previously been using Raycasts, but this did not scale well with the thousands of independent objects that we need to handle. 

Helpfully, Three.js had an example, using this technique on their website, so this was easily adapted to our use-case, improving the performance of our object selection functionality. 

Good to know: OpenGL Shading Language (GLSL) is a high-level shading language with a syntax based on the C programming language.

 

Expansion 

Once I was feeling more confident with GLSL I decided to tackle the problem in Three.js of per-object opacity. The only way to change opacity, is to change it on the object's material (which is often shared between thousands of objects on our platform; to save memory). I first tried to implement opacity as a geometry attribute which the shader could read; but the issue here was that changing thousands of attributes every frame was not CPU efficient and didn’t utilise the parallel computation abilities of the GPU effectively. The second iteration I thought of, was to use pre-determined values for each object, that the GPU could use to calculate the current opacity.

Using the formula below, the GPU can calculate the opacity for smoothly fading objects by only updating the time value every frame. 

Code 2

 

Setting these values as geometry attributes still caused small freezes, as thousands of values needed to be sent to the GPU when the fading was started. Instead, we bundle these values as RGB components in a texture, where each pixel is a different object, then select the pixel in the shader using the index of the object as the UV coordinate.  

Code 1

Results 

Through these shaders, we offloaded a lot of mathematical work that needed to be calculated every frame, onto the GPU - which is specifically designed to handle tasks of that kind. We also reduced memory use by avoiding material duplication. On top of this, learning how to efficiently pass thousands of values to the GPU for calculations can open up the potential to allow the application to perform much more complex calculations every frame (think physics simulations).  

Interested in seeing the application of shaders on a CORE model? Get in touch with us to book a demo or a 15-minute discovery call.

Partful Partful

Speed up your workflow today