这里讲得纹理合成指的是合成图片纹理,也就是二维纹理合成,并不是指将纹理直接合成到三维模型上面。虽然从本质上面来说,三维纹理合成和二维纹理是一样的。
纹理合成基本上是用的非参数化的采样方法,也就是Li-Yi-Wei的那篇纹理合成的论文所使用的方法。基本思路就是为当前要合成的像素在样图中寻找一个最佳的邻域,至于这个邻域的形状是可以变化的。合成像素选取的顺序也是可以变化的。这些或多或少都会影响合成的最终结果。LiYiWei的论文里面选取的是L形状的邻域,并且使用扫描线的顺序。
在LiYiWei的三维纹理合成论文居然又提到了合成顺序对合成结果影响不大,这个好像没那么正确。因为,合成纹理像素的使用,需要匹配邻域,而且这些邻域最好是已经合成的像素,如果不是就不会得到较好的结果。这个是最关键的一点。所以说,合成顺序对合成结果还是有一定的影响的。
为什么L邻域能够得到较好的结果了,那是因为扫描线顺序下,L邻域里面的像素都是已经合成的像素。所以,如果打算对合成纹理算法进行局部的修改就需要注意邻域和合成顺序的设定,必须尽可能使用已经合成的像素。
为什么说三维纹理的合成和二维纹理的合成,本质是一致的了?因为三维纹理合成需要局部铺平,来构造邻域,然后的匹配和二维的思想是完全一致的。
下面是我自己实现代码的合成结果。纹理合成的代码网上还是能够找到的,虽然不是那么好找,这也只限于二维合成的代码。最好不要看csdn上面那个自以为写了加速的代码。
样本纹理:
合成纹理:
使用GDI+和CListCtrl实现缩略图控件
本来这个东西我是用CxImage加载了多种格式的图片,结果今天发现在vs2010的debug模型下有内存溢出,调试了半天才找到是它的原因,所以非常不爽,换了GDI+的实现方法。该控件支持加载多种格式的图片,并且在增加的项内部存储的图片路径。重要的一点是必须在控件的OnDestroy里面释放申请的ItemData,否则退出程序时候还是能检测到内存泄露。这个也是找了半天才找到的。郁闷之。
关于该控件类的具体实现过程,有点复杂。使用到是比较简单,绑定到CListCtrl控件上。用InitCtrl初始化,用AddImage添加。
该类还支持多选和全选,以及删除所有的选择。代码如下:
1 | #pragma once |
1 | // ImageListCtrl.cpp : 实现文件 |
使用GDI+加载多种格式的纹理
今天在做一个缩略图的控件的时候发现,CXImage库在VS2010的debug模型下有内存泄露,这可是我查错查了几个小时最后得出的结论。瞬间觉得很不爽,就不用CxImage了。觉得加载不同种格式的需求,GDI+应该能够满足我的要求。那么久果断改为GDI+了。GDI+我也没使用过,果断只能搜索其用法了。
后面发现只需要使用Gdiplus::Bitmap就可以处理多种格式的图片。虽然该类叫做位图,实际上可以处理多种格式,据说从Gdiplus::Image继承而来。
nsp; 使用GDI+据说在vs2010下也不需要初始化该库,vc6和vs2008下面需要初始化。初始化也很简单,在App里面包含个成员ULONG_PTR m_gdiplusToken,在InitInstance里面加上Gdiplus::GdiplusStartupInput gdiplusStartupInput;Gdiplus::GdiplusStartup(m_gdiplusToken, gdiplusStartupInput, NULL);在ExitInstance里面加上Gdiplus::GdiplusShutdown(m_gdiplusToken);就行了。
由于GDI+使用的是Unicode,而我一般用的是多字节工程,所以还需要将图片名字转换为Unicode。具体过程是先把图片名字转换为Unicode,再用Gdiplus::Bitmap类的对象bitmap加载图片,然后锁定图片数据,申请一块数据从bitmap内部提取出像素数据,然后释放图片数据,剩下的就是ogl的操作了。
代码如下:
1 | #ifndef XPC_YX_TEXTURE_H |
1 | #include "stdafx.h" |
现在,在vs2010和vs2008下,不需要使用额外的库就能处理多种格式的纹理了。下面是一个贴图效果,用的是一维纹理显示了sdf值在模型顶点上的分布。
OpenGL使用多种格式和任意尺寸的图片作为纹理
处理多种格式本身就是件麻烦的事情。但是使用cximage的话,就可以完美解决这个问题了。cximage可以处理多种常见格式的图片,比如bmp,png,jpg等。
那么如何使用任意尺寸的图片了。glTexImage2D必须使用长宽二次方的图片,使用非二次方的图片,必须用glTexSubImage2D进行处理,具体方法,可以参考 OpenGL使用长宽非二次方纹理 。
那么如何避免使用glTexSubImage2D了。我们可以使用函数gluBuild2DMipmaps来完成以上两个函数完成的工作,还不需要对纹理坐标进行放缩。这是一件非常方便的事情。
下面提供我的纹理类。使用方法很简单。先调用加载图片函数,再调用初始化纹理函数,可以选择二维和一维垂直方向的。然后,在绘制的时候使用纹理就行了。
1 | #ifndef XPC_YX_TEXTURE_H |
1 | #include "stdafx.h" |
要使用该类,还得配置CxImage。下面这张图片是使用一维垂直纹理的效果。
PaintMeshCutting的实现
这篇论文讲的是交互式模型分割。用户用画笔在模型上面绘制出一笔,然后获得相关的分割区域。
这篇论文的思路大致分为三步。第一步,对所有顶点或者面计算sdf值。第二步,将笔画投影作为前景,估计一个混合高斯模型,另外随机选取1000个顶点或面估计一个高斯混合模型。第三步,利用sdf值和高斯混合模型,进行graph-cut。
关于sdf值和graph-cut都可以阅读论文引用的文章从而得到知晓。sdf值的计算可以到提出该观点的作者主页上面下载相关软件。但是该软件只能计算面的sdf值,不过可以再映射到顶点上面。graph-cut的应用更为广泛,这个也能到Yuri Y. Boykov主页上下载相关代码,然后使用即可。
更困难的事情是确定graph-cut的参数,这个需要多次调整。
下面是实现效果。
使用CGAL实现简单的模型分割
第一步,先把鼠标轨迹点逆投影到三维世界的观察坐标系。
第二步,把第一步获得的三维坐标点集合投射到模型上面,获得笔画,就是模型内部的一些相互连接的边。
第三步,沿着第二步获得那些相互连接的边进行欧拉操作分裂边。可能需要多分割几次才会分割出模型的一部分。
第四步,搜索整个模型,对不同的部分进行标记。
下面是实现的一些效果。绿色线是鼠标轨迹,红色线是投影到模型上面的边的连接线。
下面具体解释下以上几个步骤。
步骤一,比较简单,网上代码一堆,不过要自己注意理解。
1 | glGetIntegerv(GL_VIEWPORT, m_nViewPort); |
该代码就是把m_ptStroke的点转化为观察坐标系的点m_pt3ds。
步骤二,大致思路如下:第一步,把所有鼠标经过的点,都投影回观察空间。第二步,找到离第一个鼠标点p[0],最近的模型内的顶点pClosed。第三步,循环剩余的鼠标点,如果当前鼠标点离pClosed的相邻顶点更近的话,那么将pClosed更新为上一次找到的pClosed的相邻顶点。所有的这些,pClosed点就是笔画的投影点。
1 | FILE* fpLog = fopen("log.txt", "w"); |
步骤三是欧拉操作。我的思路是将经过的边都变成边界边,方便第四步的搜索。首先,split_edge。然后,把沿着边的相邻的两个面沿着原来的边split_facet,再删除多余的面。那么,该经过的边就会变成边界边。代码如下:
1 | for (int i = 1; i < vh.size(); ++i) |
步骤五,对整个模型的面进行dfs即可了。代码如下:
1 | void SetComponent() |
因为每个面里面都有个代表属于哪个组件的标记,这样就实现了模型的分割。利用该方法就能实现分割出狗的腿和头部等等。
代码分享链接:链接: http://pan.baidu.com/s/1eQ50rf0 密码: 7f6v
使用CGAL实现平面分割三维模型
关于cgal的使用就不介绍了。安装了也很麻烦,但是可以直接使用库,可以去我的上一篇文章下载,cgal下载。
这里只简单的说说实现切割模型的方法。
判断所有的顶点在面的哪一面,如果一个面的所有点都在切割面的正面,那么这个面就是在切割面的positive部分,反之,如果一个面的所有点都在切割面的负面,那么这个面就是在切割面的negative部分,其余的面就是和切割面相交了。根据这个简单的原理,我们就可以用切割面把模型分为三部分。以下是我用cgal实现的效果。
从图片来看,效果基本还行,只是切割处不光滑,但是对于这么简单的算法能实现的效果,已经不错了,性价比很高。
如果想进一步平滑分割处,有什么办法了?我们既然已经把模型内部的面分成三部分了,那么我们只需要计算会和切割面相交的面和切割面的交点,面和面的交点应该是一条直线,那么应该有2个交点,我们再沿着这条相交线,对模型内部的面进行分割就行了。如果分割后出现非三角面,我们还可以进行三角面片化。具体的实现也不会难如上青天。
分享下我的OpenGL,CGAL,Boost库
作为在学习图形学的苦逼,刚开始弄这些库也不简单。即使会弄,也麻烦得要死。装cgal就得去找教程,按照教程安装。
但是如果有安装好的库之后,只需要配置vs的路径就行了。而且库可以带走,不用多次安装了。我还是有一点分享精神的,我就把我的库都分享出来吧。
CGAL是4.1的,Boost是1.47的,OpenGL里面有glew,还有个freeimage,不过没有glm。glm可以去opengl超级宝典的源代码里面找,不过感觉不是很需要glm,即使需要几何计算,还不如找成熟的几何计算的类。
由于这篇文章访问量较大,而且不少同学留言问过我链接失效的事情,因此再次更新百度云盘的分享链接:
点此下载,密码:15j6。
VS2010下使用glui
编译一个程序的时候发现glui一直链接不上去,也许是我的glui版本太低了,结果到最后发现作者设置了lib的附加路径。也许glui2.35真的不能在vs2010下使用了。我下载过好几次,都还是发现glui2.36编译不过去,最后把一个模板实例化移到全局命名空间里面才编译过去了。这里打算讲一下怎么编译对应开发平台下的代码库和编译glui2.36。
我下载过cximage,opencv2.2,cgal,glui等开发库,有些开发库可能一下载下来就有头文件和lib,dll了。但是大多的是下载下来一个工程。而且,这个工程的原始环境还可能和你所用的环境不一样。比如,你下载下来的是vs2005或者vs2008的工程,但是你的环境是vs2010。如果,你在vs2010下面用别人用vs2005编译好的lib就可以链接不成功,这个时候你就需要用vs2010打开工程,重新编译vs2010下的lib。一般情况是打开包含所有工程的workspace,然后点击批生成,把所有需要的lib都生成出来。具体步骤就不用细说了。
但是还是可能遇到其它问题,比如说glui2.36就是编译不过去,因为出现了语法问题。那么就需要发挥你的聪明才智了,经验越多,语法了解的越多,就能够编译过去。我这里再上传,我修改过的glui.236的工程,里面已经包含vs2010下面的各种lib和dll了,放心使用吧。工程和lib,dll都在msvc目录下面,头文件在include里面。
glui2.36
使用VB.NET操作二进制文件以序列化对象集合
如果是用c,c++这当然是相当熟悉的操作了,也不需要做个记录了。但是现在用的是vs2010版本的vb.net。而现在我有一个大对象,里面存储了整形,浮点,字符串,还有图片。我想将数据归档到文件里面,还有从文件中恢复出来。所以,只能使用二进制的读写方法,顺便记录图片的长度。至于要不要记录字符串的长度,只能问VB.NET了。
读取文件的使用BinaryReader,写文件的使用BinaryWriter。这2个对.net的string也支持二进制读写,所以只需要存储图片的长度了。图片本身当做byte[]存储。这2个类在Imports System.IO空间,都需要从FileStream创建。
虽说对VB.NET不熟悉,但是实现之后还是发现这个东西很好用的。以后还可以用来参考下。
存储的对象的类定义如下,
1 | Imports System.IO |
对象类的集合类定义如下。该类支持归档到文件,从文件恢复,查找,添加,删除操作。
1 | Imports System.IO |
上面的集合类里面定义了个大数组,是因为我对.NET不熟悉,不知道这样的对象数组定义,产生的是对象集合,还是虚的引用集合。我觉得是引用集合就不用声明数据的大小了吧,结果还是需要声明。而且,读取文件的时候还需要给每个数据项new一个Project。反正不清楚为什么是这样的原因,因为对.NET真的一点不熟悉。