Custom vertex shader with lambert lights


#1

I’m trying to reuse the fragment of MeshLambertMaterial in my custom vertex shader.
Basically my shader is drawing a cylinder between 2 boxes.
As you can see from the jsfiddle, the light is not updated correctly,
the directional light follows the camera, and if you look from the side you can see the boxes are properly illuminated
but the cylinder is not, it is only illuminated from the top.
Do I somehow need to recalculate the normals in my vertex shader? How can i do that?

https://jsfiddle.net/tfethwfe/157/

Thanks


#2

You are using onBeforeCompile which has some unique behavior that you may not expect.

If you’ve already copied the Entire vertex shader, you could try to build a THREE.ShaderMaterial({lights:true}) and see if it behaves any differently.

This proposal might seem interesting to you:

Listed are a few ways out in the wild that people approach the problem you’re facing of “extending materials”. There’s also a slight modification suggested for the core that would remove a tremendous amount of code from your fiddle. (take a look at the instancing lambert example).

If you are asking for specific help regarding some GLSL logic, i’d suggest creating a minimal example using THREE.ShaderMaterial without all the unnecessary includes.

At first glance it’s hard to tell what the expected result is supposed to be.

By overriding the entire shader, you may also end up nuking certain things that THREE.WebGLRenderer does to that string in between you instantiating the material and when onBeforeCompile get’s called. You could log the incoming string and compare it to your LINK_VERTEX.

I don’t know much about onBeforeCompile (other than what i’ve observed in the proposal) and how exactly it’s supposed to behave, but i do know that overall in software engineering functions with a lot of side-effects can be dangerous.

There aren’t that many people using this functionality. @Mugen87 is the only person I know that hinted at knowing how to properly use this API, so i hope he may shed some light on this :slight_smile:


#3

So actually the shaders here are not the problem, it doesn’t seem to have had any extra defines or includes and stuff before you completely overwrite it in it’s entirety.

The uniforms you just stuck straight up into the material and it doesnt work like that. You can declaratively pass uniforms in the constructior on ShaderMaterial but you cannot on other materials. It has to be attached to the shader asynchronously.

Again i’m the last person to ask about how onBeforeCompile works, but what i’ve seen other people do is something like this:

material.onBeforeCompile => {
  shader.uniforms.yourUniform = yourUniform
  ...
  }

So it looks like you would have to run a loop inside there,

material.onBeforeCompile = shader => {

  Object.keys(THREE.UniformsLib['lights']).forEach(key=>addUniformToShader(key,shader))
}

but i would advise care, even though it’s not documented, this function doesn’t particularly like you writing any code in it other than shader.vertexShader.replace() and decorating the shader.uniforms object. I didn’t know it was a feature so i filed it as a bug by accident:

It’s just my observation though, i don’t know how to properly use onBeforeCompile i always ended up in some kind of a mess with a lot of repetitive code that i don’t know when happens, asynchronous material instantiation and things.

Your example is pretty good, hope it helps someone shed some light on this useful functionality. I’d like to learn how to properly use it :slight_smile:


#4

Hmmmm, this looks like this might be what you’re looking for:

I feel it’s somewhat of a limited example, it didn’t help me learn a lot on how to properly use it :frowning:

I think it’s also problematic to document because it will soon be deprecated.

You should be doing this kind of stuff with a NodeMaterial when it’s integrated in a few years. I think Three.js wants to move in a direction where you shouldn’t really have access to GLSL at all, but rather use a wrapper. Like instead of typing foo + bar it would be an instance of javascript objects like AddComponentNode with NumberInputNode etc. Each Material will become a scene graph of it’s own so to speak.

I see it kinda like what GLSL -> BufferGeometry == NodeMaterial -> Geometry. Because geometry works with numbers and they have to be converted to efficient typed arrays it makes sense to deprecate a class like Geometry that creates a lot of javascript cruft.

However since Materials mostly just contain one big string, there’s a lot of wiggle room, so instead of having to write ugly GLSL and store it in a primitive string, you can have a whole data structure of JS classes describing that GLSL. Writing GLSL is becoming obsolete, it’s all going to be replaced by a visual editor.

I may have misunderstood all of this though.


#5

Just a side note, there is also a way to reduce the amount of code in your fiddle by using onBeforeCompile

onBeforeCompile = shader=>{
shader.vertexShader = shader.vertexShader.replace('foo',foo))
shader.vertexShader = shader.vertexShader.replace('bar',bar))
shader.vertexShader = shader.vertexShader.replace('baz',baz))
shader.vertexShader = shader.vertexShader.replace('qux',qux))
}

I’m not sure if this is the proper way to use it, but it’s how the example works. This way you wouldn’t have to copy over the entire template just to insert some strings into a bigger string.

I didn’t like this pattern when i ended up doing it like this.

I’m really good at understanding the GLSL code and the structure of the template, i’m really really bad at regular expressions. I mostly paste the shader here and then try to experiment with various expressions while looking at the cheat sheet.
https://regexr.com/

It’s somewhat frustrating, but i know the key to writing shaders and graphics are regular expressions, so i’m trying to improve in that area. :wink: I’ve put too much focus in linear algebra, the graphics pipeline, software design patterns and other less relevant stuff.

I wonder if there is a way to isolate the patterns of the most likely usage and code that you might see in this callback, and abstract some of it. ShaderMaterial somehow just works. I wrote down some random thoughts in that proposal above.


#6

Thanks for the insights but I do believe my issue is that I somehow need to transform the normals as well.
Here is a simplified fiddle: https://jsfiddle.net/tfethwfe/158/
The boxes are lambert and the the light reflection should be the same for the cylinder as well.

Some examples:
See that from this side the boxes are illuminated but the cylinder looks dark
image

The the other side:
image

While from the top the cylinder is properly illuminated with the boxes:

As well as from behind:
image


#7

As i have said, in that case, i would advise to make a simple THREE.ShaderMaterial where only your code will run.

If you expect some logic to do something to attribute vec3 normal; output that as a varying and read it in the shader.

I looked at it again and i’ve no idea what’s supposed to happen.

const VERTEX_POSITION = `
//vec3 scale = vec3( 1, 1, scaleZ );
//transformed = rotateVector(rotation, position * scale ) + midPoint;

vec3 src = vec3(0,1,1);
vec3 dest = vec3(3,1,1);

mat3 lookAt = calcLookAtMatrix(dest, src, vec3(0, 1, 0));
vec4 qua = q_look_at(lookAt);
vec3 scaled = vec3(1, 1, distance(src, dest));
vec3 midPoint = (src + dest) * 0.5;
transformed = rotateVector(qua, position * scaled) + midPoint;
`;

Hard to read it’s commented GLSL in a string literal. (even though it looks better here).

edit

yeah looks like you’re trying to rotate your vertices without rotating the normals.


#8

Yes that was it!

Actually I didn’t realise I should just rotate normals, I thought it has something to do with the stretching of the cylinder. Thanks a lot for the help dude, replacing the code to ShaderMaterial made it kinda more clear what needs to be done.

Anyway the solution (if anyone cares is simply):

Removing:

#include <defaultnormal_vertex>

And adding:

vec3 transformedNormal = normalMatrix * rotateVector(qua, objectNormal);


#9

Glad to hear that.

Sigh at the usage of onBeforeCompile. Granted i don’t think you used it correct, but even if you did… Helping you came at a cost :rofl:

My heart hurts when i see people using that horrible feature, writing so much repetitive and redundant code and such. Better to be outside and grab some fresh air and do it with as few line as possible. It’s a trigget. But oh well, glad i could help.