# Generating Normals for 3D Vertex Noise-Modulated Mesh In GLSL

10 replies to this topic

### #1toneburst

New Member

• Members
• 14 posts

Posted 21 November 2008 - 04:40 PM

Hi,

I'm just trying to generate working normals in my simple Vertex Noise shader, and haven't been having much success.

I've been using GLSLs builtin noise3() function, and the simple 'neighbours' technique outlined by tonfilm on his blog

Just wondering if anyone can offer any advice on where I might be going wrong, or tell me whether or not this technique is suitable for use in this particular case.

Here is my vertex shader, which applies noise to the vertex position using noise3(), calculates a normal (wrongly), changes the texture coords for a simple Envmap lookup in the Fragment Shader, and sets a varying variable for basic diffuse lighting:

// 3D Noise controls

uniform vec3 Offset;

uniform float ScaleIn;

uniform float ScaleOut;

// 3D Noise function

vec4 noise3D(in vec4 vert, in vec3 gridOffset)

{

vert.xyz += noise3(gridOffset + Offset + vert.xyz * ScaleIn) * ScaleOut;

return vert;

}

// Structure to hold vertex position and normal

struct posNorm {vec4 pos;vec3 norm;};

// Calculate and return vertex position and normal

posNorm vNoise3D(in vec4 vert)

{

// Init output variable of custom type posNorm (defined above)

posNorm result;

// Calculate new vertex position using function defined above

result.pos = noise3D(vert, vec3(0.0));

// Calculate normals

float gridOffset	= 0.001;

vec4 neighbourX0	= noise3D(vert, vec3(gridOffset, 0.0, 0.0));

vec4 neighbourY0	= noise3D(vert, vec3(0.0, gridOffset, 0.0));

vec3 tangent		= neighbourX0.xyz - result.pos.xyz;

vec3 bitangent	= neighbourY0.xyz - result.pos.xyz;

vec3 norm			= cross(tangent, bitangent);

norm				= normalize(norm);

norm				= gl_NormalMatrix * norm;

result.norm		= norm;

return result;

}

///////////////////////////

//      Main Loop        //

///////////////////////////

void main(void)

{

// Initial vertex position

vec4 vertex = gl_Vertex;

// get final vertex position and normals

posNorm outPut = vNoise3D(vertex);

// Call envMapVS function, passing it vertex position and normal

vec3 normal = gl_NormalMatrix * outPut.norm;

// Apply environment-map lighting function

envMapVS(vertex, normal);

gl_Position = gl_ModelViewProjectionMatrix * outPut.pos;

}

I don't think I need to post the Fragment Shader, as it's very simple, and I think it's the normal-estimation in the VS that's the problem here.

The screenshots below show the result I'm getting (with the envmap effect turned off):

As you can see, some of the Normals are clearly 'flipped'

Any advice on how I might solve this annoying issue much appreciated!

a|x
http://machinesdontcare.wordpress.com

### #2toneburst

New Member

• Members
• 14 posts

Posted 23 November 2008 - 07:36 PM

Anyone?

a|x

### #3toneburst

New Member

• Members
• 14 posts

Posted 02 December 2008 - 03:59 PM

If someone could tell me if I'm barking up the wrong tree completely with this one (ie is this particular method workable when using noise3()), I'd be really grateful.

a|x

### #4Kenneth Gorking

Senior Member

• Members
• 939 posts

Posted 02 December 2008 - 11:35 PM

I just looked over it again, and noticed tonfilm mentions that this only works with a 2D input mesh to be offset by the noise, like a flat terrain.

tonfilm said:

Assuming the input mesh is a grid in the xy-plane

Also, you seem to be multiplying the resulting normal twice with gl_NormalMatrix.
"Stupid bug! You go squish now!!" - Homer Simpson

### #5toneburst

New Member

• Members
• 14 posts

Posted 03 December 2008 - 09:35 AM

Hi Kenneth Gorking!

Thanks very much for getting back to me.
Doh! I've been very silly... of course, this only works with parametric surfaces, where, as you say, the input is a flat mesh...

The offsets I used to calculate 'neighbours' were on the X and Y axis, which of course failed to take into account the fact the original mesh is a sphere.

Soo... since the mesh is spherical, maybe if I calculate spherical coordinates for the vertices, then base the noise on rho, theta and phi values, rather than X, Y and Z coordinates, and calculate neighbours using offsets to two of the spherical coordinates, it may just work...

Thanks for the pointer!

I'll let you know how it goes.

a|x

### #6Kenneth Gorking

Senior Member

• Members
• 939 posts

Posted 04 December 2008 - 12:16 AM

Another way to do it, is to pass along the original normal of the mesh, and then create a new modified normal from the direction between the old and new vertex. Something like:

newNormal = normalize(orgNormal + normalize(newVertex - orgVertex));
"Stupid bug! You go squish now!!" - Homer Simpson

### #7toneburst

New Member

• Members
• 14 posts

Posted 04 December 2008 - 10:13 AM

Hi again.

Kenneth Gorking said:

Another way to do it, is to pass along the original normal of the mesh, and then create a new modified normal from the direction between the old and new vertex. Something like:

