OpenGL超级宝典(第7版)笔记13 前三章实例 下个五子棋 (上)

文章目录

  • OpenGL超级宝典(第7版)笔记13 前三章实例 下个五子棋 (上)
  • 前言
  • 1 初构建
  • 2 构建数据结构
  • 3 绘制
    • 3.1 绘制准备
    • 3.2 绘制棋盘
    • 3.3 绘制棋子
  • 4 startup、render、shutdown
  • 5 总结

前言

上一篇我们对前三章做了一个知识的梳理(知识图),还提了一下OpenGL扩展的相关内容。

这一篇是对前三章内容的综合应用,其中涉及了很多着色器的细节,会非常精彩,但可能篇幅会比较长,我们会尽量用更多之前学习的知识(有时候是不得已,有时候是更加方便快速)

1 初构建

首先我们要大体把程序的框架定出来,然后再去讨论细枝末节的具体内容。本身五子棋是个小游戏,其并不包含很多的部分,就是一个棋盘,然后两边下棋(同一个电脑操作)最终有人赢了之后重新开始,没有什么其他的模式,所以不需要很复杂的设计。其主要就是四大部分:输入,判断,绘制,数据。流程如下:

然后是具体的构建四部分内容,我比较喜欢先构建数据,因为程序总是在围绕这数据的修改来运行,所以先敲定数据的结构在对其他的部分进行构建比较好。

2 构建数据结构

首先我们肯定是要观察一下其余部分对数据结构的要求:

首先对输入模块要求我们有一个专门的变量来记录当前的棋子类型(是白子还是黑子)

判断模块需要我们进行是否五子连珠的判断,我们要针对棋盘来设计一个数据结构,要求既能保存棋子的布局,又能方便连珠的判断,这里我们决定用二维数组来解决(一维数组的缺点是在判断斜向的五子连珠时有点麻烦),用二维数组代表棋盘中的每个格子,无棋子为0,有黑子为1,有白子为2。除此以外还需要一个变量来记录当前是否有人获胜。

绘制模块需要我们分别绘制棋盘和棋子,棋盘我们可以用细分的方式来生成(用上了细分着色器、几何着色器),而棋子我们打算用点绘制来生成(将点的大小调大,作为棋子),这就要求我们有两个着色器程序(其实一个也可以但是我们还是想尝试一下新东西)。数据方面我们打算把棋盘的顶点数据直接写入顶点着色器中(用上了gl_VertexID),而棋子的位置我们用顶点属性来传入(用上了顶点属性),如果把上面的棋盘中每个格子都测试一遍就太慢了,尤其是刚开始的时候棋子的数量并不多,所以我们单独弄一个数组来安装顺序存放下的棋子(还可以通过它实现悔棋的功能),同时添加一个记录当前一共下了多少个棋子的变量。

综上暂时需要有:
记录棋子种类的变量
记录是否有人获胜的变量
记录棋盘中棋子情况的二维数组
记录下子顺序的数组(同时实现绘制)
记录一共下了多少子的变量

struct chess{GLchar chesskind=0;
};
chess chessboard[17][17];//二维数组表示棋盘中棋子情况,用于判断五子连珠
GLchar chess_draw_list[17*17][3]={0};
//下子顺序的数组,[0]表示种类[1]表示x[2]表示y,用于绘制
//不要用chessboard来绘制,因为每次绘制都要遍历每个格子,
//      刚开始的时候可能只有一两个格子有棋子
//      而chess_draw_list只需要依次绘制,
//      直到所有棋子绘制完成就行了,减少了判断的次数
GLint chess_number=0;//一共下的棋子数
GLint nowkind = 1;//黑为1白为2
int iswin = 0;//是否有人赢了

3 绘制

3.1 绘制准备

虽然绘制应该位于流程的后半段,但是如果我们不能绘制出棋盘,那么我们的输出就不能确定如何转化为对应的格子位置。而且当前最大的难点还是opengl绘制一块的。

