Динамические тени, основаные на 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, но так хоть посмотреть можно не градиент синего // а то, что получилось. }
В итоге получаем следующее :