Landing page with Three.js & Shader programming
Tags: webdev, frontend
Published: 2023-12-07 08:07:48

Three.js is a good framework to make a landing page with 3D object & visual effecting.

This is a function to render 3D text with shader programming.

  let AddText = (scene, font , fontsize, position, text) =>
  {
    let vTxShader=`
    varying vec3 vWorldPosition;
    varying vec4 fragCoord;
    varying vec2 vUv;
    uniform vec3 iResolution;
    uniform float iTime;
    varying vec4 fragColor;
    uniform float iFontsize;
    uniform vec3  iPosition;
  
    #define PI 3.14159265358979
    #define P2 6.28318530717959
  
    float Pow5 (float x)
    {
        return x*x * x*x * x;
    }
  
    float saturate(float x)
    {
      return max(0., min(1., x));
    }
  
    vec3 F_Schlick1(float u, vec3 f0, float f90) {
      return f0 + (vec3(f90) - f0) * pow(1.0 - u, 5.0);
    }
  
    float F_Schlick2(float u, float f0) {
      float f = pow(1.0 - u, 5.0);
      return f + f0 * (1.0 - f);
    }
  
    vec3 F_Schlick(vec3 f0, float f90, float u) {
      return f0 + (f90 - f0) * exp2((-5.55473f * u - 6.98316f) * u);  //native_powr(1.f - u, 5.f);
    }
  
    float Fr_DisneyDiffuse(float NdotV, float NdotL, float LdotH, float linearRoughness) {
      float energyBias = mix(0., 0.5, linearRoughness);
      float energyFactor = mix(1.0, 1.0 / 1.51, linearRoughness);
      float fd90 = energyBias + 2.0 * LdotH*LdotH * linearRoughness;
      vec3 f0 = vec3(1.0f, 1.0f, 1.0f);
      float lightScatter = F_Schlick(f0, fd90, NdotL).r;
      float viewScatter = F_Schlick(f0, fd90, NdotV).r;
      
      return lightScatter * viewScatter * energyFactor;
    }
  
    float DisneyDiffuse(float NdotV, float NdotL, float LdotH, float perceptualRoughness)
    {
        float fd90 = 0.5 + 2. * LdotH * LdotH * perceptualRoughness;
        // Two schlick fresnel term
        float lightScatter   = (1. + (fd90 - 1.) * Pow5(1. - NdotL));
        float viewScatter    = (1. + (fd90 - 1.) * Pow5(1. - NdotV));
  
        return lightScatter * viewScatter;
    }
  
  
    float ggxNormalDistribution( float NdotH, float roughness )
    {
      float a2 = roughness * roughness;
      float d = ((NdotH * a2 - NdotH) * NdotH + 1.);
      return a2 / (d * d * PI);
    }
  
    float ggxSchlickMaskingTerm(float NdotL, float NdotV, float roughness)
    {
      float GGXV = NdotL * sqrt(NdotV * NdotV * (1.0 - roughness) + roughness);
      float GGXL = NdotV * sqrt(NdotL * NdotL * (1.0 - roughness) + roughness);
      float G = 0.5/(GGXV + GGXL);
      return G;
    }
  
    vec3 schlickFresnel(vec3 f0, float lDotH)
    {
      return f0 + (vec3(1.0f, 1.0f, 1.0f) - f0) * pow(1.0f - lDotH, 5.0f);
    }
  
    float D_GGX(float NoH, float roughness) {
      float a = NoH * roughness;
      float k = roughness / (1.0 - NoH * NoH + a * a);
      return k * k * (1.0 / PI);
    }
  
    float V_Kelemen(float LoH) {
      return 0.25 / (LoH * LoH);
    }
  
    float Fd_Lambert() {
      return 1.0 / PI;
    }
  
    float V_SmithGGXCorrelated(float NoV, float NoL, float roughness) {
      float a2 = roughness * roughness;
      float GGXV = NoL * sqrt(NoV * NoV * (1.0 - a2) + a2);
      float GGXL = NoV * sqrt(NoL * NoL * (1.0 - a2) + a2);
      return 0.5 / (GGXV + GGXL);
    }
  
  
    float roughnessValue = 1.1;
    vec3 WorldSpaceCameraPos = vec3(0.1, 0.8, 0.8);
    vec3 lightpos = vec3(0., 4., -8.);
    vec3 baseColor = vec3(0.9, .45, .1);
    float lightColor = 3.5;
    float clearCoat = 0.5;
    float transparent = .8;
  
    void main() {
    vUv = uv;
    vec4 worldPosition = modelMatrix * vec4(position, 1.0);
    vWorldPosition = vec3(-worldPosition.z, worldPosition.y, -worldPosition.x);
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
  
    vec3 origin = (vec4(iPosition,1.0) * modelMatrix).xyz + vec3(iFontsize/2.5, 5.0, 2.0);
    WorldSpaceCameraPos += origin;
    lightpos += origin;
  
    float linearRoughness = roughnessValue * roughnessValue;
    vec3 normalDirection = normalize(worldPosition.xyz);
    vec3 viewDirection = normalize(WorldSpaceCameraPos-worldPosition.xyz);
    vec3 lightDirection = normalize(lightpos-worldPosition.xyz);
    vec3 halfDirection = normalize(lightDirection + viewDirection );
  
  
    float NdotL = saturate(dot(normalDirection, lightDirection));
    float NdotV = saturate(dot(normalDirection, viewDirection));
    float VdotH = saturate(dot(viewDirection, halfDirection));
    float NdotH = saturate(dot(normalDirection, halfDirection));
    float LdotH = saturate(dot(lightDirection, halfDirection));
  
    float diffuse = Fr_DisneyDiffuse(NdotV, NdotL, LdotH, linearRoughness);
    vec3 diffuseColor = baseColor * diffuse/PI;
  
    vec3 F = schlickFresnel(baseColor, LdotH);	
    float  D = ggxNormalDistribution(NdotH, linearRoughness);
    float  G = ggxSchlickMaskingTerm(NdotL, NdotV, linearRoughness);
      
    vec3 specularReflection = lightColor * baseColor * (F * D * G);

    float clearCoatPerceptualRoughness = clamp(roughnessValue, 0.089, 1.0);
    float clearCoatRoughness = clearCoatPerceptualRoughness * clearCoatPerceptualRoughness;
  
    float f0 = 0.04;
    float  Dc = D_GGX(NdotH, clearCoatRoughness);
    float  Vc = V_Kelemen(LdotH);
    float  Fc = F_Schlick2(f0, LdotH) * clearCoat; // clear coat strength
    float Frc = (Dc * Vc) * Fc;
    vec3 Fd = diffuseColor * Fd_Lambert();
    float V = V_SmithGGXCorrelated(NdotV, NdotL, clearCoatRoughness);
    vec3 Fr = (Dc * V) * F;
    vec3 CoatColor =  baseColor * ((Fd + Fr * (1.0 - Fc)) * (1.0 - Fc) + Frc);
  
    float alpha = transparent;//baseColor.a;
    vec3 specularColor = specularReflection;    
    vec4 glassColor =  vec4(diffuseColor + specularColor, alpha);
  
  
    fragColor = vec4(diffuseColor, 1.0);
    fragColor = vec4(specularReflection, 1.0);
    fragColor = glassColor;
  
    fragCoord = gl_Position;
    }
    `;
    let fTxShader=`
    varying vec4 fragColor;
  
    void main(void)
    {
      gl_FragColor = fragColor;
    }
    `;

    txtMaterial.push(new THREE.ShaderMaterial({
      side: THREE.DoubleSide,
      uniforms: {
        iResolution: {
          value: new THREE.Vector3(window.innerWidth , window.innerHeight, 1)
        },
        iTime: {
          value: time
        },
        iFontsize: {
          value: fontsize
        },
        iPosition: {
          value: position
        }
      },
      vertexShader: vTxShader,
      fragmentShader: fTxShader
    })
    );


    const textGeometry = new TextGeometry( text, {
      font: font,
      size: fontsize,
      height: fontsize,
      curveSegments: 20,
      bevelEnabled: true,
      bevelThickness: fontsize*.5,
      bevelSize: fontsize/10.0,
      bevelOffset: 0.0,
      bevelSegments: 20
    } );
    
    var textmesh = new THREE.Mesh( textGeometry, [txtMaterial[txtMaterial.length-1],txtMaterial[txtMaterial.length-1]]);
    textmesh.position.set(position.x, position.y, position.z);
    scene.add( textmesh );

    textTitle.push(textmesh);
    return textmesh;
  }