Народный учебник по OpenGL
de301fb4

Радиальное размытие и текстурный рендеринг


Radial Blur & Rendering To A Texture

Привет! Меня зовут Дарио Корно (Dario Corno), еще меня знают как rio of SpinningKids. Я занимаюсь построением сцен с 1989 года. Прежде всего, хочу объяснить, для чего я решил написать эту небольшую статью. Я бы хотел, чтобы все, пожелавшие скачать демонстрационную программу, смогли разобраться в ней и в эффектах, которые в ней реализованы.

Демонстрационные программы часто показывают, как путем простого, а иногда грубого программирования добиться неплохих художественных возможностей программы. Вы можете увидеть просто убойные эффекты в демонстрационных примерах, существующих на данный момент. Кучу таких программ вы найдете по адресу http://www.pouet.net или http://ftp.scene.org.

Теперь закончим со вступлением и приступим к изучению программы.

Я покажу, как создавать эффект «eye candy» (реализованный в программе), похожий на радиальное размытие. Иногда его относят к объемному освещению, но не верьте этому, это просто имитация радиального размытия! ;D

 

Радиальное размытие обычно достигалось (когда еще не было аппаратного рендеринга) смазыванием каждого пикселя исходного изображения в направлении от центра размытия.

На современной аппаратуре обеспечением пока довольно трудно реализовать размытие с помощью использования буфера цвета (по крайней мере, это относится ко всем «gfx»-видеокартам), поэтому для получения подобного эффекта мы пойдем на маленькую хитрость.

И как плюс, при изучении построения эффекта радиального размытия вы научитесь простому способу текстурного рендеринга!

В качестве трехмерного объекта я выбрал пружину, поскольку это нетривиальная фигура, да и обычные кубы  мне уже порядком поднадоели :)

Хочу заметить, что эта статья является скорее руководством по созданию эффектов. Я не буду слишком вдаваться в подробности программы. Постарайтесь больше понимать все сердцем :)

Ниже приведены объявления переменных и необходимый заголовочный файл:




 
#include <math.h>  // Нам потребуются некоторые математические операции
float    angle;          // Угол вращения спирали
float    vertexes[3][3]; // Массив трех вершин
float    normal[3];      // Массив нормали
Gluint  BlurTexture;     // Номер текстуры
Функция EmptyTexture() создает пустую текстуру и возвращает ее номер. Нам потребуется выделить некоторую свободную память (а точнее 128*128*4 беззнаковых целочисленных).
128*128 – размер текстуры (128 пикселей ширины и столько же высоты), цифра 4 означает, что для каждого пикселя нам нужно 4 байта для хранения компонент RED, GREEN, BLUE и ALPHA.
 
GLuint EmptyTexture()     // Создание пустой текстуры
{
  GLuint txtnumber;      // Идентификатор текстуры
  unsigned int* data;    // Указатель на хранимые данные
  // Выделение памяти под массив пустой текстуры (128x128x4 байт)
  data = (unsigned int*)new GLuint[((128 * 128)* 4 * sizeof(unsigned int))];
 
После выделения памяти нам нужно обнулить ее при помощи функции ZeroMemory, передав ей указатель (data) и размер обнуляемой памяти.
Хочу обратить ваше внимание на то, что характеристики магнификации (увеличения) и минификации (уменьшения) текстуры (это методы, применяемые для изменения размера текстуры до размера объекта, на который эта текстура «натягивается» - прим. перев.) устанавливаются параметром GL_LINEAR (определяющим цвет точки как среднее арифметическое всех элементов текстуры, входящих в отображаемый пиксел - прим. перев.). А параметр GL_NEAREST при растяжении текстуры дал ба нам не очень красивую картинку.
 
  // Очистка памяти массива
  ZeroMemory(data,((128 * 128)* 4 * sizeof(unsigned int)));
  glGenTextures(1, &txtnumber);            // Создать 1 текстуру
  glBindTexture(GL_TEXTURE_2D, txtnumber); // Связать текстуру
  // Построить текстуру по информации в data
  glTexImage2D(GL_TEXTURE_2D, 0, 4, 128, 128, 0,
      GL_RGBA, GL_UNSIGNED_BYTE, data);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);


  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
  delete [] data;   // Удалить data
  return txtnumber; // Вернуть идентификатор текстуры
}
 
