如何在包含高多边形网格的模型中实现3D场景中的3D光线绘制?
迭代所有三角形以执行三角形-直线相交测试需要花费太多时间。我知道存在像八叉树之类的方法,应该可以将这些方法用于场景中的模型,但我不知道应该如何在网格级别使用这些概念。但是,如果您在网格级别使用八叉树,应该如何掩盖超过八叉树体积边界的多边形问题?
对于实时OpenGL应用程序中具有高多边形模型的3D光线相交,您有什么建议或建议使用哪种方法吗?
i7uaboj41#
对于光线拾取渲染对象(如通过鼠标),最佳选择是使用已渲染的缓冲区,因为与复杂场景上的光线相交测试相比,读取缓冲区的成本非常低。其思想是将每个可拾取的渲染对象渲染到每个您需要的有关它们的信息的单独缓冲区中,例如:
1.深度缓冲
这将为您提供光线与对象相交的3D位置。1.模具缓冲区
如果每个对象使用其ID(或其在对象列表中的索引)渲染到模具,则可以直接获得拾取的对象。1.任何其他
也有二次颜色附件和FBO的存在。所以你可以添加任何其他东西,比如法线向量,或者你需要的任何东西。
如果编码正确,所有这些都只会略微降低性能(甚至根本不会),因为您不需要计算任何东西,只需要对每个缓冲区的每个片段进行一次写入。
拾取本身很容易,您只需从所有需要的缓冲区中读取相应的像素,并将其转换为所需的格式。
以下是使用固定管道(无着色器)的简单C++/VCL示例...
//--------------------------------------------------------------------------- # include <vcl.h> # include <math.h> # pragma hdrstop # include "Unit1.h" # include "gl_simple.h" //--------------------------------------------------------------------------- # pragma package(smart_init) # pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- void matrix_mul_vector(double *c,double *a,double *b,double w=1.0) { double q[3]; q[0]=(a[ 0]*b[0])+(a[ 4]*b[1])+(a[ 8]*b[2])+(a[12]*w); q[1]=(a[ 1]*b[0])+(a[ 5]*b[1])+(a[ 9]*b[2])+(a[13]*w); q[2]=(a[ 2]*b[0])+(a[ 6]*b[1])+(a[10]*b[2])+(a[14]*w); for(int i=0;i<3;i++) c[i]=q[i]; } //--------------------------------------------------------------------------- class glMouse { public: int sx,sy; // framebuffer position [pixels] double pos[3]; // [GCS] ray end coordinate (or z_far) double beg[3]; // [GCS] ray start (z_near) double dir[3]; // [GCS] ray direction double depth; // [GCS] perpendicular distance to camera WORD id; // selected object id double x0,y0,xs,ys,zFar,zNear; // viewport and projection double *eye; // camera direct matrix pointer double fx,fy; // perspective scales glMouse(){ eye=NULL; for (int i=0;i<3;i++) { pos[i]=0.0; beg[i]=0.0; dir[i]=0.0; } id=0; x0=0.0; y0=0.0; xs=0.0; ys=0.0; fx=0.0; fy=0.0; depth=0.0; } glMouse(glMouse& a){ *this=a; }; ~glMouse(){}; glMouse* operator = (const glMouse *a) { *this=*a; return this; }; // glMouse* operator = (const glMouse &a) { ...copy... return this; }; void resize(double _x0,double _y0,double _xs,double _ys,double *_eye) { double per[16]; x0=_x0; y0=_y0; xs=_xs; ys=_ys; eye=_eye; glGetDoublev(GL_PROJECTION_MATRIX,per); zFar =0.5*per[14]*(1.0-((per[10]-1.0)/(per[10]+1.0))); zNear=zFar*(per[10]+1.0)/(per[10]-1.0); fx=per[0]; fy=per[5]; } void pick(double x,double y) // test screen x,y [pixels] position { int i; double l; GLfloat _z; GLint _id; sx=x; sy=ys-1.0-y; // read depth z and linearize glReadPixels(sx,sy,1,1,GL_DEPTH_COMPONENT,GL_FLOAT,&_z);// read depth value depth=_z; // logarithmic depth=(2.0*depth)-1.0; // logarithmic NDC z=(2.0*zNear*zFar)/(zFar+zNear-(z*(zFar-zNear))); // linear <zNear,zFar> // read object ID glReadPixels(sx,sy,1,1,GL_STENCIL_INDEX,GL_INT,&_id); // read stencil value id=_id; // win [pixel] -> GL NDC <-1,+1> x= (2.0*(x-x0)/xs)-1.0; y=1.0-(2.0*(y-y0)/ys); // ray start GL camera [LCS] beg[2]=-zNear; beg[1]=(-beg[2]/fy)*y; beg[0]=(-beg[2]/fx)*x; // ray direction GL camera [LCS] for (l=0.0,i=0;i<3;i++) l+=beg[i]*beg[i]; l=1.0/sqrt(l); for (i=0;i<3;i++) dir[0]=beg[0]*l; // ray end GL camera [LCS] pos[2]=-depth; pos[1]=(-pos[2]/fy)*y; pos[0]=(-pos[2]/fx)*x; // convert to [GCS] matrix_mul_vector(beg,eye,beg); matrix_mul_vector(pos,eye,pos); matrix_mul_vector(dir,eye,dir,0.0); } }; //--------------------------------------------------------------------------- // camera & mouse double eye[16],ieye[16]; // direct view,inverse view and perspective matrices glMouse mouse; // objects struct object { WORD id; // unique non zero ID double m[16]; // direct model matrix object(){}; object(object& a){ *this=a; }; ~object(){}; object* operator = (const object *a) { *this=*a; return this; }; /*object* operator = (const object &a) { ...copy... return this; };*/ }; const int objs=7; object obj[objs]; // textures GLuint txr=-1; //--------------------------------------------------------------------------- void matrix_inv(double *a,double *b) // a[16] = Inverse(b[16]) { double x,y,z; // transpose of rotation matrix a[ 0]=b[ 0]; a[ 5]=b[ 5]; a[10]=b[10]; x=b[1]; a[1]=b[4]; a[4]=x; x=b[2]; a[2]=b[8]; a[8]=x; x=b[6]; a[6]=b[9]; a[9]=x; // copy projection part a[ 3]=b[ 3]; a[ 7]=b[ 7]; a[11]=b[11]; a[15]=b[15]; // convert origin: new_pos = - new_rotation_matrix * old_pos x=(a[ 0]*b[12])+(a[ 4]*b[13])+(a[ 8]*b[14]); y=(a[ 1]*b[12])+(a[ 5]*b[13])+(a[ 9]*b[14]); z=(a[ 2]*b[12])+(a[ 6]*b[13])+(a[10]*b[14]); a[12]=-x; a[13]=-y; a[14]=-z; } //--------------------------------------------------------------------------- void gl_draw() { int i; object *o; double a; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT ); glEnable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); glEnable(GL_STENCIL_TEST); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); glStencilMask(0xFFFF); // Write to stencil buffer glStencilFunc(GL_ALWAYS,0,0xFFFF); // Set any stencil to 0 for (o=obj,i=0;i<objs;i++,o++) { glMatrixMode(GL_MODELVIEW); glLoadMatrixd(ieye); glMultMatrixd(o->m); glStencilFunc(GL_ALWAYS,o->id,0xFFFF); // Set any stencil to object ID vao_draw(); } glStencilFunc(GL_ALWAYS,0,0xFFFF); // Set any stencil to 0 glDisable(GL_STENCIL_TEST); // no need fot testing // render mouse glMatrixMode(GL_MODELVIEW); glLoadMatrixd(ieye); a=0.1*mouse.depth; glColor3f(0.0,1.0,0.0); glBegin(GL_LINES); glVertex3d(mouse.pos[0]+a,mouse.pos[1],mouse.pos[2]); glVertex3d(mouse.pos[0]-a,mouse.pos[1],mouse.pos[2]); glVertex3d(mouse.pos[0],mouse.pos[1]+a,mouse.pos[2]); glVertex3d(mouse.pos[0],mouse.pos[1]-a,mouse.pos[2]); glVertex3d(mouse.pos[0],mouse.pos[1],mouse.pos[2]+a); glVertex3d(mouse.pos[0],mouse.pos[1],mouse.pos[2]-a); glEnd(); Form1->Caption=AnsiString().sprintf("%.3lf , %.3lf , %.3lf : %u",mouse.pos[0],mouse.pos[1],mouse.pos[2],mouse.id); // debug buffer views if ((Form1->ck_depth->Checked)||(Form1->ck_stencil->Checked)) { glDisable(GL_DEPTH_TEST); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D,txr); GLfloat *f=new GLfloat[xs*ys],z; if (Form1->ck_depth ->Checked) { glReadPixels(0,0,xs,ys,GL_DEPTH_COMPONENT,GL_FLOAT,f); for (i=0;i<xs*ys;i++) f[i]=1.0-(2.0*mouse.zNear)/(mouse.zFar+mouse.zNear-(((2.0*f[i])-1.0)*(mouse.zFar-mouse.zNear))); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, xs, ys, 0, GL_RED, GL_FLOAT, f); } if (Form1->ck_stencil->Checked) { glReadPixels(0,0,xs,ys,GL_STENCIL_INDEX,GL_FLOAT,f); for (i=0;i<xs*ys;i++) f[i]/=float(objs); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, xs, ys, 0, GL_GREEN, GL_FLOAT, f); } delete[] f; glColor3f(1.0,1.0,1.0); glBegin(GL_QUADS); glTexCoord2f(1.0,0.0); glVertex2f(+1.0,-1.0); glTexCoord2f(1.0,1.0); glVertex2f(+1.0,+1.0); glTexCoord2f(0.0,1.0); glVertex2f(-1.0,+1.0); glTexCoord2f(0.0,0.0); glVertex2f(-1.0,-1.0); glEnd(); glMatrixMode(GL_PROJECTION); glPopMatrix(); glDisable(GL_TEXTURE_2D); glEnable(GL_DEPTH_TEST); } glFlush(); SwapBuffers(hdc); } //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner) { int i; object *o; gl_init(Handle); vao_init(); // init textures glGenTextures(1,&txr); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D,txr); glPixelStorei(GL_UNPACK_ALIGNMENT, 4); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_NEAREST); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,GL_COPY); glDisable(GL_TEXTURE_2D); // init objects glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslatef(-1.5,4.7,-8.0); for (o=obj,i=0;i<objs;i++,o++) { o->id=i+1; // unique non zero ID glGetDoublev(GL_MODELVIEW_MATRIX,o->m); glRotatef(360.0/float(objs),0.0,0.0,1.0); glTranslatef(-3.0,0.0,0.0); } for (o=obj,i=0;i<objs;i++,o++) { glLoadMatrixd(o->m); glRotatef(180.0*Random(),Random(),Random(),Random()); glGetDoublev(GL_MODELVIEW_MATRIX,o->m); } } //--------------------------------------------------------------------------- void __fastcall TForm1::FormDestroy(TObject *Sender) { glDeleteTextures(1,&txr); gl_exit(); vao_exit(); } //--------------------------------------------------------------------------- void __fastcall TForm1::FormResize(TObject *Sender) { gl_resize(ClientWidth,ClientHeight); // obtain/init matrices glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslatef(0,0,-15.0); glGetDoublev(GL_MODELVIEW_MATRIX,ieye); matrix_inv(eye,ieye); mouse.resize(0,0,xs,ys,eye); } //--------------------------------------------------------------------------- void __fastcall TForm1::FormPaint(TObject *Sender) { gl_draw(); } //--------------------------------------------------------------------------- void __fastcall TForm1::Timer1Timer(TObject *Sender) { gl_draw(); } //--------------------------------------------------------------------------- void __fastcall TForm1::FormMouseWheel(TObject *Sender, TShiftState Shift, int WheelDelta, TPoint &MousePos, bool &Handled) { GLfloat dz=2.0; if (WheelDelta<0) dz=-dz; glMatrixMode(GL_MODELVIEW); glLoadMatrixd(ieye); glTranslatef(0,0,dz); glGetDoublev(GL_MODELVIEW_MATRIX,ieye); matrix_inv(eye,ieye); gl_draw(); } //--------------------------------------------------------------------------- void __fastcall TForm1::FormMouseMove(TObject *Sender, TShiftState Shift, int X, int Y) { mouse.pick(X,Y); } //--------------------------------------------------------------------------- void __fastcall TForm1::ck_depthClick(TObject *Sender) { gl_draw(); } //---------------------------------------------------------------------------
此处预览左起RGB,深度,模板:
GIF截图:
前3个数字是拾取的像素在[GCS]中的3D位置,标题中的最后一个数字是拾取的ID,其中0表示没有对象。
[GCS]
0
下面的示例使用gl_simple,h:
gl_simple,h
您可以忽略VCL内容,因为它并不重要,只需将事件移植到您的环境中即可。
那么该怎么做呢:
1.渲染
您需要将模板缓冲区添加到您的GL窗口像素格式,因此在我的示例中,我只需添加:
pfd.cStencilBits = 16;
从gl_simple.h转换为gl_init()函数。还将其位添加到glClear中,并将每个对象模板设置为其ID,就像我在gl_draw()中所做的那样。
gl_simple.h
gl_init()
glClear
gl_draw()
我编写了一个小glMouse类,它完成了所有繁重的任务。每次更改透视、视图或视区时,调用其glMouse::resize函数。这将准备稍后计算所需的所有常量。注意它需要直接摄像/取景矩阵!
glMouse
glMouse::resize
现在,在每次鼠标移动(或单击或任何其他操作)时,调用glMouse::pick函数,然后使用类似id的结果,该结果将返回ID拾取的对象使用或pos,这是光线对象交点的全局世界坐标([GCS])中的3D坐标。
glMouse::pick
id
pos
该函数只读取深度和模板缓冲区。将深度线性化,如下所示:
并计算[GCS]中的射线beg,dir,pos,depth。
beg,dir,pos,depth
你有两个选择,要么将你的法线渲染为另一个缓冲区,这是最简单、最精确的。或读取周围2个或更多相邻像素的深度,计算其3D位置。如果需要,用叉积计算出你的正常和平均值。但这可能会导致边缘出现瑕疵。
正如评论中提到的,为了提高精度,您应该使用线性深度缓冲区,而不是如下所示的线性化对数:
顺便说一句,我在这里使用了相同的技术(在基于GDI的软件等轴测渲染中):
[编辑1]8位模板缓冲区
如今,可靠的模板位宽只有8位,这将ID的数量限制在255个。在大多数情况下,这是不够的。一种解决方法是将索引渲染为颜色,然后将帧存储到CPU内存中,然后正常渲染颜色。然后在需要时使用存储的帧进行拾取。渲染到纹理或颜色附加也是一种可能。
[编辑2]一些相关链接
oalqel3c2#
使用八叉树。确保它适合您的整个网格。
此外,听起来像是将每个多边形指定给一个叶/水桶,这是不正确的。将多边形指定给它们所在的所有树叶/水桶。
2条答案
按热度按时间i7uaboj41#
对于光线拾取渲染对象(如通过鼠标),最佳选择是使用已渲染的缓冲区,因为与复杂场景上的光线相交测试相比,读取缓冲区的成本非常低。其思想是将每个可拾取的渲染对象渲染到每个您需要的有关它们的信息的单独缓冲区中,例如:
1.深度缓冲
这将为您提供光线与对象相交的3D位置。
1.模具缓冲区
如果每个对象使用其ID(或其在对象列表中的索引)渲染到模具,则可以直接获得拾取的对象。
1.任何其他
也有二次颜色附件和FBO的存在。所以你可以添加任何其他东西,比如法线向量,或者你需要的任何东西。
如果编码正确,所有这些都只会略微降低性能(甚至根本不会),因为您不需要计算任何东西,只需要对每个缓冲区的每个片段进行一次写入。
拾取本身很容易,您只需从所有需要的缓冲区中读取相应的像素,并将其转换为所需的格式。
以下是使用固定管道(无着色器)的简单C++/VCL示例...
此处预览左起RGB,深度,模板:
GIF截图:
前3个数字是拾取的像素在
[GCS]
中的3D位置,标题中的最后一个数字是拾取的ID,其中0
表示没有对象。下面的示例使用
gl_simple,h
:您可以忽略VCL内容,因为它并不重要,只需将事件移植到您的环境中即可。
那么该怎么做呢:
1.渲染
您需要将模板缓冲区添加到您的GL窗口像素格式,因此在我的示例中,我只需添加:
从
gl_simple.h
转换为gl_init()
函数。还将其位添加到glClear
中,并将每个对象模板设置为其ID,就像我在gl_draw()
中所做的那样。我编写了一个小
glMouse
类,它完成了所有繁重的任务。每次更改透视、视图或视区时,调用其glMouse::resize
函数。这将准备稍后计算所需的所有常量。注意它需要直接摄像/取景矩阵!现在,在每次鼠标移动(或单击或任何其他操作)时,调用
glMouse::pick
函数,然后使用类似id
的结果,该结果将返回ID拾取的对象使用或pos
,这是光线对象交点的全局世界坐标([GCS]
)中的3D坐标。该函数只读取深度和模板缓冲区。将深度线性化,如下所示:
并计算
[GCS]
中的射线beg,dir,pos,depth
。你有两个选择,要么将你的法线渲染为另一个缓冲区,这是最简单、最精确的。或读取周围2个或更多相邻像素的深度,计算其3D位置。如果需要,用叉积计算出你的正常和平均值。但这可能会导致边缘出现瑕疵。
正如评论中提到的,为了提高精度,您应该使用线性深度缓冲区,而不是如下所示的线性化对数:
顺便说一句,我在这里使用了相同的技术(在基于GDI的软件等轴测渲染中):
[编辑1]8位模板缓冲区
如今,可靠的模板位宽只有8位,这将ID的数量限制在255个。在大多数情况下,这是不够的。一种解决方法是将索引渲染为颜色,然后将帧存储到CPU内存中,然后正常渲染颜色。然后在需要时使用存储的帧进行拾取。渲染到纹理或颜色附加也是一种可能。
[编辑2]一些相关链接
oalqel3c2#
使用八叉树。确保它适合您的整个网格。
此外,听起来像是将每个多边形指定给一个叶/水桶,这是不正确的。将多边形指定给它们所在的所有树叶/水桶。