Very briefly explained, once you have a 3D axis, you start by drawing the 3D axis at camera.Position + camera.Front * smallDist
, so it always draws at the center of the screen.
Then, you use glViewport(...)
to set the viewport to the corner of the screen, and draw the axis. You can also use eg. glLineWidth(3.0f)
to make the lines stand out more.
Here's a rough idea:
#include <iostream>
#include <vector>
#include <math.h>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/ext.hpp>
// camera.h is from basic camera learnopengl tutorial
#include "camera.h"
using glm::mat4;
using glm::vec3;
using glm::radians;
using glm::lookAt;
using std::vector;
void processInput(GLFWwindow *window);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
float lastX = SCR_WIDTH / 2.0f;
float lastY = SCR_HEIGHT / 2.0f;
bool firstMouse = true;
// timing
float deltaTime = 0.0f; // time between current frame and last frame
float lastFrame = 0.0f;
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
class Line {
int shaderProgram;
unsigned int VBO, VAO;
vector<float> vertices;
vec3 startPoint;
vec3 endPoint;
mat4 MVP = mat4(1.0);
vec3 lineColor;
public:
Line(vec3 start, vec3 end) {
startPoint = start;
endPoint = end;
lineColor = vec3(1,1,1);
const char *vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"uniform mat4 MVP;\n"
"void main()\n"
"{\n"
" gl_Position = MVP * vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
const char *fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"uniform vec3 color;\n"
"void main()\n"
"{\n"
" FragColor = vec4(color, 1.0f);\n"
"}\n\0";
// vertex shader
int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
// check for shader compile errors
// fragment shader
int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
// check for shader compile errors
// link shaders
shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
// check for linking errors
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
vertices = {
start.x, start.y, start.z,
end.x, end.y, end.z,
};
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices)*vertices.size(), vertices.data(), GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
int setMVP(mat4 mvp) {
MVP = mvp;
return 1;
}
int setColor(vec3 color) {
lineColor = color;
return 1;
}
int draw() {
glUseProgram(shaderProgram);
glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "MVP"), 1, GL_FALSE, &MVP[0][0]);
glUniform3fv(glGetUniformLocation(shaderProgram, "color"), 1, &lineColor[0]);
glBindVertexArray(VAO);
glDrawArrays(GL_LINES, 0, 2);
return 1;
}
~Line() {
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteProgram(shaderProgram);
}
};
int main()
{
// glfw: initialize and configure
// ------------------------------
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
// glfw window creation
// --------------------
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "drawing lines", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
glfwMakeContextCurrent(window);
glfwSetCursorPosCallback(window, mouse_callback);
// glad: load all OpenGL function pointers
// ---------------------------------------
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
camera.Position = vec3(0,0,3);
camera.MovementSpeed = 0.01f;
// 3d lines example
Line line1(vec3(0,0,0), vec3(0.04,0,0));
line1.setColor(vec3(1,0,0));
Line line2(vec3(0,0,0), vec3(0,0.04,0));
line2.setColor(vec3(0,1,0));
Line line3(vec3(0,0,0), vec3(0,0,0.04));
line3.setColor(vec3(0,0,1));
Line line4(vec3(0,0,0), vec3(10,0,0));
line4.setColor(vec3(1,0,0));
Line line5(vec3(0,0,0), vec3(0,10,0));
line5.setColor(vec3(0,1,0));
Line line6(vec3(0,0,0), vec3(0,0,10));
line6.setColor(vec3(0,0,1));
glm::mat4 projection = glm::perspective(glm::radians(45.0f), (float) SCR_WIDTH / (float)SCR_HEIGHT, 0.01f, 1000.0f);
// render loop
// -----------
while (!glfwWindowShouldClose(window))
{
float currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
// input
// -----
processInput(window);
// render
// ------
glClearColor(0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.01f, 1000.0f);
glm::mat4 view = camera.GetViewMatrix();
float ar = (float)SCR_WIDTH / (float)SCR_HEIGHT;
float fov = 45.0f;
float nearDist = 0.01f;
float Hnear = 2.0f * tan(glm::radians(fov/2)) * nearDist;
float Wnear = Hnear * ar;
glm::vec3 axisPosition = camera.Position + glm::normalize(camera.Front) * 0.2f;
line1.setMVP(projection * view * glm::translate(glm::mat4(1.0f), axisPosition));
line2.setMVP(projection * view * glm::translate(glm::mat4(1.0f), axisPosition));
line3.setMVP(projection * view * glm::translate(glm::mat4(1.0f), axisPosition));
glViewport(SCR_WIDTH-SCR_WIDTH/10.0f,SCR_HEIGHT-SCR_HEIGHT/10.0f,SCR_WIDTH/10.0f,SCR_HEIGHT/10.0f);
glLineWidth(3.0f);
line1.draw();
line2.draw();
line3.draw();
glLineWidth(1.0f);
glViewport(0,0,SCR_WIDTH, SCR_HEIGHT);
line4.setMVP(projection * view);
line5.setMVP(projection * view);
line6.setMVP(projection * view);
line4.draw();
line5.draw();
line6.draw();
// glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
// -------------------------------------------------------------------------------
glfwSwapBuffers(window);
glfwPollEvents();
}
// glfw: terminate, clearing all previously allocated GLFW resources.
// ------------------------------------------------------------------
glfwTerminate();
return 0;
}
// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow *window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
camera.ProcessKeyboard(FORWARD, deltaTime);
if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
camera.ProcessKeyboard(BACKWARD, deltaTime);
if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
camera.ProcessKeyboard(LEFT, deltaTime);
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
camera.ProcessKeyboard(RIGHT, deltaTime);
}
// glfw: whenever the mouse moves, this callback is called
// -------------------------------------------------------
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
if (firstMouse)
{
lastX = xpos;
lastY = ypos;
firstMouse = false;
}
float xoffset = xpos - lastX;
float yoffset = lastY - ypos; // reversed since y-coordinates go from bottom to top
lastX = xpos;
lastY = ypos;
camera.ProcessMouseMovement(xoffset, yoffset);
}
Result:
