OpenGL программирование/Введение в современный OpenGL

Материал из Викиучебника
Перейти к: навигация, поиск
Ваша первая программа

Введение[править]

Большинство документаций по OpenGL описывает функции, которые в настоящее время устарели, в частности "фиксированный конвейер". OpenGL 2.0 и поздние версии содержат программируемый конвейер, где программируемая часть осуществляет шейдеры, написан в GLSL , базируемый на языке ANSI C.

Этот документ предназначен для людей, которые только начали изучать OpenGL и хотят использовать его с самого начала изучения. Программируемые конвейеры более гибки, но менее удобны в использовании, чем фиксируемые конвейеры. Вы однако убедитесь, что мы начнем с простой код в первую очередь. Мы будем использовать подход похожий на подход NeHe's в учебнике OpenGL 1.x, с примерами и учебными материалами, чтобы лучше понять теорию о программируемых конвейерах позднее.

Вначале вершинные массивы и шейдеры будут похоже на мучительную работу , по сравнению со старым непосредственным режимом и фиксированным конвейер рендеринга. Однако, в конце концов, особенно если вы будите использовать буфер объектов , ваш код станет более чистым, а графика будет быстрее.

Примеры кода, на этих страницах находятся в общем доступе. Не стесняйтесь делать то ,что вы хотите с ним.

Распространяйтеэтот документ между своих друзей! Викиучебник заслуживает большего вклада и признания:)

Заметки:

  • Вероятно можно смешать фиксированного конвейера и программируемого конвейера до некоторой степени, но фиксированный конвейер является устаревшим, и не доступен во всех OpenGL ES 2.0 (или его производной WebGL ), таким образом мы не будем использовать это.
  • В настоящие время OpenGL 3 и 4, в которые внедрили особенность геометрических шейдеров, но на этот раз это всего лишь эволюция света по сравнению с предыдущей версией. Поскольку он недоступен на мобильных платформах от 2012, поэтому сейчас мы сконцентрируемся на OpenGL 2.0.

Основные библиотеки[править]

Мы установим и используем следующие библиотеки:

  • OpenGL
  • GLEW (OpenGL Extension Wrangler) : действующие прототипы на C (и позже загружаемые расширение OpenGL)
  • GLUT (OpenGL Utility Toolkit) : создает окно OpenGL достаточно простым и кроссплатформенным способом

Под Debian and Ubuntu GNU/Linux, есть подходящие пакеты:

  • libgl1-mesa-dev (ищите в репозитории вашего линукс по "mesa")
  • libglew1.5-dev (... "glew")
  • freeglut3-dev (... "glut")

Удостоверьтесь, что вы установили основные инструменты компилятора, используйте этот мета-пакет:

  • build-essential

Т.к. вам придется подключать эти библиотеки непосредственно к вашей OpenGL-программе, проверьте APIs, Libraries and acronyms, чтобы понять, как именно могут называться отличающиеся названия пакетов с ними.

Заметка: Мы выбрали GLUT, потому что это минимальный портативный пакет, который мы смогли достать. Мы не будем использовать продвинутые особенности какого-либо продукта, и почти весь наш код будет построен на OpenGL, таким образом вы не должны иметь трудностей при переходе на другие, более сложные или не-кроссплатформенные библиотеки - такие как Qt, WGL, GLFW, SDL и SFML впоследствие.

Отображения треугольника в 2D[править]

Давайте начнем с простого :) Rather than struggling with a complex program that would take us a long time to hack until it works for the first time, the goal here is to get a basic but functional program that we can then improve upon step by step,

The triangle is the most basic unit in 3D programming. Actually, everything you see in video games is made of triangles! Small, textured triangles, but triangles nonetheless :)

To display a triangle in the programmable pipeline, we'll need at minimum:

  • a Makefile to build our application
  • initialize OpenGL and the helper libraries
  • an array with the coordinates of the 3 vertices (plural of vertex, i.e. a 3D point) of the triangle
  • a GLSL program with:
    • a vertex shader: we pass it each vertex individually, and it will compute their on-screen (2D) coordinates
    • a fragment (pixel) shader: OpenGL will pass it each pixel that is contained in our triangle, and it will compute its color
  • pass the vertices to the vertex shader

Makefile[править]

It is very easy to configure 'make' for our example. Write this in a 'Makefile' file:

LDLIBS=-lglut -lGLEW -lGL
all: triangle

To compile your application, type in a terminal:

make

A little more fancy Makefile looks like this:

CC=g++
  1. прим: на Mac OS X, начиная с версии Xcode 5.0, вместо gcc/g++ поставляется другой компилятор:
  2. CC=llvm
LDLIBS=-lglut -lGLEW -lGL
all: triangle
clean:
        rm -f *.o triangle
.PHONY: all clean

This allows you to type 'make clean' which removes the triangle binary and any intermediate object files that may have been generated. The .PHONY rule is there to tell make that "all" and "clean" are not files, so it will not get confused if you actually have files named like that in the same directory.

If you want to use a different programming environment, see the Setting Up OpenGL section.

Initialization[править]

Let's create a file triangle.c:

/* Using the standard output for fprintf */
#include <stdio.h>
#include <stdlib.h>
/* Use glew.h instead of gl.h to get all the GL prototypes declared */
#include <GL/glew.h>
/* Using the GLUT library for the base windowing setup */
#include <GL/glut.h>
/* ADD GLOBAL VARIABLES HERE LATER */
 
int init_resources(void)
{
  /* FILLED IN LATER */
  return 1;
}
 
void onDisplay()
{
  /* FILLED IN LATER */
}
 
void free_resources()
{
  /* FILLED IN LATER */
}
 
int main(int argc, char* argv[])
{
  /* Glut-related initialising functions */
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_RGBA|GLUT_DOUBLE|GLUT_DEPTH);
  glutInitWindowSize(640, 480);
  glutCreateWindow("My First Triangle");
 
  /* Extension wrangler initialising */
  GLenum glew_status = glewInit();
  if (glew_status != GLEW_OK)
  {
    fprintf(stderr, "Error: %s\n", glewGetErrorString(glew_status));
    return EXIT_FAILURE;
  }
 
  /* When all init functions runs without errors,
  the program can initialise the resources */
  if (1 == init_resources())
  {
    /* We can display it if everything goes OK */
    glutDisplayFunc(onDisplay);
    glutMainLoop();
  }
 
  /* If the program exits in the usual way,
  free resources and exit with a success */
  free_resources();
  return EXIT_SUCCESS;
}

In init_resources, we'll create our GLSL program. In onDisplay, we'll draw the triangle. In free_resources, we'll destroy the GLSL program.

Vertex array[править]

Our first triangle will be displayed in 2D - we'll move into something more complex soon. We describe the triangle with the 2D (x,y) coordinates of its 3 points. By default, OpenGL's coordinates are within the [-1, 1] range.

  GLfloat triangle_vertices[] = {
     0.0,  0.8,
    -0.8, -0.8,
     0.8, -0.8
  };

For now let's just keep this data structure in mind, we'll write it in the code later.

Note: the coordinates are between -1 and +1, but our window is not square! In the next lessons, we'll see how to fix the aspect ratio.

Vertex shader[править]

This is the GLSL program that will get each point of our array one by one, and tell where to put them on the screen. In this case, our points are already in 2D screen coordinates, so we don't change them. Our GLSL program is like this:

#version 120
attribute vec2 coord2d;
void main(void) {
  gl_Position = vec4(coord2d, 0.0, 1.0);
}
  • #version 120 means v1.20, the version of GLSL in OpenGL 2.1.
  • OpenGL ES 2's GLSL is also based on GLSL v1.20, but its version is 1.00 (#version 100). [1].
  • coord2d is the current vertex; it's an input variable that we'll need to declare in our C code
  • gl_Position is the resulting screen position; it's a built-in output variable
  • the vec4 takes our x and y coordinates, then 0 for the z coordinate. The last one, w=1.0 is for homogeneous coordinates (used for transformation matrices).

Now we need to make OpenGL compile this shader. Start the init_resources above main:

