Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/opengl/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/rust/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Delphi 如何在控件中正确呈现OpenGL?_Delphi_Opengl_Delphi 10.2 Tokyo - Fatal编程技术网

Delphi 如何在控件中正确呈现OpenGL?

Delphi 如何在控件中正确呈现OpenGL?,delphi,opengl,delphi-10.2-tokyo,Delphi,Opengl,Delphi 10.2 Tokyo,我正在尝试创建一个基本上是OpenGL窗口的自定义控件 我在一些指南的帮助下进行了所有的设置和工作(至少看起来是这样),以设置像素格式等,但是我注意到,当我调整父窗体的大小时,OpenGL图形会被缩放/拉伸 为了说明这一点,下图是它的显示方式: 调整表单大小后,它现在显示如下,例如: 忽略顶部的OSD,因为这是我使用的屏幕录制软件的一部分,它也会失真 在这里,我添加了一个Gif,以更好地演示调整表单大小时发生的情况: 以下是我的自定义控件的单位: unit OpenGLControl;

我正在尝试创建一个基本上是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
的父控件设置为表单似乎可以按预期工作,例如:

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
与客户端对齐时有效。如果面板未对齐,则在调整窗口大小时会扭曲。

如果视口为矩形,则必须通过将场景坐标映射到视口来考虑这一点

必须使用正交投影矩阵。投影矩阵将所有顶点数据从眼睛坐标转换为剪辑坐标。然后,通过与剪辑坐标的w分量相除,这些剪辑坐标也被转换为归一化设备坐标(NDC)。标准化设备坐标的范围为(-1,-1,-1)到(1,1,1)

如果使用正交投影矩阵,则眼睛空间坐标将线性映射到NDC。正交矩阵可以通过以下方式设置

要解决此问题,必须计算视口的
纵横比
,这是一个浮点值,表示视口的宽度和高度之间的关系,并且必须初始化正交投影矩阵

根据的文档,is
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;

仅供参考,您应该重写
CreateWnd()
(甚至是
CreateWindowHandle()
),而不是重写
CreateHandle()
。在调用
继承的
之前,您需要重写
DestroyWnd()
(或
DestroyWindowHandle()
)来调用
GLRelease()
,否则在重新创建窗口时会泄漏OpenGL资源。感谢@RemyLebeausee mine,特别是
gl\u resize
函数和用法,注意,y轴从VCL画布反转。。。你需要注意的是投影的纵横比。。。有几种方法可以计算它,每种方法的行为都不同(只按x或y缩放,或只按较大的边缩放,或只按较小的边缩放…)也许我做错了什么,因为在调整表单大小时它仍然扭曲,请参阅我添加的Gif示例。请参阅问题中的更新1,仅当父控件(如Panel1)与client@Craig使用
ClientWidth
ClientHeight
代替
Width
Height
。请参阅对答案的更改。已尝试过此方法,谢谢,但遗憾的是,仅当父面板与客户端对齐时,此方法才有效。我想这很好,因为它通常会以这种方式对齐