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
原创文章作者保留版权 转载请注明原作者 并给出链接