Win32 OpenGL编程(16) 纹理贴图

系统 1860 0

write by 九天雁翎(JTianLing) -- blog.csdn.net/vagrxie

讨论新闻组及文件

好久好久没有继续OpenGL了...中间发生了太多事情,比如Objective C及Cocoa的学习, 粗略看了一些游戏引擎的源代码,Google离开了,一个公司可以很有骨气的说走就走,暂时没有办法离开的人,该继续的还是得继续,现在回过神来,还是留 点时间来学OpenGL吧,不过作为工作需要,我以后可能会常常附带OpenGL ES的信息,甚至,不是OpenGL ES可用的我就一笔带过了,毕竟,我的工作现在是在IPhone平台......


概念

上一节讲过了在OpenGL中位图的显示,但是,那都是简单的将图片贴出来,放在平面上的效果,这一节解释一下怎么在3D物体上应用位图,这个过程被称作 纹理贴图(Texture Mapping),想想以前的三棱锥吧,最好最好的时候,我们也仅仅是实现了渐变颜色或者是光照,那样太单调了。既然是金字塔型,我们自然希望它想真的金 字塔一样,有着岩石的外表,就像经过了岁月的沧桑。
实际中,在制作游戏的时候中,要想某个物体想现实世界的物体,基本上不太可能完全通过程序的顶点色去实现,那样太复杂了,现实世界应该怎样描绘,美术比 程序员更加清楚,更重要的是,美术不仅知道怎么描述这个世界,还比程序员更加知道怎么从现实中获取素材,及处理素材,程序描绘世界最好的办法应该是很好的 将美术制作的图片资源恰当的显示出来,而不是真的编写程序去描绘每个顶点的颜色,我们是程序员,不是上帝.........

概念上,纹理贴图的英文更加能够反映其实质,Texture Mapping,注意Mapping一词,C++中的map没有少用吧,这里是映射的意思,纹理贴图本质就是一个位图(即纹理)中像素到某个立体图形的映 射过程,也有纹理映射,材质贴图等多种翻译.


2D纹理贴图

首先,需要说明的是,纹理贴图的应用实在太广,内容实在太多,我根本没有办法简简单单一篇文章就覆盖全部的知识,甚至是常用的部分都很难,这里只能大概 的讲些基础概念及给出几个例子了,相对来说,比NEHE的例子还是要多一点。(NEHE每个知识一个例子,而且没有任何原理讲解)

这里先 提供一个简单的2D对2D的例子.

//OpenGL 初始化开始
void SceneInit( int w, int h)
{
GLenum err = glewInit();
if (err != GLEW_OK)
{
MessageBox( NULL , _T( "Error" ), _T( "Glew init failed." ), MB_OK);
exit(- 1 );
}


glClearColor ( 0.0 , 0.0 , 0.0 , 0.0 );
glShadeModel(GL_FLAT);
glEnable(GL_DEPTH_TEST);

HBITMAP hBmp=(HBITMAP)LoadImageW( NULL , L"tiger.bmp" , IMAGE_BITMAP, 0 , 0 , LR_CREATEDIBSECTION | LR_LOADFROMFILE );

if (!hBmp)
{
exit( 3 );
}

GetObject(hBmp, sizeof (gBmp), &gBmp);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4 );
//glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

glGenTextures( 1 , &gTexName);
glBindTexture(GL_TEXTURE_2D, gTexName);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0 , GL_RGBA, gBmp.bmWidth, gBmp.bmHeight,
0 , GL_BGR, GL_UNSIGNED_BYTE, gBmp.bmBits);
}

//这里进行所有的绘图工作
void SceneShow(GLvoid)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_TEXTURE_2D);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
glBindTexture(GL_TEXTURE_2D, gTexName);

glBegin(GL_QUADS);
glTexCoord2f( 0.0 , 0.0 ); glVertex3f(- 1.0 , - 1.0 , 0.0 );
glTexCoord2f( 1.0 , 0.0 ); glVertex3f( 1.0 , - 1.0 , 0.0 );
glTexCoord2f( 1.0 , 1.0 ); glVertex3f( 1.0 , 1.0 , 0.0 );
glTexCoord2f( 0.0 , 1.0 ); glVertex3f(- 1.0 , 1.0 , 0.0 );

glEnd();
SwapBuffers(ghDC);
glDisable(GL_TEXTURE_2D);
}

LoadImage 的使用参看上一节讲位图显示的内容:《 Win32 OpenGL编程(15) 位图显示