/*
Function: init_resources
Receives: void
Returns: int
This function creates all GLSL related stuff
explained in this example.
Returns 1 when all is ok, 0 with a displayed error
*/
int init_resources(void)
{
  GLint compile_ok = GL_FALSE, link_ok = GL_FALSE;
 
  GLuint vs = glCreateShader(GL_VERTEX_SHADER);
  const char *vs_source = 
#ifdef GL_ES_VERSION_2_0
    "#version 100\n"  // OpenGL ES 2.0
#else
    "#version 120\n"  // OpenGL 2.1
#endif
    "attribute vec2 coord2d;                  "
    "void main(void) {                        "
    "  gl_Position = vec4(coord2d, 0.0, 1.0); "
    "}";
  glShaderSource(vs, 1, &vs_source, NULL);
  glCompileShader(vs);
  glGetShaderiv(vs, GL_COMPILE_STATUS, &compile_ok);
  if (0 == compile_ok)
  {
    fprintf(stderr, "Error in vertex shader\n");
    return 0;
  }

We pass the source as a string to glShaderSource (later we'll read the shader code differently and more conveniently). We specify the type GL_VERTEX_SHADER.

Fragment shader[править]

Once OpenGL has our 3 points screen position, it will fill the space between them to make a triangle. For each of the pixels between the 3 points, it will call the fragment shader. In our fragment shader, we'll tell that we want to color each pixel in blue:

#version 120
void main(void) {
  gl_FragColor[0] = 0.0;
  gl_FragColor[1] = 0.0;
  gl_FragColor[2] = 1.0;
}

We compile it similarly, with type GL_FRAGMENT_SHADER. Let's continue our init_resources procedure:

  GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
  const char *fs_source =
    "#version 120           \n"
    "void main(void) {        "
    "  gl_FragColor[0] = 0.0; "
    "  gl_FragColor[1] = 0.0; "
    "  gl_FragColor[2] = 1.0; "
    "}";
  glShaderSource(fs, 1, &fs_source, NULL);
  glCompileShader(fs);
  glGetShaderiv(fs, GL_COMPILE_STATUS, &compile_ok);
  if (!compile_ok) {
    fprintf(stderr, "Error in fragment shader\n");
    return 0;
  }

GLSL program[править]

A GLSL program is the combination of the vertex and fragment shader. Usually they work together, and the vertex shader can even pass additional information to the fragment shader.

Create a global variable below #include to store the program handle:

GLuint program;

Here is how to link the vertex and fragment shaders in a program. Continue our init_resources procedure with:

  program = glCreateProgram();
  glAttachShader(program, vs);
  glAttachShader(program, fs);
  glLinkProgram(program);
  glGetProgramiv(program, GL_LINK_STATUS, &link_ok);
  if (!link_ok) {
    fprintf(stderr, "glLinkProgram:");
    return 0;
  }

Pass the triangle vertices to the vertex shader[править]

We mentioned that we pass each triangle vertex to the vertex shader using the coord2d attribute. Here is how to declare it in the C code.

First, let's create a second global variable:

GLint attribute_coord2d;

End our init_resources procedure with:

  const char* attribute_name = "coord2d";
  attribute_coord2d = glGetAttribLocation(program, attribute_name);
  if (attribute_coord2d == -1) {
    fprintf(stderr, "Could not bind attribute %s\n", attribute_name);
    return 0;
  }
 
  return 1;
}

Now we can pass our triangle vertices to the vertex shader. Let's write our onDisplay procedure. Each section is explained in the comments:

void onDisplay()
{
  /* Clear the background as white */
  glClearColor(1.0, 1.0, 1.0, 1.0);
  glClear(GL_COLOR_BUFFER_BIT);
 
  glUseProgram(program);
  glEnableVertexAttribArray(attribute_coord2d);
  GLfloat triangle_vertices[] = {
     0.0,  0.8,
    -0.8, -0.8,
     0.8, -0.8,
  };
  /* Describe our vertices array to OpenGL (it can't guess its format automatically) */
  glVertexAttribPointer(
    attribute_coord2d, // attribute
    2,                 // number of elements per vertex, here (x,y)
    GL_FLOAT,          // the type of each element
    GL_FALSE,          // take our values as-is
    0,                 // no extra data between each position
    triangle_vertices  // pointer to the C array
  );
 
  /* Push each element in buffer_vertices to the vertex shader */
  glDrawArrays(GL_TRIANGLES, 0, 3);
  glDisableVertexAttribArray(attribute_coord2d);
 
  /* Display the result */
  glutSwapBuffers();
}

glVertexAttribPointer tells OpenGL to retrieve each vertex from the data buffer created in init_resources and pass it to the vertex shader. These vertices define the on-screen position for each point, forming a triangle whose pixels are colored by the fragment shader.

Note: in the next tutorial we'll introduce Vertex Buffer Objects, a slightly more complex and newer way to store the vertices in the graphic card.

The only remaining part is free_resources, to clean-up when we quit the program. It's not critical in this particular case, but it's good to structure applications this way:

void free_resources()
{
  glDeleteProgram(program);
}

Our first OpenGL 2.0 program is complete!

If this fails[править]

The next tutorial adds more robustness to our first, minimalist code. Make sure to try the tut02 code (see link to code below).

Experiment![править]

Our first program, with another fragment shader

Feel free to experiment with this code:

  • try to make a square by displaying 2 triangles
  • try to change the colors
  • read the OpenGL man pages for each function that we used:
  • try to use this code in the fragment shader - what does it do?
gl_FragColor[0] = gl_FragCoord.x/640.0;
gl_FragColor[1] = gl_FragCoord.y/480.0;
gl_FragColor[2] = 0.5;

In the next tutorial, we'll add a few utility functions to make it easier to write and debug shaders.

Notes[править]

  1. Cf. OpenGL ES Shading Language 1.0.17 Specification. Khronos.org. Проверено 2011-09-10 г. - The OpenGL ES Shading Language (also known as GLSL ES or ESSL) is based on the OpenGL Shading Language (GLSL) version 1.20

Шаблон:OpenGL Programming BottomNav