I need to plot a set of 3D points (could be curve/fonts) on surface of a mesh such that they can be retrieved later, so rendering points on a texture and attaching the texture does not work. The points will be drawed/edited by mouse clicks. This is for CAD purposes so precision is important.
How can I get 3D vertices in mesh local coordinates from 2D mouse position?
I am using OpenGL for the rendering part with perspective projection created using glm::perspective() with:
FOV = 45.0f
aspect ratio = 16 : 9
zNear = 0.1f
zFar = 100.0f
triangulated mesh
Is it possible to do Ray-Triangle Intersection calculations in the object space?
Does the camera Position (Origin) have to be World Space?
what you need is (Assuming old api OpenGL default notations):
perspective projection: FOVx,FOVy,znear
inverse of Model*View matrix
mouse position converted to <-1,+1> range
list of triangles your mesh is composed of
You can do this like this:
cast ray from camera origin
convert it to mesh local coordinates
compute closest ray/triangle intersection
Yes you can compute this in mesh local coordinates and yes in such case you need Ray in the same coordinates.
Here simple C++/old api OpenGL/VCL example:
//---------------------------------------------------------------------------
#include <vcl.h>
#include <math.h>
#include "gl_simple.h"
#include "GLSL_math.h"
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
float mx=0.0,my=0.0; // mouse position
//---------------------------------------------------------------------------
// Icosahedron
#define icoX .525731112119133606
#define icoZ .850650808352039932
const GLfloat vdata[12][3] =
{
{-icoX,0.0,icoZ}, {icoX,0.0,icoZ}, {-icoX,0.0,-icoZ}, {icoX,0.0,-icoZ},
{0.0,icoZ,icoX}, {0.0,icoZ,-icoX}, {0.0,-icoZ,icoX}, {0.0,-icoZ,-icoX},
{icoZ,icoX,0.0}, {-icoZ,icoX,0.0}, {icoZ,-icoX,0.0}, {-icoZ,-icoX,0.0},
};
const int tindices=20;
const GLuint tindice[tindices][3] =
{
{0,4,1}, {0,9,4}, {9,5,4}, {4,5,8}, {4,8,1},
{8,10,1}, {8,3,10}, {5,3,8}, {5,2,3}, {2,7,3},
{7,10,3}, {7,6,10}, {7,11,6}, {11,0,6}, {0,1,6},
{6,1,10}, {9,0,11}, {9,11,2}, {9,2,5}, {7,2,11}
};
//---------------------------------------------------------------------------
void icosahedron_draw() // renders mesh using old api
{
int i;
GLfloat nx,ny,nz;
glEnable(GL_CULL_FACE);
glFrontFace(GL_CW);
glBegin(GL_TRIANGLES);
for (i=0;i<tindices;i++)
{
nx =vdata[tindice[i][0]][0];
ny =vdata[tindice[i][0]][1];
nz =vdata[tindice[i][0]][2];
nx+=vdata[tindice[i][1]][0];
ny+=vdata[tindice[i][1]][1];
nz+=vdata[tindice[i][1]][2];
nx+=vdata[tindice[i][2]][0]; nx/=3.0;
ny+=vdata[tindice[i][2]][1]; ny/=3.0;
nz+=vdata[tindice[i][2]][2]; nz/=3.0;
glNormal3f(nx,ny,nz);
glVertex3fv(vdata[tindice[i][0]]);
glVertex3fv(vdata[tindice[i][1]]);
glVertex3fv(vdata[tindice[i][2]]);
}
glEnd();
}
//---------------------------------------------------------------------------
vec3 ray_pick(float mx,float my,mat4 _mv) // return closest intersection using mouse mx,my <-1,+1> position and inverse of ModelView _mv
{
// Perspective settings
const float deg=M_PI/180.0;
const float _zero=1e-6;
float znear=0.1;
float FOVy=45.0*deg;
float FOVx=FOVy*xs/ys; // use aspect ratio if you do not know screen resolution
// Ray endpoints in camera local coordinates
vec3 pos=vec3(mx*tan(0.5*FOVx)*znear,my*tan(0.5*FOVy)*znear,-znear);
vec3 dir=vec3(0.0,0.0,0.0);
// Transform to mesh local coordinates
pos=(_mv*vec4(pos,1.0)).xyz;
dir=(_mv*vec4(dir,1.0)).xyz;
// convert endpoint to direction
dir=normalize(pos-dir);
// needed variables
vec3 pnt=vec3(0.0,0.0,0.0);
vec3 v0,v1,v2,e1,e2,n,p,q,r;
int i,ii=1;
float t=-1.0,tt=-1.0,u,v,det,idet;
// loop through all triangles
for (int i=0;i<tindices;i++)
{
// load v0,v1,v2 with actual triangle
v0.x=vdata[tindice[i][0]][0];
v0.y=vdata[tindice[i][0]][1];
v0.z=vdata[tindice[i][0]][2];
v1.x=vdata[tindice[i][1]][0];
v1.y=vdata[tindice[i][1]][1];
v1.z=vdata[tindice[i][1]][2];
v2.x=vdata[tindice[i][2]][0];
v2.y=vdata[tindice[i][2]][1];
v2.z=vdata[tindice[i][2]][2];
//compute ray(pos,dir) triangle(v0,v1,v2) intersection
e1=v1-v0;
e2=v2-v0;
// Calculate planes normal vector
p=cross(dir,e2);
det=dot(e1,p);
// Ray is parallel to plane
if (abs(det)<1e-8) continue;
idet=1.0/det;
r=pos-v0;
u=dot(r,p)*idet;
if ((u<0.0)||(u>1.0)) continue;
q=cross(r,e1);
v=dot(dir,q)*idet;
if ((v<0.0)||(u+v>1.0)) continue;
t=dot(e2,q)*idet;
// remember closest intersection to camera
if ((t>_zero)&&((t<=tt)||(ii!=0)))
{
ii=0; tt=t;
// barycentric interpolate position
t=1.0-u-v;
pnt=(v0*t)+(v1*u)+(v2*v);
}
}
return pnt; // if (ii==1) no intersection found
}
//---------------------------------------------------------------------------
void gl_draw()
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glDisable(GL_TEXTURE_2D);
glEnable(GL_DEPTH_TEST);
glEnable(GL_LIGHT0);
glEnable(GL_CULL_FACE);
// glDisable(GL_CULL_FACE);
glFrontFace(GL_CCW);
glEnable(GL_COLOR_MATERIAL);
/*
glPolygonMode(GL_FRONT,GL_FILL);
glPolygonMode(GL_BACK,GL_LINE);
glDisable(GL_CULL_FACE);
*/
// set projection
glMatrixMode(GL_PROJECTION); // operacie s projekcnou maticou
glLoadIdentity(); // jednotkova matica projekcie
gluPerspective(45,float(xs)/float(ys),0.1,100.0); // matica=perspektiva,120 stupnov premieta z viewsize do 0.1
// set view
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(0.2,0.0,-5.0);
static float ang=0.0;
glRotatef(ang,0.2,0.7,0.2); ang+=5.0; if (ang>=360.0) ang-=360.0;
// obtain actual modelview matrix (mv) and its inverse (_mv)
mat4 mv,_mv;
float m[16];
glGetFloatv(GL_MODELVIEW_MATRIX,m);
mv.set(m);
_mv=inverse(mv);
// render mesh
glColor3f(0.5,0.5,0.5);
glEnable(GL_LIGHTING);
icosahedron_draw();
glDisable(GL_LIGHTING);
// get point mouse points to
vec3 p=ray_pick(mx,my,_mv);
// render it for visual check
float r=0.1;
glColor3f(1.0,1.0,0.0);
glBegin(GL_LINES);
glVertex3f(p.x-r,p.y,p.z); glVertex3f(p.x+r,p.y,p.z);
glVertex3f(p.x,p.y-r,p.z); glVertex3f(p.x,p.y+r,p.z);
glVertex3f(p.x,p.y,p.z-r); glVertex3f(p.x,p.y,p.z+r);
glEnd();
// glFlush();
glFinish();
SwapBuffers(hdc);
}
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner)
{
// Init of program
gl_init(Handle); // init OpenGL
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
// Exit of program
gl_exit();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
// repaint
gl_draw();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormResize(TObject *Sender)
{
// resize
gl_resize(ClientWidth,ClientHeight);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::tim_redrawTimer(TObject *Sender)
{
gl_draw();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormMouseMove(TObject *Sender, TShiftState Shift, int X, int Y)
{
// just event to obtain actual mouse position
// and convert it from screen coordinates <0,xs),<0,ys) to <-1,+1> range
mx=X; mx=(2.0*mx/float(xs-1))-1.0;
my=Y; my=1.0-(2.0*my/float(ys-1)); // y is mirrored in OpenGL
}
//---------------------------------------------------------------------------
The only imnportant thing is function ray_pick which returns your 3D point based on mouse 2D position and actual inverse of ModelView matrix...
Here preview:
I render yellow cross at the found 3D position as you can see its in direct contact to surface (as half of its line are below surface).
On top of usual stuff I used mine libs: gl_simple.h for the OpenGL context creation and GLSL_math.h instead of GLM for vector and matrix math. But the GL context can be created anyhow and you can use what you have for the math too (I think even the syntax is the same as GLM as they tried to mimic GLSL too...)
Looks like for aspects 1:1 this works perfectly, and in rectangular aspects its slightly imprecise the further away from screen center you get (most likely because I used raw gluPerspective which has some imprecise terms in it, or I missed some correction while ray creation but I doubt that)
In case you do not need high precision (not your case as CAD/CAM needs as high precision as you can get) You can get rid of the ray mesh intersection and directly pick depth buffer at mouse position and compute the resulting point from that (no need for mesh).
For more info see related QAs:
Understanding 4x4 homogenous transform matrices
OpenGL 3D-raypicking with high poly meshes
Related
Prologue
This Q&A is a remake of:
How to highlight 3D perspective camera view (frustrum) on topside 2D minimap?
which was closed (and failed first reopening cycle) due to lack of info and no response of the original author. However I think this is an interesting question though so I decided to Ask&Answer this myself (this time with all the needed specs).
Question
Let assume our world is a uniform rectangular square grid (represented by 2D array of tiles) mapped on a plane (let say plane XY (Z=0.0) for simplicity) and is rendered with perspective projection. like this:
How to map the perspective frustrum (visible part of the map/plane) to the red colored polygonal shape on the minimap ?
To be more universal let assume this as input:
plane (Z=0.0) defined as start point p0 and two basis vectors du,dv which maps the 2D map array of tiles to 3D ...
ModelView matrix and Perspective matrix used
And wanted output:
4 point polygon (on minimap) representing the visible part of out plane
Limitations (to more or less match the original question):
use C++
old style OpenGL (GL,GLU)
no 3th party lib for vector/matrix math
So what we want is to obtain the 4 intersection points between our plane (Z=0.0) and camera frustrum. So the idea is to cast 4 rays (one for each edge of frustrum) from the camera focal point and simply compute the ray/plane intersection. As the plane is Z=0.0 the intersection point has Z=0.0 too so the intersection is quite easy to compute.
Cast ray for each corner/edge
from camera focal point to screen corner (in screen space)
and convert it to world global coordinates (by reverting perspective and using inverse modelview matrix it is described later). The ray should be in form:
p(t) = p + dp*t
where p is the focal point and dp is direction vector (does not need to be normalized)
compute the intersection with XY plane (Z=0.0)
As the z=0.0 then:
0 = p.z + dp.z*t
t = -p.z/dp.z
so we can compute the intersection point directly.
convert 3D intersection points to u,v inside map
for that simple dot product is enough. So if p is our intersection point then:
u = dot(p-p0,du)
v = dot(p-p0,dv)
where u,v are coordinates in our 2D map array or minimap. In case your u,v are axis aligned then you can use directly (p.x-p0.x,p.y-p0.y) without any dot product
How to convert point p from camera coordinates to global world coordinates:
revert perspective
first obtain perspective matrix parameters
double per[16],zNear,zFar,fx,fy;
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];
This will give you the frustrums near and far planes and scaling for x,y axises. Now reverting perspective is simply inverting the perspective divide like this:
p[1]*=(-p[2]/fy); // apply inverse of perspective
p[0]*=(-p[2]/fx);
The znear and zfar are needed for casting the rays. For more info see:
depth buffer got by glReadPixels is always 1
global world coordinates
simply use inverse of ModelView matrix on our p. So first obtain the matrix:
double cam[16];
glGetDoublev(GL_MODELVIEW_MATRIX,cam);
As inverse you can use my matrix_inv so now the final step is:
p = Inverse(cam)*p;
but do not forget that p must be homogenuous so (x,y,z,1) for points and (x,y,z,0) for vectors.
Look here if you lack the background knowledge or need vector/matrix math:
Understanding 4x4 homogenous transform matrices
Here Small C++ example of this:
//---------------------------------------------------------------------------
void matrix_mul_vector(double *c,double *a,double *b)
{
double q[3];
q[0]=(a[ 0]*b[0])+(a[ 4]*b[1])+(a[ 8]*b[2])+(a[12]);
q[1]=(a[ 1]*b[0])+(a[ 5]*b[1])+(a[ 9]*b[2])+(a[13]);
q[2]=(a[ 2]*b[0])+(a[ 6]*b[1])+(a[10]*b[2])+(a[14]);
for(int i=0;i<3;i++) c[i]=q[i];
}
//---------------------------------------------------------------------------
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 draw_map()
{
int i,j;
double u,v,p[3],dp[3];
// here 3D view must be already set (modelview,projection)
glDisable(GL_CULL_FACE);
// [draw 3D map]
const int n=30; // map size
double p0[3]={0.0,0.0,0.0}; // map start point
double du[3]={1.0,0.0,0.0}; // map u step (size of grid = 1.0 )
double dv[3]={0.0,1.0,0.0}; // map v step (size of grid = 1.0 )
glColor3f(0.5,0.7,1.0);
glBegin(GL_LINES);
for (j=0;j<=n;j++)
{
for (i=0;i<3;i++) p[i]=p0[i]+(double(j)*du[i])+(double(0)*dv[i]); glVertex3dv(p);
for (i=0;i<3;i++) p[i]=p0[i]+(double(j)*du[i])+(double(n)*dv[i]); glVertex3dv(p);
for (i=0;i<3;i++) p[i]=p0[i]+(double(0)*du[i])+(double(j)*dv[i]); glVertex3dv(p);
for (i=0;i<3;i++) p[i]=p0[i]+(double(n)*du[i])+(double(j)*dv[i]); glVertex3dv(p);
}
glEnd();
// [compute trapeze points]
double cam[16],per[16],pt[4][3],zNear,zFar,fx,fy;
glGetDoublev(GL_PROJECTION_MATRIX,per); // obtain matrices
glGetDoublev(GL_MODELVIEW_MATRIX,cam);
matrix_inv(cam,cam);
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];
for (j=0;j<4;j++) // 4 corners
{
for (i=0;i<3;i++) dp[i]=0.0; // cast ray from camera focus dp
if (j==0) { p[0]=-1.0; p[1]=-1.0; } // to screen corner p
if (j==1) { p[0]=-1.0; p[1]=+1.0; }
if (j==2) { p[0]=+1.0; p[1]=+1.0; }
if (j==3) { p[0]=+1.0; p[1]=-1.0; }
p[2]=zNear; // start position at screen plane
p[1]*=(-p[2]/fy); // apply inverse of perspective
p[0]*=(-p[2]/fx);
// transform to worlds global coordinates
matrix_mul_vector( p,cam, p);
matrix_mul_vector(dp,cam,dp);
// compute intersection of ray and XY plane (z=0) as pt[j] (i exploited the fact that the intersection have z=0.0 for arbitrary plane it would be a bit more complicated)
for (i=0;i<3;i++) dp[i]=p[i]-dp[i];
u=p[2]/dp[2];
if (u<0.0) u=(p[2]-zFar)/dp[2]; // no intersection means "infinite" visibility
for (i=0;i<3;i++) pt[j][i]=p[i]-(u*dp[i]);
u=0.0;
}
// [draw 2D minimap]
GLint vp0[4];
GLint vp1[4]={10,10,150,150}; // minimap position and size ppixels[
double q0[2]={-1.0,-1.0 }; // minimap start point
double eu[2]={2.0/double(n),0.0}; // minimap u step
double ev[2]={0.0,2.0/double(n)}; // minimap v step
// set 2D view for minimap
glDisable(GL_DEPTH_TEST);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
glGetIntegerv(GL_VIEWPORT,vp0);
glViewport(vp1[0],vp1[1],vp1[2],vp1[3]);
glColor3f(0.0,0.0,0.0); // clear background
glBegin(GL_QUADS);
for (i=0;i<2;i++) p[i]=q0[i]+(double(0)*eu[i])+(double(0)*ev[i]); glVertex2dv(p);
for (i=0;i<2;i++) p[i]=q0[i]+(double(n)*eu[i])+(double(0)*ev[i]); glVertex2dv(p);
for (i=0;i<2;i++) p[i]=q0[i]+(double(n)*eu[i])+(double(n)*ev[i]); glVertex2dv(p);
for (i=0;i<2;i++) p[i]=q0[i]+(double(0)*eu[i])+(double(n)*ev[i]); glVertex2dv(p);
glEnd();
glColor3f(0.15,0.15,0.15); // grid
glBegin(GL_LINES);
for (j=0;j<=n;j++)
{
for (i=0;i<2;i++) p[i]=q0[i]+(double(j)*eu[i])+(double(0)*ev[i]); glVertex2dv(p);
for (i=0;i<2;i++) p[i]=q0[i]+(double(j)*eu[i])+(double(n)*ev[i]); glVertex2dv(p);
for (i=0;i<2;i++) p[i]=q0[i]+(double(0)*eu[i])+(double(j)*ev[i]); glVertex2dv(p);
for (i=0;i<2;i++) p[i]=q0[i]+(double(n)*eu[i])+(double(j)*ev[i]); glVertex2dv(p);
}
glEnd();
glColor3f(0.5,0.5,0.5); // border of minimap
glLineWidth(2.0);
glBegin(GL_LINE_LOOP);
for (i=0;i<2;i++) p[i]=q0[i]+(double(0)*eu[i])+(double(0)*ev[i]); glVertex2dv(p);
for (i=0;i<2;i++) p[i]=q0[i]+(double(n)*eu[i])+(double(0)*ev[i]); glVertex2dv(p);
for (i=0;i<2;i++) p[i]=q0[i]+(double(n)*eu[i])+(double(n)*ev[i]); glVertex2dv(p);
for (i=0;i<2;i++) p[i]=q0[i]+(double(0)*eu[i])+(double(n)*ev[i]); glVertex2dv(p);
glEnd();
glLineWidth(1.0);
// 2D minimap render of the pt[]
glColor3f(0.7,0.1,0.1); // trapeze
glBegin(GL_LINE_LOOP);
for (j=0;j<4;j++)
{
// get u,v from pt[j]
for (i=0;i<3;i++) p[i]=pt[j][i]-p0[i];
for (u=0.0,i=0;i<3;i++) u+=p[i]*du[i];
for (v=0.0,i=0;i<3;i++) v+=p[i]*dv[i];
// convert to 2D position and render
for (i=0;i<2;i++) p[i]=q0[i]+(u*eu[i])+(v*ev[i]); glVertex2dv(p);
}
glEnd();
// restore 3D view
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glViewport(vp0[0],vp0[1],vp0[2],vp0[3]);
glEnable(GL_DEPTH_TEST);
}
//---------------------------------------------------------------------------
And preview:
As you can see we need just matrix*vector multiplication and pseudo inverse matrix functions for this (all others like dot,+,- are really simple and directly encoded as inline code) and both are simple enough to directly implement it in code so no need for GLM or similar lib.
Also I was too lazy to clip the 4 point polygon to minimap size so instead I used glViewport which did it for me.
Here Win32 BDS2006 VCL/C++/OpenGL1.0 Demo:
Demo Source+Binary
Just select slow download and enter the validation code from image. It does not use any 3th party libs other than GL,GLU. The camera is static so just add keyboard/mouse events to your liking. If you want to port this to your environment just mimic the events behavior and ignore the VCL stuff.
The OpenGL init is done based on this:
simple complete GL+VAO/VBO+GLSL+shaders example in C++
I just removed the GLEW,GLSL and VAO stuff from it.
How to implement 3d raypicking in an 3d scene with models that contain high poly meshes?
It takes too much time to iterate over all triangles to perform a triangle-line-intersection test. I know that there exist methods like octree etc. and it should be possible to use these for the models in the scene, but I do not know how I should use these concepts at mesh-level. But if you use an octree at mesh-level, how should one cover problems with polygons, that exceed the boundaries of the octree volumes?
Do you have any advice which method is suitable or recommended for 3d ray-intersections with high poly models for real-time OpenGl applications?
For ray picking rendered objects (like by mouse) the best option is to use the already rendered buffers as there is very little cost of reading them in comparison to ray intersection tests on complex scene. The idea is to render each pick-able rendered object to separate buffer per each info you need about them for example like this:
Depth buffer
this will give you the 3D position of the ray intersection with object.
Stencil buffer
if each object rendered to stencil with its ID (or its index in object list) then you can get the picked object directly.
any other
there are also secondary color attachments and FBO's out there. So you can add any other stuff like normal vector or what ever you need.
If coded right all of this will reduce performance only slightly (even not at all) as you do not need to compute anything its just a single write per fragment per buffer.
The picking itself is easy you just read the corresponding pixel from all the buffers you need and convert to wanted format.
Here simple C++/VCL example using fixed pipeline (no shaders)...
//---------------------------------------------------------------------------
#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
depth=(2.0*zNear)/(zFar+zNear-(depth*(zFar-zNear))); // linear <0,1>
depth=zNear + depth*(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();
}
//---------------------------------------------------------------------------
Here preview of from left RGB,Depth,Stencil:
Here captured GIF:
the first 3 numbers are the 3D position of picked pixel in [GCS] and the last number in caption is the picked ID where 0 means no object.
The example is using gl_simple,h from here:
simple complete GL+VAO/VBO+GLSL+shaders example in C++
You can ignore the VCL stuff as its not important just port the events to your environment...
So what to do:
rendering
You need add stencil buffer to your GL window pixel format so in my case I just add:
pfd.cStencilBits = 16;
into gl_init() function from gl_simple.h. Also add its bit into glClear and set each objects stencil to its ID Like I did in gl_draw().
picking
I wrote a small glMouse class that do all the heavy lifting. On each change of perspective, view, or viewport call its glMouse::resize function. That will prepare all the constants needed for the computations later. Beware it needs direct camera/view matrix !!!
Now on each mouse movement (or click or whatever) call the glMouse::pick function and then use the results like id which will return the ID picked object was rendered with or pos which is the 3D coordinate in global world coordinates ([GCS]) of the ray object intersection.
The function just read the depth and stencil buffers. Linearize depth like here:
depth buffer got by glReadPixels is always 1
and compute the ray beg,dir,pos,depth in [GCS].
Normal
You got 2 options either render your normal as another buffer which is the simplest and most precise. Or read depths of 2 or more neighboring pixels around picked one compute their 3D positions. From that using cross product compute you normal(s) and average if needed. But this can lead to artifacts on edges.
As mentioned in the comments to boost accuracy you should use linear depth buffer instead of linearized logarithmic like this:
Linear depth buffer
Btw I used the same technique in here (in GDI based SW isometric render):
Improving performance of click detection on a staggered column isometric grid
[Edit1] 8bit stencil buffer
Well these days the reliable stencil bitwidth is only 8bit which limits the number of ids to 255. That is in most cases not enough. A workaround is to render the indexes as colors then store the frame into CPU memory and then render colors normaly. Then when needed using the stored frame for picking. Rendering to texture or color attachment is also a possibility.
[Edit2] some related links
objects moving with mouse
objects moving and orienting with mouse
Use an Octree. Make sure it fits in whole of your mesh.
Also, it sounds like you are assigning each to poly to just one leaf/bucket, which is not right. Assign polys to all leafs/buckets they appear in.
I'm just a noob to GLSL and don't know how to do this in GLSL.
What I trying to do is making alpha value to 1 on center of sphere and drop gradually on outer.
So I made a prototype using Blender node editor and that's how I did.
Now I am trying to do this in glsl.
Maybe i can use gl_Normal to replace "normal on Geometry" on Blender.
(Though it's removed after version 140, my final goal is just "make" it, so ignore that.)
And there are also dot function to calculate "dot product on vector math" on glsl.
Now i need is "View vector of camera data" and "ColorRamp".
I think "ColorRamp" can be done with mix and sin functions,
but have no idea how to get "View vector of camera data".
I already read this, and understand what it is, but don't know how to get.
So How can I get "View vector of camera data"?
Well without depth the shaders are simple enough:
// Vertex
varying vec2 pos; // fragment position in world space
void main()
{
pos=gl_Vertex.xy;
gl_Position=ftransform();
}
// Fragment
varying vec2 pos;
uniform vec4 sphere; // sphere center and radius (x,y,z,r)
void main()
{
float r,z;
r=length(pos-sphere.xy); // radius = 2D distance to center (ignoring z)
if (r>sphere.a) discard; // throw away fragments outside sphere
r=0.2*(1.0-(r/sphere[3])); // color gradient from 2D radius ...
gl_FragColor=vec4(r,r,r,1.0);
}
Yes you can also use gl_ModelViewProjectionMatrix * gl_Vertex; instead of the ftransform(). As you can see I used world coordinates so I do not need to play with radius scaling... If you want also the gl_FragDepth to make this 3D then you have to work in screen space which is much more complicated and I am too lazy to try it. Anyway change the gradient color to whatever you like.
The rendering in C++ is done like this:
void gl_draw()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
GLint id;
float aspect=float(xs)/float(ys);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60.0/aspect,aspect,0.1,100.0);
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glRotatef(15.0,0.0,1.0,0.0);
glTranslatef(1.0,1.0,-10.0);
glDisable(GL_DEPTH_TEST);
glDisable(GL_TEXTURE_2D);
float xyzr[4]={ 0.7,0.3,-5.0,1.5 };
// GL 1.0 circle for debug
int e; float a,x,y,z;
glBegin(GL_LINE_STRIP);
for (a=0.0,e=1;e;a+=0.01*M_PI)
{
if (a>=2.0*M_PI) { e=0; a=2.0*M_PI; }
x=xyzr[0]+(xyzr[3]*cos(a));
y=xyzr[1]+(xyzr[3]*sin(a));
z=xyzr[2];
glVertex3f(x,y,z);
}
glEnd();
// GLSL sphere
glUseProgram(prog_id);
id=glGetUniformLocation(prog_id,"sphere"); glUniform4fv(id,1,xyzr);
glBegin(GL_QUADS);
glColor3f(1,1,1);
glVertex3f(xyzr[0]-xyzr[3],xyzr[1]-xyzr[3],xyzr[2]);
glVertex3f(xyzr[0]+xyzr[3],xyzr[1]-xyzr[3],xyzr[2]);
glVertex3f(xyzr[0]+xyzr[3],xyzr[1]+xyzr[3],xyzr[2]);
glVertex3f(xyzr[0]-xyzr[3],xyzr[1]+xyzr[3],xyzr[2]);
glEnd();
glUseProgram(0);
glFlush();
SwapBuffers(hdc);
}
And result:
In white is the debug GL 1.0 circle to see if the two are placed in the same place. Change the gradient to match your needs. I did not use transparency so if you need it change the alpha component and enable/set BLENDing.
The xs,ys is resolution of my GL window. and xyzr is your sphere { x,y,z,r } definition. Hope I did not forget to copy something. This code and answer take advantage of (so look there for more info in case I miss something):
GLSL render Disc pattern
complete GL+GLSL+VAO/VBO C++ example
I have written a simple openGL program in C++. This program draws a sphere in 3D perspective projection and tries to draw a line joining the center of the sphere to the current cursor position in 2D orthographic projection. Now for drawing the line I can't figure out the coordinate of center of the sphere.
This is my code :
#ifdef __APPLE__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif
void passive(int,int);
void reshape(int,int);
void init(void);
void display(void);
void camera(void);
int cursorX,cursorY,width,height;
int main (int argc,char **argv) {
glutInit (&argc,argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGBA);
glutInitWindowSize(1364,689);
glutInitWindowPosition(0,0);
glutCreateWindow("Sample");
init();
glutDisplayFunc(display);
glutIdleFunc(display);
glutPassiveMotionFunc(passive);
glutReshapeFunc(reshape);
glutMainLoop();
return 0;
}
void display() {
glClearColor (0.0,0.0,0.0,1.0);
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Render 3D content
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60,(GLfloat)width/(GLfloat)height,1.0,100.0); // create 3D perspective projection matrix
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
camera();
glTranslatef(-6,-2,0);
glColor3f(1,0,0);
glutSolidSphere(5,50,50);
glPopMatrix();
// Render 2D content
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, width,height, 0); // create 2D orthographic projection matrix
glMatrixMode(GL_MODELVIEW);
glColor3f(1,1,1);
glBegin(GL_LINES);
glVertex2f( centreX,centreY ); // coordinate of center of the sphere in orthographic projection
glVertex2f( cursorX,cursorY );
glEnd();
glutSwapBuffers();
}
void camera(void) {
glRotatef(0.0,1.0,0.0,0.0);
glRotatef(0.0,0.0,1.0,0.0);
glTranslated(0,0,-20);
}
void init(void) {
glEnable (GL_DEPTH_TEST);
glEnable (GL_BLEND);
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_COLOR_MATERIAL);
}
void reshape(int w, int h) {
width=w; height=h;
}
void passive(int x1,int y1) {
cursorX=x1; cursorY=y1;
}
I can,t figure out the values for centreX and centreY. Anyway I can get the correct values to draw the line?
You may be interested in using something like gluProject to go from your object coordinates to the actual (projected) position on screen. Once you have the screen coordinates of the object, it's easy to draw a line from one point to another.
In this case, you'll want to project the centre point of the sphere. For more complex objects I've found that it makes sense to project all of the corners of the object's bounding box and then take the extents of the screenspace position of those corners.
You should get the modelview, viewport and projection matrices before you switch to your orthographic projection (2D mode).
Obviously, in order to go from a screen position (say, where you clicked in the window) to a world position, you'll want to use its companion function, gluUnProject.
Note that the coordinates that come out of gluProject do not necessarily correspond directly to the window position; you might have to flip the "Y" coordinate.
Take a look at this GDSE discussion for some other ideas about how to solve the problem.
I hope you can help me with a little problem...
I know how to draw a circle, that's not a problem - here is the code in c#
void DrawEllipse()
{
GL.Color3(0.5, 0.6, 0.2);
float x, y, z;
double t;
GL.Begin(BeginMode.Points);
for (t = 0; t <= 360; t += 0.25)
{
x = (float)(3*Math.Sin(t));
y = (float)(3*Math.Cos(t));
z = (float)0;
GL.Vertex3(x, y, z);
}
GL.End();
}
But there is a problem - when I Rotate 'Gl.Rotate(angle, axis)' and then redraw a circle - yeah, it's still circle in the 3D, but I want a circle in the screen - I mean static circle which is not rotating with 3D object in it... Is that possible? How to repair the code?
Are you trying to draw a 2D circle on top of a 3D scene to create a HUD or similar? If you are then you should research 2D OpenGL, glOrtho and using multiple viewports in a scene. There is a discussion around this here:
http://www.gamedev.net/topic/388298-opengl-hud/
Just draw it at a position before the camera!
Use pushMatrix() and popMatrix().
Or you can draw the other things between pushMatrix() and popMatrix(). Then draw the circle.
HUD (heads-up display): http://en.wikipedia.org/wiki/HUD_(video_gaming)
void setupScene ()
{
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
// set the perspective
glFrustum(...) // or glu's perspective
}
void loop ()
{
// main scene
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glViewport (...)
// push the camera position into GL_MODELVIEW
// (i.e. the inverse matrix of its object position)
// draw your normal 3D objects
// switch to 2D projection (for the HUD)
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glOrtho(....)
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
// draw the objects onto the HUD
// switch back to 3d projection (i.e. restore GL_PROJECTION)
// glEnable (GL_DEPTH_TEST);
glMatrixMode(GL_PROJECTION);
glPopMatrix();
// glMatrixMode(GL_MODELVIEW);
// swap buffers
}
The commented code is optional, depending on what you're gonna do in the end. Take it as hints.