10.Моделирование рисования древесным углем Одной из характерных особенностей для изображений в этой технике является заметно увеличенная контрастность изображения. Простейшим способом моделирования этого для изображений в оттенках серого цвета является преобразование интенсивности изображения по следующей формуле: В этой формуле в качестве степени p используется величина, большая единицы. Типичным значением степени является 3.5. Данное преобразование применяется к значению освещенности, полученной по формуле Фонга. В результате получается изображение с усиленным контрастом. Полученная таким образом освещенность используется для индексирования в специальную текстуру, называемую Contrast Enhancement Texture. На следующем рисунке приводится пример такой текстуры. Рис 3. Пример Contrast Enhancement Texture. Наложением этой текстуры удается не только еще больше усилить контрастность, но и придать получившемуся изображению зернистость, характерную для рисунков углем по бумаге. Однако, если t-координата для наложения СЕТ-текстуры задается явно как модифицированная освещенность, то какую величину следует взять в качестве первой текстурной координаты s ? Если в качестве s для обращения к СЕТ взять одну из стандартных текстурных координат, связанных с изображаемым объектом, то это может привести к появлению заметных артефактов. Наиболее удобным было бы взять в качестве данной текстурной координаты случайное значение. В качестве источника такого случайного значения удобно использовать дополнительную текстуру, значения цветовых компонент для которой были получены при помощи генератора псевдослучайных чисел. Тогда, использовав обычные текстурные координаты для обращения к такой текстуре, на выходе мы получим фактически случайное значение цвета, одну из компонент которого можно использовать в качестве текстурной координаты s для обращения к СЕТ. Еще одним характерным элементом рисунков углем является так называемое "размывание" (smuding), когда художник (обычно просто рукой) слегка размазывает уголь по бумаге для получения плавных полутонов. Достаточно простым способом моделирования размывания является просто смешение (blending) текстурированного (при помощи СЕТ) изображения с нетекстурированным изображением с усиленной контрастностью. Заключительным шагом является наложения рисунка на бумагу, которая задается при помощи отдельной текстуры, и также вносит свой вклад в получающееся изображения. Данный алгоритм довольно легко реализуется при помощи шейдеров. При этом вершинный шейдер отвечает за вычисления освещенности в вершинах и преобразование ее для увеличения контраста. Задачей фрагментного шейдера является наложение СЕТ, смешение текстурированной модели с нетекстурированной и наложения бумаги на получившееся значение. Ниже приводятся листинги вершинного и фрагментного шейдеров на GLSL. // // Charcoal vertex shader // uniform vec3 lightPos; uniform vec3 eyePos; varying vec3 color; void main(void) { const vec3 ambient = vec3 ( 0.0 ); const vec3 diffuse = vec3 ( 1.0, 1.0, 1.0 ); const vec3 luminance = vec3 ( 0.3, 0.59, 0.11 ); vec3 p = vec3 ( gl_ModelViewMatrix * gl_Vertex ); vec3 l = normalize ( lightPos - p ); vec3 n = normalize ( gl_NormalMatrix * gl_Normal ); // compute illumination color = ambient + diffuse * max ( dot ( n, l ), 0.0 ); // apply CEO color = vec3 ( pow ( clamp ( dot ( color, luminance ), 0.0, 1.0 ), 3.5 ) ); gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; gl_TexCoord [0] = gl_MultiTexCoord0; } // // Charcoal fragment shader // varying vec3 color; uniform sampler2D uniform sampler2D uniform sampler2D randomTex; cetTex; paperTex; void main (void) { vec4 random vec3 contrast vec3 smudge vec3 paper = = = = texture2D ( randomTex, gl_TexCoord [0].xy ); texture2D ( cetTex, vec2 ( random.x, 1.0 - color.x ) ).rgb; 0.5 * (contrast + color); texture2D ( paperTex, gl_FragCoord.xy / 512.0 ).rgb; gl_FragColor.rgb = contrast + vec3 ( 1.0 ) - paper; gl_FragColor.a = 1.0; } На следующем рисунке приводится изображение, построенное с использованием этих шейдеров. Рис 4. Изображение в технике рисования углем по бумаге. Для получения Contrast Enhancement Texture использовался простой скрипт на языке Python, приводимый на следующем листинге. # # Script to create contrast enhancement texture # import PIL, Image, random, math, ImageEnhance # basic parameters: bitmap size and contrast power size = 512 contrastExp = 15 noiseDensity = 3.0 im = Image.new ( "RGB", (size, size) ) # fill it with white for i in range (size): for j in range (size): im.putpixel ( (i,j), ( 255, 255, 255 ) ) numBlackPixels = int( noiseDensity * float(size) * float(size) ) for i in range ( numBlackPixels) : x = random.random () y = random.random () # apply contrast enhansment y = math.pow ( y, contrastExp ) px = int(x*(size-1)) py = size - 1 - int(y*(size-1)) if py < 0: py = 0 if py >= size: py = size - 1 im.putpixel ( (px, py), (0, 0, 0)) sh = ImageEnhance.Sharpness ( im ) im2 = sh.enhance ( 0.2 ) im2.show () im2.save ( "cet.bmp", "bmp" ) Следующий скрипт используется для построения текстуры, состоящей из случайных значений. # # Script to random texture # import PIL, Image, random # basic parameters: bitmap size size = 256 im = Image.new ( "RGB", (size, size) ) for i in range (size): for j in range (size): r = int ( 255.0 * random.random () ) g = int ( 255.0 * random.random () ) b = int ( 255.0 * random.random () ) im.putpixel ( (i,j), ( r, g, b ) ) im.show () im.save ( "random.bmp", "bmp" ) 10.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 unsigned Vector3D float int int bool eye ( -0.5, -0.5, 1.5 ); light ( 5, 0, 4 ); decalMap; stoneMap; teapotMap; randomMap; cetMap; 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 } void init () { glClearColor glEnable glEnable glDepthFunc () ( GL_PROJECTION ); // select the projection (); // restore the old projection ( GL_MODELVIEW ); (); // select the modelview matrix // restore the old projection ( ( ( ( 1.0, 1.0, 1.0, 1.0 ); GL_DEPTH_TEST ); 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_TEXTURE1_ARB ); glDisable ( GL_TEXTURE_2D ); glActiveTextureARB ( GL_TEXTURE2_ARB ); glDisable ( GL_TEXTURE_2D ); glActiveTextureARB ( GL_TEXTURE0_ARB ); glBindTexture ( GL_TEXTURE_2D, paperMap ); glEnable ( 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, randomMap ); glActiveTextureARB ( GL_TEXTURE2_ARB ); glBindTexture ( GL_TEXTURE_2D, cetMap ); glEnable ( GL_TEXTURE_2D ); glTexParameteri glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); glActiveTextureARB ( GL_TEXTURE3_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 ); // quit requested 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 ) { 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 charcoal effect" ); 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 ( "charcoal.vsh", "charcoal.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" ); randomMap = createTexture2D ( true, "random.bmp" ); cetMap = createTexture2D ( true, "cet.bmp" ); paperMap = createTexture2D ( true, "paper.dds" ); program.bind (); program.setTexture program.setTexture program.setTexture program.setTexture program.unbind (); ( ( ( ( "mainTex", "randomTex", "cetTex", "paperTex", 0 1 2 3 ); ); ); ); printf ( "Render scene with charcoal effect\n" ); printf ( "Press F key to turn charcoal mode on/off\n" ); glutMainLoop (); return 0; }