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

B5e4e568a6d3ac0022536d83a2b3124e
0
toneburst 101 Nov 21, 2008 at 16:40

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

http://tonfilm.blogspot.com/2007/01/calculate-normals-in-shader.html for normal-estimation.

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):
vNoiseNorms\_01.png
vNoiseNorms\_02.png
As you can see, some of the Normals are clearly ‘flipped’

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

Thanks in advance,

a x

http://machinesdontcare.wordpress.com

10 Replies

Please log in or register to post a reply.

B5e4e568a6d3ac0022536d83a2b3124e
0
toneburst 101 Nov 23, 2008 at 19:36

Anyone?

a x
B5e4e568a6d3ac0022536d83a2b3124e
0
toneburst 101 Dec 02, 2008 at 15:59

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.

Thanks in advance,

a x
46407cc1bdfbd2db4f6e8876d74f990a
0
Kenneth_Gorking 101 Dec 02, 2008 at 23:35

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

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

Also, you seem to be multiplying the resulting normal twice with gl_NormalMatrix.

B5e4e568a6d3ac0022536d83a2b3124e
0
toneburst 101 Dec 03, 2008 at 09:35

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
46407cc1bdfbd2db4f6e8876d74f990a
0
Kenneth_Gorking 101 Dec 04, 2008 at 00:16

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));

B5e4e568a6d3ac0022536d83a2b3124e
0
toneburst 101 Dec 04, 2008 at 10:13

Hi again.
@Kenneth Gorking

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:

01.png
02.png
03.png
04.png
05.png

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

46407cc1bdfbd2db4f6e8876d74f990a
0
Kenneth_Gorking 101 Dec 04, 2008 at 10:26

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

B5e4e568a6d3ac0022536d83a2b3124e
0
toneburst 101 Dec 04, 2008 at 10:47

@Kenneth Gorking

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.
//
//
// See 3Dlabs-License.txt for license information
//

// 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
46407cc1bdfbd2db4f6e8876d74f990a
0
Kenneth_Gorking 101 Dec 04, 2008 at 23:45

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…

B5e4e568a6d3ac0022536d83a2b3124e
0
toneburst 101 Dec 05, 2008 at 09:17

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.
Thanks for all your help,

a x