glGenTextures用于生成一 个材质的ID,就像OpenGL的显示列表的用法。

glBindTexture 为刚创建的纹理(以纹理ID表示)绑定一个纹理,这里的参数表示是2D的纹理,事实上,纹理可以使1维的,3维的,甚至4维的。

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
4句确定了此2D纹理映射的一些参数,参数实在太多,要都解释,估计抵 的上一本书的一章了。
GL_TEXTURE_WRAP *的参数用于指定当纹 理的顶点指定超过范围时的做法,这里的做法就是重复(REPEAT)。类似于设置桌面时,图小于桌面分辨率的平铺效果。(哦?MS显示桌面的方式难道也是 用纹理贴图?) GL_TEXTURE_MIN_FILTER, GL_TEXTURE_MAG_FILTER用于指定缩小方法时的计算的方法, 虽然都叫FILTER, 但是事实上放大时相当于插值。这里用的是最近点的方式,最常用的还有线性方式。一般来说,线程方式会有更大的消耗,更好的质量。《OpenGL SUPERBIBLE》说线性方式在现代的显卡中的性能弱点可以忽略不计,并且其优点值得使用。从字面上很好理解最近点方式,一般取纹理中心的像素单元放 大缩小,可能导致严重锯齿。线性方式,则是取每几个临近像素点进行加权平均,决定新像素点的值,详细了解。。。看看数字图像处理的书?印象中大学的数字图 像处理教程就有比较详细的解释。

glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
确定纹理的应用方法,说实话,我不太喜欢OpenGL中这种组合参数API,
通过几个参数组合的方式来决定某个特性, 查资料太费 神,左看右看,半天都不明白自己需要的一个简单功能为啥弄的这么麻烦。
此句的意思建议去看看
OpenGL Reference Manual 》的 解释,希望你能在10个小时内看明白那5,6个10多行的表格表示的3个参数的组合表示什么意思-_-!对了,忘了说,某些时候,还需要组合多次 glTexEnv*调用来表示一个操作内容-_-! 再看看下面的glTexImage2D函数,红宝 书用了整整一面多的篇幅,列举其参数的可能值,不过,有人能看得懂吗?
想起来某年某月某日,
某人问我:"OpenGL与D3D孰好?"
我 答曰:"或D3D否?"
其责之:"何好之有?"
我叹之:“OpenGL神设计之,唯神知其所用。"
这是题外话。。。。感叹一下, 想起了Qt设计者关于API设计哲学的论述,(参看< 设计 qt 风格的c++api【转】 >) 尤为感叹。勿怪。
那这句程序到底是啥意思呢?。。。。。。。。。。我说我没有看懂,你不会怪我吧?基本上, GL_DECAL在材质没有Alpha通道时,相当于 GL_REPLACE,(用材质替代原来物体的颜 色)有Alpha通道时,相当于GL_BLEND,(用材质与原有物体混合) 我有点语无伦次了。。。。不 过想想 OpenGL Reference Manual 》, 《OpenGL 编程指南》几万个字都没有讲清楚,那么我几十个字没有讲清楚也就不奇怪了。 )《OpenGL 编程指南》如是说:纹理贴图是个相当大的主题,并且具有相当程序的复杂性.
基本上,就当这 些都是一坨配置吧.
下图是出来的效果:


这样的效果像什么?就像是上一节讲到的位图显示吧?呵 呵,的确,我也有这样的感叹,事实上,这才是OpenGL中显示图形的最佳方式.....而在OpenGL ES中,前面那些接口甚至都不存在........这里,我通过irrlicht的 draw2DImage 函 数可以获得印证.以下是Irrlicht中的一个 draw2DImage 函数的具体实现:

void COpenGLDriver::draw2DImage( const video::ITexture* texture, const core::rect<s32>& destRect,
const core::rect<s32>& sourceRect, const core::rect<s32>* clipRect,
const video::SColor* const colors, bool useAlphaChannelOfTexture)
{
if (!texture)
return ;

// texcoords need to be flipped horizontally for RTTs
const bool isRTT = texture->isRenderTarget();
const core::dimension2d<u32>& ss = texture->getOriginalSize();
const f32 invW = 1.f / static_cast <f32>(ss.Width);
const f32 invH = 1.f / static_cast <f32>(ss.Height);
const core::rect<f32> tcoords(
sourceRect.UpperLeftCorner.X * invW,
(isRTT?sourceRect.LowerRightCorner.Y:sourceRect.UpperLeftCorner.Y) * invH,
sourceRect.LowerRightCorner.X * invW,
(isRTT?sourceRect.UpperLeftCorner.Y:sourceRect.LowerRightCorner.Y) *invH);

const video::SColor temp[ 4 ] =
{
0xFFFFFFFF ,
0xFFFFFFFF ,
0xFFFFFFFF ,
0xFFFFFFFF
};

const video::SColor* const useColor = colors ? colors : temp;

disableTextures( 1 );
setActiveTexture( 0 , texture);
setRenderStates2DMode(useColor[ 0 ].getAlpha()< 255 || useColor[ 1 ].getAlpha()< 255 ||
useColor[ 2 ].getAlpha()< 255 || useColor[ 3 ].getAlpha()< 255 ,
true , useAlphaChannelOfTexture);

if (clipRect)
{
if (!clipRect->isValid())
return ;

glEnable(GL_SCISSOR_TEST);
const core::dimension2d<u32>& renderTargetSize = getCurrentRenderTargetSize();
glScissor(clipRect->UpperLeftCorner.X, renderTargetSize.Height-clipRect->LowerRightCorner.Y,
clipRect->getWidth(), clipRect->getHeight());
}

glBegin(GL_QUADS);

glColor4ub(useColor[ 0 ].getRed(), useColor[ 0 ].getGreen(), useColor[ 0 ].getBlue(), useColor[ 0 ].getAlpha());
glTexCoord2f(tcoords.UpperLeftCorner.X, tcoords.UpperLeftCorner.Y);
glVertex2f(GLfloat(destRect.UpperLeftCorner.X), GLfloat(destRect.UpperLeftCorner.Y));

glColor4ub(useColor[ 3 ].getRed(), useColor[ 3 ].getGreen(), useColor[ 3 ].getBlue(), useColor[ 3 ].getAlpha());
glTexCoord2f(tcoords.LowerRightCorner.X, tcoords.UpperLeftCorner.Y);
glVertex2f(GLfloat(destRect.LowerRightCorner.X), GLfloat(destRect.UpperLeftCorner.Y));

glColor4ub(useColor[ 2 ].getRed(), useColor[ 2 ].getGreen(), useColor[ 2 ].getBlue(), useColor[ 2 ].getAlpha());
glTexCoord2f(tcoords.LowerRightCorner.X, tcoords.LowerRightCorner.Y);
glVertex2f(GLfloat(destRect.LowerRightCorner.X), GLfloat(destRect.LowerRightCorner.Y));

glColor4ub(useColor[ 1 ].getRed(), useColor[ 1 ].getGreen(), useColor[ 1 ].getBlue(), useColor[ 1 ].getAlpha());
glTexCoord2f(tcoords.UpperLeftCorner.X, tcoords.LowerRightCorner.Y);
glVertex2f(GLfloat(destRect.UpperLeftCorner.X), GLfloat(destRect.LowerRightCorner.Y));

glEnd();

if (clipRect)
glDisable(GL_SCISSOR_TEST);
}

除了剪裁测试,还有其对材质进行了 一层封装,核心内容(从glBegin到glEnd间的部分)就是上述纹理贴图的内容.讲到这里,疑问来了,在OpenGL中,我们到底应该使用哪种方式 来进行位图的显示呢?前面讲过的方法明显要更加简单,纹理贴图的方法明显要更绕,但是适用范围更广.那么,我想,先看看效率吧,同样的图的显示,我测试一 下此节程序及上节程序的最大帧率.
加入下述FPS测试代码,并且通过wglSwapIntervalEXT(0);关闭垂直同步(默认是开启垂直 同步的).(为了相对公平,原glbitmap的程序也改成了双缓冲,新的程序关闭了深度测试)
// called every frame
int CalculateFPS(DWORD now)
{
static int frameCounted = 0;
static int startTime = GetTickCount();
static int fps = 0;
++frameCounted;

int elapsed = now - startTime;

if (elapsed >= 1500 )
{
fps = ( 1000 * frameCounted ) / elapsed;
startTime = now;
frameCounted = 0;
}

return fps;
}

// calculate the fps and display it in the window captain
void DisplayFPS(DWORD now)
{
static char buffer[100];
int fps = CalculateFPS(now);

sprintf(buffer, "fps=%d", fps);
SetWindowTextA(ghWnd, buffer);
}

