I’m aware of onBeforeCompile but I don’t find it convenient to work with due to the fact that it requires knowledge of internal library includes and working with customProgramCacheKey is also not very intuitive for me.
So, I wrote this shader as a default template, maybe it will be useful for somebody else who writes custom shaders and in need of simple illumination.
It is based on per-pixel Lambertian diffusion and provides the same results for Ambient and Directional lights and similar results for Point lights (except at a close distance).
Below is a split-canvas test for comparison, you can move split using buttons or arrow keys, or pan the camera.
Link:
https://jsfiddle.net/tfoller/r4tghjnz/9/
Screenshot:
Here is the shader:
<script type="x-shader/x-vertex" id="vs_lamb">
// interpolated world positions and normals
varying mat2x3 vpn;
void main() {
vec4 pos = vec4(position, 1.0);
vec4 nrm = vec4(normal, 0.0);
gl_Position = projectionMatrix * modelViewMatrix * pos;
vpn = mat2x3(modelMatrix * mat2x4(pos, nrm));
vpn[1] = normalize(vpn[1]);
}
</script>
<script type="x-shader/x-vertex" id="fs_lamb">
struct ambientLight {
vec3 color;
float intensity;
};
struct directLight {
vec3 position;
vec3 color;
float intensity;
};
struct pointLight {
vec3 position;
vec3 color;
float intensity;
float power;
float range;
float decay;
};
uniform vec3 dif_color;
uniform ambientLight amb_light;
uniform directLight dir_light;
uniform pointLight pnt_light;
varying mat2x3 vpn;
void main() {
// ambient light
vec3 light = amb_light.color * amb_light.intensity;
// direct light
vec3 dl_pos = dir_light.position;
vec3 dl_dir = normalize(dl_pos);
float dl_dot = dot(dl_dir, vpn[1]);
light += clamp(dl_dot, 0.0, 1.0) * dir_light.color * dir_light.intensity;
// point light
vec3 pl_pos = pnt_light.position;
vec3 pl_dist = pl_pos - vpn[0];
float range = length(pl_dist);
if(pnt_light.range == 0.0 || pnt_light.range >= range) {
vec3 pl_dir = normalize(pl_dist);
float pl_dot = dot(pl_dir, vpn[1]);
float decay = 1.0;
if(pnt_light.decay > 0.0)
decay = pnt_light.power * pow(range, -pnt_light.decay);
light += clamp(pl_dot, 0.0, 1.0) * pnt_light.color * pnt_light.intensity * decay;
}
gl_FragColor = vec4(light * dif_color, 1.0);
}
</script>
And the most relevant code - setup for the shader mesh:
const intensity = {amb: 0.2, dir: 0.8, pnt: 1.5};
const amb_light = new THREE.AmbientLight(0xfa0000, intensity.amb);
const dir_light = new THREE.DirectionalLight(0x008f00, intensity.dir);
const pl = { power: 1, decay: 0, };
const pnt_light = new THREE.PointLight(0xffffff, intensity.pnt, 1000, pl.decay);
const amb_uni = {
color: Object.values(amb_light.color),
get intensity() { return amb_light.intensity },
};
const dir_uni = {
position: Object.values(dir_light.position),
color: Object.values(dir_light.color),
get intensity() { return dir_light.intensity },
};
const pnt_uni = {
position: Object.values(pnt_light.position),
color: Object.values(pnt_light.color),
range: pnt_light.distance,
get intensity() { return pnt_light.intensity },
get power() { return pl.power },
get decay() { return pl.decay },
};
const cube2 = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.ShaderMaterial( {
uniforms: {
dif_color: { value: Object.values(cube.material.color) },
amb_light: { value: amb_uni },
dir_light: { value: dir_uni },
pnt_light: { get value() { return pnt_uni } },
},
vertexShader: getShader('vs_lamb'),
fragmentShader: getShader('fs_lamb')
}),
);