So, I've begun a quest to implement awesome lighting without using OpenGL's lighting system. I've successfully implemented Phong diffuse lighting. Specular is giving me trouble.
I need to know what spaces the OpenGL constants I'm using occupy, because it seems they are mis-transformed, and that it results in lighting glitches.
I have confirmed that there is no problem with my C++ code by successfully loading and running a Phong diffuse shader. The C++ code may, however, be passing invalid data to the shaders, which is one of the things I'm worried about. I will paste my shaders with comments, as well as all C++ code directly pertaining to the shaders (although I'm 90% sure the problem is in the shaders).
In these images, the light sources are large points, and the axes are shown.
The lights are rotating at y = 0 around an icosphere.
Here's the diffuse, so you get an idea what the model is...
Note I haven't done per-pixel yet...
Here's the Fresnel lighting, as shown in source...
Note how the lit faces are facing the light, not somewhere between the light and the camera
Here's the Blinn-Phong, which I had to multiply by 30...
Note again how the lit faces point towards the light source, and also the fact that I had to multiply the Specular factor (S) by 30 to achieve this
Vertex Shader Source (loaded from "dirlight.vs")
const int MAXLIGHTS = 4;
uniform bool justcolor = false;
uniform int lightcount;
uniform vec4 lightposs[MAXLIGHTS];
uniform vec4 lightdirs[MAXLIGHTS];
uniform vec4 lightdifs[MAXLIGHTS];
uniform vec4 lightambs[MAXLIGHTS];
//diffuse
vec4 D;
//specular, normaldotlight
float S, NdotL[MAXLIGHTS];
//normal, eyevec, lightvecs, halfvecs
vec3 N, E, L[MAXLIGHTS], H[MAXLIGHTS];
void main() {
//if(lightcount > MAXLIGHTS) lightcount = MAXLIGHTS;
D = vec4(0.0, 0.0, 0.0, 0.0);
S = 0.0;
N = gl_Normal;
E = normalize(vec3(-gl_Vertex));
for(int i = 0; i < lightcount; i++)
{
//calculating direction to light source
L[i] = normalize(vec3(lightposs[i] - gl_Vertex));
//normal dotted with direction to light source
NdotL[i] = max(dot(N, L[i]), 0.0);
//diffuse term, works just fine
D += gl_Color * lightdifs[i] * NdotL[i];
if(NdotL[i] >= 0.0)
{
//halfvector = normalize(lightdir + eyedir)
H[i] = normalize(L[i] + E);
//Blinn-Phong, only lights up faces whose normals
//point directly to the light source for some reason...
//S += max(0.0, dot(H[i], N));
//Fresnel, lights up more than Blinn-Phong
//but the faces still point directly to the light source,
//not somewhere between the lightsource and myself, like they should.
S += pow(max(0.0, dot(reflect(L[i], N), E)), 50.0);
}
else
{
H[i] = vec3(0.0, 0.0, 0.0);
}
}
//currently only showing specular. To show diffuse add D.
gl_FrontColor = justcolor ? gl_Color : vec4(S * 0.3, S * 0.3, S * 0.3, 1.0);
gl_Position = ftransform();
}
Fragment Shader Source (loaded from "dirlight.fs")
void main()
{
gl_FragColor = gl_Color;
}
Excerpt from C++ main initialization...
//class program manages shaders
Program shaders = Program();
//attach a vertex shader, compiled from source in dirlight.vs
shaders.addShaderFile(GL_VERTEX_SHADER, "dirlight.vs");
//attach a fragment shader compiled from source in dirlight.fs
shaders.addShaderFile(GL_FRAGMENT_SHADER, "dirlight.fs");
//link program
shaders.link();
//use program
shaders.use();
//Program::getUniformLoc(const char* name) grabs the location
//of the uniform specified
GLint sTime = shaders.getUniformLoc("time");
GLint lightcount = shaders.getUniformLoc("lightcount");
GLint lightdir = shaders.getUniformLoc("lightdirs");
GLint lightdif = shaders.getUniformLoc("lightdifs");
GLint lightamb = shaders.getUniformLoc("lightambs");
GLint lightpos = shaders.getUniformLoc("lightposs");
GLint justcolor = shaders.getUniformLoc("justcolor");
glUniform1i(justcolor, 0);
glUniform1i(lightcount, 2);
//diffuse light colors
GLfloat lightdifs[] = {1.f, 1.f, 1.f, 1.f,
1.f, 1.f, 1.f, 1.f};
glUniform4fv(lightdif, 2, lightdifs);
glUniform4f(lightamb, 0.4f, 0.4f, 0.4f, 1.f);
Excerpt from C++ main loop...
//My lights rotate around the origin, where I have placed an icosphere
GLfloat lightposs[] = {-4 * sinf(newTime), lighth, -4 * cosf(newTime), 0.0f,
-4 * sinf(newTime + M_PI), lighth, -4 * cosf(newTime + M_PI), 0.0f};
glUniform4fv(lightpos, 2, lightposs);
There are few important things missing from your code. First you should transform vertex position and normal into eye space. Lighting calculations are easiest there. Vertex position transforms using the modelview matrix, the normals transform with the transposed inverse of the modelview. Usually light positions are in world coordinates, so it makes sense to supply an additional matrix from world to eye coordinates.
Related
I am currently learning how to map 2d textures to 3d objects using GLSL. I have a main.cpp, fragment shader, and vertex shader to achieve this as well as a Sphere.obj I made using Maya and some PNG images.
I just created a basic sphere poly model in Maya then exported it as a ".obj".
My fragment shader code is listed below for reference:
#version 410
// Inputs from application.
// Generally, "in" like the eye and normal vectors for things that change frequently,
// and "uniform" for things that change less often (think scene versus vertices).
in vec3 position_eye, normal_eye;
uniform mat4 view_mat;
// This light setup would usually be passed in from the application.
vec3 light_position_world = vec3 (10.0, 25.0, 10.0);
vec3 Ls = vec3 (1.0, 1.0, 1.0); // neutral, full specular color of light
vec3 Ld = vec3 (0.8, 0.8, 0.8); // neutral, lessened diffuse light color of light
vec3 La = vec3 (0.12, 0.12, 0.12); // ambient color of light - just a bit more than dk gray bg
// Surface reflectance properties for Phong or Blinn-Phong shading models below.
vec3 Ks = vec3 (1.0, 1.0, 1.0); // fully reflect specular light
vec3 Kd = vec3 (0.32, 0.18, 0.5); // purple diffuse surface reflectance
vec3 Ka = vec3 (1.0, 1.0, 1.0); // fully reflect ambient light
float specular_exponent = 400.0; // specular 'power' -- controls "roll-off"
// These come from the VAO for texture coordinates.
in vec2 texture_coords;
// And from the uniform outputs for the textures setup in main.cpp.
uniform sampler2D texture00;
uniform sampler2D texture01;
out vec4 fragment_color; // color of surface to draw
void main ()
{
// Ambient intensity
vec3 Ia = La * Ka;
// These next few lines sample the current texture coord (s, t) in texture00 and 01 and mix.
vec4 texel_a = texture (texture00, fract(texture_coords*2.0));
vec4 texel_b = texture (texture01, fract(texture_coords*2.0));
//vec4 mixed = mix (texel_a, texel_b, texture_coords.x);
vec4 mixed = mix (texel_a, texel_b, texture_coords.x);
Kd.x = mixed.x;
Kd.y = mixed.y;
Kd.z = mixed.z;
// Transform light position to view space.
// Vectors here are appended with _eye as a reminder once in view space versus world space.
// "Eye" is used instead of "camera" since reflectance models often phrased that way.
vec3 light_position_eye = vec3 (view_mat * vec4 (light_position_world, 1.0));
vec3 distance_to_light_eye = light_position_eye - position_eye;
vec3 direction_to_light_eye = normalize (distance_to_light_eye);
// Diffuse intensity
float dot_prod = dot (direction_to_light_eye, normal_eye);
dot_prod = max (dot_prod, 0.0);
vec3 Id = Ld * Kd * dot_prod; // final diffuse intensity
// Specular is view dependent; get vector toward camera.
vec3 surface_to_viewer_eye = normalize (-position_eye);
// Phong
//vec3 reflection_eye = reflect (-direction_to_light_eye, normal_eye);
//float dot_prod_specular = dot (reflection_eye, surface_to_viewer_eye);
//dot_prod_specular = max (dot_prod_specular, 0.0);
//float specular_factor = pow (dot_prod_specular, specular_exponent);
// Blinn
vec3 half_way_eye = normalize (surface_to_viewer_eye + direction_to_light_eye);
float dot_prod_specular = max (dot (half_way_eye, normal_eye), 0.0);
float specular_factor = pow (dot_prod_specular, specular_exponent);
// Specular intensity
vec3 Is = Ls * Ks * specular_factor; // final specular intensity
// final color
fragment_color = vec4 (Is + Id + Ia, 1.0);
}
I type in the following command into the terminal to run my package:
./go fs.glsl vs.glsl Sphere.obj image.png image2.png
I am trying to map a world map.jpg to my sphere using this method and ignore the 2nd image input. But it won't run. Can someone tell me what I need to comment out in my fragment shader to ignore the second texture input so my code will run?
PS: How would I go about modifying my fragment shader to implement various types of 'tiling'? I'm a bit lost on this as well. Any examples or tips are appreciated.
Here is the texture portion of my main.cpp code.
// load textures
GLuint tex00;
int tex00location = glGetUniformLocation (shader_programme, "texture00");
glUniform1i (tex00location, 0);
glActiveTexture (GL_TEXTURE0);
assert (load_texture (argv[4], &tex00));
//assert (load_texture ("ship.png", &tex00));
GLuint tex01;
int tex01location = glGetUniformLocation (shader_programme, "texture01");
glUniform1i (tex01location, 1);
glActiveTexture (GL_TEXTURE1);
assert (load_texture (argv[5], &tex01));
/*---------------------------SET RENDERING DEFAULTS---------------------------*/
// Choose vertex and fragment shaders to use as well as view and proj matrices.
glUniformMatrix4fv (view_mat_location, 1, GL_FALSE, view_mat.m);
glUniformMatrix4fv (proj_mat_location, 1, GL_FALSE, proj_mat.m);
// The model matrix stores the position and orientation transformations for the mesh.
mat4 model_mat;
model_mat = translate (identity_mat4 () * scale(identity_mat4(), vec3(0.5, 0.5, 0.5)), vec3(0, -0.5, 0)) * rotate_y_deg (identity_mat4 (), 90 );
// Setup basic GL display attributes.
glEnable (GL_DEPTH_TEST); // enable depth-testing
glDepthFunc (GL_LESS); // depth-testing interprets a smaller value as "closer"
glEnable (GL_CULL_FACE); // cull face
glCullFace (GL_BACK); // cull back face
glFrontFace (GL_CCW); // set counter-clock-wise vertex order to mean the front
glClearColor (0.1, 0.1, 0.1, 1.0); // non-black background to help spot mistakes
glViewport (0, 0, g_gl_width, g_gl_height); // make sure correct aspect ratio
I am currently using this glsl file to handle lighting for a 3d object that I am trying to display. I am not sure what values I need to put in for light_position_world, Ls, Ld, La, Ks, Kd, Ka, Ia and fragment_color. The scene I am trying to illuminate is centered at (427, 385, 89) roughly. I dont need it to be perfect but I need some values that will let me see my design on screen so that I can manipulate them and learn how this all works. Any additional tips or explanation would be much appreciated. Thanks!
#version 410
in vec3 position_eye, normal_eye;
uniform mat4 view_mat;
// fixed point light properties
vec3 light_position_world = vec3 (427.029, 385.888, 0);
vec3 Ls = vec3 (1.0f, 0.0f, 0.0f);
vec3 Ld = vec3 (1.0f, 0.0f, 0.0f);
vec3 La = vec3 (1.0f, 0.2f, 0.0f);
// surface reflectance
vec3 Ks = vec3 (1.0f, 1.0f, 1.0f);
vec3 Kd = vec3 (1.0f, 0.8f, 0.72f);
vec3 Ka = vec3 (1.0f, 1.0f, 1.0f);
float specular_exponent = 10.0; // specular 'power'
out vec4 fragment_colour; // final colour of surface
void main () {
// ambient intensity
vec3 Ia = vec3 (0, 0, 0);
// diffuse intensity
// raise light position to eye space
vec3 light_position_eye = light_position_world; //vec3 (view_mat * vec4 (light_position_world, 1.0));
vec3 distance_to_light_eye = light_position_eye - position_eye;
vec3 direction_to_light_eye = normalize (distance_to_light_eye);
float dot_prod = dot (direction_to_light_eye, normal_eye);
dot_prod = max (dot_prod, 0.0);
vec3 Id = Ld * Kd * dot_prod; // final diffuse intensity
// specular intensity
vec3 surface_to_viewer_eye = normalize (-position_eye);
// blinn
vec3 half_way_eye = normalize (surface_to_viewer_eye + direction_to_light_eye);
float dot_prod_specular = max (dot (half_way_eye, normal_eye), 0.0);
float specular_factor = pow (dot_prod_specular, specular_exponent);
vec3 Is = Ls * Ks * specular_factor; // final specular intensity
// final colour
fragment_colour = vec4 (255, 25, 25, 0);
}
There are a few problems with your code.
1) Assuming, light_position_world is the position of the light in world space, the light is below your scene. So the scene won't be illuminated from above.
2) Assuming, *_eye means a coordinate in eye space and *_world is a coordinate in world space, you may not interchange between those spaces by simply assigning vectors. You have to use a view matrix and it's inverse view matrix to go from world to eye space and from eye space to world space respectivly.
3) The output color of the shader, fragment_colour, is always set to a dark red-ish color. So the compiler will just leave out all the lighting calculations. You have to use something like this: fragment_colour = Ia + Id * material + Is * material, where material is the color of your material - e.g. gray for metal.
It seems like you don't understand the underlying basics. I suggest you read a few articles or tutorials about lighting and transformation/maths in OpenGL.
If you have consumed a fair bit of literature, experiment with your code. Try out, what different calculations do and how they influence the end product. You won't get 100% physically accurate lighting anyways, so there's nothing to go wrong.
I'm having some issues with my specular lighting, I have ambient and diffuse but I am now looking at specular to try make a nice looking model.
I have the following in my vertexShader:
#version 330
layout (location = 0) in vec3 Position;
layout (location = 1) in vec3 Normal;
out vec4 Colour0;
// Transforms
uniform mat4 gModelToWorldTransform;
uniform mat4 gWorldToViewToProjectionTransform;
// Ambient light parameters
uniform vec3 gAmbientLightIntensity;
// Directional light parameters
uniform vec3 gDirectionalLightIntensity;
uniform vec3 gDirectionalLightDirection;
// Material constants
uniform float gKa;
uniform float gKd;
uniform float gKs;
uniform float gKsStrength;
void main()
{
// Transform the vertex from local space to homogeneous clip space
vec4 vertexPositionInModelSpace = vec4(Position, 1);
vec4 vertexInWorldSpace = gModelToWorldTransform * vertexPositionInModelSpace;
vec4 vertexInHomogeneousClipSpace = gWorldToViewToProjectionTransform * vertexInWorldSpace;
gl_Position = vertexInHomogeneousClipSpace;
// Calculate the directional light intensity at the vertex
// Find the normal in world space and normalise it
vec3 normalInWorldSpace = (gModelToWorldTransform * vec4(Normal, 0.0)).xyz;
normalInWorldSpace = normalize(normalInWorldSpace);
// Calculate the ambient light intensity at the vertex
// Ia = Ka * ambientLightIntensity
vec4 ambientLightIntensity = gKa * vec4(gAmbientLightIntensity, 1.0);
// Setup the light direction and normalise it
vec3 lightDirection = normalize(-gDirectionalLightDirection);
//lightDirection = normalize(gDirectionalLightDirection);
// Id = kd * lightItensity * N.L
// Calculate N.L
float diffuseFactor = dot(normalInWorldSpace, lightDirection);
diffuseFactor = clamp(diffuseFactor, 0.0, 1.0);
// N.L * light source colour * intensity
vec4 diffuseLightIntensity = gKd * vec4(gDirectionalLightIntensity, 1.0f) * diffuseFactor;
vec3 lightReflect = normalize(reflect(gDirectionalLightDirection, Normal));
//Calculate the specular light intensity at the vertex
float specularFactor = dot(normalInWorldSpace, lightReflect);
specularFactor = pow(specularFactor, gKsStrength);
vec4 specularLightIntensity = gKs * vec4(gDirectionalLightIntensity, 1.0f) * specularFactor;
// Final vertex colour is the product of the vertex colour
// and the total light intensity at the vertex
vec4 colour = vec4(0.0, 1.0, 0.0, 1.0);
Colour0 = colour * (ambientLightIntensity + diffuseLightIntensity + specularLightIntensity);
}
Then in my main.cpp I have the some code to try and get this working together, the specular light is definitely doing something, only, rather than making the model look shiny, it seems to intensify the light to the point where I can't see any details.
I create the following variables:
// Lighting uniforms location
GLuint gAmbientLightIntensityLoc;
GLuint gDirectionalLightIntensityLoc;
GLuint gDirectionalLightDirectionLoc;
GLuint gSpecularLightIntensityLoc;
// Materials uniform location
GLuint gKaLoc;
GLuint gKdLoc;
GLuint gKsLoc;
GLuint gKsStrengthLoc;
I then set my variables like so in the renderSceneCallBack() function which is called in the main:
// Set the material properties
glUniform1f(gKaLoc, 0.2f);
glUniform1f(gKdLoc, 0.9f);
glUniform1f(gKsLoc, 0.5f);
glUniform1f(gKsStrengthLoc, 0.5f);
I then create a initLights() function which I would like to handle all lighting, this is also called in the main:
static void initLights()
{
// Setup the ambient light
vec3 ambientLightIntensity = vec3(0.2f, 0.2f, 0.2f);
glUniform3fv(gAmbientLightIntensityLoc, 1, &ambientLightIntensity[0]);
// Setup the direactional light
vec3 directionalLightDirection = vec3(0.0f, 0.0f, -1.0f);
normalize(directionalLightDirection);
glUniform3fv(gDirectionalLightDirectionLoc, 1, &directionalLightDirection[0]);
vec3 directionalLightIntensity = vec3(0.8f, 0.8f, 0.8f);
glUniform3fv(gDirectionalLightIntensityLoc, 1, &directionalLightIntensity[0]);
//Setup the specular Light
vec3 specularLightIntensity = vec3(0.5f, 0.5f, 0.5f);
glUniform3fv(gSpecularLightIntensityLoc, 1, &specularLightIntensity[0]);
}
Can anyone see what I might be doing wrong, I could have some of the calculatiuons wrong and I just don't see it. Both the ambient/diffuse lighting are working correctly. This photo illustrates whats currently happening, ambient on the left, diffuse in the middle and specular with strength set to 30 on the right.
Answer
I forgot to pass this value into the main:
gKsStrengthLoc = glGetUniformLocation(shaderProgram, "gKsStrength");
//assert(gDirectionalLightDirectionLoc != 0xFFFFFFFF);
Everything works now using the answer selected
Your value for gKsStrength looks way too small:
glUniform1f(gKsStrengthLoc, 0.5f);
This value controls how shiny the object is, with higher values making it more shiny. This makes sense if you look at the calculation in the shader:
specularFactor = pow(specularFactor, gKsStrength);
The larger the exponent, the faster the value drops off, which means that the specular highlight becomes more narrow.
Typical values might be something like 10.0f for moderately shiny, 30.0f for quite shiny, and even higher for very shiny materials.
With your value of 0.5f, you get a very wide specular "highlight". Your value for the specular intensity is also fairly high (0.5), so the highlight is going to cover most of the object, and saturate the colors for large parts.
First of all, I'm sorry if the title is misleading but I'm not quite sure how to describe the issue, if it is an issue at all.
I'm vert new to OpenGL, and I have just started to scratch the surface of GLSL following this tutorial.
The main part of the rendering funcion looks like this
GLfloat ambientLight[] = {0.5f, 0.5f, 0.5f, 1.0f};
glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambientLight);
//Add directed light
GLfloat lightColor1[] = {0.5f, 0.5f, 0.5f, 1.0f}; //Color (0.5, 0.2, 0.2)
//Coming from the direction (-1, 0.5, 0.5)
GLfloat lightPos1[] = { 40.0 * cos((float) elapsed_time / 500.0) , 40.0 * sin((float) elapsed_time / 500.0), -20.0f, 0.0f};
glLightfv(GL_LIGHT0, GL_DIFFUSE, lightColor1);
glLightfv(GL_LIGHT0, GL_POSITION, lightPos1);
glPushMatrix();
glTranslatef(0,0,-50);
glColor3f(1.0, 1.0, 1.0);
glRotatef( (float) elapsed_time / 100.0, 0.0,1.0,0.0 );
glUseProgram( shaderProg );
glutSolidTeapot( 10 );
glPopMatrix();
Where "shaderProg" is a shader program consisting of a vertex shader
varying vec3 normal;
void main(void)
{
normal = gl_Normal;
gl_Position = ftransform();
}
And a fragment shader
uniform vec3 lightDir;
varying vec3 normal;
void main() {
float intensity;
vec4 color;
intensity = dot(vec3(gl_LightSource[0].position), normalize(normal));
if (intensity > 0.95)
color = vec4(1.0,0.5,0.5,1.0);
else if (intensity > 0.5)
color = vec4(0.6,0.3,0.3,1.0);
else if (intensity > 0.25)
color = vec4(0.4,0.2,0.2,1.0);
else
color = vec4(0.2,0.1,0.1,1.0);
gl_FragColor = color;
}
I have two issues.
First is that according to the tutorial the uniform lightDir should be usable, yet I only get results with vec3(gl_LightSource[0].position). Is there any difference between the two?
The other problem is that the setup rotates the light around the teapot differently when using the shader program. Without the shader the light orbits the teapot in the XY axis of the camera. Yet, if the shader is used, the light moves in the XZ axis of the camera. Have I made a mistake? Or have i forgot som translation in the shaders?
Thanks in advance : )
First is that according to the tutorial the uniform lightDir should be
usable, yet I only get results with vec3(gl_LightSource[0].position).
Is there any difference between the two?
That tutorial uses lightDir as a uniform variable. You have to set that yourself. via some glUniform call. If it is the same or not will depend on what exactly you set as the light position here. The lightDir as it is used here is the vector from the surface point you want to shade to the light source. The tutorial uses a directional light, so the light direction is the same everywhere in the scene and does not really depend on the position of the vertex/fragment. You can do the same with the fixed-function lighting by setting the w component of the light poisition to 0. If you don't do that, the results will be very different.
A side note: The GLSL code in that tutorial is unforunately relying on lots of deprecated features. If you learn GLSL, I would really recommend that you learn modern GL core profile.
lightDir is not a pre-defined uniform. The typical definition for a light direction vector is just a normalized vector to the light position in your shader, which you can easily calculate yourself by normalizing the position vector:
vec3 lightDir = normalize(gl_LightSource[0].position.xyz);
You could also pass it into the shader as a uniform you define yourself. For this approach, you would define the uniform in your fragment shader:
uniform vec3 lightDir;
and then get the uniform location with the glGetUniformLocation() call, and set a value with the glUniform3f() call. So once after linking the shader, you have this:
GLint lightDirLoc = glGetUniformLocation(shaderProg, "lightDir");
and then every time you want to change the light direction to (vx, vy, vz):
glUniform3f(lightDirLoc, vx, vy, vz);
For the second part of your question: The reason you get different behavior for the light position with the fixed pipeline compared to what you get with your own shader is that the fixed pipeline applies the current modelview matrix to the specified light position, which is not done in your shader.
As a number of others already suggested: If you learn OpenGL now, I strongly recommend that you skip the legacy features, which includes the fixed function light source parameters. In this case, you can simply use uniform variables you define yourself, as I already illustrated as an option for the lightDir variable above.
I am trying to implement a simple projective texture mapping approach by using shaders in OpenGL 3+. While there are some examples on the web I am having trouble creating a working example with shaders.
I am actually planning on using two shaders, one which does a normal scene draw, and another for projective texture mapping. I have a function for drawing a scene void ProjTextureMappingScene::renderScene(GLFWwindow *window) and I am using glUseProgram() to switch between shaders. The normal drawing works fine. However, it is unclear to me how I am supposed to render the projective texture on top of an already textured cube. Do I somehow have to use a stencil buffer or a framebuffer object(the rest of the scene should be unaffected)?
I also don't think that my projective texture mapping shaders are correct since the second time I render a cube it shows black. Further, I tried to debug by using colors and only the t component of the shader seems to be non-zero(so the cube appears green). I am overriding the texColor in the fragment shader below just for debugging purposes.
VertexShader
#version 330
uniform mat4 TexGenMat;
uniform mat4 InvViewMat;
uniform mat4 P;
uniform mat4 MV;
uniform mat4 N;
layout (location = 0) in vec3 inPosition;
//layout (location = 1) in vec2 inCoord;
layout (location = 2) in vec3 inNormal;
out vec3 vNormal, eyeVec;
out vec2 texCoord;
out vec4 projCoords;
void main()
{
vNormal = (N * vec4(inNormal, 0.0)).xyz;
vec4 posEye = MV * vec4(inPosition, 1.0);
vec4 posWorld = InvViewMat * posEye;
projCoords = TexGenMat * posWorld;
// only needed for specular component
// currently not used
eyeVec = -posEye.xyz;
gl_Position = P * MV * vec4(inPosition, 1.0);
}
FragmentShader
#version 330
uniform sampler2D projMap;
uniform sampler2D gSampler;
uniform vec4 vColor;
in vec3 vNormal, lightDir, eyeVec;
//in vec2 texCoord;
in vec4 projCoords;
out vec4 outputColor;
struct DirectionalLight
{
vec3 vColor;
vec3 vDirection;
float fAmbientIntensity;
};
uniform DirectionalLight sunLight;
void main (void)
{
// supress the reverse projection
if (projCoords.q > 0.0)
{
vec2 finalCoords = projCoords.st / projCoords.q;
vec4 vTexColor = texture(gSampler, finalCoords);
// only t has non-zero values..why?
vTexColor = vec4(finalCoords.s, finalCoords.t, finalCoords.r, 1.0);
//vTexColor = vec4(projCoords.s, projCoords.t, projCoords.r, 1.0);
float fDiffuseIntensity = max(0.0, dot(normalize(vNormal), -sunLight.vDirection));
outputColor = vTexColor*vColor*vec4(sunLight.vColor * (sunLight.fAmbientIntensity + fDiffuseIntensity), 1.0);
}
}
Creation of TexGen Matrix
biasMatrix = glm::mat4(0.5f, 0, 0, 0.5f,
0, 0.5f, 0, 0.5f,
0, 0, 0.5f, 0.5f,
0, 0, 0, 1);
// 4:3 perspective with 45 fov
projectorP = glm::perspective(45.0f * zoomFactor, 4.0f / 3.0f, 0.1f, 1000.0f);
projectorOrigin = glm::vec3(-3.0f, 3.0f, 0.0f);
projectorTarget = glm::vec3(0.0f, 0.0f, 0.0f);
projectorV = glm::lookAt(projectorOrigin, // projector origin
projectorTarget, // project on object at origin
glm::vec3(0.0f, 1.0f, 0.0f) // Y axis is up
);
mModel = glm::mat4(1.0f);
...
texGenMatrix = biasMatrix * projectorP * projectorV * mModel;
invViewMatrix = glm::inverse(mModel*mModelView);
Render Cube Again
It is also unclear to me what the modelview of the cube should be? Should it use the view matrix from the slide projector(as it is now) or the normal view projector? Currently the cube is rendered black(or green if debugging) in the middle of the scene view, as it would appear from the slide projector(I made a toggle hotkey so that I can see what the slide projector "sees"). The cube also moves with the view. How do I get the projection unto the cube itself?
mModel = glm::translate(projectorV, projectorOrigin);
// bind projective texture
tTextures[2].bindTexture();
// set all uniforms
...
// bind VBO data and draw
glBindVertexArray(uiVAOSceneObjects);
glDrawArrays(GL_TRIANGLES, 6, 36);
Switch between main scene camera and slide projector camera
if (useMainCam)
{
mCurrent = glm::mat4(1.0f);
mModelView = mModelView*mCurrent;
mProjection = *pipeline->getProjectionMatrix();
}
else
{
mModelView = projectorV;
mProjection = projectorP;
}
I have solved the problem. One issue I had is that I confused the matrices in the two camera systems (world and projective texture camera). Now when I set the uniforms for the projective texture mapping part I use the correct matrices for the MVP values - the same ones I use for the world scene.
glUniformMatrix4fv(iPTMProjectionLoc, 1, GL_FALSE, glm::value_ptr(*pipeline->getProjectionMatrix()));
glUniformMatrix4fv(iPTMNormalLoc, 1, GL_FALSE, glm::value_ptr(glm::transpose(glm::inverse(mCurrent))));
glUniformMatrix4fv(iPTMModelViewLoc, 1, GL_FALSE, glm::value_ptr(mCurrent));
glUniformMatrix4fv(iTexGenMatLoc, 1, GL_FALSE, glm::value_ptr(texGenMatrix));
glUniformMatrix4fv(iInvViewMatrix, 1, GL_FALSE, glm::value_ptr(invViewMatrix));
Further, the invViewMatrix is just the inverse of the view matrix not the model view (this didn't change the behaviour in my case, since the model was identity, but it is wrong). For my project I only wanted to selectively render a few objects with projective textures. To do this, for each object, I must make sure that the current shader program is the one for projective textures using glUseProgram(projectiveTextureMappingProgramID). Next, I compute the required matrices for this object:
texGenMatrix = biasMatrix * projectorP * projectorV * mModel;
invViewMatrix = glm::inverse(mView);
Coming back to the shaders, the vertex shader is correct except that I re-added the UV texture coordinates (inCoord) for the current object and stored them in texCoord.
For the fragment shader I changed the main function to clamp the projective texture so that it doesn't repeat (I couldn't get it to work with the client side GL_CLAMP_TO_EDGE) and I am also using the default object texture and UV coordinates in case the projector does not cover the whole object (I also removed lighting from the projective texture since it is not needed in my case):
void main (void)
{
vec2 finalCoords = projCoords.st / projCoords.q;
vec4 vTexColor = texture(gSampler, texCoord);
vec4 vProjTexColor = texture(projMap, finalCoords);
//vec4 vProjTexColor = textureProj(projMap, projCoords);
float fDiffuseIntensity = max(0.0, dot(normalize(vNormal), -sunLight.vDirection));
// supress the reverse projection
if (projCoords.q > 0.0)
{
// CLAMP PROJECTIVE TEXTURE (for some reason gl_clamp did not work...)
if(projCoords.s > 0 && projCoords.t > 0 && finalCoords.s < 1 && finalCoords.t < 1)
//outputColor = vProjTexColor*vColor*vec4(sunLight.vColor * (sunLight.fAmbientIntensity + fDiffuseIntensity), 1.0);
outputColor = vProjTexColor*vColor;
else
outputColor = vTexColor*vColor*vec4(sunLight.vColor * (sunLight.fAmbientIntensity + fDiffuseIntensity), 1.0);
}
else
{
outputColor = vTexColor*vColor*vec4(sunLight.vColor * (sunLight.fAmbientIntensity + fDiffuseIntensity), 1.0);
}
}
If you are stuck and for some reason can not get the shaders to work, you can check out an example in "OpenGL 4.0 Shading Language Cookbook" (textures chapter) - I actually missed this, until I got it working by myself.
In addition to all of the above, a great help for debugging if the algorithm is working correctly was to draw the frustum (as wireframe) for the projective camera. I used a shader for frustum drawing. The fragment shader just assigns a solid color, while the vertex shader is listed below with explanations:
#version 330
// input vertex data
layout(location = 0) in vec3 vp;
uniform mat4 P;
uniform mat4 MV;
uniform mat4 invP;
uniform mat4 invMV;
void main()
{
/*The transformed clip space position c of a
world space vertex v is obtained by transforming
v with the product of the projection matrix P
and the modelview matrix MV
c = P MV v
So, if we could solve for v, then we could
genrerate vertex positions by plugging in clip
space positions. For your frustum, one line
would be between the clip space positions
(-1,-1,near) and (-1,-1,far),
the lower left edge of the frustum, for example.
NB: If you would like to mix normalized device
coords (x,y) and eye space coords (near,far),
you need an additional step here. Modify your
clip position as follows
c' = (c.x * c.z, c.y * c.z, c.z, c.z)
otherwise you would need to supply both the z
and w for c, which might be inconvenient. Simply
use c' instead of c below.
To solve for v, multiply both sides of the equation above with
-1
(P MV)
This gives
-1
(P MV) c = v
This is equivalent to
-1 -1
MV P c = v
-1
P is given by
|(r-l)/(2n) 0 0 (r+l)/(2n) |
| 0 (t-b)/(2n) 0 (t+b)/(2n) |
| 0 0 0 -1 |
| 0 0 -(f-n)/(2fn) (f+n)/(2fn)|
where l, r, t, b, n, and f are the parameters in the glFrustum() call.
If you don't want to fool with inverting the
model matrix, the info you already have can be
used instead: the forward, right, and up
vectors, in addition to the eye position.
First, go from clip space to eye space
-1
e = P c
Next go from eye space to world space
v = eyePos - forward*e.z + right*e.x + up*e.y
assuming x = right, y = up, and -z = forward.
*/
vec4 fVp = invMV * invP * vec4(vp, 1.0);
gl_Position = P * MV * fVp;
}
The uniforms are used like this (make sure you use the right matrices):
// projector matrices
glUniformMatrix4fv(iFrustumInvProjectionLoc, 1, GL_FALSE, glm::value_ptr(glm::inverse(projectorP)));
glUniformMatrix4fv(iFrustumInvMVLoc, 1, GL_FALSE, glm::value_ptr(glm::inverse(projectorV)));
// world camera
glUniformMatrix4fv(iFrustumProjectionLoc, 1, GL_FALSE, glm::value_ptr(*pipeline->getProjectionMatrix()));
glUniformMatrix4fv(iFrustumModelViewLoc, 1, GL_FALSE, glm::value_ptr(mModelView));
To get the input vertices needed for the frustum's vertex shader you can do the following to get the coordinates (then just add them to your vertex array):
glm::vec3 ftl = glm::vec3(-1, +1, pFar); //far top left
glm::vec3 fbr = glm::vec3(+1, -1, pFar); //far bottom right
glm::vec3 fbl = glm::vec3(-1, -1, pFar); //far bottom left
glm::vec3 ftr = glm::vec3(+1, +1, pFar); //far top right
glm::vec3 ntl = glm::vec3(-1, +1, pNear); //near top left
glm::vec3 nbr = glm::vec3(+1, -1, pNear); //near bottom right
glm::vec3 nbl = glm::vec3(-1, -1, pNear); //near bottom left
glm::vec3 ntr = glm::vec3(+1, +1, pNear); //near top right
glm::vec3 frustum_coords[36] = {
// near
ntl, nbl, ntr, // 1 triangle
ntr, nbl, nbr,
// right
nbr, ftr, ntr,
ftr, nbr, fbr,
// left
nbl, ftl, ntl,
ftl, nbl, fbl,
// far
ftl, fbl, fbr,
fbr, ftr, ftl,
//bottom
nbl, fbr, fbl,
fbr, nbl, nbr,
//top
ntl, ftr, ftl,
ftr, ntl, ntr
};
After all is said and done, it's nice to see how it looks:
As you can see I applied two projective textures, one of a biohazard image on Blender's Suzanne monkey head, and a smiley texture on the floor and a small cube. You can also see that the cube is partly covered by the projective texture, while the rest of it appears with its default texture. Finally, you can see the green frustum wireframe for the projector camera - and everything looks correct.