Skip to content

adityaprasad-sudo/Spectra

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

47 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Three JS Car Showcase

Just built this out of curiosity to give my three js skills a push and to test them. Also test my blender skills ( Not very good 🥹)

This project features three car models with the carparts such as doors, bonnet, trunk, etc are animated purely in javascript (Three js).

I tried my best to optimize the webpage as much as possible such as by avoiding realtime shadows since for the shadows i have used baked shadows BUT even if the Device is not capable enough i have added an option to turn the graphic settings down ; )

What I used

Well i did use a lot of stuff but here are the common ones

  • Blender (Very important without this i couldnt have made the webpage)
  • Three JS (Most important Took a lot of time to implement the car model and the enviorment with the lighting )
  • Documentation of THREE JS ( Three js bible [almost made me cry😭] )
  • 3D models ( Any model is fine since i will explain how to make the models work )
  • HTML/CSS (do i need to explain why this is needed)
  • Basic Blender SKills
  • Maths
  • Coffee

Getting Started

Welcome to hell sorry for my messy code :(

Dependencies

  • THREE JS
  • Blender or any other 3D software you know

Blender Part

  1. lets first load the 3D model in blender and try to set pivots on which each door will rotate follow the steps below :-

    load the model Load model Choose the file format depending on your model ( eg :- if your file format is .gltf/.glb then choose .gltf 2.0 )

  2. Part selecting

    now there could be two case :-

    case-I: The whole car body is welded ie you cant select a part like door distinctively, this is the way to seperate them:- case-1 expample this is an example


    steps to seperate them

    editmode seperating result

    If The door is already seprated you can directly jump to joining all the door parts or whatever you are trying to animate eg doors trunk etc.

    VERY IMPORTANT:- After the door parts have been seperated select all the parts asscoiate with the door and press CTRL+J to jion them ie combine them to form a single element below is a more clearer image of what i mean

    doorselect

    After That hit TAB to enter edit mode again and fix the pivot at a fake hinge point or for more realism fix it at a hinge midpoint below is a more detailed image

    fakepivot fakepivot fakepivot

    The Above steps is to create a fake pivot point it doesnt look that bad

    to create a more relaistic door pivot follow the steps below Very IMPORTANT First find your Hinge mesh

    hingemesh

    after finding the hinge mesh go into edit mode and select all the face of it from above

    hingemesh

    then press F to form a single circular mesh and select it

    hingemesh

    if you cant select the face first go into the face mode from here

    hingemesh

    after selecting Press Shift+S to open up the quick menu and then press Cursor to Active

    hingemesh

    After that you would be able to see the cursor in the middle of the hinge

    hingemesh

  3. step to map the pivot of the door to the cursor

    After placing the cursor on your desired position you would have to click on the doot but make you are in the select mode(should not be in cursor mode) otherwise the cursor will shift whereever you click on.

    After clicking the door Right Click it and click set origin to and then origin to 3d cursor

    hingemesh

  4. Final Step (Exporting)

    while Exporting make sure you export it as glb format so that the textures are not messed up as glb file packages all the textures and stuff in one go however there is a slight problem i will disscuss it further

    How to export?

    hingemesh hingemesh

And thats it now if you rotate the door around z axis it should rotate about the hinge or wherever you previously placed your 3D cursor at. You can repeate the same steps to pivot the remaining doors, bonnet, trunk etc. Since animation part is handeled in THREE JS

The Dome setup in blender can be found here - https://drive.google.com/file/d/1ntffneU6PKUBpr8NixmF1iX9B3QB_HEN/view?usp=sharing


The Code

Here I will explain the things i used to optimize and the parts that took me long to figure out.

  1. lighting and Enviourment:- To make the lighting as realistic as possibly i used a dome ground projection setup basically i wrapped a 4k resolution hdri in a dome and by adjusting the radius and height of the dome i was able to produce a good quality floor and enviourment.
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
const gen = new PMREMGenerator(renderer);
gen.compileEquirectangularShader();
const oaderchig= new RGBELoader(manager);
let skybox; // 
oaderchig.load('./hdris/hrdi.hdr', (texture) => {
    const envmap = gen.fromEquirectangular(texture).texture;
    scene.environment = envmap;
    scene.environmentIntensity = 1.2;
    skybox = new GroundProjectedSkybox(texture);
    skybox.scale.setScalar(100)
    skybox.radius = 70
    skybox.height = 10
    scene.add(skybox);
    texture.dispose();
})

The above is the whole dome with hdri setup, i have used GroundProjectSkybox constructor to build a new dome if you zoom out it looks like a sphere but if we adjust the height it adds a floor inside the cube adjusting the height also adjusts the radius of the floor ( inside the sphere )

NOTE:- the skybox.radius only affects the radius of the floor while skybox.scale.setScalar affects the whole sphere ie you can also shape it like a oval elliptical structure since the syntax is scale.setScalar(X,Y,Z) but if you specify scale.setScalar(100) it gives a perfect sphere with radius 100.

File Formats

  1. if you have an .exr file then use EXRLoader() and import import { EXRLoader } from 'three/addons/loaders/EXRLoader.js';

  2. If you have an .hdri file then use RGBELoader() and import import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';

Rendering

As of now I am using webgl instead of webgpu since webgpu is a newer tech and requires updated browers and webgpu might not work correctly on all devices especially the older ones.

To initialize WebGL use const renderer = new THREE.WebGLRenderer({ antialias: true }); we are using antialias:true to smooth out the edges of the 3D Model

To initialize WebGPU use const renderer = new THREE.WebGPURenderer({ antialias: true }); and import import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';

Now, after creating a raw scene using the renderers we pass it thorugh the effect composer to add effects like bloom it looks good but try to find a sweet spot because too much bloom doesnt look good and too little has no effect on the scene

NOTE:- Also I have found that when using the webgl renderer it tends to overbrigthen the bright parts of the scene to avoid that use renderer.toneMapping = THREE.ACESFilmicToneMapping and adjust the exposure by renderer.toneMappingExposure = 1

Shadows

This is very tricky because if you use real time shadows the frame rates would drop drastically like i was getting 55-60 without the shadows and with the shadows enabled it drops to 25-30 so instead of using real time shadows we will bake the shadows in blender and use the .png file from there to use as a floor in our project I have given the dome.blend file just open it and follow these instructions to make its shadow map:-

STEPS TO MAKE SHADOW MAP

  1. first import you desired model into the .blend file in blender

import

  1. If the render engine is set to eevee change it to cycles also you can switch from CPU to GPU compute if you have a good dedicated gpu it can drastically improve performance

import

NOTE:- if you want to see how the shadow looks switch to rendered view by clicking the water bubble typa thing in the top right of the viewport.

  1. select the floor mesh and the image node of the floor in the shader editor

import

  1. Click the Bake button to bake the shadows and get a png file

import

  1. save the baked shadow as a png

import

How to use the bakedshadow.png in your project

To use different shadow maps for different cars we use this custom floor code that automatically removes the white background from the baked shadows and allows us to adjust its alpha value to make the shadows more darker or lighter for instance if you increase the aplha the shadows become darker and lowering the alpha makes the shadows lighter

NOTE:- I have taken help from AI to make this peice of code

            const texsha = new THREE.TextureLoader(manager).load('./shadowmaps/astonshadow.png'); //loadingthe shadow.png and using it as a the texture of the floor and change the png file accordingly
            const geosha = new THREE.PlaneGeometry(6.5, 10); //ajust this accordingly if you car is bigger make this value bigger 6.5 is the length and 10 is the width of the plane in this case
            const matsha = new THREE.ShaderMaterial({
                transparent: true,
                depthWrite: false,
                uniforms:{
                    tDiffuse:{value:texsha},
                    shadowColor:{value: new THREE.Color(0x000000)}
                },
                vertexShader:`
                    varying vec2 vUv;
                    void main(){
                        vUv = uv;
                        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
                    }`,
                fragmentShader: `
                uniform sampler2D tDiffuse;
                uniform vec3 shadowColor;
                varying vec2 vUv;
                void main(){
                    vec4 tex = texture2D(tDiffuse, vUv);
                    float alpha = 1.0 - tex.r;
                    gl_FragColor = vec4(shadowColor, alpha*0.99); // adjust the alpha here to make the shadows darker or brighter
                }`
            })

Paste it in every car block you are making or using also dont forget to change the png file for different cars

Carpart Animations and reflections

First we are initializing an empty array named Carparts and then when we load a model of car we are extracting its parts such as doorleft, doorright, bonnet, trunk etc and pusing it to the carparts array and then we are using a simple raycaster (It is available on three js examples ) to select the mesh we click on and then i have divided the door opening into two parts swan and normal as the doors in the aston martin rotate a little upward and the then opens in the yaxis you can also change the target animation and how long should the animation by editing these peices of code

part.rotation[axis] = THREE.MathUtils.lerp(part.rotation[axis], targetangle, 0.08(edit this value if you want the animations to last longer));

editing the above value can make the animations of door last longer

let targetangle = part.userData.isOpen ? (Math.PI/3(edit this angle to make the doors open more)) : 0

editing the above value would make the doors open at an specific angle as per your needs

For the reflections since we are having an hdri file already the car paint would reflect those and make it seam like its realtime reflections but it isnt truly realtime its just an hdri image which saves a lot of frame rates

Edit Mode

This isnt the best edit of the car but the working of it is beautifull

  1. first you need an audio file with the music then seperate its vocals , intruments, drums etc.
  2. use an frequency analyser to target the specific paino frequency or any other intrument frequency do the same for drums, vocals etc.
  3. Then use const painofreq = analyzepiano.getFrequencyData(); to get the frequency data as an array list and then use const paipeak = painofreq[25] to target the specific paino frequency and then use that frequency to either switch the camera or flash the headlights according to the beats everything can understood in the code from line 1045 to 1108
  4. Make an array containg the positions for the camera with thier fovs to show in the edit.

Headlights,tailights and indicators Initializing lights just like we initialized doors

    const highlb = carModel.getObjectByName('Object_249');
    const lowrb = carModel.getObjectByName('Object_175.021');
    const highrb = carModel.getObjectByName('Object_256');
    const taillight = carModel.getObjectByName('Object_460');

NOTE:-VERY IMPORTANT Put the object name from blender in the eg:- carmodel.getObjectByName('OBJECT NAME HERE')

To make them glow we use a custom function which alters the objects material while playin with emissiveIntensity to control how intensive the light should glow you can also adjust the intensity by editing hbmesh.forEach(mesh => mesh(use your own headligth mesh name).material.emissiveIntensity = 50(edit this value to alter intensity));

For the indicators

We use simple mathematics which involves playing with the sin() and raise it to the power 4 then its output would only return [0,1] which we use to make the indicators blink heres the mathematical function Math.pow(Math.sin(time), 4) and heres the code we used to make it do infinite ossilations with time:-

    const time = Date.now() * 0.003;
    let wavelight = Math.pow(Math.sin(time), 4);
    const flashinte = wavelight*16

Bonuses

I also animated the ectascy and screencover of rolls royce by using the same technics i used while making the door animations i only changed the axis of rotations and angle however while animating the ectascy i first animated its cover and then when the animation for the cover of ectascy was over then the animation for ectasy played look at code from line 945 - 971

Sweeping indicators

This was made with a little help of ai basicaly i altered the mesh uv to make it straight paralell verticals lines then we set the startpoint of light to 0 and the endpoint of the light to 1 and stor it in a variable named usweep ANd then use step(vUv.x, usweep) function to talk to the GPU directly and try to load the pixels.

AI USAGE

I have used ai to fix code and fix the logics and have used inline suggestions in vscode so the ai contribution in this project is about 20-25%.

Bonus Draco file compression GLB

To further optimize the webpage I used google's draco file compression to reduce the size of glb file heres how to do it

  1. save your glb file in a folder and open cmd in that same folder
  2. type npm install -g gltf-pipeline in the cmd window
  3. use command gltf-pipeline -i yourglbfile.glb -o output.glb -d
  4. copy the draco file to the project's folder
  5. use this code to load draco file formats
const draloader = new DRACOLoader();
draloader.setDecoderPath('https://www.gstatic.com/draco/versioned/decoders/1.5.6/');
const loader = new GLTFLoader();
loader.setDRACOLoader(draloader);

One Problem with GLb

If you are a blender pro sometimes the blender mathmatics doesnt fit while exporting in glb format as a result some textures are glitched to fix it

  1. open the uv editor in blender of the mesh whose textures are glicthing.
  2. Fix the UV map by resizing and scaling just make sure that the map covers the whoe texture.
  3. now export and the problem would be fixed.

Tips

  1. Try to find a car which is detailed to only an extent where the users would be able to watch do not use hyper ultra realistic models for this project as it would cause frame drops
  2. Use Shadow maps/baked shadows do not use real time shadows or reflections as this would cause drastic frame drop and will not even work in low end devices
  3. try to name every object a unique name in blnder so that its easier to know which mesh is which
  4. Use Draco compression to reduce the size of the glb file thereby increasing the loading speed of the website
  5. Make a good graphics menu to adjust everything just like i have done so it can run on low end devices

THANK YOU

About

Detailed 3d Showcase of cars

Topics

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Contributors