• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

Shaders Burnt out on this shader error

S

Shifty

Guest
Hey yall, just really need a second (or more) pair of eyes on this because i'm not sure what i'm missing.

Trying to make a 2d directional spotlight effect with a shader. Heres the SpotLight() function that does the lighting calc.
Compile error reads:

Fragment Shader: normal_map_lighting_test at line 80 : 'SpotLight'
Fragment Shader: normal_map_lighting_test at line 80 : ''

Yes! very informative thank you :mad:
Line 80 points to this line:

"vec3 NormalMap = texture2D(normalMap, v_vTexcoord).rgb;"

My expirence with GLSL debugging in GMS tells me that the actual error tends to occur on one of the lines immediately following the one stated in the compile error. However i just can't seem to see whats wrong.

Code:
vec3 SpotLight (in sampler2D normalMap, in vec3 LightPos, in vec4 LightColor, in vec3 ConeDirection) {
    vec4 DiffuseColor = texture2D(gm_BaseTexture, v_vTexcoord);
    vec3 NormalMap = texture2D(normalMap, v_vTexcoord).rgb;   
    NormalMap.g = 1.0 - NormalMap.g;
    
    vec3 coneDirection = normalize(ConeDirection);
    vec3 SurfaceToLight = normalize(LightPos);
    
    float LightToSurfaceAngle = degrees(acos(dot(-SurfaceToLight, coneDirection)));
    
    vec3 LightDir = vec3(LightPos.xy - (Fragcoord.xy / Resolution.xy), LightPos.z);   
    LightDir.x *= Resolution.x / Resolution.y;
    
    float D = length(LightDir);
    vec3 N = normalize(NormalMap * 2.0 - 1.0);
    vec3 L = normalize(LightDir);
    
    vec3 Diffuse = (LightColor.rgb * LightColor.a) * max(dot(N , L), 0.0);
    vec3 Ambient = AmbientColor.rgb* AmbientColor.a;
    if(LightToSurfaceAngle > ConeAngle)
    {
        Attenuation = 0.0;
    } else {
        float Attenuation = 1.0 / (Falloff.x + (Falloff.y*D) + (Falloff.z*D*D) );
    }   
    vec3 Intensity = Ambient + Diffuse * Attenuation;
    vec3 Color = DiffuseColor.rgb * Intensity;
    return Color;
}

Heres the Main() of the shader so you can see how SpotLight() is being used.

This is set up to handle multiple lights and does a single pass for each object in the scene. Before judging this lighting is very static at the moment for testing, its ugly and I hate it :D
Code:
void main()
{   
    LightPosition[0] = LightPos1;
    LightPosition[1] = LightPos2;
    
    vec4 DiffuseColor = texture2D(gm_BaseTexture, v_vTexcoord);
    vec3 sum = vec3(0.0);
    for(int i=0; i < NUMBER_LIGHTS; i++){   
        if (i == 0) {
            sum += LightCalc(u_normals, LightPosition[i], LightColor);
        }
        else {                                                                   
            sum += SpotLight(u_normals, LightPosition[i], vec4(1.0, 0.3, 0.3, 1.0), ConeDirection);
        }
    
    }   
    gl_FragColor = vec4(sum, DiffuseColor.a);           
}

Any advice on glsl es is welcome as well. Thanks for looking.
 
M

MishMash

Guest
Few things you can try:

1) Remove the in qualifier on the function parameters, you don't need this for a function call in GLSL ES. Generally, in and out are only used in GLSL in order for the GPU to know which values are being pulled from global memory, and which are being written back. Within the execution context of a shader (i.e. within a function), these aren't needed as the memory is already present. The only time they are essential in GLSL is in the main scope function of a shader, because that determines how it interfaces with other shaders and the memory system as a whole.

2) Given you are only dealing with one normal map, and it is likely going to be a uniform variable, you don't necessarily need to pass its reference around inside functions. It is simply more efficient just to reference the uniform directly. (There can also be some iffy-ness when using sampler2D as a parameter, because the compiler can interpret this as you wanting to create a copy of a sampler, which does not work in the local scope of a function because they take up more memory than that. Best to try and always work with them globally where possible.)

-- A general suggestions:
- This shader will begin to get very inefficient once you start dealing with large quantities of lights. There are a number of reasons for this: #1 you will end up having to pass a lot of data to the shader. #2 You will have to specify a fixed number of lights because you cannot use dynamically sized for loops in shaders in GM. #3 Every pixel will process data from lights that may not even affect that pixel.

A better approach is to use deferred rendering. You can keep your lighting functions the same, but the difference being that you run one shader call per light, and accumulate the results using additive blending outside of the shader. Instead, each light is drawn as a sprite, with the shader enabled. You can then simply draw a sprite that is the size of light, meaning only the pixels within that light are ever processed. This is far more efficient. It also means offscreen lights automatically do not get processed, and you can essentially have a huge number of lights, without having to do complex calculations of determining which ones are near. In practise, if you had 100 lights, it is also likely to be the case that a single pixel is only affected by 5 or so lights, whereas if you wanted 100 lights using forward rendering (as you have now), each pixel would have to do lighting calculations on 100 lights.
I also find deferred rendering more simple to implement, as you can add new types of lights without touching the existing shaders, as each type of light would have its own shader :)
 
