NeHe Tutorials Народный учебник по OpenGL
Урок 44. OpenGL

3D Lens Flare With Occlusion Testing

 

Привет, это я с новым уроком. Здесь мы рассмотрим, как реализовать блики от оптики при помощи расширения класса glCamera. Если вы внимательно рассмотрите блики, которые возникают на оптических линзах от солнца, вы заметите, что у них есть одно общее свойство. Блики при движении объектива проходят через центр объектива. Учитывая это, можно отбросить z-координату, и сделать блики, используя только x и y. Но возникает одна проблема при таком подходе, как без z-координаты узнать направлена ли камера на источник света или нет? Для реализации бликов, нам понадобиться немного математики.  Мы так же добавим несколько функций в класс камеры. Вначале нам нужны функции для определения того, попадает или нет точка или сфера во внутрь пирамиды видимости камеры. Далее нам необходим набор текстур для вывода бликов и последнее: мы все должны сделать как можно быстрее.

 

Я извиняюсь, но в предыдущем уроке в функции SetPerspective была допущена ошибка, и необходимо исправить ее, до того как мы начнем, вот код функции с исправлениями.

 

void glCamera::SetPrespective()

{

  GLfloat Matrix[16];

  glVector v;

 

  // Вычислим вектор направления

  glRotatef(m_HeadingDegrees, 0.0f, 1.0f, 0.0f);

  glRotatef(m_PitchDegrees, 1.0f, 0.0f, 0.0f);

 

  // Получить готовую матрицу из OGL,

  // в которой вектор направления будет в 3 строке

  glGetFloatv(GL_MODELVIEW_MATRIX, Matrix);

 

  // Извлечь вектор направления из матрицы

  // Элемент 10 должен быть инвертирован!

  m_DirectionVector.i = Matrix[8];

  m_DirectionVector.j = Matrix[9];

  m_DirectionVector.k = -Matrix[10];

 

  // Ok. Стереть результат предыдущих манипуляций

  glLoadIdentity();

 

  // Вращать сцену в правильной ориентации

  glRotatef(m_PitchDegrees, 1.0f, 0.0f, 0.0f);

  glRotatef(m_HeadingDegrees, 0.0f, 1.0f, 0.0f);

 

  // Масштабировать направление на нашу скорость

  v = m_DirectionVector;

  v *= m_ForwardVelocity;

 

  // Увеличить нашу позицию на вектор

  m_Position.x += v.i;

  m_Position.y += v.j;

  m_Position.z += v.k;

 

  // Сместиться на новую позицию

  glTranslatef(-m_Position.x, -m_Position.y, -m_Position.z);

}

 

Теперь приступим! Для вывода бликов мы используем 4 текстуры. Первая текстура – это большое яркое пятно света. Наш источник света будет в ярком пятне свете. Эта текстура всегда отображается в позиции источника света. Следующая текстура – это полосы. Полосы окружают источник света и выходят за него. Эта текстура так же отображается в позиции источника света. Текстура светового пятна (но не яркого), более сплошная, будет динамически двигаться по экрану. Текстура пятна, похожая на яркое пятно, но использование ее дает лучшие результаты, чем яркого светового пятна. Последняя текстура – это ореол. Это полые кольца для бликов и они двигаются по экрану в зависимости от ориентации и положения камеры. Есть и другие типы текстур, которые вы можете использовать для вывода бликов, если хотите. Смотрите ссылки в конце урока для получения дополнительной информации. Ниже пример таких текстур.

 

Большое яркое пятно

 

Полосы

Пятно

 

Ореол

 

Теперь, когда ясно, что рисовать, поговорим о том, как это сделать. Очевидно, нет смысла рисовать блики, когда камера не смотрит на источник света, для этого мы должны получить пирамиду видимости. Ее можно получить, умножая матрицу вида и модели на матрицу проекции, затем найти отсекающую плоскость. Другой подход использовать расширения OGL. Можно использовать расширения GL_HP_occlusion_test или GL_NV_occlusion_query, для определения видна или нет та или иная вершина камерой, но не все видеокарты поддерживают эти расширения, поэтому мы пойдем старым путем в этом уроке. Вот функция UpdateFrustum.

 

void glCamera::UpdateFrustum()

{

  GLfloat  clip[16];

  GLfloat  proj[16];

  GLfloat  modl[16];

  GLfloat  t;

 

  // Взять текущую матрицу проекции

  glGetFloatv( GL_PROJECTION_MATRIX, proj );

 

  // Взять текущую матрицу вида и модели

  glGetFloatv( GL_MODELVIEW_MATRIX, modl );

 

  // Умножить две матрицы

  clip[ 0] = modl[ 0] * proj[ 0] + modl[ 1] * proj[ 4] + modl[ 2] * proj[ 8] + modl[ 3] * proj[12];

  clip[ 1] = modl[ 0] * proj[ 1] + modl[ 1] * proj[ 5] + modl[ 2] * proj[ 9] + modl[ 3] * proj[13];

  clip[ 2] = modl[ 0] * proj[ 2] + modl[ 1] * proj[ 6] + modl[ 2] * proj[10] + modl[ 3] * proj[14];

  clip[ 3] = modl[ 0] * proj[ 3] + modl[ 1] * proj[ 7] + modl[ 2] * proj[11] + modl[ 3] * proj[15];

 

  clip[ 4] = modl[ 4] * proj[ 0] + modl[ 5] * proj[ 4] + modl[ 6] * proj[ 8] + modl[ 7] * proj[12];

  clip[ 5] = modl[ 4] * proj[ 1] + modl[ 5] * proj[ 5] + modl[ 6] * proj[ 9] + modl[ 7] * proj[13];

  clip[ 6] = modl[ 4] * proj[ 2] + modl[ 5] * proj[ 6] + modl[ 6] * proj[10] + modl[ 7] * proj[14];

  clip[ 7] = modl[ 4] * proj[ 3] + modl[ 5] * proj[ 7] + modl[ 6] * proj[11] + modl[ 7] * proj[15];

 

  clip[ 8] = modl[ 8] * proj[ 0] + modl[ 9] * proj[ 4] + modl[10] * proj[ 8] + modl[11] * proj[12];

  clip[ 9] = modl[ 8] * proj[ 1] + modl[ 9] * proj[ 5] + modl[10] * proj[ 9] + modl[11] * proj[13];

  clip[10] = modl[ 8] * proj[ 2] + modl[ 9] * proj[ 6] + modl[10] * proj[10] + modl[11] * proj[14];

  clip[11] = modl[ 8] * proj[ 3] + modl[ 9] * proj[ 7] + modl[10] * proj[11] + modl[11] * proj[15];

 

  clip[12] = modl[12] * proj[ 0] + modl[13] * proj[ 4] + modl[14] * proj[ 8] + modl[15] * proj[12];

  clip[13] = modl[12] * proj[ 1] + modl[13] * proj[ 5] + modl[14] * proj[ 9] + modl[15] * proj[13];

  clip[14] = modl[12] * proj[ 2] + modl[13] * proj[ 6] + modl[14] * proj[10] + modl[15] * proj[14];

  clip[15] = modl[12] * proj[ 3] + modl[13] * proj[ 7] + modl[14] * proj[11] + modl[15] * proj[15];

 

  // Извлечь правую плоскость

  m_Frustum[0][0] = clip[ 3] - clip[ 0];

  m_Frustum[0][1] = clip[ 7] - clip[ 4];

  m_Frustum[0][2] = clip[11] - clip[ 8];

  m_Frustum[0][3] = clip[15] - clip[12];

 

  // Нормализовать результат

  t = GLfloat(sqrt( m_Frustum[0][0] * m_Frustum[0][0] + m_Frustum[0][1] * m_Frustum[0][1] + m_Frustum[0][2] * m_Frustum[0][2] ));

  m_Frustum[0][0] /= t;

  m_Frustum[0][1] /= t;

  m_Frustum[0][2] /= t;

  m_Frustum[0][3] /= t;

 

  // Извлечь левую плоскость

  m_Frustum[1][0] = clip[ 3] + clip[ 0];

  m_Frustum[1][1] = clip[ 7] + clip[ 4];

  m_Frustum[1][2] = clip[11] + clip[ 8];

  m_Frustum[1][3] = clip[15] + clip[12];

 

  // Нормализовать результат

  t = GLfloat(sqrt( m_Frustum[1][0] * m_Frustum[1][0] + m_Frustum[1][1] * m_Frustum[1][1] + m_Frustum[1][2] * m_Frustum[1][2] ));

  m_Frustum[1][0] /= t;

  m_Frustum[1][1] /= t;

  m_Frustum[1][2] /= t;

  m_Frustum[1][3] /= t;

 

  // Извлечь нижнюю плоскость

  m_Frustum[2][0] = clip[ 3] + clip[ 1];

  m_Frustum[2][1] = clip[ 7] + clip[ 5];

  m_Frustum[2][2] = clip[11] + clip[ 9];

  m_Frustum[2][3] = clip[15] + clip[13];

 

  // Нормализовать результат

  t = GLfloat(sqrt( m_Frustum[2][0] * m_Frustum[2][0] + m_Frustum[2][1] * m_Frustum[2][1] + m_Frustum[2][2] * m_Frustum[2][2] ));

  m_Frustum[2][0] /= t;

  m_Frustum[2][1] /= t;

  m_Frustum[2][2] /= t;

  m_Frustum[2][3] /= t;

 

  // Извлечь верхнюю плоскость

  m_Frustum[3][0] = clip[ 3] - clip[ 1];

  m_Frustum[3][1] = clip[ 7] - clip[ 5];

  m_Frustum[3][2] = clip[11] - clip[ 9];

  m_Frustum[3][3] = clip[15] - clip[13];

 

  // Нормализовать результат

  t = GLfloat(sqrt( m_Frustum[3][0] * m_Frustum[3][0] + m_Frustum[3][1] * m_Frustum[3][1] + m_Frustum[3][2] * m_Frustum[3][2] ));

  m_Frustum[3][0] /= t;

  m_Frustum[3][1] /= t;

  m_Frustum[3][2] /= t;

  m_Frustum[3][3] /= t;

 

  // Извлечь дальнюю плоскость

  m_Frustum[4][0] = clip[ 3] - clip[ 2];

  m_Frustum[4][1] = clip[ 7] - clip[ 6];

  m_Frustum[4][2] = clip[11] - clip[10];

  m_Frustum[4][3] = clip[15] - clip[14];

 

  // Нормализовать результат

  t = GLfloat(sqrt( m_Frustum[4][0] * m_Frustum[4][0] + m_Frustum[4][1] * m_Frustum[4][1] + m_Frustum[4][2] * m_Frustum[4][2] ));

  m_Frustum[4][0] /= t;

  m_Frustum[4][1] /= t;

  m_Frustum[4][2] /= t;

  m_Frustum[4][3] /= t;

 

  // Извлечь ближнюю плоскость

  m_Frustum[5][0] = clip[ 3] + clip[ 2];

  m_Frustum[5][1] = clip[ 7] + clip[ 6];

  m_Frustum[5][2] = clip[11] + clip[10];

  m_Frustum[5][3] = clip[15] + clip[14];

 

  // Нормализовать результат

  t = GLfloat(sqrt( m_Frustum[5][0] * m_Frustum[5][0] + m_Frustum[5][1] * m_Frustum[5][1] + m_Frustum[5][2] * m_Frustum[5][2] ));

  m_Frustum[5][0] /= t;

  m_Frustum[5][1] /= t;

  m_Frustum[5][2] /= t;

  m_Frustum[5][3] /= t;

}

 

