Reputation: 108
I want to control my camera so that it can rotate around the model.
The theoretical code should be:
// `camera_rotation_angle_x_` and `camera_rotation_angle_y_` are initialized to 0, and can be modified by the user.
glm::mat4 CreateViewMatrix(glm::vec3 eye_pos, glm::vec3 scene_center, glm::vec3 up_vec) {
auto eye_transform = glm::translate(glm::mat4(1.0f), -scene_center); // recenter
eye_transform = glm::rotate(eye_transform, camera_rotation_angle_x_, glm::vec3(1.0f, 0.0f, 0.0f));
eye_transform = glm::rotate(eye_transform, camera_rotation_angle_y_, glm::vec3(0.0f, 1.0f, 0.0f));
eye_transform = glm::translate(eye_transform, scene_center); // move back
eye_pos = eye_transform * glm::vec4(eye_pos, 1.0f);
up_vec = eye_transform * glm::vec4(up_vec, 0.0f);
return glm::lookAt(eye_pos, scene_center, up_vec);
}
But the two lines "recenter" and "move back" must be written as follows to rotate correctly, otherwise the distance from the camera to the center will vary when the rotation parameters change:
auto eye_transform = glm::translate(glm::mat4(1.0f), scene_center); // recenter *The sign has changed*
...
eye_transform = glm::translate(eye_transform, -scene_center); // move back *The sign has changed*
...
// Correctness test, only when the distance remains constant, the rotation logic is correct.
cout << "eye_pos: " << eye_pos[0] << ", " << eye_pos[1] << ", " << eye_pos[2] << endl;
cout << "distance: " << (sqrt(
pow(eye_pos[0] - scene_center[0], 2)
+ pow(eye_pos[1] - scene_center[1], 2)
+ pow(eye_pos[2] - scene_center[2], 2)
)) << endl;
It is the correct logic to subtract the central value first and then add it back. It does not make any sense to add and then subtract.
So what's going wrong that I have to write code with logic errors in order for it to work properly?
The caller's code is below, maybe the bug is here?
// `kEyePos`, `kSceneCenter`, `kUpVec`, `kFovY`, `kAspect`, `kDistanceEyeToBack` and `kLightPos` are constants throughout the lifetime of the program
UniformBufferObject ubo{};
ubo.model = glm::mat4(1.0f);
ubo.view = CreateViewMatrix(kEyePos, kSceneCenter, kUpVec);
ubo.proj = glm::perspective(kFovY, kAspect, 0.1f, kDistanceEyeToBack);
// GLM was originally designed for OpenGL, where the Y coordinate of the clip coordinates is inverted.
// The easiest way to compensate for that is to flip the sign on the scaling factor of the Y axis in the projection matrix.
// Because of the Y-flip we did in the projection matrix, the vertices are now being drawn in counter-clockwise order instead of clockwise order.
// This causes backface culling to kick in and prevents any geometry from being drawn.
// You should modify the frontFace in `VkPipelineRasterizationStateCreateInfo` to `VK_FRONT_FACE_COUNTER_CLOCKWISE` to correct this.
ubo.proj[1][1] *= -1;
ubo.light_pos = glm::vec4(kLightPos, 1.0f); // The w component of point is 1
memcpy(vk_buffer_->GetUniformBufferMapped(frame_index), &ubo, sizeof(ubo));
Upvotes: 0
Views: 311
Reputation: 108
I found that the problem was with the order of matrix construction, not with the glm::lookAt
method.
m = glm::rotate(m, angle, up_vec)
is equivalent to
m = m * glm::rotate(glm::mat4(1), angle, up_vec)
, not
m = glm::rotate(glm::mat4(1), angle, up_vec) * m
as I thought.
This easily explains why swapping "recenter" and "move back" works properly.
The correct code is as follows:
glm::mat4 VulkanRendering::CreateViewMatrix(glm::vec3 eye_pos, glm::vec3 scene_center, glm::vec3 up_vec) const {
// Create transform matrix in reverse order.
// First rotate around the X axis, and then around the Y axis, otherwise it does not match the practice of most games.
auto view_transform = glm::translate(glm::mat4(1.0f), scene_center); // last: move back
view_transform = glm::rotate(view_transform, camera_rotation_angle_y_, glm::vec3(0.0f, 1.0f, 0.0f));
view_transform = glm::rotate(view_transform, camera_rotation_angle_x_, glm::vec3(1.0f, 0.0f, 0.0f));
view_transform = glm::translate(view_transform, -scene_center); // first: recenter
eye_pos = view_transform * glm::vec4(eye_pos, 1.0f); // The w component of point is 1
up_vec = view_transform * glm::vec4(up_vec, 0.0f); // The w component of vector is 0
return glm::lookAt(eye_pos, scene_center, up_vec);
}
Upvotes: 1