网页设计

OpenGL介绍:3D文本渲染教程

利用DirectX和OpenGL等工具的可用性,编写一个桌面应用程序现在渲染3D元素并不难。然而,与许多技术一样,有时也存在一些障碍,使开发人员难以进入这个利基市场。随着时间的推移,DirectX和OpenGL之间的竞争使得开发人员更容易使用这些技术,以及更好的文档和更容易成为熟练的DirectX或OpenGL开发人员的过程。

DirectX是Windows平台特有的技术,由微软公司推出并维护。另一方面,OpenGL是一个用于3D图形竞技场的跨平台API,其规范由Khronos Group维护。

在Toptal的OpenGL介绍中,我将解释如何编写一个非常简单的应用程序来渲染3D文本模型。我们将使用Qt/Qt Creator来实现UI,使其易于在多个平台上编译和运行此应用程序。为本文构建的原型的源代码如下可以在GitHub上找到

这个简单的应用程序的目标是生成3D模型,将它们保存到具有简单格式的文件中,并在屏幕上打开和渲染它们。渲染场景中的3D模型将是可旋转和可缩放的,以提供更好的深度和维度感。

先决条件

在开始之前,我们需要用一些有用的工具来准备我们的开发环境。我们需要的第一件事是Qt框架和相关的实用程序,可以从上面下载www.qt.io.它也可以通过您的操作系统的标准包管理器使用;如果是这样的话,你可能想先尝试一下。本文要求您熟悉Qt框架。但是,如果您不熟悉框架,请不要气馁,因为原型依赖于框架的一些相当琐碎的特性。

你也可以使用微软Visual Studio 2013在Windows上。在这种情况下,请确保您使用的是适当的Qt插件用于Visual Studio

此时,您可能希望克隆仓库从GitHub并在阅读本文时遵循它。

OpenGL概述

我们将首先用一个文档小部件创建一个简单的Qt应用程序项目。由于它是一个基本的小部件,编译和运行它不会产生任何有用的东西。使用Qt设计器,我们将添加一个“文件”菜单,其中包含四个项目:“Newâ€φ”,“Openâ€φ”,“关闭”和“退出”。您可以找到将这些菜单项绑定到相应操作的代码在存储库中

点击“New …”应该弹出一个对话框,看起来像这样:

1

在这里,用户可以输入一些文本,选择一种字体,调整得到的模型高度,并生成一个3D模型。单击“Create”应该保存模型,如果用户从左下角选择了适当的选项,也应该打开模型。如您所知,这里的目标是将一些用户输入的文本转换为3D模型并在显示器上呈现。

该项目将有一个简单的结构,组件将被分解成少量的c++和头文件:

2

createcharmodeldlg.h / cpp

文件包含QDialog派生对象。这实现了对话框小部件,允许用户输入文本,选择字体,并选择是否将结果保存到文件和/或以3D方式显示。

gl_widget.h / cpp

包含QOpenGLWidget派生对象的实现。这个小部件用于渲染3D场景。

mainwindow.h / cpp

包含主应用程序小部件的实现。因为这些文件是由Qt Creator向导创建的,所以没有改变。

main.cpp

包含main(â€μ)函数,该函数创建主应用程序小部件并在屏幕上显示它。

model2d_processing.h / cpp

包含2D场景创建功能。

model3d.h / cpp

包含存储3D模型对象的结构,并允许对其进行操作(保存,加载等)。

model_creator.h / cpp

包含类的实现,允许创建3D场景模型对象。

OpenGL实现

为了简洁起见,我们将跳过使用Qt Designer实现用户界面的明显细节,以及定义交互元素行为的代码。当然,这个原型应用程序还有一些更有趣的方面,这些方面不仅重要,而且与我们想要讨论的3D模型编码和渲染有关。例如,在这个原型中,将文本转换为3D模型的第一步涉及将文本转换为2D单色图像。一旦图像生成,就可以知道图像中的哪个像素构成了文本,哪些像素只是“空白”空间。使用OpenGL渲染基本文本有一些更简单的方法,但我们采用这种方法是为了覆盖使用OpenGL渲染3D的一些基本细节。

生成此图像,我们用QImage::Format_Mono标志实例化一个QImage对象。因为我们只需要知道哪些像素是文本的一部分,哪些不是,所以单色图像应该可以正常工作。当用户输入一些文本时,我们同步更新这个QImage对象。基于字体大小和图像宽度,我们尽量使文本符合用户定义的高度。

接下来,我们枚举作为文本一部分的所有像素——在本例中是黑色像素。这里的每个像素都被视为独立的近似正方形的单位。基于此,我们可以生成一个三角形列表,计算它们顶点的坐标,并将它们存储在我们的3D模型文件中。