Эта сложная функция! Я думаю теперь вы поняли, почему есть расширения для этого! Хотя в ней используется простая математика, но очень много всяких действий. В этой функции 190 операций (умножений, сложений, вычитаний, делений) и плюс к этому шесть раз вычисляется квадратный корень. Так как мы будем вызывать эту функцию при каждой отрисовке сцены, давайте, оптимизируем ее. Ниже оптимизированная версия этой функции. Если не вращать или не смещать матрицу проекции, то эта функция будет работать для извлечения плоскостей пирамиды видимости.

 

void glCamera::UpdateFrustumFaster()

{

  GLfloat   clip[16];

  GLfloat   proj[16];

  GLfloat   modl[16];

  GLfloat   t;

 

  // Взять текущую матрицу проекции

  glGetFloatv( GL_PROJECTION_MATRIX, proj );

 

  // Взять текущую матрицу вида и модели

  glGetFloatv( GL_MODELVIEW_MATRIX, modl );

 

  // Умножить две матрицы

  // Но помните, что эта функция будет работать только, если

  // НЕТ вращения или смещения вашей матрицы проекции

  clip[ 0] = modl[ 0] * proj[ 0];

  clip[ 1] = modl[ 1] * proj[ 5];

  clip[ 2] = modl[ 2] * proj[10] + modl[ 3] * proj[14];

  clip[ 3] = modl[ 2] * proj[11];

 

  clip[ 4] = modl[ 4] * proj[ 0];

  clip[ 5] = modl[ 5] * proj[ 5];

  clip[ 6] = modl[ 6] * proj[10] + modl[ 7] * proj[14];

  clip[ 7] = modl[ 6] * proj[11];

 

  clip[ 8] = modl[ 8] * proj[ 0];

  clip[ 9] = modl[ 9] * proj[ 5];

  clip[10] = modl[10] * proj[10] + modl[11] * proj[14];

  clip[11] = modl[10] * proj[11];

 

  clip[12] = modl[12] * proj[ 0];

  clip[13] = modl[13] * proj[ 5];

  clip[14] = modl[14] * proj[10] + modl[15] * proj[14];

  clip[15] = modl[14] * proj[11];

 

  // Извлечь правую плоскость

  m_Frustum[0][0] = clip[ 3] - clip[ 0];

  m_Frustum[0][1] = clip[ 7] - clip[ 4];

  m_Frustum[0][2] = clip[11] - clip[ 8];

  m_Frustum[0][3] = clip[15] - clip[12];

 

  // Нормализовать результат

  t = GLfloat(sqrt( m_Frustum[0][0] * m_Frustum[0][0] + m_Frustum[0][1] * m_Frustum[0][1] + m_Frustum[0][2] * m_Frustum[0][2] ));

  m_Frustum[0][0] /= t;

  m_Frustum[0][1] /= t;

  m_Frustum[0][2] /= t;

  m_Frustum[0][3] /= t;

 

  // Извлечь левую плоскость

  m_Frustum[1][0] = clip[ 3] + clip[ 0];

  m_Frustum[1][1] = clip[ 7] + clip[ 4];

  m_Frustum[1][2] = clip[11] + clip[ 8];

  m_Frustum[1][3] = clip[15] + clip[12];

 

  // Нормализовать результат

  t = GLfloat(sqrt( m_Frustum[1][0] * m_Frustum[1][0] + m_Frustum[1][1] * m_Frustum[1][1] + m_Frustum[1][2] * m_Frustum[1][2] ));

  m_Frustum[1][0] /= t;

  m_Frustum[1][1] /= t;

  m_Frustum[1][2] /= t;

  m_Frustum[1][3] /= t;

 

  // Извлечь нижнюю плоскость

  m_Frustum[2][0] = clip[ 3] + clip[ 1];

  m_Frustum[2][1] = clip[ 7] + clip[ 5];

  m_Frustum[2][2] = clip[11] + clip[ 9];

  m_Frustum[2][3] = clip[15] + clip[13];

 

  // Нормализовать результат

  t = GLfloat(sqrt( m_Frustum[2][0] * m_Frustum[2][0] + m_Frustum[2][1] * m_Frustum[2][1] + m_Frustum[2][2] * m_Frustum[2][2] ));

  m_Frustum[2][0] /= t;

  m_Frustum[2][1] /= t;

  m_Frustum[2][2] /= t;

  m_Frustum[2][3] /= t;

 

  // Извлечь верхнюю плоскость

  m_Frustum[3][0] = clip[ 3] - clip[ 1];

  m_Frustum[3][1] = clip[ 7] - clip[ 5];

  m_Frustum[3][2] = clip[11] - clip[ 9];

  m_Frustum[3][3] = clip[15] - clip[13];

 

  // Нормализовать результат

  t = GLfloat(sqrt( m_Frustum[3][0] * m_Frustum[3][0] + m_Frustum[3][1] * m_Frustum[3][1] + m_Frustum[3][2] * m_Frustum[3][2] ));

  m_Frustum[3][0] /= t;

  m_Frustum[3][1] /= t;

  m_Frustum[3][2] /= t;

  m_Frustum[3][3] /= t;

 

  // Извлечь дальнюю плоскость

  m_Frustum[4][0] = clip[ 3] - clip[ 2];

  m_Frustum[4][1] = clip[ 7] - clip[ 6];

  m_Frustum[4][2] = clip[11] - clip[10];

  m_Frustum[4][3] = clip[15] - clip[14];

 

  // Нормализовать результат

  t = GLfloat(sqrt( m_Frustum[4][0] * m_Frustum[4][0] + m_Frustum[4][1] * m_Frustum[4][1] + m_Frustum[4][2] * m_Frustum[4][2] ));

  m_Frustum[4][0] /= t;

  m_Frustum[4][1] /= t;

  m_Frustum[4][2] /= t;

  m_Frustum[4][3] /= t;

 

  // Извлечь ближнюю плоскость

  m_Frustum[5][0] = clip[ 3] + clip[ 2];

  m_Frustum[5][1] = clip[ 7] + clip[ 6];

  m_Frustum[5][2] = clip[11] + clip[10];

  m_Frustum[5][3] = clip[15] + clip[14];

 

  // Нормализовать результат

  t = GLfloat(sqrt( m_Frustum[5][0] * m_Frustum[5][0] + m_Frustum[5][1] * m_Frustum[5][1] + m_Frustum[5][2] * m_Frustum[5][2] ));

  m_Frustum[5][0] /= t;

  m_Frustum[5][1] /= t;

  m_Frustum[5][2] /= t;

  m_Frustum[5][3] /= t;

}

 

