我决定写一些关于编程方面以及组件之间如何通信的文章。也许它将为某些领域提供一些启示。
简报
要在屏幕上绘制您在问题中张贴的单个图像,该怎么办?
有多种方法可以在屏幕上绘制三角形。为简单起见,我们假设不使用顶点缓冲区。(顶点缓冲区是您存储坐标的内存区域。)让我们假设程序只是简单地告诉图形处理管道有关一行中每个顶点的信息(一个顶点只是空间中的坐标)。
但是,在绘制任何东西之前,我们首先必须运行一些脚手架。稍后我们将说明原因:
// Clear The Screen And The Depth Buffer
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Reset The Current Modelview Matrix
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
// Drawing Using Triangles
glBegin(GL_TRIANGLES);
// Red
glColor3f(1.0f,0.0f,0.0f);
// Top Of Triangle (Front)
glVertex3f( 0.0f, 1.0f, 0.0f);
// Green
glColor3f(0.0f,1.0f,0.0f);
// Left Of Triangle (Front)
glVertex3f(-1.0f,-1.0f, 1.0f);
// Blue
glColor3f(0.0f,0.0f,1.0f);
// Right Of Triangle (Front)
glVertex3f( 1.0f,-1.0f, 1.0f);
// Done Drawing
glEnd();
那是做什么的呢?
当您编写要使用图形卡的程序时,通常会选择某种与驱动程序的接口。驱动程序的一些众所周知的接口是:
对于此示例,我们将坚持使用OpenGL。现在,你的界面司机是什么让你所有你需要让你的程序的工具谈话到图形卡(或驱动程序,然后会谈到卡)。
该界面一定会为您提供某些工具。这些工具采用可以从程序中调用的API的形式。
该API是我们在上面的示例中看到的。让我们仔细看看。
脚手架
在真正进行任何实际绘图之前,您必须执行设置。您必须定义您的视口(实际要渲染的区域),透视图(相机进入您的世界),要使用的抗锯齿(平滑三角形的边缘)...
但是我们不会考虑任何一个。我们只是看一眼每个框架要做的事情。喜欢:
清除画面
图形管道不会为您每一帧清除屏幕。您必须告诉它。为什么?这就是为什么:
如果您不清除屏幕,则只需在每一帧上画图即可。这就是为什么我们称之为glClear
与GL_COLOR_BUFFER_BIT
集。另一位(GL_DEPTH_BUFFER_BIT
)告诉OpenGL清除深度缓冲区。该缓冲区用于确定哪些像素在其他像素之前(或之后)。
转型
图片来源
转换是我们获取所有输入坐标(三角形的顶点)并应用我们的ModelView矩阵的部分。这是解释我们的模型(顶点)如何旋转,缩放和平移(移动)的矩阵。
接下来,我们应用投影矩阵。这将移动所有坐标,以便它们正确面对我们的相机。
现在,我们使用视口矩阵再次进行变换。我们这样做是为了将模型缩放到显示器的大小。现在,我们有一组准备渲染的顶点!
我们待会再进行转换。
画画
要画一个三角形,我们可以简单地告诉OpenGL开始一个新的三角形的列表,通过调用glBegin
与GL_TRIANGLES
不变。
您还可以绘制其他形式。像三角带或三角扇子。这些主要是优化,因为它们需要较少的CPU和GPU之间的通信才能绘制相同数量的三角形。
之后,我们可以提供一组3个顶点的列表,这些顶点应组成每个三角形。每个三角形使用3个坐标(就像我们在3D空间中一样)。另外,我还通过在调用之前调用来为每个顶点提供颜色。glColor3f
glVertex3f
OpenGL 自动计算3个顶点之间的阴影(三角形的3个角)。它将在多边形的整个表面上插值颜色。
相互作用
现在,当您单击窗口时。该应用程序仅需捕获表示单击的窗口消息。然后,您可以在程序中运行所需的任何操作。
这得到了很多,一旦你要开始与你的3D场景互动更加困难。
您首先必须清楚地知道用户单击窗口的像素。然后,考虑到您的透视图,您可以从鼠标单击场景的角度来计算射线的方向。然后,您可以计算场景中的任何对象是否与该光线相交。现在您知道用户是否单击了对象。
那么,如何使其旋转呢?
转型
我知道通常会应用两种类型的转换:
不同之处在于骨骼会影响单个顶点。矩阵始终以相同的方式影响所有绘制的顶点。让我们看一个例子。
例
之前,我们在绘制三角形之前加载了单位矩阵。单位矩阵是根本不提供任何转换的矩阵。因此,无论我画什么,都只会受我的观点的影响。因此,三角形将完全不会旋转。
如果我现在要旋转它,我既可以自己算算自己(在CPU上)和简单的调用glVertex3f
与其他坐标(即旋转)。或者我可以通过glRotatef
在绘制之前进行调用来让GPU完成所有工作:
// Rotate The Triangle On The Y axis
glRotatef(amount,0.0f,1.0f,0.0f);
amount
当然只是一个固定值 如果要设置动画,则必须跟踪amount
并在每一帧增加它。
那么,等等,之前所有矩阵讨论发生了什么?
在这个简单的示例中,我们不必关心矩阵。我们简单地打电话glRotatef
给我们,一切由我们来完成。
glRotate
产生angle
围绕向量xyz 的度数旋转。当前矩阵(请参阅glMatrixMode)乘以旋转矩阵,乘积替换当前矩阵,就好像使用以下矩阵作为参数调用了glMultMatrix一样:
x 21-c + cx y y1-c-zsxz1-c + y s s 0yx1-c + zsy 21-c + cyz 1-c-x s 0 x1-c-y sy y z1-c + x sz 21-c + c 0 0 0 0 1
好吧,谢谢!
结论
显而易见的是,关于 OpenGL 的讨论很多。但这并没有告诉我们任何事情。通讯在哪里?
在此示例中,OpenGL唯一要告诉我们的是何时完成。每次操作将花费一定的时间。有些操作花费了难以置信的时间,而另一些则非常快。
将顶点发送到GPU将是如此之快,我什至不知道如何表达它。从单个帧将数千个顶点从CPU发送到GPU根本没有问题。
清除屏幕可能要花费一毫秒或更长时间(请注意,通常每帧绘制时间只有16毫秒),具体取决于视口的大小。要清除它,OpenGL必须以要清除的颜色绘制每个像素,该像素可能是数百万个像素。
除此之外,我们几乎只能向OpenGL询问我们的图形适配器的功能(最大分辨率,最大抗锯齿,最大色深等)。
但是我们也可以用每个都有特定颜色的像素填充纹理。因此,每个像素都具有一个值,纹理是一个充满数据的巨型“文件”。我们可以将其加载到图形卡中(通过创建纹理缓冲区),然后加载着色器,告诉该着色器将纹理用作输入,并对“文件”进行一些非常繁琐的计算。
然后,我们可以将计算结果(以新颜色的形式)“渲染”为新的纹理。
这样便可以使GPU以其他方式为您工作。我认为CUDA的表现与该方面相似,但我从未有过使用它的机会。
我们真的只触及了整个主题。3D图形编程是一头野兽。
图片来源