Saturday, July 9, 2022

Making scene lights work in a Three.js custom shader

Here's a step by step breakdown of what I had to do to pass scene lights to a custom shader in Three.js

First off, set up your scene with some lights:


Define the lights:

var light1, light2;


Add the lights to your scene:

    // Add lights.

    //color, strength, max distance, decay power

    light1 = new THREE.PointLight(0x2596be, 1, 100.0, 2.0);

    light1.position.set( 30, 10, -20 );

    this.scene_.add(light1);

   

    //color, strength, max distance, decay power

    light2 = new THREE.PointLight(0xf1bb75, 1, 100.0, 2.0);

    light2.position.set( -40,-2, 20 );

    this.scene_.add(light2);


Then, add the light data to your shader uniforms using the Three.UniformsUtils.merge function

    // setup material uniforms

    var uniforms = THREE.UniformsUtils.merge(

      [THREE.UniformsLib['lights'],

      {

        time : {value: time},

      }

      ]

    )



Pass the uniforms to the materials, along with Lights: true

    const material = new THREE.ShaderMaterial({

      uniforms:  uniforms,

      vertexShader: await vsh.text(),

      fragmentShader: await fsh.text(),

      lights: true

    });



In your vertex shader, transform the normal by the normalMatrix, export viewPosition

varying vec3 vNormal;

varying vec3 vViewPosition;

 

void main() {

  vec4 ViewPosition4 = modelViewMatrix * vec4(position, 1.0);

  vViewPosition = ViewPosition4.xyz;

 

  gl_Position = projectionMatrix * ViewPosition4;

  vNormal = normalMatrix * normal;

}




Then, in your fragment shader bring in this struct and this uniform (for point lights, anyway)

struct PointLight {

  vec3 position;

  vec3 color;

  float distance;

  float decay;

};

uniform PointLight pointLights[ NUM_POINT_LIGHTS ];


Then do whatever you want to do with the lights in a loop that goes through all of them:

  vec3 normal = normalize(vNormal);

  vec4 addedLights = vec4(0.1, 0.1, 0.1, 1.0);

 

  for(int l = 0; l < NUM_POINT_LIGHTS; l++) {

    vec3 lightVector = pointLights[l].position  - vViewPosition;

    vec3 lightDirection = normalize(lightVector );

    vec3 lightColor = pointLights[l].color;

   

    addedLights.rgb += clamp(dot(lightDirection, normal), 0.0, 1.0) * lightColor ;

  }



The complete fragment shader:

varying vec3 vPos;

varying vec3 vNormal;

varying vec3 vViewPosition;

 

struct PointLight {

  vec3 position;

  vec3 color;

  float distance;

  float decay;

};

uniform PointLight pointLights[ NUM_POINT_LIGHTS ];

 

void main() {

  vec3 normal = normalize(vNormal);

  vec4 addedLights = vec4(0.1, 0.1, 0.1, 1.0);

 

  for(int l = 0; l < NUM_POINT_LIGHTS; l++) {

    vec3 lightVector = pointLights[l].position  - vViewPosition;

    vec3 lightDirection = normalize(lightVector );

    vec3 lightColor = pointLights[l].color;

   

    addedLights.rgb += clamp(dot(lightDirection, normal), 0.0, 1.0) * lightColor ;

  }

 

   gl_FragColor = addedLights;

 

}

 


If you want to incorporate distance based falloff:

varying vec3 vPos;

varying vec3 vNormal;

varying vec3 vViewPosition;

 

struct PointLight {

  vec3 position;

  vec3 color;

  float distance;

  float decay;

};

uniform PointLight pointLights[ NUM_POINT_LIGHTS ];

 

float lightingFalloff(float lightDistance,  float cutoffDistance,  float decayExponent ) {

  if( cutoffDistance > 0.0 && decayExponent > 0.0 ) {

    return pow( clamp( -lightDistance / cutoffDistance + 1.0, .0, 1.0 ), decayExponent );

  }

  else {

    return 1.0;

  }

}

 

void main() {

  vec3 normal = normalize(vNormal);

  vec4 addedLights = vec4(0.1, 0.1, 0.1, 1.0);

 

  for(int l = 0; l < NUM_POINT_LIGHTS; l++) {

    vec3 lightVector = pointLights[l].position  - vViewPosition;

    vec3 lightDirection = normalize(lightVector );

    float lightDistance = length(lightVector);

    vec3 lightColor = pointLights[l].color;

   

    addedLights.rgb += clamp(dot(lightDirection, normal), 0.0, 1.0) * lightColor ;

    addedLights.rgb *= lightingFalloff(lightDistance,  pointLights[l].distance,  pointLights[l].decay );

  }

   gl_FragColor = addedLights;

}





No comments:

Post a Comment