Программирование игр, создание игрового движка, OpenGL, DirectX, физика, форум
GameDev.ru / Программирование / Статьи / Динамические тени, основаные на CubeMap

Динамические тени, основаные на CubeMap

Автор:

Это статья для начинающих, в которой приведён простейший пример построения динамических теней с помощью СubeMap'ов.

Источник построенный на CubeMap имеет свои плюсы и минусы.

Плюсы:
Точечный свет, который не нужно направлять.
Освещение / затемнение всего, что будет в CubeMap'e.
Простота создания.

Минусы:
Нужно строить свой cubemap, для каждого источника света, а из-за этого потеря скорости.

Для начала нужно сделать сам источник света, с которым мы и будем работать:

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

Однако нам ещё понадобится Уин, для CubeMap'a:

В и тоге получаем класс источника света:

  TLightSource = class
  public
    Position : TVector;
    Radius   : Single;
    Color    : TVector;
    ShadowMap: Cardinal;
  end;

К тому же, нам нужно включать и выключать его во время надобности. И самое главное, нужна функция обновления CubeMap'a теней, в итоге у нас ещё добавится 4 процедурки (создания, включения, выключения и обновления теней).

Начнём с простого: Create (создания), Bind (включения), unBind(выключения).

Процедура создания источника света должна применить к источнику все основные параметры и создать для него CubeMap :

(Приведёные примеры взяты из кода демки к статье)

constructor TLightSource.Create(const Pos,Clr:TVector; Rad:Single);
const
  light_ambient   : Array[0..3] of Single = (0.05, 0.05, 0.05, 1);
begin
  Position  := Pos;
  Color     := Clr;
  Radius    := Rad;

  ShadowMap := CreateNewCube(ShadowRes); //ShadowRes константа равная 256;

  glLightfv(GL_Light0, GL_AMBIENT , @light_ambient);
end;

Функция создания CubeMap'a:

function CreateNewCube(res: Integer): Cardinal;
var
  i: GLenum;
begin
  glGenTextures(1, @Result);
  glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, Result);
  for i := GL_TEXTURE_CUBE_MAP_POSITIVE_X to GL_TEXTURE_CUBE_MAP_NEGATIVE_Z do
    glCopyTexImage2D(i, 0, GL_RGBA8, 0, 0, res, res, 0);

  glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

  glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

end;
procedure Bind;
var
  p : array [0..3] of single;
begin
  // Подключаем карту теней
  // Проверка на наличие шейдеров у видео карты (чтобы работало везде)
  if GL_ARB_shading_language_100 then
    TextureCubeMap_Enable(ShadowMap, 2); // Подключается ShadowMap
                                         // на 2 мульти текстуру

  // Этот источник света будет работать в том случае,
  // если шейдеров у видео карты не будет
  GLEnable(GL_Lighting);
  GLEnable(GL_Light0);

  // Передаем параметры источника света
  p[0] := Position.X;
  p[1] := Position.Y;
  p[2] := Position.Z;
  p[3] := 1;
  glLightfv(GL_Light0, GL_POSITION, @p); 

  p[0] := color.x;
  p[1] := color.y;
  p[2] := color.z;
  p[3] := Radius;
  glLightfv(GL_Light0, GL_DIFFUSE,  @p);
  glLightfv(GL_Light0, GL_SPECULAR, @p);
end;
procedure unBind;
begin
  glDisable(GL_Lighting);
  glDisable(GL_Light0);

  if (GL_ARB_shading_language_100) then
    TextureCubeMap_Disable(2);
end;

Шаг 1 до получения теней :

Рендерим сцену в cubemap и записываем в него дистанции точки от источника света. Обновляем CubeMap.

procedure UpdateShadowMap;
var
  cs: Cardinal;
  Render:Procedure;
