这是我看油管cherno的ray tracing视频笔记记录。
本次讲了如何将键盘控制移动和旋转代入应用程序中。
引入Camera.h和Camera.cpp两个文件:
// Camera.h
#pragma once
#include <glm/glm.hpp>
#include <vector>
class Camera
{
public:
Camera(float verticalFOV, float nearClip, float farClip);
bool OnUpdate(float ts);
void OnResize(uint32_t width, uint32_t height);
const glm::mat4& GetProjection() const { return m_Projection; }
const glm::mat4& GetInverseProjection() const { return m_InverseProjection; }
const glm::mat4& GetView() const { return m_View; }
const glm::mat4& GetInverseView() const { return m_InverseView; }
const glm::vec3& GetPosition() const { return m_Position; }
const glm::vec3& GetDirection() const { return m_ForwardDirection; }
const std::vector<glm::vec3>& GetRayDirections() const { return m_RayDirections; }
float GetRotationSpeed();
private:
void RecalculateProjection();
void RecalculateView();
void RecalculateRayDirections();
private:
glm::mat4 m_Projection{ 1.0f };
glm::mat4 m_View{ 1.0f };
glm::mat4 m_InverseProjection{ 1.0f };
glm::mat4 m_InverseView{ 1.0f };
float m_VerticalFOV = 45.0f;
float m_NearClip = 0.1f;
float m_FarClip = 100.0f;
glm::vec3 m_Position{ 0.0f, 0.0f, 0.0f };
glm::vec3 m_ForwardDirection{ 0.0f, 0.0f, 0.0f };
// Cached ray directions
std::vector<glm::vec3> m_RayDirections;
glm::vec2 m_LastMousePosition{ 0.0f, 0.0f };
uint32_t m_ViewportWidth = 0, m_ViewportHeight = 0;
};
// Camera.cpp
#include "Camera.h"
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/quaternion.hpp>
#include <glm/gtx/quaternion.hpp>
#include "Walnut/Input/Input.h"
using namespace Walnut;
Camera::Camera(float verticalFOV, float nearClip, float farClip)
: m_VerticalFOV(verticalFOV), m_NearClip(nearClip), m_FarClip(farClip)
{
m_ForwardDirection = glm::vec3(0, 0, -1);
m_Position = glm::vec3(0, 0, 3);
}
bool Camera::OnUpdate(float ts)
{
glm::vec2 mousePos = Input::GetMousePosition();
glm::vec2 delta = (mousePos - m_LastMousePosition) * 0.002f;
m_LastMousePosition = mousePos;
if (!Input::IsMouseButtonDown(MouseButton::Right))
{
Input::SetCursorMode(CursorMode::Normal);
return false;
}
Input::SetCursorMode(CursorMode::Locked);
bool moved = false;
constexpr glm::vec3 upDirection(0.0f, 1.0f, 0.0f);
glm::vec3 rightDirection = glm::cross(m_ForwardDirection, upDirection);
float speed = 5.0f;
// Movement
if (Input::IsKeyDown(KeyCode::W))
{
m_Position += m_ForwardDirection * speed * ts;
moved = true;
}
else if (Input::IsKeyDown(KeyCode::S))
{
m_Position -= m_ForwardDirection * speed * ts;
moved = true;
}
if (Input::IsKeyDown(KeyCode::A))
{
m_Position -= rightDirection * speed * ts;
moved = true;
}
else if (Input::IsKeyDown(KeyCode::D))
{
m_Position += rightDirection * speed * ts;
moved = true;
}
if (Input::IsKeyDown(KeyCode::Q))
{
m_Position -= upDirection * speed * ts;
moved = true;
}
else if (Input::IsKeyDown(KeyCode::E))
{
m_Position += upDirection * speed * ts;
moved = true;
}
// Rotation
if (delta.x != 0.0f || delta.y != 0.0f)
{
float pitchDelta = delta.y * GetRotationSpeed();
float yawDelta = delta.x * GetRotationSpeed();
glm::quat q = glm::normalize(glm::cross(glm::angleAxis(-pitchDelta, rightDirection),
glm::angleAxis(-yawDelta, glm::vec3(0.f, 1.0f, 0.0f))));
m_ForwardDirection = glm::rotate(q, m_ForwardDirection);
moved = true;
}
if (moved)
{
RecalculateView();
RecalculateRayDirections();
}
return moved;
}
void Camera::OnResize(uint32_t width, uint32_t height)
{
if (width == m_ViewportWidth && height == m_ViewportHeight)
return;
m_ViewportWidth = width;
m_ViewportHeight = height;
RecalculateProjection();
RecalculateRayDirections();
}
float Camera::GetRotationSpeed()
{
return 0.3f;
}
void Camera::RecalculateProjection()
{
m_Projection = glm::perspectiveFov(glm::radians(m_VerticalFOV), (float)m_ViewportWidth, (float)m_ViewportHeight, m_NearClip, m_FarClip);
m_InverseProjection = glm::inverse(m_Projection);
}
void Camera::RecalculateView()
{
m_View = glm::lookAt(m_Position, m_Position + m_ForwardDirection, glm::vec3(0, 1, 0));
m_InverseView = glm::inverse(m_View);
}
void Camera::RecalculateRayDirections()
{
m_RayDirections.resize(m_ViewportWidth * m_ViewportHeight);
for (uint32_t y = 0; y < m_ViewportHeight; y++)
{
for (uint32_t x = 0; x < m_ViewportWidth; x++)
{
glm::vec2 coord = { (float)x / (float)m_ViewportWidth, (float)y / (float)m_ViewportHeight };
coord = coord * 2.0f - 1.0f; // -1 -> 1
glm::vec4 target = m_InverseProjection * glm::vec4(coord.x, coord.y, 1, 1);
glm::vec3 rayDirection = glm::vec3(m_InverseView * glm::vec4(glm::normalize(glm::vec3(target) / target.w), 0)); // World space
m_RayDirections[x + y * m_ViewportWidth] = rayDirection;
}
}
}
根据glm库文件的键盘输入,实时根据输入的按键或是鼠标的移动进行监听和渲染视角。
在主函数中改变如下:
// Renderer.cpp
glm::vec4 Renderer::TraceRay(const Ray& ray)
{
float radius = 0.5f;
// rayDirection = glm::normalize(rayDirection);
// (bx^2 + by^2)t^2 + (2(axbx + ayby))t + (ax^2 + ay^2 - r^2) = 0
// where
// a = ray origin
// b = ray direction
// r = radius
// t = hit distance
//float a = coord.x * coord.x + coord.y * coord.y + coord.z * coord.z;
float a = glm::dot(ray.Direction, ray.Direction);
float b = 2.0f * glm::dot(ray.Origin, ray.Direction);
float c = glm::dot(ray.Origin, ray.Origin) - radius * radius;
// Quadratic formula discriminant:
// b^2 - 4ac
float discriminant = b * b - 4.0f * a * c;
if (discriminant < 0.0f)
{
return glm::vec4(0, 0, 0, 1);
}
// (-b +- sqrt(discriminant)) / 2a
float t0 = (-b + glm::sqrt(discriminant)) / (2.0f * a);
float closestT = (-b - glm::sqrt(discriminant)) / (2.0f * a);
//glm::vec3 h0 = rayOrigin + rayDirection * t0;
glm::vec3 hitPoint = ray.Origin + ray.Direction * closestT;
glm::vec3 normal = glm::normalize(hitPoint);
glm::vec3 lightDir = glm::normalize(glm::vec3(-1, -1, -1));
float d = glm::max(glm::dot(normal, -lightDir), 0.0f); // == cos(angle)
glm::vec3 sphereColor(1, 0, 1);
sphereColor *= d;
return glm::vec4(sphereColor, 1.0f); // 0xff000000
}
其中ray类是一个非常简单的头文件:
#pragma once
#include <glm/glm.hpp>
struct Ray
{
glm::vec3 Origin;
glm::vec3 Direction;
};
渲染函数如下:
void Renderer::Render(const Camera& camera)
{
//const glm::vec3& rayOrigin = camera.GetPosition();
Ray ray;
ray.Origin = camera.GetPosition();
// render every pixel
for (uint32_t y = 0; y < m_FinalImage->GetHeight(); ++y)
{
for (uint32_t x = 0; x < m_FinalImage->GetWidth(); ++x)
{
//glm::vec2 coord = { (float)x / (float)m_FinalImage->GetWidth(), (float)y / (float)m_FinalImage->GetHeight() };
//coord = coord * 2.0f - 1.0f; // -1 -> 1
ray.Direction = camera.GetRayDirections()[x + y * m_FinalImage->GetWidth()];
glm::vec4 color = TraceRay(ray);
color = glm::clamp(color, glm::vec4(0.0f), glm::vec4(1.0f));
m_ImageData[x + y * m_FinalImage->GetWidth()] = Utils::ConvertToRGBA(color);
}
}
m_FinalImage->SetData(m_ImageData);
}
运行结果如下,可以像在unity初始页面一样旋转视角:
release编译使得渲染过程快乐很多,渲染一次平均9ms,也就是接近一百帧的速度,可以说对于游戏的测评速度,是十分快了。
旋转视角的时候会掉帧到20ms一次左右,大致是每秒50帧,就不是很高了,可以说速度并不是很好。