由于这回我们要创建两个着色器程序,一个用于绘制棋盘(将用到细分着色器,几何着色器),另一个用于绘制棋子(就是点的绘制,只用顶点着色器和片段着色器),所以我们要稍微整理一下之前的函数,因为之前的函数只是绘制单一的着色器程序,现在我们可能需要各种各样的着色器,而且着色器可能还会有复用,所以我们把着色器的编译,和着色器程序的链接分成两个函数来进行封装。

因为有很多的着色器,所以我们把着色器对象放到全局变量中:

GLuint vs[3]={0};
GLuint tcs[3]={0};
GLuint tes[3]={0};
GLuint gs[3]={0};
GLuint fs[3]={0};
//着色器,我们准备了3个位置,可以放3个同种的着色器
GLuint rendering_program1;
GLuint rendering_program2;
//着色器程序,一共两个
GLuint vertex_array_object;
//当然这里还包含了顶点数组

然后我们要建立着色器的编译函数:

void compile_shader(void) {#pragma region 编辑着色器内容static const GLchar* vertex_shader_source[] = {//着色器内容};static const GLchar* vertex_shader_source2[] = {//着色器内容};static const GLchar* fragment_shader_source2[] = {//着色器内容};
#pragma endregion#pragma region 编译各种着色器并检查情况GLint shader_success;GLchar infoLog[512];//定义一个整型变量来表示是否成功编译,还定义了一个储存错误消息(如果有的话)的容器。vs[0] = glCreateShader(GL_VERTEX_SHADER);glShaderSource(vs[0], 1, vertex_shader_source, NULL);glCompileShader(vs[0]);glGetShaderiv(vs[0], GL_COMPILE_STATUS, &shader_success);//检查编译是否成功if (!shader_success){glGetShaderInfoLog(vs[0], 512, NULL, infoLog);std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;}vs[1] = glCreateShader(GL_VERTEX_SHADER);glShaderSource(vs[1], 1, vertex_shader_source2, NULL);glCompileShader(vs[1]);glGetShaderiv(vs[1], GL_COMPILE_STATUS, &shader_success);//检查编译是否成功if (!shader_success){glGetShaderInfoLog(vs[1], 512, NULL, infoLog);std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;}fs[0] = glCreateShader(GL_FRAGMENT_SHADER);glShaderSource(fs[0], 1, fragment_shader_source, NULL);//试试这个是否有格子花纹 glCompileShader(fs[0]);glGetShaderiv(fs[0], GL_COMPILE_STATUS, &shader_success);//检查编译是否成功if (!shader_success){glGetShaderInfoLog(fs[0], 512, NULL, infoLog);std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;}//更多需要的着色器也要进行编译,并检查是否出错#pragma endregionreturn;
}

这里并没有把整体的内容都写出来(包括着色器的内容,还有后面其他着色器的编译),大家根据需求进行补充就行,记得要把着色器的值放到全局变量中。

当然与之配套的是要对着色器进行删除:

void delete_shader(void){int i=0;for(i=0;i<3;i++){if(vs[i]!=0)glDeleteShader(vs[i]);if(tcs[i]!=0)glDeleteShader(tcs[i]);if(tes[i]!=0)glDeleteShader(tes[i]);if(gs[i]!=0)glDeleteShader(gs[i]);if(fs[i]!=0)glDeleteShader(fs[i]);}return;
}

整理好了着色器的编译之后,我们来对着色器程序的链接进行一个封装,我们这里把第一个着色器程序的着色器都放在下标为0的着色器中(例如fs[0]、vs[0]、gs[0]等等),当然你可以根据你的情况来选择着色器(注意要在着色器删除之前进行链接!!),你当然可以用向函数传入数值的方式来指定着色器,让封装函数更灵活(这里我们就懒得弄了)