Эта все еще большая функция, но в ней наполовину меньше операций (102). Оптимизация довольно простая. Я только выбросил все умножения, которые обычно равны нулю, когда умножаются матрицы проекции и модели. Если вы реально хотите оптимизировать эту функцию, тогда используйте расширение в этом месте. Расширения будут делать тоже самое, но быстрее, поскольку вычисления делаются на видеокарте. В любом случае вызов функции UpdateFrustum каждый раз при рисовании сцены будет понижать быстродействие, но далее мы извлечем пользу из этого. Сейчас мы можем сказать, видит ли камера ту или иную точку или объект. Если у вас множество объектов на сцене, то можно отобразить только те объекты, которые видны камерой. Это полезно, например, когда вы отображаете протяженный ландшафт, и вы можете выбрать только ту часть ландшафта для отображения, которая попадает в камеру, и при этом вы не загружаете видеокарту, посылая только нужные вершины для отображения нужного куска ландшафта. Ниже приведена функция, которая проверяет находится та или иная вершина в пирамиде видимости. Есть также функция проверки сферы, но я не привожу ее, так как эти функции почти идентичны.

 

BOOL glCamera::PointInFrustum(glPoint p)

{

  int i;

  // Идея этого алгоритма в том, что если точка

  // внутри всех 6 отсекающих плоскостей,

  // тогда она внутри пирамиды видимости,

  // и тогда функция вернет True.

  for(i = 0; i < 6; i++)

  {

    if(m_Frustum[i][0] * p.x + m_Frustum[i][1] * p.y + m_Frustum[i][2] * p.z + m_Frustum[i][3] <= 0)

    {

      return(FALSE);

    }

  }

  return(TRUE);

}

 

Сейчас мы выполним несколько геометрических преобразований с помощью функции gluProject. Фактически мы определяем, где находится проекция точки, используя произвольную область просмотра. Мы извлекаем с помощью glGet текущую матрицу преобразований, и поэтому мы будем иметь реальную позицию на экране, где будет отрисована точка. Так же полезно и то, что мы получаем значение z-координаты этой точки, что позволит нам обнаружить, закрыт ли источник света, хоть каким-то объектом.

 

// ########## Новая функция от rIO.Spinning Kids ##########

bool glCamera::IsOccluded(glPoint p)

