Github Draw Circle Around a Point
Get-go created on 2016-03-19
Last updated on 2019-02-03
This commodity builds on code and information that was provided in a number of other articles on this site. Meet Related Articles, at the bottom of this article, for the full listing.
The only shapes that OpenGL tin draw are points, lines, and triangles,so how do you draw circles and other shapes? A reasonable approximation of a circle tin can be drawn using many, many triangles with one vertex at the centre of the circle and the other ii vertices sharing the same location as i of the vertices of the triangles side by side to information technology on the outer edge of the circle. Triangles of course accept straight sides, but the more triangles you lot utilise to define the circle, and therefore the closer the vertices on the edge of the circle are, the closer they represent a rounded edge. Merely that is a lot of work, and there is a better way: ascertain a square whose sides are equal to the diameter of the circle you lot want to describe and take the fragment shader make up one's mind whether the point it is drawing is within or outside the circle. That is what nosotros will do in this mail service.
The program that we will create in this post volition simply draw a dark-green circle at the centre of the view, but we volition be modifying the program over time to display circles and other objects at various locations in the view and rotate them around the centre of the view. New techniques will exist described equally they are used.
Drawing A Circumvolve
Outset by creating a new empty project in Visual Studio called
CirclesAndRotators, then add three new classes to it called CirclesAndRotatorsApp, CirclesAndRotatorsFrame, and CirclesAndRotatorsCanvas. Follow the steps outlined in:
- Creating wxWidgets Programs with Visual Studio 2017 – Part 1
- Visual Studio, wxWidgets and OpenGL
- Hello Triangle: OpenGL with wxWidgets, and
- OpenGL Shaders.
replacing class names as advisable.
A number of the methods in the classes remain the same, so they will not be repeated in this mail. However, the source of the full plan is provided on Github if y'all detect yous are having problems. The code for this commodity is in the primary branch.
CirclesAndRotatorsApp.OnInit is modified to place code in a endeavor-take hold of block because additional exceptions may be thrown. The catch block simply displays a message box with the text of the exception. For a full discussion of treatment exceptions in wxWidgets programs, see C++ Exceptions and wxWidgets. Here is the OnInit lawmaking:
bool CirclesAndRotatorsApp::OnInit() { try { CirclesAndRotatorsFrame* mainFrame = new CirclesAndRotatorsFrame(nullptr, L"Circles and Rotators"); mainFrame->Show(truthful); } catch (std::exception& e) { wxMessageBox(e.what(), "CirclesAndRotators"); } return true; }
The CirclesAndRotatorsFrame constructor creates a CirclesAndRotatorsCanvas object that is 800 by 800 pixels in size. While this size can be changed, the lawmaking in this program assumes that the sail is foursquare (i.e. has the aforementioned number of pixels in both ten and y directions). If you exercise not create a square canvas, you will accept to alter the program to compensate.
Every bit with the other programs I have shown and so far, the bulk of the code is in the canvas course.
The BuildCircleVertexShader method builds the vertex shader for the circumvolve. Hither is the code:
void CirclesAndRotatorsCanvas::BuildCircleVertexShader() { const GLchar* vertexSource = "#version 330 core\north" "in vec2 position;" "void main()" "{" " gl_Position = vec4(position, 0.0, one.0);" "}"; m_circleVertexShader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(m_circleVertexShader, one, &vertexSource, NULL); glCompileShader(m_circleVertexShader); CheckShaderCompileStatus(m_circleVertexShader, "Circle Vertex Shader did not compile."); }
This simple vertex shader takes the two-dimensional position of the vertex and stores it in the gl_Position global variable. The rest of the code you have seen in the previous posts except for the call to CheckShaderCompileStatus. This method checks if the shader compiled, and throws an exception if information technology did not:
void CirclesAndRotatorsCanvas::CheckShaderCompileStatus(GLuint shader, const std::string& msg) const { // check shader compile status, and throw exception if compile failed GLint status; glGetShaderiv(shader, GL_COMPILE_STATUS, &status); if (status != GL_TRUE) { throw std::exception(msg.c_str()); } }
Note: The condition returned by glGetShaderiv contains merely GL_TRUE or GL_FALSE. If GL_FALSE, and so you tin can call glGetShaderInfoLog to retrieve information on the error. Practise not expect detailed error letters. This would possibly be a good topic for a future article.
The BuildCircleFragmentShader method determines whether the fragment (pixel) being displayed is inside or outside the circle. Here is the source lawmaking for the method:
void CirclesAndRotatorsCanvas::BuildCircleFragmentShader() { const GLchar* fragmentSource = "#version 330 cadre\northward" "uniform vec2 viewDimensions;" "uniform float outerRadius;" "out vec4 outColor;" "void main()" "{" // convert fragment coordinate (i.e. pixel) to view coordinate " bladder ten = (gl_FragCoord.ten - viewDimensions.ten / 2.0f) / (viewDimensions.x / 2.0f);" " float y = (gl_FragCoord.y - viewDimensions.y / 2.0f) / (viewDimensions.y / ii.0f);" // discard fragment if outside the circumvolve " float len = sqrt(10 * ten + y * y);" " if (len > outerRadius) {" " discard;" " }" // else ready its colour to dark-green " outColor = vec4(0.0, ane.0, 0.0, one.0);" "}"; m_circleFragmentShader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(m_circleFragmentShader, i, &fragmentSource, NULL); glCompileShader(m_circleFragmentShader); CheckShaderCompileStatus(m_circleFragmentShader, "Circle Fragment Shader did not compile"); }
In gild to make up one's mind if the fragment is inside or outside the circumvolve, nosotros need three values: the position of the fragment, given in gl_FragCoord, the radius of the circle, given by the uniform value outerRadius, and the dimensions of the canvas. gl_FragCoord gives the location of the pixel containing the fragment, but outerRadius is the radius of the circle in device coordinates (x and y between -1 and +1), so the dimensions of the canvas are required to convert between the pixel coordinates and the device coordinates.
The lines that ascertain x and y convert the gl_FragCoord ten and y coordinates into device coordinates. The length of the vector (len) from the origin (middle of the view) to the fragment is calculated and compared with the radius of the circle. If the fragment is within the circumvolve, then the pixel colour is set to green, and if the fragment is outside the circle, the fragment is discarded. If you wish, rather than discard the fragment, you could set information technology to a different colour than light-green or the background colour so that y'all can encounter the square that contains the circumvolve.
The BuildCircleShaderProgram method outset calls BuildCircleVertexShader and BuildCircleFragmentShader, so links them to create the shader program. The location of the position attribute input to the vertex shader is obtained and the enabled. The locations of the ii uniform variables input to the fragment shader are obtained adjacent, and finally, the size of the canvas is prepare (viewDimensions in the fragment shader). Annotation: the size of the sail is not modifiable, so this compatible value needs to be ready just once. Hither is the source lawmaking for the BuildCircleShaderProgram method:
void CirclesAndRotatorsCanvas::BuildCircleShaderProgram() { // build the circle shaders BuildCircleVertexShader(); BuildCircleFragmentShader(); // create and link circle shader program m_circleShaderProgram = glCreateProgram(); glAttachShader(m_circleShaderProgram, m_circleVertexShader); glAttachShader(m_circleShaderProgram, m_circleFragmentShader); glBindFragDataLocation(m_circleShaderProgram, 0, "outColor"); glLinkProgram(m_circleShaderProgram); // set upwards position attribute used in circle vertex shader GLint posAttrib = glGetAttribLocation(m_circleShaderProgram, "position"); glEnableVertexAttribArray(posAttrib); glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), 0); // gear up the uniform arguments m_circleOuterRadius = glGetUniformLocation(m_circleShaderProgram, "outerRadius"); m_viewDimensions = glGetUniformLocation(m_circleShaderProgram, "viewDimensions"); // The canvas size is fixed (and should be square), and then initialize the value here glUseProgram(m_circleShaderProgram); wxSize canvasSize = GetSize(); glUniform2f(m_viewDimensions, static_cast(canvasSize.x), static_cast(canvasSize.y)); }
The fragment shader determines if the a fragment is inside or outside of the circumvolve. But to get a fragment to determine this, we accept to define the square that contains the circle. This is done in the CreateSquareForCircleMethod:
void CirclesAndRotatorsCanvas::CreateSquareForCircle() { // ascertain vertices for the two triangles float points[] = { -0.2f, -0.2f, 0.2f, -0.2f, 0.2f, 0.2f, -0.2f, 0.2f }; // define the indices for the triangles GLuint elements[] = { 0, i, ii, two, 3, 0 }; // setup vertex assortment object glGenVertexArrays(1, &m_circleVao); glBindVertexArray(m_circleVao); // upload vertex data glGenBuffers(1, &m_circleVbo); glBindBuffer(GL_ARRAY_BUFFER, m_circleVbo); glBufferData(GL_ARRAY_BUFFER, sizeof(points), points, GL_STATIC_DRAW); // upload chemical element information glGenBuffers(1, &m_circleEbo); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_circleEbo); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(elements), elements, GL_STATIC_DRAW); }
Wait a minute. To form a foursquare, nosotros need two triangles which take a total of half dozen vertices, just the points array but contains 4 vertices. Aye, that is true, only note that the first triangle is divers by the vertices v0, v1, and v2, and the second triangle is defined by the vertices v2, v3, and v0. Rather than requiring that these vertices be restated, OpenGL has the concept of elements. Note that the elements array contains 6 values that correspond to the six vertex numbers for the ii triangles. Beneath these definitions, there are two calls to glBindBuffer and glBufferData, ane for the points array that specifies the type as GL_ARRAY_BUFFER, and the second for the elements array that specifies the type as GL_ELEMENT_ARRAY_BUFFER. This tells the GPU that the kickoff buffer contains vertex data and the second buffer contains an array that specifies which vertices to utilise when drawing.
That seems like extra work, and more than buffer space in the GPU. Assuming 4 bytes required for each float and each unsigned int in the GPU, defining half-dozen vertices results in 48 bytes of buffer space. Using 4 vertices and 6 elements, in that location is a total of 56 bytes, and so at that place is more buffer infinite required in this case. But what happens if the vertices are specified in 3D, every bit would be the case in most OpenGL programs? For just these 2 triangles, specifying the triangles using only the points array uses 72 bytes; specifying the triangles using both points and elements arrays, once more uses 72 bytes. Now add a tertiary triangle that shares 2 vertices with other triangles: we have 108 bytes versus 96 bytes. Every bit the number of shared vertices increases, the use of elements increases the savings. Since a normal OpenGL programme will define hundreds or even thousands of attached triangles, the saving can become quite substantial.
Finally, hither is the code that draws the rectangle resulting in the circumvolve:
void CirclesAndRotatorsCanvas::OnPaint(wxPaintEvent& event) { SetCurrent(*m_context); // ready background to blackness glClearColor(0.0, 0.0, 0.0, one.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // use the circleShaderProgram glUseProgram(m_circleShaderProgram); // set outer radius for circumvolve here. We volition be modulating it in later // instance glUniform1f(m_circleOuterRadius, 0.2f); // draw the square that will contain the circle. // The circle is created inside the foursquare in the circumvolve fragment shader glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); glFlush(); SwapBuffers(); }
We accept seen most of this before. The simply differences are the three lines:
glUseProgram(m_circleShaderProgram); glUniform1f(m_circleOuterRadius, 0.2f); glDrawElements(GL_TRIANGLES, vi, GL_UNSIGNED_INT, 0);
In previous instance programs, glUseProgram was chosen immediately after the shader plan was linked. In those cases, but ane shader programme was created. As we develop the CirclesAndRotators program farther, nosotros will create additional shader programs. One shader programme will be used for drawing some shapes, and additional shader programs will exist used when drawing other shapes. Hence, nosotros need to call glUseProgram for the appropriate shader programme before drawing whatever objects that utilise that shader plan.
If previous examples, glDrawArrays was called to draw the objects. If nosotros called glDrawArrays here, only ane triangle would be drawn. Try it and see. To use elements, we must call glDrawElements instead.
The only remaining chore is to release the GPU resources in the CirclesAndRotatorsCanvas destructor.
Here is the resulting display:
The source lawmaking for the plan is provided in the master branch on GitHub.
Related Articles
This article is built on lawmaking and information that was provided in:
- OpenGL Shaders
which in turn was built from lawmaking and information provided in:
- Creating wxWidgets Programs with Visual Studio 2017 – Part 1
- Visual Studio, wxWidgets and OpenGL
- Hello Triangle: OpenGL with wxWidgets
The code provided is further developed in:
- Device Coordinates and Object Coordinates
- Moving the Circle Off Centre
- Ii Rotating Circles
- Adding a Moving Triangle
- Circles and Rotators Design and Coding Decisions
shirlowbuttly1994.blogspot.com
Source: https://computingonplains.wordpress.com/drawing-circles-with-opengl/