Unable to get light shading to work in a simple shader


I have the need to combine up to 10 repeating texture patterns on a geometry with alpha channel for each texture pattern. However, the GSG’s getNumTextureStages() reports that I can have only 4 texture stages, with one texture and alpha channel each. This why I have to try to program a custom shader so I can upload 10 textures and alpha channels to do splatting.

Thus far the only thing I’ve managed to get working is splatting 10 textures with 4 alpha textures (with up to 3 channels each). However, this looks really bad because there is no shading and no shadows. For starters I need shading to work so I can at least get my project really started.

The model I am using is a GeoMipTerrain constructed from a heightfield.

I tried to combine some snippets from the net to get shading to work. I got the fragment shader code from the following snippet: [url]Lights inputs in GLSL shaders]

The problem is that when shader output picture on screen, the picture is full of black patches:

These black patches move and strech as I move the camera. Maybe I am using wrong types of input coordinates or something?

I can recite much of the the jargon regarding the math behind shading, but I have no real understanding what it all means. This is all just like reading religious scripture with a foreign language, just rehearsed and imitated rote. Thus, if some code snippet does not work, I have no means to figure out why it is broken.

So now I ask help. Can somebody figure out what am I doing wrong with the shading part?

EDIT: I tried loading a model from egg file instead of the GeoMipTerrain, and the shading is done properly. Thus it seems like GeoMipTerrain does not properly generate normals or something. What can be done? Can I somehow correct the normals in GeoMipTerrain?

Shader inputs via Python:

        dlight = DirectionalLight('dlight')
        self.dlight = self.render.attachNewNode(dlight)
        self.dlight.setHpr(0, -60, 0)
        self.terrain = GeoMipTerrain('test')
        root = self.terrain.getRoot()
        shader = Shader.load(Shader.SL_GLSL, vertex='terrainv.glsl', fragment='terrainf.glsl')
        for i in xrange(0, 10):
            root.setShaderInput("tex%d" % i, loader.loadTexture("splat/tex%d.jpg" % i))
        root.setShaderInput('alpha012', loader.loadTexture('splat/alpha012.png'))
        root.setShaderInput('alpha345', loader.loadTexture('splat/alpha345.png'))
        root.setShaderInput('alpha678', loader.loadTexture('splat/alpha678.png'))
        root.setShaderInput('alpha9', loader.loadTexture('splat/alpha9.png'))
        root.setShaderInput('tiling', 128)
        root.setShaderInput('customLight', self.dlight)

Note that self.terrain is a GeoMipTerrain node constructed from a heightmap.

Vertex shader:

#version 330
// Uniform inputs
uniform mat4 p3d_ModelViewProjectionMatrix;
uniform mat4 p3d_ModelViewMatrix;
uniform mat3 p3d_NormalMatrix;