{

  GLint viewport[4]; // Место для данных окна просмотра

  GLdouble mvmatrix[16], projmatrix[16]; // Место для матрицы трансформации

  GLdouble winx, winy, winz; // Место для координат проекции

  GLdouble flareZ; // Z-координата

  GLfloat bufferZ; // Глубина

 

  glGetIntegerv (GL_VIEWPORT, viewport); // Извлечь текущее окно просмотра

  glGetDoublev (GL_MODELVIEW_MATRIX, mvmatrix); // Извлечь текущую матрицу просмотра

  glGetDoublev (GL_PROJECTION_MATRIX, projmatrix); // Извлечь матрицу проекции

 

  // Определим 2D позиции 3D точки

  gluProject(p.x, p.y, p.z, mvmatrix, projmatrix, viewport, &winx, &winy, &winz);

  flareZ = winz;

 

  // Прочитаем один пиксель из буфера глубины (точно там, где наш блик будет нарисован)

  glReadPixels(winx, winy,1,1,GL_DEPTH_COMPONENT, GL_FLOAT, &bufferZ);

 

  // Если текущая z-координата пикселя там, где будет блик меньше, чем

  // z-координата точки p, то перед бликом что-то есть.

  if (bufferZ < flareZ)

    return true;

  else

    return false;

}

 

