Главная Статьи Ссылки Скачать Скриншоты Юмор Почитать Tools Проекты Обо мне Гостевая |
Довольно часто возникает необходимость в наложении на основную текстуру ряда отдельных изображений (элементов). Это могут быть царапины и повреждения на стенах, цветы и т.п. на ландшафте и много другое.
Главным здесь является то, что таких накладываемых элементов должно быть очень много и их наложение не должно носить регулярный характер.
В подобных случаях очень полезным приемом может оказаться так называемый texture bombing. Это процедурный метод для наложения небольших элементов (обычно изображений) на заданную поверхность нерегулярным образом.
Этому посвящена целая глава книги GPU Gems, однако эта книга не всем у нас доступна и, к тому же, на компакт-диске к ней отсутствует исходный код для этой главы.
Простейший вариант texture bombing заключается в использовании текстурных координат (s,t) для разбиения поверхности объекта на ряд участков (называемых в оригинале cells). Эти участки образуют прямоугольную матрицу, при этом индекс для текущего участка (cell.x, cell.y)) легко вычисляется как целая часть промасштабированных текстурных координат:
vec2 scaledTex = gl_TexCoord [0].xy * scale; vec2 cell = floor ( scaledTex ); vec2 offs = scaledTex - cell;
Важным моментом является то, что для каждой точки, принадлежащей данному участку, значение вектора cell будет одним и тем же.
Это значит, что этот вектор можно использовать для определения координат накладываемого на данный участок элемента. При этом полученное значение координат будет согласованным для всех фрагментов, соответствующих данному участку.
Поскольку нам требуется наложить элемент случайным образом, а встроенного генератора случайных числе в GLSL нет, то для получения (почти) случайных значений можно воспользоваться специальной текстурой.
Для этого создадим текстуру, значения цветовых компонент для каждого пиксела будут получены при помощи генератора случайных чисел. Обратите внимание, что для такой текстуры нельзя включать пирамидальное фильтрование, иначе вся случайность может просто исчезнуть.
Рис 1. Пример текстуры со случайными значениями (randomMap).
Поскольку значения компонент вектора cell всегда целочисленные, то для получения "хороших" текстурных координат следует обращаться к текстуре randomMap используя произведение cell на некоторый заведомо дробный вектор.
vec2 rndOffs = cell * vec2 ( 0.037, 0.119 ); vec3 rnd = texture2D ( randomMap, rndOffs ).rgb;
Далее мы будем использовать вектор rnd.xy как положение верхнего левого угла накладываемого изображения и использовать вектор offs для индексирования накладываемой текстуры.
Рис 2. Наложение элемента на участок cell.
Таким образом мы приходим к следующему фрагментному шейдеру.
uniform sampler2D randomMap; uniform sampler2D decalMap; uniform sampler2D glyphMap; void main (void) { const float scale = 1.0; // get decal color vec4 decalColor = texture2D ( decalMap, gl_TexCoord [0].xy * 0.25 ); // get cell no based on texture coordinates vec2 tex = gl_TexCoord [0].xy * scale; vec2 cell = floor ( tex ); vec2 offs = tex - cell; vec2 rndOffs = cell * vec2 ( 0.037, 0.119 ); vec3 rnd = texture2D ( randomMap, rndOffs ).rgb; vec4 bomb = texture2D ( glyphMap, offs.xy - rnd.xy ); decalColor += bomb; gl_FragColor = decalColor; }
В качестве вершинного шейдера мы будем использовать простейший вершинный шейдер, передающий текстурные координаты для нулевого текстурного блока.
void main(void) { gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; gl_TexCoord [0] = gl_MultiTexCoord0; }
На следующем рисунке приводится изображение кубика с наложенной на него элементами.
Рис 3. Простейший случай наложения.
Обратите внимание, что вместо случайного наложения текстуры мы получили во многих случаях просто "обрезки" накладываемого изображения. При этом это "обрезание" происходит по границам раздела отдельных участков.
Связано это с тем, что накладываемая картинка (см рис. 4) может легко выйти за пределы текущего участка и попасть в соседние, где она уже отрисована не будет - в соседних участках уже имеется свое наложение.
Рис 4. Накладываемое изображение выходит за пределы текущего участка.
Есть два простых способа борьбы с этим.
Во-первых, можно промасштабировать вектор rnd.xy и текстурные координаты накладываемого изображения, чтобы гарантировать, что оно никогда не выйдет за пределы данного участка. Но подобный подход (хотя он и очень прост), не дает красиво выглядящего результирующего изображения.
Во-вторых, можно при обработке соседних участков (лежащих правее и ниже) проверять, не попадает ли в них части изображения с данного участка и при необходимости выводить их.
Рассмотрим далее второй подход подробнее.
Рис 5. Соседние участки, которые может задеть накладываемое изображение.
Поскольку при обработке фрагмента из одного участка мы не имеем возможности обратиться к фрагментам из других участков, то нам следует обрабатывая данный участок проверить не попадает ли в него изображения с лежащий выше и левее соседних участков.
В результате мы приходим к следующей фрагментной программе.
uniform sampler2D randomMap; uniform sampler2D decalMap; uniform sampler2D glyphMap; void main (void) { const float scale = 1.0; // get decal color vec4 decalColor = texture2D ( decalMap, gl_TexCoord [0].xy * 0.25 ); // get cell no based on texture coordinates vec2 tex = gl_TexCoord [0].xy * scale; vec2 cell = floor ( tex ); vec2 offs = tex - cell; int i, j; for ( i = -1; i <= 0; i++ ) for ( j = -1; j <= 0; j++ ) { vec2 curCell = cell + vec2 ( i, j ); vec2 curOffs = offs - vec2 ( i, j ); vec2 rndOffs = curCell * vec2 ( 0.037, 0.119 ); vec3 rnd = texture2D ( randomMap, rndOffs ).rgb; vec2 t = curOffs.xy - rnd.xy; decalColor += texture2D ( glyphMap, t ); } gl_FragColor = decalColor; }
На рисунке 6 Вы видите получаемое изображение.
Рис 6. Изображение с учетом влияния соседних участков.
Обратите внимание, что для того, чтобы приведенный выше шейдер работал корректно, необходимо для накладываемой текстуры использовать режим отсечения текстурных координат GL_CLAMP.
Ниже приводится листинг функции main программы на С++, строящей изображение с рисунка 6.
int main ( int argc, char * argv [] ) { // initialize glut glutInit ( &argc, argv ); glutInitDisplayMode ( GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH ); glutInitWindowSize ( 500, 500 ); // create window glutCreateWindow ( "Example of GLSL texture bombing shader - 2" ); // register handlers glutDisplayFunc ( display ); glutReshapeFunc ( reshape ); glutKeyboardFunc ( key ); glutMouseFunc ( mouse ); glutMotionFunc ( motion ); glutIdleFunc ( animate ); init (); initExtensions (); printfInfo (); if ( !GlslProgram :: isSupported () ) { printf ( "GLSL not supported.\n" ); return 1; } if ( !program.loadShaders ( "bomb-2.vsh", "bomb-2.fsh" ) ) { printf ( "Error loading shaders:\n%s\n", program.getLog ().c_str () ); return 2; } // load textures decalMap = createTexture2D ( true, "../Textures/16.jpg" ); glyphMap = createTexture2D ( false, "particle2.tga" ); randomMap = createTexture2D ( false, "random.bmp" ); glBindTexture ( GL_TEXTURE_2D, glyphMap ); glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP ); glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP ); // install program object as part of // current state and set textures program.bind (); program.setTexture ( "decalMap", 0 ); program.setTexture ( "randomMap", 1 ); program.setTexture ( "glyphMap", 2 ); program.unbind (); glutMainLoop (); return 0; }
В рассмотренных примерах осуществлялось наложение белого изображения путем сложения цветов. Однако, если вместо сложения использовать замещение цветов, то можно получить вместо аккуратного закрывания одного изображения другим (из другого участка), случайный характер закрывания.
Для борьбы с этим можно ввести приоритеты текстур. В качестве источников приоритетов удобно использовать один из каналов (синий или альфа) текстуры randomMap.
float priority = -1.0; . . . if ( rnd.w > priority ) // higher priority case { decacColor = glyphColor; priority = rnd.w; }
Зачастую одного изображения на участок оказывается слишком мало. Несложно модифицировать код для получения SAMPLES_PER_CELL изображений на участок - для этого достаточно ввести еще один цикл (и в нем необходимо также корректировать координаты, используемые для обращения к randomMap).
uniform sampler2D randomMap; uniform sampler2D decalMap; uniform sampler2D glyphMap; #define SAMPLES_PER_CELL 3 void main (void) { const float scale = 1.0; // get diffuse color vec4 decalColor = texture2D ( decalMap, gl_TexCoord [0].xy * 0.25 ); // get cell no based on texture coordinates vec2 tex = gl_TexCoord [0].xy * scale; vec2 cell = floor ( tex ); vec2 offs = tex - cell; int i, j, sample; for ( i = -1; i <= 0; i++ ) for ( j = -1; j <= 0; j++ ) { vec2 curCell = cell + vec2 ( i, j ); vec2 curOffs = offs - vec2 ( i, j ); vec2 rndOffs = curCell * vec2 ( 0.037, 0.119 ); for ( sample = 0; sample < SAMPLES_PER_CELL; sample++ ) { vec3 rnd = texture2D ( randomMap, rndOffs ).rgb; rndOffs += vec2 ( 0.3123123, 0.431247 ); decalColor += texture2D ( glyphMap, curOffs.xy - rnd.xy ); } } gl_FragColor = decalColor; }
Рис 7. Использование нескольких изображений на участок.
Можно несколько улучшить вид получаемого изображения если вывод для каждого участка не ровно SAMPLES_PER_CELL элементов, а случайное число от 0 до SAMPLES_PER_CELL.
Для этого достаточно сравнивать одну из компонент вектора rnd с заданной вероятностью (например, 0.5) и осуществлять вывод только в том случае, если эта компонента меньше или равно заданной вероятности.
uniform sampler2D randomMap; uniform sampler2D decalMap; uniform sampler2D glyphMap; #define SAMPLES_PER_CELL 3 void main (void) { const float scale = 1.0; // get decal color vec4 decalColor = texture2D ( decalMap, gl_TexCoord [0].xy * 0.25 ); // get cell no based on texture coordinates vec2 tex = gl_TexCoord [0].xy * scale; vec2 cell = floor ( tex ); vec2 offs = tex - cell; int i, j, sample; for ( i = -1; i <= 0; i++ ) for ( j = -1; j <= 0; j++ ) { vec2 curCell = cell + vec2 ( i, j ); vec2 curOffs = offs - vec2 ( i, j ); vec2 rndOffs = curCell * vec2 ( 0.037, 0.119 ); for ( sample = 0; sample < SAMPLES_PER_CELL; sample++ ) { vec3 rnd = texture2D ( randomMap, rndOffs ).rgb; rndOffs += vec2 ( 0.3123123, 0.431247 ); if ( rnd.z > 0.5 ) decalColor += texture2D ( glyphMap, curOffs.xy - rnd.xy ); } } gl_FragColor = decalColor; }
Рис 8. Изменяемое количество изображений на участок.
Если требуется случайно выводить не одно и то же изображение, а одно из нескольких (равновероятно), то достаточно поместить эти изображения в одну текстуру (как строку из NUM_IMAGES изображений). Тогда достаточно слегка изменить способ расчета текстурных координат на накладываемом изображении для выбора одного изображения из нескольких.
Для получения индекса отдельного изображения можно использовать одну из компонент вектора rnd.
float index = floor ( NUM_IMAGES * rnd.z ); curOffs.x = (curOffs.x + index) / (float) NUM_IMAGES;
Можно легко модифицировать описанный алгоритм, для работы в трехмерном случае. Для этого номер участка будет задаваться трехмерным вектором, определяемым по исходным пространственным координатам объекта (с учетом масштаба).
На основе этого номера строится двухмерный вектор для обращения к randomMap. На следующих листингах приводятся вершинный и фрагментный шейдеры для случайного распределения в пространстве набора шаров (со случайно заданным цветом).
// vertex shader varying vec4 vertex; void main(void) { vertex = gl_Vertex; // untransformed vertex gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; gl_TexCoord [0] = gl_MultiTexCoord0 * 0.25; } // fragment shader varying vec4 vertex; uniform sampler2D randomMap; uniform sampler2D decalMap; void main (void) { const float scale = 0.8; // get diffuse color vec4 decalColor = texture2D ( decalMap, gl_TexCoord [0].xy ); // get cell no based on 3D-coordinates vec3 tex = vertex.xyz * scale; vec3 cell = floor ( tex ); vec3 offs = tex - cell; vec3 rndOffs = cell / scale; vec4 colorSum = vec4 ( 0.0 ); for ( int i = 0; i < 7; i++ ) { vec2 rndTex = rndOffs.xy + 0.1* rndOffs.zx + 0.3*rndOffs.yz; vec3 rnd = texture2D ( randomMap, rndTex ).rgb; vec3 center = vec3 ( 0.2 ) + 0.6 * rnd; float radius = 0.2 * fract ( 0.6 * rndOffs.x ); if ( length ( center - offs ) < radius ) // place a bomb decalColor = vec4 ( rnd, 1.0 ); rndOffs += vec3 ( 0.345, 0.386, 0.773 ); } gl_FragColor = decalColor; gl_FragColor.a = 1.0; }
На следующем рисунке приводится получающееся изображение.
Рис 9. Трехмерный texture bombing.
В заключении приводятся вершинный и фрагментный шейдеры, реализующие эффект разбегающихся кругов на воде от капель дождя при помощи texture bombing.
// // Rain via texture bombing vertex shader // varying vec4 vertex; void main(void) { vertex = gl_Vertex * vec4 ( 0.98423, 1.1023, 1.09348, 0.89231); gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; gl_TexCoord [0] = gl_MultiTexCoord0; } // // Rain via texture bombing // varying vec4 vertex; uniform float time; uniform sampler2D randomMap; uniform sampler2D decalMap; void main (void) { const float scale = 0.61234; // get decal color vec4 decalColor = texture2D ( decalMap, gl_TexCoord [0].xy ); // get time cell offset vec3 cell0 = floor ( vertex.xyz * scale ); vec3 offs0 = vertex.xyz * scale - cell0; vec3 rnd0 = texture2D ( randomMap, offs0.xy ).rgb; // offset time with cell0 float timeCell = floor ( time * 0.2 + rnd0.x ); float timeOffs = time * 0.2 - timeCell; // get cell no based on 3D-coordinates vec3 tex = vertex.xyz * scale + vec3 ( timeCell * 0.5 ); vec3 cell = floor ( tex ); vec3 offs = tex - cell; vec3 rndOffs = cell / scale; vec4 colorSum = vec4 ( 0.0 ); for ( int i = 0; i < 2; i++ ) { vec2 rndTex = rndOffs.xy + 0.1* rndOffs.zx + 0.3*rndOffs.yz; vec3 rnd = texture2D ( randomMap, rndTex ).rgb; vec3 center = vec3 ( 0.2 ) + 0.6 * rnd; float radius = 0.4 * fract ( 1.0 * ( timeOffs + rndOffs.x ) ); if ( abs ( length ( center - offs ) - radius ) < 0.015 ) // place a bomb colorSum += vec4 ( 1.0 ) * ( 1.0 - radius / 0.7 ); rndOffs += vec3 ( 0.345, 0.386, 0.773 ); } gl_FragColor = decalColor + colorSum; gl_FragColor.a = 1.0; }
Рис 10. Изображения, получаемые при помощи шейдера rain.
По этой ссылке можно скачать весь исходный код к этой статье. Также доступны для скачивания откомпилированные версии для M$ Windows, Linux и Mac OS X.