bilinear:二次插值,精度更高,但需要自己动手计算。
对于复杂的物体表面来说逐一指定其纹理坐标是相当烦琐的事,所以OpenGL支持纹理坐标自动生成。可用glTexGen命令开关。详情见手册或联机帮助。
注意:OpenGL1.2还支持GL_TEXTURE_3D,在低版本OpenGL中三维纹理则是一个展扩。
以下代码是展示了完整的贴纹理过程:
//----纹理尺寸----------#define TEXW 64#define TEXH 64byte tex[TEXW][TEXH][3];//----生成纹理数据------int i,j;//----定义纹理--------- glPixelStorei(GL_UNPACK_ALIGNMENT,1); glTexImage2D(GL_TEXTURE_2D,0,3,TEXW,TEXH,0,GL_RGB,GL_UNSIGNED_BYTE,tex); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT); glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);//----打开纹理计算----- glEnable(GL_TEXTURE_2D);//----使用纹理---------- glBegin(GL_QUADS); glTexCoord2f(-1,-1); glVertex3f(-1,-1,0); glTexCoord2f(-1,1); glVertex3f(-1,1,0); glTexCoord2f(1,1); glVertex3f(1,1,0); glTexCoord2f(1,-1); glVertex3f(1,-1,0); glEnd();
源文档 <http://www.cnblogs.com/yxnchinahlj/archive/2010/11/23/1885233.html>
OpenGL纹理贴图
纹理映射是将指定图像的一部分映射到允许进行纹理映射的每个图段上。这种映射伴随着使用一幅图像的颜色到某一图段的(s,t,r)坐标所指示的位置上并修改该图段的RGBA颜色。但要特别注意的是,在OpenGL中,纹理映射仅在RGBA模式下说明,在颜色索引模式下,它的使用未作定义。概括地说,使用纹理绘制的一般步骤为:定义纹理贴图、控制纹理、说明纹理贴图方式,定义纹理坐标等。
2.1 定义纹理
纹理的定义有两种:连续法和离散法。连续法把纹理定义为一个二元函数,函数的定义域就是纹理空间。而离散法则是把纹理定义在一个二维数组中,该数组表示纹理空间中行间隔和列间隔固定的一组网格点上的纹理值。网格点之间的其它点的纹理值可以通过对相邻网格点上纹理值进行插值来获得。通过纹理空间与物体空间之间的坐标变换,可以把纹理映射到物体表面。一般来说,离散法是较为常用的纹理定义方法。其实现函数为
glTexlmage2D()。该函数的原型如下:void glTexImage2D(Gl_enum target,GLint level,Gl_enum compo—nents, GLsizei width, GLsizei height, Glint border,Gl_enumformat。Gl_enumtype,const GLvoid pixels);其中:target指定纹理映射,此处必须是GL—TEXT—URE 2D;level指定纹理图像分辨率的级数,当只
有一种分辨率时,level=0;Components是选择用于调整和混合的成分;width和height分别指定纹理图像的宽和高,必须是2 ,凡为正数;Border为边界的宽度,必须是0和1;format和type分别指定纹理映射的格式和数据类型;Pixels指定一个指针,指向纹理数据在内存中的位置。
2.2 控制纹理
纹理图像在映射到物体时会产生许多问题。这些问题主要有纹理图像的纹理怎样对应到屏幕上的像素、怎样通过纹理贴图实现纹理缩放和纹理重复等。其实现函数为glTexParmneter(),该函数的原型(以glTexParmneterf形式为例)为:void glTexPa—rmneterf(GLeRuin target,GLeRuin pname,GLfloat pa—ram),其中target参数为目标纹理,pname参数的取值有以下几个:GL TEXTURE MIN FILTER、GL,ⅡⅨ TURE—MAG一兀I肛R、GL—TEXrrI yRE— WRAP一GL— TEXTU RE —WRAP— T,而parmn参数的取值要根据pname而定。
2.3 说明纹理贴图方式
OpenGL用于纹理贴图方式的函数为glTex~v(),该函数的原型(以glTexEnvf形式为例)为:voidglTexEnv(Gl_enum target,Gl_enum pname,GLfloat pa—ram)其中target参数必须为GL—TEXTURE —ENV,pname参数必须为GL—TEXTURE —ENV—MODE,而参数parmn为GL—MODULATE 、GL—DECAL或GL—BLEND。
2.4 定义纹理坐标
纹理坐标控制纹理图像中的像素怎样映射到物体。纹理坐标可以是1、2、3、4维的,通常用齐次坐标来表示,即(5,t,r,q)。OpenGL定义纹理坐标的函数为 xCoord()。该函数共有32种不同的形式。例如:glTexCoord4f(O.Of,0.Of,0.Of,0.Of)。
3 基于MFC的OpenGL中的纹理贴图利用VC++的MFC AppWizard(exe)建立一个新项目OpenglTexture,选择基于单文档界面的应用,其它选项都使用缺省值。在OpenglTextureView.h头文件中,添加各成员变量和成员函数。
for(i=0;i<128;i++)
{
for(j=0;j<64;j++)
{
c=(((i&Ox08)==0) ((j ))==0)*255;
g=(4*i)%255;
b:(j*i)%255;
imag~Ei儿j][0]=(GIaxbyte)b;
image[i儿J儿1]=(GIaxbyte)g;
image~i][j][2]=(GIaibyte)c;
}
}
glPixelStorei(GL—UNPACK— ALIGNMENT,2);
glTexImage2D(GL—TEXTURE一2D,0,3,64,64 ,0,GL— RGB,GL—UNSIGNED— BYTE,image);
//定义纹理
glTexParameteri(GL— TEXTURE一2D,GL— TEXTURE — W RAP— S,
GL— CLAMP);
//控制纹理
glTexParameteri(GL—
TEXTURE一2D,GL— TE XTURE — WRAP—T,GL— CLAMP);
glTexParameteri(GL—TEXTURE一2D,GL—TEXTURE—MAG—FIL—E R,GL— NEAREST);
glTexParameteri(GL— TEXTURE 一2D,GL— TEXTURE — MIN— FIL—TE R,GL— NE AREST);
rSTexEnvf(GL—TEXTURE —ENV,GL—TEXTURE —ENV—MODE,GL— DECAL);//说明纹理贴图方式
glEnable(GL— TEXTURE 一2D);//启动纹理贴图
glShadeModel(GL—SMOOTH);
glBegin(GL— QUADS);//定义纹理坐标和物体几何坐标
glTexCoord2f(1.of,1.Of);glVertex3f(1.Of,1.Of,0.Of);
glTexCoord2f(1.of,0.Of);glVertex3f(1.Of,一1.Of,0.Of);
glTexCoord2f(0.of,0.Of);glVertex3f(一1.Of,一1.Of,0.Of);
glTexCoord2f(0.Of.I.Of);glVertex3f(一I.Of,I.Of,0.Of);
glEnd();
glDisabh(GL—TEXTURE 一2D);//关闭纹理贴图
结束语
利用OpenGL强大的图形功能,可以轻松地实现逼真的贴图模型。在此基础上,运用VC++的MFC应用程序模式,可对OpenGL产生的模型进行更进一步的控制和变化。同时提供给用户一个友好的操作环境,这在当今的时尚编程中是不可或缺的。
源文档 <http://hi.baidu.com/imfei/blog/item/c55aa0c4cff5dec239db4909.html>
int CMubanView::LoadGLTextures()
{
int Status=FALSE; //状态参数跟踪是否能够载入位图以及能否创建纹理
AUX_RGBImageRec* TextureImage[1]; //设置纹理数组
memset(TextureImage,0,sizeof(void*)*1); //清除图像记录,将指针设置为NULL
if (TextureImage[0]=LoadBMP("it.bmp")) //载入位图
{
Status=TRUE;
glGenTextures(1,&m_texture[0]); //创建纹理
glBindTexture(GL_TEXTURE_2D,m_texture[0]); //根据来自位图的数据创建NEAREST纹理
glTexImage2D(GL_TEXTURE_2D, //产生的是2D纹理
0, //图像的详细程度,一般为0
3, //图像的成分,为RGB
TextureImage[0]->sizeX/2, //图像宽
TextureImage[0]->sizeY/2, //图像高
0, //图像边框
GL_RGB, //图像是RGB三色组成
GL_UNSIGNED_BYTE, //图像数据是无符号字节类型
TextureImage[0]->data); //图像数据来源
//采用GL_LINEAR使得纹理从很远处到离屏幕很近时都平滑显示
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); // 线形滤波
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST); // 线形滤波
return Status;
}
AUX_RGBImageRec* CMubanView::LoadBMP(char *Filename)
{
FILE *File=NULL;
if (!Filename) //文件名是否存在(判断结果---文件名是存在的)
{
return NULL;
}
File=fopen(Filename,"r"); //读取文件
if (File) //文件读取成功
{
fclose(File); //关闭文件流
return auxDIBImageLoad(Filename); //载入位图并返回指针
}
return NULL;
}
源文档 <http://bbs.gameres.com/showthread.asp?threadid=100160>
品寒绝顶雪舞人间
OPENGL的纹理
在3D图形中,纹理映射是广泛使用的。纹理映射也是相当复杂的过程:
一 定义纹理
二 控制滤波
三 说明映射方式
四 绘制场景给出顶点的纹理坐标和几何坐标
注意!!纹理映射只能在RGBA模式下使用,不适用于颜色索引模式
1.纹理定义
void glTexImage2D( GLenum target, GLint level, GLint components,
GLsizei width, GLsizei height, GLint border,
GLenum format, GLenum type, const GLvoid *pixels );
定义一个二维纹理映射。
target是常数 GL_TEXTURE_2D
level表示多级分辨率的纹理图象的级数。若只有一种分辨率,level为0。
components是从1到4的整数,1:选择R;2:选择R A;3:选择R G B;
4:选择R G B A;
width height是纹理的尺寸。
format和type描述映射格式和数据类型。它们与前面讲的glDrawPixels()中
OPENGL的纹理
在3D图形中,纹理映射是广泛使用的。纹理映射也是相当复杂的过程:
一 定义纹理
二 控制滤波
三 说明映射方式
四 绘制场景给出顶点的纹理坐标和几何坐标
注意!!纹理映射只能在RGBA模式下使用,不适用于颜色索引模式
1.纹理定义
void glTexImage2D( GLenum target, GLint level, GLint components,
GLsizei width, GLsizei height, GLint border,
GLenum format, GLenum type, const GLvoid *pixels );
定义一个二维纹理映射。
target是常数 GL_TEXTURE_2D
level表示多级分辨率的纹理图象的级数。若只有一种分辨率,level为0。
components是从1到4的整数,1:选择R;2:选择R A;3:选择R G B;
4:选择R G B A;
width height是纹理的尺寸。
format和type描述映射格式和数据类型。它们与前面讲的glDrawPixels()中
GL_NEAREST_MIPMAP_NEAREST
GL_NEAREST_MIPMAP_LINEAR
GL_LINEAR_MIPMAP_NEAREST
GL_LINEAR_MIPMAP_LINEAR
2.1 滤波
原始纹理图象是个方形图象,把它映射到奇形怪状的物体上,一般不可能图象
上的一个象素对应屏幕的一个象素。因此局部放大缩小时,就要定义合适的滤
波方式(以2D为例):
void glTexParameter(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
void glTexParameter(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
前者是放大滤波(GL_TEXTURE_MAG_FILTER),
后者是缩小滤波(GL_TEXTURE_MIN_FILTER);
另外,GL_NEAREST是利用最坐标最靠近象素中心的纹理元素,这有可能使图样
走型,但计算速度快;GL_LINEAR利用线形插值,效果好但计算量大。
2.2重复与缩限
纹理映射可以重复映射或者缩限映射,重复映射时纹理可以在自己的坐标S T方
向重复。
对于重复映射:
void glTexParameterfv(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
void glTexParameterfv(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
参数GL_REPEAT改为GL_CLAMP,则缩限,所有大于1的纹理元素值置为1。所有小于
0的纹理元素值置为0。
0的纹理元素值置为0。
3. 映射方式
处理纹理本身图案颜色和物体本身颜色的关系:
void glTexEnv{if}[v](GLenum target,GLenum pname,TYPE param);
target必须是GL_TEXTURE_ENV;
pname是GL_TEXTURE_ENV_MODE,则param可以是 GL_DECAL GL_MODULATE或
GL_BLEND,说明纹理值与原来颜色不同的处理方式。
pname是GL_TEXTURE_ENV_COLOR,则参数param是包含4个浮点数(R、G、B、A)
的数组。这些值只在采用GL_BLEND纹理函数时才采用。
4. 纹理坐标
坐标的定义:纹理图象是方形的,纹理坐标可定义成s,t,r,q坐标,仿照齐次
坐标系的x,y,z,w坐标。
void glTexCoord{1234}{sifd}[v](TYPE coords);
设置当前纹理坐标,此后调用glVertex*()所产生的顶点都赋予当前的纹理坐标。
5. 坐标自动产生
有时不需要为每个物体顶点赋予纹理坐标,可以使用
void glTexGen{if}(GLenum coord,GLenum pname,TYPE param);
coord为:GL_S GL_T GL_R或GL_Q,指明哪个坐标自动产生
pname为GL_TEXTURE_GEN_MODE时
param为常数:GL_OBJECT_LINEAR GL_EYE_LINEAR或GL_SPHERE_MAP,它们决定用
哪个函数来产生纹理坐标
pname为GL_OBJECT_PLANE或GL_EYE_PLANE,param时一个指向参数数组的指针。
先请看一个简单的例子:
//sample.cpp
#include "glos.h"
#include <GL/gl.h>
#include <GL/glaux.h>
#include "windows.h"
void myinit(void);
void CALLBACK display(void);
void CALLBACK reshape(GLsizei w,GLsizei h);
//创建纹理图象的子程序
#define TEXTUREWIDTH 64
#define TEXTUREHEIGHT 64
GLubyte Texture[TEXTUREWIDTH][TEXTUREHEIGHT][3];
void makeTexture(void)
void makeTexture(void)
{
int i,j,r,g,b;
for(i=0;i<TEXTUREWIDTH;i++)
{
for(j=0;j<TEXTUREHEIGHT;j++)
{
r=(i*j)%255;
g=(4*i)%255;
b=(4*j)%255;
Texture[i][j][0 =(GLubyte)r;
Texture[i][j][1 =(GLubyte)g;
Texture[i][j][2 =(GLubyte)b;
}
}
}
void myinit(void)
{
auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
auxInitPosition(0,0,500,500);
auxInitWindow("sample1");
auxInitWindow("sample1");
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);
//创建纹理图象的原始数据保存在Texture[][][]中
makeTexture();
glPixelStorei(GL_UNPACK_ALIGNMENT,1);
//定义二维纹理
glTexImage2D(GL_TEXTURE_2D,0,3,TEXTUREWIDTH,
TEXTUREHEIGHT,0,GL_RGB,GL_UNSIGNED_BYTE,
&Texture[0][0][0]);
//控制滤波
glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);
glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);
glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
//说明映射方式
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL);
//这个应该很熟了,启用纹理模式
glEnable(GL_TEXTURE_2D);
glEnable(GL_TEXTURE_2D);
// glShadeModel(GL_FLAT);
}
void CALLBACK reshape(GLsizei w,GLsizei h)
{
glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
//定义立体视景体
gluPerspective(60.0,1.0*(GLfloat)w/(GLfloat)h,1.0,30.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef(0.0,0.0,-3.6);
}
void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glBegin(GL_QUADS);//绘制四边形
//先绘制正方形,用来显示实际未变形的纹理图样
//先绘制正方形,用来显示实际未变形的纹理图样
glTexCoord2f(0.0,0.0);glVertex3f(-2.0,-1.0,0.0);
glTexCoord2f(0.0,1.0);glVertex3f(-2.0,1.0,0.0);
glTexCoord2f(1.0,1.0);glVertex3f(0.0,1.0,0.0);
glTexCoord2f(1.0,0.0);glVertex3f(0.0,-1.0,0.0);
//绘制一个不规则四边形,用来显示纹理是如何随物体形状而变形的。
glTexCoord2f(0.0,0.0);glVertex3f(0.0,-1.0,0.0);
glTexCoord2f(0.0,1.0);glVertex3f(0.0,1.0,0.0);
glTexCoord2f(1.0,1.0);glVertex3f(1.41421,1.0,-1.41421);
glTexCoord2f(1.0,0.0);glVertex3f(1.41421,-1.0,-1.41421);
glEnd();
glFlush();
}
void main(void)
{
myinit();
auxReshapeFunc(reshape);
auxMainLoop(display);
}
//end of sample
从例子来看,除了纹理的定义和控制比较麻烦和不容易理解外,其应用是十分
方便的。只须从纹理的坐标系选出合适点附在实际物体顶点上即可。对于复杂
的纹理定义和控制,你也可以自行改变一些参数,看看效果如何。例如把
GL_LINEAR改成GL_NEAREST,则纹理的明显变的粗糙,但计算结果却快的多。
你也可以改动glTexCoord2f()的参数,看看如何选定纹理的一部分(例子中是
选定全部纹理)来贴图。例如1.0改成0.5则选择实际纹理的左上1/4部分来贴图。
下次将给出一个更复杂的纹理应用的例子和说明。18:03 98-1-21
---------------------------------------------
这次将结束纹理的内容。紧接上次,在上次平面纹理贴图中,我们先
定义了一个数组(一维或二维...)来定义纹理的数据,所以纹理本身
是一个N维空间,有自己的坐标和顶点。在上次的例子中,我们学会了
如何把纹理数据中的坐标和屏幕物体坐标相结合,就象把一块布料扯成
合适的形状贴在物体表面。而上次唯一没有使用的函数是纹理坐标的自
动产生(最后一个给出的函数),它的意义是产生一个环境纹理,所有
环境内的物体都赋予此纹理,很象一个特殊光源。
/
//sample.cpp
#include "glos.h"
#include <GL/gl.h>
#include <GL/glaux.h>
#include "windows.h"
void myinit(void);
void CALLBACK display(void);
void CALLBACK reshape(GLsizei w,GLsizei h);
//定义一个一维纹理的数据,从生成来看,保持红色、兰色分量255(MAX),
//所以是渐变的紫色纹理,饱和度不断变化。
//所以是渐变的紫色纹理,饱和度不断变化。
#define TEXTUREWIDTH 64
GLubyte Texture[3*TEXTUREWIDTH];
void makeTexture(void)
{
int i;
for(i=0;i<TEXTUREWIDTH;i++)
{
Texture[3*i =255;
Texture[3*i+1 =255-2*i;
Texture[3*i+2 =255;
}
}
GLfloat sgenparams[]={1.0,1.0,1.0,0.0};
void myinit(void)
{
auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
auxInitPosition(0,0,500,500);
auxInitWindow("sample1");
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);
//创建纹理
makeTexture();
glPixelStorei(GL_UNPACK_ALIGNMENT,1);
//控制纹理
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
glTexParameterf(GL_TEXTURE_1D,GL_TEXTURE_WRAP_S,GL_REPEAT);
glTexParameterf(GL_TEXTURE_1D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameterf(GL_TEXTURE_1D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexImage1D(GL_TEXTURE_1D,0,3,TEXTUREWIDTH,0,
GL_RGB,GL_UNSIGNED_BYTE,Texture);
//唯一与前面例子不同的地方:启用纹理坐标自动产生,生成环境纹理
//纹理的方向S
glTexGeni(GL_S,GL_TEXTURE_GEN_MODE,GL_OBJECT_LINEAR);
glTexGenfv(GL_S,GL_OBJECT_PLANE,sgenparams);
//启用纹理
glEnable(GL_TEXTURE_1D);
glEnable(GL_TEXTURE_GEN_S);
//启用消隐
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LESS);
glDepthFunc(GL_LESS);
//一些绘图控制,详细可参阅VC5联机帮助
glEnable(GL_CULL_FACE);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_AUTO_NORMAL);
glEnable(GL_NORMALIZE);
glFrontFace(GL_CW);
glCullFace(GL_BACK);
glMaterialf(GL_FRONT,GL_SHININESS,64.0);
// glShadeModel(GL_FLAT);
}
void CALLBACK reshape(GLsizei w,GLsizei h)
{
glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if(w<=h)
glOrtho(-4.0,4.0,-4.0*(GLfloat)h/(GLfloat)w,
glOrtho(-4.0,4.0,-4.0*(GLfloat)h/(GLfloat)w,
4.0*(GLfloat)h/(GLfloat)w,-4.0,4.0);
else
glOrtho(-4.0*(GLfloat)h/(GLfloat)w,
4.0*(GLfloat)h/(GLfloat)w,-4.0,4.0,-4.0,4.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glPushMatrix();
glRotatef(30.0,1.0,0.0,0.0);
//功能强大的辅助库函数:呵呵画出一个大茶壶。
auxSolidTeapot(1.5);
glPopMatrix();
glFlush();
}
void main(void)
{
myinit();
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glPushMatrix();
glRotatef(30.0,1.0,0.0,0.0);
//功能强大的辅助库函数:呵呵画出一个大茶壶。
auxSolidTeapot(1.5);
glPopMatrix();
glFlush();
}
void main(void)
{
myinit();
auxReshapeFunc(reshape);
auxMainLoop(display);
}
//end of sample
至此纹理的全部内容已经完毕。从运行结果来看,一个物体全部进行
了表面的纹理映射。
-12---------------------------------------------
此次讲解OPENGL复杂建模方式,将分几个部分完成,这篇先介绍图原扩展:
如何利用专用函数精确绘制平面图形。下次会讲解如何利用法向量生成曲面。
1.点和线
void glPointSize(GLfloat size);
设置点的宽度,size必须>0,缺省1
void glLineWidth(GLfoat width);
设置线宽,width>0,缺省为1
void glLineStipple(GLint factor,GLushort pattern);
设置线的模式,factor用于对模式进行拉伸的比例因子,pattern是线的模式
例如11001100是虚线(1绘制,0不绘制)
必须要启用glEnable(GL_LINE_STIPPLE)才能使用以上函数,不再使用时调用
glDisable(GL_LINE_STIPPLE)关闭,这与以前的glEnable();glDisable();的
用法都是类似的。请看下面例子:
///
//sample.cpp
#include "glos.h"
#include "glos.h"
#include <GL/gl.h>
#include <GL/glaux.h>
#include "windows.h"
void myinit(void);
void CALLBACK display(void);
void CALLBACK reshape(GLsizei w,GLsizei h);
void myinit(void)
{
auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
auxInitPosition(0,0,600,500);
auxInitWindow("sample1");
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);
glShadeModel(GL_FLAT);
}
/*
void CALLBACK reshape(GLsizei w,GLsizei h)
{
glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if(w<=h)
glOrtho(-4.0,4.0,-4.0*(GLfloat)h/(GLfloat)w,
4.0*(GLfloat)h/(GLfloat)w,-4.0,4.0);
else
glOrtho(-4.0*(GLfloat)h/(GLfloat)w,
4.0*(GLfloat)h/(GLfloat)w,-4.0,4.0,-4.0,4.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
*/
//自定义的绘制直线的函数,参数为起始点和终止点坐标
void line2i(GLint x1,GLint y1,GLint x2,GLint y2)
{
glBegin(GL_LINES);
glVertex2f(x1,y1);
glVertex2f(x2,y2);
glEnd();
glEnd();
}
void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
//首先绘制一系列点,点的大小不断增加
int i;
glColor3f(0.8,0.6,0.4);
for(i=1;i<=10;i++)
{
glPointSize(i*2);
glBegin(GL_POINTS);
glVertex2f(30.0+((GLfloat)i*50.0),330.0);
glEnd();
}
//再绘制两条虚线,第二条比第一条松散一些,由pattern参数即可看出
glEnable(GL_LINE_STIPPLE);
glLineStipple(1,0x0101);//间隔1位
glColor3f(1.0,0.0,0.0);
line2i(20,250,250,250);
glLineStipple(1,0x00ff);//间隔2位
glLineStipple(1,0x00ff);//间隔2位
glColor3f(0.0,0.0,1.0);
line2i(300,250,550,250);
//改变线的绘制宽度的效果--加宽
//重新画出上面两条虚线
glLineWidth(5.0);
glEnable(GL_LINE_STIPPLE);
glLineStipple(1,0x0101);
glColor3f(1.0,0.0,0.0);
line2i(50,150,250,150);
glLineStipple(1,0x00ff);
glColor3f(0.0,0.0,1.0);
line2i(300,150,550,150);
glFlush();
}
void main(void)
{
myinit();
// auxReshapeFunc(reshape);
auxMainLoop(display);
auxMainLoop(display);
}
//end of sample
//
2.多边形
void glPolygonMode(GLenum face,GLenum mode);
控制多边形指定面的绘图模式,
face为:GL_FRONT GL_BACK或GL_FRONT_AND BACK
mode为:GL_POINT GL_LINE或GL_FILL表示多边型的轮廓点、轮廓线和填充模式
的绘制方式。缺省是填充方式。
void glPolygonStipple(const GLubyte *mask);
其中mask必须是指向32*32的位图指针,1是绘制、0不绘制
使用上述函数也要调用:
glEnable(GL_POLYGON-STIPPLE);
glDisable(GL_POLYGON_STIPPLE);
请看下面例子:
/
//sample.cpp
#include "glos.h"
#include <GL/gl.h>
#include <GL/gl.h>
#include <GL/glaux.h>
#include "windows.h"
void myinit(void);
void CALLBACK display(void);
void CALLBACK reshape(GLsizei w,GLsizei h);
//定义填充模式32*32点阵
GLubyte pattern[]={
0x00,0x01,0x80,0x00,
0x00,0x03,0xc0,0x00,
0x00,0x07,0xe0,0x00,
0x00,0x0f,0xf0,0x00,
0x00,0x1f,0xf8,0x00,
0x00,0x3f,0xfc,0x00,
0x00,0x7f,0xfe,0x00,
0x00,0xff,0xff,0x00,
0x01,0xff,0xff,0x80,
0x03,0xff,0xff,0xc0,
0x07,0xff,0xff,0xe0,
0x0f,0xff,0xff,0xf0,
0x1f,0xff,0xff,0xf8,
0x3f,0xff,0xff,0xfc,
0x3f,0xff,0xff,0xfc,
0x7f,0xff,0xff,0xfe,
0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,
0x7f,0xff,0xff,0xfe,
0x3f,0xff,0xff,0xfc,
0x1f,0xff,0xff,0xf8,
0x0f,0xff,0xff,0xf0,
0x07,0xff,0xff,0xe0,
0x03,0xff,0xff,0xc0,
0x01,0xff,0xff,0x80,
0x00,0xff,0xff,0x00,
0x00,0x7f,0xfe,0x00,
0x00,0x3f,0xfc,0x00,
0x00,0x1f,0xf8,0x00,
0x00,0x0f,0xf0,0x00,
0x00,0x07,0xe0,0x00,
0x00,0x03,0xc0,0x00,
0x00,0x01,0x80,0x00
};
void myinit(void)
void myinit(void)
{
auxInitDisplayMode(AUX_SINGLE|AUX_RGBA);
auxInitPosition(0,0,400,400);
auxInitWindow("sample1");
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);
glShadeModel(GL_FLAT);
}
/*
void CALLBACK reshape(GLsizei w,GLsizei h)
{
glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if(w<=h)
glOrtho(-4.0,4.0,-4.0*(GLfloat)h/(GLfloat)w,
4.0*(GLfloat)h/(GLfloat)w,-4.0,4.0);
else
glOrtho(-4.0*(GLfloat)h/(GLfloat)w,
glOrtho(-4.0*(GLfloat)h/(GLfloat)w,
4.0*(GLfloat)h/(GLfloat)w,-4.0,4.0,-4.0,4.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
*/
void CALLBACK display(void)
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
//选用兰色作为填充色
glColor3f(0.0,0.0,1.0);
//启用多边形绘制模式
glEnable(GL_POLYGON_STIPPLE);
//利用定义好的填充模式绘制多边形
glPolygonStipple(pattern);
//绘制长方形
glRectf(48.0,80.0,210.0,305.0);
glFlush();
}
void main(void)
{
//选用兰色作为填充色
glColor3f(0.0,0.0,1.0);
//启用多边形绘制模式
glEnable(GL_POLYGON_STIPPLE);
//利用定义好的填充模式绘制多边形
glPolygonStipple(pattern);
//绘制长方形
glRectf(48.0,80.0,210.0,305.0);
glFlush();
}
void main(void)
{
myinit();
// auxReshapeFunc(reshape);
auxMainLoop(display);
}
//end of sample
例子中的运行结果是给出一个表面有定义图样的长方形
-13---------------------------------------------
这里讲解OPENGL的曲线生成
1.曲线定义
void glMap1{fd}(GLenum target,TYPE u1,TYPE u2,GLint stride,
GLint order,const TYPE *points);
target指出控制点的意义以及在points参数中需要多少值。具体如下:
target 意义
GL_MAP1_VERTEX_3 X Y Z顶点坐标
GL_MAP1_VERTEX_4 X Y Z W顶点坐标
GL_MAP1_INDEX 颜色索引
GL_MAP1_COLOR_4 R G B A
GL_MAP1_NORMAL 法向量
GL_MAP1_TEXTURE_COORD_1 S 纹理坐标
GL_MAP1_TEXTURE_COORD_2 S T纹理坐标
GL_MAP1_TEXTURE_COORD_3 S T R纹理坐标
u1,u2是曲线变量U的范围(具体可以参阅图形学书籍)一般是0到1
stride是跨度,表示points中控制点偏移量(或说是控制点的维数)
源文档 <http://www.pinxue.net/OpenGL/credbook/chapter9_textuer.htm>
三维随机分形地形生成
原文: http://www.gameprogrammer.com/fractal.html
作者: Paul Martz [ martz@frii.com ]
翻译:品雪 [pinxue@263.net] 1999/8/4
目 录
说明:本页所有图像均经过优化以减小尺寸,所以与实际图像会有细微差别。
第一部分:生成随机分形地形
介绍
十年前,我参加 1986 年 SIGGRAPH 活动, Gavin S. P. Miller 那篇题为 Definition and Rendering of Terrain Maps 的论文让我充满敬畏。该文描述了少数生成分形地形的算法,作者还介绍了一个他们认为更先进的新方法。
开始我被这些算法能够生成难以置信的风景图所震惊!(尽管这些算法被作者认为"漏洞百出")后来,读过论文,这些算法之简单将我完全打败了。
我从此成为一个分形地形迷。
算法背后的数学可能相当复杂。然而,完全理解这些数学并不是掌握这些算法的必要条件。很好,否则我得在解释算法之前讲解所有的数,也许永远也讲不到算法。此外,关于分形数学的文字材料数以吨计,参见本文本的参考部分会有所帮助。
同样的原因,我不会深入到数学细节,也不包括对分形的广泛总览及它们可被用来做的每样东西。相反,我将描述分形地形生成背后的概念,并集中仔细讲解 我个人最喜欢的 "diamond-square" 算法。我将演示如何使用这个算法静态拼嵌高度数据数组,这些数据可用于几何地形数据、地形纹理数据及云纹理映射。
分形有什么用呢?假定你已经知道,那正是你读本文的原因。随机地形图对飞行模拟或制作背景纹理图(如显示一带远山)十分有用。生成地形的算法也可用于生成部分云天的纹理图。
在继续之前,申明一下:我不是游戏程序员。如果你为找到一个快速绘制地形的算法而读此文,那你来错了地方。我只描述生成地形模型的过程。着色绘制是你自己的事。
自相似
任何分形最关键的概念是自相似。当一个物体的一部分放大后看起来仍与整个物体一样,那这个物体就是自相似。
考虑一下人体的循环系统。这是自然界中自相似的好例子。从最大的动脉和静脉分支直到最小的微血管,整个过程都显现相同的分支模式。如果你不知道正在使用显微镜,将无法分辨微血管和大动脉。
现在再考虑一个简单的球。它是自相似的吗?不!大幅度放大后,它看起来不再象一个球,而象块平板。如果你不相信,看看户外。除非恰好你在太空轨道上看本文,否则将完全没法看出球是个球体。球体不是自相似的。它最用传统的欧几里德几何描述而不是分开。
地形属于自相似范畴。手掌上的碎岩锯齿状边缘与远处地平线边的山脊有相同的不规则形状。这使我们可以用分形来生成地形,不管显示时怎么放大,它看起来仍然象地面。
关自相似请注意:严格意义下,它意味着自分辨 (self-identical) ,即,自身精确的缩略拷贝在逐渐放大缩小时可见。我并不知道自然界存在任何自分辨分形。但 mandelbrot 集是自分辨的。我不会进一步讨论 Mandelbrot 集。到参考里找进一步的信息。
一维中点变换
后边要讲的 diamond-square 算法,在两维上使用一种中点变换算法。为帮助你了解个大概,我们先看一维情况。
当山脉出现在远处地平线处时,一维中点变换是绘制山脊的好算法。看看它是怎么工作的:
以一条水平地平线段开始
重复足够多次{
对场景中的每条线段做{
找到线段的中点
在 Y 方向上随机移动中点一段距离
减小随机数取值范围
}
}
将随机数值域减速小多泊呢?那取决于你想要分形的陡峭程度。每次循环减少的越多,所得山脊线就越平滑。但如果减得太多,则会有明显的锯齿感。可以粗糙度存在一个常量里。后面会解释如何做。
来看个例子。我们以一条 x 从 -1.0 到 1.0 , y 均为 0 的线段开始。开始,我们将随机值范围设为 -1.0 到 1.0 (可任意取)。这样我们在此范围里生成一个数字,并将中点移动这么多。这之后,我们就得到了:
现在第二次经过外圈循环,我们有两段,长度均原来的一半。我们的随机值也减半,即 -0.5 到 0.5 。我们为两个中点都生成一个这个范围内的随机点,结果为:
再次缩减范围,现在是 -0.25 到 0.25 。再以该范围内的数变换四个中点后,我们得到了:
有两件事你可能已经注意到了。
首先,它是递归的。实际上,它可以用一个迭代过程相当自然的实现。对于这种情况,递归或迭代都成。对于表面生成代码,使用迭代实现比递归会有一些好处。所以为保持一致,线和面相应的代码都使用迭代实现。
其次,它是个非常简单的算法,然而你能创建非常复杂的结果。这正是分形算法的美妙之处。一些简单的指令可以建立一个具有丰富细节的图像。
再跑一下题:少量简单的指令集能够生成复杂图像的事实已经成为一个新的研究领域称为分形图像压缩。其思想是保存建立图像的递归指令而不是保存图像本身。这对于自然界的分形图像是极有用的,因为指令相对图像占用的空间要少得多。 Choas (混沌)与 Fractals (分形) new Frontiers of Science 有一章及一个附录涉及本主题,是一般学习分形的好读物。
回到现实。
不用太费劲,你可以读取本函数的输出到一个绘制程序而得到类似如下的东西:
这可作为窗口景色使用。相关的好东西是它是约束的,所以你可以保留一个相当的小图像并用它拼出整个场景。如果你不介意在每个方向都看见相同的山,那就这么干。
好的,在进入 2D 分形表面之前,你得了解粗糙度常量。这个值决定每次循环随机数值域的减少量,也就是说,决定分形结果的粗糙程度。例子代码使用一个 0.0 到 1.0 之间的浮点数并称之为 H 。因此 2(-h) 是 1.0( 对于小 H) 到 0.5 (对大 H )范围内的数。随机数范围在每次循环时乘上这个值。如果 H 设为 1.0 ,则随机数范围将每次循环减半,从而得到一个非常平滑的分形。将 H 设为 0.0 ,则范围根本不减小,结果有明显的锯齿感。
下边是三个山脊,每个用不同的 H 的值绘制:
高度图
上边所说的中点变换算法可以用一个一维数组实现,数组成员是表明线段端点垂直位置的高度值。这数组是就是一个一维高度图。它将索引( x 值)映射为高度值( y 值)。
为模拟随机地形,我们想将该算法推广到 3D 空间。为做到这一点,我们需要一个两维高度值数组,它将索引 (x,z) 映射为高度 (y) 。数组只需保存高度值 (y) 。水平面值 (x 和 z) 可以在分析数组时即时生成。
通过对每个高度指定一个颜色,可以将一幅高度图显示为一幅图像。如下,高点为白色,低处为黑色。
绘制高度图的方法对于生成云彩纹理图是很有用的,后边还会讨论。这种表达也可以用于播种一个高度图。
现在我要讲讲如何拼嵌我们的二维高度图数组。
Diamond-Square 算法
正如本文开头提到过的,我先介绍 Gavin S.P.Miller 的论文中随机地形生成的概念。具有讽刺意义的是, Miller 在论文中说 diamond-square 算法是有缺陷的,然后描述了一种完全不同的基于重量平均和控制点的算法。
Miller 对 diamond-square 算法的抱怨阻止他尝试迫使该算法建立一座山,也就是,带有一个山峰,人为增加网格中心点的高度。他让数组中所有的点都随机生成。如果 Miller 简单的只随机生成中心点,那么即使是他也会同意该算法是个经典的地形生成器。 Diamond-Square 算法可以通过给数组播种值来用一个山峰推出一坐山。比数组中心点更多的点得先播种以构造可接受的结果。他也抱怨一些固有皱折问题。但你得自己判断。算法最 初是由 Fourniew , Fussell 和 Carpenter 提出的。
思想如下:你从一个很大的空 2D 数组开始。多大呢?为简化起见,他应该是方的,维数应该是 2 的 n 次方加 1 (如 33X33,65X65,129X129 等)。将四个角设为相同高度。如果你察看所得到东西,它是一个正方形。
取个简单的例子,用一个 5X5 的数组。(本文后面还要参考这图,别忘记了)。图中,图 a 的四个角种上了初始高度值,表示为黑点。
这是递归细分过程的起点,该过程分两步:
diamond 步:取四个点的正方形,在正方形中点生成一个随机值,中点为两对角线交点。中点值是平均四个角值再加上一个随机量计算得到的。这样就得到了一个棱锥。当网格上分布着多个正方形时有点象钻石。
square 步:取每个四点形成的棱锥,在棱锥的中心生成一个随机值。平均角值再加上与 diamond 步相同的随机量,计算出每条边中点值。这又给你一个正方形。
这样,如果已经生成了一个种子正方形并经过单独一次细分过程将得到四个方形。第二次经过该过程得到 16 个方形,第三次得到 64 个方形。增长得很快。方形数目等于 2( 2 + I ) ,其中 I 为递归经过细分过程的次数。
参考前五幅插图,下图示意了使用我们的 diamond-square 算法两次经过数组时发生的情况。
对于第一遍经过 diamond 步时,我们依据四个角的值在数组中心生成一个值。我们平均四个角的值(如果种子值相等则完全没必要),并加上一个 -1.0 到 1.0 之间的随机值。在插图 b 中,新值显示成黑色,已经存在的点显示为灰色。
对于 square 步,我们在相同的范围内生成随机值。这一步时有四个棱锥;他们在数组中心相交,这样我们计算四个 diamond 中心。 diamonds 的角被平均以找出新值的基数。插图 C 用黑色显示新值,现存值为灰色。
以上是第一遍,如果用线将这 9 个点边起来,就可以得到一个线框的表面,看起来就象:
现在进行第二遍。再次从 diamond 步开始。第二遍与第一遍有两点不同。首先,我们现在有四人四边形面不是一个,因此我们得计算四个方面的中心。其次,这是关键,生成随机数的范围已经被减小 了。因为例子的缘故,让我们认为正在使用一个 H=1.0 的值。这将把我们的随机数取值范围将从 (-1.0,1.0) 到 (-0.5,0.5) 。在插图 D 中,我们这一步计算得到的四个正方形中心值显示为黑色。
最后,我们进行第二遍的 square 步。有 12 个棱锥中心,我们现在需要计算 12 个新值,如图 e 中黑色所示。
现在数组中全部 25 个元素都已经生成。我们可以得到如下的线框曲面。
如果分配更大的数组,我们可以进行更多遍,每一遍加入更多细节。例如, 5 遍之后表面看起来如下所示:
前面提到过,数组维数需要为 2 的整数次方加 1 。这是因为 2D 数组中的浮点数必须等于 (2n+1)2 。 8 次迭代将需要一个 257X257 的浮点数组,对于标准的 32 位 IEEE 浮点数来说超过 256K 内存。
好了,它就是这么大。用 char 取代 floats 会有所帮助。例子程序使用 floats ,但你要真的关注内存使用那么使用 char 。修改例子使用之使用 -128 到 128 的范围是很容易的,但别忘了将你生成的值限定在 -128 到 128 范围里,子序列通过时会生成该范围以外的值,这会导致溢出。这在使用较小的 H 时尤其可能。
例子程序演示了处理尺寸的另外一种方法。用一个大数组依据 diamond-square 算法进行定位及拼嵌。然后从平行投影体顶视图进行绘制。这个图像被读回来并用作已经拼嵌成较小范围的第二个数组上的纹理图。然而,例子程序并没有这样做, 一但图像从帧缓冲读回,第一个数组就被释放了。
这有个纹理图的例子:
该图经过人工着色,山峰为白色,山谷为绿色,两者之间为灰色。尽管利用例子程序源码试试自己的配色方案。
早先还到过用迭代实现这个例程比递归好。原因是:一个递归实现可能翻采用如下形式:
执行 diamond 步;
执行 square 步;
减小随机数范围
调用自己四次。
这是个很简洁的实现,而且毫无疑问它能工作。但它要求用不充足的数据生成某些点。为什么呢?经过第一遍之后,你将再次调用以执行 square 步,但这时并没有一个棱锥四个角的全部数据。
与之相反,我用个迭代实现,伪码如下:
当 square 边长度大于 0 时{
遍历数组,对每个正方形表达执行 diamond 步
遍历数组,对每个棱锥表达执行 diamond 步
减小随机数范围
}
这样就消除了递归实现中出现的棱锥角丢失问题。但在生成数组边界的点时还是会碰到这个问题。下图中,数组中构成棱锥角的位置用亮灰色表示。它们应该被平均以找出新的基本值,即图中黑色的点。
注意用黑色标记的两个值。它们实际上是相同的值。每次你在 square 步计算一个边界上的值时,记得同时把它保存在数组对面边上。
这意味着前面插图 e 中,我们实际上不必计算 12 个单独的值,因为其中的四个在数组相对的两条边上重复。实际上只有 8 个值需要计算。
感兴趣的读都可以练习一下:取出源代码并使用它在边界的值不重复时也能工作。这对算法正常工作是没有必要的,按我写的方式去做就成。
如果你还没运行过例子程序,现在或许是时候打开看看了。它从两次迭代生成的曲面开始。曲面是用线框绘制的,只是简单的将数组中的值用线段边接起来。 数组中的值被当作 Y 值,而 X 和 Z 坐标是在数组分析时即时生成的。通过将一个方形分成两个三角形可以轻易的使用三角形绘制出这个曲面。三角形通常都是很好的图元,因为它总是凸性的平面。
用 View Opeions 对话框调节 RAndom seed 值。这会导致生成不同的曲面。调高 iterations 值给曲面增加细节。代码限制这个值到 10 ,这对于我 32M 内存的 PentiumPro 系统有点多,看起来是黑的(或许五年以后,人们会在新的处理器和更高分辨率的显示器上运行这个程序,会对我为什么将它限制到 10 十分奇怪的……)。
第一个 H 值控制表面粗糙度,默认值为 0.7 。试着设得更高或更低看看结果如何。
是的这个算法偶尔会产生局部尖刺或皱折。但我偏爱尖或皱折不明显依赖于观察角度或飞过它的速度这种超自然的效果
蓝天白云
现在我们知道如何生成表面。我们可以生成并着色大量的三角形,也可以生成一个高分辨率的纹理图并将它应用到低分辩率的表面。不管怎样,效果相当好。那么,我们怎么生成头上的天空呢?它比你想象得要简单。
diamond-square 算法拼嵌完成的数组非常适于表示云天的纹理图。与把数组看作一套高度图上的 y 值相反,把它看成云的不透明度数据。最小数组值代表最蓝��天空中最晰的部分,最大的值代表最白,天空中云最重的部分。
分析数组并生成如下的纹理图是相当琐碎的:
这与前面的高度图很象,但我已经限定了高、低值以建立清晰有云的天空。
也可以用例子程序生成一幅类似的图像。设置 Select rendering type 下拉菜单为 2D mesh/clouds 。(默认时看起来有像素感,试试把 Cloud iterations 值设为 8 以上修正之)。试试给这 H 赋不同的值(就是前面刚说过的 Cloud iterations 值),以取得不同的云的效果。
如果回到本文开头,第一幅图结合了许多我在这做的讨论。天空是用一个如上的纹理图作的,沿一个八边金字塔重复排放多次曲面几何体用一个高分辩率纹理图绘制。这个纹理图是通过从一个平行顶视图着色一个高度拼嵌有光照曲面而生成的。然后,这个图被读出用作纹理图。
跟随本文的例子程序被用于生成本文中出现的几乎所有图像。
其它算法
可能会想对曲面生成有比样本代码更多的控制。例如,可能想用自己的值给数组的前几遍初始化种子值,这样,山、谷……可以基本位于你设计的位置。然后用 diamon-square 算法填写其它细节。
修改代码,使之在赋值时略过已有值的数组成员是易于完成的。初始化数组为,例如, -10.0 ,给前几遍指定自己的值作为种子,再增强分形生成代码只给当前值为 -10.0 的赋值。简几遍将不会生成任何值,因为你的种子值已经在那儿了。后续几遍将在种子值在基础上生成新值。
如何取得种子值呢?如果想要的形状遵循某个已知的数学公式,如正弦曲线,就用这个函数生成值。否则,你得找出创造性的方法完成。我见过的一种算法是用灰度值绘制自己的高度图。将灰度映射成高度值并存入数组。再用 diamond-square 算法增加更多细节。
除了 diamond-square 算法,还有许多拼嵌表面的方法。
用连续随机增加, 2D 数组的一个随机部分增高一个很少的量。反复多次,对所选中的数组区域加上一个很小的随机值。这可以生成相当不错的结果,但不是线性计算的。如果计算时间无所谓,那么建议试试这个算法。
一另一个相似的方法,制造一个穿过数组的"折断",并增加其中的一边,就象地震出现一样。再重复多次。这也不是一个线性算法,要多遍才能取得可以接受的结果。
参考参考文献以了解更多其它途径。
第二部分 关于例子源码
安装
这个例子源码放在一个 zip 文件中,用你惯用的解压缩软件打开。如果你没有 zip 工具,试试 www.pkware.com 。
源码使用 OpenGL API 绘制。如果你的机器上没有则请下载一个。注意本程序认 SGI 实现的 OpenGL ,注意文件名。
快速起步
启动 Fractal 例子程序
打开 View Options 对话框
从 Select render type 下拉菜单选 2D mesh/renderd
将 Interations 值改为 4
将 Tile 值改为 3 。
使用程序
自己试试就知道了。
代码结构
fractmod.c 和 fractmod.h 是本例子程序的核心 C 代码。它构成分形生成模块。
CFractalExampleView 类是从 1996.12 期 M$ Journal: http://www.microsoft.com/msj 发现的 COpenGLView 类衍生的。 COpenGLView 类由 Ron Fosner 写成,他说这是他 fully-blown COpenGLView 类的精简版。要真正看懂,去买他那本由 Addison-Welsley 出版书: OpenGL prgramming for windows95 and windows nt 。
COpenGLView 有一个 RenderScene 虚成员,我们在 CFractalExampleView 里重载之。这里将完成主要的绘制工作。函数先检查 Rendering type 的设定。当设为 2D mesh/lines or 1D midpoint displacement 时,工作由 RenderScene 完成。否则,别的函数被调用。
CFractalExampleViewe::OnViewDialog 生成 View Options 对话框,并处理设置及在 CFractalExampleView 与对话框类间提取数据。
CFractalExampleView::OnInitialUpdate 管理设置所有 CFractalExamleView 成员变量为其默认值(含对话框值)。
实际上关于代码是如何工作的并没有太多可解释的地方。我假定你是一个能干的程序员,且我已经尽力给代码加上详细的注释。如果你不熟悉 OpenGL ,注意 gl 开头的函数都是 OpenGL API 调用,详见 VC++ 的帮助文档或 Blue Book 。
有一个特性是刚开始加进代码中的。在 FractalExampleView.cpp 文件中,有一个名为 DEF_HIGHT_VALUE 的预处理常量。它传给 fractmod.c 文件中的分形生成函数,用来缩放高度值。其实它应该是由 View Options 对话框控制的变量。尽管加上这个特性好了。
关键代码:
1、Square步的端点值平均算法:见核心代码文件
2、Diamond步的端点值平均算法:见核心代码文件
3、使用Square-Diamond算法进行分形数组填充:见核心代码文件
4、地形排列的边界拼接算法
z = -1.f * (float) (tile-1); for (i=0; i<tile; i++) { x = -1.f * (float) (tile-1); for (j=0; j<tile; j++) { glPushMatrix (); glTranslatef (x, 0.f, z); draw2DFractArrayAsLines (surfFA, size); glPopMatrix (); x += 2.f; } z += 2.f; }
5、地形绘制
bool RenderScene() { int size = 1 << iterations; GLfloat black[4] = {0.f, 0.f, 0.f, 1.f}; GLfloat white[4] = {1.f, 1.f, 1.f, 1.f}; // Handle inverted colors. if (colorInvert) glClearColor (black[0], black[1], black[2], black[3]); else glClearColor (white[0], white[1], white[2], white[3]); // Handle non line mode renderMode settings. if (renderMode == twoDR_clouds) return (renderCloudMap ()); else if (renderMode == twoDR_teximage) return (renderTeximageMap ()); else if (renderMode == twoDR_rendered) return (renderFullImage ()); // Shouldn't take too long, unless drawing huge // AA scenes, but what the heck... CWaitCursor hourglass; glMatrixMode (GL_PROJECTION); glLoadIdentity (); if (arrayIs2D) // Needs to be identical to the same call in renderFullScene. gluPerspective (60., aspectRatio, .1, 15.0); else glOrtho (-aspectRatio, aspectRatio, -1., 1., 1., -1.); glMatrixMode (GL_MODELVIEW); glLoadIdentity (); if (arrayIs2D) { glTranslatef (0.f, yTrans, zTrans); glRotatef (yRot, 0.f, 1.f, 0.f); } glPushAttrib (0xffffffff); glDisable (GL_DEPTH_TEST); glDisable (GL_LIGHTING); if (colorInvert) glColor4fv (white); else glColor4fv (black); if (aaLines) { glEnable (GL_LINE_SMOOTH); glEnable (GL_BLEND); glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } else { glDisable (GL_LINE_SMOOTH); glDisable (GL_BLEND); } glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if (arrayIs2D) { float x, z; UINT i, j; // validate surface if (surfFA && surfFAdirty) { freeFractArray (surfFA); surfFA = NULL; } if (!surfFA) { surfFA = alloc2DFractArray (size); if (surfFA==NULL) return (FALSE); fill2DFractArray (surfFA, size, randomSeed, DEF_HEIGHT_SCALE, surfaceH); surfFAdirty = FALSE; } z = -1.f * (float) (tile-1); for (i=0; i<tile; i++) { x = -1.f * (float) (tile-1); for (j=0; j<tile; j++) { glPushMatrix (); glTranslatef (x, 0.f, z); draw2DFractArrayAsLines (surfFA, size); glPopMatrix (); x += 2.f; } z += 2.f; } } else { float x; UINT i; float *fa; // Always validate and free 1D array. // We won't be doing any realtime transformations on it. fa = alloc1DFractArray (size); if (fa==NULL) return (FALSE); fill1DFractArray (fa, size, randomSeed, DEF_HEIGHT_SCALE, surfaceH); x = -1.f * (float) (tile-1); for (i=0; i<tile; i++) { glPushMatrix (); glTranslatef (x, 0.f, 0.f); draw1DFractArrayAsLines (fa, size); glPopMatrix (); x += 2.f; } freeFractArray (fa); } glFlush(); glPopAttrib (); return (TRUE); }
6、纹理
bool renderTeximageMap () { CWaitCursor hourglass; int size = 1 << teximageIter; UINT pmapDim, smallDim; if (teximageFA && teximageFAdirty) { freeFractArray (teximageFA); teximageFA = NULL; } if (!teximageFA) { teximageFA = alloc2DFractArray (size); if (teximageFA==NULL) return (FALSE); fill2DFractArray (teximageFA, size, randomSeed, DEF_HEIGHT_SCALE, teximageH); } // Find the biggest power of two that will fit // in the current window. This is where we will // draw the image. smallDim = (currentWidth > currentHeight) ? currentHeight : currentWidth; pmapDim = 1 << (UINT) (log ((float) smallDim) / log (2.)); glPushAttrib (0xfffffff); // Only draw into biggest power of 2 square. glViewport (0, 0, pmapDim, pmapDim); glMatrixMode (GL_PROJECTION); glLoadIdentity (); glOrtho (-1.f, 1.f, -1.f, 1.f, 1., -1.); glMatrixMode (GL_MODELVIEW); glLoadIdentity (); // The array is rendered into the XZ plane. We need to // rotate by 90 degrees so we will be looking at it // "top down". glRotatef (90.f, 1.f, 0.f, 0.f); // Init lights now. Light position must be specified // after the rotate, or else it won't be rotated. initLights (); glDisable (GL_DEPTH_TEST); glDisable (GL_FOG); glEnable (GL_LIGHTING); glDisable (GL_BLEND); glClear (GL_COLOR_BUFFER_BIT); renderAsTriangles (teximageFA, size, 1, 0); glFlush (); glPopAttrib (); if (teximageFAdirty) { GLubyte *pmap; pmap = (GLubyte *) malloc (pmapDim*pmapDim*3); if (pmap==NULL) return (FALSE); glReadPixels (0, 0, pmapDim, pmapDim, GL_RGB, GL_UNSIGNED_BYTE, pmap); glNewList (TEXIMAGEMAP, GL_COMPILE); glTexImage2D (GL_TEXTURE_2D, 0, /* lod */ 3, /* num components */ pmapDim, pmapDim, /* width, height */ 0, /* border */ GL_RGB, GL_UNSIGNED_BYTE, /* format, type */ pmap); glEndList (); free (pmap); } teximageFAdirty = FALSE; return (TRUE); }
7、云
bool renderCloudMap () { CWaitCursor hourglass; int size = 1 << cloudIter; if (cloudFA && cloudFAdirty) { freeFractArray (cloudFA); cloudFA = NULL; } if (!cloudFA) { cloudFA = alloc2DFractArray (size); if (cloudFA==NULL) return (FALSE); fill2DFractArray (cloudFA, size, randomSeed, DEF_HEIGHT_SCALE, cloudH); } glPushAttrib (0xfffffff); if (cloudFAdirty) { GLubyte *tmap; tmap = (GLubyte *) malloc (size*size*3); if (tmap==NULL) return (FALSE); surfToSky (cloudFA, size, tmap); glNewList (CLOUDMAP, GL_COMPILE); glTexImage2D (GL_TEXTURE_2D, 0, /* lod */ 3, /* num components */ size, size, /* width, height */ 0, /* border */ GL_RGB, GL_UNSIGNED_BYTE, /* format, type */ tmap); glEndList (); free (tmap); } glCallList (CLOUDMAP); glMatrixMode (GL_PROJECTION); glLoadIdentity (); if (currentWidth > currentHeight) glOrtho (-1.f, aspectRatio*2.f-1.f, -1.f, 1.f, 1., -1.); else glOrtho (-1.f, 1.f, -1.f, 2.f/aspectRatio-1.f, 1., -1.); glMatrixMode (GL_MODELVIEW); glLoadIdentity (); glDisable (GL_DEPTH_TEST); glDisable (GL_FOG); glDisable (GL_LIGHTING); glDisable (GL_BLEND); glEnable (GL_TEXTURE_2D); glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, (GLfloat) GL_REPLACE); glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (GLfloat) GL_NEAREST); glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (GLfloat) GL_NEAREST); glClear (GL_COLOR_BUFFER_BIT); glBegin (GL_QUADS); { glTexCoord2f (0.f, 0.f); glVertex2f (-1.f, -1.f); glTexCoord2f (1.f, 0.f); glVertex2f (1.f, -1.f); glTexCoord2f (1.f, 1.f); glVertex2f (1.f, 1.f); glTexCoord2f (0.f, 1.f); glVertex2f (-1.f, 1.f); } glEnd (); glFlush (); glPopAttrib (); cloudFAdirty = FALSE; return (TRUE); }
分形计算核心代码:(fractmod.c)
/* Written by: Paul E. Martz Copyright 1997 by Paul E. Martz, all right reserved Non-commercial use by individuals is permitted. 中文注释:品雪 pinxue@263.net 1999.8. */ #include <stdio.h> #include <stdlib.h> #include "math.h" #include "fractmod.h" #ifdef __cplusplus extern "C" { #endif #ifdef _WINDOWS /* 为HPUX而写,控制Win32编译*/ #define random() rand() #define srandom(x) srand(x) #endif /* 随机返回一个(min,max)之间的值。 (任意一个范围被分为32767个值。) */ static float randnum (float min, float max) { int r; float x; r = random (); x = (float)(r & 0x7fff) / (float)0x7fff; return (x * (max - min) + min); } /* 定义randnum的简化界面 */ #define fractRand(v) randnum (-v, v) /* 计算数组中中点i所在线段端点值的平均值。 * * Called by fill1DFractArray. */ static float avgEndpoints (int i, int stride, float *fa) { return ((float) (fa[i-stride] + fa[i+stride]) * .5f); } /* 计算棱锥四角的平均值,(i,j)为棱锥中心点, stride为中心到角的纵横间隔, size是纵横横向点个数 由于是正方形,纵横间隔及两方向点均相等。 注意,size为纵横向顶点数,而subSize为线段数,均代表数组维数。 size=subSize+1; Called by fill2DFractArray. */ static float avgDiamondVals (int i, int j, int stride, int size, int subSize, float *fa) { /* 为支持排列表面无缝拼接,当(i,j)处于数组的某条边时要特别注意。 下面头四条if语句处理这种情。最后的else处理一般情况(i,j不在边界上)。*/ if (i == 0) return ((float) (fa[(i*size) + j-stride] + //j-strid fa[(i*size) + j+stride] + //j+strid fa[((subSize-stride)*size) + j] + //i-strid,不存在,用对边上的顶点 fa[((i+stride)*size) + j]) * .25f);//i+strid else if (i == size-1) return ((float) (fa[(i*size) + j-stride] + //j-strid fa[(i*size) + j+stride] + //j+strid fa[((i-stride)*size) + j] + //i-strid fa[((0+stride)*size) + j]) * .25f);//i+strid,不存在,用对边上的顶点 else if (j == 0) return ((float) (fa[((i-stride)*size) + j] + fa[((i+stride)*size) + j] + fa[(i*size) + j+stride] + fa[(i*size) + subSize-stride]) * .25f); else if (j == size-1) return ((float) (fa[((i-stride)*size) + j] + fa[((i+stride)*size) + j] + fa[(i*size) + j-stride] + fa[(i*size) + 0+stride]) * .25f); else return ((float) (fa[((i-stride)*size) + j] + //i-strid fa[((i+stride)*size) + j] + //i+strid fa[(i*size) + j-stride] + //j-strid fa[(i*size) + j+stride]) * .25f); //j+strid } /* 平均 中心位于(i,j)的正方形四角的数据值 stride为正方形边长度。 X.X .*. X.X 与棱锥步不同,这次不会有边界的问题。 Called by fill2DFractArray. */ static float avgSquareVals (int i, int j, int stride, int size, float *fa) { return ((float) (fa[((i-stride)*size) + j-stride] + fa[((i-stride)*size) + j+stride] + fa[((i+stride)*size) + j-stride] + fa[((i+stride)*size) + j+stride]) * .25f); } /*判断size是否为2的次方,是则返回1,不是或为0则返回0。*/ static int powerOf2 (int size) { int i, bitcount = 0; /* 下面的代码假定sizeof(int)*8可以算出一个整型数的位数。 适用于多数平台 */ for (i=0; i<sizeof(int)*8; i++) if (size & (1<<i)) bitcount++; if (bitcount == 1) /* 注意2的整次方数只会有一个位为1 */ return (1); else return (0); } /*拼嵌一数组的值为近似分形布朗运动 */ void fill1DFractArray (float *fa, int size, int seedValue, float heightScale, float h) { int i; int stride; int subSize; float ratio, scale; if (!powerOf2(size) || (size==1)) { return; } subSize = size; size++; srandom (seedValue); /* 设定我们的粗糙度常量 随机数始终在0.0到1.0范围内生成。 随机数将乘上scale,每次迭代后scale将乘上ratio以有效的减少随机数范围。*/ ratio = (float) pow (2.,-h); scale = heightScale * ratio; /* 在数组中播种端点。要实现无缝拼接,这些端点相同*/ stride = subSize / 2; fa[0] = fa[subSize] = 0.f; while (stride) { for (i=stride; i<subSize; i+=stride) { fa[i] = scale * fractRand (.5f) + avgEndpoints (i, stride, fa); /* reduce random number range */ scale *= ratio; i+=stride; } stride >>= 1; } } /*用diamond-square算法拼嵌一个浮点数网格填充二维分形高度图*/ void fill2DFractArray (float *fa, int size, int seedValue, float heightScale, float h) { int i, j; int stride; int oddline; int subSize; float ratio, scale; //只处理维数为2的整数次方的数组 if (!powerOf2(size) || (size==1)) { return; } /* subSize 以纵横向线段数计的数组维数 segments 以顶点数计的数组维数 */ subSize = size; size++; /* initialize random number generator */ srandom (seedValue); /* 设定我们的粗糙度常量 随机数始终在0.0到1.0范围内生成。 随机数将乘上scale,每次迭代后scale将乘上ratio以有效的减少随机数范围。*/ ratio = (float) pow (2.,-h); scale = heightScale * ratio; /* 设置前四个种子值。如一个4X4数组,我们将初始化下边*代表的点: * . . . * . . . . . . . . . . . . . . . * . . . * 按diamond-square算法的术语,这将给我们正方形。 数组四角赋相同的值,这使我们排列数组时能无缝的拼接起来*/ stride = subSize / 2; fa[(0*size)+0] = fa[(subSize*size)+0] = fa[(subSize*size)+subSize] = fa[(0*size)+subSize] = 0.f; /*现在依据棱锥种子值递增加入细节。 根据stride循环,每次循环末尾都会将stride减半, 直到stride为0时结束 */ while (stride) { /* 先用square数据生成diamond数据。 第一遍经过4X4矩阵时,已有数据为下边的X, 我们要生成*处的数据: X . . . X . . . . . . . * . . . . . . . X . . . X */ for (i=stride; i<subSize; i+=stride) { for (j=stride; j<subSize; j+=stride) { fa[(i * size) + j] = scale * fractRand (.5f) + avgSquareVals (i, j, stride, size, fa); j += stride; } i += stride; } /* 先用diamond数据生成square数据。 第一遍经过这段代码时,已有数据为下边的X, 我们要生成*处的数据: X . * . X . . . . . * . X . * . . . . . X . * . X i,j代表我们在数组中的(x,y)位置。我们想生成的第一个值位于(i=2,j=0), 用"oddline"及"stride"来增加j到所需值。 */ oddline = 0; for (i=0; i<subSize; i+=stride) { oddline = (oddline == 0); for (j=0; j<subSize; j+=stride) { if ((oddline) && !j) j+=stride; /* i and j are setup. Call avgDiamondVals with the current position. It will return the average of the surrounding diamond data points. */ fa[(i * size) + j] = scale * fractRand (.5f) + avgDiamondVals (i, j, stride, size, subSize, fa); /* To wrap edges seamlessly, copy edge values around to other side of array */ if (i==0) fa[(subSize*size) + j] = fa[(i * size) + j]; if (j==0) fa[(i*size) + subSize] = fa[(i * size) + j]; j+=stride; } } /* reduce random number range. */ scale *= ratio; stride >>= 1; } } /* * alloc1DFractArray - Allocate float-sized data points for a 1D strip * containing size line segments. */ float *alloc1DFractArray (int size) { /* Increment size (see comment in alloc2DFractArray, below, for an explanation). */ size++; return ((float *) malloc (sizeof(float) * size)); } /* * alloc2DFractArray - Allocate float-sized data points for a sizeXsize * mesh. */ float *alloc2DFractArray (int size) { /* For a sizeXsize array, we need (size+1)X(size+1) space. For example, a 2x2 mesh needs 3x3=9 data points: * * * * * * * * * To account for this, increment 'size'. */ size++; return ((float *) malloc (sizeof(float) * size * size)); } /* * freeFractArray - Takes a pointer to float and frees it. Can be used * to free data that was allocated by either alloc1DFractArray or * alloc2DFractArray. */ void freeFractArray (float *fa) { free (fa); } extern void draw2DLine (float, float, float, float); /* 注意,要使用draw1DFractArrayAsLines将数组绘制成线段要先自己定义 四个参数的draw2DLine,参数为两个端点的x,y坐标。*/ void draw1DFractArrayAsLines (float *fa, int size) { int i; float x, inc; inc = 2.f / size; x = -1.f; for (i=0; i<size; i++) { draw2DLine (x, fa[i], x+inc, fa[i+1]); x += inc; } } /* 注意,要使用draw2DFractArrayAsLines将数组绘制成网格要先自己定义 六个参数的draw3DLine,参数为两个端点的x,y,z坐标。*/ void draw2DFractArrayAsLines (float *fa, int size) { extern void draw3DLine (float, float, float, float, float, float); int i, j; float x, z, inc; const int subSize = size; size++; inc = 2.f / subSize; z = -1.f; for (i=0; i<size; i++) { x = -1.f; for (j=0; j<subSize; j++) { draw3DLine (x, fa[(i*size)+j], z, x+inc, fa[(i*size+j+1)], z); if (i<subSize) draw3DLine (x, fa[(i*size)+j], z, x, fa[((i+1)*size+j)], z+inc); x += inc; } if (i<subSize) draw3DLine (x, fa[(i*size)+j], z, x, fa[((i+1)*size+j)], z+inc); z += inc; } } /*法向计算,参数为三个顶点矢量,其叉乘积就是平面法向*/ static void genNormal (float x1, float y1, float z1, float x2, float y2, float z2, float x3, float y3, float z3, float *normal) { float len; float v1x, v1y, v1z; float v2x, v2y, v2z; v1x = x2 - x1; v1y = y2 - y1; v1z = z2 - z1; v2x = x3 - x1; v2y = y3 - y1; v2z = z3 - z1; normal[0] = v1y*v2z - v1z*v2y; normal[1] = v1z*v2x - v1x*v2z; normal[2] = v1x*v2y - v1y*v2x; len = (float) sqrt (normal[0]*normal[0] + normal[1]*normal[1] + normal[2]*normal[2]); normal[0] /= len; normal[1] /= len; normal[2] /= len; } /* 用一系列带有相应法向的三角形绘制高度图 这是个简化的例程以便达到目的并快速运行,并演示了如何遍历一个数组。 要使用这个例程,你必需自己定义"draw3DTriangle"函数。 它有12个参数:头9个是三个顶点的x,y,z世界坐标, 最后3个参数是单位长度面法向的x,y,z笛卡尔值。 x,z坐标会沿两轴覆盖-1.0到1.0的网格。相应的Y坐标将从分形数组中取出。 法向量将参考"上"或+y方向生成,顶点顺序应该使用右手法则与法向保持一致。 (右手法则:从法向量所指方向上看,顶点按逆时针排列) */ void draw2DFractArrayAsTriangles (float *fa, int size) { extern void draw3DTriangle (float, float, float, float, float, float, float, float, float, float, float, float); int i, j; float x, z, inc; const int subSize = size; size++; inc = 2.f / subSize; z = -1.f; for (i=0; i<subSize; i++) { x = -1.f; for (j=0; j<subSize; j++) { float normal[3]; genNormal (x, fa[(i*size)+j], z, x, fa[((i+1)*size)+j], z+inc, x+inc, fa[(i*size+j+1)], z, normal); draw3DTriangle (x, fa[(i*size)+j], z, x, fa[((i+1)*size)+j], z+inc, x+inc, fa[(i*size+j+1)], z, normal[0], normal[1], normal[2]); genNormal (x, fa[((i+1)*size)+j], z+inc, x+inc, fa[((i+1)*size)+j+1], z+inc, x+inc, fa[(i*size+j+1)], z, normal); draw3DTriangle (x, fa[((i+1)*size)+j], z+inc, x+inc, fa[((i+1)*size)+j+1], z+inc, x+inc, fa[(i*size+j+1)], z, normal[0], normal[1], normal[2]); x += inc; } z += inc; } } #ifdef __cplusplus } #endif
参 考
- Miller, Gavin S. P., The Definition and Rendering of Terrain Maps. SIGGRAPH 1986 Conference Proceedings (Computer Graphics, Volume 20, Number 4, August 1986).
- Voss, Richard D., FRACTALS in NATURE: characterization, measurement, and simulation. SIGGRAPH 1987 course notes #15.
- Peitgen, Jurgens, and Saupe, Chaos and Fractals, New Frontiers of Science. Springer-Verlag, 1992.
- Fournier, A., Fussell, D., Carpenter, L., Computer Rendering of Stochastic Models, Communications of the ACM, June 1982
源文档 <http://www.pinxue.net/OpenGL/dx/3d_fract_terrain.htm>