终于写好Z Buffer作业了
Z buffer这个算法比较简单,基本就照教材上写的,省去了活化多边形链表,感觉没必要。就核心代码,有位师兄用一个晚上就写好了,我比较挫用了一天时间。为了添加一些一般3D SDK(e.g. D3D OpenGL)的简单绘制功能,如局部光照模型、逐像素光照计算(Per Pixel Lighting)等,重新整理了程序架构和算法。
主要功能
- obj文件载入,只用到顶点位置和法向量信息,没有法向的根据模型计算法向,取差角小于90度的邻面共点平均。
- Gouraud和Phong绘制模型,即对颜色的双线性插值(Gouraud Shading or Per Vertex Shading)和对法向量的双线性插值(Phong Shading or Per Pixel Shading)。
- 光照计算,Phong光照模型,已实现点光源和方向光。
- 正交投影(Orthogonal Projection)和透视投影(Perspective Projection)。
数据结构是仿D3D的,接口是仿OpenGL的,依照OpenCV代码学用template,界面是Qt的,没有使用第三方3D库。
class CScanLine
{
public:
CScanLine(int w, int h, QImage *img);
void setRenderTarget(int w, int h, QImage *img);
// start create target
void begin(TargetType type);
void end();
// 数据输入
void vertex3d(double x, double y, double z);
void color3f(float r, float g, float b);
// 清空缓存
void clear(int target, const Color4u & c = Color4u(0,0,0,255), double depth = 1.0);
// 相机相关, facade design model
void lookAt(const Vec3d & eye, const Vec3d & at, const Vec3d & up);
void perspective(double fovy, double aspect, double zNear, double zFar);
void frustum(double left, double right, double bottom, double top, double near, double far);
void ortho(double left, double right, double bottom, double top, double near, double far);
void setRenderState(int state, int val);
};
存在问题
多边形边沿部分有锯齿,主要是因为相邻多边形在这里深度都一样,反复覆盖。
截图
如果让我重做一次研究生——王泛森 院士
《如果让我重做一次研究生》——王泛森 院士 http://www.nhlue.edu.tw/~gice/epaper/08/word/edit01.pdf 之前也看过一些类似的文章,这篇写得比较深刻,虽然专科不同,但思想应该是一致的。
ln for windows
Introduction
Linux下有个很好用的小工具 —— ln,可以用它来创建目录或文件的硬链接(hard link)或软链接(symbolic)。所谓硬链接就是一个文件(目录)可以有多个名称,在Windows下不常见。而软链接就是一个指向另一个文件(目录)的符号文件了,类Windows下的快捷方式了,不过我觉得功能更强大些,如Window下就不能cd到一个目录快捷方式中,因为它被看作一个文件(.lnk)。硬链接要求在同一个硬盘分区,只有全部删除才真正删除该文件(目录);软链接可以任意,甚至网络文件(目录),但原文件(目录)删除了,软链接就失效了。使用如下:
ln [-s] TAEGET LINK_NAME
硬链接和软链接都很有用,如可以同一个文件(目录)多处使用,据说Win7下大量使用。可以下面介绍几个Windows下使用硬链接的方法(软链接也就是快捷方式,就不说了,大家都会用; ))。
就罗列下好了,要用的可以 Google 下,或参考 Reference
Windows XP 自带的,方便,可以创建文件和目录的 hard link。使用方法:>fsutil hardlink create NewFilename ExistingFilename
Junction *
第三方工具,只能创建目录的 hard link。使用方法:创建>junction LINK_NAME TARGET
删除>junction -d LINK_NAME
注意:一定要用命令删除,不然连原始原始数据也删除了,这点与 Linux 下的 ln -s 不同。
Win2K Resource Kit也有相关工具 * ,不过没用过。
Tweak
可以把上面 Windows 系统文件硬链接创建方式简单化
@echo off
if [%1] == [] fsutil hardlink create else fsutil hardlink create "%2" "%1"
保存成 lnfile.bat 文件到 PATH 包含的目录下(如X:\Windows\system32),小工具建议 独立保存到一个目录,再把该目录加到 PATH 环境变量下。现在还有一个功能没用实现,就 是跨磁盘分区的文件软链接,只能用快捷方式。
Reference
ln for windows *
Dropbox免费网盘高级使用技巧 *
ln命令详细用法 *
NTFS symbolic link *
OpenCV 源码中的安全指针和指针对齐
OpenCV 2.0
OpenCV 2.0中为很多1.0中 C
语言的数据结构提供了 C++
的类了,考虑到兼容性,保留旧的API。但是凭借C++构造和析造的功能,OpenCV2.0的内存管理方便了许多,使用新API的代码中类似 cvRelease
的代码将不复存在。同时CV2.0也提供了一个安全指针类,让旧的需要手动管理内存的数据结构(如IplImage
, CvMat
等)也可以不用手动释放了,快哉!不过推荐直接用新的API(一些是仿Matlab的),如Mat
,程序可以更简洁、更直观。一个精典的OpenCV的Hello World程序现在可以这样写:
#include <cv.h>
#include <highgui.h>
using namespace cv;
#ifdef _DEBUG
#pragma comment(lib, "cv200d.lib")
#pragma comment(lib, "cxcore200d.lib")
#pragma comment(lib, "highgui200d.lib")
#else
#pragma comment(lib, "cv200.lib")
#pragma comment(lib, "cxcore200.lib")
#pragma comment(lib, "highgui200.lib")
#endif // _DEBUG
const string WIN_NAME = "Lena";
int main(int argc, char* argv[])
{
Mat img = imread("lena.jpg");
namedWindow(WIN_NAME, CV_WINDOW_AUTOSIZE);
imshow(WIN_NAME, img);
waitKey(0);
return 0;
}
安全指针
如果你还是习惯用CV1.0的API,那一定要试试CV2.0的安全指针(也叫智能指针)Ptr
template。据说这个是参考了 C++0x和 Boost 库的相关技术。使用也很简单:
Ptr<IplImage> img = cvReadImage("lena.jpg"); // 不用cvReleaseImage();
我把 Ptr
类从 OpenCV 中独立出来,template class 定义如下:
template<typename Tp> class Ptr
{
public:
Ptr();
Ptr(Tp* obj);
~Ptr();
Ptr(const Ptr& ptr);
Ptr& operator = (const Ptr& ptr);
void addref();
void release();
void deleteobj();
bool empty() const;
Tp* operator -> ();
const _Tp* operator -> () const;
operator _Tp* ();
operator const _Tp() const;
protected:
_Tp obj;
int* refcount;
};
简单地说,就是加了个指针引用数(refcount)和一些方便调用的操作符重装(operator->()
)。值得注意的是,Ptr
template 对指针指向的对象有一个要求,就是可以用delete
操作土符来释放内存。你可能就想到IplImage
就不满足这个要求了,这怎么办?可以使用模板特化(template specialization)重载 Ptr<Iplimage>::delete_obj()
函数:
template<> inline void Ptr<IplImage>::deleteobj()
{
cvReleaseImage(&obj);
}
PS:考虑到多线程时,CV2.0中的一些基本操作(如加法运算CV_ADD
)都写成了函数或宏,保证互斥资源访问安全,看源代码时可注意下。
指针对齐
指针对齐也可以叫作内存地址对齐,主要是考虑到在一些架构上,只有被指定数(如4
)整除的内存地址才可以正常访问,否则程序就会Crash了。CV2.0中的很多指针都是“对齐”过的,如指针的地址都是可以被16
整除。CV2.0的内存主要是通过 malloc
来分配的,返回的内存地址不可能都可以被16
整除,所以要进行对齐操作。那如何对齐,对齐后截断后剩下来的内存怎么维护?
CV2.0的这样维护的:在malloc
是多申请一个指针的空间,这个指针指向malloc
得到的真实内存地址,只在free
时使用它。相关函数有
typedef unsigned char uchar;
#define CVMALLOCALIGN 16
//////////////////////////////////////////////////////////////////////////
template<typename Tp> static inline _Tp* alignPtr(Tp* ptr, int n=(int)sizeof(Tp))
{
return (Tp)(((sizet)ptr + n-1) & -n);
}
_declspec(dllexport) void fastMalloc( sizet size )
{
uchar* udata = (uchar)malloc(size + sizeof(void) + CVMALLOCALIGN);
// ^- HERE 多申请一个 void* 的空间,
// 用于存储 udata
uchar** adata = alignPtr((uchar**)udata + 1, CVMALLOCALIGN);
adata[-1] = udata; // <- 存储 udata
return adata;
}
declspec(dllexport) void fastFree(void* ptr)
{
if(ptr)
{
uchar* udata = ((uchar*)ptr)[-1];
assert(udata < (uchar)ptr &&
((uchar*)ptr - udata) <= (ptrdifft)(sizeof(void*)+CVMALLOCALIGN));
free(udata);
}
}
Reference
- OpenCV2.0 自带的《The Reference Manual)
- What is “Pointer Alignment”
</ol>
Web 3D App
很早之前就了解到一些在Web上开发3D程序相关的技术,如基于flash的3D API,还有 Unity3D ,还有Google的O3D,当时也太在意,只试玩了些Unity3D的Web游戏。因为当时电脑比较挫,Unity3D的程序可以也比常规的3D程序慢(我运行过独立的Unity3D程序,效率还可以),所以印象不深刻。今天上 O3D 官网 逛了下,上面的 Demo 在我的新电脑(NV285)上跑得很顺,也没时间看具体的Develop Guide,就截几个图吧。
注意const Type **引起的Compile Error
问题
这个问题是在写计算机视觉的作业时发现的,使用OpenCV2.0库的C++ API来计算图像直方图,程序如下:
int bins = 256;
int histSize[] = { bins };
float granges[] = {0, 255};
float* ranges[] = { granges };
int channels[] = { 0 };
calcHist(&gray, 1, channels, Mat(), // do not use mask
hist, 1, histSize, ranges<<NOTE HERE>>);
可是提示:
error C2665: 'cv::calcHist' : none of the 2 overloads could convert all the argument types
......
while trying to match the argument list '(cv::Mat *, int, int [1], cv::Mat, cv::MatND, int, int [1], float *[1], bool, bool)'
calcHist 声明有两个
CV_EXPORTS void calcHist( const Mat* images, int nimages,
const int* channels, const Mat&mask,
MatND&hist, int dims, const int* histSize,
const float** ranges, bool uniform=true,
bool accumulate=false );
CV_EXPORTS void calcHist( const Mat* images, int nimages,
const int* channels, const Mat&mask,
SparseMat&hist, int dims, const int* histSize,
const float** ranges, bool uniform=true,
bool accumulate=false );
可是都没法匹配
尝试
对 calcHist 的每个参数进行强制类型转换,luckly,我从后面开始
calcHist(&gray, 1, channels, Mat(), // do not use mask
hist, 1, histSize, (const float**)ranges<<NOTE HERE>>);
这样是可以的,但下面就不行了。
calcHist(&gray, 1, channels, Mat(), // do not use mask
hist, 1, histSize, const_cast<float**>(ranges)<<NOTE HERE>>);
分析
参考《Effective c++》Item21上的做法,如果const位于星号的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;如果const位于星号的右侧,const就是修饰指针本身,即指针本身是常量。 也就是说上面的 const float** ranges 中 ranges 不是 const,ranges[i] 才是const,但这还是没法说明问题。等以后考虑了。。。
参考
为什么 char** 不能自动转化为 const char** 1.
- 《Effective c++》
MSVC环境下的template编译[纠正]
一直以为自己对 template 的理解有问题,考虑到 template 是用到时再实例化,所以不能像一般的 class 那样写,比如这样:
// foo.h
class Foo
{
public:
Foo();
Type bar(Type&);
private:
Type _val;
};
// foo.cpp
#include"foo.h"Foo::Foo()
: _val(0)
{};
Type Foo::bar(Type&val)
{
_val = val;
return _val;
}
这就是所谓的“分离编译模式”了,也就是类的定义和成员函数声明在头文件中,成员函数实现在另一个文件中。这样做的好处很多:
- 代码条理清晰,代码多了也碍眼。
- 包括该模块时,可以隐藏具体实现细节。
- 减少调用者包括的头文件
实现中用到的库文件、头文件,可以不被该模块包含者包含,如实现中用到了 assert,可以只在cpp文件中包括cassert文件。
- 优化编译速度
如只有cpp文件改动头文件没有改动时,调用者不用重新编译;而如果是“包含编译模式”(合在一起)的话,调用者也得重新编译了,也不知道写代码(特别是Debug)时,有什么时间都花在了编译上了。
上面的 class 封装成 template 就成了:
// foo.h
template <class>
class Foo
{
public:
Foo();
Type bar(Type&);
private:
Type _val;
};
// foo.cpp
template <class>
Foo <type>
::Foo()
: _val(0)
{};
template <class>
Type Foo <type>
::bar(Type&val)
{
_val = val;
return _val;
}
一些资料上也提到了 template 的“分离编译模式”,大致有以下这几种:
- export 关键字法
- cpp 实现文件包含法
- 只在头文件中声明(伪分离)
1. export 关键字法
也就是实现文件中的函数定义都加上关键字 export,来声明一个可导出的函数模板。
// model2.h
// 分离模式:只提供模板声明
template <typename>
Type min( Type t1, Type t2 );
// model2.c
// 模板定义
export template <typename>
Type min( Type t1, Type t2 ) {/* . . . */}
使用函数模板min()实例的程序只需在使用该实例之前包含这个头文件:
// user.c
#include"model2.h"int i, j;
double d = min( i, j ); // OK: 用法,需要一个实例
可惜对于类是没法用了。。。
2. cpp 实现文件包含法
也就是在头文件的底部包含cpp文件
// foo.h
#ifndef _FOO_H_
#define _FOO_H_
template <class>
class Foo
{
public:
Foo();
Type bar(Type&);
private:
Type _val;
};
#include"foo.cpp"#endif // _FOO_H_
3. 只在头文件中声明(伪分离)
这个就比较无语了。。。
// foo.h
#ifndef _FOO_H_
#define _FOO_H_
#include"foo.cpp"#endif // _FOO_H_
// foo.cpp
template <class>
class Foo
{
public:
Foo();
Type bar(Type&);
private:
Type _val;
};
template <class>
Foo <type>
::Foo()
: _val(0)
{};
template <class>
Type Foo <type>
::bar(Type&val)
{
_val = val;
return _val;
}
总结:
经过试验,VS2008不支持分离编译模式(前两种),只支持“第三种”,可以忽略了。。
纠正:
2009-11-27 查看 OpenCV2.0 源码后纠正上面的总结,并得到VS2008的“分离编译模式”注意点,解决方法有两种:
- 到实现文件的后缀名改成属于头文件的后缀名,如h,hpp等,这样VS就不会编译实现文件了,也就是不生成obj文件。
- 或者,自定义实现文件的编译方式,右击实现文件-》属性中设置,快捷键ALt + F7,选择自定义,也就是默认不编译。
再次总结:
问题的真正原因往往会被表象所迷惑。
参考:
C++ Template学习笔记之函数模板(5)——模板编译模式 1.
请问在vs2005中类模板的编译采用的是哪种编译模型? 1.
C++ Template: Why do I get unresolved externals with my template code? 1.
Compile error: unexpected class
VS2008错误提示
error C2236: unexpected ‘class’ ‘SkyCloudParticle’. Did you forget a ‘;’?
这是一个令人郁闷的错误提示,提示停在:
class SkyCloudParticle
{ // << HERE
};
分析
怀疑是包含的头文件的类定义有问题,但检查后一切正常。后来终于想到可能是在其它地方 include 这个头文件时,前面的头文件中有 class 定义没写好。经查看所有头类定义,终于发现了问题。问题出现在另一个类(SkyCloudsManager)的定义上,包括关系如下:
// file: SkyCloudsManager.cpp
#include "SkyCloudsManager.hpp"
#include "SkyCloud.hpp"
// file: SkyCloud.hpp
#include "SkyCloudParticle.hpp"
// file: SkyCloudsManager.hpp
class SkyCloudsManager
{
} << DEFINITION ERROR
总结
冷静思考!