begin
    Включаем FBO или Point Buffer, в моём случае это Point Buffer
    pBuffer^.Enable; 
    // Настраеваем матрицу проекции
    glViewport(0, 0, ShadowRes, ShadowRes);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity;
    gluPerspective(90, 1, 1, Radius);
    glMatrixMode(GL_MODELVIEW);

    // Бегая по шести старанам CubeMap'a
    // устанавливаем камеру вида для каждой из сторон
    for cs := GL_TEXTURE_CUBE_MAP_POSITIVE_X to GL_TEXTURE_CUBE_MAP_NEGATIVE_Z do
    begin
      glClear(GL_DEPTH_BUFFER_BIT or GL_Color_BUFFER_BIT);
      glLoadIdentity;
      case cs of
        GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB: begin
            glRotatef(180, 0, 0, 1);
            glRotatef(90, 0, 1, 0);
          end;
        GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB: begin
            glRotatef(-90, 1, 0, 0);
          end;
        GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB: begin
            glRotatef(180, 1, 0, 0);
          end;
        GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB: begin
            glRotatef(180, 0, 0, 1);
            glRotatef(-90, 0, 1, 0);
          end;
        GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB: begin
            glRotatef(90, 1, 0, 0);
          end;
        GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB: begin
            glRotatef(180, 0, 0, 1);
          end;
      end;
    // Устанавливаем центер мира
      with Position do glTranslatef(-x, -y, -z);

    // Включаем шейдер теней (он будет приведён ниже)
    // Передаем шейдеру информацию о позиции и радиусе источника света
    Shader^.start;
      Shader^.BindUniform('light', @position,3);
      Shader^.BindUniform('radius',@radius, 1);

    //Рендерим сцену
    Render;
    // Останавливаем шейдер
    Shader^.stop;

    //И записываем всё что было в поле видимости в конкретную сторону
      glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, ShadowMap);
      glCopyTexSubImage2D(cs, 0, 0, 0, 0, 0, ShadowRes, ShadowRes);
      glFlush;
    end;
    // Отключаем буфер
    pBuffer^.Disable;

end;

Шейдерный код для теней:

shadow.vp

uniform vec3 light;
uniform float radius;
varying vec3 vertex;

void main()
{
   gl_Position  = ftransform();
   vertex = ( gl_Vertex.xyz - light ) / radius;
}

shadow.fp

varying vec3 vertex;

void main()
{
 vec4 pack = vec4( 1.0, 256.0, 65536.0, 16777216.0 );
 // Сжимаем RGB цвет в длину (раскидывая её по компонентам) =)
 vec4 len  = length(vertex) * pack;
 
 gl_FragColor = fract(len);
// p.s.
// Можно конечно и без Fract'a, но так хоть посмотреть можно не градиент синего
// а то, что получилось.
}

В итоге получаем следующее :

картинка итого работы шейдера теней | Динамические тени, основаные на CubeMap

Шаг 2 получение теней:

Рендерим туже сцену, используя полученный cubemap.

  // Устанавливаем рендер сцены на "только Z",
  // то есть рисуем только сам меш (без текстур)
  MAT_MoDE:=MAT_Z_Only;

  light.UpdateShadowMap(@gl_draw_model,@FShadowBuffer,@Shadow);
  ....

  // Устанавливаем рендер сцены на "освещение", рисуем все как нужно =)
  MAT_MoDE:=MAT_Lighting;

  // Включаем шейдер для получения результата "на лицо"
  bump.Start;
  // Передаём нужные параметры источника света позицию и радиус
  bump.BindUniform('light', @light.position,3);
  bump.BindUniform('radius',@light.radius, 1);

  light.Bind;
    gl_draw_model();
  light.unBind;

  //Отрубаем шейдер
  bump.Stop;

  //....

В шейдере получаем из cubemap'a дистанцию точки, рассчитываем заново дистанцию точки от источника света (они будут служить текстурными координатами и дистанцией для сравнивания), сравниваем и получаем ч/б уровень яркости. После его можно использовать везде, где нам понадобится.

bump.vp

varying   vec3  light_direction;
varying   vec3  eye_direction;
varying   vec4  shadow;
uniform   vec3 light;
uniform   float radius;