Сейчас мы сосредоточимся на другой проблеме, которую нам надо решить. Поскольку мы создаем блики в 3D, то их будем рисовать текстурированными четырехугольниками, но они могут не всегда смотреть на камеру. Это плохо, так как блики всегда будут плоские, даже если мы смотрим на источник света со стороны. Можно вместо текстур использовать точечные спрайты (point sprites). Точечные спрайты лучше, так как вместо посылки на конвейер OGL четырех точек с координатами текстуры, вы можете посылать одну точку и можно не указывать текстурных координат. Точечные спрайты великолепны для использования в системе частиц, и они также могут отлично использоваться для бликов. Вначале мы должны вычислить эти точки, а потом отобразит их. Но точечные спрайты поддерживаются только одним расширением (GL_NV_point_sprite). Чтобы урок можно было использовать всем, мы не будем использовать здесь это расширение. Одним из способов сделать так чтобы блики были всегда направлены на камеру – это просто сделать реверс вращений, которые мы используем для настройки камеры. Это хорошо работает, но когда камера позади источника света, возникает проблема. Чтобы избежать это мы сделаем так, чтобы камера никогда не находилась позади источника света. Для этого источник света будет двигаться с камерой. Это дает дополнительный эффект, при котором источник света будет бесконечно удален, а также позволит выстроить блики по прямой линии. Хватит разговоров, вот код для того, чтобы получить необходимые векторы и точки.

 

  GLfloat Length = 0.0f;

 

  // Нарисовать блик, если источник света на одной линии обзора

  if(SphereInFrustum(m_LightSourcePos, 1.0f) == TRUE)

  {

    // Вектор между камерой и источником света

    vLightSourceToCamera = m_Position - m_LightSourcePos;

    // Длина

    Length = vLightSourceToCamera.Magnitude();

    // Найдем точку на векторе направления камеры

    ptIntersect = m_DirectionVector * Length;

    // Найдем действительные координаты точки на вектор направления камеры

    ptIntersect += m_Position;

    // Вычислим вектор, между источником света и точкой пересечения

    vLightSourceToIntersect = ptIntersect - m_LightSourcePos;

    Length = vLightSourceToIntersect.Magnitude(); // Длина

    vLightSourceToIntersect.Normalize(); // Нормализуем вектор

 

Сначала найдем расстояние между источником света и камерой. Далее нам необходимо найти точку пересечения на луче вектора направления камеры. Расстояние между точкой пересечения и камерой равно расстоянию между источником света и камерой. После этого мы можем найти прямую, на которой мы будем рисовать блики. Эта прямая задается двумя точками: источником света и точкой пересечения. Вот поясняющий рисунок:

 

Теперь, когда мы имеем вектор направления, для того чтобы отобразить блики надо нарисовать ореол и вспышки. Ниже код, который рисует блики вдоль вектора. Мы создаем новую точку при помощи сдвига по вектору vLightSourceToIntersect и затем добавляем позицию источника света:

 

    glEnable(GL_BLEND);

    glBlendFunc(GL_SRC_ALPHA, GL_ONE);

    glDisable(GL_DEPTH_TEST);

    glEnable(GL_TEXTURE_2D);

 

    // ########## Новый код от rIO.Spinning Kids ##########

 

    if (!IsOccluded(m_LightSourcePos)) // Проверить закрыт ли источник света

    {

      // нарисовать большое неясное световое пятно

      RenderBigGlow(0.60f, 0.60f, 0.8f, 1.0f, m_LightSourcePos, 16.0f);

      // Полосы

      RenderStreaks(0.60f, 0.60f, 0.8f, 1.0f, m_LightSourcePos, 16.0f);

      // маленькое пятно

      RenderGlow(0.8f, 0.8f, 1.0f, 0.5f, m_LightSourcePos, 3.5f);

 

      // Точка на 15% дальше от источника света по направлению к точке пересечения

      pt = vLightSourceToIntersect * (Length * 0.1f);

      pt += m_LightSourcePos;

   

      // Маленькое пятно

      RenderGlow(0.9f, 0.6f, 0.4f, 0.5f, pt, 0.6f);

 

      // Точка на 30% дальше от источника света по направлению к точке пересечения

      pt = vLightSourceToIntersect * (Length * 0.15f);

      pt += m_LightSourcePos;

   

      // Ореол

      RenderHalo(0.8f, 0.5f, 0.6f, 0.5f, pt, 1.7f);

   

      // Точка на 35% дальше от источника света по направлению к точке пересечения

      pt = vLightSourceToIntersect * (Length * 0.175f);

      pt += m_LightSourcePos;

   

      // Ореол

      RenderHalo(0.9f, 0.2f, 0.1f, 0.5f, pt, 0.83f);

 

      // Точка на 57% дальше от источника света по направлению к точке пересечения

      pt = vLightSourceToIntersect * (Length * 0.285f);

      pt += m_LightSourcePos;

   

      // Ореол

      RenderHalo(0.7f, 0.7f, 0.4f, 0.5f, pt, 1.6f);

   

      // Точка на 55.1% дальше от источника света по направлению к точке пересечения

      pt = vLightSourceToIntersect * (Length * 0.2755f);

      pt += m_LightSourcePos;

   

      // Маленькое пятно

      RenderGlow(0.9f, 0.9f, 0.2f, 0.5f, pt, 0.8f);

 

      // Точка на 95.5% дальше от источника света по направлению к точке пересечения

      pt = vLightSourceToIntersect * (Length * 0.4775f);

      pt += m_LightSourcePos;

   

      // Маленькое пятно

      RenderGlow(0.93f, 0.82f, 0.73f, 0.5f, pt, 1.0f);

    

      // Точка на 98% дальше от источника света по направлению к точке пересечения

      pt = vLightSourceToIntersect * (Length * 0.49f);

      pt += m_LightSourcePos;

   

      // Ореол

      RenderHalo(0.7f, 0.6f, 0.5f, 0.5f, pt, 1.4f);

 

      // Точка на 130% дальше от источника света по направлению к точке пересечения

      pt = vLightSourceToIntersect * (Length * 0.65f);

      pt += m_LightSourcePos;

   

      // Маленькое пятно

      RenderGlow(0.7f, 0.8f, 0.3f, 0.5f, pt, 1.8f);

   

      // Точка на 126% дальше от источника света по направлению к точке пересечения

      pt = vLightSourceToIntersect * (Length * 0.63f);

      pt += m_LightSourcePos;

   

      // Маленькое пятно

      RenderGlow(0.4f, 0.3f, 0.2f, 0.5f, pt, 1.4f);

 

      // Точка на 160% дальше от источника света по направлению к точке пересечения

      pt = vLightSourceToIntersect * (Length * 0.8f);

      pt += m_LightSourcePos;

   

      // Ореол

      RenderHalo(0.7f, 0.5f, 0.5f, 0.5f, pt, 1.4f);

   

      // Точка на 156.5% дальше от источника света по направлению к точке пересечения

      pt = vLightSourceToIntersect * (Length * 0.7825f);

      pt += m_LightSourcePos;

   

      // Маленькое пятно

      RenderGlow(0.8f, 0.5f, 0.1f, 0.5f, pt, 0.6f);

 

      pt = vLightSourceToIntersect * (Length * 1.0f);

      pt += m_LightSourcePos;

   

      // Ореол

      RenderHalo(0.5f, 0.5f, 0.7f, 0.5f, pt, 1.7f);

   

      // Точка на 195% дальше от источника света по направлению к точке пересечения

      pt = vLightSourceToIntersect * (Length * 0.975f);

      pt += m_LightSourcePos;

   

      // Маленькое пятно

      RenderGlow(0.4f, 0.1f, 0.9f, 0.5f, pt, 2.0f);

    }

 

    glDisable(GL_BLEND );

    glEnable(GL_DEPTH_TEST);

    glDisable(GL_TEXTURE_2D);

 

Ниже функции RenderBigGlow, RenderStreaks, RenderGlow и RenderHalo. Функции почти идентичны, за исключением текстуры, которую они вызывают.

 

void glCamera::RenderHalo(GLfloat r, GLfloat g, GLfloat b, GLfloat a, glPoint p, GLfloat scale)

{

  glPoint q[4];

 

  // По существу мы делаем 2D прямоугольник из 4 точек

  // при этом мы не нуждаемся в z-координате, поскольку

  // мы инверсно вращаем камеру, поэтому

  // текстурированный прямоугольник будет всегда обращен

  // на зрителя.

  q[0].x = (p.x - scale); // Задать x-координату минус масштаб относительно центральной точки.

  q[0].y = (p.y - scale); // Задать y-координату минус масштаб относительно центральной точки.

  q[1].x = (p.x - scale); // Задать x-координату минус масштаб относительно центральной точки.

  q[1].y = (p.y + scale); // Задать y-координату плюс масштаб относительно центральной точки.

  q[2].x = (p.x + scale); // Задать x-координату плюс масштаб относительно центральной точки.

  q[2].y = (p.y - scale); // Задать y-координату минус масштаб относительно центральной точки.

  q[3].x = (p.x + scale); // Задать x-координату плюс масштаб относительно центральной точки.

  q[3].y = (p.y + scale); // Задать y-координату плюс масштаб относительно центральной точки.

 

  glPushMatrix(); // Сохранить матрицу вида модели

  glTranslatef(p.x, p.y, p.z); // Сместить в точку

  glRotatef(-m_HeadingDegrees, 0.0f, 1.0f, 0.0f);

  glRotatef(-m_PitchDegrees, 1.0f, 0.0f, 0.0f);

  glBindTexture(GL_TEXTURE_2D, m_HaloTexture); // Привязать текстуру ореола

  glColor4f(r, g, b, a); // Задать цвет, так как текстура в градациях серого

  glBegin(GL_TRIANGLE_STRIP); // Нарисовать ореол

    glTexCoord2f(0.0f, 0.0f);         

    glVertex2f(q[0].x, q[0].y);

    glTexCoord2f(0.0f, 1.0f);

    glVertex2f(q[1].x, q[1].y);

    glTexCoord2f(1.0f, 0.0f);

    glVertex2f(q[2].x, q[2].y);

    glTexCoord2f(1.0f, 1.0f);

    glVertex2f(q[3].x, q[3].y);

  glEnd();                   

  glPopMatrix(); // Восстановить матрицу вида модели

}

 

void glCamera::RenderGlow(GLfloat r, GLfloat g, GLfloat b, GLfloat a, glPoint p, GLfloat scale)

{

  glPoint q[4];

 

  // По существу мы делаем 2D прямоугольник из 4 точек

  // при этом мы не нуждаемся в z-координате, поскольку

  // мы инверсно вращаем камеру, поэтому

  // текстурированный прямоугольник будет всегда обращен

  // на зрителя.

  q[0].x = (p.x - scale); // Задать x-координату минус масштаб относительно центральной точки.

  q[0].y = (p.y - scale); // Задать y-координату минус масштаб относительно центральной точки.

  q[1].x = (p.x - scale); // Задать x-координату минус масштаб относительно центральной точки.

  q[1].y = (p.y + scale); // Задать y-координату плюс масштаб относительно центральной точки.

  q[2].x = (p.x + scale); // Задать x-координату плюс масштаб относительно центральной точки.

  q[2].y = (p.y - scale); // Задать y-координату минус масштаб относительно центральной точки.

  q[3].x = (p.x + scale); // Задать x-координату плюс масштаб относительно центральной точки.

  q[3].y = (p.y + scale); // Задать y-координату плюс масштаб относительно центральной точки.

 

  glPushMatrix(); // Сохранить матрицу вида модели

  glTranslatef(p.x, p.y, p.z); // Сместить в точку

  glRotatef(-m_HeadingDegrees, 0.0f, 1.0f, 0.0f);

  glRotatef(-m_PitchDegrees, 1.0f, 0.0f, 0.0f);

  glBindTexture(GL_TEXTURE_2D, m_GlowTexture); // Привязать текстуру пятна

  glColor4f(r, g, b, a); // Задать цвет, так как текстура в градациях серого

  glBegin(GL_TRIANGLE_STRIP); // Нарисовать пятно

    glTexCoord2f(0.0f, 0.0f);         

    glVertex2f(q[0].x, q[0].y);

    glTexCoord2f(0.0f, 1.0f);

    glVertex2f(q[1].x, q[1].y);

    glTexCoord2f(1.0f, 0.0f);

    glVertex2f(q[2].x, q[2].y);

    glTexCoord2f(1.0f, 1.0f);

    glVertex2f(q[3].x, q[3].y);

  glEnd();                   

  glPopMatrix(); // Восстановить матрицу вида модели

}

 

void glCamera::RenderBigGlow(GLfloat r, GLfloat g, GLfloat b, GLfloat a, glPoint p, GLfloat scale)

{

  glPoint q[4];

 

  // По существу мы делаем 2D прямоугольник из 4 точек

  // при этом мы не нуждаемся в z-координате, поскольку

  // мы инверсно вращаем камеру, поэтому

  // текстурированный прямоугольник будет всегда обращен

  // на зрителя.

  q[0].x = (p.x - scale); // Задать x-координату минус масштаб относительно центральной точки.

  q[0].y = (p.y - scale); // Задать y-координату минус масштаб относительно центральной точки.

  q[1].x = (p.x - scale); // Задать x-координату минус масштаб относительно центральной точки.

  q[1].y = (p.y + scale); // Задать y-координату плюс масштаб относительно центральной точки.

  q[2].x = (p.x + scale); // Задать x-координату плюс масштаб относительно центральной точки.

  q[2].y = (p.y - scale); // Задать y-координату минус масштаб относительно центральной точки.

  q[3].x = (p.x + scale); // Задать x-координату плюс масштаб относительно центральной точки.

  q[3].y = (p.y + scale); // Задать y-координату плюс масштаб относительно центральной точки.

 

  glPushMatrix(); // Сохранить матрицу вида модели

  glTranslatef(p.x, p.y, p.z); // Сместить в точку

  glRotatef(-m_HeadingDegrees, 0.0f, 1.0f, 0.0f);

  glRotatef(-m_PitchDegrees, 1.0f, 0.0f, 0.0f);

  glBindTexture(GL_TEXTURE_2D, m_BigGlowTexture); // Привязать текстуру пятна

  glColor4f(r, g, b, a); // Задать цвет, так как текстура в градациях серого

  glBegin(GL_TRIANGLE_STRIP); // Нарисовать пятно

    glTexCoord2f(0.0f, 0.0f);         

    glVertex2f(q[0].x, q[0].y);

    glTexCoord2f(0.0f, 1.0f);

    glVertex2f(q[1].x, q[1].y);

    glTexCoord2f(1.0f, 0.0f);

    glVertex2f(q[2].x, q[2].y);

    glTexCoord2f(1.0f, 1.0f);

    glVertex2f(q[3].x, q[3].y);

  glEnd();                   

  glPopMatrix(); // Восстановить матрицу вида модели

}

 

void glCamera::RenderStreaks GLfloat r, GLfloat g, GLfloat b, GLfloat a, glPoint p, GLfloat scale)