GLuint linkprogram1(void){GLuint program = glCreateProgram();glAttachShader(program,vs[0]);glAttachShader(program,tcs[0]);glAttachShader(program,tes[0]);glAttachShader(program,gs[0]);glAttachShader(program,fs[0]);glLinkProgram(program);GLint success;glGetProgramiv(program, GL_LINK_STATUS, &success);if (!success) {GLchar* m_infor = new GLchar[1024];glGetProgramInfoLog(program, 1023, NULL, m_infor);std::cout << "ERROR_IN: program " << std::endl << "\tError in link program " << program << std::endl;std::cout << "ERROR_IN: program " << std::endl << "\terror message : " << std::endl << m_infor << std::endl;delete []m_infor ;return 0;}return program;
}

这里我们打算用一号着色器程序来绘制棋盘(通过对正方形进行细分的方式来绘制棋盘格),用二号着色器程序来绘制每个棋子(绘制很大的点),这里并没有给出二号着色器程序的链接封装(跟上面的GLuint linkprogram1(void)很相似,但只含VS和FS两种着色器)

好了我们现在准备好了着色器相关的编译链接等函数,现在只需要专注于着色器的编写。

3.2 绘制棋盘

绘制棋盘应该是这个程序中最难的部分,我们的想法是通过传入四个顶点,通过细分产生17*17的棋盘。好,我们来看具体的着色器内容

顶点着色器:

#version 450 core
void main(void) {                                                               const vec4 vertices[4]=vec4[4](vec4(0.7,0.7,0.5,1.0),      vec4(0.7,-0.7,0.5,1.0),         vec4(-0.7,-0.7,0.5,1.0),                vec4(-0.7,0.7,0.5,1.0));            gl_Position = vertices[gl_VertexID];
}

既然我们只需要四个顶点那么就直接将位置放在着色器中就行了,顺便用一下gl_VertexID,复习一下之前提到过的内容

细分曲面控制着色器:

#version 450 core
layout (vertices=4) out;
void main(void){                                        if(gl_InvocationID==0){               gl_TessLevelInner[0]=18.0;     gl_TessLevelInner[1]=18.0;     gl_TessLevelOuter[0]=18.0;     gl_TessLevelOuter[1]=18.0;     gl_TessLevelOuter[2]=18.0;     gl_TessLevelOuter[3]=18.0;     }                                   gl_out[gl_InvocationID].gl_Position=gl_in[gl_InvocationID].gl_Position;
}

我们把内外都分成18份,这样才能生成对应的棋盘格,之后再解释为什么是18而不是16(按理来说应该分成16份才是17*17的格子)

细分曲面评估着色器:

#version 450 core
layout (quads,equal_spacing,ccw)in;
void main(void){                                        float u = gl_TessCoord.x;      float v = gl_TessCoord.y;      float omu = 1-u;               float omv = 1-v;               gl_Position =                  omu *omv *gl_in[0].gl_Position+        u   *omv *gl_in[1].gl_Position+        u   *v   *gl_in[2].gl_Position+        omu *v   *gl_in[3].gl_Position;
}

这里的细分后的顶点计算可能有点复杂,而且和我们之前学的并不一样,这是因为传入的是四边形,而不是三角形,之前三角形的是重心坐标xyz,而四边形是uv两项(详见OpenGL编程指南细分评估着色器相关章节,有的书翻译的是细分计算着色器),跟之前不一样。

我们在加上片段着色器应该就完成了:

#version 450 core
out vec4 color;
void main(void){                                        color = vec4(0.9,0.9,0.8,1.0);
}

我们来看看结果:

结果并不理想,我们的棋盘中全都是小正方形,但是每个正方形中都有一个斜杠,这是因为细分后都形成了很多的小三角形,并不是一个个小正方形,现在我们想想办法把这中间的斜杠去除掉。

想来想去,还是需要几何着色器的帮助:
最终我们的办法是这样的,我们在传入几何着色器的每个点处画一个十字,这样十字变多了之后,就会显示一个棋盘。
如:

具体的绘制方法是在每个顶点处绘制一个线段条带line_strip,绘制先后顺序如下图所示,(因为和三角形条带一样,所以需要按照绘制顺序传出顶点,顶点顺序也见下图)

好,我们给出几何着色器代码:

#version 450 core
layout (triangles) in;
layout (line_strip,max_vertices=8) out;
void main(void)
{                                               if(gl_in[0].gl_Position.x<0.5){                                                  gl_Position=gl_in[0].gl_Position;EmitVertex();                                 gl_Position=gl_in[0].gl_Position+vec4(0.1,0.0,0.0,0.0);EmitVertex();          }else{                                                                              gl_Position=gl_in[0].gl_Position;EmitVertex();                                 gl_Position=gl_in[0].gl_Position;EmitVertex();                                 }                                                                                   if(gl_in[0].gl_Position.x>-0.5){                                                 gl_Position=gl_in[0].gl_Position;EmitVertex();                                 gl_Position=gl_in[0].gl_Position+vec4(-0.1,0.0,0.0,0.0);EmitVertex();         }else{                                                                              gl_Position=gl_in[0].gl_Position;EmitVertex();                                 gl_Position=gl_in[0].gl_Position;EmitVertex();                                 }                                                                                   if(gl_in[0].gl_Position.y<0.5){                                                  gl_Position=gl_in[0].gl_Position;EmitVertex();                                 gl_Position=gl_in[0].gl_Position+vec4(0.0,0.1,0.0,0.0);EmitVertex();          }else{                                                                              gl_Position=gl_in[0].gl_Position;EmitVertex();                                 gl_Position=gl_in[0].gl_Position;EmitVertex();                                 }                                                                                   if(gl_in[0].gl_Position.y>-0.5){                                                 gl_Position=gl_in[0].gl_Position;EmitVertex();                                 gl_Position=gl_in[0].gl_Position+vec4(0.0,-0.1,0.0,0.0);EmitVertex();         }else{                                                                              gl_Position=gl_in[0].gl_Position;EmitVertex();                                 gl_Position=gl_in[0].gl_Position;EmitVertex();                                 }
}

你可能会奇怪gl_in[0].gl_Position.x<0.5这样的判断是干什么的,其实这里是让绘制边缘点的十字时不要画多出的部分,要不然棋盘边缘就不是光滑的了(可见之前的图中的蓝色箭头),至于为什么是0.5,而不是0.7,这是在不断的调试过程中得到的,因为这些点是细分生成的,并不是边缘上的点,所以点的位置要靠内一点,要保证这个值把最外层的点排除在外,见下图:

0.7是从顶点着色器中传入的顶点位置,但是细分后得到的顶点时红色和绿色的顶点,而位于外侧的绿色顶点在绘制线段条带时有些线段不要绘制,所以上面设置的值在0.5左右。

别忘了设置一下线段的粗细:

glLineWidth(3.0);

现在我们绘制好的结果是:

3.3 绘制棋子

绘制棋子我们打算用glVertexAttrib4fv();像着色器程序中传入点,然后绘制。

我们想通过vec4来传入3个值,用x表示是黑棋还是白棋,用y表示水平方向上对应的格子,用z表示垂直方向上对应的格子(比如左上第一个就是y=0,z=0 第一行第二个就是y=1,z=0 第三行第五个就是y=4,z=2以此类推),然后我们通过计算得到棋子位置的标准坐标(OpenGL的标准设备坐标)

顶点着色器:

layout(location = 0) in vec4 a;
out vec4 vscolor;
void main(void) {                                           float x = 0.0;                         float y = 0.0;                         x = (a.y - 9) * 0.7 / 9.0;             y = (-a.z + 9) * 0.7 / 9.0;               gl_Position = vec4(x, y, 0.5, 1.0);    //具体的落子位置计算if ((a.x -1.0)<0.001&&(a.x -1.0)>-0.001) { vscolor = vec4(0.0,0.0,0.0,1.0);       }                                           else if ((a.x -2.0)<0.001&&(a.x -2.0)>-0.001) {vscolor = vec4(1.0, 1.0, 1.0, 1.0);       }                                           else {                                      vscolor = vec4(1.0, 0.0, 0.0, 1.0);        }//判断棋子的颜色
}

