📜 ⬆️ ⬇️

A short course in computer graphics, addendum: GLSL


The official translation (with a bit of polishing) is available here.




Another introductory article for beginners to program real-time graphics.


I once had a task to (quickly) visualize molecules. For example, a molecule can be represented simply as a collection of spheres like this:


')
Specifically, this virus consists of about three million atoms. You can download his model on the wonderful site rcsb.org .

This is a great topic for learning shaders.

First, I’ll just show you how OpenGL is called and how our shader code is linked to it.

Opengl helloworld


As usual, I created a repository for companion code. OpenGL itself does not have a normal cross-platform way to create a context for the render, so here I use the GLUT library to create a window, although I don’t really do any user interaction. At the same time, in addition to GLUT, for this tutorial we need the GLU and GLEW libraries.

Here is the simplest program that draws a kettle :

Hidden text
#include <GL/glu.h> #include <GL/glut.h> #include <vector> #include <cmath> const int SCREEN_WIDTH = 1024; const int SCREEN_HEIGHT = 1024; const float camera[] = {.6,0,1}; const float light0_position[4] = {1,1,1,0}; void render_scene(void) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); gluLookAt(camera[0], camera[1], camera[2], 0, 0, 0, 0, 1, 0); glColor3f(.8, 0., 0.); glutSolidTeapot(.7); glutSwapBuffers(); } void process_keys(unsigned char key, int x, int y) { if (27==key) { exit(0); } } void change_size(int w, int h) { glMatrixMode(GL_PROJECTION); glLoadIdentity(); glViewport(0, 0, w, h); glOrtho(-1,1,-1,1,-1,8); glMatrixMode(GL_MODELVIEW); } int main(int argc, char **argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); glutInitWindowPosition(100,100); glutInitWindowSize(SCREEN_WIDTH, SCREEN_HEIGHT); glutCreateWindow("GLSL tutorial"); glClearColor(0.0,0.0,1.0,1.0); glutDisplayFunc(render_scene); glutReshapeFunc(change_size); glutKeyboardFunc(process_keys); glEnable(GL_COLOR_MATERIAL); glEnable(GL_DEPTH_TEST); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glLightfv(GL_LIGHT0, GL_POSITION, light0_position); glutMainLoop(); return 0; } 



Let's figure it out, and start right away with main ().


We have the simplest keyboard handling, I (somewhat brutally) quit the program when I press the ESC key. When changing the geometry of the window, I say OpenGL that the projection is still orthogonal, and that it should display a square with the coordinates [-1,1] x [-1,1] to the full window size.

The most interesting thing about us is the render_scene () function.


As a result, we should have this picture:



GLSL helloworld


Here you can find the source for the simplest use of shaders. Github has a very handy version comparison tool, see what I changed .

The picture should be like this:



What exactly was added to the code? To begin with, two new files were added: frag_shader.glsl and vert_shader.glsl, written not in C ++, but in GLSL . This is the shader code that will be fed directly to the graphics card. And a binding was added to main.cpp that tells OpenGL to use these shaders.

Namely, a prog_hdlr handler is created, and previously read from text files are linked to it, and then compiled vertex and fragment shaders are linked to it.

We draw "molecule" by means of standard OpenGL


So, we learned to call OpenGL context and link shaders to it. Let's lay them aside and draw a dozen thousand randomly spaced spheres. I want to keep the code as simple as possible, and therefore I will not load the real molecule, although the .pdb format is quite text-based and very simple. The task is set as follows: let's just draw a large number of randomly spaced spheres of random color.

Here is a commit that does not use shaders, but draws just ten thousand spheres by calling glutSolidSphere ().

Do not forget to look at the changes. I added an array of atoms that contains arrays of seven elements: the first three are the coordinates of the center of the current atom, then its radius, and then three more of its color.

This picture should be:



Personally, it hurts me to look at this picture: the intersection of two spheres is an arc of a circle, and here we have anything, but not a circle. This is a consequence of the fact that we drew each sphere with sixteen parallels and sixteen meridians, that is, about five hundred triangles for each sphere. And besides the poor quality of the picture, we also directly raise the question of efficiency: if we want to draw ten million atoms, then we will need to send five billion triangles, which is starting to hurt the bus bandwidth.

Can shaders help us?


They can! Shaders are not only a change in lighting, although initially they were designed specifically for this. I want to minimize data transfer between the CPU and the GPU, so I will send only one vertex for each sphere that needs to be drawn.

I am writing code under the old GLSL # 120, because I need it to run on very old machines, the new GLSL has a slightly different syntax, but the general ideas are strictly the same.

Here is the code that draws the same areas, but using shaders.

So what is the idea?

To begin with, on the CPU side, we send one vertex for each sphere that needs to be drawn.
If you do not write any shaders, then we get just such a picture:

Hidden text


Further, in the vertex shader, we can change gl_PointSize, this will end up with a set of squares:

Hidden text

Note that the fragment shader will be executed for each pixel of the square!

That is, now everything is quite simple, we consider how far the given pixel of the square is far from the center, if
it exceeds the radius of the sphere, then we call discard:

Hidden text

Received a set of flat confetti, which pass through the center of each sphere.

What is interesting, we have the right to change the depth of each fragment:

Hidden text


It remains only to calculate the lighting, so this picture will turn out as a result:



Compare it with the picture where we drew the triangulated spheres. The image is much more accurate and is drawn much faster.

Now it remains to add the reading of the .pdb file, screen-space ambient occlusion and you will get the title picture of this article.




For those who want to understand how to draw spheres with the help of shaders, but with perspective, and not with glOrtho, there is an excellent article .

Source: https://habr.com/ru/post/253791/


All Articles