测试结果是glBitmap版本大概是950~970之 间.而纹理贴图版本只有930到950之间,也就是说,glBitmap版本在单纯的图片显示上,效率还是有优势的,毕竟,OpenGL提供的特殊接口不 可能比通用实现方式要慢(最坏也要一样,因为特殊接口起码还能用通用实现方式实现,不然写驱动的都吃白饭了)但是,就如前面所说的,纹理贴图方式适用范围 更广,OpenGL ES中,也只能使用此方式,另外,"使用纹理贴图方式,还能利用上很多3D的特效,因为,只要使用OpenGL写引擎的,几乎都用的纹理贴图的方式." (我同事的原话)


2D纹理,3D图形

无论如何,OpenGL是为3D而生的,那么光讲2D那就相当于没有讲 OpenGL,这里,提供一个2D纹理映射到3D图形的例子.例子来自于 《Nehe OpenGL Tutorials》( 本教程位置 ),因为贴图位置的设置太繁琐了,不想自己再写一个了.例子经过改造,嵌入了 Win32的框架中,然后,显示的还是上面的老虎.


//OpenGL初始化开始
void SceneInit( int w, int h)
{
GLenum err = glewInit();
if (err != GLEW_OK)
{
MessageBox( NULL , _T( "Error" ), _T( "Glew init failed." ), MB_OK);
exit(- 1 );
}

wglSwapIntervalEXT( 0 );

glClearColor ( 0.0 , 0.0 , 0.0 , 0.0 );
glShadeModel(GL_FLAT);

glViewport( 0 , 0 ,WIDTH,HEIGHT); // Reset The Current Viewport

glMatrixMode(GL_PROJECTION); // Select The Projection Matrix
glLoadIdentity(); // Reset The Projection Matrix

// Calculate The Aspect Ratio Of The Window
gluPerspective( 45.0f ,(GLfloat)WIDTH/(GLfloat)HEIGHT, 0.1f , 100.0f );

glMatrixMode(GL_MODELVIEW); // Select The Modelview Matrix
glLoadIdentity(); // Reset The Modelview Matrix


HBITMAP hBmp=(HBITMAP)LoadImageW( NULL , L"tiger.bmp" , IMAGE_BITMAP, 0 , 0 , LR_CREATEDIBSECTION | LR_LOADFROMFILE );

if (!hBmp)
{
exit( 3 );
}

GetObject(hBmp, sizeof (gBmp), &gBmp);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4 );

glGenTextures( 1 , &gTexName);
glBindTexture(GL_TEXTURE_2D, gTexName);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0 , GL_RGBA, gBmp.bmWidth, gBmp.bmHeight,
0 , GL_BGR, GL_UNSIGNED_BYTE, gBmp.bmBits);

glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);


glEnable(GL_TEXTURE_2D); // Enable Texture Mapping ( NEW )
glShadeModel(GL_SMOOTH); // Enable Smooth Shading
glClearColor( 0.0f , 0.0f , 0.0f , 0.5f ); // Black Background
glClearDepth( 1.0f ); // Depth Buffer Setup
glEnable(GL_DEPTH_TEST); // Enables Depth Testing
glDepthFunc(GL_LEQUAL); // The Type Of Depth Testing To Do
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Really Nice Perspective Calculations
}

//这里进行所有的绘图工作
void SceneShow(GLvoid)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear The Screen And The Depth Buffer
glLoadIdentity(); // Reset The View
glTranslatef( 0.0f , 0.0f ,- 5.0f );

glRotatef(xrot, 1.0f , 0.0f , 0.0f );
glRotatef(yrot, 0.0f , 1.0f , 0.0f );
glRotatef(zrot, 0.0f , 0.0f , 1.0f );

glBindTexture(GL_TEXTURE_2D, gTexName);