Следующая функция нормализует длину векторов нормали. Векторы представлены массивом из 3-х элементов типа float, в котором 1-й элемент – это X, второй – Y и третий – Z. Нормализованный вектор (Nv) выражается через Vn = (Vox / |Vo| , Voy / |Vo|, Voz / |Vo|), где Vo – исходный вектор, |Vo| - его длина, и x, y, z – его компоненты. В программе необходимо сделать следующее: вычислить длину исходного вектора: sqrt(x^2 + y^2 + z^2) ,где x,y,z - это 3 компоненты вектора.  Затем надо разделить все три компоненты вектора нормали на полученное значение длины.
 
// Преобразовать вектор нормали (3 координаты) в единичный вектор с единичной длиной
void ReduceToUnit(float vector[3]) {
  float length;      // Переменная для длины вектора
  // Вычисление длины вектора
  length = (float)sqrt((vector[0]*vector[0]) + (vector[1]*vector[1])
           + (vector[2]*vector[2]));
  // Предотвращение деления на нуль
  if(length == 0.0f)   length = 1.0f;
  vector[0] /= length;    // Деление всех трех элементов
  vector[1] /= length;    // на длину единичного вектора
  vector[2] /= length;    // нормали
}
 
Следующая функция подсчитывает нормаль, заданную тремя вершинами (хранящимися в трех массивах типа float). У нас есть два параметра: v[3][3] и out[3],  первый из них – матрица со значениями типа float, размерностью m=3 и n=3, где каждая строка из трех элементов является вершиной треугольника. out – это переменная, в которую мы поместим результат - вектор нормали.
Немного (простой) математики. Мы собираемся воспользоваться знаменитым векторным произведением, которое определяется как операция между двумя векторами, дающая в результате вектор, перпендикулярный обоим исходным векторам. Нормаль - это вектор, ортогональный к поверхности, но направленный в противоположную сторону (и имеющий нормализованную (единичную) длину). Теперь представьте себе, что два вектора расположены вдоль сторон одного треугольника, ортогональный вектор (полученный в результате умножения этих векторов) этих двух сторон треугольника является нормалью этого треугольника.


Осуществить это легче, чем понять.
Сначала найдем вектор, идущий от вершины 0 к вершине 1, затем вектор, идущий от вершины 1 к вершине 2, что просто осуществляется путем вычитания каждой компоненты (координаты) стартовой вершины из компоненты следующей вершины стороны треугольника (соответствующей искомому вектору) в порядке обхода треугольника. Так мы получим вектора сторон треугольника. А в результате векторного произведения этих двух векторов (V*W) получим вектор нормали треугольника (если быть точнее, - вектор нормали плоскости, образованной сторонами этого треугольника, так как известно, что для образования плоскости достаточно двух несовпадающих векторов – прим.перев.).
 
Давайте взглянем на программу.
V[0][] – это первая вершина, V[1][] – это вторая вершина, V[2][] – это третья вершина. Каждая вершина состоит из: V[][0] – x-координата вершины, V[][1] – y-координата вершины, V[][2] – z-координата вершины.
Простым вычитанием всех соответствующих координат первой вершины из координат следующей мы получим ВЕКТОР от первой вершины к следующей. v1[0] = v[0][0] - v[1][0] - это выражение подсчитывает X-компоненту ВЕКТОРА, идущего от ВЕРШИНЫ 0 к вершине 1. v1[1] = v[0][1] - v[1][1] - это выражение подсчитывает Y-компоненту, v1[2] = v[0][2] - v[1][2]  подсчитывает Z компоненту и так далее.
Так мы получим два вектора, что даст нам возможность вычислить нормаль треугольника.
Вот формула векторного произведения:
out[x] = v1[y] * v2[z] - v1[z] * v2[y]         
out[y] = v1[z] * v2[x] - v1[x] * v2[z]
out[z] = v1[x] * v2[y] - v1[y] * v2[x]
В итоге в массиве out[] у нас будет находиться нормаль треугольника.
 
