我正在尝试创建一个自定义控件,该控件本质上将是OpenGL窗口。
在一些指南的帮助下,我已经完成了所有的设置和工作(至少看起来是这样),但是,当我调整父窗体的尺寸时,我注意到OpenGL图形已缩放/拉伸。
为了说明这一点,下图是它的外观:
调整表格的大小后,现在如下所示:
忽略顶部的OSD,因为这是我使用的屏幕录像机软件的一部分,该软件也会变形。
在这里,我添加了一个Gif,以更好地演示在调整窗体大小时发生的情况:
这是我的自定义控件的单位:
unit OpenGLControl;
interface
uses
Winapi.Windows,
System.SysUtils,
System.Classes,
Vcl.Controls;
type
TOpenGLControl = class(TCustomControl)
private
FDC: HDC;
FRC: HGLRC;
FOnPaint: TNotifyEvent;
protected
procedure SetupPixelFormat;
procedure GLInit;
procedure GLRelease;
procedure CreateHandle; override;
procedure Paint; override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property OnPaint: TNotifyEvent read FOnPaint write FOnPaint;
end;
implementation
uses
OpenGL;
{ TOpenGLControl }
constructor TOpenGLControl.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
end;
destructor TOpenGLControl.Destroy;
begin
GLRelease;
inherited Destroy;
end;
procedure TOpenGLControl.CreateHandle;
begin
inherited;
GLInit;
end;
procedure TOpenGLControl.SetupPixelFormat;
var
PixelFormatDescriptor: TPixelFormatDescriptor;
pfIndex: Integer;
begin
with PixelFormatDescriptor do
begin
nSize := SizeOf(TPixelFormatDescriptor);
nVersion := 1;
dwFlags := PFD_DRAW_TO_WINDOW or PFD_SUPPORT_OPENGL or PFD_DOUBLEBUFFER;
iPixelType := PFD_TYPE_RGBA;
cColorBits := 32;
cRedBits := 0;
cRedShift := 0;
cGreenBits := 0;
cGreenShift := 0;
cBlueBits := 0;
cBlueShift := 0;
cAlphaBits := 0;
cAlphaShift := 0;
cAccumBits := 0;
cAccumRedBits := 0;
cAccumGreenBits := 0;
cAccumBlueBits := 0;
cAccumAlphaBits := 0;
cDepthBits := 16;
cStencilBits := 0;
cAuxBuffers := 0;
iLayerType := PFD_MAIN_PLANE;
bReserved := 0;
dwLayerMask := 0;
dwVisibleMask := 0;
dwDamageMask := 0;
end;
pfIndex := ChoosePixelFormat(FDC, @PixelFormatDescriptor);
if pfIndex = 0 then Exit;
if not SetPixelFormat(FDC, pfIndex, @PixelFormatDescriptor) then
raise Exception.Create('Unable to set pixel format.');
end;
procedure TOpenGLControl.GLInit;
begin
FDC := GetDC(Handle);
if FDC = 0 then Exit;
SetupPixelFormat;
FRC := wglCreateContext(FDC);
if FRC = 0 then Exit;
if not wglMakeCurrent(FDC, FRC) then
raise Exception.Create('Unable to initialize.');
end;
procedure TOpenGLControl.GLRelease;
begin
wglMakeCurrent(FDC, 0);
wglDeleteContext(FRC);
ReleaseDC(Handle, FDC);
end;
procedure TOpenGLControl.Paint;
begin
inherited;
if Assigned(FOnPaint) then
begin
FOnPaint(Self);
end;
end;
end.
要进行测试,请创建一个新的应用程序,然后向表单中添加一个TPanel
,还创建表单OnCreate
和OnDestroy
事件处理程序,然后使用以下命令:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, OpenGLControl;
type
TForm1 = class(TForm)
Panel1: TPanel;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure OpenGLControlPaint(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
FOpenGLControl: TOpenGLControl;
implementation
uses
OpenGL;
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
FOpenGLControl := TOpenGLControl.Create(nil);
FOpenGLControl.Parent := Panel1;
FOpenGLControl.Align := alClient;
FOpenGLControl.Visible := True;
FOpenGLControl.OnPaint := OpenGLControlPaint;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FOpenGLControl.Free;
end;
procedure TForm1.OpenGLControlPaint(Sender: TObject);
begin
glViewPort(0, 0, FOpenGLControl.Width, FOpenGLControl.Height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glBegin(GL_TRIANGLES);
glColor3f(0.60, 0.10, 0.35);
glVertex3f( 0.0, 1.0, 0.0);
glVertex3f(-1.0,-1.0, 0.0);
glVertex3f( 1.0,-1.0, 0.0);
glEnd;
SwapBuffers(wglGetCurrentDC);
end;
end.
有趣的是,将FOpenGLControl
的父级设置为Form似乎可以正常工作,例如:
procedure TForm1.FormCreate(Sender: TObject);
begin
FOpenGLControl := TOpenGLControl.Create(nil);
FOpenGLControl.Parent := Form1;
FOpenGLControl.Align := alClient;
FOpenGLControl.Visible := True;
FOpenGLControl.OnPaint := OpenGLControlPaint;
end;
重要的是要知道我对OpenGL的了解有限,而这大多数对我来说是新知识,我不确定这是否与设置我认为已经完成的窗口的视口有关,但是问题可能出在其他地方,或者我做错了什么。
所以我的问题是,当父窗口调整大小时,如何正确呈现控件内的OpenGL而不会拉伸/扭曲?
谢谢。
更新1
procedure TForm1.FormResize(Sender: TObject);
var
Aspect: Single;
begin
glViewPort(0, 0, FOpenGLControl.Width, FOpenGLControl.Height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
Aspect := Real(FOpenGLControl.Width) / Real(FOpenGLControl.Height);
glOrtho(-Aspect, Aspect, -1.0, 1.0, -1.0, 1.0);
end;
procedure TForm1.OpenGLControlPaint(Sender: TObject);
begin
glBegin(GL_TRIANGLES);
glColor3f(0.60, 0.10, 0.35);
glVertex3f(0.0, 1.0, 0.0);
glVertex3f(-1.0,-1.0, 0.0);
glVertex3f( 1.0,-1.0, 0.0);
glEnd;
SwapBuffers(wglGetCurrentDC);
end;
以上方法有效,但仅当父级与客户对齐时才有效,在本示例中,Panel1
与客户对齐时。如果面板未对齐,则在调整窗口大小时会扭曲。
答案 0 :(得分:1)
如果视口是矩形的,则必须通过将场景的坐标映射到视口来考虑。
您必须使用正交投影矩阵。投影矩阵将所有顶点数据从眼睛坐标转换为剪辑坐标。然后,通过将片段坐标的w分量除以将这些片段坐标也转换为归一化设备坐标(NDC)。规范化的设备坐标在(-1,-1,-1)至(1、1、1)之间。
如果使用正交投影矩阵,则眼空间坐标将线性映射到NDC。正交矩阵可以通过glOrtho
进行设置。
要解决您的问题,必须计算视口的Aspect
,这是一个浮点值,代表视口的宽度和高度之间的关系,因此必须初始化正交投影矩阵
根据TCustomControl
的文档,Width
和Height
是控件的垂直和水平大小(以像素为单位)。但这不等于控件的客户区的大小。请改用ClientWidth
和ClientHeight
,以像素为单位给出控件工作区的宽度和高度。
procedure TForm1.FormResize(Sender: TObject);
var
Aspect: Single;
begin
glViewPort(0, 0, FOpenGLControl.ClientWidth, FOpenGLControl.ClientHeight);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
Aspect := Real(FOpenGLControl.ClientWidth) / Real(FOpenGLControl.ClientHeight);
glOrtho(-Aspect, Aspect, -1.0, 1.0, -1.0, 1.0);
end;