НФР_3

advertisement
8.4 Обзор методов имитации акварели
Моделирование акварели средствами компьютерной графики - одна из интереснейших
подзадач NPR. В настоящее время существует несколько подходов к моделированию
акварельных красок:
Размытие изображения и затемнение краев
Простейший подход использует размытие изображения и затемнение краев (четких
границ на изображении), но этот метод не дает реалистичного результата.
Методы, использующие покрытие изображения последовательностью штрихов
В зависимости от желаемого эффекта можно настраивать множество параметров штрихов
- размер, ориентацию, степень прозрачности и т.д. Если проводить анализ фильтруемого
изображения, можно использовать дополнительные возможности - ориентация штрихов
перпендикулярно градиенту, <незалезание> за края. Такие методы успешно применяются
для имитации импрессионизма, однако для акварели это не дает нужного эффекта изображения получаются не очень естественными. Используются в некоторых
коммерческих программах.
Метод визуализации поверхностей с эффектами акварели
Очень любопытный подход, использующий создание освещенной сцены путем наложения
нескольких полупрозрачных слоев краски. Каждый слой создается с помощью линейной
интегральной свертки шума Перлина (Perlin noise), затем с помощью обратной
вычитательной модели освещения (inverted substractive lighting model) вычисляется
распределение толщины слоев. [9]
Метод имитации акварели с помощью клеточных автоматов
Этот метод описан в [21]. Рассмотрим его подробнее.
Нижеописанная модель имитирует поведение воды и частиц краски с помощью
клеточного автомата.
Клеточный автомат представляет собой набор клеток, заданных регулярной сеткой,
каждая клетка находится в каждый момент времени в определенном состоянии. С
течением времени состояние каждой клетки меняется в зависимости от ее собственного
состояния и состояния соседних клеток. Пожалуй, самый известный пример
использования клеточных автоматов - игра Жизнь.
Перенос пигмента и модель диффузии
Чтобы смоделировать перенос пигмента с кисти на бумагу, а также распределение
пигмента на бумаге, используется двумерный клеточный автомат на конечной сетке,
представляющей поверхность бумаги. Он представляет собой массив клеток, каждая из
которых способна содержать определенное количество воды и пигмента (растворенного в
воде). Это похоже на лоток для приготовления льда, где краска наливается в один из
контейнеров и, если контейнер полон, растекается в соседние.
Итак, мы имеем следующее представление:





Каждая клетка Pij имеет 2 индекса i и j
Содержимое клеток описывается двумя переменными Wij и Iij, количеством воды и
пигмента соответственно.
Каждая клетка имеет 4-х соседей
Функция перехода состояний Sij = (Wij, Iij)
Sij(t + ∆t) = f (Sij(t), Sk(t) | k натуральное)
Моделирование бумаги
Чтобы смоделировать волокнистую структуру бумаги, добавим условие, что каждая
клетка может вмещать ограниченное количество воды и пигмента. Добавим к состоянию
каждой клетки 2 дополнительные переменные Bij и Cij - константы, выбранные
пропорционально толщине бумаги. Bij описывает высоту "дна" клетки, а Cij максимальный объем клетки. Волокна бумаги могут моделироваться различными
способами. Можно использовать псевдослучайные процессы, или использовать
библиотеки предопределенных волокон.
Моделирование кисти
Кисть состоит из отдельных щетинок. На каждой щетинке может помещаться некоторое
количество краски, и по мере рисования краска стекает на кончик кисточки, перемещается
на бумагу и кисточка сохнет. Дополнительный эффект дает неравномерное распределение
краски на щетинках кисти. Необычный, но эффективный способ представления кисти как второго клеточного автомата, с теми же состояниями Bij и Cij. Во время рисования
краска стекает к кончику кисти, что может быть смоделировано изменением высоты дна
для каждой клетки таким образом: Bij = Bij + θx. В этом выражении х - расстояние от
клетки Pij до кончика кисти, а θ - константа, пропорциональная наклону кисти.
Моделирование процесса рисования
Процесс рисования начинается с переноса частиц воды и краски с кисти на бумагу.
Клетки получают определенное количество воды и краски, далее за распределение его по
бумаге отвечает функция переноса. Это происходит за 4 шага. Для каждого временного
шага:
1. Перенос и диффузия частиц воды. Если клетка Pij полна воды, вода перельется в
соседние клетки. В то же время часть воды может перелиться из соседних клеток в Pij.
2. Перенос частиц краски. С краской аналогично.
3. Перенос краски, чтобы сбалансировать концентрацию. После переноса воды и краски в и из соседних клеток, концентрация краски нарушилась. Изменение концентрации
краски зависит коэффициента диффузии краски в воде и концентрации воды в клетках,
где происходит балансировка.
4. Испарение воды. На каждом шаге часть воды испаряется, и краска сохнет. Если вся вода
испарилась, частицы краски остаются в клетке Pij как сухая краска.
Эти 4 шага описывают локальную функцию переноса клеточного автомата, которая
выполняется для каждого временного шага.
Эта модель достаточно сложна и берет в расчет некоторые свойства акварели. В
зависимости от конкретной реализации кисти и функции переноса модель может быть
более или менее точной, однако в любом случае она имитирует только локальное
перетекание воды с пигментом, что является существенным ограничением.
Метод, в основе которого - моделирование физических процессов течения воды и
рассеивания пигмента.
Этот метод показал в настоящее время наиболее привлекательный и реалистичный
результат. [1], [16]
Несмотря на то что для создания настоящих акварельных картин на компьютере лучше
использовать последний подход, именно он показывает в настоящее время наиболее
привлекательный и реалистичный результат, а остальные подходы моделируют лишь
некоторые эффекты акварели, эти результаты тоже могут быть достаточно интересными.
9.Моделирование техники акварели
Одним из довольно простых методов моделирования техники акварели (watercolor
rendering) является разбиение процесса построения изображения на отдельные слои. Для
каждого такого слоя вычисляется его толщина (определяющая степень его влияния) и все
они последовательно наносятся на бумагу.
Самым первым слоем является слой, моделирующий диффузное освещение (за
исключением ярких бликов). Для определения толщины этого слоя используется
модифицированный вариант модели Фонга.
Толщина этого слоя определяется по следующей формуле:
Следующий слой отвечает на неосвещенные части и его толщина задается следующей
формулой:
Последний слой представлен шумовой текстурой, служащей для моделирования мазков
кистью. При это обычно эта шумовая текстура вытягивается в каком-либо направлении
для получения вида вытянутых мазков, выглядящих гораздо более реалистично.
Довольно простым способом композиции всех этих слоев является следующий:
Для рендеринга моделей, состоящих из большого числа небольших граней, вычисление
параметров слоев проще всего реализовать в вершинном шейдере.
Ниже приводится реализация такого шейдера на GLSL.
//
// Watercolor vertex shader
//
uniform
uniform
varying
varying
vec3
vec3
vec3
vec3
lightPos;
eyePos;
diffuseThickness;
unlitThickness;
void main(void)
{
const vec3
const vec3
const vec3
const float
one
ambient
diffuse
specPower
=
=
=
=
vec3 ( 1.0 );
vec3 ( 0.4, 0.4, 0.4 );
vec3 ( 0.0, 0.0, 1.0 );
50.0;
vec3
vec3
vec3
vec3
vec3
p
l
v
h
n
=
=
=
=
=
vec3 ( gl_ModelViewMatrix * gl_Vertex );
normalize ( lightPos - p );
normalize ( vec3 ( eyePos ) - p );
normalize ( l + v );
normalize ( gl_NormalMatrix * gl_Normal );
// compute layers thicknesses
diffuseThickness = (1.0 - pow ( max ( dot ( n, h ), 0.0 ), specPower ) ) * (one diffuse);
unlitThickness
= (1.0 - max ( dot ( n, l ), 0.0 ) ) * (one - ambient);
gl_Position
= gl_ModelViewProjectionMatrix * gl_Vertex;
gl_TexCoord [0] = gl_MultiTexCoord0;
}
Задачей фрагментного шейдера является получение значения из шумовой текстуры и
смешение его с проинтерполированными параметрами слоев.
Соответствующая реализация приводится ниже.
//
// Watercolor fragment shader
//
varying vec3 diffuseThickness;
varying vec3 unlitThickness;
uniform sampler2D noiseMap;
void main (void)
{
vec3 color = diffuseThickness;
vec3 noise = texture2D ( noiseMap, gl_TexCoord [0].xy * vec2 ( 0.7, 2.0 ) ).xyz;
color = vec3 ( 1.0 ) - color * unlitThickness * noise.x;
gl_FragColor = vec4 ( color, 1.0 );
}
Рис 1. Чайник, изображенный в стиле акварели.
Можно слегка модифицировать описанный алгоритм, "смягчая" резкие края объектов. Для
этого достаточно домножить толщину диффузного слоя на max((n,v),0). Данный
множитель, обращаясь в нуль на контурных линиях объекта, производит требуемое
смягчение.
При этом толщина диффузного слоя вычисляется по следующей формуле:
Изображение, полученное при использовании смягчения краев, приводится на рис 2.
Рис 2. Изображение чайника, выполненное в стиле акварели со смягчением краев.
Ниже приводятся соответствующие вершинный и фрагментный шейдеры.
//
// Watercolor vertex shader with edge softening
//
uniform vec3
uniform vec3
lightPos;
eyePos;
varying vec3
varying vec3
diffuseThickness;
unlitThickness;
void main(void)
{
const vec3
const vec3
const vec3
const float
vec3
vec3
vec3
vec3
vec3
p
l
v
h
n
=
=
=
=
=
one
ambient
diffuse
specPower
=
=
=
=
vec3 ( 1.0 );
vec3 ( 0.4, 0.4, 0.4 );
vec3 ( 0.0, 0.0, 1.0 );
50.0;
vec3 ( gl_ModelViewMatrix * gl_Vertex );
normalize ( lightPos - p );
normalize ( vec3 ( eyePos ) - p );
normalize ( l + v );
normalize ( gl_NormalMatrix * gl_Normal );
// compute layers thicknesses
diffuseThickness = (1.0 - pow ( max ( dot ( n, h ), 0.0 ), specPower ) ) *
(one - diffuse) * max ( dot ( n, v ), 0.0 );
unlitThickness
= (1.0 - max ( dot ( n, l ), 0.0 ) ) * (one - ambient);
gl_Position
= gl_ModelViewProjectionMatrix * gl_Vertex;
gl_TexCoord [0] = gl_MultiTexCoord0;
}
//
// Watercolor fragment shader with edge softening
//
varying vec3 diffuseThickness;
varying vec3 unlitThickness;
uniform sampler2D noiseMap;
void main (void)
{
vec3 color = diffuseThickness;
vec3 noise = texture2D ( noiseMap, gl_TexCoord [0].xy * vec2 ( 0.7, 2.0 ) ).xyz;
color = vec3 ( 1.0 ) - color * unlitThickness * noise.x;
gl_FragColor = vec4 ( color, 1.0 );
}
9.1 Листинг программы для моделирования эффекта акварели
#include
"libExt.h"
#include
#include
#include
<glut.h>
<stdio.h>
<stdlib.h>
#include
#include
#include
#include
#include
"libTexture.h"
"TypeDefs.h"
"Vector3D.h"
"Vector2D.h"
"GlslProgram.h"
Vector3D
Vector3D
unsigned
unsigned
unsigned
unsigned
unsigned
Vector3D
float
int
int
bool
eye
( -0.5, -0.5, 1.5 );
light ( 5, 0, 4 );
decalMap;
stoneMap;
teapotMap;
noiseMap;
paperMap;
rot ( 0, 0, 0 );
angle
= 0;
mouseOldX = 0;
mouseOldY = 0;
useFilter = true;
// camera position
// light position
// decal (diffuse) texture
GlslProgram program;
void
startOrtho
{
glMatrixMode
matrix
glPushMatrix
glLoadIdentity
()
// select the projection
();
();
// store the projection matrix
// reset the projection matrix
// set up an ortho screen
glOrtho
glMatrixMode
glPushMatrix
glLoadIdentity
( GL_PROJECTION );
( 0, 512, 0, 512, -1, 1 );
( GL_MODELVIEW );
();
();
// select the modelview matrix
// store the modelview matrix
// reset the modelview matrix
}
void
endOrtho
{
glMatrixMode
matrix
glPopMatrix
matrix
glMatrixMode
glPopMatrix
matrix
}
()
( GL_PROJECTION );
// select the projection
();
// restore the old projection
( GL_MODELVIEW );
();
// select the modelview matrix
// restore the old projection
void init ()
{
glClearColor ( 1.0, 1.0, 1.0, 1.0 );
glEnable
( GL_DEPTH_TEST );
glEnable
glDepthFunc
( GL_TEXTURE_2D );
( GL_LEQUAL
);
glHint ( GL_POLYGON_SMOOTH_HINT,
GL_NICEST );
glHint ( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
}
void display ()
{
glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
startOrtho ();
glActiveTextureARB ( GL_TEXTURE0_ARB );
glBindTexture
( GL_TEXTURE_2D, paperMap );
glEnable
( GL_TEXTURE_2D );
glActiveTextureARB ( GL_TEXTURE1_ARB );
glDisable
( GL_TEXTURE_2D );
glActiveTextureARB ( GL_TEXTURE2_ARB );
glDisable
( GL_TEXTURE_2D );
glDepthMask
glColor4f
( GL_FALSE );
( 1, 1, 1, 1 );
/*
glBegin ( GL_QUADS );
glTexCoord2f ( 0, 0 );
glVertex2f
( 0, 0 );
glTexCoord2f ( 1,
0 );
glVertex2f
( 511, 0 );
glTexCoord2f ( 1, 1 );
glVertex2f
( 511, 511 );
glTexCoord2f ( 0, 1 );
glVertex2f
( 0, 511 );
glEnd
();
*/
endOrtho ();
glDepthMask
( GL_TRUE );
glActiveTextureARB ( GL_TEXTURE0_ARB );
glBindTexture
( GL_TEXTURE_2D, teapotMap );
glActiveTextureARB ( GL_TEXTURE1_ARB );
glBindTexture
( GL_TEXTURE_2D, noiseMap );
glActiveTextureARB ( GL_TEXTURE2_ARB );
glBindTexture
( GL_TEXTURE_2D, paperMap );
glActiveTextureARB ( GL_TEXTURE0_ARB );
if ( useFilter )
program.bind ();
glMatrixMode ( GL_MODELVIEW );
glPushMatrix ();
glRotatef
glRotatef
glRotatef
( rot.x, 1, 0, 0 );
( rot.y, 0, 1, 0 );
( rot.z, 0, 0, 1 );
glutSolidTeapot ( 0.4 );
glPopMatrix ();
if ( useFilter )
program.unbind ();
glutSwapBuffers ();
}
void reshape ( int w, int h )
{
glViewport
( 0, 0, (GLsizei)w, (GLsizei)h );
glMatrixMode
( GL_PROJECTION );
glLoadIdentity ();
gluPerspective ( 60.0, (GLfloat)w/(GLfloat)h, 1.0, 60.0 );
glMatrixMode
( GL_MODELVIEW );
glLoadIdentity ();
gluLookAt
( eye.x, eye.y, eye.z,
// eye
0, 0, 0,
// center
0, 0, 1 );
// up
}
void key ( unsigned char key, int x, int y )
{
if ( key == 27 || key == 'q' || key == 'Q' )
exit ( 0 );
if ( key == 'f' || key == 'F' )
useFilter = !useFilter;
}
void
animate ()
{
angle = 0.001f * glutGet ( GLUT_ELAPSED_TIME );
light.x = 2*cos ( angle );
light.y = 2*sin ( angle );
light.z = 3 + 0.3 * sin ( angle / 3 );
program.bind ();
program.setUniformVector ( "eyePos",
eye
);
program.setUniformVector ( "lightPos", light );
program.setUniformFloat ( "time",
angle );
program.unbind ();
glutPostRedisplay ();
}
void motion ( int x, int y )
{
rot.y -= ((mouseOldY - y) * 180.0f) / 200.0f;
rot.z -= ((mouseOldX - x) * 180.0f) / 200.0f;
rot.x = 0;
if ( rot.z > 360 )
rot.z -= 360;
if ( rot.z < -360 )
rot.z += 360;
if ( rot.y > 360 )
rot.y -= 360;
if ( rot.y < -360 )
rot.y += 360;
mouseOldX = x;
mouseOldY = y;
glutPostRedisplay ();
}
void mouse ( int button, int state, int x, int y )
{
// quit requested
if ( state == GLUT_DOWN )
{
mouseOldX = x;
mouseOldY = y;
}
}
int main ( int argc, char * argv [] )
{
// initialize glut
glutInit
( &argc, argv );
glutInitDisplayMode ( GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH );
glutInitWindowSize ( 512, 512 );
// create window
glutCreateWindow ( "OpenGL watercolor rendering" );
glutDisplayFunc
glutReshapeFunc
glutKeyboardFunc
glutMouseFunc
glutMotionFunc
glutIdleFunc
(
(
(
(
(
(
display
reshape
key
mouse
motion
animate
// register handlers
);
);
);
);
);
);
init
();
initExtensions ();
if ( !GlslProgram :: isSupported () )
{
printf ( "GLSL not supported.\n" );
return 1;
}
if ( !program.loadShaders ( "watercolor.vsh", "watercolor.fsh" ) )
{
printf ( "Error loading shaders:\n%s\n", program.getLog ().c_str () );
return 3;
}
decalMap = createTexture2D ( true, "../../Textures/oak.bmp" );
stoneMap = createTexture2D ( true, "../../Textures/block.bmp" );
teapotMap = createTexture2D ( true, "../../Textures/Oxidated.jpg" );
noiseMap = createTexture2D ( true, "noise-2D.png" );
paperMap = createTexture2D ( true, "paper.dds" );
program.bind ();
program.setTexture ( "noiseMap", 1 );
program.setTexture ( "paperMap", 2 );
program.unbind ();
//
//
printf ( "Render scene in watercolor mode\n" );
printf ( "Press F key to turn watercolor mode on/off\n" );
glutMainLoop ();
return 0;
}
Related documents
Download