Delphi下的OpenGL开发

2016-02-19 12:49 9 1 收藏

给自己一点时间接受自己,爱自己,趁着下午茶的时间来学习图老师推荐的Delphi下的OpenGL开发,过去的都会过去,迎接崭新的开始,释放更美好的自己。

【 tulaoshi.com - 编程语言 】

OpenGL最初是由Silicon图形公司开发的底层图形库规范。你的系统中准确实现这个规范的部分,通常被称为OpenGL驱动,它允许你使用几何集合(点,线,多边形,图像等等)来描述你希望表现的场景。让肉眼观察起来较为舒适的中等规模场景,通常在毫秒级的速度上实现,这意味着该库文件有足够的能力来支持你创建一个生机勃勃的虚拟世界。
  
  OpenGL驱动一般以二进制库文件的形式提供。它能够动态的连接到你的程序中。在Windows平台上,它将是成为DLL的形式(在你的系统目录下检查opengl.dll)。自从Delphi能够使用任何DLL开始,它对OpenGL 3D编程的能力就像其他任何语言一样容易了。本文将帮助你获得在Delphi中进行OpenGL开发的有效知识。
  
  数学基础
  OpenGL拥有强大的数学基础,因此对它功能的限制完全取决于你的想象能力(译者注:没有做不到,只有想不到)。对于理解那些公理和引理,更好的是让我们立刻认识一个简单的3D坐标系统,它是3D编程中惯用的坐标系统。如下:
  
  
  Delphi下的OpenGL开发
  
  你应该如何理解你的屏幕(蓝色的方块)在场景中的放置位置呢?发出四条射线并形成屏幕的那个点,是该想象空间中的视点(point of view)。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指定视点到屏幕的距离(就好象“近剪贴板”(near clipping plane))同时25.0指定“远剪贴板”(far clipping plane)。任何近剪贴板前面的物体以及远剪贴板后面的物体都将不可见。当然,你能够任意摆弄这些数字,以使他们适合你需要的场景。
  
  从基本元素(primitive)到对象
  现在开始最有意思的部分:对象。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个单位。
  
  这是屏幕截图:
  Delphi下的OpenGL开发
  
  即使它看起来不象3D图形,但它是我们的初始块。在下面你可以看到这个例子的源代码。
  
  在你开始钻研代码前,还有些话要说。每次OpenGL编程,都包含一些初始化输出设备的OS设定(OS-specific)代码。如果你使用Win32,你将需要设置像素格式以及建立显示上下文环境脱离windows设备上下文环境。如果windows系统级编程你并不很在行,你可以把如下的代码作为模版使用。FormCreate中被调用函数的详细信息可以参考帮助文档。
  
  FILE: Tri.pas
  unit Tri;
  
  interface
  
  uses
  OpenGL, Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ExtCtrls, ComCtrls;
  
  type
  TForm1 = class(TForm)
  procedure FormCreate(Sender: TObject);
  procedure FormPaint(Sender: TObject);
  private
  procedure Draw; //Draws an OpenGL scene on request
  public
  end;
  
  var
  Form1: TForm1;
  
  implementation
  
  {$R *.DFM}
  
  procedure setupPixelFormat(DC:HDC);
  const
  pfd:TPIXELFORMATDESCRIPTOR = (
  nSize:sizeof(TPIXELFORMATDESCRIPTOR); // size
  nVersion:1; // version
  dwFlags:PFD_SUPPORT_OPENGL or PFD_DRAW_TO_WINDOW or
  PFD_DOUBLEBUFFER; // support double-buffering
  iPixelType:PFD_TYPE_RGBA; // color type
  cColorBits:24; // preferred color depth
  cRedBits:0; cRedShift:0; // color bits (ignored)
  cGreenBits:0; cGreenShift:0;
  cBlueBits:0; cBlueShift:0;
  cAlphaBits:0; cAlphaShift:0; // no alpha buffer
  cAccumBits: 0;
  cAccumRedBits: 0; // no accumulation buffer,
  cAccumGreenBits: 0; // accum bits (ignored)
  cAccumBlueBits: 0;
  cAccumAlphaBits: 0;
  cDepthBits:16; // depth buffer
  cStencilBits:0; // no stencil buffer
  cAuxBuffers:0; // no auxiliary buffers
  iLayerType:PFD_MAIN_PLANE; // main layer
  bReserved: 0;
  dwLayerMask: 0;
  dwVisibleMask: 0;
  dwDamageMask: 0; // no layer, visible, damage masks
  );
  var pixelFormat:integer;
  begin
  pixelFormat := ChoosePixelFormat(DC, @pfd);
  if (pixelFormat = 0) then
  exit;
  if (SetPixelFormat(DC, pixelFormat, @pfd) TRUE) then
  exit;
  end;
  
  procedure GLInit;
  begin
  // set viewing projection
  glMatrixMode(GL_PROJECTION);
  glFrustum(-0.1, 0.1, -0.1, 0.1, 0.3, 25.0);
  // position viewer
  glMatrixMode(GL_MODELVIEW);
  glEnable(GL_DEPTH_TEST);
  end;
  
  procedure TForm1.FormCreate(Sender: TObject);
  var DC:HDC;
  RC:HGLRC;
  i:integer;
  begin
  DC:=GetDC(Handle); //Actually, you can use any windowed control here
  SetupPixelFormat(DC);
  RC:=wglCreateContext(DC); //makes OpenGL window out of DC
  wglMakeCurrent(DC, RC); //makes OpenGL window active
  GLInit; //initialize OpenGL
  end;
  
  procedure TForm1.Draw;
  const S=1.0; D=5.0;
  begin
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
  glLoadIdentity;
  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);
  end;
  
  procedure TForm1.FormPaint(Sender: TObject);
  begin
  Draw;
  end;
  
  end.
  FILE: Tri.dfm
  object Form1: TForm1
  BorderStyle = bsDialog
  Caption = 'BASIC OpenGL Program'
  ClientHeight = 318
  ClientWidth = 373
  OnCreate = FormCreate
  OnPaint = FormPaint
  end
  
  
  
  3D历险
  好了,让我们开始真正的3D吧。将先前的代码作为框架,我们增加一些画线的代码建立一个带阴影面的四面体。应该如何用基本图形元素来构建呢?我们使用四个三角形。一个在底部,另外三个作为侧面。这里就是生成他们的代码:
  procedure 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:TGLArrayf3=(-D, 0, -H1); //bootom left
  a2:TGLArrayf3=( D, 0, -H1); //bootom right
  a3:TGLArrayf3=( 0, 0, H2); //bootom back
  a4:TGLArrayf3=( 0, HY, 0); //top
  begin
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
  glLoadIdentity;
  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);
  end;
  
  虽然看起来有点复杂,不过当你面对下面这张图时,它就很容易理解了。
  Delphi下的OpenGL开发
  
  我们定义顶点a1 – a4同时依据4个顶点位置建立指定的三角形。当你定义自己的三角形(或者其他的多边形),请使用如下的规则:始终按照逆时针顺序排列定点序号,就像你正在外部观看侧面一样。通过这个规则,我们可以指定指定a1-a2-a4,a1-a3-a2(仰视),a2-a3-a4和a3-a1-a4。
  
  现在就替换Tri.pas中TForm1.Darw()部分,程序运行的效果不会体现出过多的变化。它看起来仍然不象三维图形。这是因为我们还没有设定任何光源。
  
  LIGHTS! CAMERA! OPENGL!
  在OpenGL中光源模式有两部分:光源自身(颜色,强度等等)和对象材质。材质,依次包括颜色,一些物理参数(比如不透明性光泽性)以及纹理。深入其中,这会是一个巨大的世界,我们将一步步地接近。
  
  定义一个光源相当容易。
  procedure GLInit;
  const
  light0_position:TGLArrayf4=( -8.0, 8.0, -16.0, 0.0);
  ambient: TGLArrayf4=( 0.3, 0.3, 0.3, 0.3);
  begin
  // set viewing projection
  glMatrixMode(GL_PROJECTION);
  glFrustum(-0.1, 0.1, -0.1, 0.1, 0.3, 25.0);
  // position viewer */
  glMatrixMode(GL_MODELVIEW);
  glEnable(GL_DEPTH_TEST);
  
  // set lights
  glEnable(GL_LIGHTING);
  glLightfv(GL_LIGHT0, GL_POSITION, @light0_position);
  glLightfv(GL_LIGHT0, GL_AMBIENT, @ambient);
  glEnable(GL_LIGHT0);
  end;
  
  代码内的两个常量是必须的。一个定义光源位置(位于视点的后面的左上角),另外一个定义环境光线。这将产生少量的散乱光线,使你能够看到完全位于阴影中的某些物体。
  
  虽然你可以使用光照设定光源,可是物体仍然没有绘制阴影。这是因为OpenGL需要知道你指定的每个多边形的“normal”以便进行光线计算(Normal是一个与表面正交的向量) 。如果你没有自己的向量函数库,可以使用以下方法计算三角形中三个顶点的normal。这个函数是以定点逆时针排列为基础的,因为normal是一个向量的叉积,如果你不遵守该规则,会使向量指向四面体内部。
  
  function getNormal(p1,p2,p3:TGLArrayf3):TGLArrayf3;
  var a,b:TGLArrayf3;
  begin
  //make two vectors
  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];
  //calculate cross-product
  result[0]:=a[1]*b[2]-a[2]*b[1];
  result[1]:=a[2]*b[0]-a[0]*b[2];
  result[2]:=a[0]*b[1]-a[1]*b[0];
  end;
  
  使用这个函数,就可以设定所有的计算光线必需的信息了:
  procedure 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:TGLArrayf3=(-D, 0, -H1);
  a2:TGLArrayf3=(D, 0, -H1);
  a3:TGLArrayf3=(0, 0, H2);
  a4:TGLArrayf3=(0, HY, 0);
  var n1, n2, n3, n4: TGLArrayf3; //normals
  begin
  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 or GL_DEPTH_BUFFER_BIT);
  glEnable(GL_NORMALIZE);
  glShadeModel(GL_FLAT);
  glCullFace(GL_BACK);
  glLoadIdentity;
  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);
  end;
  
  这便是以上代码的效果:
  Delphi下的OpenGL开发
  现在,使用一点Delphi VCL提供的的东西。在窗体上放一个Timer,指定一个类成员“angle:single”并在每次Timer触发时让他增加1:
  procedure TForm1.Timer1Timer(Sender: TObject);
  begin
  angle:=angle+1.0;
  Draw;
  end;
  
  离一个充满生气的OpenGL仅差条线:
  
  glRotatef(angle, 0.0, 1.0, 0.0);
  
  把它放在glBegin()内三角开始绘制前的位置上,这样你的阴影部分就可以旋转了,至此,一切结束。

