Главная Статьи Ссылки Скачать Скриншоты Юмор Почитать Tools Проекты Обо мне Гостевая |
Одной из хорошо распиаренных возможностей DX10 являются так называемые мягкие частицы (soft particles). Однако на самом деле для реализации этого эффекта вполне достаточно поддержки SM3 и и они великолепно делаются на OpenGL (не требуя при этом мелкомягкой и убогой ви$ты).
Рассмотрим что же именно подразумевается под "мягкими" частицами и в чем заключается их отличие от традиционных частиц.
Для представления обычных частиц используются так называемые billboard'ы - грани, всегда повернутые к камере. На billboard обычно натягивается текстура. OpenGL предоставляет хорошую поддержку таких частиц через расширения ARB_point_sprite и ARB_point_parameters.
Пока мы работаем с большим количеством маленьких частиц особых проблем не заметно. Но при использовании небольшого числа больших частиц становится заметно, что частицы представлены billboard'ами - при пересечении billboard и грани происходит ее частичное отсечение (см. рис 1).
Рис 1. Артефакты, возникающие при использовании традиционных частиц.
Еще хуже дело обстоит, когда частица вдруг пропадает, за счет того, что порождающая ее точка оказываются невидима (не проходит тест глубины) - в этом случае резкие скачки в изображении легко заметны (это обычно происходит при использовании расширения ARB_point_sprite, проверяющего глубину всего для одной точки billboard-а).
Это связано с тем, что частицы представлена одним полигоном, в то время как частицы обычно соответствуют не плоским фигурам, а объемными.
Мягкие частицы как раз и опираются на представление частиц как некоторых объемов с туманом - в этом случае учитывается какая именно часть этого объема видна и подобных резких скачков не происходит.
Рис 2. Сцена с рис. 1, но с использованием мягких частиц.
Рис 3. Отличие мягкой частицы от обычной.
Основная задача при рендеринге мягких частиц - это определение видимой части частицы, точнее луча, лежащего внутри объема частицы.
В идеальном случае при выводе такой частицы нам необходимо определить длину части луча (соответствующего текущему фрагменту), целиком лежащей внутри видимой части объема частицы.
При этом возможны два случая.
1. Луч "протыкает" объемную частицу насквозь и просто берем прозрачность из текстуры (рис 4.a).
2. Внутри шара находятся непрозрачные объекты, выведенные ранее. Тогда нам нужен отрезок от точки входа в частицу до ближайшего пересечения луча с объектами сцен, лежащими внутри объемной частицы. В этом случае отношение длины этого отрезка к заданной толщине частицы задает увеличение прозрачности частицы (рис 4.b).
Рис 4.
Таким образом, в тем местах, где billboad уходит за объекты сцены происходит плавное "пропадание" частицы (вместо резкого скачка).
Для того, чтобы можно было понять протыкает ли луч насквозь такую объемную частицу или нет, нам достаточно прочесть значение из z-буфера, соответствующее данному фрагменту, и сравнить его с текущей глубиной частицы с учетом заранее заданной толщины частицы.
Поскольку полупрозрачные объекты, в том числе и частицы, всегда выводят после вывода непрозрачных, то можно после вывода всех непрозрачных объектов скопировать буфер глубины в текстуру и при выводе мягких частиц читать из нее значения глубины.
Это удобно еще и потому, что во многих случаях для повышения быстродействия сначала осуществляется рендеринг всей сцены только в буфер глубины (подобный прием был использован Дж. Кармаком еще в Doom III).
Ниже приводится соответствующий фрагментный шейдер и скриншот, полученный при его использовании.
// // Soft particles fragment shader varying vec3 pos; uniform sampler2DRect depthMap; uniform sampler2D particleMap; void main (void) { const float tau = 0.7; const float n = 0.1; // zNear const float f = 100.0; // zFar float scale = 0.3; float d = distance ( pos, gl_TexCoord [1].xyz ); float r = gl_TexCoord [1].w; if ( d >= r ) discard; float w = r*r - d*d; float zs = texture2DRect ( depthMap, gl_FragCoord.xy ).r; float d1 = n*f/(f - zs*(f-n)); float d2 = n*f/(f - gl_FragCoord.z*(f-n)); float dz = min ( 0.5, scale * ( d1 - d2 ) / r ); vec4 clr = texture2D ( particleMap, gl_TexCoord [0].xy ); gl_FragColor = vec4 ( clr.rgb, dz*clr.r ); // use Red as Alpha for RGB greyscale textures }
Рис. 5. Скриншоты.
Ниже приводится полный исходный код на С++, использованный для рендеринга сцены с рис. 2 и 5.
// // Example of soft particles in OpenGL // // Author: Alex V. Boreskoff <steps3d@narod.ru> // #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 "GlslProgram.h" #include "utils.h" #include "Camera.h" Vector3D eye ( -1.5, -1.5, 1.5 ); // camera position unsigned decalMap; // decal (diffuse) texture unsigned stoneMap; unsigned teapotMap; unsigned depthMap; unsigned particleMap; float angle = 0; float yaw = 0; float pitch = 0; float roll = 0; Camera camera ( eye, 0, 0, 0 ); // camera to be used GlslProgram program; 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 ), Vector3D ( 1, 2, 1.5 ), decalMap, true ); drawBox ( Vector3D ( 1, 3, 0 ), Vector3D ( 1, 1, 1.5 ), decalMap, true ); drawBox ( Vector3D ( -2, 1, 0 ), Vector3D ( 1, 1, 1.5 ), decalMap, true ); glBindTexture ( GL_TEXTURE_2D, teapotMap ); glTranslatef ( 0.2, 1, 0.7 ); glRotatef ( angle * 45.3, 1, 0, 0 ); glRotatef ( angle * 57.2, 0, 1, 0 ); glutSolidTeapot ( 0.5 ); glPopMatrix (); } void displayParticle ( const Vector3D& pos, float r ) { glMultiTexCoord4f ( GL_TEXTURE1_ARB, pos.x, pos.y, pos.z, r ); // now setup particle glBegin ( GL_QUADS ); glMultiTexCoord2f ( GL_TEXTURE0_ARB, 0, 0 ); glVertex3fv ( pos ); glMultiTexCoord2f ( GL_TEXTURE0_ARB, 0, 1 ); glVertex3fv ( pos ); glMultiTexCoord2f ( GL_TEXTURE0_ARB, 1, 1 ); glVertex3fv ( pos ); glMultiTexCoord2f ( GL_TEXTURE0_ARB, 1, 0 ); glVertex3fv ( pos ); glEnd (); } void display () { glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); camera.apply (); displayBoxes (); glActiveTextureARB ( GL_TEXTURE1_ARB ); glBindTexture ( GL_TEXTURE_2D, particleMap ); glActiveTextureARB ( GL_TEXTURE0_ARB ); glBindTexture ( GL_TEXTURE_RECTANGLE_ARB, depthMap ); glCopyTexImage2D ( GL_TEXTURE_RECTANGLE_ARB, 0, GL_DEPTH_COMPONENT, 0, 0, 640, 480, 0 ); glDepthMask ( GL_FALSE ); glEnable ( GL_BLEND ); glBlendFunc ( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); program.bind (); displayParticle ( Vector3D ( 0, 0, 0.5 ), 1 ); displayParticle ( Vector3D ( 1, 1, 0.5 ), 1 ); displayParticle ( Vector3D ( 1, 2, 0.5 ), 1 ); program.unbind (); glDepthMask ( GL_TRUE ); glDisable ( GL_BLEND ); 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 ); 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 ( 640, 480 ); // create window glutCreateWindow ( "OpenGL Soft Particles example" ); // register handlers glutDisplayFunc ( display ); glutReshapeFunc ( reshape ); glutKeyboardFunc ( key ); glutSpecialFunc ( specialKey ); glutPassiveMotionFunc ( mouseFunc ); glutIdleFunc ( animate ); init (); initExtensions (); assertExtensionsSupported ( "EXT_framebuffer_object" ); decalMap = createTexture2D ( true, "../../Textures/oak.bmp" ); stoneMap = createTexture2D ( true, "../../Textures/block.bmp" ); teapotMap = createTexture2D ( true, "../../Textures/Oxidated.jpg" ); particleMap = createTexture2D ( true, "maskSmoke.bmp" ); if ( !program.loadShaders ( "sf.vsh", "sf.fsh" ) ) { printf ( "Error loading shaders:\n%s\n", program.getLog ().c_str () ); return 3; } // create depth texture glGenTextures ( 1, &depthMap ); glBindTexture ( GL_TEXTURE_RECTANGLE_ARB, depthMap ); glTexParameteri ( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); glTexParameteri ( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); glTexParameteri ( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); glTexParameteri ( GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); glTexImage2D ( GL_TEXTURE_RECTANGLE_ARB, 0, GL_DEPTH_COMPONENT, 640, 480, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL ); program.bind (); program.setTexture ( "depthMap", 0 ); program.setTexture ( "particleMap", 1 ); program.unbind (); camera.setRightHanded ( false ); glutMainLoop (); return 0; }
По этой ссылке можно скачать весь исходный код к этой статье. Также доступны для скачивания откомпилированные версии для M$ Windows и Linux.