{

  glPoint q[4];

 

  // По существу мы делаем 2D прямоугольник из 4 точек

  // при этом мы не нуждаемся в z-координате, поскольку

  // мы инверсно вращаем камеру, поэтому

  // текстурированный прямоугольник будет всегда обращен

  // на зрителя.

  q[0].x = (p.x - scale); // Задать x-координату минус масштаб относительно центральной точки.

  q[0].y = (p.y - scale); // Задать y-координату минус масштаб относительно центральной точки.

  q[1].x = (p.x - scale); // Задать x-координату минус масштаб относительно центральной точки.

  q[1].y = (p.y + scale); // Задать y-координату плюс масштаб относительно центральной точки.

  q[2].x = (p.x + scale); // Задать x-координату плюс масштаб относительно центральной точки.

  q[2].y = (p.y - scale); // Задать y-координату минус масштаб относительно центральной точки.

  q[3].x = (p.x + scale); // Задать x-координату плюс масштаб относительно центральной точки.

  q[3].y = (p.y + scale); // Задать y-координату плюс масштаб относительно центральной точки.

 

  glPushMatrix(); // Сохранить матрицу вида модели

  glTranslatef(p.x, p.y, p.z); // Сместить в точку

  glRotatef(-m_HeadingDegrees, 0.0f, 1.0f, 0.0f);

  glRotatef(-m_PitchDegrees, 1.0f, 0.0f, 0.0f);

  glBindTexture(GL_TEXTURE_2D, m_StreaksTexture); // Привязать текстуру полос

  glColor4f(r, g, b, a); // Задать цвет, так как текстура в градациях серого

  glBegin(GL_TRIANGLE_STRIP); // Нарисовать полосы

    glTexCoord2f(0.0f, 0.0f);         

    glVertex2f(q[0].x, q[0].y);

    glTexCoord2f(0.0f, 1.0f);

    glVertex2f(q[1].x, q[1].y);

    glTexCoord2f(1.0f, 0.0f);

    glVertex2f(q[2].x, q[2].y);

    glTexCoord2f(1.0f, 1.0f);

    glVertex2f(q[3].x, q[3].y);

  glEnd();                   

  glPopMatrix(); // Восстановить матрицу вида модели

}

 

