OpenGL 最初由 Silicon Graphics 公司開發,是一套跨平台的底層圖形庫規範。作為業界標準的 3D 圖形 API,它透過幾何圖元(點、線、多邊形、圖像等)描述場景,並利用硬體加速在毫秒級渲染中等規模場景,足以支撐生動逼真的虛擬世界構建。在 Windows 平台上,OpenGL 以動態連結庫形式提供,Delphi 完美支援 DLL 呼叫,使 3D 程式開發如同其他語言般直觀便捷。
OpenGL 擁有強大的數學基礎,功能限制僅取決於想像力。理解 3D 程式設計的第一步是掌握右手座標系統:X 軸向右、Y 軸向上、Z 軸指向觀察者。螢幕在場景中位於近剪裁平面,視點發出四條射線形成可視錐體。透過 glMatrixMode(GL_PROJECTION) 切換至投影矩陣模式,再呼叫 glFrustum(-0.1, 0.1, -0.1, 0.1, 0.3, 25.0) 定義透視投影:前四個參數設定近平面左、右、下、上邊界;0.3 為視點到近平面距離;25.0 為遠剪裁平面距離。超出此範圍的物體將不被渲染,開發者可根據場景需求自由調整參數。
OpenGL 僅支援點、線、多邊形三種基本圖元,但透過三角形組合可模擬任意複雜曲面——現代 3D 遊戲即完全由三角形構建。繪圖流程類似 Pascal 的 begin...end 區塊,以 glBegin() 和 glEnd() 包裹頂點資料。例如繪製一個距離視點 5 單位、高 1、寬 2 的三角形:
const S = 1.0; D = 5.0;
glBegin(GL_TRIANGLES);
glVertex3f(-S, 0, D); glVertex3f(S, 0, D); glVertex3f(0, S, D);
glEnd;
每個 OpenGL 程式都需進行作業系統層級的初始化:設定像素格式、建立渲染內容並關聯裝置內容。以下程式碼展示完整的 Delphi 視窗應用程式框架,包含像素格式描述符設定、OpenGL 狀態初始化(深度測試、投影矩陣)、WM_PAINT 訊息處理與雙緩衝交換:
procedure setupPixelFormat(DC: HDC);
const pfd: TPIXELFORMATDESCRIPTOR = (
nSize: SizeOf(TPIXELFORMATDESCRIPTOR); nVersion: 1;
dwFlags: PFD_SUPPORT_OPENGL or PFD_DRAW_TO_WINDOW or PFD_DOUBLEBUFFER;
iPixelType: PFD_TYPE_RGBA; cColorBits: 24; cDepthBits: 16;
iLayerType: PFD_MAIN_PLANE; bReserved: 0; ...);
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
glMatrixMode(GL_PROJECTION);
glFrustum(-0.1, 0.1, -0.1, 0.1, 0.3, 25.0);
glMatrixMode(GL_MODELVIEW);
glEnable(GL_DEPTH_TEST);
end;
procedure TForm1.FormCreate(Sender: TObject);
var DC: HDC; RC: HGLRC;
begin
DC := GetDC(Handle);
setupPixelFormat(DC);
RC := wglCreateContext(DC);
wglMakeCurrent(DC, RC);
GLInit;
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.Draw;
const D = 1.5; H1 = D / 1.732; H2 = D * 1.732 - H1; HY = 3.0;
const a1: TGLArrayf3 = (-D, 0, -H1); a2: TGLArrayf3 = (D, 0, -H1);
a3: TGLArrayf3 = (0, 0, H2); a4: TGLArrayf3 = (0, HY, 0);
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); // 側面1
glVertex3fv(@a2); glVertex3fv(@a3); glVertex3fv(@a4); // 側面2
glVertex3fv(@a3); glVertex3fv(@a1); glVertex3fv(@a4); // 側面3
glEnd;
SwapBuffers(wglGetCurrentDC);
end;
OpenGL 光照包含光源屬性(位置、顏色、強度)與材質屬性(顏色、光澤、透明度、貼圖)。啟用光照需三步驟:glEnable(GL_LIGHTING)、設定光源參數(glLightfv)、啟用特定光源(glEnable(GL_LIGHT0))。環境光確保陰影面仍可見。關鍵在於為每個多邊形指定法向量,供光照計算使用。以下函數根據三個頂點(逆時針序)計算法向量(叉積):
function getNormal(p1, p2, p3: TGLArrayf3): TGLArrayf3;
var a, b: TGLArrayf3;
begin
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];
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; HY = 3.0;
const 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;
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;
在視窗放置 Timer 元件,每次觸發增加旋轉角度並重繪。在 glBegin 前插入 glRotatef(angle, 0.0, 1.0, 0.0) 即可實現 Y 軸自轉,瞬間賦予靜態模型生命力:
procedure TForm1.Timer1Timer(Sender: TObject);
begin
angle := angle + 1.0;
Draw;
end;
至此,從環境建置、基本圖元、複雜建模、光照渲染到動畫互動,完整展示了 Delphi 下 OpenGL 3D 開發的核心流程與關鍵技術點。