OpenGL - Нормални вектори

Нормалите представляват вектори, които са перпендикулярни на лицето на даден полигон. Нормалните вектори са нужни при изчисленията на динамичната светлина. Ако не използвате готови обекти от помощните библиотеки ( които са с определени нормални вектори ), а такива създадени от вас, определянето на нормали за страните на обектите е задължително. За да изчислите нормалния вектор е нужно просто да вземете един от върховете на страната ( която може да бъде триъгълник, квадрат или друга ), да построите два вектора към съседните два върха и да ги умножите векторно. Ето как става това :

  1. // дефинираме променливите в които
  2. // ще съхраним x, y и z позициите на
  3. // три точки от фигурата
  4. float Ax, Ay, Az;
  5. float Bx, By, Bz;
  6. float Cx, Cy, Cz;
  7.  
  8. // дефинираме два масива в
  9. // които ще съхраним векторите
  10. // от А към B, от А към C и нормалния вектор
  11. float Vab[3];
  12. float Vac[3];
  13. float N[3]
  14.  
  15. // намираме първия вектор
  16. Vab = Bx - Ax;
  17. Vab = By - Ay;
  18. Vab = Bz - Az;
  19.  
  20. // намираме втория вектор
  21. Vac = Cx - Ax;
  22. Vac = Cy - Ay;
  23. Vac = Cz - Az;
  24.  
  25. // намираме нормалния вектор
  26. N[0] = Vab[1]*Vac[2] - Vab[2]*Vac[1];
  27. N[1] = Vab[2]*Vac[0] - Vab[0]*Vac[2];
  28. N[2] = Vab[0]*Vac[1] - Vab[1]*Vac[0];
  29.  
  30. // нормализираме нормалния вектор т.е.
  31. // свеждаме неговата дължина по x, y и z
  32. // в рамките на [0.0, 1.0]
  33. float lenght;
  34.  
  35. lenght = 1.0 / sqrt( N[0]*N[0] + N[1]*N[1] + N[2]*N[2] );
  36.  
  37. N[0] *= lenght; N[1] *= lenght; N[2] *= lenght;

Това всичко, което трябва да направите за да изчислите нормалния вектор на дадена повърхност. Ако не разбирате защо е така - не се притеснявайте и аз не разбирам защо е така :))

Както може би забелязвате, в по-горните редове изчисляваме единсвено нормалата за определена повърхност, но как OpenGL би получил резултата? За да укажете на OpenGL текущия нормален вектор, за повърхността която предстои да бъде изрисувана, трябва да използвате функцията :

void glNormal3f( GLfloat nx, GLfloat, ny, GLfloat nz ) - тя приема 3 аргумента и това са x, y и z координатите на нормалния вектор. Например :

  1. glBegin( GL_TRIANGLES );
  2.  
  3.   glNormal3f( N[0], N[1], N[2] );
  4.  
  5.   glVertex3f( ... );
  6.   glVertex3f( ... );
  7.   glVertex3f( ... );
  8.  
  9. glEnd( );


А сега искам да ви науча на още нещо. Представете си че имате някаква фигура, която има много страни ( триъгълници или нещо друго ) и вие определите нормалите на всяка страна, след това стоите гордо :) и пускате програмата за да видите как ще изглежда всичко, но сте леко разочаровани. Полигоните изграждащи фигурата си отразяват светлината, в зависимост от ъгъла под който тя пада върху тях, но всичко изглежда някак си нискодетайлно, изградено от отделни плочки, и това е така защото то наистина е изградено от отделни полигони и е ниско детайлно. Какво е решението? Да направите модел с изключително много полигони? Не.
Трябва да изчислите по една нормала за всеки връх от 3D обекта, вместо за всяка повърхност. Как става това? Ами изчислявате нормалните вектори за всички повърхности, които се намират до върха, събирате нормалните им вектори и ги делите на броя им ( на броя на повърхностите около върха ) и получавате нормала за дадения връх. Благодарение на тази "технология" дори нискодетайлните обекти изглеждат овални, все едно са изградени от хиляди полигони.
Представете си, че имате пирамида и искате да определите нормален вектор за един от четирите и върха. При пирамидата всеки връх е заобиколен от три страни и ако N1[3], N2[3], N3[3] са масивите в които са съхранени нормализираните нормални вектори на тези три страни, то нормалния вектор за върха между тях може да се изчисли по следния начин :

  1. float Vbuffer[3]; // дефинираме масива, в който ще съхраним сбора на векторите
  2. float Vertex_Normal[3]; // нормалата за върха
  3.  
  4. // събираме векторите
  5. Vbuffer[0] = N1[0]+ N2[0] + N3[0];
  6. Vbuffer[1] = N1[1]+ N2[1] + N3[1];
  7. Vbuffer[2] = N1[2]+ N2[2] + N3[2];
  8.  
  9. // изчисляваме нормалата за върха
  10. Vertex_Normal[0] = Vbuffer[0] / 3;
  11. Vertex_Normal[1] = Vbuffer[1] / 3;
  12. Vertex_Normal[2] = Vbuffer[2] / 3;

Важно е да се отбележи, че при изрисуването на обекта, за да посочите правилно вертексните нормали на OpenGL трябва да извиквате функцията glNormal*( ) всеки път, когато определяте връх с функцията glVertex*( ). Представете си че имате някаква фигура съставена от много триъгълници и е нужно с помоща на цикъл да изрисувате всеки от тях. Ето така трябва да изглежда всичко, ако използвате вертексни нормали :

  1. glBegin( GL_TRIANGLES );
  2.  
  3.   for( ... )
  4.   {
  5.  
  6.       glNormal3f( ... ); glVertex3f( ... );
  7.       glNormal3f( ... ); glVertex3f( ... );
  8.       glNormal3f( ... ); glVertex3f( ... );
  9.  
  10.   }
  11.  
  12. glEnd( );


Надявам се че сте разбрали какво представляват вертексните нормали, защото ефекта за детайлност, който създават, е наистина добър. И за малко да забравя - след като сте определили нормалите на даден обект, в никакъв случай не променяйте размера на обекта с функцията glScale*( ). Eто каква е разликата между нормалите за лице и вертексните нормали :

 

Както знаете готовите обекти от GLAUX и GLUT имат предварително определени нормали за всеки връх. Ако искате да направите така, че да се използват нормални вектори за всяко лице, а не за всеки връх, просто трябва да извикате вече познатата ви функция glShadeModel( GLenum mode ) с един от аргументите :

GL_FLAT - за едноцветни полигони ( понеже всеки полигон може да има само един цвят, ще получите "плочковидния" ефект на нормалите за лице )
GL_SMOOTH - за преливащи цветове ( ефекта на вертексните нормали ще се запази )

И за малко да забравя - след като сте определили нормалите на даден обект, в никакъв случай не променяйте размера на обекта с функцията glScale*( ).
Преди време писах един код, който зарежда обекти от 3D Studio Max от файл *.ase, изчислявайки и вертексните нормали на обектите. Една от следващите ми задачи ще е да го преведа в добър ред и да добавя някои обяснения, преди да го сложа в сайта. Сигурен съм, че ще ви послужи за много неща :))

Автор: Иван Георгиев Иванов