Building my Personal Three.js Portfolio
After playing with three.js for several years I can say my passion is still going strong! Early this month I have just released my latest personal portfolio site, which showcases my featured and past projects, especially those written with Three.js. Go take a peek or mess around! https://arkon.digital/
When I have finally completed the site, it was a moment of joy and satisfaction. This took me two months from design to development; it was definitely no easy work. Making this portfolio is quite the journey, and it deserves a place on my portfolio itself! Let me share with you my design process and the cool threejs stuff I’ve learnt along the way.
Design Process
My design process is fairly simple: Setting up a moodboard, do some explorations, and finally make a Hi-Fi design for my pages on Figma.
I perused a number of portfolio websites and took screenshots of those I liked. Here are some of the examples:
I really liked the realistic reflection effect from OHZI Interactive Sudio, just beautifully done. It looks like a rough floor with patches of gloss applied on it. The central cube is also intriguing, there is a light source within it that changes color over time, shining through the textured glass-like surface.
The creativity and craftsmenship in Rogier’s portfolio is off the charts. Every detail is designed perfectly, adding up to a fantabulous experience. The idea to show an image with that matrix of cubes is beautifully executed. I wonder how that mysterious fog with light shining through is achieved.
I also got inspirations from Pinterest boards for the shader effects to be used in the scene.
Initially, I had an idea about building a distinct 3D environment/world for each section of my portfolio, and these sections are connected through portals. So I went to Pinterest to find some amazing portal art.
This was a pretty cool idea but I threw it away quickly as I realised the amount of time and work involved. I decided to do something more achievable: a simple geometry with a custom shader. I came across this pin and I’m instantly hooked:
This looks like a really cool effect! This is when I started doing some shader explorations trying to achieve the same effect.
Shader Explorations
After I’ve made a rough wireframe of the home page, I started working on the shader.
Shadertoy is usually my starting point when I try to find inspirations on making custom shaders. Sometimes if you’re lucky you can find the exact shader that can be plugged directly into your scene. Not so lucky this time, but I found one with a similar effect and I thought I could build from there.
It’s a pretty cool turbulence effect done with cheap transformations. The pattern changes and revolves back to its starting pattern indefinitely.
And from there I started experimenting with the glsl code, changing various parameters, modifying the transformations, and got these pretty cool patterns:
But however hard I tried, the pattern just didn’t feel close enough with the target result. I had to give up before spending too much time(I’m just a shader amateur🥲). This is definitely a challenge I’d love to come back to. Feel free to take this on and post on shadertoy if you know how to do it! I’ll be super psyched to check it out and praise you with my utmost respect!
Finally, I settled with another equally fascinating shader that has a magical, candy-like glossy gradient that I really liked.
After applying it on the sphere and making some adjustments to the glsl, here is the first version of my threejs scene:
Customizing the ThreeJS Reflector
At the beginning, I used the Reflector
class from threejs addon out-of-the-box, providing custom vertex and fragment shaders to it, producing the result in the above screenshot. As you may have guessed, I’ve used perlin noise to produce the pattern on the mirror. I passed a roughness map prerendered using perlin noise to the shaders via a sampler2D
uniform. In simplified terms, the above effect is just a glsl mix
function between the smooth mirrored colors and the blurred colors on rougher areas.
Although the effect was already looking nice, it wasn’t good/realistic enough. Next, I added fresnel to the sphere, darken the sphere colors in reverse y-axis direction to simulate light from above and revised the perlin pattern.
By that point, I was pretty satisfied with how the sphere looks, but the mirror pattern was still disappointing somehow. I figured if I use a realistic texture map like rough ground or walls with proper normals and lighting calculations, the mirror pattern should look more realistic.
Looking at the source code of Reflector
, it does not support the use of a custom material though. That means I need to extend the Reflector
class in order to use MeshStandardMaterial
instead of ShaderMaterial
. The merits of using a PBR material are that lighting calculations on textures are built-in; I don’t have to reinvent the wheel. However, now I’d have to extend MeshStandardMaterial
via onBeforeCompile
to include the code for displaying the mirrored scene. Not a big deal!
For the realistic texture, I used this version of rock wall from polyhaven and adjusted the texture offsets in the 3js scene to a position I liked best. I downloaded both the roughness map(in fact I downloaded the displacement map since it has more contrast, and modified it to have more contrast even) and normal map and plugged those into my MeshStandardMaterial
. With some more adjustments here and there, I finally arrived at the scene you can now see on my portfolio site.
I was pretty amazed at how the end results look. At darker areas of the texture, roughness value is pretty low such that the reflector reflects mostly the sphere colors, yet keeping a bit of the texture colors, as if through a still layer of water. With the rock-like texture, it really gives out the illusion that the rocky feature is half-submerged in water. The brighter reflections on the rock edges are really lifting up the realism level too.
What I’ve covered so far is just the home page. There are more to the project if I have to tell it all, e.g. page transitions in sync with the sphere transitioning into a sphere of blue dots. But it’s time for me to go, so until next time! Happy Coding~