Главная Статьи Ссылки Скачать Скриншоты Юмор Почитать Tools Обо мне Гостевая Форум |
Одним из простых и достаточно красивых спецэффектов, которые можно легко добавить к своей программе является так называемая глубина резкозти (depth-of-field).
Это понятие связано с тем, что любое реальное оптическое устройство (т.е. устройство, где диаметр объектива больше нуля) не может одновременно удерживать все объекты в фокусе.
Всегда какие-то объекты (находящиеся на заданном - фокусном - расстоянии) будут в фокусе, т.е. будут отображены четко, а объекты, находящиеся заметно ближе или дальше этого расстояния окажутся не в фокусе, т.е. будут размытыми. При этом величина этого размытия зависит от того насколько сильно расстояние до эотого объекта отличается от фокусного расстояния.
По законам оптики существует соотношение между расстоянием от линзы до "премника" и расстоянием от линзы до объекта, при котром объект будет в фокусе.
Рис 1. Работа простейшего объектива.
Это отношение задается следущей формулой, здесь через u и v обозначены расстояния от линзы до "приемника" и объекта, а через f - т.н. фокусноео расстояние объектива.
Если мы расположим поверхность на расстоянии d от объектива, то точек на "приемнике" будет соответствовать круг на этой плоскости - так нызваемый Circle Of Confusion (COC).
Существует формула для получения диаметра COC по параметрам объектива f, a и фокусному расстоянию dfocus:
Однако для применения на практике эта формула довольно сложна и мы длаее будем опираться на гораздо более простой подход, рассмотренный в книгах ShaderX2 и ShaderX3.
Свойства объектива мы будем характеризовать всего двумя величинами - фокусным расстоянием (focalDistance) и тем, как зависит степень размычтости объекта от отклонения расстояния до фокусного (focalRange).
Это позволяет прямо во фрагментном шейдере по z-координате (в системе координат наблюдателя) вычислить степень размытости для данного фрагмента (blur) и записать ее в альфа-канал получающегося изображения.
Ниже приводятся соответствующие вершинный и фрагментный шейдеры.
// // Vertex shader of 1st pass of depth-of-field rendering // varying float z; void main(void) { vec4 pos = gl_ModelViewMatrix * gl_Vertex; z = pos.z; gl_Position = ftransform(); gl_TexCoord [0] = gl_MultiTexCoord0; }
// // Fragment shader of 1st pass of depth-of-field rendering // varying float z; uniform sampler2D tex; uniform float focalDistance, focalRange; void main (void) { float blur = clamp ( abs ( focalDistance + z ) / focalRange, 0.0, 1.0 ); gl_FragData [0] = vec4 ( texture2D ( tex, gl_TexCoord [0].st).rgb, blur ); }
Следующим шагом будет построение уменьшенного в 4х4 раза изобаржения (для этого можно использую билинейную фильтрацию обойтись всего четырьмя выборками из текстуры) и ее размытие.
Рис 2. Использование билинейной интерполяции.
// // Fragment shader of 2nd pass of depth-of-field rendering // uniform sampler2D tex; void main (void) { const vec2 d1 = vec2 ( 1.0/512.0, 1.0/512.0 ); const vec2 d2 = vec2 ( 1.0/512.0, -1.0/512.0 ); const vec2 d3 = vec2 ( -1.0/512.0, 1.0/512.0 ); const vec2 d4 = vec2 ( -1.0/512.0, -1.0/512.0 ); vec2 p = gl_TexCoord [0].st; gl_FragData [0] = (texture2D ( tex, vec2 ( p + d1 ) ) + texture2D ( tex, vec2 ( p + d2 ) ) + texture2D ( tex, vec2 ( p + d3 ) ) + texture2D ( tex, vec2 ( p + d4 ) ) ) * 0.25; }
Следующим шагом является избирательное размытие исходного изображения с использованием в качестве набора точек, имеющих распределение, назыаемое диск Пуассона.
Простейший вариант заключается в степени размытия (blur) из альфа-канала исходного изображения для управления размером адра фильтра - радиус фильтра задается как blur*radiusScale:
Для повышения качества можно брать значения сразу из двух текстур - исходной и уменьшенной. При этом вес каждой из текстур будет определяться степенью размытости исходной точки.
Заключительным шагом является борьба с так называемыми leaking artifacts - когда яркий объект попдает в область фильтра, соответствующего другой точке и дает светлый ореол, хотя эти точки могут иметь совершенно различную глубину (и степень размытости).
Простейшим способом борьбы с этим является просто уменьшение веса прочитанных значений, для которых степень размытия меньше степени размытости исходного пиксела.
Ниже приводится соответствующая прогрммная реализация.
// // Fragment shader of 3rd pass of depth-of-field rendering // uniform sampler2D tex; uniform sampler2D texLow; uniform float radiusScale; void main (void) { const vec2 poisson1 = vec2 ( 0.0, 0.0 ); const vec2 poisson2 = vec2 ( 0.527837, -0.85868 ); const vec2 poisson3 = vec2 ( -0.040088, 0.536087 ); const vec2 poisson4 = vec2 ( -0.670445, -0.179949 ); const vec2 poisson5 = vec2 ( -0.419418, -0.616039 ); const vec2 poisson6 = vec2 ( 0.440453, -0.639399 ); const vec2 poisson7 = vec2 ( -0.757088, 0.349334 ); const vec2 poisson8 = vec2 ( 0.574619, 0.685879 ); vec2 p = gl_TexCoord [0].st; vec4 c = texture2D ( tex, p ); float cd = c.a; float discRadius = cd * 10.0 / 512.0; float discRadiusLow = discRadius * 0.4 * 4.0; vec4 tapLow, tapHigh, tap; float blur; c = vec4 ( 0.0 ); // sample 1 tapLow = texture2D ( texLow, p + poisson1 * discRadiusLow ); tapHigh = texture2D ( tex, p + poisson1 * discRadiusLow ); blur = tapHigh.a; tap = mix ( tapHigh, tapLow, blur ); // apply leaking reduction tap.a = ( tap.a >= cd ? 1.0 : tap.a ); c.rgb += tap.rgb * tap.a; c.a += tap.a; // sample 2 tapLow = texture2D ( texLow, p + poisson2 * discRadiusLow ); tapHigh = texture2D ( tex, p + poisson2 * discRadiusLow ); blur = tapHigh.a; tap = mix ( tapHigh, tapLow, blur ); // apply leaking reduction tap.a = ( tap.a >= cd ? 1.0 : tap.a ); c.rgb += tap.rgb * tap.a; c.a += tap.a; // sample 3 tapLow = texture2D ( texLow, p + poisson3 * discRadiusLow ); tapHigh = texture2D ( tex, p + poisson3 * discRadiusLow ); blur = tapHigh.a; tap = mix ( tapHigh, tapLow, blur ); // apply leaking reduction tap.a = ( tap.a >= cd ? 1.0 : tap.a ); c.rgb += tap.rgb * tap.a; c.a += tap.a; // sample 4 tapLow = texture2D ( texLow, p + poisson4 * discRadiusLow ); tapHigh = texture2D ( tex, p + poisson4 * discRadiusLow ); blur = tapHigh.a; tap = mix ( tapHigh, tapLow, blur ); // apply leaking reduction tap.a = ( tap.a >= cd ? 1.0 : tap.a ); c.rgb += tap.rgb * tap.a; c.a += tap.a; // sample 5 tapLow = texture2D ( texLow, p + poisson5 * discRadiusLow ); tapHigh = texture2D ( tex, p + poisson5 * discRadiusLow ); blur = tapHigh.a; tap = mix ( tapHigh, tapLow, blur ); // apply leaking reduction tap.a = ( tap.a >= cd ? 1.0 : tap.a ); c.rgb += tap.rgb * tap.a; c.a += tap.a; // sample 6 tapLow = texture2D ( texLow, p + poisson6 * discRadiusLow ); tapHigh = texture2D ( tex, p + poisson6 * discRadiusLow ); blur = tapHigh.a; tap = mix ( tapHigh, tapLow, blur ); // apply leaking reduction tap.a = ( tap.a >= cd ? 1.0 : tap.a ); c.rgb += tap.rgb * tap.a; c.a += tap.a; // sample 7 tapLow = texture2D ( texLow, p + poisson7 * discRadiusLow ); tapHigh = texture2D ( tex, p + poisson7 * discRadiusLow ); blur = tapHigh.a; tap = mix ( tapHigh, tapLow, blur ); // apply leaking reduction tap.a = ( tap.a >= cd ? 1.0 : tap.a ); c.rgb += tap.rgb * tap.a; c.a += tap.a; // sample 8 tapLow = texture2D ( texLow, p + poisson8 * discRadiusLow ); tapHigh = texture2D ( tex, p + poisson8 * discRadiusLow ); blur = tapHigh.a; tap = mix ( tapHigh, tapLow, blur ); // apply leaking reduction tap.a = ( tap.a >= cd ? 1.0 : tap.a ); c.rgb += tap.rgb * tap.a; c.a += tap.a; gl_FragData [0] = c / c.a; }
Ниже приводится полный исходный код на С++ для примера, демонстрирующего использование данного эффекта.
// // Example of depth-of-field // // Author: Alex V. Boreskoff <steps3d@narod.ru>,<steps3d@gmail.com> // #include "libExt.h" #ifdef MACOSX #include <GLUT/glut.h> #else #include <glut.h> #endif #include <stdio.h> #include <stdlib.h> #include "libTexture.h" #include "TypeDefs.h" #include "Vector3D.h" #include "Vector2D.h" #include "boxes.h" #include "FrameBuffer.h" #include "GlslProgram.h" #include "utils.h" #include "Camera.h" Vector3D eye ( -0.5, -0.5, 1.5 ); // camera position unsigned decalMap; // decal (diffuse) texture unsigned stoneMap; unsigned teapotMap; unsigned blockMap; unsigned depthMap; float angle = 0; float yaw = 0; float pitch = 0; float roll = 0; float focalDistance = 4.5; float focalRange = 20; float radiusScale = 3.0 / 512.0; Camera camera ( eye, 0, 0, 0 ); // camera to be used FrameBuffer buffer ( 512, 512, FrameBuffer :: depth32 ); FrameBuffer buffer2 ( 512/4, 512/4 ); FrameBuffer buffer3 ( 512/4, 512/4 ); GlslProgram program1; // build G-buffer GlslProgram program2; // test G-buffer GlslProgram program3; // test G-buffer GlslProgram blur; void displayBoxes (); void reshape ( int w, int h ); void displayBoxes () { glMatrixMode ( GL_MODELVIEW ); glPushMatrix (); drawBox ( Vector3D ( -5, -5, 0 ), Vector3D ( 10, 10, 3 ), stoneMap, false ); drawBox ( Vector3D ( 3, 2, 0.5 ), Vector3D ( 1, 2, 2 ), decalMap, true ); drawBox ( Vector3D ( -3, -2, 0.5 ), Vector3D ( 1, 2, 2 ), teapotMap, true ); drawBox ( Vector3D ( 1, -1, 0.25 ), Vector3D ( 1, 2, 1.5 ), blockMap, true ); drawBox ( Vector3D ( -3, 3, 0.7 ), Vector3D ( 1.5, 2, 1 ), decalMap, true ); drawBox ( Vector3D ( 3, -3, 0.25 ), Vector3D ( 1, 1, 1.5 ), blockMap, true ); glBindTexture ( GL_TEXTURE_2D, teapotMap ); glTranslatef ( 0.2, 1, 1.5 ); glRotatef ( angle * 45.3, 1, 0, 0 ); glRotatef ( angle * 57.2, 0, 1, 0 ); glutSolidTeapot ( 0.3 ); glPopMatrix (); } void display () { buffer.bind (); program1.bind (); glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); program1.bind (); program1.setUniformFloat ( "focalDistance", focalDistance ); program1.setUniformFloat ( "focalRange", focalRange ); camera.apply (); displayBoxes (); program1.unbind (); buffer.unbind ( true ); glBindTexture ( GL_TEXTURE_2D, buffer.getColorBuffer () ); program2.bind (); buffer2.bind (); startOrtho ( 512/4, 512/4 ); drawQuad ( 512/4, 512/4 ); endOrtho (); buffer2.unbind ( true ); program2.unbind (); glBindTexture ( GL_TEXTURE_2D, buffer2.getColorBuffer () ); blur.bind (); buffer3.bind (); startOrtho ( 512/4, 512/4 ); drawQuad ( 512/4, 512/4 ); endOrtho (); buffer3.unbind ( true ); blur.unbind (); glActiveTextureARB ( GL_TEXTURE1_ARB ); glBindTexture ( GL_TEXTURE_2D, buffer2.getColorBuffer () ); glActiveTextureARB ( GL_TEXTURE0_ARB ); glBindTexture ( GL_TEXTURE_2D, buffer.getColorBuffer () ); program3.bind (); program3.setUniformFloat ( "radiusScale", radiusScale ); startOrtho ( 512, 512 ); drawQuad ( 512, 512 ); endOrtho (); program3.unbind (); glutSwapBuffers (); } void reshape ( int w, int h ) { camera.setViewSize ( w, h, 60 ); camera.apply (); } void key ( unsigned char key, int x, int y ) { if ( key == 27 || key == 'q' || key == 'Q' ) // quit requested exit ( 0 ); else if ( key == 'w' || key == 'W' ) camera.moveBy ( camera.getViewDir () * 0.2 ); else if ( key == 'x' || key == 'X' ) camera.moveBy ( -camera.getViewDir () * 0.2 ); else if ( key == 'a' || key == 'A' ) camera.moveBy ( -camera.getSideDir () * 0.2 ); else if ( key == 'd' || key == 'D' ) camera.moveBy ( camera.getSideDir () * 0.2 ); else if ( key == '+' ) focalDistance += 0.1; else if ( key == '-' ) focalDistance -= 0.1; else if ( key == '*' ) focalRange += 0.3; else if ( key == '/' ) focalRange -= 0.3; glutPostRedisplay (); } void specialKey ( int key, int x, int y ) { if ( key == GLUT_KEY_UP ) yaw += M_PI / 90; else if ( key == GLUT_KEY_DOWN ) yaw -= M_PI / 90; else if ( key == GLUT_KEY_RIGHT ) roll += M_PI / 90; else if ( key == GLUT_KEY_LEFT ) roll -= M_PI / 90; camera.setEulerAngles ( yaw, pitch, roll ); glutPostRedisplay (); } void mouseFunc ( int x, int y ) { static int lastX = -1; static int lastY = -1; if ( lastX == -1 ) // not initialized { lastX = x; lastY = y; } yaw -= (y - lastY) * 0.02; roll += (x - lastX) * 0.02; lastX = x; lastY = y; camera.setEulerAngles ( yaw, pitch, roll ); glutPostRedisplay (); } void animate () { static float lastTime = 0.0; float time = 0.001f * glutGet ( GLUT_ELAPSED_TIME ); angle += 2 * (time - lastTime); lastTime = time; glutPostRedisplay (); } int main ( int argc, char * argv [] ) { // initialize glut glutInit ( &argc, argv ); glutInitDisplayMode ( GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH ); glutInitWindowSize ( buffer.getWidth (), buffer.getHeight () ); // create window glutCreateWindow ( "OpenGL Depth Of Field Demo" ); // register handlers glutDisplayFunc ( display ); glutReshapeFunc ( reshape ); glutKeyboardFunc ( key ); glutSpecialFunc ( specialKey ); glutPassiveMotionFunc ( mouseFunc ); glutIdleFunc ( animate ); init (); initExtensions (); assertExtensionsSupported ( "EXT_framebuffer_object" ); decalMap = createTexture2D ( true, "wood.png" ); stoneMap = createTexture2D ( true, "brick.tga" ); teapotMap = createTexture2D ( true, "../../Textures/water.bmp" ); blockMap = createTexture2D ( true, "../../Textures/block.bmp" ); buffer.create (); buffer.bind (); if ( !buffer.attachColorTexture ( GL_TEXTURE_2D, buffer.createColorTexture ( GL_RGBA, GL_RGBA8 ), 0 ) ) printf ( "buffer error with color attachment\n"); if ( !buffer.isOk () ) printf ( "Error with framebuffer\n" ); buffer.unbind (); buffer2.create (); buffer2.bind (); if ( !buffer2.attachColorTexture ( GL_TEXTURE_2D, buffer2.createColorTexture ( GL_RGBA, GL_RGBA8 ), 0 ) ) printf ( "buffer2 error with color attachment\n"); if ( !buffer2.isOk () ) printf ( "Error with framebuffer2\n" ); buffer2.unbind (); buffer3.create (); buffer3.bind (); if ( !buffer3.attachColorTexture ( GL_TEXTURE_RECTANGLE_ARB, buffer3.createColorRectTexture ( GL_RGBA, GL_RGBA8 ), 0 ) ) printf ( "buffer3 error with color attachment\n"); if ( !buffer3.isOk () ) printf ( "Error with framebuffer3\n" ); buffer3.unbind (); if ( !program1.loadShaders ( "dof1-p1.vsh", "dof1-p1.fsh" ) ) { printf ( "Error loading shaders:\n%s\n", program1.getLog ().c_str () ); return 3; } if ( !program2.loadShaders ( "dof1-p2.vsh", "dof1-p2.fsh" ) ) { printf ( "Error loading shaders2:\n%s\n", program2.getLog ().c_str () ); return 3; } if ( !program3.loadShaders ( "dof1-p3.vsh", "dof1-p3.fsh" ) ) { printf ( "Error loading shaders3:\n%s\n", program3.getLog ().c_str () ); return 3; } if ( !blur.loadShaders ( "blur.vsh", "blur.fsh" ) ) { printf ( "Error loading shaders3:\n%s\n", program3.getLog ().c_str () ); return 3; } program1.bind (); program1.setTexture ( "tex", 0 ); program1.unbind (); program2.bind (); program2.setTexture ( "tex", 0 ); program2.unbind (); program3.bind (); program3.setTexture ( "tex", 0 ); program3.setTexture ( "texLow", 1 ); program3.unbind (); blur.bind (); blur.setTexture ( "tex", 0 ); blur.unbind (); camera.setRightHanded ( false ); printf ( "Depth of Field demo.\n\tUse + and - to change focal distance.\n\tUse * and / to change focal range.\n\tUse mouse and wsad to control camera.\n" ); glutMainLoop (); return 0; }
Рис 3. Скриншоты программы.
По этой ссылке можно скачать весь исходный код к этой статье. Также доступны для скачивания откомпилированные версии для M$ Windows, Linux и Mac OS X