Ok. So, I've been messing around with shadows in my game engine for the last week. I've mostly implemented cascading shadow maps (CSM), but I'm having a bit of a problem with shadowing that I just can't seem to solve.
The only light in this scene is a directional light (sun), pointing {-0.1 -0.25 -0.65}. I calculate 4 sets of frustum bounds for the four splits of my CSMs with this code:
// each projection matrix calculated with same near plane, different far
Frustum make_worldFrustum(const glm::mat4& _invProjView) {
Frustum fr; glm::vec4 temp;
temp = _invProjView * glm::vec4(-1, -1, -1, 1);
fr.xyz = glm::vec3(temp) / temp.w;
temp = _invProjView * glm::vec4(-1, -1, 1, 1);
fr.xyZ = glm::vec3(temp) / temp.w;
...etc 6 more times for ndc cube
return fr;
}
For the light, I get a view matrix like this:
glm::mat4 viewMat = glm::lookAt(cam.pos, cam.pos + lightDir, {0,0,1});
I then create each ortho matrix from the bounds of each frustum:
lightMatVec.clear();
for (auto& frus : cam.frusVec) {
glm::vec3 arr[8] {
glm::vec3(viewMat * glm::vec4(frus.xyz, 1)),
glm::vec3(viewMat * glm::vec4(frus.xyZ, 1)),
etc...
};
glm::vec3 minO = {INFINITY, INFINITY, INFINITY};
glm::vec3 maxO = {-INFINITY, -INFINITY, -INFINITY};
for (auto& vec : arr) {
minO = glm::min(minO, vec);
maxO = glm::max(maxO, vec);
}
glm::mat4 projMat = glm::ortho(minO.x, maxO.x, minO.y, maxO.y, minO.z, maxO.z);
lightMatVec.push_back(projMat * viewMat);
}
I have a 4 layer TEXTURE_2D_ARRAY bound to 4 framebuffers that I draw the scene into with a very simple vertex shader (frag disabled or punchthrough alpha).
I then draw the final scene. The vertex shader outputs four shadow texcoords:
out vec3 slShadcrd[4];
// stuff
for (int i = 0; i < 4; i++) {
vec4 sc = WorldBlock.skylMatArr[i] * vec4(world_pos, 1);
slShadcrd[i] = sc.xyz / sc.w * 0.5f + 0.5f;
}
And a fragment shader, which determines the split to use with:
int csmIndex = 0;
for (uint i = 0u; i < CameraBlock.csmCnt; i++) {
if (-view_pos.z > CameraBlock.csmSplits[i]) index++;
else break;
}
And samples the shadow map array with this function:
float sample_shadow(vec3 _sc, int _csmIndex, sampler2DArrayShadow _tex) {
return texture(_tex, vec4(_sc.xy, _csmIndex, _sc.z)).r;
}
And, this is the scene I get (with each split slightly tinted and the 4 depth layers overlayed):
Great! Looks good.
But, if I turn the camera slightly to the right:
Then shadows start disappearing (and depending on the angle, appearing where they shouldn't be).
I have GL_DEPTH_CLAMP enabled, so that isn't the issue. I'm culling front faces, but turning that off doesn't make a difference to this issue.
What am I missing? I feel like it's an issue with one of my projections, but they all look right to me. Thanks!
EDIT:
All four of the the light's frustums drawn. They are all there, but only z is changing relative to the camera (see comment below):
EDIT:
Probably more useful, this is how the frustums look when I only update them once, when the camera is at (0,0,0) and pointing forwards (0,1,0). Also I drew them with depth testing this time.
IMPORTANT EDIT:
It seems that this issue is directly related to the light's view matrix, currently:
glm::mat4 viewMat = glm::lookAt(cam.pos, cam.pos + lightDir, {0,0,1});
Changing the values for eye and target seems to affect the buggered shadows. But I don't know what I should actually be setting this to? Should be easy for someone with a better understanding than me :D
Solved it! It was indeed an issue with the light's view matrix! All I had to do was replace camPos with the centre point of each frustum! Meaning that each split's light matrix needed a different view matrix. So I just create each view matrix like this...
glm::mat4 viewMat = glm::lookAt(frusCentre, frusCentre+lightDir, {0,0,1});
And get frusCentre simply...
glm::vec3 calc_frusCentre(const Frustum& _frus) {
glm::vec3 min(INFINITY, INFINITY, INFINITY);
glm::vec3 max(-INFINITY, -INFINITY, -INFINITY);
for (auto& vec : {_frus.xyz, _frus.xyZ, _frus.xYz, _frus.xYZ,
_frus.Xyz, _frus.XyZ, _frus.XYz, _frus.XYZ}) {
min = glm::min(min, vec);
max = glm::max(max, vec);
}
return (min + max) / 2.f;
}
And bam! Everything works spectacularly!
EDIT (Last one!):
What I had was not quite right. The view matrix should actually be:
glm::lookAt(frusCentre-lightDir, frusCentre, {0,0,1});
Related
For learning purposes im trying to create simple drawing program.Right now im trying to add panning and zooming features.
This is how i update my camera m_rightward and m_upward are 1 and 0 based on if wasd is clicked.
void Camera::updateCamera(int width, int height)
{
this->height = height;
this->width = width;
cc.client_height = this->height;
cc.client_width = this->width;
cc.m_time = ::GetTickCount();
//panning and zooming in here
new_pos = world_cam.getTranslation() + world_cam.getZDirection()*(m_forward*0.1f);
new_pos = new_pos + world_cam.getXDirection()*(m_rightward * 20);
new_pos = new_pos + world_cam.getYDirection()*(m_upward * 20);
world_cam.setTranslation(new_pos);
temp = world_cam;
temp.inverse();
cc.m_view = temp;
cc.m_proj.setOrthoLH
(
width,
height,
-1.0f,
1.0f
);
}
and this is how i fill my linestrip to draw
void AppWindow::onLeftMouseDown(const Point & mouse_pos)
{
Point pos = screenToClient(mouse_pos);
clickedPoint = Vector3D(pos.m_x, pos.m_y, 0.0f);
if (isLPressed)
{
if (counter == 0)
{
LineStrip.push_back(Line(clickedPoint));
counter++;
}
else
{
LineStrip.back().addPoint(clickedPoint);
counter++;
}
}
}
and this is vertex shader
VS_OUTPUT vsmain(VS_INPUT input)
{
VS_OUTPUT output = (VS_OUTPUT) 0;
float4 new_pos = mul(input.position, m_world);
float clip_x = (new_pos.x / client_width) * 2.0 - 1.0;
float clip_y = 1.0 - (new_pos.y / client_height) * 2.0;
output.position = float4(clip_x, clip_y, 0, 1);
output.color = input.color;
return output;
}
Right now i can draw lines as i wanted.The problem is i cant make panning and zooming work.It just doesn't move the screen at all.I'm guessing how i fill linestrip and vertex shader output is wrong.If you any of you help me out of this i will be so glad.
Thank you
Your main issue here stems from a misunderstanding of how 3D space works.
There are five main spaces used in the 3D rendering pipeline: Local Space, World Space, View Space, Projection Space/Homogeneous Clip Space, and Screen Space. The matrices used to convert between them are named for the space they convert to:
The World Matrix converts from Local Space to World Space.
The View Matrix converts from World Space to View Space.
The Projection Matrix converts from View Space to Projection Space.
The rasterizer does the final transformation based on the settings in the D3D11_VIEWPORT structure, specified in the ID3D11DeviceContext::RSSetViewports call.
Your system is simple enough that you don't need a world matrix. Since you presumably are making a 2D drawing program with the 3D API, you can specify the points for each line directly in world space.
In order to support camera operations, however, you need a view matrix, and in order to do the rendering properly, you will need a projection matrix and a D3D11_VIEWPORT structure with the size of the client area filled in.
In your code you create an orthographic projection matrix, which is perfect (though your near and far plane specifications should be the minumum and maximum zoom you support). You don't, however, create a view matrix properly. The view matrix is the inverse transpose of the camera's world matrix, and it can be created directly with XMMatrixLookAtLH or XMMatrixLookToLH (LookTo is likely to be a better option for your system).
Finally, when you update the shader's constant buffer with the new matrix, multiply the view and projection matrices together before writing the new combined matrix down to the buffer.
As a consequence of this new complexity, you no longer need the majority of the code in the vertex shader - you can do the multiplication directly:
VS_OUTPUT vsmain(VS_INPUT input)
{
VS_OUTPUT output = (VS_OUTPUT) 0;
output.position = mul(input.position, m_viewprojection);
output.color = input.color;
return output;
}
The final stage is the adding of new points. Because of the added view and projection matrices, the coordinates you get from the mouse down event are not world space coordinates. You need to convert them to clip space, then multiply them by the inverse of the combined view/projection matrix to convert them into the real coordinates in world space. Then you can add the new line segment to the strip, the same as how you have in your code.
The conversion to clip space is basic algebra - the conversion the rasterizer does is:
[ScreenX, ScreenY] = [(ProjX + 1)*(ScreenWidth/2), (ProjY + 1)*(ScreenHeight/2)]
To convert back to projection space:
[ProjX, ProjY] = [(ScreenX * (2 / ScreenWidth)) - 1, (ScreenY * (2 / ScreenHeight)) - 1]
From there, you can do a matrix-vector multiplication to get the X and Y coordinates in world space. Since this app is 2D (presumably), you can ignore the lost Z component and just take X and Y. If you ever convert to 3D however, you will need to project a ray from the X and Y coordinates out into the world to find the object to select, or somehow fix the Z coordinate with a different algorithm.
I adopted "Separating axis theorem (SAT)" to realize "OBB collision detection".
As shown below, SAT requires three elements.
The coordinates (x, y, z) of the midpoint
Length of each axis
Direction vector of each axis
// Initialized
SATOBB::SATOBB(glm::vec3 &pos, std::vector<glm::vec3> &dir, glm::vec3 &len)
{
i_Pos = pos;
i_Dir = dir;
i_Len = len;
m_Dir.push_back(glm::vec3(0,0,0)); // Yeah... I know this strange code.. Thanks for tkausel
}
// i_... is before change, m_... is after change
void SATOBB::update(
glm::mat4 &Rotate,
glm::mat4 &Trans,
glm::mat4 &Scale
)
{
glm::vec3 m_Pos = Trans * glm::vec4(i_Pos, 1.0f);
for (int i=0; i<i_Dir.size(); i++){
glm::vec3 m_Dir = Rotate * glm::vec4(i_Dir, 1.0f);
}
glm::vec3 m_Len = Scale * glm::vec4(i_Len, 1.0f);
}
I think the code for calculating "3." is wrong.
So, please let me know the correct calculation code.
For calculation, I wanted to use the mat4 function, so vec3 is used for "1. & 2.." (For reasons of expediency)
"3." was calculated using vec3.
Is it really enough to multiply the vector by the rotation matrix?
That is the problem.
I have a triangle and have 3 vertices anywhere in space.
I attempted to get the max and min coordinates for it.
void findBoundingBox(glm::vec3 & minBB, glm::vec3 & maxBB)
{
minBB.x = std::min(minBB.x, mCoordinate.x);
minBB.y = std::min(minBB.y, mCoordinate.y);
minBB.z = std::min(minBB.z, mCoordinate.z);
maxBB.x = std::max(maxBB.x, mCoordinate.x);
maxBB.y = std::max(maxBB.y, mCoordinate.y);
maxBB.z = std::max(maxBB.z, mCoordinate.z);
}
}
Now I tried to set
:
glm::vec3 InverseViewDirection(50.0f, 200, 200); //Inverse View Direction
glm::vec3 LookAtPosition(0.0,0,0); // I can make it anywhere with barycentric coord, but this is the simple case
glm::vec3 setupVector(0.0, 1, 0);
I tried to set the orthographic view to wrap the triangle by:
myCamera.setProjectionMatrix(min.x, max.x, max.y,min.y, 0.0001f, 10000.0f);
But its not neatly bounding the triangle in my view.
I've been stumped on this for a day, any pointers?
Bad: output : (I want the view to neatly bound the triangle)
Edit:
Based on a comment ( I have tried to update the bounds with the view matrix (model is identity, so ignoring that for now)
still no luck :(
glm::vec4 minSS = ((myCamera.getViewMatrix()) * glm::vec4(minWS, 0.0));
glm::vec4 maxSS = ((myCamera.getViewMatrix()) * glm::vec4(maxWS, 0.0));
myCamera.setProjectionMatrix(minSS.x, maxSS.x, maxSS.y, minSS.y, -200.0001f, 14900.0f);
You will need to apply all transformations that come before the perspective transformation to your input points when you calculate the bounding box.
In your code fragments, it looks like you're applying a viewing transform with an arbitrary viewpoint (50, 200, 200) as part of your rendering. You need to apply this same transformation to your input points before you feed them into your findBoundingBox() function.
In more mathematical terms, you typically have something like this in your vertex shader, with InputPosition being the original vertex coordinates:
gl_Position = ProjectionMatrix * ViewMatrix * ModelMatrix * InputPosition;
To determine a projection matrix that will map all your points to a given range, you need to look at all points that the projection matrix is applied to. With the notation above, those points are ViewMatrix * ModelMatrix * InputPosition. So when you calculate the bounding box, the model and view matrices (or the modelview matrix if you combine them) needs to be applied to the input points.
I'm creating some random vectors/directions in a loop as a dome shape like this:
void generateDome(glm::vec3 direction)
{
for(int i=0;i<1000;++i)
{
float xDir = randomByRange(-1.0f, 1.0f);
float yDir = randomByRange(0.0f, 1.0f);
float zDir = randomByRange(-1.0f, 1.0f);
auto vec = glm::vec3(xDir, yDir, zDir);
vec = glm::normalize(vec);
...
//some transformation with direction-vector
}
...
}
This creates vectors as a dome-shape in +y direction (0,1,0):
Now I want to rotate the vec-Vector by a given direction-Vector like (1,0,0).
This should rotate the "dome" to the x-direction like this:
How can I achieve this? (preferably with glm)
A rotation is generally defined using some sort of offset (axis-angle, quaternion, euler angles, etc) from a starting position. What you are looking for would be more accurately described (in my opinion) as a re-orientation. Luckily this isn't too hard to do. What you need is a change-of-basis matrix.
First, lets just define what we're working with in code:
using glm::vec3;
using glm::mat3;
vec3 direction; // points in the direction of the new Y axis
vec3 vec; // This is a randomly generated point that we will
// eventually transform using our base-change matrix
To calculate the matrix, you need to create unit vectors for each of the new axes. From the example above it becomes apparent that you want the vector provided to become the new Y-axis:
vec3 new_y = glm::normalize(direction);
Now, calculating the X and Z axes will be a tad more complicated. We know that they must be orthogonal to each other and to the Y axis calculated above. The most logical way to construct the Z axis is to assume that the rotation is taking place in the plane defined by the old Y axis and the new Y axis. By using the cross-product we can calculate this plane's normal vector, and use that for the Z axis:
vec3 new_z = glm::normalize(glm::cross(new_y, vec3(0, 1, 0)));
Technically the normalization isn't necessary here since both input vectors are already normalized, but for the sake of clarity, I've left it. Also note that there is a special case when the input vector is colinear with the Y-axis, in which case the cross product above is undefined. The easiest way to fix this is to treat it as a special case. Instead of what we have so far, we'd use:
if (direction.x == 0 && direction.z == 0)
{
if (direction.y < 0) // rotate 180 degrees
vec = vec3(-vec.x, -vec.y, vec.z);
// else if direction.y >= 0, leave `vec` as it is.
}
else
{
vec3 new_y = glm::normalize(direction);
vec3 new_z = glm::normalize(glm::cross(new_y, vec3(0, 1, 0)));
// code below will go here.
}
For the X-axis, we can cross our new Y-axis with our new Z-axis. This yields a vector perpendicular to both of the others axes:
vec3 new_x = glm::normalize(glm::cross(new_y, new_z));
Again, the normalization in this case is not really necessary, but if y or z were not already unit vectors, it would be.
Finally, we combine the new axis vectors into a basis-change matrix:
mat3 transform = mat3(new_x, new_y, new_z);
Multiplying a point vector (vec3 vec) by this yields a new point at the same position, but relative to the new basis vectors (axes):
vec = transform * vec;
Do this last step for each of your randomly generated points and you're done! No need to calculate angles of rotation or anything like that.
As a side note, your method of generating random unit vectors will be biased towards directions away from the axes. This is because the probability of a particular direction being chosen is proportional to the distance to the furthest point possible in a given direction. For the axes, this is 1.0. For directions like eg. (1, 1, 1), this distance is sqrt(3). This can be fixed by discarding any vectors which lie outside the unit sphere:
glm::vec3 vec;
do
{
float xDir = randomByRange(-1.0f, 1.0f);
float yDir = randomByRange(0.0f, 1.0f);
float zDir = randomByRange(-1.0f, 1.0f);
vec = glm::vec3(xDir, yDir, zDir);
} while (glm::length(vec) > 1.0f); // you could also use glm::length2 instead, and avoid a costly sqrt().
vec = glm::normalize(vec);
This would ensure that all directions have equal probability, at the cost that if you're extremely unlucky, the points picked may lie outside the unit sphere over and over again, and it may take a long time to generate one that's inside. If that's a problem, it could be modified to limit the iterations: while (++i < 4 && ...) or by increasing the radius at which a point is accepted every iteration. When it is >= sqrt(3), all possible points would be considered valid, so the loop would end. Both of these methods would result in a slight biasing away from the axes, but in almost any real situation, it would not be detectable.
Putting all the code above together, combined with your code, we get:
void generateDome(glm::vec3 direction)
{
// Calculate change-of-basis matrix
glm::mat3 transform;
if (direction.x == 0 && direction.z == 0)
{
if (direction.y < 0) // rotate 180 degrees
transform = glm::mat3(glm::vec3(-1.0f, 0.0f 0.0f),
glm::vec3( 0.0f, -1.0f, 0.0f),
glm::vec3( 0.0f, 0.0f, 1.0f));
// else if direction.y >= 0, leave transform as the identity matrix.
}
else
{
vec3 new_y = glm::normalize(direction);
vec3 new_z = glm::normalize(glm::cross(new_y, vec3(0, 1, 0)));
vec3 new_x = glm::normalize(glm::cross(new_y, new_z));
transform = mat3(new_x, new_y, new_z);
}
// Use the matrix to transform random direction vectors
vec3 point;
for(int i=0;i<1000;++i)
{
int k = 4; // maximum number of direction vectors to guess when looking for one inside the unit sphere.
do
{
point.x = randomByRange(-1.0f, 1.0f);
point.y = randomByRange(0.0f, 1.0f);
point.z = randomByRange(-1.0f, 1.0f);
} while (--k > 0 && glm::length2(point) > 1.0f);
point = glm::normalize(point);
point = transform * point;
// ...
}
// ...
}
You need to create a rotation matrix. Therefore you need a identity Matrix. Create it like this with
glm::mat4 rotationMat(1); // Creates a identity matrix
Now your can rotate the vectorspacec with
rotationMat = glm::rotate(rotationMat, 45.0f, glm::vec3(0.0, 0.0, 1.0));
This will rotate the vectorspace by 45.0 degrees around the z-axis (as shown in your screenshot). Now your almost done. To rotate your vec you can write
vec = glm::vec3(rotationMat * glm::vec4(vec, 1.0));
Note: Because you have a 4x4 matrix you need a vec4 to multiply it with the matrix. Generally it is a good idea always to use vec4 when working with OpenGL because vectors in smaller dimension will be converted to homogeneous vertex coordinates anyway.
EDIT: You can also try to use GTX Extensions (Experimental) by including <glm/gtx/rotate_vector.hpp>
EDIT 2: When you want to rotate the dome "towards" a given direction you can get your totation axis by using the cross-product between the direction and you "up" vector of the dome. Lets say you want to rotate the dome "toward" (1.0, 1.0, 1.0) and the "up" direction is (0.0, 1.0, 0.0) use:
glm::vec3 cross = glm::cross(up, direction);
glm::rotate(rotationMat, 45.0f, cross);
To get your rotation matrix. The cross product returns a vector that is orthogonal to "up" and "direction" and that's the one you want to rotate around. Hope this will help.
I have a cube rendered on the screen which represents a car (or similar).
Using Projection/Model matrices and Glm I am able to move it back and fourth along the axes and rotate it left or right.
I'm having trouble with the vector mathematics to make the cube move forwards no matter which direction it's current orientation is. (ie. if I would like, if it's rotated right 30degrees, when it's move forwards, it travels along the 30degree angle on a new axes).
I hope I've explained that correctly.
This is what I've managed to do so far in terms of using glm to move the cube:
glm::vec3 vel; //velocity vector
void renderMovingCube(){
glUseProgram(movingCubeShader.handle());
GLuint matrixLoc4MovingCube = glGetUniformLocation(movingCubeShader.handle(), "ProjectionMatrix");
glUniformMatrix4fv(matrixLoc4MovingCube, 1, GL_FALSE, &ProjectionMatrix[0][0]);
glm::mat4 viewMatrixMovingCube;
viewMatrixMovingCube = glm::lookAt(camOrigin, camLookingAt, camNormalXYZ);
vel.x = cos(rotX); vel.y=sin(rotX);
vel*=moveCube;
//move cube
ModelViewMatrix = glm::translate(viewMatrixMovingCube,globalPos*vel);
//bring ground and cube to bottom of screen
ModelViewMatrix = glm::translate(ModelViewMatrix, glm::vec3(0,-48,0));
ModelViewMatrix = glm::rotate(ModelViewMatrix, rotX, glm::vec3(0,1,0)); //manually turn
glUniformMatrix4fv(glGetUniformLocation(movingCubeShader.handle(), "ModelViewMatrix"), 1, GL_FALSE, &ModelViewMatrix[0][0]); //pass matrix to shader
movingCube.render(); //draw
glUseProgram(0);
}
keyboard input:
void keyboard()
{
char BACKWARD = keys['S']; char FORWARD = keys['W'];
char ROT_LEFT = keys['A']; char ROT_RIGHT = keys['D'];
if (FORWARD) //W - move forwards
{
globalPos += vel;
//globalPos.z -= moveCube;
BACKWARD = false;
}
if (BACKWARD)//S - move backwards
{
globalPos.z += moveCube;
FORWARD = false;
}
if (ROT_LEFT)//A - turn left
{
rotX +=0.01f;
ROT_LEFT = false;
}
if (ROT_RIGHT)//D - turn right
{
rotX -=0.01f;
ROT_RIGHT = false;
}
Where am I going wrong with my vectors? I would like change the direction of the cube (which it does) but then move forwards in that direction.