Vin*_*nce 43 c++ opengl normals computational-geometry
我画了一个有10000个顶点(100x100)的三角形网格,它将是一个草地.我使用了gldrawelements().我看了一整天,仍然无法理解如何计算这个法线.每个顶点是否有自己的法线或每个三角形都有自己的法线?有人能指出我正确的方向如何编辑我的代码以合并法线?
struct vertices {
GLfloat x;
GLfloat y;
GLfloat z;
}vertices[10000];
GLuint indices[60000];
/*
99..9999
98..9998
........
01..9901
00..9900
*/
void CreateEnvironment() {
int count=0;
for (float x=0;x<10.0;x+=.1) {
for (float z=0;z<10.0;z+=.1) {
vertices[count].x=x;
vertices[count].y=0;
vertices[count].z=z;
count++;
}
}
count=0;
for (GLuint a=0;a<99;a++){
for (GLuint b=0;b<99;b++){
GLuint v1=(a*100)+b;indices[count]=v1;count++;
GLuint v2=(a*100)+b+1;indices[count]=v2;count++;
GLuint v3=(a*100)+b+100;indices[count]=v3;count++;
}
}
count=30000;
for (GLuint a=0;a<99;a++){
for (GLuint b=0;b<99;b++){
indices[count]=(a*100)+b+100;count++;//9998
indices[count]=(a*100)+b+1;count++;//9899
indices[count]=(a*100)+b+101;count++;//9999
}
}
}
void ShowEnvironment(){
//ground
glPushMatrix();
GLfloat GroundAmbient[]={0.0,0.5,0.0,1.0};
glMaterialfv(GL_FRONT,GL_AMBIENT,GroundAmbient);
glEnableClientState(GL_VERTEX_ARRAY);
glIndexPointer( GL_UNSIGNED_BYTE, 0, indices );
glVertexPointer(3,GL_FLOAT,0,vertices);
glDrawElements(GL_TRIANGLES,60000,GL_UNSIGNED_INT,indices);
glDisableClientState(GL_VERTEX_ARRAY);
glPopMatrix();
}
Run Code Online (Sandbox Code Playgroud)
编辑1这是我写的代码.我只使用数组而不是向量,并将所有法线存储在名为normals的结构中.但它仍然不起作用.我在*indices处得到一个未处理的异常.
struct Normals {
GLfloat x;
GLfloat y;
GLfloat z;
}normals[20000];
Normals* normal = normals;
//***************************************ENVIRONMENT*************************************************************************
struct vertices {
GLfloat x;
GLfloat y;
GLfloat z;
}vertices[10000];
GLuint indices[59403];
/*
99..9999
98..9998
........
01..9901
00..9900
*/
void CreateEnvironment() {
int count=0;
for (float x=0;x<10.0;x+=.1) {
for (float z=0;z<10.0;z+=.1) {
vertices[count].x=x;
vertices[count].y=rand()%2-2;;
vertices[count].z=z;
count++;
}
}
//calculate normals
GLfloat vector1[3];//XYZ
GLfloat vector2[3];//XYZ
count=0;
for (int x=0;x<9900;x+=100){
for (int z=0;z<99;z++){
vector1[0]= vertices[x+z].x-vertices[x+z+1].x;//vector1x
vector1[1]= vertices[x+z].y-vertices[x+z+1].y;//vector1y
vector1[2]= vertices[x+z].z-vertices[x+z+1].z;//vector1z
vector2[0]= vertices[x+z+1].x-vertices[x+z+100].x;//vector2x
vector2[1]= vertices[x+z+1].y-vertices[x+z+100].y;//vector2y
vector2[2]= vertices[x+z+1].z-vertices[x+z+100].z;//vector2z
normals[count].x= vector1[1] * vector2[2]-vector1[2]*vector2[1];
normals[count].y= vector1[2] * vector2[0] - vector1[0] * vector2[2];
normals[count].z= vector1[0] * vector2[1] - vector1[1] * vector2[0];count++;
}
}
count=10000;
for (int x=100;x<10000;x+=100){
for (int z=0;z<99;z++){
vector1[0]= vertices[x+z].x-vertices[x+z+1].x;//vector1x -- JUST ARRAYS
vector1[1]= vertices[x+z].y-vertices[x+z+1].y;//vector1y
vector1[2]= vertices[x+z].z-vertices[x+z+1].z;//vector1z
vector2[0]= vertices[x+z+1].x-vertices[x+z-100].x;//vector2x
vector2[1]= vertices[x+z+1].y-vertices[x+z-100].y;//vector2y
vector2[2]= vertices[x+z+1].z-vertices[x+z-100].z;//vector2z
normals[count].x= vector1[1] * vector2[2]-vector1[2]*vector2[1];
normals[count].y= vector1[2] * vector2[0] - vector1[0] * vector2[2];
normals[count].z= vector1[0] * vector2[1] - vector1[1] * vector2[0];count++;
}
}
count=0;
for (GLuint a=0;a<99;a++){
for (GLuint b=0;b<99;b++){
GLuint v1=(a*100)+b;indices[count]=v1;count++;
GLuint v2=(a*100)+b+1;indices[count]=v2;count++;
GLuint v3=(a*100)+b+100;indices[count]=v3;count++;
}
}
count=30000;
for (GLuint a=0;a<99;a++){
for (GLuint b=0;b<99;b++){
indices[count]=(a*100)+b+100;count++;//9998
indices[count]=(a*100)+b+1;count++;//9899
indices[count]=(a*100)+b+101;count++;//9999
}
}
}
void ShowEnvironment(){
//ground
glPushMatrix();
GLfloat GroundAmbient[]={0.0,0.5,0.0,1.0};
GLfloat GroundDiffuse[]={1.0,0.0,0.0,1.0};
glMaterialfv(GL_FRONT,GL_AMBIENT,GroundAmbient);
glMaterialfv(GL_FRONT,GL_DIFFUSE,GroundDiffuse);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
glNormalPointer( GL_FLOAT, 0, normal);
glVertexPointer(3,GL_FLOAT,0,vertices);
glDrawElements(GL_TRIANGLES,60000,GL_UNSIGNED_INT,indices);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);
glPopMatrix();
}
//***************************************************************************************************************************
Run Code Online (Sandbox Code Playgroud)
dat*_*olf 113
每个顶点是否有自己的法线或每个三角形都有自己的法线?
像往常一样,答案是:"这取决于".由于法线被定义为垂直于给定平面内所有向量的向量(在N维中),因此需要一个平面来计算法线.顶点位置只是一个点,因此是单数,所以你实际上需要一个面来计算法线.因此,天真地,可以假设法线是每个面,因为正常计算中的第一步是通过评估面边缘的叉积来确定面法线.
假设你有一个带有点A,B,C的三角形,那么这些点有位置向量↑A,↑B,↑C和边有矢量↑B - ↑A和↑C - ↑A所以面法线向量是↑ N f =(↑B - ↑A)×(↑C - ↑A)
请注意,如上所述,↑N f的大小与面部区域成正比.
在平滑曲面中,顶点在面之间共享(或者您可以说这些面共享一个顶点).在这种情况下,顶点处的法线不是它所属面的面法线之一,而是它们的线性组合:
↑N v =Σp↑N f ; 其中p是每个面的权重.
可以假设参与面法线之间具有相等的权重.但是假设面部越大,它对正常的贡献越大就越有意义.
现在回想一下你用矢量↑v进行标准化,然后用它的接收长度缩放它:↑v i =↑v/|↑v | .但正如已经说过的,脸部法线的长度已经取决于脸部的面积.因此,上面给出的加权因子p已经包含在向量本身中:它的长度,即幅度.因此,我们可以通过简单地总结所有面法线来获得顶点法线向量.
在照明计算中,法向量必须是单位长度,即标准化为可用.总结之后,我们将新发现的顶点法线标准化并使用它.
细心的读者可能已经注意到我特意说光滑表面共享顶点.事实上,如果几何体中有一些折痕/硬边,则两侧的面不共享顶点.在OpenGL中,顶点是整个组合
你改变其中一个,你有一个完全不同的顶点.现在一些3D建模者只将顶点看作一个点的位置,并且每个面都存储其余的属性(Blender就是这样一个建模者).这节省了一些内存(或相当大的内存,具体取决于属性的数量).但OpenGL需要全部内容,因此如果使用这样的混合范例文件,您必须首先将其分解为OpenGL兼容数据.看看Blender的一个导出脚本,比如PLY导出器,看看它是如何完成的.
现在来介绍一些其他的事情.在你的代码中你有这个:
glIndexPointer( GL_UNSIGNED_BYTE, 0, indices );
Run Code Online (Sandbox Code Playgroud)
索引指针与顶点数组索引无关!这是一个anachronsim,从图形仍然使用调色板而不是真正的颜色.通过给出RGB值来设置像素颜色,但是通过偏移到有限的颜色调色板中的单个数字来设置像素颜色.调色板颜色仍然可以在几种图形文件格式中找到,但是没有像样的硬件使用它们了.
请从内存和代码中删除glIndexPointer(和glIndex),它们不会按照您的想法执行操作整个索引颜色模式使用起来很神秘,坦率地说我不知道1998年之后构建的任何硬件仍然支持它.
gen*_*ult 24
每个顶点.
使用交叉积来计算给定顶点周围三角形的面法线,将它们加在一起并进行标准化.
Kon*_*nis 23
竖起大拇指为datenwolf!我完全同意他的做法.为每个顶点添加相邻三角形的法线向量,然后进行标准化是要走的路.我只是想推的答案一点点,有一处的特殊但相当常见的情况细看矩形,光滑网具有恒定的x/y的一步.换句话说,矩形x/y网格在每个点具有可变高度.
这样的网格是通过在x和y上循环并为z设置值来创建的,并且可以表示像山坡的表面.因此,网格的每个点都由向量表示
P = (x, y, f(x,y))
Run Code Online (Sandbox Code Playgroud)
其中f(x,y)是给出网格上每个点的z的函数.
通常要绘制这样的网格,我们使用TriangleStrip或TriangleFan,但任何技术都应该为生成的三角形提供类似的地形.
|/ |/ |/ |/
...--+----U----UR---+--...
/| /| 2 /| /| Y
/ | / | / | / | ^
| / | / | / | / |
|/ 1 |/ 3 |/ |/ |
...--L----P----R----+--... +-----> X
/| 6 /| 4 /| /|
/ | / | / | / |
| /5 | / | / | /
|/ |/ |/ |/
...--DL---D----+----+--...
/| /| /| /|
Run Code Online (Sandbox Code Playgroud)
对于triangleStrip,每个顶点P =(x0,y0,z0)具有6个相邻的顶点
up = (x0 , y0 + ay, Zup)
upright = (x0 + ax, y0 + ay, Zupright)
right = (x0 + ax, y0 , Zright)
down = (x0 , y0 - ay, Zdown)
downleft = (x0 - ax, y0 - ay, Zdownleft)
left = (x0 - ax, y0 , Zleft)
Run Code Online (Sandbox Code Playgroud)
其中ax/ay分别是x/y轴上的恒定网格步长.在正方形网格上ax = ay.
ax = width / (nColumns - 1)
ay = height / (nRows - 1)
Run Code Online (Sandbox Code Playgroud)
因此,每个顶点具有6个相邻的三角形,每个三角形具有其自己的法向量(表示为N1至N6).这些可以使用定义三角形边的两个向量的叉积来计算,并且要小心我们对叉积的顺序.如果法线向量指向Z方向:
N1 = up x left =
= (Yup*Zleft - Yleft*Zup, Xleft*Zup - Xup*ZLeft, Xleft*Yup - Yleft*Xup)
=( (y0 + ay)*Zleft - y0*Zup,
(x0 - ax)*Zup - x0*Zleft,
x0*y0 - (y0 + ay)*(x0 - ax) )
N2 = upright x up
N3 = right x upright
N4 = down x right
N5 = downleft x down
N6 = left x downleft
Run Code Online (Sandbox Code Playgroud)
并且每个点P的结果法向量是N1到N6的和.我们在求和后归一化.创建循环,计算每个法向量的值,添加它们然后进行标准化非常容易.然而,正如Shickadance先生所指出的,这可能需要相当长的时间,特别是对于大型网格和/或嵌入式设备.
如果我们仔细观察并手动执行计算,我们会发现大多数术语相互抵消,为我们提供了一个非常优雅且易于计算的最终解决方案,用于得到的向量N.这里的重点是通过避免计算N1到N6的坐标来加速计算,为每个点做6个交叉乘积和6个加法.代数帮助我们直接跳到解决方案,使用更少的内存和更少的CPU时间.
我不会显示计算的细节,因为它很长但很直接,并且会跳转到网格上任何点的法线向量的最终表达式.为清楚起见,仅分解N1,其他矢量看起来相似.求和后,我们得到尚未归一化的N:
N = N1 + N2 + ... + N6
= .... (long but easy algebra) ...
= ( (2*(Zleft - Zright) - Zupright + Zdownleft + Zup - Zdown) / ax,
(2*(Zdown - Zup) + Zupright + Zdownleft - Zup - Zleft) / ay,
6 )
Run Code Online (Sandbox Code Playgroud)
你去!只需规范化此向量,您就可以获得网格上任意点的法线向量,前提是您知道其周围点的Z值以及网格的水平/垂直步长.
注意,这是周围三角形的法向量的加权平均值.权重是三角形的面积,并且已经包含在叉积中.
您甚至可以通过仅考虑四个周围点(上,下,左和右)的Z值来进一步简化它.在这种情况下,你得到:
| \|/ |
N = N1 + N2 + N3 + N4 ..--+----U----+--..
= ( (Zleft - Zright) / ax, | /|\ |
(Zdown - Zup ) / ay, | / | \ |
2 ) \ | / 1|2 \ | /
\|/ | \|/
..--L----P----R--...
/|\ | /|\
/ | \ 4|3 / | \
| \ | / |
| \|/ |
..--+----D----+--..
| /|\ |
Run Code Online (Sandbox Code Playgroud)
这更加优雅,甚至更快计算.
希望这会使一些网格更快.干杯
小智 6
对于像我这样遇到这个问题的人,你的答案可能是这样的:
// Compute Vertex Normals
std::vector<sf::Glsl::Vec3> verticesNormal;
verticesNormal.resize(verticesCount);
for (i = 0; i < indices.size(); i += 3)
{
// Get the face normal
auto vector1 = verticesPos[indices[(size_t)i + 1]] - verticesPos[indices[i]];
auto vector2 = verticesPos[indices[(size_t)i + 2]] - verticesPos[indices[i]];
auto faceNormal = sf::VectorCross(vector1, vector2);
sf::Normalize(faceNormal);
// Add the face normal to the 3 vertices normal touching this face
verticesNormal[indices[i]] += faceNormal;
verticesNormal[indices[(size_t)i + 1]] += faceNormal;
verticesNormal[indices[(size_t)i + 2]] += faceNormal;
}
// Normalize vertices normal
for (i = 0; i < verticesNormal.size(); i++)
sf::Normalize(verticesNormal[i]);
Run Code Online (Sandbox Code Playgroud)