OpenGL 最初是由 Silicon 图形公司开发的底层图形库规范。你的系统中准确实现这个规范的部分 , 通常被称为 OpenGL 驱动 , 它允许你使用几何集合 (点 , 线 , 多边形 , 图像等等) 来描述你希望表现的场景。让肉眼观察起来较为舒适的中等规模场景 , 通常在毫秒级的速度上实现 , 这意味着该库文件有足够的能力来支持你创建一个生机勃勃的虚拟世界。
OpenGL 驱动一般以二进制库文件的形式提供。它能够动态的连接到你的程序中。在 Windows 平台上 , 它将是成为 Dll 的形式 (在你的系统目录下检查 OpenGl.dll) 。自从 Delphi 能够使用任何 DLL 开始 , 它对 OpenGL 3D 编程的能力就像其他任何语言一样容易了。本文将帮助你获得在 Delphi 中进行 OpenGL 开发的有效知识。
数学基础
OpenGL 拥有强大的数学基础 , 因此对它功能的限制完全取决于你的想象能力 (译者注 : 没有做不到 , 只有想不到) 。对于理解那些公理和引理 , 更好的是让我们立刻认识一个简单的 3D 坐标系统 , 它是 3D 编程中惯用的坐标系统。如下:
你应该如何理解你的屏幕 (蓝色的方块) 在场景中的放置位置呢?发出四条射线并形成屏幕的那个点 , 是该想象空间中的视点 (point de vue) 。opengl 让你调用两个简单的函数来定义这个场景
glMatrixMode (gl_projection);
Glfrustum (-0,1, 0,1, -0,1, 0,1, 0,3, 25,0);
在这个调用的过程中的 -0,1,0.1, -0,1,0.1 定义了这个可视屏幕的左上角和右下角坐标; 0,3 指定视点到屏幕的距离 (就好象 就好象 就好象 近剪贴板 近剪贴板 近剪贴板 近剪贴板 近剪贴板 近剪贴板 近剪贴板 近剪贴板 近剪贴板 近剪贴板 近剪贴板 近剪贴板 近剪贴板 近剪贴板 近剪贴板 近剪贴板 近剪贴板 近剪贴板 近剪贴板 近剪贴板 近剪贴板 近剪贴板 近剪贴板 近剪贴板 '' )) 同时 25,0 指定 指定 远剪贴板 远剪贴板 远剪贴板 远剪贴板 远剪贴板 远剪贴板 远剪贴板 远剪贴板 远剪贴板 远剪贴板 远剪贴板 远剪贴板 远剪贴板 远剪贴板 。任何近剪贴板前面的物体以及远剪贴板后面的物体都将不可见。当然 , 你能够任意摆弄这些数字 , 以使他们适合你需要的场景。
从基本元素 (primitif) 到对象
现在开始最有意思的部分 : 对象。opengl 仅仅支持以下几种基本几何图形 : 点 , 线和多边形。没有表面或者更高级的图形 (比如球状图形) 能被作为基本图形元素绘制。但是它们能够用多边形完美的模仿出来。随意看看现代 3D 游戏 , 你会发现它们完全由三角形建立。因此 , 我们不会被此限制所约束。
对象的绘制非常类似 Pascal 语言编程。每个块都应该被 Begin-end 包含着 , 更为确切的说是 glbegin () 和 glend () : :
const s = 1,0; D = 5,0;
...
glbegin (gl_triangles);
glvertex3f (-s, 0, d); glvertex3f (s, 0, d); glvertex3f (0, s, d);
Glend;
这是个简单的三角形。它距离你的视点有 5 个单位 , 自身高 1 个单位 , 宽 2 个单位。
:
即使它看起来不象 3D 图形 , 但它是我们的初始块。在下面你可以看到这个例子的源代码。
在你开始钻研代码前 , 还有些话要说。每次 OpenGL 编程 , 都包含一些初始化输出设备的 OS 设定 (spécifique au système d'exploitation) 代码。如果你使用 Win32 , 你将需要设置像素格式以及建立显示上下文环境脱离 Windows 设备上下文环境。如果 Windows 系统级编程你并不很在行 , 你可以把如下的代码作为模版使用。formCreate 中被调用函数的详细信息可以参考帮助文档。
Fichier: tri.pas
unité tri;
interface
usages
OpenGL, Windows, Messages, Sysutils, Classes, Graphiques, Contrôles, Formulaires, Dialogues,
Stdctrls, extctrls, comctrls;
taper
Tform1 = classe (tform)
Procédure FormCreate (expéditeur: tobject);
Procédure FormPaint (expéditeur: tobject);
privé
Procédure tirage; // dessine une scène OpenGL sur demande
publique
fin;
var
FORM1: TFORM1;
mise en œuvre
{$ R * .dfm}
procédure setuppixelformat (DC: HDC);
const
pfd: tpixelformatdescriptor = (
nSize: sizeof (tpixelformatDescriptor); // taille
Nversion: 1; // version
dwflags: pfd_support_opengl ou pfd_draw_to_window ou
Pfd_doublebuffer; // prend en charge le double tampon
iPixelType: pfd_type_rgba; // Type de couleur
ccolorbits: 24; // Profondeur de couleur préférée
crédits: 0; CredShift: 0; // des bits de couleur (ignorés)
CgreenBits: 0; Cgreenshift: 0;
CBlueBits: 0; CBlueshift: 0;
Calphabits: 0; Calphashift: 0; // Pas de tampon alpha
Caccumbits: 0;
CaccumredBits: 0; // pas de tampon d'accumulation,
caccumgreenbits: 0; // Accums Bits (ignoré)
CaccumblueBits: 0;
caccumalphabits: 0;
CDEPTHBITS: 16; // tampon de profondeur
CSTCHICTBITS: 0; // pas de tampon de pochoir
Cauxbuffers: 0; // Pas de tampons auxiliaires
iLayerType: pfd_main_plane; // couche principale
BRERSERVED: 0;
dwlayermask: 0;
Dwvisiblemask: 0;
DwdamageMask: 0; // Pas de couche, visibles, masques de dégâts
));
var pixelformat: entier;
commencer
pixelformat: = choosePixElformat (dc, @pfd);
if (pixelformat = 0) alors
sortie;
if (setPixelformat (dc, pixelformat, @pfd) <> true) puis
sortie;
fin;
procédure Glinit;
commencer
// Définir la projection
glMatrixMode (gl_projection);
Glfrustum (-0,1, 0,1, -0,1, 0,1, 0,3, 25,0);
// Visionneuse de position
glMatrixMode (GL_MODELVIEW);
GLENABLE (gl_depth_test);
fin;
Procédure TForm1.FormCreate (Sender: Tobject);
var dc: hdc;
RC: HGLRC;
I: entier;
commencer
Dc: = getdc (manche); // En fait, vous pouvez utiliser n'importe quel contrôle fenêtré ici
Setuppixelformat (DC);
Rc: = wglCreateContext (dc); // fait une fenêtre OpenGl de DC
WGLMAKECURRENT (DC, RC); // rend la fenêtre OpenGl active
GLINIT; // Initialiser OpenGL
fin;
Procédure tform1.Draw;
const s = 1,0; D = 5,0;
commencer
glClear (gl_color_buffer_bit ou gl_depth_buffer_bit);
gllolodIdentity;
gltranslatef (0,0, 0,0, -12,0);
glbegin (gl_triangles);
glvertex3f (-s, 0, d); glvertex3f (s, 0, d); glvertex3f (0, s, d);
Glend;
Swapbuffers (wglgetcurrentdc);
fin;
procédure tform1.formpaint (expéditeur: tobject);
commencer
Dessiner;
fin;
fin.
Fichier: tri.dfm
objet Form1: TForm1
Borderstyle = bsdialogic
Légende = «Programme OpenGL de base»
ClientHeight = 318
ClientWidth = 373
OnCreate = formCreate
Onpaint = forage
fin
3D 历险
好了 , 让我们开始真正的 3D 吧。将先前的代码作为框架 , 我们增加一些画线的代码建立一个带阴影面的四面体。应该如何用基本图形元素来构建呢?我们使用四个三角形。一个在底部 , : :
Procédure tform1.Draw;
const d = 1,5;
H1 = D / 1,732;
H2 = D * 1,732-H1; // d / h = tg (30) = 1 / sqrt (3)
Hy = 3,0;
const // vertexes
a1: tglaRayf3 = (- d, 0, -H1); // bootom à gauche
a2: tglaRayf3 = (d, 0, -H1); // bootom à droite
a3: tglaRayf3 = (0, 0, h2); // Bootom Back
a4: tglaRayf3 = (0, hy, 0); //haut
commencer
glClear (gl_color_buffer_bit ou gl_depth_buffer_bit);
gllolodIdentity;
gltranslatef (0,0, 0,0, -12,0);
glbegin (gl_triangles);
glvertex3fv (@ a1); glvertex3fv (@ a3); glvertex3fv (@ a2);
glvertex3fv (@ a1); glvertex3fv (@ a2); glvertex3fv (@ a4);
glvertex3fv (@ a2); glvertex3fv (@ a3); glvertex3fv (@ a4);
glvertex3fv (@ a3); glvertex3fv (@ a1); glvertex3fv (@ a4);
Glend;
Swapbuffers (wglgetcurrentdc);
fin;
虽然看起来有点复杂 , 不过当你面对下面这张图时 , 它就很容易理解了。
我们定义顶点 A1 - A4 同时依据 4 个顶点位置建立指定的三角形。当你定义自己的三角形 (或者其他的多边形) , 请使用如下的规则 : 始终按照逆时针顺序排列定点序号 , 就像你正在外部观看侧面一样。通过这个规则 , 我们可以指定指定 A1-A2-A4 , A1-A3-A2 (仰视) , A2-A3-A4 和 A3-A1-A4。
现在就替换 tri.pas 中 tform1.darw () 部分 , 程序运行的效果不会体现出过多的变化。它看起来仍然不象三维图形。这是因为我们还没有设定任何光源。
LUMIÈRES! CAMÉRA! OpenGL!
在 OpenGl 中光源模式有两部分 : 光源自身 (颜色 , 强度等等) 和对象材质。材质 , 依次包括颜色 , 一些物理参数 (比如不透明性光泽性) 以及纹理。深入其中 , 这会是一个巨大的世界 , 我们将一步步地接近。
定义一个光源相当容易。
procédure Glinit;
const
LIGHT0_POSITION: TGLArrayf4 = (-8,0, 8.0, -16,0, 0,0);
Ambient: TGLArrayf4 = (0,3, 0,3, 0,3, 0,3);
commencer
// Définir la projection
glMatrixMode (gl_projection);
Glfrustum (-0,1, 0,1, -0,1, 0,1, 0,3, 25,0);
// Positionner la visionneuse * /
glMatrixMode (GL_MODELVIEW);
GLENABLE (gl_depth_test);
// Définit les lumières
GLENABLE (GL_LIGHTING);
GLlightfv (GL_LIGHT0, GL_POSITION, @ LIGHT0_POSITION);
GLlightfv (gl_light0, gl_ambient, @ambient);
GLENABLE (GL_LIGHT0);
fin;
代码内的两个常量是必须的。一个定义光源位置 (位于视点的后面的左上角) , 另外一个定义环境光线。这将产生少量的散乱光线 , 使你能够看到完全位于阴影中的某些物体。
虽然你可以使用光照设定光源 , 可是物体仍然没有绘制阴影。这是因为 OpenGl 需要知道你指定的每个多边形的 «Normal» 以便进行光线计算 (normal 是一个与表面正交的向量) 。如果你没有自己的向量函数库 , 可以使用以下方法计算三角形中三个顶点的 NORMAL 。这个函数是以定点逆时针排列为基础的 , 因为 NORMAL 是一个向量的叉积 , 如果你不遵守该规则 , 会使向量指向四面体内部。
Fonction GetNormal (P1, P2, P3: TGLArrayf3): TGLArrayf3;
var a, b: tglaRayf3;
commencer
// fait deux vecteurs
a [0]: = p2 [0] -p1 [0]; a [1]: = p2 [1] -p1 [1]; a [2]: = p2 [2] -p1 [2];
b [0]: = p3 [0] -p1 [0]; b [1]: = p3 [1] -p1 [1]; b [2]: = p3 [2] -p1 [2];
// calculer le produit transversal
résultat [0]: = a [1] * b [2] -a [2] * b [1];
résultat [1]: = a [2] * b [0] -a [0] * b [2];
résultat [2]: = a [0] * b [1] -a [1] * b [0];
fin;
使用这个函数 , : :
Procédure tform1.Draw;
const d = 1,5;
H1 = D / 1,732;
H2 = D * 1,732-H1; // d / h = tg (30) = 1 / sqrt (3)
Hy = 3,0;
const // vertexes
a1: tglaRayf3 = (- d, 0, -H1);
a2: tglaRayf3 = (d, 0, -H1);
a3: tglaRayf3 = (0, 0, h2);
a4: tglaRayf3 = (0, hy, 0);
var n1, n2, n3, n4: tglaRayf3; // Normales
commencer
n1: = getNormal (a1, a3, a2);
n2: = getNormal (a1, a2, a4);
n3: = getNormal (a2, a3, a4);
n4: = getNormal (a3, a1, a4);
glClear (gl_color_buffer_bit ou gl_depth_buffer_bit);
GLENABLE (GL_NORMALISE);
GlShadeModel (GL_FLAT);
glCullFace (GL_BACK);
gllolodIdentity;
gltranslatef (0,0, 0,0, -12,0);
glbegin (gl_triangles);
glNormal3fv (@ n1);
glvertex3fv (@ a1); glvertex3fv (@ a2); glvertex3fv (@ a3);
glNormal3fv (@ n2);
glvertex3fv (@ a1); glvertex3fv (@ a2); glvertex3fv (@ a4);
glNormal3fv (@ n3);
glvertex3fv (@ a2); glvertex3fv (@ a3); glvertex3fv (@ a4);
glNormal3fv (@ n4);
glvertex3fv (@ a3); glvertex3fv (@ a1); glvertex3fv (@ a4);
Glend;
Swapbuffers (wglgetcurrentdc);
fin;
:
现在 , 使用一点 Delphi VCL 提供的的东西。在窗体上放一个 Timer , 指定一个类成员 指定一个类成员 指定一个类成员 指定一个类成员 `` Angle: simple "并在每次 Timer 触发时让他增加 1:
Procédure TForm1.Timer1Timer (expéditeur: TOBject);
commencer
angle: = angle + 1,0;
Dessiner;
fin;
离一个充满生气的 OpenGl 仅差条线 :
glrotatef (angle, 0,0, 1,0, 0,0);
把它放在 glbegin () 内三角开始绘制前的位置上 , 这样你的阴影部分就可以旋转了 , 至此 , 一切结束。