当你打开Artoolkit工具包的examples文件夹,你会发现simpleVRML这个例子与其他的例子是不一样的。最主要的区别在于使用的MainLoop()框架不同,这个例子是以ar为主框架,而其他的大部分例子则是以opengl为主框架。因为框架不同,所以该例子显示的模型不是用OPENGL绘制的,而是用绘图软件绘制的,比如3Ds MAX、Sketchup、Solidworks……,以wrl格式文件存储。wrl文件是一种虚拟现实文本格式文件,也是VRML的场景模型文件的扩展名,可以用VRML浏览器打开,用VrmlPad软件修改、编辑。VRML(Virtual Reality Modeling Language)即虚拟现实建模语言。是一种用于建立真实世界的场景模型或人们虚构的三维世界的场景建模语言,也具有平台无关性。是目前Internet上基于 WWW的三维互动网站制作的主流语言。
下面来看一下simpleVRML.c程序的整体架构,头文件和变量的声明我就不多说了。
#ifdef _WIN32
#include
#endif
#include
#include
#include
#ifdef __APPLE__
#include
#else
//#define GLUT_DISABLE_ATEXIT_HACK
#include
#endif
#include
#include
#include // arParamDisp()//显示参数
#include
#include
#include
#include "object.h"
/*Constants常量*/
#define VIEW_SCALEFACTOR 0.025 //1.0 ARToolKit unit becomes 0.025 of my OpenGL units.范围因子,一个ARToolKit单位变成0.025个OpenGL单位。
#define VIEW_SCALEFACTOR_1 1.0 // 1.0 ARToolKit unit becomes 1.0 of my OpenGL units.
#define VIEW_SCALEFACTOR_4 4.0 // 1.0 ARToolKit unit becomes 4.0 of my OpenGL units.
#define VIEW_DISTANCE_MIN 4.0 // Objects closer to the camera than this will not be displayed.目标与摄像机距离小于这个不显示
#define VIEW_DISTANCE_MAX 4000.0 // Objects further away from the camera than this will not be displayed.目标与摄像机距离大于这个不显示
/*Global variables全局变量*/
//Preferences.
//OpenGL初始化参数设置Preferences.
static int prefWindowed = TRUE;
static int prefWidth = 640; // Fullscreen mode width.//全屏模式的宽度
static int prefHeight = 480;// Fullscreen mode height.//全屏模式的高度
static int prefDepth = 32;// Fullscreen mode bit depth.//全屏模式的位深度
static int prefRefresh = 0; // Fullscreen mode refresh rate. Set to 0 to use default rate.//全屏模式刷新率
//Image acquisition.//图像采集
static ARUint8 *gARTImage = NULL;//获取图像
//Marker detection.//标识检测
static int gARTThreshhold = 100; //标识检测时的二值化阈值
static long gCallCountMarkerDetect = 0;//初始化ARToolKit 帧频计数器为0
//Transformation matrix retrieval.//转换矩阵检索
static int gPatt_found = FALSE;//At least one marker.//用gPatt_found判断是否找到标识
// Drawing.
static ARParam gARTCparam;
static ARGL_CONTEXT_SETTINGS_REF gArglSettings = NULL;
//Object Data.
static ObjectData_T *gObjectData;
static int gObjectDataCount;
//打开相机,导入相机参数,更改相机长宽大小,初始化相机参数,捕获图像
static int setupCamera(const char *cparam_name, char *vconf, ARParam *cparam)
{
ARParam wparam;//储存相机参数
int xsize, ysize;//储存图像大小的值
// Open the video path.ddd//打开视频路径
if (arVideoOpen(vconf) < 0)
{
fprintf(stderr, "setupCamera(): Unable to open connection to camera.\n");
return (FALSE);
}
// Find the size of the window.//得到当前视频图像的大小
if (arVideoInqSize(&xsize, &ysize)<0) return (FALSE);
fprintf(stdout,"Camera image size (x,y)= (%d,%d)\n", xsize, ysize);
// Load the camera parameters, resize for the window and init.
//导入相机参数,重新定义窗口大小
if (arParamLoad(cparam_name, 1, &wparam) < 0)
{
fprintf(stderr, "setupCamera(): Error loading parameter file %s for camera.\n", cparam_name);
return (FALSE);
}
arParamChangeSize(&wparam, xsize, ysize, cparam);
fprintf(stdout, "*** Camera Parameter ***\n");
arParamDisp(cparam);//显示参数值
arInitCparam(cparam);//初始化相机参数
if (arVideoCapStart() != 0)
{
fprintf(stderr, "setupCamera(): Unable to begin camera data capture.\n");
return (FALSE);
}
return (TRUE);
}
//导入一个或多个标识的图像矩阵
static int setupMarkersObjects(char *objectDataFilename)
{
// Load in the object data - trained markers and associated bitmap files.
//导入一个或多个标识图---训练标识及其相关的位图文件,这里使用了object.c文件中的read_VRMLdata()函数
if ((gObjectData=read_VRMLdata(objectDataFilename,&gObjectDataCount)) == NULL)
{
fprintf(stderr, "setupMarkersObjects(): read_VRMLdata returned error !!\n");
return (FALSE);
}
printf("Object count = %d\n", gObjectDataCount);
return (TRUE);
}
// Report state of ARToolKit global variables arFittingMode,
// arImageProcMode, arglDrawMode, arTemplateMatchingMode, arMatchingPCAMode.
//显示当前AR的系统变量状态
static void debugReportMode(void)
{
/*arFittingMode:ARToolKit的合适的显示模式----对相机失真的校正模式,可以用纹理映射使能校正,
可能的值①AR_FITTING_TO_INPUT:输入图像;②AR_FITTING_TO_IDEAL:默认的补偿图像。*/
if(arFittingMode==AR_FITTING_TO_INPUT ) {
fprintf(stderr,"FittingMode (Z): INPUT IMAGE\n");
} else {
fprintf(stderr,"FittingMode (Z): COMPENSATED IMAGE\n");
}
/*arImageProcMode:定义标识图像分辨率的大小.①如果图像整体被分析,这个变量的值为AR_IMAGE_PROC_IN_FULL,即使用全分辨率图像;
②AR_IMAGE_PROC_IN_HALF:使用半分辨率图像,默认模式。------定义于头文件中*/
if(arImageProcMode==AR_IMAGE_PROC_IN_FULL ) {
fprintf(stderr,"ProcMode (X) : FULL IMAGE\n");
} else {
fprintf(stderr,"ProcMode (X) : HALF IMAGE\n");
}
/*arglDrawMode:定义绘制后台显示的配置模式.①AR_DRAW_BY_GL_DRAW_PIXELS:使用GL_DRAW_PIXELS函数;
②AR_DRAW_BY_TEXTURE_MAPPING:使用四方格进行纹理映射模式------定义于头文件中*/
if (arglDrawModeGet(gArglSettings)==AR_DRAW_BY_GL_DRAW_PIXELS) {
fprintf(stderr, "DrawMode (C) : GL_DRAW_PIXELS\n");
} else if (arglTexmapModeGet(gArglSettings)==AR_DRAW_TEXTURE_FULL_IMAGE) {
fprintf(stderr, "DrawMode (C) : TEXTURE MAPPING (FULL RESOLUTION)\n");
} else {
fprintf(stderr, "DrawMode (C) : TEXTURE MAPPING (HALF RESOLUTION)\n");
}
/*arTemplateMatchingMode:有2种可能的取值:①AR_TEMPLATE_MATCHING_COLOR:颜色模板;②AR_TEMPLATE_MATCHING_BW:BW模板------定义于头文件中*/
if( arTemplateMatchingMode==AR_TEMPLATE_MATCHING_COLOR ) {
fprintf(stderr,"TemplateMatchingMode (M) : Color Template\n");
} else {
fprintf(stderr,"TemplateMatchingMode (M) : BW Template\n");
}
/*arMatchingPCAMode:有2种可能的取值:①AR_MATCHING_WITHOUT_PCA:没有PCA;②AR_MATCHING_WITH_PCA:有PCA,默认模式------定义于头文件中*/
if( arMatchingPCAMode==AR_MATCHING_WITHOUT_PCA ) {
fprintf(stderr,"MatchingPCAMode (P) : Without PCA\n");
} else {
fprintf(stderr,"MatchingPCAMode (P) : With PCA\n");
}
}
static void Idle(void) //增加一个空闲任务,让应用程序在空闲时执行指定的函数。
{
static int ms_prev;
int ms;
float s_elapsed;
ARUint8 *image;
ARMarkerInfo *marker_info; // Pointer to array holding the details of detected markers.
int marker_num;// Count of number of markers detected.
int i, j, k;
// Find out how long since Idle() last ran.
//计算上次运行Idle函数到现在用了多长时间
ms = glutGet(GLUT_ELAPSED_TIME);
s_elapsed = (float)(ms - ms_prev) * 0.001;
if (s_elapsed < 0.01f) return; // Don't update more often than 100 Hz.超过100HZ时不更新
ms_prev = ms;
// Update drawing.
arVrmlTimerUpdate();
//Grab a video frame.捕获一帧图像
if((image = arVideoGetImage()) != NULL) {
gARTImage = image; // Save the fetched image.缓存这一帧图像
gPatt_found = FALSE; // Invalidate any previous detected markers.使之前检测到的任何标记都无效
gCallCountMarkerDetect++; // Increment ARToolKit FPS counter.增加ARToolKit 帧频计数器
// Detect the markers in the video frame.
//在图像帧中检测标识
if (arDetectMarker(gARTImage, gARTThreshhold, &marker_info, &marker_num) < 0) {
exit(-1);
}
// Check for object visibility.检查对象的可视化
for (i = 0; i < gObjectDataCount; i++) {
// Check through the marker_info array for highest confidence
// visible marker matching our object's pattern.
//从检测出的形似标识物的数组中按确信度匹配标识
k = -1;
for (j = 0; j < marker_num; j++) {
if (marker_info[j].id == gObjectData[i].id) {
if( k == -1 ) k = j; // First marker detected.检测到的第一个标识
else if (marker_info[k].cf < marker_info[j].cf) k = j; // Higher confidence marker detected.找到最高可信度的那个标识
}
}
if (k != -1) {
// Get the transformation between the marker and the real camera.
//得到标识和真实相机的变换矩阵
//fprintf(stderr, "Saw object %d.\n", i);
if (gObjectData[i].visible == 0) {
arGetTransMat(&marker_info[k],
gObjectData[i].marker_center, gObjectData[i].marker_width,
gObjectData[i].trans);
} else {
arGetTransMatCont(&marker_info[k], gObjectData[i].trans,
gObjectData[i].marker_center, gObjectData[i].marker_width,
gObjectData[i].trans);
}
gObjectData[i].visible = 1;
gPatt_found = TRUE;
} else {
gObjectData[i].visible = 0;
}
}
// Tell GLUT to update the display.
/*告诉glut,要显示的内容已经改变了,标记当前窗口需要重新绘制。在下一次循环中,
display函数将被回调,重新绘制窗口。节省CPU资源,防止display函数在没有更新的情况下被频繁调用。*/
glutPostRedisplay();
}
}
// This function is called when the
// GLUT window is resized.
/*手动改变显示窗口大小时调用的程序,当窗口大小变化时,为了防止物体变形,这时要重设投影转换矩阵,设置视口转换矩阵,以及视图转换矩阵。*/
static void Reshape(int w, int h)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glViewport(0, 0, (GLsizei) w, (GLsizei) h);//获得窗口的大小,负责把视景体截取的图像按照怎样的高和宽显示到屏幕上
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
// Call through to anyone else who needs to know about window sizing here.
}
// This function is called on events when the visibility of the
// GLUT window changes (including when it first becomes visible).
//当窗口的可视化改变时,调用该函数,包括第一次可视化也要调用。每次循环至少调用一次Idle函数。
static void Visibility(int visible)
{
if (visible == GLUT_VISIBLE) {
glutIdleFunc(Idle);
} else {
glutIdleFunc(NULL);
}
}
//This function is called when the window needs redrawing.
static void Display(void)//当窗口需要重新绘制模型的时候,调用该函数。
{
int i;
GLdouble p[16];
GLdouble m[16];
//第一步:清除屏幕,显示最新的后台缓冲帧
// Select correct buffer for this context.
/*指定缓存将要被哪个颜色绘制。当像素颜色被写到帧缓存时,这些颜色被写进由glDrawBuffer指定的颜色缓存区。
①GL_NONE没有缓存区要被写入;②GL_FRONT_LEFT只有左前缓存区被写入;类似的都是这样。本例中的代码是后缓存区被绘制。*/
glDrawBuffer(GL_BACK);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear the buffers for new frame.为绘制新帧清除之前帧的颜色缓存和深度缓存。
/*在后台缓存中显示相机图像作为背景,这个必须在渲染3D目标前调用。根据你的argDrawMode,argTexnapMode和内部图像的格式,
调用的OpenGL函数是不同的。*/
arglDispImage(gARTImage, &gARTCparam, 1.0, gArglSettings); // zoom = 1.0.
arVideoCapNext();/*开启下一帧,这个函数应该每一帧调用一次,它可以允许视频驱动完成常规任务,给视频捕获器信号,表示最近的视频帧已经处理完毕,
视频驱动可以重新使用被原来帧占用的内存。最好的调用地方是在arglDispImage和argDispImage之后。
在一些操作系统中相当于一个noop,空指令。返回:成功则返回0,返回-1表示视频驱动遇到错误。*/
gARTImage = NULL; // Image data is no longer valid after calling arVideoCapNext().
//当调用了arVideoCapNext()函数后,此时图像数据不再有效,等待接收下一帧图像。
//第二步:寻找标识,并根据相机参数设置投影矩阵
if (gPatt_found)//如果找到标识,就执行下面的程序
{
// Projection transformation.投影变换:创建一个透视投影矩阵,传递给OpenGL设置模型视景矩阵。
arglCameraFrustumRH(&gARTCparam, VIEW_DISTANCE_MIN, VIEW_DISTANCE_MAX, p);//把相机参数转换成OpenGL投影矩阵16的数组,然后直接导入,
//生成的三维物体就会匹配真实的相机视角。
glMatrixMode(GL_PROJECTION);
glLoadMatrixd(p);
glMatrixMode(GL_MODELVIEW);
//Viewing transformation.视景变换
glLoadIdentity();//将当前指定的矩阵重置为单位矩阵
// Lighting and geometry that moves with the camera should go here.
// (I.e. must be specified before viewing transformations.)
//none
// All other lighting and geometry goes here.
// Calculate the camera position for each object and draw it.
for (i = 0; i < gObjectDataCount; i++) //对标识进行遍历
{
if ((gObjectData[i].visible != 0) && (gObjectData[i].vrml_id >= 0))
{
//如果该标识被设置为可见(该设置在Idle()函数中完成)且ID正确那么就执行以下代码
//fprintf(stderr, "About to draw object %i\n", i);
arglCameraViewRH(gObjectData[i].trans, m, VIEW_SCALEFACTOR_4);//创建一个视景矩阵,传递给OpenGL设置虚拟相机的视景变换。转换标识转换矩阵
//成OpenGL视景矩阵,这16个值就是真实相机的位置和方向,使用他们设置虚拟相机的位置,使三维目标准确的放置在物理标识上。
glLoadMatrixd(m);//这里是设置虚拟相机的位置。
//第三步:在标识上画出虚拟物体。
arVrmlDraw(gObjectData[i].vrml_id);//根据前面加载的模型ID绘制模型
}
}
} // gPatt_found
// Any 2D overlays go here.
//none
glutSwapBuffers();//清理缓存
}
int main(int argc, char** argv)
{
int i;
char glutGamemode[32];
const char *cparam_name ="Data/camera_para.dat";
#ifdef _WIN32
char *vconf = "Data\\WDM_camera_flipV.xml";
#else
char *vconf = "";
#endif
char objectDataFilename[] = "Data/object_data_vrml";
// ----------------------------------------------------------------------------
// Library inits.
//
glutInit(&argc, argv);
// ----------------------------------------------------------------------------
// Hardware setup.
//
//打开相机,导入相机参数,更改相机长宽大小,初始化相机参数,捕获图像。
if (!setupCamera(cparam_name, vconf, &gARTCparam)) {
fprintf(stderr, "main(): Unable to set up AR camera.\n");
exit(-1);
}
#ifdef _WIN32
CoInitialize(NULL);
#endif
// ----------------------------------------------------------------------------
// Library setup.
//
// Set up GL context(s) for OpenGL to draw into.
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
if (!prefWindowed) {
if (prefRefresh) sprintf(glutGamemode, "%ix%i:%i@%i", prefWidth, prefHeight, prefDepth, prefRefresh);
else sprintf(glutGamemode, "%ix%i:%i", prefWidth, prefHeight, prefDepth);
glutGameModeString(glutGamemode);
glutEnterGameMode();
} else {
glutInitWindowSize(gARTCparam.xsize, gARTCparam.ysize);
glutCreateWindow(argv[0]);
}
// Setup argl library for current context.
if ((gArglSettings = arglSetupForCurrentContext()) == NULL) {
fprintf(stderr, "main(): arglSetupForCurrentContext() returned error.\n");
exit(-1);
}
debugReportMode();
arUtilTimerReset();//在刚开始的时候即帧数为0的时候,复位定时器arUtilTimerReset,以便使用arUtilTimer得到从复位开始消耗的时间。
if (!setupMarkersObjects(objectDataFilename)) {
fprintf(stderr, "main(): Unable to set up AR objects and markers.\n");
Quit();
}
// Test render all the VRML objects.
fprintf(stdout, "Pre-rendering the VRML objects...");
fflush(stdout);
glEnable(GL_TEXTURE_2D);
for (i = 0; i < gObjectDataCount; i++) {
arVrmlDraw(gObjectData[i].vrml_id);
}
glDisable(GL_TEXTURE_2D);
fprintf(stdout, " done\n");
// Register GLUT event-handling callbacks.//注册GLUT事件处理回调
// NB: Idle() is registered by Visibility.//注意:Idle()函数在Visibility中注册。
glutDisplayFunc(Display);//注册绘图函数,这样操作系统在必要时刻就会对窗体进行重新绘制操作。
glutReshapeFunc(Reshape);//注册重塑性函数
glutVisibilityFunc(Visibility);//注册可视化函数
glutKeyboardFunc(Keyboard);//注册鼠标响应函数
glutMainLoop();//进入GLUT事件处理循环,让所有的与“事件”有关的函数调用无限循环。在一个GLUT程序中,这个例程被调用一次。
//一旦被调用,这个程序将永远不会返回 ,它将调用必要的任何已注册的回调。
return (0);
}
typedef struct {
char name[256];
int id;
int visible;
double marker_coord[4][2];
double trans[3][4];
int vrml_id;
int vrml_id_orig;
double marker_width;
double marker_center[2];
} ObjectData_T;
#the number of patterns to be recognized 2
#pattern 1VRML Wrl/bud_B.dat Data/patt.hiro 80.0 0.0 0.0
#pattern 2VRML Wrl/snoman.dat Data/patt.kanji 80.0 0.0 0.0
bud_B.wrl
0.0 0.0 0.0 # Translation
0.0 0.0 0.0 0.0 # Rotation
10.0 10.0 10.0 # Scale
Cylinder.wrl
0.0 0.0 0.0 # Translation
90.0 1.0 0.0 0.0 # Rotation
80.0 80.0 80.0 # Scale
接下来,还要修改Data/object_data_vrml文件的内容,将第2行的Wrl/bud_B.dat修改成Wrl/Cylinder.dat,具体修改如下。
#the number of patterns to be recognized 2
#pattern 1VRML Wrl/Cylinder.dat Data/patt.hiro 80.0 0.0 0.0
#pattern 2VRML Wrl/snoman.dat Data/patt.kanji 80.0 0.0 0.0