void calcNormal(float v[3][3], float out[3])       
// Вычислить нормаль для четырехугольников используя 3 точки
{
  float v1[3],v2[3];      // Вектор 1 (x,y,z) & Вектор 2 (x,y,z)
  static const int x = 0  // Определение X-координаты
  static const int y = 1  // Определение Y-координаты


  static const int z = 2; // Определение Z-координаты
  // Вычисление вектора между двумя точками вычитанием
  // x,y,z-координат одной точки из координат другой.
  // Подсчет вектора из точки 1 в точку 0
  v1[x] = v[0][x] - v[1][x];  // Vector 1.x=Vertex[0].x-Vertex[1].x
  v1[y] = v[0][y] - v[1][y];  // Vector 1.y=Vertex[0].y-Vertex[1].y
  v1[z] = v[0][z] - v[1][z];  // Vector 1.z=Vertex[0].y-Vertex[1].z
  // Подсчет вектора из точки 2 в точку 1
  v2[x] = v[1][x] - v[2][x];  // Vector 2.x=Vertex[0].x-Vertex[1].x
  v2[y] = v[1][y] - v[2][y];  // Vector 2.y=Vertex[0].y-Vertex[1].y
  v2[z] = v[1][z] - v[2][z];  // Vector 2.z=Vertex[0].z-Vertex[1].z
  // Вычисление векторного произведения
  out[x] = v1[y]*v2[z] - v1[z]*v2[y];  // для Y - Z
  out[y] = v1[z]*v2[x] - v1[x]*v2[z];  // для X - Z
  out[z] = v1[x]*v2[y] - v1[y]*v2[x];  // для X - Y
 
  ReduceToUnit(out);      // Нормализация векторов
}
Следующая функция задает точку наблюдения с использованием функции gluLookAt. Мы разместим эту точку в точке (0,5,50), она будет направлена на точку (0,0,0)(центр сцены), при этом верхний вектор будет задан с направлением вверх (0,1,0)! ;D
 
void ProcessHelix()      // Рисование спирали (или пружины)
{
  GLfloat x;      // x-координата спирали
  GLfloat y;      // y-координата спирали
  GLfloat z;      // z-координата спирали
  GLfloat phi;    // Угол
  GLfloat theta;  // Угол
  GLfloat v,u;    // Углы
  GLfloat r;      // Радиус скручивания
  int twists = 5; // пять витков
  // Задание цвета материала
  GLfloat glfMaterialColor[]={0.4f,0.2f,0.8f,1.0f}; 
  // Настройка рассеянного освещения
  GLfloat specular[]={1.0f,1.0f,1.0f,1.0f};     
  glLoadIdentity();    // Сброс матрицы модели
  // Точка камеры (0,5,50) Центр сцены (0,0,0)
  // Верх но оси Y
  gluLookAt(0, 5, 50, 0, 0, 0, 0, 1, 0);
  glPushMatrix();      // Сохранение матрицы модели
  // Смещение позиции вывода на 50 единиц вглубь экрана
  glTranslatef(0,0,-50);   


  glRotatef(angle/2.0f,1,0,0);  // Поворот на angle/2 относительно X
  glRotatef(angle/3.0f,0,1,0);  // Поворот на angle/3 относительно Y
  glMaterialfv(GL_FRONT_AND_BACK,
      GL_AMBIENT_AND_DIFFUSE, glfMaterialColor);
  glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,specular);
 