glBegin(GL_QUADS);
// Front Face
glTexCoord2f( 0.0f , 0.0f ); glVertex3f(- 1.0f , - 1.0f , 1.0f );
glTexCoord2f( 1.0f , 0.0f ); glVertex3f( 1.0f , - 1.0f , 1.0f );
glTexCoord2f( 1.0f , 1.0f ); glVertex3f( 1.0f , 1.0f , 1.0f );
glTexCoord2f( 0.0f , 1.0f ); glVertex3f(- 1.0f , 1.0f , 1.0f );
// Back Face
glTexCoord2f( 1.0f , 0.0f ); glVertex3f(- 1.0f , - 1.0f , - 1.0f );
glTexCoord2f( 1.0f , 1.0f ); glVertex3f(- 1.0f , 1.0f , - 1.0f );
glTexCoord2f( 0.0f , 1.0f ); glVertex3f( 1.0f , 1.0f , - 1.0f );
glTexCoord2f( 0.0f , 0.0f ); glVertex3f( 1.0f , - 1.0f , - 1.0f );
// Top Face
glTexCoord2f( 0.0f , 1.0f ); glVertex3f(- 1.0f , 1.0f , - 1.0f );
glTexCoord2f( 0.0f , 0.0f ); glVertex3f(- 1.0f , 1.0f , 1.0f );
glTexCoord2f( 1.0f , 0.0f ); glVertex3f( 1.0f , 1.0f , 1.0f );
glTexCoord2f( 1.0f , 1.0f ); glVertex3f( 1.0f , 1.0f , - 1.0f );
// Bottom Face
glTexCoord2f( 1.0f , 1.0f ); glVertex3f(- 1.0f , - 1.0f , - 1.0f );
glTexCoord2f( 0.0f , 1.0f ); glVertex3f( 1.0f , - 1.0f , - 1.0f );
glTexCoord2f( 0.0f , 0.0f ); glVertex3f( 1.0f , - 1.0f , 1.0f );
glTexCoord2f( 1.0f , 0.0f ); glVertex3f(- 1.0f , - 1.0f , 1.0f );
// Right face
glTexCoord2f( 1.0f , 0.0f ); glVertex3f( 1.0f , - 1.0f , - 1.0f );
glTexCoord2f( 1.0f , 1.0f ); glVertex3f( 1.0f , 1.0f , - 1.0f );
glTexCoord2f( 0.0f , 1.0f ); glVertex3f( 1.0f , 1.0f , 1.0f );
glTexCoord2f( 0.0f , 0.0f ); glVertex3f( 1.0f , - 1.0f , 1.0f );
// Left Face
glTexCoord2f( 0.0f , 0.0f ); glVertex3f(- 1.0f , - 1.0f , - 1.0f );
glTexCoord2f( 1.0f , 0.0f ); glVertex3f(- 1.0f , - 1.0f , 1.0f );
glTexCoord2f( 1.0f , 1.0f ); glVertex3f(- 1.0f , 1.0f , 1.0f );
glTexCoord2f( 0.0f , 1.0f ); glVertex3f(- 1.0f , 1.0f , - 1.0f );
glEnd();

xrot+= 0.3f ;
yrot+= 0.2f ;
zrot+= 0.4f ;

SwapBuffers(ghDC);
}
显 示效果:



基本的意思还是和上面没有区别,最最主要的部 分为顶点的设置部分,会发现,将2D纹理映射到2D及映射到3D的区别并不大,2D纹理的位置指定还是按照一个一个面的来.这里,只不过以前2D时是仅有 一个面,现在面多了而已,方法还是一样.另外,深度测试还是开启为好,不然会乱作一团.

本文的完整源代码在代码库中的2010-3-25目录下


参考资料

1. 《 OpenGL Reference Manual 》,OpenGL 参考手册

2. 《OpenGL 编程指南》(《 OpenGL Programming Guide 》),Dave Shreiner,Mason Woo,Jackie Neider,Tom Davis 著,徐波译,机械工业出版社

3. 《Nehe OpenGL Tutorials》,Nehe著,在 http://nehe.gamedev.net/ 上可以找到教程及相关的代码下载,(有PDF版本教程下载)Nehe自己还做了一个面向对象的框架,作为演示程序来说,这样的框架非常合适。也有 中文版 ,各取所需吧。

4.《OpenGL SUPERBIBLE》Fourth Edition,Comprehensive Tutorial and Reference,Richard S.Wright, Jr.,Benjamin Lipchak, Nicholas Haemel. Addison-Wesley


完整源代码获取说明

由于 篇幅限制,本文一般仅贴出代码的主要关心的部分,代码带工程(或者makefile)完整版(如果有的话)都能用Mercurial在Google Code中下载。文章以博文发表的日期分目录存放,请直接使用Mercurial克隆下库:

https://blog-sample-code.jtianling.googlecode.com/hg/

Mercurial使用方法见《 分布式的,新一代版本控制系统 Mercurial的介绍及简要入门

要是仅仅想浏览全部代码也 可以直接到google code上去看,在下面的地址:

http://code.google.com/p/jtianling/source/browse?repo=blog-sample-code

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- blog.csdn.net/vagrxie

Win32 OpenGL编程(16) 纹理贴图


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论