To change the position, rotation, or size of 3D models as rendered in graphics programming, their vertex positional values are multiplied by different transformation matrices. Translating vertex X, Y, Z positional values requires simply adding or subtracting the movement amount. Similarly, scaling requires simply multiplying vertex positional values by a scaling value.
However, rotation is appreciably more complicated, because even for rotation around a single axis, then per vertex, it requires multiplying its X, Y, Z values by the sine or cosine of the angle, about which the other two axes rotate, the results of which are then added accordingly – increasing or cancelling by varying amounts to produce the new X, Y or Z value.
Conveniently, the OpenGL maths library (GLM) includes all the transformation matrices we need; therefore we don’t need to derive them ourselves. The program demonstrated in the video below makes use of... glm::translate, glm::rotate and glm::scale, to produce one stationary spinning cube and five orbiting spinning cubes.
#include <glad/glad.h> // GLAD: https://github.com/Dav1dde/glad ... GLAD 2 also works via the web-service: https://gen.glad.sh/ (leaving all checkbox options unchecked) #include <GLFW/glfw3.h> // OpenGL Mathematics(GLM) ... https://github.com/g-truc/glm/blob/master/manual.md // ------------------------------------ // GLM Headers // ------------------ #include <glm/glm.hpp> // Include all GLM core. // #include <glm/ext.hpp> // Include all GLM extensions. #include <glm/gtc/matrix_transform.hpp> // Specific extensions. #include <glm/gtc/type_ptr.hpp> #include <iostream> #include <fstream> // Used in "shader_configure.h" to read the shader text files. #include <sstream> #include "shader_configure.h" // Used to create the shaders. // Function Prototypes // --------------------------- void update_orbit(glm::mat4& orbit_mat, glm::vec3& orbit_vec, glm::vec3 const& orbit_axis, float angle, glm::vec3& orbit_pos); void update_norm_vec(glm::mat4& orbit_mat, glm::mat4& spinning_mat, glm::vec3& face); void draw_cube(unsigned VAO, unsigned cube_indices_bytes, unsigned orbit_loc, float* orbit_mat); int main() { // (1) GLFW: Initialise & Configure // ----------------------------------------- if (!glfwInit()) exit(EXIT_FAILURE); 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.75f); // Window size will be 75% the monitor's size. int window_height = (int)(monitor_height * 0.75f); // Cast is simply to silence the compiler warning. GLFWwindow* window = glfwCreateWindow(window_width, window_height, "Spinning Cubes Shining Light Beams – Matrix Transformations", NULL, NULL); // GLFWwindow* window = glfwCreateWindow(window_width, window_height, "Spinning Cubes Shining Light Beams", glfwGetPrimaryMonitor(), NULL); // Full Screen Mode ("Alt" + "F4" to Exit!) if (!window) { glfwTerminate(); exit(EXIT_FAILURE); } glfwMakeContextCurrent(window); // Set the window to be used and then centre that window on the monitor. glfwSetWindowPos(window, (monitor_width - window_width) / 2, (monitor_height - window_height) / 2); glfwSwapInterval(1); // Set VSync rate 1:1 with monitor's refresh rate. // (2) GLAD: Load OpenGL Function Pointers // ------------------------------------------------------- if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) // For GLAD 2 use the following instead: gladLoadGL(glfwGetProcAddress) { glfwTerminate(); exit(EXIT_FAILURE); } glEnable(GL_DEPTH_TEST); // Enabling depth testing allows rear faces of 3D objects to be hidden behind front faces. // (3) Compile Shaders Read from Text Files // ------------------------------------------------------ const char* vert_shader = "../../Shaders/shader_glsl.vert"; const char* frag_shader = "../../Shaders/shader_glsl.frag"; Shader main_shader(vert_shader, frag_shader); main_shader.use(); // (4) Declare the Cube's Vertex Position Values // ----------------------------------------------------------- float cube_vertices[] = { // Note: "Being able to store the vertex attributes for that vertex only once is very economical, as a vertex's attribute... // data is generally around 32 bytes, while indices are usually 2-4 bytes in size." (Hence, glDrawElements() further down) -1.0f, -1.0f, 1.0f, // left, bottom, rear ..... 0 -1.0f, -1.0f, -1.0f, // left, bottom, front .... 1 -1.0f, 1.0f, 1.0f, // left, top, rear ............ 2 -1.0f, 1.0f, -1.0f, // left, top, front .......... 3 1.0f, -1.0f, 1.0f, // right, bottom, rear .... 4 1.0f, -1.0f, -1.0f, // right, bottom, front ... 5 1.0f, 1.0f, 1.0f, // right, top, rear ........... 6 1.0f, 1.0f, -1.0f // right, top, front ......... 7 }; // glDrawArrays() vs glDrawElements() // ---------------------------------------------- // Typical bytes being used here via using glDrawElements() = 8 * 32 + 36 * 3 = 364 bytes. // If we were using glDrawArrays(), then: 36 vertices (instead of the 8 above) * typically 32 bytes = 1152 bytes. // Saving 28 vertices: 28 * 32 = 896 bytes, but at the expense of 36 indices x typically 3 bytes = 108 bytes. // Therefore the final saving then becomes: 896 - 108 = 788 bytes, which is a 68.4% reduction. unsigned int cube_indices[] = // Drawing 6 faces = 12 triangles. { 1,0,5, 5,0,4, 5,4,7, 7,4,6, 0,2,4, 4,2,6, 3,2,1, 1,2,0, 3,1,7, 7,1,5, 2,3,6, 6,3,7 }; // (5) Store the Cube's Vertex Data in Buffer Objects Ready for Drawing // ------------------------------------------------------------------------------------------ unsigned int VAO, VBO, EBO; glGenVertexArrays(1, &VAO); glGenBuffers(1, &VBO); glGenBuffers(1, &EBO); glBindVertexArray(VAO); // Binding this VAO 1st causes the following VBO and EBO to become associated with this VAO. glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(cube_vertices), cube_vertices, GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(cube_indices), cube_indices, GL_STATIC_DRAW); glEnableVertexAttribArray(0); // Void pointer is for legacy reasons. Two possible meanings: "offset for buffer objects" & "address for client state arrays" glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); glBindVertexArray(0); // Unbind VAO // (6) Declare & Set Up the Various Matrices... https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glUniform.xhtml // ------------------------------------------------------- unsigned int model_num_loc = glGetUniformLocation(main_shader.ID, "model_num"); // Accessed directly in both shaders. // Used only in the vertex shader // ---------------------------------------- unsigned int view_matrix_loc = glGetUniformLocation(main_shader.ID, "view"); unsigned int projection_matrix_loc = glGetUniformLocation(main_shader.ID, "projection"); unsigned int scaling_mat_loc = glGetUniformLocation(main_shader.ID, "scaling_mat"); unsigned int spinning_mat_loc = glGetUniformLocation(main_shader.ID, "spinning_mat"); unsigned int orbiting_cube_loc = glGetUniformLocation(main_shader.ID, "orbiting_cube"); unsigned int outer_cube_loc = glGetUniformLocation(main_shader.ID, "outer_cube"); // Used only in the fragment shader // -------------------------------------------- unsigned int orbit_pos_loc = glGetUniformLocation(main_shader.ID, "orbit_pos"); unsigned int face_dir_loc = glGetUniformLocation(main_shader.ID, "face_dir"); glm::vec3 cameraPos(0.0f, 0.0f, 20.0f); glm::vec3 camTarget(0.0f, 0.0f, 0.0f); glm::vec3 cameraUp(0.0f, 1.0f, 0.0f); glm::mat4 view = glm::lookAt(cameraPos, camTarget, cameraUp); glUniformMatrix4fv(view_matrix_loc, 1, GL_FALSE, glm::value_ptr(view)); // Uniform: Transfer view matrix to vertex shader. glm::mat4 projection = glm::perspective(glm::radians(55.0f), (float)window_width / (float)window_height, 0.1f, 100.0f); glUniformMatrix4fv(projection_matrix_loc, 1, GL_FALSE, glm::value_ptr(projection)); glm::mat4 scaling_mat(1.0f); // Initialise to identity matrix. scaling_mat = glm::scale(scaling_mat, glm::vec3(0.5f, 0.5f, 0.5f)); glUniformMatrix4fv(scaling_mat_loc, 1, GL_FALSE, glm::value_ptr(scaling_mat)); glm::mat4 outer_cube(1.0f); // Initialise to identity matrix. outer_cube = glm::scale(outer_cube, glm::vec3(20.0f, 20.0f, 20.0f)); outer_cube = glm::rotate(outer_cube, glm::radians(45.0f), glm::vec3(0, 1, 0)); // Rotation axes are typically recommended to be normalised. outer_cube = glm::rotate(outer_cube, glm::radians(75.0f), glm::vec3(1, 0, 1)); // https://glm.g-truc.net/0.9.4/api/a00151.html#gaacb9cbe8f93a8fef9dc3e25559df19c0 glm::mat4 spinning_mat(1.0f); // Initialise to identity matrix. glm::mat4 orbit_1_mat(1.0f); glm::mat4 orbit_2_mat(1.0f); glm::mat4 orbit_3_mat(1.0f); glm::mat4 orbit_4_mat(1.0f); glm::mat4 orbit_5_mat(1.0f); glm::vec3 orbit_1_vec = glm::vec3(-4.5f, 0.0f, 0.0f); orbit_1_mat = glm::translate(orbit_1_mat, orbit_1_vec); // Translate to initial orbit position. glm::vec3 orbit_2_vec = glm::vec3(0.0f, 4.5f, 0.0f); orbit_2_mat = glm::translate(orbit_2_mat, -orbit_2_vec); glm::vec3 orbit_3_vec = glm::vec3(0.0f, 0.0f, 4.5f); orbit_3_mat = glm::translate(orbit_3_mat, -orbit_3_vec); glm::vec3 orbit_4_vec = glm::vec3(3.5f, 0.0f, 3.5f); orbit_4_mat = glm::translate(orbit_4_mat, -orbit_4_vec); glm::vec3 orbit_5_vec = glm::vec3(-3.5f, 0.0f, 3.5f); orbit_5_mat = glm::translate(orbit_5_mat, -orbit_5_vec); // (7) Enter the Main-Loop // -------------------------------- 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() % 5 + 1); // Cast is simply to silence the compiler warning. float spin_vary = 0.0f; int spin_dir = 1; glm::vec3 face_dir[6]{}; glm::vec3 orbit_pos[6]{}; while (!glfwWindowShouldClose(window)) // Main-Loop { // (8) Randomise the Cube Spinning Speed & Axis // -------------------------------------------------------------- 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() % 5 + 1); } spinning_mat = glm::rotate(spinning_mat, glm::radians(spin_speed), glm::normalize(glm::vec3(x_spin, y_spin, z_spin))); // (9) Calculate the Transformation Matrices for the Orbiting Cubes (Typically classes would be used once things start to get complicated) // ------------------------------------------------------------------------------------- // (Temporarily moving back to centre (0, 0, 0) before rotating, and then returning along a different path) orbit_1_mat = glm::translate(orbit_1_mat, -orbit_1_vec); orbit_1_mat = glm::rotate(orbit_1_mat, glm::radians(0.5f), glm::vec3(0, 0, 1)); // Perpendicular axis to orbit. orbit_1_mat = glm::translate(orbit_1_mat, orbit_1_vec); orbit_pos[0] = orbit_1_mat * glm::vec4(0, 0, 0, 1); // Used to calculate an accurate line-of-site for specular-type lighting in fragment shader. // std::cout << "Orbit position: " << orbit_pos[0].x << " --- " << orbit_pos[0].y << " --- " << orbit_pos[0].z << "\n"; // (The order of multiplying matrices together matters) glm::mat4 transform = orbit_1_mat * spinning_mat; // Spin 1st... Apply orbit 2nd... Notice right to left (i.e., 1st operation is on the right) // (Cannot use "spinning_mat" directly because the "orbit_xx_mat"... translate >> rotate >> translate... also changes the rotation) glm::mat3 norm_corr_matrix = glm::transpose(glm::inverse(glm::mat3(transform))); face_dir[0] = glm::normalize(norm_corr_matrix * glm::vec3(1, 0, 0)); // Arbitrarily simply selecting the right face. // (Normalising not actually required unless scaling is involved) /* std::cout << " --- Normalised length: " << glm::length(norm_corr_matrix * glm::vec3(1, 0, 0)) << " --- Length without normalising: " << glm::length(glm::mat3(transform) * glm::vec3(1, 0, 0)) << "\n\n"; */ // (The above breakdown is for illustration purposes, i.e., the remaining orbit positions [1] to [5] below, all call the relevant functions) // ----------------------------------------------------------------- update_orbit(orbit_2_mat, orbit_2_vec, glm::vec3(1, 0, 0), 0.5f, orbit_pos[1]); update_norm_vec(orbit_2_mat, spinning_mat, face_dir[1]); update_orbit(orbit_3_mat, orbit_3_vec, glm::vec3(0, 1, 0), 0.5f, orbit_pos[2]); update_norm_vec(orbit_3_mat, spinning_mat, face_dir[2]); update_orbit(orbit_4_mat, orbit_4_vec, glm::vec3(1, 0, -1), 0.5f, orbit_pos[3]); update_norm_vec(orbit_4_mat, spinning_mat, face_dir[3]); update_orbit(orbit_5_mat, orbit_5_vec, glm::vec3(-1, 0, -1), -0.5f, orbit_pos[4]); update_norm_vec(orbit_5_mat, spinning_mat, face_dir[4]); orbit_pos[5] = glm::vec3(0, 0, 0); // This is the centre stationary spinning cube. face_dir[5] = glm::mat3(spinning_mat) * glm::vec3(1, 0, 0); glUniform3fv(face_dir_loc, 6, glm::value_ptr(face_dir[0])); // Uniform: Transfer matrices to array in fragment shader. glUniform3fv(orbit_pos_loc, 6, glm::value_ptr(orbit_pos[0])); // (10) Finally Clear the Screen & Draw Everything // -------------------------------------------------------------- glClearColor(0.75f, 0.55f, 0.35f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glUniform1i(model_num_loc, 1); // This is the very large outer cube that doesn't spin. draw_cube(VAO, sizeof(cube_indices), outer_cube_loc, glm::value_ptr(outer_cube)); glUniform1i(model_num_loc, 2); // This is the centre stationary spinning cube. draw_cube(VAO, sizeof(cube_indices), spinning_mat_loc, glm::value_ptr(spinning_mat)); glUniform1i(model_num_loc, 3); // These are all the small, orbiting spinning cubes. draw_cube(VAO, sizeof(cube_indices), orbiting_cube_loc, glm::value_ptr(orbit_1_mat)); draw_cube(VAO, sizeof(cube_indices), orbiting_cube_loc, glm::value_ptr(orbit_2_mat)); draw_cube(VAO, sizeof(cube_indices), orbiting_cube_loc, glm::value_ptr(orbit_3_mat)); draw_cube(VAO, sizeof(cube_indices), orbiting_cube_loc, glm::value_ptr(orbit_4_mat)); draw_cube(VAO, sizeof(cube_indices), orbiting_cube_loc, glm::value_ptr(orbit_5_mat)); glfwSwapBuffers(window); glfwPollEvents(); } // (11) Exit the Application // -------------------------------- glDeleteProgram(main_shader.ID); // This OpenGL function call is talked about in: shader_configure.h /* glfwDestroyWindow(window) // Call this function to destroy a specific window */ glfwTerminate(); // Destroys all remaining windows and cursors, restores modified gamma ramps, and frees resources. exit(EXIT_SUCCESS); // Function call: exit() is a C/C++ function that performs various tasks to help clean up resources. } void update_orbit(glm::mat4& orbit_mat, glm::vec3& orbit_vec, glm::vec3 const& orbit_axis, float angle, glm::vec3& orbit_pos) { orbit_mat = glm::translate(orbit_mat, orbit_vec); orbit_mat = glm::rotate(orbit_mat, glm::radians(angle), orbit_axis); orbit_mat = glm::translate(orbit_mat, -orbit_vec); // Not transferring all the positions, most noticeably causes all the smaller cubes, own face dot, to display on the larger centre spinning cube. // ------------------------------------------- orbit_pos = orbit_mat * glm::vec4(0, 0, 0, 1); // Transferred to the fragment shader in the main() while loop. } void update_norm_vec(glm::mat4& orbit_mat, glm::mat4& spinning_mat, glm::vec3& face) { glm::mat4 transform = orbit_mat * spinning_mat; glm::mat3 norm_corr_matrix = glm::transpose(glm::inverse(glm::mat3(transform))); face = glm::normalize(norm_corr_matrix * glm::vec3(1, 0, 0)); // Arbitrarily simply selecting the right face. } void draw_cube(unsigned VAO, unsigned cube_indices_bytes, unsigned orbit_loc, float* orbit_mat) { glUniformMatrix4fv(orbit_loc, 1, GL_FALSE, orbit_mat); glBindVertexArray(VAO); // Not necessary for this simple case of using only one VAO. Although, it's being unbound at initialisation further up. glDrawElements(GL_TRIANGLES, cube_indices_bytes / sizeof(float), GL_UNSIGNED_INT, 0); glBindVertexArray(0); }
#pragma once // Instead of using include guards. class Shader { public: GLuint ID; // Public Program ID. // Constructor // --------------- Shader(const char* vert_path, const char* frag_path) { char character; std::string vert_string; std::string frag_string; std::ifstream vert_stream; std::ifstream frag_stream; // Read vertex shader text file // ------------------------------------ vert_stream.open(vert_path); // I decided not to implement: Exception handling try catch method. if (vert_stream.is_open()) // Note: There are various other methods for accessing the stream, i.e., vert_stream.get() is just one option. { while (vert_stream.get(character)) // Loop getting single characters until EOF (value false) is returned. vert_string += character; // "The first signature returns the character read, or the end-of-file value (EOF) if no characters are available in the stream..." vert_stream.close(); std::cout << "File: " << vert_path << " opened successfully.\n\n"; } else std::cout << "ERROR!... File: " << vert_path << " could not be opened.\n\n"; // Read fragment shader text file // ---------------------------------------- frag_stream.open(frag_path); if (frag_stream.is_open()) { while (frag_stream.get(character)) frag_string += character; frag_stream.close(); std::cout << "File: " << frag_path << " opened successfully.\n\n"; } else std::cout << "ERROR!... File: " << frag_path << " could not be opened.\n\n"; std::cout << vert_string << "\n\n"; // Output the shader files to display in the console window. std::cout << frag_string << "\n\n"; const char* vert_pointer = vert_string.c_str(); const char* frag_pointer = frag_string.c_str(); // Compile shaders // ---------------------- GLuint vert_shad, frag_shad; // Declare in here locally. Being attached to the public Program ID allows the shaders to be used publicly. // Create vertex shader // --------------------------- vert_shad = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vert_shad, 1, &vert_pointer, NULL); glCompileShader(vert_shad); Check_Shaders_Program(vert_shad, "vert_shader"); // Create fragment shader // ------------------------------- frag_shad = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(frag_shad, 1, &frag_pointer, NULL); glCompileShader(frag_shad); Check_Shaders_Program(frag_shad, "frag_shader"); // Create shader program // ------------------------------ ID = glCreateProgram(); glAttachShader(ID, vert_shad); // This also avoids deletion via: glDeleteShader(vert_shad) as called below. glAttachShader(ID, frag_shad); glLinkProgram(ID); Check_Shaders_Program(ID, "shader_program"); // Note: Flagging the program object for deletion before calling "glUseProgram" would accidentally stop the program installation of the rendering state // ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- glDeleteShader(vert_shad); // Flag shader object for automatic deletion (freeing memory) when no longer attached to a program object... glDeleteShader(frag_shad); // ... program object is deleted (glDeleteProgram ) within: main() when the application ends. // glUseProgram(ID); // Typically this is called within: main() to select individual shaders that have been created. // glDeleteProgram(ID); // Alternatively the program object can be deleted here after 1st calling: glUseProgram(ID) } // Activate the shader // ------------------------- void use() { glUseProgram(ID); // Function called from within main() to select an individual shader to be used. } private: // Check shader compilations and program object for linking errors // ------------------------------------------------------------------------------------- void Check_Shaders_Program(GLuint type, std::string name) { int success; int error_log_size; char info_log[1000]; // 1000 characters max. Typically it's less than 500 even for multiple shader errors. if (name == "vert_shader" || name == "frag_shader") { glGetShaderiv(type, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(type, 1024, &error_log_size, info_log); std::cout << "\n--- Shader Compilation Error: " << name << "\n\n" << info_log << "\n" << "Error Log Number of Characters: " << error_log_size << "\n\n"; } } else // "shader_program" { glGetProgramiv(type, GL_LINK_STATUS, &success); if (!success) { glGetProgramInfoLog(type, 1024, &error_log_size, info_log); std::cout << "\n--- Program Link Error: " << name << "\n\n" << info_log << "\n" << "Error Log Number of Characters: " << error_log_size << "\n"; } } } };
#version 420 core layout (location = 0) in vec3 aPos; // Attribute data: vertex(s) X, Y, Z position via VBO set up on the CPU side. // layout (location = 1) in vec3 aNormal; // Not being used. out vec3 vert_pos_varying; // Vertex position coordinates passed to the fragment shader as interpolated per-vertex. out vec3 vert_pos_transformed; // Transformed cube vertex position coordinates also passed as interpolated. // Uniform variables passed from the CPU side // ---------------------------------------------------------- uniform int model_num; // This particular uniform is declared both here in the vertex shader and also within the fragment shader. uniform mat4 view; uniform mat4 projection; uniform mat4 scaling_mat; uniform mat4 spinning_mat; uniform mat4 orbiting_cube; uniform mat4 outer_cube; void main() { vert_pos_varying = aPos; // Send aPos vertex position values to the fragment shader, which are used as the initial colour values. mat4 mat_trans; if (model_num == 1) mat_trans = outer_cube; // Outer cube if (model_num == 2) mat_trans = spinning_mat; // Centre cube if (model_num == 3) mat_trans = orbiting_cube * spinning_mat * scaling_mat; // Orbiting cubes (Right-to-left multiplication order) // Notes for typical (Not being implemented for this demo) lighting via normal vectors // -------------------------------------------------------------------------------------------------------------- // mat3 norm_corr_matrix; // norm_corr_matrix = transpose(inverse(mat3(mat_trans))); // ---------------------------------------------------------------------------- // 1) aNormal: Rotation only (i.e., no translations) transformations don't require transforming via "norm_corr_matrix". // 2) If transformations involve scaling then normal vectors require normalising. // ----------------------------------------------------------------------------------------------------- // norm_vec = norm_corr_matrix * aNormal; // norm_vec = normalize(norm_vec); (It doesn't do any harm to simply normalise them anyway) (Never try to normalise 0,0,0 vectors) vert_pos_transformed = vec3(mat_trans * vec4(aPos, 1.0)); // Send transformed position values, which are used for the lighting effects. // https://www.khronos.org/opengl/wiki/Vertex_Post-Processing gl_Position = projection * view * mat_trans * vec4(aPos, 1.0); // Output to vertex stream for the "Vertex Post-Processing" stage. }
#version 420 core out vec4 fragment_colour; in vec3 vert_pos_varying; // Vertex position coordinates received from the fragment shader as interpolated per-vertex. in vec3 vert_pos_transformed; // Transformed cube vertex position coordinates also received as interpolated. // Uniform variables passed from the CPU side // ---------------------------------------------------------- uniform int model_num; // This particular uniform is declared both here in the fragment shader and also within the vertex shader. uniform vec3 orbit_pos[6]; uniform vec3 face_dir[6]; void main() { vec3 colour_rgb; colour_rgb.x = abs(vert_pos_varying.x); colour_rgb.y = abs(vert_pos_varying.y); colour_rgb.z = abs(vert_pos_varying.z); // Calculating the spatial direction from each cube's centre to the interpolated vertex position being rasterised... in 3D/World space // -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- vec3 light_dir_1 = normalize(orbit_pos[0] - vert_pos_transformed); // Each cube's light is actually being allowed to shine/pass through all the other cubes. vec3 light_dir_2 = normalize(orbit_pos[1] - vert_pos_transformed); vec3 light_dir_3 = normalize(orbit_pos[2] - vert_pos_transformed); vec3 light_dir_4 = normalize(orbit_pos[3] - vert_pos_transformed); vec3 light_dir_5 = normalize(orbit_pos[4] - vert_pos_transformed); vec3 light_dir_6 = normalize(orbit_pos[5] - vert_pos_transformed); int spec_intensity = 64; // Lower values result in shining a larger spot size onto other cube's faces (Values 6 to 128 work best) // Dot on 1 side... Calculating the angular difference between each cube's rotational face direction and the above spatial direction (and using some power of this dot product) // ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- float spec_a = pow(max(dot(face_dir[0], light_dir_1), 0), spec_intensity); // face_dir[i] have all been normalised on the CPU side. float spec_b = pow(max(dot(face_dir[1], light_dir_2), 0), spec_intensity); // For perpendicular vectors the dot product = 0 float spec_c = pow(max(dot(face_dir[2], light_dir_3), 0), spec_intensity); // ... and for parallel vectors in the same direction the dot product = 1 float spec_d = pow(max(dot(face_dir[3], light_dir_4), 0), spec_intensity); // ... and for parallel vectors in the opposite direction the dot product = -1 float spec_e = pow(max(dot(face_dir[4], light_dir_5), 0), spec_intensity); // max() because... "The result is undefined if x < 0 or if x = 0 and y <= 0" float spec_f = pow(max(dot(face_dir[5], light_dir_6), 0), spec_intensity); // ... https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/pow.xhtml // Dot on 2 sides // ------------------- // float spec_a = pow(abs(dot(face_dir[0], light_dir_1)), spec_intensity); // float spec_b = pow(abs(dot(face_dir[1], light_dir_2)), spec_intensity); // float spec_c = pow(abs(dot(face_dir[2], light_dir_3)), spec_intensity); // float spec_d = pow(abs(dot(face_dir[3], light_dir_4)), spec_intensity); // float spec_e = pow(abs(dot(face_dir[4], light_dir_5)), spec_intensity); // float spec_f = pow(abs(dot(face_dir[5], light_dir_6)), spec_intensity); // "A numeric literal that uses a decimal is by default of type float. To create a float literal from an integer value, use the suffix f or F as in C/C++" // ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ vec3 col_a = vec3(102.0 / 255, 0.0 / 255, 102.0 / 255); // Set individual specular colours here: OpenGL RGB [0, 1] = Actual RGB / 255 vec3 col_b = vec3(51.0 / 255, 102.0 / 255, 204.0 / 255); vec3 col_c = vec3(153.0 / 255, 237.0 / 255, 0.0 / 255); vec3 col_d = vec3(170.0 / 255, 102.0 / 255, 0.0 / 255); vec3 col_e = vec3(204.0 / 255, 0.0 / 255, 255.0 / 255); vec3 col_f = vec3(255.0 / 255, 80.0 / 255, 80.0 / 255); vec3 specular_light = col_a * spec_a + col_b * spec_b + col_c * spec_c + col_d * spec_d + col_e * spec_e + col_f * spec_f; colour_rgb += specular_light * 2.5; // Apply the main specular light beam effect with intensity. if (model_num == 1) colour_rgb *= 0.5; // Colour intensity of outer cube. else colour_rgb *= 0.6; // Colour intensity of all spinning cubes. if (length(orbit_pos[0] - vert_pos_transformed) < 0.75) // Targeting only a close distance from the cube's centre... Purple if (spec_a > 0.015) // Set the dot size on cube face based on the specular value. colour_rgb = col_a * 1.25; // Set dot colour and intensity on cube face. if (length(orbit_pos[1] - vert_pos_transformed) < 0.75) // orbit_pos[0] to [4] are the orbiting cubes... Blue if (spec_b > 0.015) // Only 0.75 required above because orbiting cubes VBO vertices are scaled smaller in vertex shader to 0.5 from centre. colour_rgb = col_b * 1.0; if (length(orbit_pos[2] - vert_pos_transformed) < 0.75) // Green if (spec_c > 0.015) colour_rgb = col_c * 1.0; if (length(orbit_pos[3] - vert_pos_transformed) < 0.75) // Orange if (spec_d > 0.015) colour_rgb = col_d * 1.0; if (length(orbit_pos[4] - vert_pos_transformed) < 0.75) // Pink if (spec_e > 0.015) colour_rgb = col_e * 1.0; if (length(orbit_pos[5] - vert_pos_transformed) < 1.25) // [5] = The centre stationary spinning cube... Red if (spec_f > 0.015) colour_rgb = col_f * 1.0; // Uncomment to enable ribbon pattern // ------------------------------------------------- // if (abs(vert_pos_varying.x) < 0.1) // colour_rgb = col_d * 1.0; // // if (abs(vert_pos_varying.y) < 0.1) // colour_rgb = col_e * 1.0; fragment_colour = vec4(colour_rgb, 1.0); // The resulting fragment colour that will be displayed. }