Extendable shader material with customizable lights and shadows

The main idea behind this ShaderMaterial wrapper is to have lights attached to the material, so it’s possible to illuminate any object with any light(s), also I wanted to have more control over shadows and experiment with different ways to create them.

The lighting for this material is based on per-pixel Lambertian diffusion plus specular highlights.

The material currently supports texture map, bump map, translucency, gamma and any number of ambient, direct and point lights and 3 shadow modes for fun.

Point lights have attenuation over distance, light shadows have 4 levels of blur and strength.

Lights can be built-in THREE lights or stand-alone JavaScript objects.

This is still a work in progress but I wanted to share a couple of scenes.

Here is one with multiple objects illuminated by a custom material and UI to play with its settings (clickable link):


And now, when I married this material with instanced meshes, I could test it on more complex geometry, illuminating different groups of meshes with different lights (clickable link):


An example of material setup:

dir.userData.shadow = {
	blur: 5,
	strength: 0.9,
	tex: { w: 1000, h: 1000 },
	cam: new THREE.OrthographicCamera(-700/9, 700/9, 700/9, -700/9, 50, 300),
	lookat: dir_lookat,
pnt0.userData.shadow = {
	blur: 5,
	strength: 1,
	tex: { w: 1050, h: 875 },
	cam: new THREE.PerspectiveCamera(55, 1, 30, 300),
	lookat: lookAt,

const mat_main = cust.material('lamb', {
	uni: {
		color: { value: [1, 1, 1] },
		amb: {
			get color() { return [amb.color.r, amb.color.g, amb.color.b] },
			get intensity() { return amb.intensity },
		dir: {
			get color() { return [dir.color.r, dir.color.g, dir.color.b] },
			get intensity() { return dir.intensity },
			position: [dir.position.x, dir.position.y, dir.position.z],
			lookat: dir_lookat,
			shadow: dir.userData.shadow,
		pnt: [{
			get color() { return [pnt0.color.r, pnt0.color.g, pnt0.color.b] },
			get intensity() { return pnt0.userData.power },
			position: [pnt0.position.x, pnt0.position.y, pnt0.position.z],
			get decay() { return pnt0.userData.decay },
			shadow: pnt0.userData.shadow,
			get color() { return [pnt1.color.r, pnt1.color.g, pnt1.color.b] },
			get intensity() { return pnt1.userData.power },
			position: [pnt1.position.x, pnt1.position.y, pnt1.position.z],
			get decay() { return pnt1.userData.decay },
			shadow: pnt1.userData.shadow,
		specular: {
			get position() {
				const pos = camera.position;
				return [pos.x, pos.y, pos.z];
			get strength() { return ui.spec.str },
			get falloff() { return ui.spec.fo },
		shad_mode: { get value() { return ui.shad_mode } },
		map: ['/_v1_jsm/textures/uv.png', () => { callback('r') }, ],

1 Like

Unfortunately, I just get a black view. Edge browser, Windows 11

There’s a momentary flash where I can see your scene, but then its gone. When I adjust a couple of the sliders, I get the following warning in the browser console.

GL_INVALID_VALUE : glViewport: negative width/height

const renderRTVP = (rend, scene, camera, outputRT, vp, clear = true) => {
	const bool = !clear && rend.autoClear;
	if (bool) rend.autoClear = false;
	const vp_old = new THREE.Vector4();
	rend.render(scene, camera);
-->	rend.setViewport(vp_old);
	if(bool) rend.autoClear = true;

Hope this helps. The screen shots look amazing.

Thanks, I’ll have a look, not sure what might cause this.

I tested these pages in Chrome and FF, Win10.

Ok, I fixed some problems with the viewport, so now I can see these pages open w/o error on android mobile, can you please check if it fixed it for you as well?

Also added a button to test hi DPI screens, thanks!

Works! Looks fantastic. Nice work

1 Like


Added texturized point lights to turn them into spot lights and to enable artistic control over the light intensity in the cross-section.

Here is the example with 3 sample textures

texture UI controls can be found under Aperture section of the material setup (clickable link):


Also put all relevant files on github:

1 Like