# 三维世界

[ 主页 ] [上一层]

到目前为止，本书已经介绍了Windows下制作图像动画的技术。现在转向对三维图形介绍，三维图形是许多多媒体应用程序和计算机游戏的主体部分。本章将开发一些C＋＋类来表示三维形体，并且把这些类用到一个显示三维形体的例程中。尽管计算机图形学使用射线追踪技术可以生成真实画面的显示，但本章的重点只放在如何显示由三边形成四边形定义的，并且实际可行的复杂形体。本章在给出三维坐标变换和向量运算的简单描述之后，接着给出表示三维形体的C＋＋类。了解三维图形的详细情况，特别是有关空间几何学的问题。
要描绘三维物体就得有三维坐标系。描绘三维坐标系，最好先从二维坐标系统开始，您可能很熟悉二维笛卡尔坐标系。如果把书页想象成一个平面，在定义二维坐标系时可以先选定一点作为原点。按通常的做法，选取书页的左下角作为原点。然后可以把书页下底边沿作为X轴，左边沿作Y铀。请注意两坐标轴是相互垂直的。一旦定义了原点和坐标轴，就可以用指定坐标的方法指定页上的任意一点的位置，点的坐标为分别沿X轴和Y轴的距离。由下图可见X坐标是该点沿X轴的距离Y坐标是该点沿Y轴的距离。点在平面上的位置常表示为（x，y），其中x和y就是X坐标和Y坐标。
通常所用的三维笛卡尔坐标系是二维笛卡尔坐标系的扩展。三维坐标系又另加了一轴——Z轴，它垂直于x轴和Y轴决定的平面。如果把x轴的正方向想象为书页的右方向，把Y轴的正方向想象为书页向上万向，那么z轴的正方向即可以指向书页内，又可以指出书页外。按右手法则，Z轴正方向应该是指出书页外（见图）。
在图形学和工程学的规则中，右手法则是表达和操作三维坐标的标准数学约定。不过只要您清楚所选坐标系的含义，并且做到前后一致，那么具体选择什么坐标系其实并不重要。
建立三维物体模型的一个方法是指明构成物体边界的各个表面。尽管大多数现实物体由曲面构成，但是使用简单的多边形去逼近物体边界还是可能的。本章使用多边形集来表示物体边界，当然许多简单物体的边界本来就是平面，像立方体，甚至是有曲面的物体也可以由大量平面逼近，如圆柱体和球体。尽管如此，本书重点不是在实际物体模型上，而是可用在游戏中的足够简单的模型。
多边形界定的物体在许多计算机游戏中用得很广，例如使用三维动画的飞行模拟器，在绘出适当的阴影后，多边形也可经获得足够的真实效果，并且它们在操作时计算都很有效。
多边形是由它的角——也即项点——定义的。每个项点是一个三维点。与二维点类似，三维点也是由X，Y，Z三个坐标表示的，通常写成（x，y，z)。要用多边形定义物体，就得先从取物体的三维坐标系开始。下图表明如何定义一个单位立方体——即各边长度均为“1”的立方体。
当您在构造复杂的三维景物时，也许希望把同一物体复制到景物中的不同位置，也许还要对物体进行比例变换和旋转，要完成这些功能，就需要进行三维坐标变换。首先为景物定义坐标系。然后对单个物体分别进行比例变换、旋转或平移，以分别达到各个物体不同的要求。例如我们下面所看到的这包含一个圆柱体和一个圆锥体的三维景物——每个三维物体进行不同的方式的比例变换，旋转和定位，我们可以将他们通过变形组合成一支毛笔。
用来在计算机上构造3D模型的常用方法是B-rep和CSG方法：
1．B-rep造型
B－rep是有界表现（boundary－representation）的缩写（头字语）。3- D子程序仅仅描绘对象的外层表皮——边界，B－rep模型的表面（外表）由小平面或多边形构成。小平面群可以结合在一起建立不同的形状、曲线或复杂的模型。
B－rep造型不仅容易在一台个人计算机上实现，而且它是快速的，并能产生现实主义的想像力。隐藏的表面迁移可使用不同的算法来实现。
B－rep技术具有限制之处。将两个模型结合在一起或通过一个现有模型来钻出一个孔（hole）从数学上是困难的。B－rep模型算法仅考虑对象的边界，并不是实体容量或内部文章。
2．CSG造型
CSG是结构实体几何学（constructive solid geometry）的缩写（头字语）。CSG模型建立起3－D实体，诸如立方体、平行六面体、柱体、球体等等。由于这些子对象的每一种也是一个3－D实体，这些子对象可被组合一起产生更复杂的甘象。CSG考虑一个对象的体积结构，所以软件可在模型中钻一个孔，或者执行其它的操作。一个柱体代表想要的孔，而在数学上是从模型中减去。这类3-D逻辑操作称为一种3-D布尔（Boolean）操作或欧拉（Euler）操作。
CSG模型化用于这样的应用方面，即重量、质量、惯性矩、密度和其它基本工程的要素是重要的。CSG模型化是真正的实体模型化，而且不像B－rep造型，CSG是非常耗时的和存贮器集中的（内存大）。
图形用户输入
3D图形软件经常给用户这种能力，在建立3D模型期间同软件发生交互作用。这种创造的交互作用常常取决于压延（extrusion）和子对象。
1．旋转（Revolve）、压延（extrude）和弯曲（sweep）
3D函数relvolve、extrude和sweep用于从一个2D外形构造3D对象。用户指定一个2D形状，然后指出沿什么方向旋转外形以及旋转多少度。软件负责产生3D对象。这个旋转函数对于B－rep和CSG模型两者都能很好工作。
生更复杂的甘象。CSG考虑一个对象的体积结构，所以软件可在模型中钻一个孔，或者执行其它的操作。一个柱体代表想要的孔，而在数学上是从模型中减去。这类3-D逻辑操作称为一种3-D布尔（Boolean）操作或欧拉（Euler）操作。
CSG模型化用于这样的应用方面，即重量、质量、惯性矩、密度和其它基本工程的要素是重要的。CSG模型化是真正的实体模型化，而且不像B－rep造型，CSG是非常耗时的和存贮器集中的（内存大）。
图形用户输入
3D图形软件经常给用户这种能力，在建立3D模型期间同软件发生交互作用。这种创造的交互作用常常取决于压延（extrusion）和子对象。
1．旋转（Revolve）、压延（extrude）和弯曲（sweep）
3D函数relvolve、extrude和sweep用于从一个2D外形构造3D对象。用户指定一个2D形状，然后指出沿什么方向旋转外形以及旋转多少度。软件负责产生3D对象。这个旋转函数对于B－rep和CSG模型两者都能很好工作。
3D图形软件经常给用户这种能力，在建立3D模型期间同软件发生交互作用。这种创造的交互作用常常取决于压延（extrusion）和子对象。
1．旋转（Revolve）、压延（extrude）和弯曲（sweep）
3D函数relvolve、extrude和sweep用于从一个2D外形构造3D对象。用户指定一个2D形状，然后指出沿什么方向旋转外形以及旋转多少度。软件负责产生3D对象。这个旋转函数对于B－rep和CSG模型两者都能很好工作。
extrude函数也取一个2D外形并转动它成为一个3D对象。用户规定向前延伸或挤压该外形有多么深。extrude函数也工作于B－rep和CSG造型两者。
sweep函数类似于extrude，除了延伸沿着一条曲线发生而不是像extrude函数的直线以外。
2．二次原体（Quadric primitives）
Revolve、extrude和sweep全部都依赖于用户的输入。某些软件采取不同的途径，在一个3D工具套件（toolkit）中提供准备使用的子对象。用户选择一个希望的子对象井然后通知3D软件子对象定位在何处。典型的子对象也称为二次原体，包括柱体、锥体、球体、双曲线体、抛物线体、平行六面体、楔形体、棱锥体、层次体、圆环等。用户可处理每个原体的大小和比例。
3．曲面（curved surfaces）
平滑曲面可由B－rep和CSG造型系统两者来构成。一个直纹曲面是通过在两条曲线之间构造一组直线而建立的曲面。这两条线通常是参数曲线，其最终点和控制点已被最终用户所规定。一个立方体转接（patch）曲面是一个由四个弯曲的边缘为界的曲面。
4．欧拉（Euler）操作
欧拉操作也称为3D布尔操作，模拟真实世界的工作方式。欧拉操作包括结合（joining）、相交（intersection）和相减（subtraction）。
结合表明两个实体彼此连结。相交表示当两个实体被连结时从共有容积所得到的实体。相减意指从另一个实体中移去一个实体的容积。
利用3D影像的C＋＋图形程序使用xyz坐标来描述形状和环境。x 维描述左方和右方测量，y坐标描述上方和下方测量。z 坐标描述近、远测量。
（l）对象和世界坐标
对象坐标是描述3D对象基本形状的xyz维。世界坐标是描述对象的xyz坐标，当对象被放置在一个指定的3D环境中某个位置和旋度上。所以称为世界坐标是因为3D环境被称为3D世界。
（2）摄影机和彩像面坐标
摄影机（Camera）坐标是xyz坐标，它描述该对象对于一个观察者如何出现在3D环境中的指定位置。摄影机位置被称为视点（观察点）（Viewpoint）。
影像面坐标是xy坐标，它将出现在一个两维的设置在摄影机和3D对象之间的影像面（或窗口）上。
（3）屏幕坐标
屏幕坐标是x y坐标，它能够在计算机显示屏幕上报绘出来。屏幕坐标也称为显示器（display）坐标，它是将影像面定标以适合显示屏幕的结果。
在这个3D操作过程中计算每组坐标都包括从矩阵数学导出的正弦（sine）和余弦（cosine）公式。
除了以基本图形编程技术为基础的实体之外，希望建立一个3D图形程序的任何程序员都需要了解3D编程概念。
就像目测图形广泛领域内的任何其它训练一样，3D图形具有它自己的体系。下面我们介绍这些3D编程术语：
（l）造型（Modeling）
造型就指描绘3D对象的形状的作用（行动）。造型本身牵涉到获取正确的形状和体积。
坐标是xyz测量，它表示在模型上的一个特定的点。坐标系统意指用于测量宽度、高度和深度的xyz轴系统——3D的三维。许多矛盾的坐标系统在今天使用着。
（2）扮刷（Rendering）
粉刷意指用一种适合于布景中光源方式对3D模型涂阴影或涂彩色。隐藏面迁移和可视面检测适用于描绘3D对象的技术，因此被别的对象面所隐藏的表面或背后面正确地被画出或被放弃（当情况需求时）。
实体造型参照这佯的3D算法，它将每一个对象作为一个有质量的实体来处理。结构实体几何学（CSG）使用实体造型技术。B-rep造型使用小平面或多边形从对象的表层或边界来构造实体。建立和处理3D模型
3D图形依赖于xyz坐标，它被用来描述3D空间中的唯一位置。然而，存在不同类型的3D空间。一个模型必须跟随通过每种类型3D空间的一条展开的路径，直到完成的模型能在计算机屏幕上显示出来为止。
通过上面的介绍，我们了解到：要实现从二维到三位的转换，不但要经过繁杂的数学转换、矩阵运算；为了在屏幕上生成这些三维图形，还需要建立大量的图形函数。所以，通常一个程序设计者，在编制三维图形程序时，都先建立功能齐备的数学函数库和图形函数库。
一般来说，数学函数库中应包含三大部分函数：
数学函数，这部分函数用来对数字进行运算和操作；
向量和矩阵函数，这些函数用来生成并控制向量，三维物体的生成大多由此部分函数完成；
仿射变换，这些函数使用一个4×4的矩阵对点的位置进行转换，也就是平常所说的平移、旋转等函数。
这里我们介绍一个库函数中较常见的一个函数——向量旋转函数：void Rotate3D (int m， float Theta， Matx4×4 A)
{kk1}
int m1，m2；
float c，s；
ZeroMatrix(A)；
//使一个4×4的矩阵中所有元素为0
A[m-1，m-1]=1．0；
A[3，3]=1．0；
m1=(m%3)+1；
m2=(m1%3)；
m1-=1；
c=CosD(Theta)；
//求用度指定的角的余弦
s=SinD(Theta)；
//求用度指定的角的正弦
A[m1，m1]=c；
A[m1，m2]=s；
A[m2，m2]=c；
A[m2，m1]=-s；
}
Rotate3D函数为实现一个向量的旋转生成一个矩阵。我们可以看到在定义此函数时，用到了函数库中前面定义的更简单的函数，使得函数定义十分容易。可见建立一个完备的函数库使用起来会非常方便，而且通过函数由简单到复杂的逐步建立，生成一个函数库并不是一件难事。读者可参照上面讲到的步骤，根据自己绘图的需要，由简单的数学函数、矩阵生成开始，通过这些很小的函数去堆积成具有很强功能的函数。
上一节我们介绍了数学函数库的建立方法，这里，让我们再来看一下如何建立图形函数库。
图形函数库的建立要比数学函数库的建立复杂一些，它不但要求包括画点画线的函数，还要解决信息的存储问题，颜色的调用和对平面作复杂的数学模型转换。
建立图形函数库也主要应从以下几个方面入手：
预置一个数组，确定转换信息存储地址所需要的偏移量。这样可以减少绘图时的计算量。
预制调色板数组，并在寄存器中指定合适的显示内存地址。并结合显示硬件编制合适的色彩调用程序。还应注意屏幕的初始化。
定义一些最基本的图形绘制函数，如画点、画线、画矩形、圆形等。
最复杂的是最后一步，你可能需要建立基于复杂的数学公式的图形转换，对前面提到过的那些图形特殊处理，甚至需要构造复杂的数学曲面。当然这样，你在建立数学函数库时也要费一番功夫。
当然，如果你只是做一个很简单的图形设计，则完全没必要这么麻烦，你可以不必下大功夫去管信息的合理存储，色彩的调用（注意，屏幕的初始化却是必不可少的）。你只需构造几个简单的画图函数，其余工作在主函数中以简练的语言来实现。
这里，我们来看一个初始化屏幕的例子：
void InitGrapics( )
{kk1}
Xres=MaxXres；
Yres=MaxYres；
PreCalc( )；
//预制信息存储偏移量的数组
SetMode(19)；
//设置显示方式
ClearPalette(Color)；
//将调色板中红、绿、蓝的值清零
InitPalette(Color)；
//调整颜色强度，设置颜色标志
SetPalette(Color)；
//将定义颜色传送到寄存器
}
对于简单图形的实现，在图像编程一章中已作了介绍，这里不再另外讲解。
以上我们介绍了三维图形设计的基本概念和常用方法，下面我们将以几个程序来看一下绘制三维图形常用的一些语句和算法等。首先我们来看一下这个小程序，运行下面这个程序将绘出一个具有立体感的网状图形：
include<graphics.h>
main()
{kk1}
int graphdriver=DETECT,graphmode;
int a=100;
int b=10;
int x,y;
int i;
initgraph(&graphdriver,&graphmode,"");
x=y=a;
for(i=0;i<11;i++)
{kk1}
asteroid(x,y);
x+=b;
y-=b;
}
x=y=a;
for(i=0;i<11;i++)
{kk1}
asteroid(x,y);
x-=b;
y+=b;
}
getch();
closegraph();
asteroid(x,y)
int x,y;
{kk1}
line(320,100-y/2,320-x,100);
line(320-x,100,320,100+y/2);
line(320,100+y/2,320+x,100);
line(320+x,100,320,100-y/2);
}
下面我们再来看一下这个椭圆子程序的设计，根据数据关系式，用C语言编写椭圆子程序，清单如下：
#define rad 3. 1415926/180
void elipse (x0,y0,a,b,start,bfe,efe,inc)
int inc;
float x0,y0,a,b,start,bfe,efe;
{kk1}
float sta,bf,ef,dt,t,ang,sum,xb,yb,x,y;
int i;
sta=start*rad； // 角度化为弧度
ef = efe*rad；
bf=bfe*rad；
dt=(ef-bf)/(float)inc； //dt为分段间隔角
t=sta+bf;
xb=x0+a*cos(t)+(a-b)*sin(bf)*sin(sta);
//计算起点坐标
yb=yo+a*sin(t)-(a-b)*sin(bf)*cos(sta)；
move(xb,yb); //移到起点处
for (i=1;i<=inc;i++)
{kk1}
ang=bf+i*dt;
sum=sta+ang; //计算椭圆上动点的坐标
x=xo+a*cos(sum)+(a-b)*sin(ang)*sin(sta);
y=yo+a*sin(sum)-(a-b)*sin(ang)*cos(sta)；
drawto(x,y); }} //分段画椭圆
当start=0度时，画水平椭圆；
当start=60度时，画正面椭圆；
当start=120度时，画侧面椭圆；
这一章里，我们以C语言三维图形设计为主，介绍了三维图形设计的基本概念，各种造型技巧以及C语言实现方法。由于三维图形在转换过程中要用到大量的数学模型记公式，我们可以看到用编程的方法实现起来，并不是一件易事。虽然，在建立了完备的图形程序库后，对于一个实体建模，可以用很少的语句在主函数中实现。但构造程序库的工作量也不小，而且，若要实现更复杂的图形处理，就需要增加程序库中的函数。可见，用编程来建立三维世界，不但费时费力，也极不方便。
随着计算机图形技术的发展，三维图形处理软件越来越多，功能也越来越强。通过这些可视化软件，我们可以很方便的做出各种各样的三维物体，并对其随心所欲的进行移动、旋转、变形。你也可以把更多的时间花在图形效果的处理，制做出精美的三维立体图像。在下一章里，我们就会看到如何用这些软件制作动人的计算机艺术画像。

返回到上面