Далее займемся подсчетом формулы спирали и визуализацией пружины. Это совсем несложно, и я не хотел бы останавливаться на этих пунктах, так как «соль» данного урока совсем в другом. Участок программы, отвечающий за спираль, заимствован у друзей из «Listen Software» (и несколько оптимизирован). Он написан наиболее простым и не самым быстрым способом. Использование массивов вершин не делает его быстрее.
 
  r=1.5f;        // Радиус
  glBegin(GL_QUADS);    // Начать рисовать четырехугольник
  for(phi=0; phi <= 360; phi+=20.0)  // 360 градусов шагами по 20
  {// 360 градусов * количество_витков шагами по 20
   for(theta=0; theta<=360*twists; theta+=20.0)
    {// Подсчет угла первой точки  (0)
    v=(phi/180.0f*3.142f);
    // Подсчет угла первой точки  (0)
    u=(theta/180.0f*3.142f);
    // Подсчет x-позиции (первая точка)
    x=float(cos(u)*(2.0f+cos(v) ))*r;
    // Подсчет y-позиции (первая точка)
    y=float(sin(u)*(2.0f+cos(v) ))*r;
    // Подсчет z-позиции (первая точка)
    z=float((( u-(2.0f*3.142f)) + sin(v) ) * r);
    vertexes[0][0]=x;  // x первой вершины
    vertexes[0][1]=y;  // y первой вершины
    vertexes[0][2]=z;  // z первой вершины
    // Подсчет угла второй точки (0)
    v=(phi/180.0f*3.142f); 
    // Подсчет угла второй точки (20)
    u=((theta+20)/180.0f*3.142f); 
    // Подсчет x-позиции (вторая точка)   
    x=float(cos(u)*(2.0f+cos(v) ))*r;
    // Подсчет y-позиции (вторая точка) 
    y=float(sin(u)*(2.0f+cos(v) ))*r;
    // Подсчет z-позиции (вторая точка)
    z=float((( u-(2.0f*3.142f)) + sin(v) ) * r);
    vertexes[1][0]=x;  // x второй вершины
    vertexes[1][1]=y;  // y второй вершины
    vertexes[1][2]=z;  // z второй вершины


    // Подсчет угла третьей точки  ( 20 )
    v=((phi+20)/180.0f*3.142f);
    // Подсчет угла третьей точки  ( 20 )
    u=((theta+20)/180.0f*3.142f);
    // Подсчет x-позиции (третья точка)
    x=float(cos(u)*(2.0f+cos(v) ))*r;
    // Подсчет y-позиции (третья точка)
    y=float(sin(u)*(2.0f+cos(v) ))*r;
    // Подсчет z-позиции (третья точка)
    z=float((( u-(2.0f*3.142f)) + sin(v) ) * r);
    vertexes[2][0]=x;  // x третьей вершины
    vertexes[2][1]=y;  // y третьей вершины
    vertexes[2][2]=z;  // z третьей вершины
    // Подсчет угла четвертой точки (20)
    v=((phi+20)/180.0f*3.142f);
    // Подсчет угла четвертой точки (0)
    u=((theta)/180.0f*3.142f);
    // Подсчет x-позиции (четвертая точка)
    x=float(cos(u)*(2.0f+cos(v) ))*r;
    // Подсчет y-позиции (четвертая точка)
    y=float(sin(u)*(2.0f+cos(v) ))*r;
    // Подсчет z-позиции (четвертая точка)
    z=float((( u-(2.0f*3.142f)) + sin(v) ) * r);
    vertexes[3][0]=x;  // x четвертой вершины
    vertexes[3][1]=y;  // y четвертой вершины
    vertexes[3][2]=z;  // z четвертой вершины
    // Вычисление нормали четырехугольника
    calcNormal(vertexes,normal);
    // Установка нормали
    glNormal3f(normal[0],normal[1],normal[2]);
    // Визуализация четырехугольника
  glVertex3f(vertexes[0][0],vertexes[0][1],vertexes[0][2]);
  glVertex3f(vertexes[1][0],vertexes[1][1],vertexes[1][2]);
  glVertex3f(vertexes[2][0],vertexes[2][1],vertexes[2][2]);
  glVertex3f(vertexes[3][0],vertexes[3][1],vertexes[3][2]);
  }
  }
  glEnd();       // Конец визуализации четырехугольника
  glPopMatrix(); // Восстанавливаем матрицу
}
 
Две функции (ViewOrtho и ViewPerspective) написаны для упрощения рисования в ортогональной проекции и возврата перспективную.
Функция ViewOrtho выбирает текущую матрицу проекции и сохраняет ее копию в стеке системы OpenGL. Затем в матрицу проекции грузится единичная матрица, и устанавливается ортогональный просмотр при текущем разрешении экрана.


После этого мы получим возможность рисовать в 2D-координатах от 0,0 в верхнем левом углу и 640,480  нижнем правом углу экрана.
И в конце, делается активной матрица модели для визуализации.
Функция ViewPerspective выбирает текущую матрицу проекции и восстанавливает из стека «неортогональную» матрицу проекции, которая была сохранена функцией ViewOrtho. Потом также выбирается с матрица модели, чтобы мы могли заняться визуализацией.
Советую применять эти две процедуры, позволяющие легко переключаться между 2D и 3D-рисованием и не волноваться об искажениях матрицы проекции и матрицы модели.
 
