您的位置:首页 > 编程语言

【图形学与游戏编程】开发笔记-入门篇2:一些需要的基础知识

2016-07-16 17:46 701 查看
(本系列文章由pancy12138编写,转载请注明出处:http://blog.csdn.net/pancy12138)

在上一篇文章中,我们讲了一个简单的3D渲染管线的工作流程,也就是计算机是如何描述并绘制出一个3D游戏的。这些知识虽然并不能让大家现在就直接写出酷炫的3D画面,但是这是打开思维的第一步,一旦我们了解了3D渲染管线的工作原理,那么之后的各个细节的讲解就会变的很容易。大家在之后设计程序的时候也就更容易理解每一步的原理和目的,从而更好的学习。如果大家在之前对数据结构和算法这些已经很熟悉的话,那么应该就会发现其实c,c++这些语言在或大型,或复杂的程序设计里面所起的作用就是一个沟通人与电脑的工具,我们大部分时间在处理的都是架构,算法等问题。那么图形学以及游戏程序设计也是一样的,在学习之前我们需要了解我们最终要达到的目的和效果是什么,为什么需要某部分的知识以及如何设计算法和架构才能达到这个效果。之后我们再来使用相关的API以及程序语言来进行实现就是水到渠成的事情了。

这一篇文章呢,主要是讲解在学习图形学之前,大家需要至少了解一些的必要知识。(这也就是说,学习这些知识还是需要一些基础知识的deth,如比如你要是c语言都写的比较吃力的话就得先补补基础

),那么必要的知识到底需要多少呢。其实并不算很多,基本上大家大一学的课程都掌握的比较熟练的话就算是可以了,下面是我认为大家需要提前了解的部分:

1,至少要熟悉一种程序语言,3D游戏开发除了脚本部分以外基本上CPU的部分都是用C++来开发的(像unreal连脚本都是C++的),所以我们这个教程也是以C++作为开发语言的。那么大家需要对C++熟悉多少呢.........其实最好的情况是大家能够轻松的使用c++完成各种复杂的数据结构,并且能够很好地架构一些中小型的系统(几百几千行的那种就可以了

)因为大家回头学起来就会发现,相比于学校里面的小型程序,3D游戏的程序随便写写工程量就会很大,而且随着学习的深入还会附带一大堆复杂的算法,如果大家之前语言基础不是很好的话会在debug的过程中消耗大量的时间,而且众所周知的是图形学程序出了问题一般还真不好找,因为你要调cpu的问题的同时还得关注着gpu上是不是有bug.

..........当然啦,即便大家现在不擅长c++也是可以直接上手学习的,还可以顺便训练训练C++,前期一般不会有太大的问题,只不过需要你有足够的毅力去挑战自我啦



2,要了解一些线性代数和基本的数学知识,因为整个图形学的程序设计基本上都离不开矩阵,向量,方程这些的......当然,刚开始学的话不需要了解太深,比如线代的话知道矩阵和向量的作用,了解求逆,转置,正交这些名词的意思就好了。我想大家大一的时候应该都会学习这些课吧,如果还没学到的话我在下面会将一些相关的知识讲一部分,也算是帮助大家理解。

3,最好使用过API,对这个词汇有一个概念。API是指应用程序接口(可以理解成APP后面跟一个Interface),像opencv,opengl,directx,windows
API,android等等,这些API会将一些接近底层的东西替我们封装起来,这样我们在设计程序的时候就不需要去关心驱动啊,硬件啊,兼容性啊这些令人头疼的问题。当然像C语言的scanf(),printf()以及c++的cout,cin这些也算是简单的api,不过我这里所说的是用于处理专门问题的api,如果大家之前用过API(无论哪一种,那个方向的都算)开发过软件的话就会更易于理解directx以及opengl的用法。因为如果直接从黑框框程序跳跃到这种API的开发模式的话还是需要一段适应时间的。我在下面也会将一些windows
api相关的知识讲一部分,这样大家在之后的教程可以更好地上手。

好啦,上面的三个就是基本的要求了,不过这只是对大家作为新手入门的要求,随着学习的深入,大家可能会需要熟悉更多的知识,比如数据结构啊(最经典的就是骨骼动画二叉树),算法啊(比如A*寻路),信号处理啊(比如水面模拟)这些的。不过作为新入门来说,这些东西暂时也不需要,大家大可以先不管以后用到再说。

那么接下来我会简单介绍一下上述的基础知识,不过我这里默认大家的语言基础已经过关了,也就是说我不会在下面讲和c/c++有关的知识,毕竟这些知识,因为我们的教程里面这个知识是唯一要求大家熟练掌握的(其他的都是了解就好deth

),而要想达到熟练也不是我一两句话能讲清楚的。

首先是线性代数与矩阵,如果大家以前没有接触过线性代数的话可能不太清楚这门课是干什么用的。那么我先说一下他最大的用处,线性代数对于我们的图形程序设计而言,最大的好处就是“使用矩阵乘法来代替线性变换”。大家是不是感觉听上去灰常高大上

,那么接下来我们来用通俗的语言来讲解一下。我们在高中的时候呢描述一个参数的变化一般就是用一个函数f(x),如果f(x)的最高次为1的话我们就称之为一次函数,比如f(x)
= 2*x + 1这样的,当自变量的维度不是一维的时候,我们就得用一组方程组来描述了,假设自变量是一个三维坐标:那么一个通用的一次函数组可以用如下的方法来表示:

f(x,y,z).x = X(x,y,z) = a1*x + b1*y + c1*z +d1

f(x,y,z).y = Y(x,y,z) = a2*x + b2*y + c2*z +d2

f(x,y,z).z
= Z(x,y,z) = a3*x + b3*y + c3*z +d3

上述的函数组合无论自变量的维度有多高,只要函数里面不出现超过一次的变量,我们都将其称作线性的,而线性代数也基本上就是研究这种线性的方程或者函数组合。

那么接下来,我们来思考一下,上述的函数如果用计算机存储的话,我们应当怎么做呢。很明显我们只要把函数里面的参数全存储下来就可以了。下面我来写一份上述方程组的实现代码:

struct vector
{
int x;
int y;
int z;
}
vector liner_function(vector input,int rec[3][4])
{
vector output;
output.x = a1*x + b1*y + c1*z+d1;
output.y = a1*x + b1*y + c1*z+d1;
output.z = a1*x + b1*y + c1*z+d1;
return output;
}


那么上面的简易函数就实现了我们的多维一次函数,接下来那我们既可以正式讲解线性代数部分了,上述的函数其实就是完成了一次变换的工作,将自变量通过一个方程变换成了新的因变量。线性代数里面将这个用于变换的方程用矩阵来表示,将这个自变量称之为向量,将执行函数的过程称之为矩阵乘法,也就是如下的一种写法:

                           [a1 b1 c1 d1]

f(x,y,z) = (x,y,z)*  [a2 b2 c2 d2] 

                           [a3 b3 c3 d3]

上面的就是线性代数里面描述多维一次函数组的一种方法,这个乘号代表了一种新的乘法。上面那个乘法具体的运算法则就是用矩阵的每一列与向量进行点积。不过这个怎么算并不重要,矩阵的乘法公式大家基本上都知道,也不算什么难的知识,主要是大家要知道为什么要这么乘。那么把上面的f(x,y,z)转换成后面的那个乘法有什么意义呢?这就是我们接下来要讨论的问题:

首先最简单的意义就是我们把多维线性函数组这样一个逻辑上比较复杂的描述,转换成了“向量*矩阵”这种非常简练的描述,无论是写成程序还是统一思想都变得很简单。

接下来我们就要说一下他最重要的一个用处了。我们现在假设我们一个自变量,需要经过很多不同的函数才能处理成我们最终需要的因变量。比如说对于一个顶点a(x,y,z),我们先通过函数f1(a)把它变成a1,然后再通过f2(a1)把它变成a2......等,用高中的知识来描述就是fn(fn-1(...f2(f1(a))))这种问题。并且这种问题还算是经常出现的,那么我们是否需要对于每一个自变量,都得运行这么多次函数才能算出最终结果呢?答案是不需要的,很明显大家都知道可以把很多很多个函数“合成”成一个函数。那么采用矩阵的描述方法还有一个优点就是易于合并,因为矩阵乘法的一个优点就是假设A,B两个矩阵描述了两个不同的线性函数组,那么对于向量v,有(v*A)*B
= v*(A*B),而A*B的结果可以预先算出来,比如A*B = C,那么原式可以化简为v*C。其中矩阵乘矩阵也是用矩阵乘法的公式来计算的。

上面就是大家暂时需要先了解的矩阵方面的一些简单的知识,当然我这里知识用最简易的方法告诉大家矩阵的作用,期间也省略了一大堆知识点,大家如果希望能够真正的了解这门课的话还得看看课本或者参考书



接下来我们要谈一些API方面的知识,我们之后要接触的API无外乎directx/opengl以及windows API,其中前者主要处理图形运算也就是沟通GPU,后者主要处理线程啊,文件啊这些系统调用。这一节我们先介绍一个基本的windows程序是如何写出来的,这部分知识很简单,也不需要大家会的很熟练,只要你能回头写程序的时候了解每一部分是干嘛用的,以及windows程序是怎么运行的就可以了,因为前期的学习过程中可以说几乎不涉及到windows
api的调用。顺便提一下,我们之后的教程是在vs2015上展开的,因为vs2015不仅仅界面非常友好,而且还为HLSL专门提供了一个编译器。可以实时的调试shader,是一个非常优秀的IDE。在vs2015里面directx11以及directx12都有一个系统自建的工程,但是那个工程首先是windows应用商店的工程,非常不好用,并且它的架构也属于比较新手级别的架构,不利于我们后期自己架构整个渲染引擎的格式,要知道很少有人会直接用directx写游戏,大都是用它来设计引擎的,因此空白的架构对我们来说比较的重要。因此这里我们的教程不会用这种方法来书写工程。不过等以后这种工程完善的时候大家也可以转换成这种方法,因为看上去微软应该会在之后大力推广这种appstore的写法吧。

接下来我们谈windows程序的架构,相比于黑框框程序,windows程序最主要的变化就是使用了“回调”机制,也就是说整个程序分成了两个部分,第一个部分就是和我们的黑框框程序一样从winmain()开始,一直执行到return的部分,另一个部分就是需要我们处理键盘啊,鼠标啊这些信息的回调部分。下面是《windows程序设计
第五版》里面的一个最简单的windows程序,可以绘制出一个 空白窗口。

#include <windows.h>

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;                   //回调函数,用于处理windows转发来的消息(注意是windows转发的消息,并非msg中存储的消息)。

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("HelloWin") ;
HWND         hwnd ;                                                  //指向windows类的句柄。
MSG          msg ;                                                   //存储消息的结构。
WNDCLASS     wndclass ;

wndclass.style         = CS_HREDRAW | CS_VREDRAW ;                   //窗口类的类型(此处包括竖直与水平平移或者大小改变时时的刷新)。msdn原文介绍:Redraws the entire window if a movement or size adjustment changes the width of the client area.
wndclass.lpfnWndProc   = WndProc ;                                   //确定窗口的回调函数,当窗口获得windows的回调消息时用于处理消息的函数。
wndclass.cbClsExtra    = 0 ;                                         //为窗口类末尾分配额外的字节。
wndclass.cbWndExtra    = 0 ;                                         //为窗口类的实例末尾额外分配的字节。
wndclass.hInstance     = hInstance ;                                 //创建该窗口类的窗口的句柄。
wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;          //窗口类的图标句柄。
wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;              //窗口类的光标句柄。
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;     //窗口类的背景画刷句柄。
wndclass.lpszMenuName  = NULL ;                                      //窗口类的菜单。
wndclass.lpszClassName = szAppName ;                                 //窗口类的名称。

if (!RegisterClass (&wndclass))                                      //注册窗口类。
{
MessageBox (NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}

hwnd = CreateWindow (szAppName,                  // window class name创建窗口所用的窗口类的名字。
TEXT ("The Hello Program"), // window caption所要创建的窗口的标题。
WS_OVERLAPPEDWINDOW,        // window style所要创建的窗口的类型(这里使用的是一个拥有标准窗口形状的类型,包括了标题,系统菜单,最大化最小化等)。
CW_USEDEFAULT,              // initial x position窗口的初始位置水平坐标。
CW_USEDEFAULT,              // initial y position窗口的初始位置垂直坐标。
CW_USEDEFAULT,              // initial x size窗口的水平位置大小。
CW_USEDEFAULT,              // initial y size窗口的垂直位置大小。
NULL,                       // parent window handle其父窗口的句柄。
NULL,                       // window menu handle其菜单的句柄。
hInstance,                  // program instance handle窗口程序的实例句柄。
NULL) ;                     // creation parameters创建窗口的指针

ShowWindow (hwnd, iCmdShow) ;   // 将窗口显示到桌面上。
UpdateWindow (hwnd) ;           // 刷新一遍窗口(直接刷新,不向windows消息循环队列做请示)。

while (GetMessage (&msg, NULL, 0, 0))//当该窗口捕获了来自输入设备的一个信息。
{
TranslateMessage (&msg) ;        //将字符消息做转换。
DispatchMessage (&msg) ;         //向windows消息队列做请示,并等待windows调用回调函数。
}
return msg.wParam ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC         hdc ;
PAINTSTRUCT ps ;
RECT        rect ;

switch (message)
{
case WM_CREATE:
SetTimer (hwnd, 1, 1000, NULL) ;
return 0;
case WM_LBUTTONUP:
PlaySound (TEXT ("hellowin.wav"), NULL, SND_FILENAME | SND_ASYNC) ;
return 0 ;
case WM_DESTROY:
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
前面的winmain函数里面的部分非常简单,先设计一个窗口类,再实例化一个窗口,然后show()和update()就可以将窗口显示出来。这部分每个程序都是一样的大家可以直接copy上面的代码来实例化窗口。接下来就是一个while循环,除非窗口上面的小红叉被点击或者你设计了其他的关闭窗口的办法,否则这个循环会一直持续下去。然后getmessage()就会收集用户对窗口的操作(点击啊,按键啊这些),里面
的dispatchmessage()就会把这份操作给操作系统发送过去,操作系统在cpu闲下来的时候会通知我们的窗口回调函数,也就是我们下面写的那一部分来处理这个消息。这个过程就叫做回调,也就是我们虽然谢了怎么处理消息,但是调用是操作系统来做的。

事实上,游戏程序是一种破坏回调机制的程序,也就是说我们的图形渲染部分是不经过回调机制的,一般我们会直接在while循环里面调用我们自己的绘制函数来进行绘制,这个回调函数其实就算是一种傀儡一样的东西,只是负责处理窗口关闭啊,创建啊这些一次性的消息就足够了。因此大家不必太过关注windows
程序的细节,只要把上面的代码复制一下,跑出个界面再改一改参数,知道个大概就可以了,重点并不是这个玩意的架构


ok,前期我们需要的基础基本上就是这么多了,其实只要大家的语言基础过关了,剩下的都是一些很简单的知识,我估计大家花个一半天的也就学会了.......下一节是最后一篇入门篇的教程了,在下一篇会讲一下图形API,GPU以及一些相关的入门知识,敬请期待
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息