newNormal = normalize(orgNormal + normalize(newVertex - orgVertex));

Ah, great suggestion! So simple, too...

Unfortunately, it doesn't seem to work for me. I get the same jagged dark areas, and when I rotate the mesh relative to the light-source, the lighting behaves strangely- the light-source appears also to be moving, which is not the case if I use the original normal (though of course this is inaccurate after mesh distortion).

The screenshots below show the mesh rotated on the X axis, with the light-source in a constant position:

I'm sure there's something basic I'm missing here.

Can you think of anything else I could try?

a|x
http://machinesdontcare.wordpress.com

### #8Kenneth Gorking

Senior Member

• Members
• 939 posts

Posted 04 December 2008 - 10:26 AM

Did you remove the extra gl_NormalMatrix from your shader? Also, I think it should be 'orgVertex - newVertex' instead of what I wrote... :)
"Stupid bug! You go squish now!!" - Homer Simpson

### #9toneburst

New Member

• Members
• 14 posts

Posted 04 December 2008 - 10:47 AM

Kenneth Gorking said:

Did you remove the extra gl_NormalMatrix from your shader? Also, I think it should be 'orgVertex - newVertex' instead of what I wrote... :)

Yep, I removed the extra matrix-multiplication.
Swapping the order of the normal-calculation, as you suggested did make a difference, but didn't solve the problem, sadly.

Here is the Vertex Shader as it currently stands:


//

// vertexnoise.vert: Vertex shader for warping the geometry with noise.

//

// author: Philip Rideout

//

// Copyright (c) 2005-2006: 3Dlabs, Inc.

//

//

//

// Lighting + surface controls

uniform vec4 AmbientColor, DiffuseColor;

uniform vec3 LightPosition;

uniform vec4 SurfaceColor;

///////////////////////////

//       Functions       //

///////////////////////////

// Varying-type variable for lighting color, sent to Fragment Shader.

// Varyings are interpolated across polygons to give a smooth result in the FS.

varying vec4 outColor;

// Environment-Map function

void envMapVS(in vec4 vert, in vec3 norm)

{

vec4 vWorld = gl_ModelViewMatrix * vert;

vec3 nWorld = gl_NormalMatrix * norm;

// Diffuse light

vec3 vertToLight = normalize(LightPosition - vWorld.xyz);

float diffuseLight = max(dot(vertToLight, nWorld), 0.0);

// This varying variable is passed to the Fragment Shader

outColor = AmbientColor + vec4(diffuseLight * DiffuseColor.xyz, DiffuseColor.w);

// Environment mapping texture coordinates

vec3 vWorldUnit = normalize(vWorld.xyz);

vec3 f = reflect(vWorldUnit, nWorld);

float m = 2.0 * sqrt(f.x * f.x + f.y * f.y + (f.z + 1.0) * (f.z + 1.0));

// Texture coordinates set

// (determines which part of  envMap to lookup in FS).

// Also automatically interpolated between VS and FS.

gl_TexCoord[0].xy = vec2(f.x / m + 0.5, -f.y / m + 0.5);

}

// 3D Noise controls

uniform vec3 Offset;

uniform float ScaleIn;

uniform float ScaleOut;

// 3D Noise function

vec4 noise3D(in vec4 vert, in vec3 gridOffset)

{

vert.xyz += noise3(gridOffset + Offset + vert.xyz * ScaleIn) * ScaleOut;

return vert;

}

// Structure to hold vertex position and normal

struct posNorm {vec4 pos;vec3 norm;};

// Calculate and return vertex position and normal

posNorm vNoise3D(in vec4 vert)

{

// Init output variable of custom type posNorm (defined above)

posNorm result;

// Calculate new vertex position using function defined above

result.pos = noise3D(vert, vec3(0.0));

// Calculate normals

result.norm = normalize(gl_Normal + normalize(vert.xyz - result.pos.xyz));

return result;

}

///////////////////////////

//      Main Loop        //

///////////////////////////

void main(void)

{

// Initial vertex position

vec4 vertex = gl_Vertex;

// get final vertex position and normals

posNorm outPut = vNoise3D(vertex);

// Call envMapVS function, passing it vertex position and normal

vec3 normal = gl_NormalMatrix * outPut.norm;

// Apply environment-map lighting function

envMapVS(vertex, normal);

gl_Position = gl_ModelViewProjectionMatrix * outPut.pos;

}

a|x

### #10Kenneth Gorking

Senior Member

• Members
• 939 posts

Posted 04 December 2008 - 11:45 PM

After thinking some more about this, I realized that it will never work, because modifying a vertex also modifies the normals of the other vertices connected to the vertex. I'm afraid it's back to square one...
"Stupid bug! You go squish now!!" - Homer Simpson

### #11toneburst

New Member

• Members
• 14 posts

Posted 05 December 2008 - 09:17 AM

Ah...

in that case, I think I'll have to go back to the 'neighbours' method, but working in a Spherical coordinate system, as I mentioned above. Unfortunately, a GLSL compiler bug on my laptop prevents me from trying it at the moment, but I don't see why it wouldn't work, in theory...

I'll keep you posted.