// Vertex inputs
in vec4 p3d_Vertex;
in vec3 p3d_Normal;
in vec2 p3d_MultiTexCoord0;
// Output things to fragment shader
out vec2 texCoord; // texture coordinate
out vec3 vpos; // vertex position in relation to camera???
out vec3 norm; // vertex normal in camera space???
void main() {
    gl_Position = p3d_ModelViewProjectionMatrix * p3d_Vertex;
    texCoord = p3d_MultiTexCoord0;
    vpos = vec3(p3d_ModelViewMatrix * p3d_Vertex);
    norm = normalize(p3d_NormalMatrix * p3d_Normal);

Fragment shader:

#version 330 compatibility

// Texture layer alpha textures
uniform sampler2D alpha012;
uniform sampler2D alpha345;
uniform sampler2D alpha678;
uniform sampler2D alpha9;

// Texture layers
uniform sampler2D tex0;
uniform sampler2D tex1;
uniform sampler2D tex2;
uniform sampler2D tex3;
uniform sampler2D tex4;
uniform sampler2D tex5;
uniform sampler2D tex6;
uniform sampler2D tex7;
uniform sampler2D tex8;
uniform sampler2D tex9;

// Tiling multiplier
uniform float tiling = 4.0;

// Custom light shader input, works on all light types except AmbientLight
uniform gl_LightSourceParameters customLight;

uniform struct {
  // Sum of all ambient lights
  vec4 ambient;
} p3d_LightModel;

struct P3DMaterial {
  vec4 ambient;
  vec4 diffuse;
  vec3 specular;
  float shininess;

uniform P3DMaterial p3d_Material;

in vec3 vpos;
in vec3 norm;

in vec4 texCoord;

//ApplyShading(vec4 texColor, )

void main(void)
    // This is the code I was able to understand, texture splatting
    vec4 a0   = texture2D(alpha012, texCoord.xy);
    vec4 a1   = texture2D(alpha345, texCoord.xy);
    vec4 a2   = texture2D(alpha678, texCoord.xy);
    vec4 a3   = texture2D(alpha9, texCoord.xy);
    vec4 rgb0    = texture2D(tex0, texCoord.xy * tiling); // Tile
    vec4 rgb1    = texture2D(tex1,  texCoord.xy * tiling); // Tile
    vec4 rgb2    = texture2D(tex2, texCoord.xy * tiling); // Tile
    vec4 rgb3    = texture2D(tex3, texCoord.xy * tiling); // Tile
    vec4 rgb4    = texture2D(tex4, texCoord.xy * tiling); // Tile
    vec4 rgb5    = texture2D(tex5, texCoord.xy * tiling); // Tile
    vec4 rgb6    = texture2D(tex6, texCoord.xy * tiling); // Tile
    vec4 rgb7    = texture2D(tex7, texCoord.xy * tiling); // Tile
    vec4 rgb8    = texture2D(tex8, texCoord.xy * tiling); // Tile
    vec4 rgb9    = texture2D(tex9, texCoord.xy * tiling); // Tile
    rgb0 *= a0.r;
    rgb1 = mix(rgb0, rgb1, a0.g);
    rgb2 = mix(rgb1, rgb2, a0.b);
    rgb3 = mix(rgb2, rgb3, a1.r);
    rgb4 = mix(rgb3, rgb4, a1.g);
    rgb5 = mix(rgb4, rgb5, a1.b);
    rgb6 = mix(rgb5, rgb6, a2.r);
    rgb7 = mix(rgb6, rgb7, a2.g);
    rgb8 = mix(rgb7, rgb8, a2.b);
    vec4 texDiffuse = mix(rgb8, rgb9, a3.r);
    // Here is the Pong-shading thing, I don't understand what this code does :( pls halp
    gl_FragColor = p3d_LightModel.ambient * p3d_Material.ambient;
    vec3 diff = customLight.position.xyz - vpos * customLight.position.w;
    vec3 L = normalize(diff);
    vec3 E = normalize(-vpos);
    vec3 R = normalize(-reflect(L, norm));
    vec4 diffuse = clamp(texDiffuse * p3d_Material.diffuse * customLight.diffuse * max(dot(norm, L), 0), 0, 1);
    vec4 specular = vec4(p3d_Material.specular, 1) * customLight.specular * pow(max(dot(R, E), 0), p3d_Material.shininess);
    float spotEffect = dot(normalize(customLight.spotDirection), -L);    
    if (spotEffect > customLight.spotCosCutoff) {
        gl_FragColor += (diffuse + specular) / (customLight.constantAttenuation + customLight.linearAttenuation * length(diff) + customLight.quadraticAttenuation * length(diff) * length(diff));

I actually managed to get this voodoo fragment shader working with an trick: I clamped the specular adjustment between 0 and 1:

vec4 specular = clamp(vec4(p3d_Material.specular, 1) * customLight.specular * pow(max(dot(R, E), 0), p3d_Material.shininess), 0, 1);

Originally specular adjustment was not clamped, and because of how the normals in GeoMipTerrain mesh are calculated, the end result would sometimes be negative. Thus when a negative specular is added to diffuse, it would sum up to the total color being 0 ie. black.

Now here is a comparison.

Terrain shaded with my shader (10 textures, 4 alpha maps, 128 unit tiling):

Terrain shaded with setShaderAuto() without textures (for reference!):

That being said, I do not understand the math behind calculating each component of the color, so I can’t really verify that this is the correct solution.

I’d like some comments on whether the shading in the pictures looks okay. if anybody got better solutions that provide better results, then please do inform me.

I will still have to compare a terrain shaded with this texture to the terrain shaded with setShaderAuto() and see if they look similar enough.