你可能不太明白(a.x -1.0)<0.001&&(a.x -1.0)>-0.001这种判断是在干什么,这是因为传入着色器的浮点数,浮点数与整数不同,浮点数有精度的限制的。这导致在传入到着色器中时可能在精度外的小数位上出现变化,导致传入的1.0f和里面的1.0f不完全相等,所以我设置了只要相差在0.001之内的值我们都判断为相等,毕竟棋子也只分黑白…。

片段着色器:

#version 450 core
in vec4 vscolor;//黑为1白为0
out vec4 color;
void main(void){                                                color = vscolor;
}

没啥可说的,就是把结果输出。

绘制的函数:

glVertexAttrib4fv(0, a);
glDrawArrays(GL_POINTS, 0, 1);

一次只绘制一个棋子,这会导致我们频繁的调用glVertexAttrib4fv和glDrawArrays函数,多次的函数调用会拖慢时间,使得程序变慢(虽然你目前还感受不到),我们希望减少函数的调用,这需要以后我们介绍了VBO之后,才能得到解决(这个以后再说,现在先这样)。

当然在绘制之前,不要忘了设置点的大小:

glPointSize(25.0);

绘制棋盘和点后的结果(自己拿一些点试验就行了):

4 startup、render、shutdown

既然我们已经搞定了棋盘和棋子的绘制,我们现在就把startup、render、shutdown函数来编辑一下,因为我们的数据最终是由我们的输入来进行修改,而绘制是根据数据来绘制的,所以虽然我们还没处理输入的问题,但是我们现在已经可以来编辑startup、render、shutdown这三个函数了(因为数据结构已经确定了)。

void startup() {compile_shader();rendering_program1 = linkprogram1();rendering_program2 = linkprogram2();//编译生成两个着色器程序glCreateVertexArrays(1, &vertex_array_object);glBindVertexArray(vertex_array_object);//顶点数组VAO,绘制图形必须glPatchParameteri(GL_PATCH_VERTICES, 4);//设置一下细分的时候传入的是4个顶点的四边形glLineWidth(3.0);glPointSize(25.0);//绘制棋盘和棋子时候稍微加粗一下(线、点的大小设置)
}
void render(double currentTime) {static const GLfloat red[] = { 0.2f,0.3f,0.4f,1.0f };static const GLfloat black[] = { 0.2f,0.2f,0.2f,1.0f };static const GLfloat white[] = { 0.5f,0.5f,0.5f,1.0f };if (iswin == 0)glClearBufferfv(GL_COLOR, 0, red);else if (iswin == 1)glClearBufferfv(GL_COLOR, 0, black);else if (iswin == 2)glClearBufferfv(GL_COLOR, 0, white);glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);glUseProgram(rendering_program1);glDrawArrays(GL_PATCHES, 0, 4);//绘制棋盘glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);glUseProgram(rendering_program2);for (int i = 0;i < chess_number;i++) {GLfloat a[4] = { chess_draw_list[i][0],chess_draw_list[i][1],chess_draw_list[i][2],0.0f };//还记得吗,[0]表示棋子种类,[1]表示数组下标x,[2]表示数组下标yglVertexAttrib4fv(0, a);glDrawArrays(GL_POINTS, 0, 1);}  //根据下子的数量,对chess_draw_list的棋子一个一个进行绘制
}
void shutdown() {delete_shader();glDeleteVertexArrays(1, &vertex_array_object);glDeleteProgram(rendering_program1);glDeleteProgram(rendering_program2);glDeleteVertexArrays(1, &vertex_array_object);//为什么要删除两次??
}

OK,现在我们把从数据到最终的画面(OpenGL绘制)的过程都讲完了,至于如何读取输入来修改数据,我们下篇再述。

5 总结

这篇把从数据到画面的过程说完了,其中的难点是对四边形的细分,尤其是细分评估着色器中的计算,我们参考了红宝书中的内容(虽然目前还没理解透彻)。还有我们通过绘制线段条带的方式解决了细分后正方形中有斜线的问题(你有没有什么其他的解决方法呢?欢迎讨论),下一篇我们说说如何获取输入,并且修改数据,判断输赢。