来源:http://www.tulaoshi.com/n/20160219/1602048.html

延伸阅读
标签: ASP
  随着网络技术的发展和Internet的普及,Browser/Server在软件开发中已成为主流,笔者在开发一个ERP系统时,就采用了B/S软件模式,具体架构为SQL Server+IIS+IE网页采用的是Active Server Page文件。由于系统涉及大量的数据操作和查询,若纯粹采用ASP脚本语言编写势必造成效率低下,为了提高系统的整体效率和安全性,笔者采用了AS...
如何降低软件开发维护成本、提高开发效率、减小需求变更对系统的影响以及延长系统的生命周期是每个软件开发者都在思索的问题。然而传统的开发途径中上面的问题总是很难完美的解决。 MDA(模型驱动架构,Model Driven Architecture)是由OMG组织提出的新的软件开发架构,他相对于传统的开发方式有了很大的变化,软件开发的驱动力不再来自于传统...
用DELPHI开发AUTHORWARE的u32 函数功能是AUTHORWARE最突出的特征,利用AUTHORWARE提供的系统函数能够完成一些复杂的控制任务。对于一些特殊的任务,AUTHORWARE允许用户自己定义函数,使得程序设计具有更大的灵活性。对于windows系统来说,自定义函数是以动态链接库(DLL)文件存储的,因此存储自定义函数的文件与当前交互式应用程序文件是分...
标签: Delphi
分布式COM(以下简称DCOM)的出现给我们轻松的创建分布式应用提供了机会;我们可以完全不去理会低级别的Windows Sockets(DCOM通过MS-RPC让客户与对象进行通信,幸运的是要开发COM应用,开发者几乎可以不去理会MS-RPC)而开发出功能强大、偶合性低(功能模块相对独立,很好的发挥了OO的思想)、易于部署的分布式计算系统。 本文我们...
标签: Delphi
  提到 RPG (角色扮演游戏,Role Play Game),在座各位恐怕没有不熟悉的。从古老经典的 DOS 版《仙剑奇侠传》到新潮花哨的《轩辕剑》系列,无不以曲折优美的故事情节,美丽可人的主角,悦耳动情的背景音乐,震撼了每一个玩家的心灵。而说到 RPG,就不能不提 DirectX,因为 PC 上大部分的 RPG 都是用这个冬冬开发的。早在《轩辕剑叁外...

经验教程

518

收藏

57
微博分享 QQ分享 QQ空间 手机页面 收藏网站 回到头部