void ViewOrtho()      // Установка ортогонального вида
{
  glMatrixMode(GL_PROJECTION);  // Выбор матрицы проекции
  glPushMatrix();      // Сохранить матрицу
  glLoadIdentity();    // Сбросить матрицу
  glOrtho( 0, 640 , 480 , 0, -1, 1 );  // Ортогональный режим (640x480)
  glMatrixMode(GL_MODELVIEW);  // Выбор матрицы модели
  glPushMatrix();      // Сохранить матрицу
  glLoadIdentity();    // Сбросить матрицу
}
void ViewPerspective()           // Установка вида перспективы
{
  glMatrixMode( GL_PROJECTION ); // Выбор матрицы проекции
  glPopMatrix();                 // Восстановить матрицу
  glMatrixMode( GL_MODELVIEW );  // Выбрать матрицу вида
  glPopMatrix();                 // Восстановить матрицу
}
 
Ну а теперь будем учиться «подделывать» эффект размытия.
Нам требуется нарисовать сцену размытой от центра во всех направлениях. Способ рисования не должен понизить быстродействие. Непосредственно считывать и записывать пиксели мы не можем, и если мы хотим сохранить совместимость с большинством видеокарт, различные специфические возможности видеокарт также неприменимы.
Так неужели все понапрасну …?
Ну нет, решение совсем близко. OpenGL дает нам возможность «размытия» текстур. Конечно, это не реальное размытие, просто в процессе масштабирования текстуры выполняется линейная фильтрация ее изображения, и, если напрячь воображение, результат будет похож на «размытие Гаусса».


Так что же произойдет, если мы поместим много растянутых текстур  прямо сверху на 3D-сцену и затем промасштабируем их?
Ответ вы уже знаете …– радиальное размытие!
Здесь у нас три проблемы: как в реальном времени мы будем создавать текстуры и как мы будем точно совмещать 3D-объект и текстуру?
Решение проще, чем вы себе представляете!
Проблема первая: текстурный рендеринг.
 
Она просто решается форматом пикселов фонового буфера. Визуализация текстуры (да и визуализация вообще) без использования фонового буфера очень неприятна для созерцания!
Визуализация текстуры производится всего одной функцией! Нам надо нарисовать наш объект и затем скопировать результат (ПЕРЕД ТЕМ КАК ПЕРЕКЛЮЧИТЬ РАБОЧИЙ И ФОНОВЫЙ БУФЕР в текстуру с использованием функции glCopytexSubImage.
Проблема вторая: подгонка текстуры точно на передней стороне 3D-объекта.
Нам известно, что если мы поменяем параметры области просмотра без установки правильной перспективы, у нас получится растянутая визуализация наших объектов. Например, если мы установим слишком широкую область просмотра (экран), визуализация будет растянута по вертикали.
Решением этой проблемы будет во-первых то, что надо установить режим вида OpenGL, равной размеру нашей текстуры (128х128). После визуализации нашего объекта в изображение текстуры мы визуализируем эту текстуру в текущем разрешении OpenGL-экрана. Таким образом, OpenGL сначала переносит уменьшенную копию объекта в текстуру, затем растягивает ее на весь экран. Вывод текстуры происходит поверх всего экрана и поверх нашего 3D-объекта в том числе. Надеюсь, я ничего не забыл. Еще небольшое уточнение… Если вы возьмете содержимое экрана размером 640х480 и затем ужмете его до рисунка в 256х256 пикселей, то его также можно будет использовать в качестве текстуры экрана и растянуть на 640х480 пикселей. Качество окажется, скорее всего, не очень, но смотреться будет как исходное 640х480 изображение.
Забавно! Эта функция в самом деле совсем несложная и является одним из моих самых любимых «дизайнерских трюков». Она устанавливает размер области просмотра (или внутреннего окна) OpenGL равным размеру нашей текстуры (128х128), или нашего массива BlurTexture, в котором эта текстура хранится. Затем вызывается функция, рисующая спираль (наш 3D-объект). Спираль будет рисоваться в окне в 128х128 и поэтому будет иметь соответствующие размеры.


После того, как в 128х128- области визуализируется спираль, мы подключаем BlurTexture и копируем буфера цвета из области просмотра при помощи функции glCopyTexImage2D.
Параметры определяются следующим образом:
Слово GL_TEXTURE_2D показывает, что мы используем двумерную текстуру. 0 - уровень мип-мапа, с которым мы совершаем копирование буфера, это значение по умолчанию. Слово GL_LUMINANCE определяет формат копируемых данных. Я использовал GL_LUMINANCE, поскольку с этим результат гораздо красивее, это значение позволяет копировать в текстуру лишь светящуюся часть буфера. Тут же могут быть использованы значения GL_ALPHA, GL_RGB, GL_INTENSITY и так далее.
Следующие два параметра указывают OpenGL на угол экрана, с которого начнется копирование данных (0,0). Далее идут ширина и высота копируемого прямоугольника экрана. И последний параметр необходим, если мы хотим рамку, которая нам не нужна.
Теперь, когда у нас есть копия буфера цвета (с уменьшенной спиралью) в массиве BlurTexture, мы можем очистить буфер и вернуть области просмотра ее естественные размеры (640х480 – во весь экран).
ВАЖНОЕ ЗАМЕЧАНИЕ:
Этот трюк возможен лишь при поддержке двойной буферизации форматом пикселя. Причиной данного условия является то, что все эти подготовительные процедуры скрыты от пользователя, поскольку полностью совершаются в фоновом буфере.
 
void RenderToTexture()    // Визуализация в текстуру
{
  // Изменить область просмотра (в соответствии с размером текстуры)
  glViewport(0,0,128,128);
  ProcessHelix();    // Нарисовать спираль
  glBindTexture(GL_TEXTURE_2D,BlurTexture); // Подключить нашу текстуру
  // Копирование области просмотра в текстуру (от 0,0 до 128,128... без рамки)
  glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, 0, 0, 128, 128, 0);
  glClearColor(0.0f, 0.0f, 0.5f, 0.5); // Цвет фона
  // Очистка экрана и фоновом буфера
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glViewport(0 , 0,640 ,480);// Область просмотра = (0,0 - 640x480)


}
 
