Главная Статьи Ссылки Скачать Скриншоты Юмор Почитать Tools Проекты Обо мне Гостевая Форум |
В ряде случаев возникает задача, когда надо большой объект покрыть довольно небольшой текстурой. Примером этого может быть ландшафт или стена большого здания. Но часто мы не можем иметь текстуру очень большого разрешения и вынуждены ее повторять. Проблема такого подхода состоит в том, что при обычном повторении текстуры (например, через GL_REPEAT) возникает отчетливо видимое повторение, что крайне нежелательно.
Я наткнулся на описание трех подходов, позволяющих получить повторение текстуры без явно заметных повторяющихся паттернов. В этой статье я рассмотрю все эти три подхода и проиллюстрирую их применение.
Первый подход состоит в том, что при повторении текстуры каждому ее экземпляру мы назначаем свое уникальное смещение и поворот (на угол, кратный 90). Для их вычисления мы воспользуемся псевдослучайными числами. Тем самым по получим неповторяющийся паттерн на всей плоскости.
Однако при этом мы должны обеспечить непрерывное склеивание отдельных экземпляр текстуры вдоль границ. Кроме того, для чтения из текстур нам понадобится использовать производные текстурных координат для правильного выбора уровня детализации. Предлагаемый способ склеивания состоит в том, что мы берем четыре соседних экземпляра текстуры (каждый со своими смещениями и поворотом) и сшиваем их с заданными весами.
-- vertex
#version 330 core
layout(location = 0) in vec3 pos;
layout(location = 1) in vec2 tex;
layout(location = 2) in vec3 normal;
uniform mat4 proj;
uniform mat4 mv;
uniform mat3 nm;
uniform vec3 lightDir;
out vec3 l;
out vec3 n;
out vec2 tx;
void main(void)
{
vec4 pt = mv * vec4 ( pos, 1.0 );
tx = tex;
n = normalize ( nm * normal );
l = normalize ( lightDir );
gl_Position = proj * pt;
}
-- fragment
#version 330 core
in vec2 tx;
in vec3 l;
in vec3 n;
out vec4 color; // output value
uniform sampler2D image;
const vec3 lightColor = vec3 ( 1.0 );
vec4 hash4 ( in vec2 p )
{
return fract ( sin ( vec4 ( 1.0 + dot ( p, vec2(37.0,17.0)),
2.0 + dot ( p, vec2(11.0,47.0)),
3.0 + dot ( p, vec2(41.0,29.0)),
4.0 + dot ( p, vec2(23.0,31.0))))*103.0);
}
vec4 textureNoTile ( in vec2 uv )
{
ivec2 iuv = ivec2 ( floor ( uv ) );
vec2 fuv = fract ( uv );
// generate per-tile transform
vec4 ofa = hash4 ( iuv + ivec2(0,0) );
vec4 ofb = hash4 ( iuv + ivec2(1,0) );
vec4 ofc = hash4 ( iuv + ivec2(0,1) );
vec4 ofd = hash4 ( iuv + ivec2(1,1) );
vec2 ddx = dFdx ( uv );
vec2 ddy = dFdy ( uv );
// transform per-tile uvs
ofa.zw = sign ( ofa.zw - 0.5 );
ofb.zw = sign ( ofb.zw - 0.5 );
ofc.zw = sign ( ofc.zw - 0.5 );
ofd.zw = sign ( ofd.zw - 0.5 );
// uv's, and derivatives (for correct mipmapping)
vec2 uva = uv * ofa.zw + ofa.xy;
vec2 ddxa = ddx * ofa.zw;
vec2 ddya = ddy * ofa.zw;
vec2 uvb = uv * ofb.zw + ofb.xy;
vec2 ddxb = ddx * ofb.zw;
vec2 ddyb = ddy * ofb.zw;
vec2 uvc = uv * ofc.zw + ofc.xy;
vec2 ddxc = ddx * ofc.zw;
vec2 ddyc = ddy * ofc.zw;
vec2 uvd = uv * ofd.zw + ofd.xy;
vec2 ddxd = ddx * ofd.zw;
vec2 ddyd = ddy * ofd.zw;
// fetch and blend
vec2 b = smoothstep ( 0.25,0.75, fuv );
return mix( mix( textureGrad ( image, uva, ddxa, ddya ),
textureGrad ( image, uvb, ddxb, ddyb ), b.x ),
mix( textureGrad ( image, uvc, ddxc, ddyc ),
textureGrad ( image, uvd, ddxd, ddyd ), b.x), b.y );
}
void main ()
{
color = textureNoTile ( tx );
}
Рис 1. результаты склеивания первым способом
Второй способ, дающий более аккуратный результат, заключается в что мы как бы бросаем на поверхность копии повернутых текстуры. Каждая такая копия имеет свой центр и смешивание таких текстур зависит от расстояния до этого центра. Это очень сильно напоминает технику texture bombing. Основным недостатком такого подхода является 8 чтений их текстуры на каждый фрагмент, что довольно дорого.
-- vertex
#version 330 core
layout(location = 0) in vec3 pos;
layout(location = 1) in vec2 tex;
layout(location = 2) in vec3 normal;
uniform mat4 proj;
uniform mat4 mv;
uniform mat3 nm;
uniform vec3 lightDir;
out vec3 l;
out vec3 n;
out vec2 tx;
void main(void)
{
vec4 pt = mv * vec4 ( pos, 1.0 );
tx = tex;
n = normalize ( nm * normal );
l = normalize ( lightDir );
gl_Position = proj * pt;
}
-- fragment
#version 330 core
in vec2 tx;
in vec3 l;
in vec3 n;
out vec4 color; // output value
uniform sampler2D image;
const vec3 lightColor = vec3 ( 1.0 );
vec4 hash4 ( in vec2 p )
{
return fract ( sin ( vec4 ( 1.0 + dot ( p, vec2(37.0,17.0)),
2.0 + dot ( p, vec2(11.0,47.0)),
3.0 + dot ( p, vec2(41.0,29.0)),
4.0 + dot ( p, vec2(23.0,31.0))))*103.0);
}
vec4 textureNoTile ( in vec2 uv )
{
vec2 p = floor ( uv );
vec2 f = fract ( uv );
// derivatives (for correct mipmapping)
vec2 ddx = dFdx ( uv );
vec2 ddy = dFdy ( uv );
// voronoi contribution
vec4 va = vec4 ( 0.0 );
float wt = 0.0;
for ( int j = -1; j <= 1; j++ )
for ( int i = -1; i <= 1; i++ )
{
vec2 g = vec2 ( float(i), float(j) );
vec4 o = hash4 ( p + g );
vec2 r = g - f + o.xy;
float d = dot ( r, r );
float w = exp ( -5.0*d );
vec4 c = textureGrad ( image, uv + o.zw, ddx, ddy );
va += w*c;
wt += w;
}
// normalization
return va/wt;
}
void main ()
{
color = textureNoTile ( tx );
}
Третий подход дает результаты похожего качества, но ценой меньшего числа выборок из текстуры. ДЛя этого мы будем использовать вспомогательную текстуру (или функцию), разбивающую всю область на регионы и в каждом из таких регионов мы будем использовать свой вариант исходной текстуры.
Поскольку нам нужно как-то скрыть линии склейки регионов, то мы будем использовать смешивание текстур и индекс региона будет не целым числом, а вещественным.
-- vertex
#version 330 core
layout(location = 0) in vec3 pos;
layout(location = 1) in vec2 tex;
layout(location = 2) in vec3 normal;
uniform mat4 proj;
uniform mat4 mv;
uniform mat3 nm;
uniform vec3 lightDir;
out vec3 l;
out vec3 n;
out vec2 tx;
void main(void)
{
vec4 pt = mv * vec4 ( pos, 1.0 );
tx = tex;
n = normalize ( nm * normal );
l = normalize ( lightDir );
gl_Position = proj * pt;
}
-- fragment
#version 330 core
in vec2 tx;
in vec3 l;
in vec3 n;
out vec4 color; // output value
uniform sampler2D image;
const vec3 lightColor = vec3 ( 1.0 );
float sum ( vec3 v )
{
return v.x + v.y + v.z;
}
vec2 hash ( in float x )
{
return sin ( x * vec2 ( 3.0, 7.0 ) );
}
vec4 textureNoTile ( in vec2 x, in float v )
{
// sample variation pattern
float k = texture ( image, 0.005*x ).x; // cheap (cache friendly) lookup
// compute index
float index = k * 8.0;
float f = fract ( index );
// offsets for the different virtual patterns
float ia = floor ( index );
vec2 offsa = hash ( ia );
vec2 offsb = hash ( ia + 1.0 );
// compute derivatives for mip-mapping
vec2 dx = dFdx ( x );
vec2 dy = dFdy ( x );
// sample the two closest virtual patterns
vec4 cola = textureGrad ( image, x + offsa, dx, dy );
vec4 colb = textureGrad ( image, x + offsb, dx, dy );
// interpolate between the two virtual patterns
return mix ( cola, colb, smoothstep ( 0.2, 0.8, f - 0.1*sum(cola.xyz-colb.xyz)));
}
void main ()
{
color = textureNoTile ( tx, 1.0 );
}
Все исходные файлы для этой статьи (python и GLSL) добавлены в репозиторий примеров к книге.