I am attempting to address the age old issue of model vs world space coordinates when adding light to a scene with transformed models.
I have a house model with two instances. a skull, a single point light in my scene as well as some terrain and a skybox. I noticed that when applying some rotations to my models, the illumination provided by the point light was rotating with it. After some reading it became obvious that I am doing all of my computations in my shaders in model space but my light position/directions are in world space.
So, I realized I needed to uniform my space for my calculations and I think it makes sense to keep the shaders in model space and convert the light components from world to model.
When I don't do the transformation it looks almost right (though not perfect which I'm betting is b/c of the different spaces). I render a small lightbulb model at the location of the light and the tops of the houses and the terrain all illuminate as I expect relatively well.
When I do the conversion of the light position back to model space I'm expecting the light to still be illuminating from the lightbulb model. However it gets wonky.
I have no rotations on the models but some minor translations so I'm confused as to why it seems like the light source gets rotated 90 degrees around the x axis...
<model name="blackSmith" mesh="black_smith/">
<transform>
<position x="2" y="1" z="-2"></position>
<rotation x="0" y="0" z="0"></rotation>
<scale x="1" y="1" z="1"></scale>
</transform>
</model>
<model name="blackSmith2" mesh="black_smith/">
<transform>
<position x="-2" y="1" z="-2.5"></position>
<rotation x="0" y="0" z="0"></rotation>
<scale x="1" y="1" z="1"></scale>
</transform>
</model>
I am passing the model-view matrix from my vertex shader into my frag shader then doing an inverse transpose to convert the world space coordinate of the light position into model space. I figured that would be it but obviously I'm either missing something or I've done something wrong.
Here are my shaders (seperated by tags). For this example it is just a point light so u_isPointLight = true
and u_isSpotLight = false
#type vertex
#version 330 core
layout(location = 0) in vec3 a_Position;
layout(location = 1) in vec3 aNormal;
layout(location = 2) in vec2 aTexCoord;
layout(location = 3) in mat4 aInstanceModelMatrix;
out vec3 outNormal;
out vec2 outTexCoord;
out vec3 outPos;
out mat4 outMat;
uniform mat4 u_Projection;
uniform mat4 u_View;
void main() {
vec4 worldPosition = aInstanceModelMatrix * vec4(a_Position, 1.0);
gl_Position = u_Projection * u_View * worldPosition;
outPos = vec3(aInstanceModelMatrix * vec4(a_Position, 1.0));
outNormal = aNormal;
outTexCoord = aTexCoord;
outMat = u_View * aInstanceModelMatrix;
}
#type fragment
#version 330 core
out vec4 color;
in vec3 outNormal; in vec2 outTexCoord; in vec3 outPos;
in mat4 outMat;
uniform bool u_DiffuseTextureValid;
uniform sampler2D u_DiffuseTexture;
uniform vec3 u_DiffuseColour;
uniform vec3 u_LightPosition;
uniform vec3 u_LightAttenuation;
uniform vec4 u_LightParams;
uniform vec3 u_SpotDirection;
uniform bool u_isSpotLight;
uniform bool u_isPointLight;
uniform float u_SpotInnerAngle;
uniform float u_SpotOuterAngle;
uniform vec4 uAmbientLight;
uniform bool u_AmbientTextureValid;
uniform sampler2D u_AmbientTexture;
uniform bool u_SpecularTextureValid;
uniform sampler2D u_SpecularTexture;
uniform vec4 u_Specular;
uniform sampler2D u_BumpTexture;
uniform bool u_BumpTextureValid;
uniform float u_MaterialAlpha;
void main() {
const float kGamma = 0.4545454;
const float kInverseGamma = 2.2;
// WorldSpace to ModelSpace conversion
vec3 lightPosition = (transpose(inverse(outMat)) * vec4(u_LightPosition, 1.0)).xyz;
vec3 norm;
if (u_BumpTextureValid) {
norm = normalize(texture(u_BumpTexture, outTexCoord).xyz);
} else {
norm = normalize(outNormal);
}
vec3 lightDir;
if (u_isPointLight || u_isSpotLight) {
lightDir = normalize(lightPosition - outPos);
} else {
lightDir = normalize(-u_SpotDirection);
}
vec3 viewFragmentDirection = normalize(outPos);
vec3 viewNormal = normalize(outNormal);
float lightDistance = length(lightPosition - outPos);
float attenuation = 1.0;
if (u_isPointLight) {
attenuation = 1.0 / (u_LightAttenuation.x + (lightDistance * u_LightAttenuation.y) + (lightDistance * lightDistance * u_LightAttenuation.z));
}
vec3 diffuse = u_LightParams.w * u_DiffuseColour * max(dot(norm, lightDir), 0.0) * u_LightParams.xyz;
vec3 reflectedLightDirection = reflect(-lightDir, viewNormal);
vec3 specular;
if (u_SpecularTextureValid) {
float spec = pow(max(dot(viewNormal, reflectedLightDirection), 0.0), u_Specular.w);
specular = u_LightParams.xyz * spec * vec3(texture(u_SpecularTexture, outTexCoord));
} else {
float specularStrength = max(0.0, dot(viewFragmentDirection, reflectedLightDirection));
specular = u_LightParams.w * u_Specular.rgb * pow(u_LightParams.w, u_Specular.w);
}
float spotFade = 1.0;
if (u_isSpotLight && u_isPointLight) {
float spotlightTheta = dot(lightDir, normalize(-u_SpotDirection));
spotFade = (u_SpotInnerAngle - spotlightTheta) / (u_SpotOuterAngle - u_SpotInnerAngle);
if (spotlightTheta < u_SpotInnerAngle) {
specular = vec3(0, 0, 0);
diffuse = vec3(0, 0, 0);
}
}
vec4 objectColor;
if (u_DiffuseTextureValid) {
objectColor = texture(u_DiffuseTexture, outTexCoord);
} else {
objectColor = vec4(1.0, 1.0, 1.0, 1.0);
}
objectColor.rgb = pow(objectColor.rgb, vec3(kInverseGamma));
color.a = objectColor.a * u_MaterialAlpha;
vec3 ambient = uAmbientLight.a * uAmbientLight.rgb;
ambient *= attenuation;
diffuse *= attenuation * spotFade;
specular *= attenuation * spotFade;
if (u_DiffuseTextureValid) {
color.rgb = pow(objectColor.rgb + ambient + diffuse + specular, vec3(kGamma));
} else {
color.rgb = pow(objectColor.rgb * (ambient + diffuse) + specular, vec3(kGamma));
}
}
My questions are:
- Am I correctly transforming the light position from world space to model space?
- If I am doing a purely directional light (i.e. u_isSpotLight and u_isPointLight are both false), converting u_SpotDirection to modelspace by the same method should work.... right?
- Assuming 1 is correct, can anyone tell my why the light seems to have gotten the weird rotation?