Функция просто рисует несколько смешенных полигонов-прямоугольников на переднем плане нашей 3D-сцены, с использованием текстуры BlurTexture, которую мы уже подготовили. Путем постепенного изменения прозрачности (альфа) полигона и масштабирования текстуры у нас получится нечто,  похожее на радиальное размытие.
В первую очередь я отключаю флаги GEN_S и GEN_T (я фанатею по сферическому наложению, и мои программы обычно их включают :P).
Включаем 2D-текстурирование, отключаем проверку глубины, выбираем соответствующую функцию смешивания, включаем смешивание и подключаем BlurTexture.
Следующее, что мы делаем, это переключаемся на ортогональный вид сцены, что упрощает рисование полигонов, которые точно совпадают с размерами экрана. Это выстроит текстуры поверх 3D-объекта (с постепенным растяжением текстуры до размеров экрана). Таким образом, проблема два решена. Два зайца одной выстрелом! (первый «заяц» - рисование полигона в нужном месте экрана, второй «заяц» - вывод текстуры также в требуемом месте над «размываемой» спиралью, «выстрел» - переход на ортогональный вид, дающий возможность работать с обычными 2D-координатами – прим. перев.).
 
void DrawBlur(int times, float inc)  // вывод размытого изображения
{
  float spost = 0.0f;    // Начальное смещение координат
  float alphainc = 0.9f / times;  // Скорость уменьшения прозрачности
  float alpha = 0.2f;    // Начальное значение прозрачности
  // Отключить автоопределение координат текстуры
  glDisable(GL_TEXTURE_GEN_S);
  glDisable(GL_TEXTURE_GEN_T);
  glEnable(GL_TEXTURE_2D);  // Включить наложение 2D-текстур
  glDisable(GL_DEPTH_TEST); // Отключение проверки глубины
  glBlendFunc(GL_SRC_ALPHA,GL_ONE);// Выбор режима смешивание
  glEnable(GL_BLEND);    // Разрешить смешивание
  glBindTexture(GL_TEXTURE_2D,BlurTexture);// Подключить текстуру размытия
  ViewOrtho();      // переключение на ортогональный вид
  alphainc = alpha / times;  // alphainc=0.2f / число_раз визуализации размытия


 
 
Мы много раз выводим рисунок текстуры для создания эффекта размытия, масштабируя его изменением координат текстуры, и тем самым увеличивая степень размытия. Всего рисуется 25 прямоугольников с текстурой, растягиваемой на 1.015 за каждый проход цикла.
 
  glBegin(GL_QUADS);    // Рисуем прямоугольники
  for (int num = 0;num < times;num++)// Количество походов = times 
  {
    // Установить значение alpha (начальное = 0.2)
    glColor4f(1.0f, 1.0f, 1.0f, alpha);
    glTexCoord2f(0+spost,1-spost);  // Координаты текстуры  (0,1)
    glVertex2f(0,0);    // Первая вершина(0,0)
    glTexCoord2f(0+spost,0+spost);  // Координаты текстуры  (0,0)
    glVertex2f(0,480);  // Вторая вершина(0,480)
    glTexCoord2f(1-spost,0+spost);  // Координаты текстуры  (1,0)
    glVertex2f(640,480);// Третья вершина (640,480)
    glTexCoord2f(1-spost,1-spost);  // Координаты текстуры  (1,1)
    glVertex2f(640,0);  // Четвертая вершина (640,0)
    // Увеличение spost (Приближение к центру текстуры)
    spost += inc;
    // Уменьшение alpha (постепенное затухание рисунка)
    alpha = alpha - alphainc;
  }
  glEnd();        // Конец рисования
  ViewPerspective();        // Назад к перспективному виду
  glEnable(GL_DEPTH_TEST);  // Включить проверку глубины
  glDisable(GL_TEXTURE_2D); // Отключить 2D-текстурирование
  glDisable(GL_BLEND);      // Отключить смешивание
  glBindTexture(GL_TEXTURE_2D,0); // Отвязать текстуру
}
 
