I am following the LearnOpenGL tutorials and have been tinkering with shadows casting. So far everything is working correctly but there is this very specific problem where I can't cast shadows that from a purely vertical directional light. Let's add some code. My light space matrix looks like this:
glm::mat4 view = glm::lookAt(-direction, glm::vec3(0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
return glm::ortho(-10.0f, 10.0f, -10.0f, 10.0f, -10.0f, 10.0f) * view;
direction is a vector with as direction the direction of the directional light, of course. Everything works well until that direction vector is set to (0,-1,0), because it is parallel to the up vector (0,1,0). To construct the lookAt matrix, glm is performing the cross product between the up vector and the difference between the center and the eye (so in that case it's basically the direction), but that cross product won't give any result since the two vectors are parallel.
Knowing all of this, my question would be : how should my lookAt view matrix when the up vector and the direction of the light are parallel ?
Edit : Thank you for your answer, I changed my code to this :
if(abs(direction.x) < FLT_EPSILON && abs(direction.z) < FLT_EPSILON)
view = glm::lookAt(-direction, glm::vec3(0.0f), glm::vec3(0.0f, 0.0f, 1.0f));
else
view = glm::lookAt(-direction, glm::vec3(0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
return glm::ortho(-10.0f, 10.0f, -10.0f, 10.0f, -10.0f, 10.0f) * view;
and now everything works fine !
When the up-vector and the line of sight are parallel, then the view matrix is undefined, because the Cross product is (0, 0, 0).
The view matrix is an Orthogonal matrix, this means each of the 3 axis is perpendicular to plane which is formed by the 2 other axis. Respectively the angele between the axis is 90°.
The view matrix is the inverse matrix of that matrix which defines the viewing position and orientation. This matrix is defined by the parameters to glm::lookAt. 2 of the axis a res specified by the line of sight and the up-vector. The 3rd axis is calculated by the cross product.
This all means, you have to specify the matrix by 2 Orthogonal directions. If the angle between the direction vectors is not exactly (90°) then this is corrected by glm::lookAt. But the algorithm fails to do that if the vectors are parallel.
Define a line of sight (direction) vector and a up-vector, with an angle of 90° to each another. If you rotate on of them, then you've to rotate the other vector in the same way.
e.g. Lets assume you've a direction vector (line of sight) and an up-vector:
direction: (0, 0, 1)
up : (0, 1, 0)
If the direction vector is rotated by 90° then the up-vector has to be rotated by 90°, too:
direction: (0, -1, 0)
up : (0, 0, 1)
Related
I'm implementing a 3D maze game in C++, using OpenGL where the camera is at a fixed position exactly above the middle of the maze looking at the middle too. It works and looks just as I've wanted but the my code is not so nice because I needed to put a + 0.01f into the position to work well. If I miss it out, the labirynth doesn't show up, it seems like the camera points into the opposite direction.. How can I fix it elegantly?
glm::vec3 FrontDirection = glm::vec3(((GLfloat)MazeSize / 2) + 0.01f, 0.0f, ((GLfloat)MazeSize / 2)
The (0,0,0) origo location is the left corner of the maze (start position) so this is the reason I set the position like this:
glm::vec3 CameraPosition = glm::vec3(((GLfloat)MazeSize / 2), ((GLfloat)MazeSize + 5.0f), ((GLfloat)MazeSize / 2))
The UpDirection is (0.0f, 1.0f, 0.0f) lookAt function looks like this:
Matrix = glm::lookAt(CameraPosition, FrontDirection, UpDirection);
I know that by convention, in OpenGL the camera points towards the negative z-axis so the FrontDirection is basically pointing in the reverse direction of what it is targeting. For me it would be completely clear to set the positions like I did above but it still is not working as I've expected. (unless I put that + 0.01f)
Thank you for your answer in advance!
I know that by convention, in OpenGL the camera points towards the negative z-axis
In viewspace the z-axis points out of the viewport, but that is completely irrelevant. You define the view matrix, the position and the orientation of the camera. The camera position is the first argument of glm::lookAt. The camera looks in the direction of the target, the 2nd argument of glm::lookAt.
The 2nd parameter of glm::lookAt is not a direction vector, it is a point on the line of sight.
Compute a point on the line of sight by CameraPosition+FrontDirection:
Matrix = glm::lookAt(CameraPosition, CameraPosition+FrontDirection, UpDirection);
Your upwards-vector has the same direction as the line of sight. The upwards should be orthogonal to the line of sight. The up-vector defines the roll:
glm::vec3 CameraPosition = glm::vec3(
((GLfloat)MazeSize / 2), ((GLfloat)MazeSize + 5.0f), ((GLfloat)MazeSize / 2));
glm::vec3 CameraTarget = glm::vec3(
((GLfloat)MazeSize / 2), 0.0f, ((GLfloat)MazeSize / 2));
glm::vec3 UpDirection = glm::vec3(0.0f, 0.0f, 1.0f);
Matrix = glm::lookAt(CameraPosition, CameraTarget, UpDirection);
Here is the rotation code when initialising the model matrix:
_model = translate(_position) *
( rotate(_rotation.data[0], 1.0f, 0.0f, 0.0f) *
rotate(_rotation.data[1], 0.0f, 1.0f, 0.0f) *
rotate(_rotation.data[2], 0.0f, 0.0f, 1.0f)) *
scale(_scale);
Basically, I have got a 3D level and I want to rotate the level and all the objects in it around the same pivot point.
How could I do this?
This is typically done by the concatenation (i.e. multiplication) of three matrices:
T: Translate the desired pivot to the origin (0, 0, 0).
R: Apply the rotation.
Tinv: Translate back.
Because of the way OpenGL matrices are structured, the right order is Tinv * R * T. Premultiply your view matrix by that.
So I want to use quaternions and angles to control my camera using my mouse.
I accumulate the vertical/horizontal angles like this:
void Camera::RotateCamera(const float offsetHorizontalAngle, const float offsetVerticalAngle)
{
mHorizontalAngle += offsetHorizontalAngle;
mHorizontalAngle = std::fmod(mHorizontalAngle, 360.0f);
mVerticalAngle += offsetVerticalAngle;
mVerticalAngle = std::fmod(mVerticalAngle, 360.0f);
}
and compute my orientation like this:
Mat4 Camera::Orientation() const
{
Quaternion rotation;
rotation = glm::angleAxis(mVerticalAngle, Vec3(1.0f, 0.0f, 0.0f));
rotation = rotation * glm::angleAxis(mHorizontalAngle, Vec3(0.0f, 1.0f, 0.0f));
return glm::toMat4(rotation);
}
and the forward vector, which I need for glm::lookAt, like this:
Vec3 Camera::Forward() const
{
return Vec3(glm::inverse(Orientation()) * Vec4(0.0f, 0.0f, -1.0f, 0.0f));
}
I think that should do the trick, but I do not know how in my example game to get actual angles? All I have is the current and previous mouse location in window coordinates.. how can I get proper angles from that?
EDIT: on a second thought.. my "RotateCamera()" cant be right; I am experiencing rubber-banding effect due to the angles reseting after reaching 360 deegres... so how do I accumulate angles properly? I can just sum them up endlessly
Take a cross section of the viewing frustum (the blue circle is your mouse position):
Theta is half of your FOV
p is your projection plane distance (don't worry - it will cancel out)
From simple ratios it is clear that:
But from simple trignometry
So ...
Just calculate the angle psi for each of your mouse positions and subtract to get the difference.
A similar formula can be found for the vertical angle:
Where A is your aspect ratio (width / height)
It does look at the target when I move to the target to the right and looks at it up until it goes 180 degrees of the -zaxis and decides to go the other way.
Matrix4x4 camera::GetViewMat()
{
Matrix4x4 oRotate, oView;
oView.SetIdentity();
Vector3 lookAtDir = m_targetPosition - m_camPosition;
Vector3 lookAtHorizontal = Vector3(lookAtDir.GetX(), 0.0f, lookAtDir.GetZ());
lookAtHorizontal.Normalize();
float angle = acosf(Vector3(0.0f, 0.0f, -1.0f).Dot(lookAtHorizontal));
Quaternions horizontalOrient(angle, Vector3(0.0f, 1.0f, 0.0f));
ori = horizontalOrient;
ori.Conjugate();
oRotate = ori.ToMatrix();
Vector3 inverseTranslate = Vector3(-m_camPosition.GetX(), -m_camPosition.GetY(), -m_camPosition.GetZ());
oRotate.Transform(inverseTranslate);
oRotate.Set(0, 3, inverseTranslate.GetX());
oRotate.Set(1, 3, inverseTranslate.GetY());
oRotate.Set(2, 3, inverseTranslate.GetZ());
oView = oRotate;
return oView;
}
As promised, a bit of code showing the way I'd make a camera look at a specific point in space at all times.
First of all, we'd need a method to construct a quaternion from an angle and an axis, I happen to have that on pastebin, the angle input is in radians:
http://pastebin.com/vLcx4Qqh
Make sure you don't input the axis (0,0,0), which wouldn't make any sense whatsoever.
Now the actual update method, we need to get the quaternion rotating the camera from default orientation to pointing towards the target point. PLEASE note I just wrote this out of the top of my head, it probably needs a little debugging and may need a little optimization, but this should at least give you a push in the right direction.
void camera::update()
{
// First get the direction from the camera's position to the target point
vec3 lookAtDir = m_targetPoint - m_position;
// I'm going to divide the vector into two 'components', the Y axis rotation
// and the Up/Down rotation, like a regular camera would work.
// First to calculate the rotation around the Y axis, so we zero out the y
// component:
vec3 lookAtHorizontal = vec3(lookAtDir.x, 0.0f, lookAtDir.z).normalize();
// Get the quaternion from 'default' direction to the horizontal direction
// In this case, 'default' direction is along the -z axis, like most OpenGL
// programs. Make sure the projection matrix works according to this.
float angle = acos(vec3(0.0f, 0.0f, -1.0f).dot(lookAtHorizontal));
quaternion horizontalOrient(angle, vec3(0.0f, 1.0f, 0.0f));
// Since we already stripped the Y component, we can simply get the up/down
// rotation from it as well.
angle = acos(lookAtDir.normalize().dot(lookAtHorizontal));
if(angle) horizontalOrient *= quaternion(angle, lookAtDir.cross(lookAtHorizontal));
// ...
m_orientation = horizontalOrient;
}
Now to actually take m_orientation and m_position and get the world -> camera matrix
// First inverse each element (-position and inverse the quaternion),
// the position is rotated since the position within a matrix is 'added' last
// to the output vector, so it needs to account for rotation of the space.
mat3 rotationMatrix = m_orientation.inverse().toMatrix();
vec3 inverseTranslate = rotationMatrix * -m_position; // Note the minus
mat4 matrix = mat3; // just means the matrix is expanded, the last entry (bottom right of the matrix) is a 1.0f like an identity matrix would be.
// This bit is row-major in my case, you just need to set the translation of the matrix.
matrix[3] = inverseTranslate.x;
matrix[7] = inverseTranslate.y;
matrix[11] = inverseTranslate.z;
EDIT I think it should be obvious but just for completeness, .dot() takes the dot product of the vectors, .cross() takes the cross product, the object executing the method is vector A, and the parameter of the method is vector B.
I am working on rendering a terrain in OpenGL.
My code is the following:
void Render_Terrain(int k)
{
GLfloat angle = (GLfloat) (k/40 % 360);
//PROJECTION
glm::mat4 Projection = glm::perspective(45.0f, 1.0f, 0.1f, 100.0f);
//VIEW
glm::mat4 View = glm::mat4(1.);
//ROTATION
//View = glm::rotate(View, angle * -0.1f, glm::vec3(1.f, 0.f, 0.f));
//View = glm::rotate(View, angle * 0.2f, glm::vec3(0.f, 1.f, 0.f));
//View = glm::rotate(View, angle * 0.9f, glm::vec3(0.f, 0.f, 1.f));
View = glm::translate(View, glm::vec3(0.f,0.f, -4.0f)); // x, y, z position ?
//MODEL
glm::mat4 Model = glm::mat4(1.0);
glm::mat4 MVP = Projection * View * Model;
glUniformMatrix4fv(glGetUniformLocation(shaderprogram, "MVP_matrix"), 1, GL_FALSE, glm::value_ptr(MVP));
//Transfer additional information to the vertex shader
glm::mat4 MV = Model * View;
glUniformMatrix4fv(glGetUniformLocation(shaderprogram, "MV_matrix"), 1, GL_FALSE, glm::value_ptr(MV));
glClearColor(0.0, 0.0, 0.0, 1.0);
glDrawArrays(GL_LINE_STRIP, terrain_start, terrain_end );
}
I can do a rotation around the X,Y,Z axis, scale my terrain but I can't find a way to move the camera. I am using OpenGL 3+ and I am kinda new to graphics.
The best way to move the camera would be through the use of gluLookAt(), it simulates camera movement since the camera cannot be moved whatsoever. The function takes 9 parameters. The first 3 are the XYZ coordinates of the eye which is where the camera is exactly located. The second 3 parameters are the XYZ coordinates of the center which is the point the camera is looking at from the eye. It is always going to be the center of the screen. The third 3 parameters are the XYZ coordinates of the UP vector which points vertically upwards from the eye. Through manipulating those 3 XYZ coordinates you can simulate any camera movement you want.
Check out this link.
Further details:
-If you want for example to rotate around an object you rotate your eye around the up vector.
-If you want to move forward or backwards you add or subtract to the eye as well as the center points.
-If you want to tilt the camera left or right you rotate your up vector around your look vector where your look vector is center - eye.
gluLookAt operates on the deprecated fixed function pipeline, so you should use glm::lookAt instead.
You are currently using a constant vector for translation. In the commented out code (which I assume you were using to test rotation), you use angle to adjust the rotation. You should have a similar variable for translation. Then, you can change the glm::translate call to:
View = glm::translate(View, glm::vec3(x_transform, y_transform, z_transform)); // x, y, z position ?
and get translation.
You should probably pass in more than one parameter into Render_Terrain, as translation and rotation need at least six parameters.
In OpenGL the camera is always at (0, 0, 0). You need to set the matrix mode to GL_MODELVIEW, and then modify or set the model/view matrix using things like glTranslate, glRotate, glLoadMatrix, etc. in order to make it appear that the camera has moved. If you're using GLU, you can use gluLookAt to point the camera in a particular direction.