OK, this is a high-level overview, and I assume that you are already familiar with the basics of OpenGL, such as buffer objects. Let me know if something doesn't make sense or if you want more information.
The most common way to represent the landscape in computer graphics is the height field: a grid of points that are regularly located on the X and Y axes, but whose Z (height) can vary. The height field can have only one Z value for one (X, Y) grid point, so you cannot have “protrusions” on the ground, but in any case this is usually enough.
A simple way to draw a high-rise landscape is a triangular strip (or squares, but they are out of date). For simplicity, start at one corner and set the vertices in zig-zag order down the column, then return to the top and do the next column and so on. There are optimizations that can be performed to improve performance and more sophisticated ways to build geometry for a better look, but this will get you started.
(I am assuming rectangular terrain here, as this is usually done, if you really want a circle, you can replace 𝑟 and Θ with X and Y so that you have a polar grid.)
The coordinates for each vertex must be stored in the buffer object, as usual. When you call glBufferData() to load the vertex data into the GPU, specify either GL_STREAM_DRAW if the landscape will usually change from one frame to another, or GL_DYNAMIC_DRAW if it will change often but not (close to) every frame. To change the terrain, call glBufferData() again to copy another vertex dataset to the GPU.
For the vertex data itself, you can specify all three coordinates (X, Y, and Z) for each vertex; what is the simplest thing. Or, if you are using a fairly new version of GL and want to be complex, you should be able to calculate the X and Y coordinates in the vertex shader using gl_VertexID and grid sizes (passed to the shader as a uniform value). Thus, you only need to save the Z values in the buffer, which means less GPU memory and bandwidth consumption.