在整个都说完之后,我会单独开一篇将整个程序的代码放在其中,方便大家阅读,注意最终的版本可能和之前说的不太一样,主要是一些细节上的填充,一些注释上的省略,整体的逻辑和流程是一样的,我一方面希望大家自己动手写一写,另一方面也希望大家能看看整个程序,看看能不能修改让它变得更好~

关于书籍的问题
如果你手中没有该书,我还是建议你购买一本,毕竟书本毕竟更加严谨专业,我这里难免遗漏一些细节,主要是提供实例,并做一个消化,将很混乱的流程为大家理清,但这笔记一定是通俗的,是对新手友好的(当然有时候你需要在某些方面自己努努力,比如后面出现的基本线性代数的内容,还有C语言或是c++的基础知识,虽然我可能也不太懂O(∩_∩)O,慢慢来吧)。

别被吓住
刚开始的时候很容易被OpenGL的巨长的函数和超级复杂的流程吓到,其实并没有那么可怕,只要对这样或那样的流程熟悉之后,一切都变得相当简单(当然如果你能提出一个更好的流程那就更好了,当我们把很多基础的工作做完,我们会不断的提出新问题新点子,用新的技术来实现它,最终完成OpenGL的学习)

虽然我也不知道后面将是怎样的道路,但至少努力学习是没错的。

我看过的相关内容
以下并不是全看完了,大部分看了15%就看不下去了,实在是没看懂。(本人没什么计算机编程基础,算是野生程序员吧,很多内容都不能标准表述,望见谅)
如果你对opengl的工作有了一定的了解,我一开始也是从这里开始的,但是仍然有很多的不懂的,最后至今为止,我杂糅了很多的网站内容包括LearnOpenGL、极客学院、哔哩哔哩的闫令琪计算机图形学、哔哩哔哩的傅老师的OpenGL课程、OpenGL编程指南"也称为红宝书"、OpenGL超级宝典"也称为蓝宝书"、当然还有很多的csdn文章O(∩_∩)O这就不介绍了,等用到是时候我在放链接吧O(∩_∩)O

这里面图形学比较易懂也很基础推荐可以作为开始(如果你是学OpenGL需要马上用,应该可以跳过,但是其中的内容很是很重要,这会让后面涉及变换透视的章节更加易懂,推荐大家看看),之后是蓝宝书或是极客学院翻译的教程比较推荐,这两个还是比较适合你我这样的新手的。
这里不推荐看的是红宝书,这本书我看了有点类似于字典那样的工具书,不太适合新手上手学,而且讲的也并不是很通俗易懂(可能是我的书版本比较老吧…)

加油
当然如果你对我有信心,我也会持续更新(虽然前路漫漫),跟大家一同进步(虽然很可能没人看(╥╯^╰╥),无所谓了,当然如有错误还请大家指正∠(°ゝ°),哪里不懂我会尽力解决,哪里说的不好也可以指出我会及时修改~)

我们下篇见~~