void main()
{
   // Простой шейдер бумпа которы все умеют делать
   gl_Position  = ftransform();
   gl_TexCoord[0] = gl_MultiTexCoord0;

   vec3 vVertex = vec3(gl_ModelViewMatrix * gl_Vertex);

   vec3 n = normalize(gl_NormalMatrix * gl_Normal);
   vec3 t = normalize(gl_NormalMatrix * gl_MultiTexCoord1.xyz);
   vec3 b = cross(n, t);

   mat3 tbnMatrix = mat3(t.x, b.x, n.x,
                         t.y, b.y, n.y,
                         t.z, b.z, n.z);
   
   vec3 tmp = (gl_LightSource[0].position.xyz - vVertex) / (radius+0.01);
   light_direction = vec3(tbnMatrix * tmp);

   eye_direction = -vVertex;
   eye_direction = tbnMatrix * eye_direction;

   // Наше "место" для теней =)
   // Получаем дистанцию, которая в свою очередь
   //будет выполнять роль текстурных коортинат теневой карты
   shadow.xyz = (gl_Vertex.xyz - light) / radius;
   shadow.w = 1.0;
}

По сути, CubeMap теней, имеет отношение в шейдере только к освещённости точки (atten ниже в коде), больше ничего другого он не затрагивает.

bump.fp

#define CAST_SHADOW
varying vec3  light_direction; 
varying vec3  eye_direction;
varying vec4  shadow;

uniform sampler2D color_map, normal_map;
uniform samplerCube shadow_map;
uniform float exponent;

void main()
{

  #ifdef CAST_SHADOW
    vec4 unpack = vec4(1.0, 0.00390625, 0.0000152587890625, 0.000000059604644775390625);
    float zbias = 0.0100000;
  
    vec4   sat = textureCube( shadow_map, shadow.xyz );
    // "Распаковываем" RGB цвет в длину
    float  att = dot(sat,unpack);
      att = att + zbias;
      att = ( att > length(shadow.xyz) ) ? 1.0 : 0.0;
  #endif;
   
         
   vec4 allcolor = vec4(0,0,0,0);
   vec3 v        = normalize(eye_direction);
   vec2 texCoord = gl_TexCoord[0].st;

   vec4  base =           texture2D(color_map    , texCoord ).xyzw;
   vec3  bump = normalize(texture2D(normal_map   , texCoord ).xyz * 2.0 - 1.0);

  float distance_sqr  = dot(light_direction, light_direction);
  #ifdef CAST_SHADOW
   float atten    = max(0.0, 1.0 - distance_sqr) * att;
  #else
   float atten    = max(0.0, 1.0 - distance_sqr);
  #endif;
  
  vec3  l   = light_direction * inversesqrt(distance_sqr);
  float d   = max(0.0, dot(l, bump) );
  float r   = dot(reflect(-l, bump), v);
  float s    = pow( clamp(r, 0.0, 1.0), exponent) ;
  
  vec4 color = base * (gl_FrontLightProduct[0].ambient +
              gl_FrontLightProduct[0].diffuse *  d);
  color  += gl_FrontLightProduct[0].diffuse * gl_FrontLightProduct[0].specular * s ;

  allcolor  += color * (atten + gl_FrontLightProduct[0].ambient);

  gl_FragColor = vec4(allcolor.xyz , base.w);
}

Итог работы всего этого (3 источника света без оптимизации):

My Shadow's Demo GLSL | Динамические тени, основаные на CubeMap

Для того чтобы повысить производительность, рекомендую рассчитывать размер cubemap'a, исходя из дистанции источника света до камеры (так как уже это реализовал).

Скачать демку можно тут:

MyShadowsDemo (GLSL)

18 июня 2009

#CubeMap, #Delphi, #GLSL, #OpenGL, #тени

Установка дверей межкомнатных замена и установка межкомнатных дверей.
2001—2018 © GameDev.ru — Разработка игр