Вы можете использовать клавиши 'W', 'S', ', и 'D' для вращения камеры, '1 'и' 2' для включения и выключения отображения информации. 'Z' задает камере постоянную скорость вперед. 'C' – постоянную скорость назад. 'X' – останавливает камеру.

 

Вот и весь урок! Рад узнать, все ваши вопросы, комментарии и жалобы, только щелкните на мое имя в конце статьи и пошлите мне сообщение по электронной почте. Конечно, я не первый, кто сделал блики от объектива, и ниже я привожу несколько ссылок, которые мне кажутся полезными, и помогли мне при написании этого урока. Так же я благодарю Dave Steere, Cameron Tidwell, Bert Sammons, и Brannon Martindale за их помощь при проверке этого урока на разных видеокартах. Спасибо парни! Наслаждайтесь уроком!

Другие ресуры:
http://www.gamedev.net/reference/articles/article874.as
http://www.gamedev.net/reference/articles/article813.asp
http://www.opengl.org/developers/code/mjktips/lensflare
http://www.markmorley.com/opengl/frustumculling.htm
http://oss.sgi.com/projects/ogl-sample/registry/HP/occlusion_test.txt
http://oss.sgi.com/projects/ogl-sample/registry/NV/occlusion_query.txt
Ваше здоровье!

 

Примечание от Dario Corno по кличке rIO of Spinning Kids:

 

Я добавил в этот урок проверку на видимость: есть ли перед камерой хоть один объект. Если перед камерой что-то есть, то можно выключить блики. Новый код в уроке хорошо откомментирован и помечен как «Новый материал», поэтому, если хотите посмотреть его, просто поищите по тексту эту строку.

 

Вот перечень всех модификаций:

·         новая функция в классе glCamera, названная IsOccluded, возвращает «истину», если перед заданной точкой есть объект;

·         несколько новых переменных для отрисовки gluCylinder (используется для теста видимости);

·         новый код в glDraw для рисования объекта;

·         новый код для сброса gluCylinder.

 

Вот и все, надеюсь, что модификации будут вам интересны!

 

P.S: Кое-что осталось и на дом! Хорошо было бы проверять не одну точку, а несколько, чтобы сделать постепенное стирание бликов, а не исчезновение их.

© Vic Hollis

PMG  22 октября 2006 (c)  Сергей Анисимов