И вуаля: самая короткая функция Draw, когда-либо виданная, дающая возможность увидеть превосходный визуальный эффект.
Вызывается функция RenderToTexture. Здесь один раз рисуется растянутая пружина маленького размера, благодаря изменению параметров области вывода. Растянутая пружина копируется в нашу текстуру, затем буфер очищается.
Затем мы рисуем «настоящую» пружину (трехмерный объект, который вы видите на экране) при помощи функции ProcessHelix().
Наконец, мы рисуем последовательность «смешено-размазанных» полигонов-прямоугольников спереди пружины. То есть текстурированные прямоугольники будут растянуты и размазаны по изображению реальной 3D-пружины.


 
void Draw (void)      // Визуализация 3D-сцены
{
  glClearColor(0.0f, 0.0f, 0.0f, 0.5);// Очистка черным цветом
  // Очистка экрана и фонового буфера
  glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glLoadIdentity();   // Сброс вида
  RenderToTexture();  // Визуализация в текстуру
  ProcessHelix();     // Рисование спирали
  DrawBlur(25,0.02f); // Эффект размытия
  glFlush ();         // «Сброс» конвейера OpenGL
}
 
Надеюсь, данное руководство вам понравилось, хотя оно больше ничему и не учит, кроме визуализации в текстуру, но определенно может добавить красивый эффект в ваши 3D-программы.
Со всеми комментариями и предложениями по улучшению реализации этого эффекта прошу слать письма на rio@spinningkids.org. (а если вы хотите получить исправленную и улучшенную версию программы, пишите на lake@tut.by, будет интересно пообщаться :) - прим. перев. ).
Вы вольны использовать этот код бесплатно, тем не менее, если вы собираетесь использовать его в своих разработках, отметьте это и попытайтесь понять, как он работает, только так и никак иначе. Кроме того, если вы будете использовать этот код в своих коммерческих разработках (разработки, за которые вы потребуете плату), пожалуйста, выделите мне некоторый кредит.
И еще я хочу оставить вам всем небольшой список заданий для практики (домашнее задание) :D
1) Переделайте функцию DrawBlur для получения горизонтального размытия, вертикального размытия и каких-нибудь других интересных эффектов (круговое размытие, например).
2) Поменяйте параметры функции DrawBlur (увеличьте или уменьшите), чтобы получился красивый световой эффект под вашу музыку.
3) Поиграйте с функцией DrawBlur, нашей небольшой текстурой и значением GL_LUMINANCE (крутой блеск!).
4) Симитируйте объемные тени при помощи темных текстур взамен светящихся.
 
Ок. Теперь, должно быть, все.
Содержание раздела