In this tutorial, I explain exactly what ray casting is. You'll get to see my cool working example which is running the source code as available to you listed on this page. Mouse picking is another name for ray casting, so if you're looking to learn that, then this video is for you also.
Note: The video will be published in the next few days, but if you're watching this on YouTube, then it'll already be appearing here!
- You'll learn how to cast a ray from screen space, to the near plane of the view frustum, through 3D space
- You'll learn how to calculate the intersect point where the ray passes through a sphere, and we'll compare that with “glm::unProject”
- You'll learn how to transform the position of the sphere in 3D space, along the X, Y, and Z axes, by using the mouse
- You'll learn how to create a coloured and transparent hotspot on the sphere's surface by using shaders
File: main.cpp
- #include <glad/glad.h>
- #include <GLFW/glfw3.h>
-
- #define STB_IMAGE_IMPLEMENTATION
- #include "stb_image.h"
-
- // OpenGL Mathematics(GLM)
- // -----------------------
- #include <glm/glm.hpp>
- #include <glm/gtc/matrix_transform.hpp>
- #include <glm/gtc/type_ptr.hpp>
-
- #include <assimp/Importer.hpp>
- #include <assimp/scene.h>
- #include <assimp/postprocess.h>
-
- #include <vector>
- #include <iostream>
- #include <fstream>
-
- #include "load_model_meshes.h"
- #include "shader_configure.h"
-
- double mouse_wheel_val = 0;
- void mouse_wheel_callback(GLFWwindow* window, double xoffset, double yoffset)
- {
- mouse_wheel_val = yoffset;
- }
-
- int main()
- {
- // (1) GLFW: Initialise & Configure
- // --------------------------------
- if (!glfwInit())
- exit(EXIT_FAILURE);
-
- glfwWindowHint(GLFW_SAMPLES, 4); // Anti-aliasing (applies to glEnable(GL_MULTISAMPLE) further down)
- glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
- glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
- glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
-
- const GLFWvidmode* mode = glfwGetVideoMode(glfwGetPrimaryMonitor());
-
- int monitor_width = mode->width; // Monitor's width
- int monitor_height = mode->height;
-
- int window_width = (int)(monitor_width * 0.9f); // Set window size to % of monitor size
- int window_height = (int)(monitor_height * 0.9f);
-
- GLFWwindow* window = glfwCreateWindow(window_width, window_height, "OpenGL Ray Casting - Mouse Picking Technique", NULL, NULL);
-
- if (!window)
- {
- glfwTerminate();
- exit(EXIT_FAILURE);
- }
- glfwMakeContextCurrent(window); // Set the window to be used
- glfwSetWindowPos(window, (monitor_width - window_width) / 2, (monitor_height - window_height) / 2); // Centre the window
-
- glfwSwapInterval(1); // Set VSync rate 1:1 with monitor's refresh rate
- glfwSetScrollCallback(window, mouse_wheel_callback);
-
- // (2) GLAD: Load OpenGL Function Pointers
- // ---------------------------------------
- if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
- {
- glfwTerminate();
- exit(EXIT_FAILURE);
- }
- glEnable(GL_DEPTH_TEST);
- glEnable(GL_MULTISAMPLE);
- glEnable(GL_BLEND);
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-
- // (3) Compile Shaders Read from Text Files
- // ----------------------------------------
- const char* vert_shader = "../../Shaders/shader_glsl.vert";
- const char* frag_shader = "../../Shaders/shader_glsl.frag";
-
- Shader shader(vert_shader, frag_shader);
- shader.use();
-
- unsigned int view_matrix_loc = glGetUniformLocation(shader.ID, "view");
- unsigned int projection_matrix_loc = glGetUniformLocation(shader.ID, "projection");
-
- unsigned int camera_position_loc = glGetUniformLocation(shader.ID, "camera_position");
- unsigned int image_sampler_loc = glGetUniformLocation(shader.ID, "image");
-
- // (4) Load Model & Set Camera
- // ---------------------------
- Model plane("Radius 2 Size Sphere.obj");
-
- glm::vec3 camera_position(0.0f, 0.0f, 5.0f); // -Z is into the screen
- glm::vec3 camera_target(0.0001f, 0.0f, -1.0f);
- glm::vec3 camera_up(0.0f, 1.0f, 0.0f);
-
- glUniform3f(camera_position_loc, camera_position.x, camera_position.y, camera_position.z);
-
- glActiveTexture(GL_TEXTURE0); // Reusing the same texture unit for each model mesh
- glUniform1i(image_sampler_loc, 0);
-
- glm::mat4 view = glm::lookAt(camera_position, camera_target, camera_up);
- glUniformMatrix4fv(view_matrix_loc, 1, GL_FALSE, glm::value_ptr(view)); // Transfer view matrix to vertex shader uniform
-
- constexpr float FOV = glm::radians(70.0f);
- float near_plane = 0.5f;
-
- glm::mat4 projection = glm::perspective(FOV, (float)window_width / (float)window_height, near_plane, 75.0f);
- glUniformMatrix4fv(projection_matrix_loc, 1, GL_FALSE, glm::value_ptr(projection));
-
- // (5) Setup Random Values
- // -----------------------
- /* srand((unsigned)time(NULL)); // Initialise random seed
-
- float x_spin = 1.0f / (rand() % 10 + 1); // Generate random number between 1 and 10
- float y_spin = 1.0f / (rand() % 10 + 1);
- float z_spin = 1.0f / (rand() % 10 + 1);
- float spin_speed = (float)(rand() % 3 + 1);
-
- float spin_vary = 0.0f;
- int spin_dir = 1; */
-
- // (6) Sphere Variables
- // --------------------
- unsigned int sphere_trans_mat_loc = glGetUniformLocation(shader.ID, "sphere_trans_mat");
- unsigned int ray_intersect_point_loc = glGetUniformLocation(shader.ID, "ray_intersect_point");
-
- float sphere_radius = 2.0f;
-
- glm::mat4 sphere_rot_mat(1.0f);
- glm::mat4 sphere_pos_mat(1.0f);
- glm::mat4 sphere_trans_mat(1.0f);
- sphere_pos_mat = glm::translate(sphere_pos_mat, glm::vec3(0.0f, 0.0f, -7.0f)); // Change sphere position from Blender 0,0,0
-
- while (!glfwWindowShouldClose(window)) // Main Loop
- {
- // (7) Ray Projection - Part 1 (Casting the Ray)
- // ---------------------------------------------
- double mousePosX, mousePosY;
- glfwGetCursorPos(window, &mousePosX, &mousePosY);
-
- // Casting the Ray Ourselves
- // -------------------------
- float NDC_X = ((int)mousePosX * (2.0f / window_width)) - 1;
- float NDC_Y = -((int)mousePosY * (2.0f / window_height)) + 1; // GLFW is mouse Y at top = 0... so OpenGL (GLM) needs it negating
-
- float near_plane_height = glm::tan(FOV / 2.0f) * near_plane;
- float aspect_ratio = (float)window_width / (float)window_height;
-
- float X_3D = NDC_X * near_plane_height * aspect_ratio;
- float Y_3D = NDC_Y * near_plane_height;
-
- glm::vec3 near_plane_point(X_3D, Y_3D, -near_plane); // Point is now in accordance with the camera in 3D space on the near plane
- near_plane_point = glm::inverse(view) * glm::vec4(near_plane_point, 1.0f); // Point true position in 3D space
- glm::vec3 cam_dir_vec = near_plane_point - camera_position;
-
- // Casting the Ray with glm::unProject
- // -----------------------------------
- glm::vec3 unProject = glm::unProject(glm::vec3(mousePosX, window_height - mousePosY, 0.0f), view, projection, glm::vec4(0, 0, window_width, window_height));
- glm::vec3 unProj_dir_vec = unProject - camera_position;
-
- /*std::cout << "\n cam_dir_vec.x = " << cam_dir_vec.x << " -- unProj_dir_vec.x = " << unProj_dir_vec.x << " -- cam_dir_vec.y = " << cam_dir_vec.y
- << " -- unProj_dir_vec.y = " << unProj_dir_vec.y << " -- cam_dir_vec.z = " << cam_dir_vec.z << " -- unProj_dir_vec.z = " << unProj_dir_vec.z;*/
-
- glm::vec3 ray_direction = glm::normalize(cam_dir_vec);
- //glm::vec3 ray_direction = glm::normalize(unProj_dir_vec);
-
-
- // (8) Ray Projection - Part 1B (Transforming the Sphere)
- // ------------------------------------------------------
- // Option 1: Mouse Control
- // -----------------------
- float near_Z_to_sphere_Z = sphere_pos_mat[3][2] - near_plane_point.z; // [3][2] = sphere Z value
- float ray_multiple_to_sphere_Z = near_Z_to_sphere_Z / ray_direction.z;
-
- glm::vec3 ray_point_at_sphere_Z = near_plane_point + (ray_direction * ray_multiple_to_sphere_Z);
- //std::cout << "\n ray_point_at_sphere_Z.x = " << ray_point_at_sphere_Z.x << " -- ray_point_at_sphere_Z.y = " << ray_point_at_sphere_Z.y;
-
- float difference_X = ray_point_at_sphere_Z.x - sphere_pos_mat[3][0];
- float difference_Y = ray_point_at_sphere_Z.y - sphere_pos_mat[3][1];
-
- sphere_pos_mat = glm::translate(sphere_pos_mat, glm::vec3(difference_X * 0.03f, difference_Y * 0.03f, mouse_wheel_val * 0.5f));
- mouse_wheel_val = 0;
-
- sphere_trans_mat = sphere_pos_mat;
- glUniformMatrix4fv(sphere_trans_mat_loc, 1, GL_FALSE, glm::value_ptr(sphere_trans_mat));
-
- // Option 2: Random Orbits
- // -----------------------
- //spin_vary += 0.05f * spin_dir;
-
- //if (spin_vary > 6 || spin_vary < 0)
- //{
- // spin_dir = -spin_dir; // Reverse the spinning direction
-
- // x_spin = 1.0f / (rand() % 10 + 1);
- // y_spin = 1.0f / (rand() % 10 + 1);
- // z_spin = 1.0f / (rand() % 10 + 1);
- // spin_speed = (float)(rand() % 3 + 1);
- //}
- //sphere_rot_mat = glm::rotate(sphere_rot_mat, glm::radians(spin_speed), glm::vec3(x_spin, y_spin, z_spin));
- //sphere_trans_mat = sphere_rot_mat * sphere_pos_mat; // Global transform
- //glUniformMatrix4fv(sphere_trans_mat_loc, 1, GL_FALSE, glm::value_ptr(sphere_trans_mat));
-
-
- // (9) Ray Projection - Part 2 (Calculating the Sphere Intersection Point)
- // -----------------------------------------------------------------------
- glm::vec3 sphere_position = sphere_trans_mat * glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
- glm::vec3 camera_to_sphere_centre = sphere_position - camera_position;
-
- float main_angular_difference_dot = glm::dot(ray_direction, glm::normalize(camera_to_sphere_centre));
- float main_angular_difference_radians = glm::max(0.00001f, glm::acos(main_angular_difference_dot));
- float main_angular_difference_degrees = glm::degrees(main_angular_difference_radians);
- //std::cout << "\n main_angular_difference_degrees = " << main_angular_difference_degrees;
-
- float camera_to_sphere_length = glm::length(camera_to_sphere_centre);
- float camera_to_sphere_opposite = glm::sin(main_angular_difference_radians) * camera_to_sphere_length;
-
- float small_triangle1_radians = glm::asin(camera_to_sphere_opposite / sphere_radius);
- float small_triangle1_degrees = glm::degrees(small_triangle1_radians);
-
- float angle_180_deg_difference = 180.0f - small_triangle1_degrees;
- float small_triangle2_degrees = 180.0f - angle_180_deg_difference - main_angular_difference_degrees;
- float small_triangle2_opposite = glm::sin(glm::radians(small_triangle2_degrees)) * sphere_radius;
-
- float ray_intersect_length = small_triangle2_opposite / glm::sin(main_angular_difference_radians);
- glm::vec3 ray_intersect_point = camera_position + (ray_direction * ray_intersect_length);
-
- /*std::cout << "\n ray_intersect_point.x = " << ray_intersect_point.x << " --- ray_intersect_point.y = " << ray_intersect_point.y
- << " --- ray_intersect_point.z = " << ray_intersect_point.z;*/
-
- glUniform3f(ray_intersect_point_loc, ray_intersect_point.x, ray_intersect_point.y, ray_intersect_point.z);
-
-
- // (10) Clear the Screen & Draw Model Meshes
- // -----------------------------------------
- glClearColor(0.30f, 0.55f, 0.65f, 1.0f);
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-
- for (unsigned int i = 0; i < plane.num_meshes; ++i)
- {
- glBindTexture(GL_TEXTURE_2D, plane.mesh_list[i].tex_handle); // Bind texture for the current mesh
-
- glBindVertexArray(plane.mesh_list[i].VAO);
- glDrawElements(GL_TRIANGLES, (GLsizei)plane.mesh_list[i].vert_indices.size(), GL_UNSIGNED_INT, 0);
- glBindVertexArray(0);
- }
- glfwSwapBuffers(window);
- glfwPollEvents();
- }
- // (11) Exit the Application
- // -------------------------
- glDeleteProgram(shader.ID);
- glfwTerminate();
- exit(EXIT_SUCCESS);
- }
File shader_configure.h and file load_model_meshes.h can be found here: OpenGL Tutorial 5 (Quick Start) – Model Loading – Assimp Blender & Lighting
File: main.cpp
- #version 420 core
-
- layout (location = 0) in vec3 aPos; // Attribute data: vertex(s) X, Y, Z position received via VBO on the CPU side
- layout (location = 1) in vec3 aNormal;
- layout (location = 2) in vec2 aTexCoord;
-
- out vec3 vertex_normal;
- out vec2 texture_coordinates;
- out vec3 vert_pos_transformed; // Transformed model vertex position passed to fragment shader for lighting
-
- out float aPos_dist_from_ray_intersect;
-
- uniform mat4 sphere_trans_mat;
- uniform vec3 ray_intersect_point; // Where the ray hits the sphere
-
- uniform mat4 view;
- uniform mat4 projection;
-
- //void main()
- //{
- // texture_coordinates = aTexCoord;
- // vert_pos_transformed = aPos;
- // vertex_normal = aNormal;
- //
- // aPos_dist_from_ray_intersect = abs(length(vert_pos_transformed - ray_intersect_point));
- //
- // gl_Position = projection * view * vec4(aPos, 1.0);
- //}
-
- void main()
- {
- texture_coordinates = aTexCoord;
- vert_pos_transformed = vec3(sphere_trans_mat * vec4(aPos, 1.0));
-
- mat3 normal_matrix = transpose(inverse(mat3(sphere_trans_mat)));
- vertex_normal = normal_matrix * aNormal;
-
- if (length(vertex_normal) > 0)
- vertex_normal = normalize(vertex_normal);
-
- aPos_dist_from_ray_intersect = abs(length(vert_pos_transformed - ray_intersect_point));
-
- gl_Position = projection * view * sphere_trans_mat * vec4(aPos, 1.0);
- }
File: main.cpp
- #version 420 core
-
- out vec4 fragment_colour;
-
- // Must be the exact same name as declared in the vertex shader
- // ------------------------------------------------------------
- in vec3 vert_pos_transformed; // Transformed vertex position coordinates received as interpolated
- in vec3 vertex_normal;
- in vec2 texture_coordinates;
- in float aPos_dist_from_ray_intersect;
-
- uniform sampler2D image;
- uniform vec3 camera_position; // camera_position is set in main() on the CPU side
-
- void main()
- {
- vec3 view_direction = normalize(camera_position - vert_pos_transformed);
-
- vec3 light_position = vec3(0.0, 20.0, 0.0); // Light position in 3D space
- vec3 light_direction = normalize(vec3(light_position - vert_pos_transformed));
-
- vec4 image_colour = texture(image, texture_coordinates);
-
- float ambient_factor = 0.95; // Intensity multiplier
- vec4 ambient_result = vec4(ambient_factor * image_colour.rgb, 1.0);
-
- float diffuse_factor = 0.75;
- float diffuse_angle = max(dot(light_direction, vertex_normal), -0.1); // [-1.0 to 0] Results in darker lighting past 90 degrees
- vec4 diffuse_result = vec4(diffuse_factor * diffuse_angle * image_colour.rgb, 1.0);
-
- vec3 specular_colour = vec3(0.5, 0.5, 0.5);
- vec3 reflect_direction = normalize(reflect(-light_direction, vertex_normal)); // Notice the light direction is negated here
- float specular_strength = pow(max(dot(view_direction, reflect_direction), 0), 32);
- vec4 specular_result = vec4(specular_colour * specular_strength, 1.0);
-
- fragment_colour = ambient_result + diffuse_result + specular_result;
-
- if (aPos_dist_from_ray_intersect > 0)
- fragment_colour.g += 0.1 / aPos_dist_from_ray_intersect;
- // fragment_colour.a -= 0.5 / aPos_dist_from_ray_intersect;
- }