Question on Material

Hi,

I am trying to use material to color a surface based on the the angle between its normal and a given vector representing the vertical (I am computing a slope). I have gotten that to work correctly by creating a simple material with a vertex and fragment shaders, but I am not doing any shading or anything fancy, so its looks, well, flat.

What I would like to do is to add the color computed by my normal material to be added as a semi-transparent color on top of what the Common/MatDefs/Light/Lighting.j3md is providing (shading, etc). So I am looking at getting the rendering of provided by using the Lighting.j3md and then mixing an additional color on top based on some other calculations (here I have started by looking at the slope)

I do not know how to approach this ?

I was thinking of creating a new material by copying the Lighting.j3md and its associated (many) vertex and fragment shaders and adding in the shaders what is required to compute the slope derived color and add it add to the gl_FragColor computed by the Lighting.j3md shaders, but this seems to be not trivial (I am a beginner in shaders).

Thanks !

The tricky part of lighting.j3md is that it does it’s lighting calculations in “view space” in the fragment shader. That means that the interpolated normals, etc. are all in camera-relative space… where as it sounds like you would prefer world space.

Something to note is that the PBR material actually is doing lighting in ‘world space’. So the normal available in the fragment shader is in world space and its y component then can be used to determine how ‘up’ something points.

I don’t know if this is helpful information. Step 1 is going to be forking a shader and hacking away at it.

Do note: you don’t need to necessarily copy/fork all of the vert and frag files as you will likely only be using one set of them. (Single pass versus multipass lighting, etc…)

Thanks !

I did a quick try using the Lighting.j3md (that is the only one that I have in my now very dated version of JME3) and it behaves as you described : the inNormal that is passed to the vertex shader is view dependent. Where is this behaviour defined ? I looked at the material, the vertex and fragment shader and could not figure it out. I will try to import the PBRLighting.jme3 and hack that one, but I am trying to understand why the Lighting.j3md is behaving the way it is.

inNormal is the one defined from the model. The vertex attributes come from the mesh.

The inNormal is then transformed into view space further down. It’s the ‘varying’ variables you need to look at because that’s what gets interpolated over all of the fragments.

…but as said, Lighting.j3md is doing everything in view space so you’ll have to setup your own varying and transform inNormal to world space and store it in the varying.

Thanks for your reply!

I am still struggling to understand what makes the InPosition being interpolated to the view space.

Here is the very simple vertex and fragment shader I created before I tried to hack the Lighting one:

Vertex Shader:

[...]

varying vec3 normal;

void main()
{   	
  	normal = vec3(inNormal);
  	normal = normalize(normal);
  	
  	position = inPosition;
  	
  	gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1.0);
}

Fragment Shader:

[...]

uniform float m_WarningSlope; 
uniform float m_AlarmSlope;

varying vec3 normal;

const vec3 vertical = vec3(0,0,1.0);

void main() 
{	
	// Compute the angle between the normal and the vertical.
	float angle = abs(acos(dot(normal, vertical)));	
	
	if(angle < m_WarningSlope)
	{
		gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);    
	}
	  	
	if(angle >= m_WarningSlope)
	{
		gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);    
	}  	
	
	if(angle >= m_AlarmSlope)
	{
		gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);    
	}  			
}

This worked as expected and is view independent (i.e. the slope color does not change as the view point is moved around).

I tried to use the same approach and initialized the normal variable at the beginning of the Lighting.vert main function. I then added the fragment code shown above and mixed the resulting color with what computed be Lighting.frag at the very end of the main function.

While I think I could probably fix this by applying the inverse of the g_WorldViewProjectionMatrix to the normal before doing the slope angle calculation, I still do not understand why the interpolation is affecting my normal variable differently in this case. What is making the interpolation behaving differently in my shader and the Lighting one ?

I just did something similar but with the PBR shader and faced the same view-dependent normals at first.
But it works now:

Vertex shader:

[...]
attribute vec3 inNormal;
varying vec3 origNormal;

void main() {
    origNormal = inNormal;
    [...]
}

Fragment shader:

varying vec3 origNormal;

vec4 groundColor() {
    float angle = acos(dot(origNormal, vec3(0, 1, 0)));
    [...]
}

void main() {
    [...]
    vec4 albedo = groundColor();
    [...]
}

It’s not really clear to me what your latest problem is and what you want to fix. What is the difference in the output between your simple shader and the patched Lighting shaders?

Why position?

In case you didn’t know: The GPU does the interpolation over the fragments automatically. It’s not in the shader code. It can be disabled with the “flat” keyword, like flat varying vec3 origNormal.
But interpolation is not what’s causing view-dependency.

Edit:
Actually, yeah, the PBR shader doesn’t need the modification to the vertex shader. The existing “wNormal” is already in world space and can be used for the calculations.
float angle = acos(dot(wNormal, vec3(0, 1, 0)));

And btw: Reduce the PBR Material’s “Metallic” paramter to <1.0 to make the color visible.

Thanks for your answer !

I actually got the code to work in the modified Lighting vertex and fragment shader and get it to work as I expected. I am not sure what was not working in my previous attempts… My code looks very much like yours, but I modified the Lighting.vert and Lighting.frag instead (I could not get the PBRLighting to work in my setup, which is make use of an old version of JME3).

P.S. Why Position ? → position = inPosition; This is a left over from a previous test I was doing…

1 Like