S

Shifty

Guest
Thanks for feedback MishMash. I implemented some changes based on your suggestions but am still getting the same error. So let me first response to what you said.

1) ahh gotcha, I had originally written that function without the "in" identifiers but after seeing so many glsl functions using it. I experimented and didn't notice a difference so thanks for clearing that up for me.

2) very good point I have updated it so it uses global variations of NormalMap & DiffuseColor and just calling by reference which i am seeing more efficient draw time.

> general suggestions:
I have done some research on deferred rendering and I plan to experiment with both that and culling draw objects.

as to deferred rendering (which sounds way better in the long run), am I tracking correctly here?

say you have two lights
-for a simplistic flashlight, draw a triangle sprite with a controller using the flashlight shader. (is sprite alpha taken into account?)
-for a streetlight, draw a circular sprite with the controller using a streetlight shader.
The controller would draw all lighting objects using gpu_set_blendmode(bm_add);

Back to the original problem:

Compile error hasn't changed. Also found that my function didn't like being passed a raw vec4 rgba, assigned it to a variable and passing that in appeased it.

Updated SpotLight()
Code:
vec3 SpotLight (vec3 LightPos, vec4 LightColor, vec3 ConeDirection) {  
    vec3 coneDirection = normalize(ConeDirection);
    vec3 SurfaceToLight = normalize(LightPos);
   
    float LightToSurfaceAngle = degrees(acos(dot(-LightPos, coneDirection)));
   
    vec3 LightDir = vec3(LightPos.xy - (Fragcoord.xy / Resolution.xy), LightPos.z);  
    LightDir.x *= Resolution.x / Resolution.y;
   
    float D = length(LightDir);
    vec3 N = normalize(NormalMap * 2.0 - 1.0);
    vec3 L = normalize(LightDir);
   
    vec3 Diffuse = (LightColor.rgb * LightColor.a) * max(dot(N , L), 0.0);
    vec3 Ambient = AmbientColor.rgb* AmbientColor.a;
    if(LightToSurfaceAngle > ConeAngle)
    {
        Attenuation = 0.0;
    } else {
        float Attenuation = 1.0 / (Falloff.x + (Falloff.y*D) + (Falloff.z*D*D) );
    }
    vec3 Intensity = Ambient + Diffuse * Attenuation;
    vec3 Color = DiffuseColor.rgb * Intensity;
    return Color;
}
I'm gonna re-go through my math on paper and see where that takes me.
 
M

MishMash

Guest
One thing that could be an issue: as you didn't post the shader, I can't really know, but if you have any constants or uniforms which share the same identifier as those in the function parameters or body, then that would cause the code that is being compiled to get mis-interpreted by the compiler. Errors are not necessarily where they say they are, so its always useful to try and give as much information as possible.

Are you now at a point where your shader compiles? (You are compiling under GLSL ES right?)

To answer your questions:
say you have two lights
-for a simplistic flashlight, draw a triangle sprite with a controller using the flashlight shader. (is sprite alpha taken into account?)
-for a streetlight, draw a circular sprite with the controller using a streetlight shader.
The controller would draw all lighting objects using gpu_set_blendmode(bm_add);
- So for the triangle sprite, two things will happen: First of all, every pixel in that sprite will be processed in the fragment shader. (Infact, as all sprites are built up of two triangles internally when calling draw_sprite, it will run the fragment shader for every pixel in that quad). The sprite alpha will only be taken into consideration if you actively choose to use it. In your current shader main() function, you do fetch the base texture (from gm_BaseTexture) and use that alpha as the final alpha. (Though i know in this case, its the underlying surface. When doing it with deferred, this gm_BaseTexture will be the sprite you are using). Often times however, what you can do is also use the alpha from the spotlight function (you can get this by returning a vec4, where the alpha component is just the intensity value. Though i guess if you use additive, this wont really matter anyway as black is treated as nothing.

Another little trick you can do as a micro optimisation is to discard transparent pixels straight away: so after you fetch the sprite texture, but before you do the spotlight function you can discard pixels with alpha=0:
Code:
vec4 SpriteColor = texture2D(gm_BaseTexture, v_vTexcoord);
if( SpriteColor.a <= 0.0 ){
   discard;
}
But yes, your understanding is completely correct in that each light, you are setting the shader and drawing a sprite. Given that your shader controls the brightness, you do not need to actually have anything in the sprite, though I personally do tend to also use a sprite alpha mask to control the shape of the light, and multiply the end result by it, as I believe this makes it look better :)
 
Top