OpenGL超级宝典(第7版)笔记13 前三章实例 下个五子棋 (上)相关推荐

  1. 《OpenGL超级宝典第5版》学习笔记(一)—— 第一个OpenGL程序

    // GLTools库包含了一个用于操作矩阵和向量的3D数学库,并依靠GLEW获得OpenGL3.3中用来产生和渲染一些简单3D对象的函数, // 以及对视觉平截头体.相机类和变换矩阵进行管理的函数的 ...

  2. OpenGL超级宝典第7版环境配置

    1.下载源码 地址:http://www.openglsuperbible.com/ 2.运行Cmake,编译glfw库 打开项目后,编译工程(Debug和Release下都编译). 把E:\open ...

  3. OpenGL超级宝典(第7版)笔记4 渲染管线介绍 清单2.3-2.7

    OpenGL超级宝典(第7版)笔记4 渲染管线介绍 清单2.3-2.7 文章目录 OpenGL超级宝典(第7版)笔记4 渲染管线介绍 清单2.3-2.7 1 OpenGL简介 2 OpenGL渲染管线 ...

  4. OpenGL超级宝典学习笔记——操作矩阵

    为了更强大的功能和灵活性,我们有时需要直接操作矩阵.在OpenGL中4x4的矩阵用包含16个浮点数值的一维数组来表示,而不是用二维的4x4的数组来表示.OpenGL之所以这么做,因为使用一维数组更高效 ...

  5. OpenGL超级宝典(第五版)环境配置

    本文转自:http://blog.csdn.net/sunny_unix/article/details/8056807,感谢作者分享. OpenGL超级宝典(第五版)环境配置 Vs2008+winX ...

  6. OpenGL超级宝典(第五版)环境配置【转】

    OpenGL超级宝典(第五版)环境配置 Vs2008+winXP  后续会整理Ubuntu 12.04LTS下的配置作者:sunny_unix 1.各种库的配置 (1)glew 下载:https:// ...

  7. OpenGL超级宝典(第五版) 环境配置

    特别提醒:有些在word中或者其他中的代码复制到vs中会报错,原因是word中有些隐含的字符,复制到vs中就会报错:重新输一遍就可以解决问题,这里只是提醒下! 可以参阅我前面转载的一篇文章,进行比较然 ...

  8. OpenGL超级宝典(第五版) 环境配置(WinXp+VS2008)

    转自:http://blog.csdn.net/sunny_unix/article/details/8056807 OpenGL超级宝典(第五版)环境配置 1.各种库的配置 (1)glew 下载:h ...

  9. [转]OpenGL超级宝典 5e 环境配置

    OpenGL超级宝典(第五版)环境配置 1.各种库的配置 (1)glew 下载:https://sourceforge.net/projects/glew/files/glew/1.7.0/glew- ...

最新文章

  1. Go 超时引发大量 fin-wait2
  2. php字符串连接符、三元运算符
  3. 第二百一十九天 how can I 坚持
  4. ADO.NET基本运用随笔
  5. 十进制数怎样转成十六进制数
  6. python获取当前网页元素_python – 如何获取webdriver中元素的当前内容
  7. RedHat Linux 加入域
  8. Oracle中关于处理小数点位数的几个函数,取小数位数,Oracle查询函数
  9. 【图像隐写】基于matlab LDPC编码译码改进DCT水印嵌入提取【含Matlab源码 832期】
  10. securecrt登录linux下载文件,Linux使用SecureCRT上传和下载文件教程
  11. html5虚拟摇杆,关于前端:babylonjs-第三方-nipplejs虚拟摇杆
  12. (原创) 学生宿舍管理系统(简易版) C语言
  13. 阿里巴巴实习生初面面经
  14. 非常量引用的初始值必须是左值的处理方法
  15. 索尼和南卡蓝牙耳机哪款比较好?降噪效果好的蓝牙耳机测评
  16. Shadow 腾讯插件化——深度解剖框架设计
  17. 计算机哪里找产品密钥,计算机windows的密钥在哪里可以找到?
  18. ffmpeg nvenc编码
  19. 2022最全Java后端面试真题、两万字1000+道堪称史上最强的面试题不接受任何反驳
  20. 全国计算机等级考试二级 C 语言 程序设计考试大纲

热门文章

  1. OpenCV-Python图像的加法运算cv2.add函数详解
  2. iPhone X下界面满屏展示
  3. 程序员修炼之路(三)一个清华大学毕业生做猎头的感受(转)
  4. 大学笔记怎么记?三款软件 - 搭建自己的笔记框架
  5. 什么是工作分解结构(WBS)?
  6. 百度2020校招:技术改变世界,不负AI韶华
  7. [NEFU]Python应用课程 PPT速记
  8. [wn.run/]网页超好用的命令
  9. FaceNet:人脸识别和聚类的统一嵌入
  10. 随手拍好片如何炼成?用完小米10至尊纪念版我就明白了