现在我们有了自己简单的3D模型文件格式,我们可以开始专注于渲染它了。对于基于OpenGL的3D渲染,Qt提供了一个名为QOpenGLWidget的小部件。要使用这个小部件,可以覆盖三个函数:

  • initializeGl()—这是初始化代码所在的位置
  • paintGl()—每次重绘小部件时都会调用这个方法
  • resizeGl(int w, int h)——每次调整小部件的大小时,都会调用这个方法,并设置小部件的宽度和高度

3.

我们将通过在initializeGl方法中设置适当的着色器配置来初始化小部件。

glEnable (GL_DEPTH_TEST);glShadeModel (GL_FLAT);glDisable (GL_CULL_FACE);

第一行使程序只显示那些离我们更近的渲染像素,而不是那些在其他像素后面和视线之外的像素。第二行指定了平面阴影技术。第三行使程序呈现三角形,而不管它们的法线指向哪个方向。

初始化后,每次调用paintGl时,我们都将模型呈现在显示器上。在重写paintGl方法之前,必须准备缓冲区。为此,我们首先创建一个缓冲句柄。然后我们将句柄绑定到其中一个绑定点,将源数据复制到缓冲区中,最后我们告诉程序解除缓冲区的绑定:

//获取Qt对象,该对象允许对缓冲区进行操作QOpenGLFunctions func (QOpenGLContext:: currentContext ());//创建缓冲区句柄funcs.glGenBuffers (1、、);//通过它的句柄选择buffer(所以我们将使用这个buffer)/ /进一步)funcs.glBindBuffer (GL_ARRAY_BUFFERhandle);//将数据复制到缓冲区。被复制,//源数据不再使用,可以释放函数。glBufferData(GL_ARRAY_BUFFER, size_in_bytes, src_data, GL_STATIC_DRAW);//告诉程序我们已经完成了句柄funcs.glBindBuffer (GL_ARRAY_BUFFER0);

在重写的paintGl方法中,我们使用顶点数组和普通数据数组为每一帧绘制三角形:

QOpenGLFunctions func (QOpenGLContext:: currentContext ());//顶点数据glEnableClientState (GL_VERTEX_ARRAY);//使用顶点缓冲区funcs.glBindBuffer (GL_ARRAY_BUFFERm_hVertexes);//使用这个glVertexPointer (3.GL_FLOAT,00);//数据格式funcs.glVertexAttribPointer (m_coordVertex3., gl_float, gl_false,00);//提供到着色程序//正常数据glEnableClientState (GL_NORMAL_ARRAY);//使用NORMAL bufferfuncs.glBindBuffer (GL_ARRAY_BUFFERm_hNormals);//使用这个glNormalPointer (GL_FLOAT00);//数据格式funcs.glEnableVertexAttribArray (m_coordNormal);// Shader属性funcs.glVertexAttribPointer (m_coordNormal3., gl_float, gl_false,00);//提供到着色程序//画框glDrawArrays (GL_TRIANGLES0, (3.* m_model.GetTriangleCount ()));//渲染结束,缓冲区未被使用funcs.glDisableVertexAttribArray (m_coordNormal);funcs.glBindBuffer (GL_ARRAY_BUFFER0);glDisableClientState (GL_VERTEX_ARRAY);glDisableClientState (GL_NORMAL_ARRAY);

为了提高性能,我们在原型应用程序中使用了顶点缓冲对象(VBO)。这让我们可以将数据存储在显存中,并直接用于渲染。另一种方法是从渲染代码中提供数据(顶点坐标,法线和颜色):

glBegin (GL_TRIANGLES);//提供三角形#1的坐标glVertex3f (x [0), y (0], z [0]);glVertex3f (x [1), y (1], z [1]);glVertex3f (x [2), y (2], z [2]);//提供其他三角形的坐标...glEnd ();

这似乎是一个更简单的解决方案;然而,它有严重的性能影响,因为这需要数据通过显存总线传输——一个相对较慢的过程。实现paintGl方法后,我们必须注意着色器:

m_shaderProgram。addShaderFromSourceCode (QOpenGLShader::顶点,QString:: fromUtf8 (“# 400 \ r \ n版本”“\ r \ n”"layout (location = 0) in vec3 coordVertexes;\r\n""layout (location = 1) in vec3 coordNormals;\r\n"“flat out float lightIntensity;\r\n”“\ r \ n”"均匀mat4 matrixVertex;\r\n"“均匀mat4 matrixNormal;\r\n”“\ r \ n”“void main () \ r \ n”“{\ r \ n”" gl_Position = matrixVertex * vec4(coordVertexes, 1.0);\r\n"" lightIntensity = abs((matrixNormal * vec4(coordNormals, 1.0)).z);\r\n"“}”));m_shaderProgram。addShaderFromSourceCode (QOpenGLShader::片段,QString:: fromUtf8 (“# 400 \ r \ n版本”“\ r \ n”“flat in float lightIntensity;\r\n”“\ r \ n”"layout (location = 0) out vec4 FragColor;\r\n""uniform vec3 fragmentColor;\r\n"“\ r \ n”“void main () \ r \ n”“{\ r \ n”" FragColor = vec4(fragmentColor * lightIntensity, 1.0);\r\n"“}”));m_shaderProgram.link ();m_shaderProgram.bind ();m_coordVertex = m_shaderProgram.attributeLocation(QString::fromUtf8(“coordVertexes”));m_coordNormal = m_shaderProgram.attributeLocation(QString::fromUtf8(“coordNormals”));m_matrixVertex = m_shaderProgram.uniformLocation(QString::fromUtf8(“matrixVertex”));m_matrixNormal = m_shaderProgram.uniformLocation(QString::fromUtf8(“matrixNormal”));m_colorFragment = m_shaderProgram.uniformLocation(QString:“fragmentColor”));

在OpenGL中,着色器是使用一种被称为GLSL.该语言的设计目的是使3D数据在呈现之前易于操作。这里,我们需要两个着色器:顶点着色器和片段着色器。在顶点着色器中,我们将用变换矩阵转换坐标来应用旋转和缩放,并计算颜色。在片段着色器中,我们将为片段分配颜色。这些着色器程序必须被编译并与上下文链接。OpenGL提供了连接这两个环境的简单方法,以便程序内部的参数可以从外部访问或分配:

//获取模型转换矩阵QMatrix4x4 matrixVertex;...//在这里计算矩阵//设置着色程序对象的参数m_shaderProgram。setUniformValue (m_matrixVertex matrixVertex);

在顶点着色器代码中,我们通过在原始顶点上应用变换矩阵来计算新的顶点位置:

gl_Position = matrixVertex * vec4(coordVertexes,1.0);

为了计算这个变换矩阵,我们计算几个单独的矩阵:屏幕缩放、转换场景、缩放、旋转和居中。然后我们求出这些矩阵的乘积来计算最终的变换矩阵。首先将模型中心转换为原点(0,0,0),原点也是屏幕的中心。旋转是由用户使用一些指向设备与场景的交互决定的。用户可以点击场景并拖动来旋转。当用户单击时,我们存储光标位置,在移动之后,我们有第二个光标位置。使用这两个坐标,以及场景中心,我们形成一个三角形。经过一些简单的计算,我们可以确定旋转角度,我们可以更新我们的旋转矩阵来反映这个变化。对于缩放,我们只需依靠鼠标滚轮来修改OpenGL小部件X轴和Y轴的缩放因子。模型被向后平移0.5,以保持它在渲染场景的平面后面。 Finally, to maintain the natural aspect ratio we need to adjust the decrease of the model expansion along the longer side (unlike the OpenGL scene, the widget where it is rendered may have different physical dimensions along either axes). Combining all these, we calculate the final transformation matrix as follows:

无效GlWidget:: GetMatrixTransform (QMatrix4x4& matrixVertex,常量model3dex&model) {matrixVertex.setToIdentity();QMatrix4x4 matrixScaleScreen;dimMin =static_cast<> (qMin(宽度(),高度()));浮动scaleScreenVert =static_cast<浮动> (dimMin /static_cast<>(高度()));浮动scaleScreenHorz =static_cast<浮动> (dimMin /static_cast<>(宽度()));matrixScaleScreen。规模(scaleScreenHorz scaleScreenVert,1.0度);QMatrix4x4 matrixCenter;浮动centerX, centerY, centerZ;模型。GetCenter(centerX, centerY, centerZ);matrixCenter。翻译(-centerX, -centerY, -centerZ);QMatrix4x4 matrixScale;浮动半径=1.0;model.GetRadius(半径);浮动规模=static_cast<浮动>(m_scaleCoeff / radius);matrixScale。规模(规模、规模、0.5度/半径);QMatrix4x4 matrixTranslateScene;matrixTranslateScene.translate (0.0度0.0度,0.5度);matrixVertex = matrixScaleScreen * matrixTranslateScene * matrixScale * m_matrixRotate * matrixCenter;}

结论

在OpenGL 3D渲染的介绍中,我们探索了一种允许我们利用显卡渲染3D模型的技术。出于同样的目的,这比使用CPU周期要有效得多。我们使用了一个非常简单的阴影技术,并通过处理用户从鼠标输入使场景具有交互性。我们避免使用显存总线在显存和程序之间来回传递数据。尽管我们只是用3D渲染了一行文本,但更复杂的场景也可以用非常相似的方式渲染。

公平地说,本教程几乎没有触及3D建模和渲染的表面。这是一个广泛的话题,这个OpenGL教程不能说这是你需要知道的所有能够构建3D游戏或建模软件。然而,本文的目的是让您了解这个领域,并展示如何轻松地开始使用OpenGL构建3D应用程序。

共享的http://www.toptal.com/

irinapapuc的文章(3个帖子

irinapapuc是LeraBlog的作者。作者的观点完全是他们自己的,可能不反映LeraBlog员工的观点和意见。