现代OpenGL教程 01 - 入门指南

2023-11-19

文章转载自:http://huangwei.pro/2015-05/modern-opengl1/

以下是我学习opengl得到的启示最多的一篇文章,我强烈地建议大家去读一下这位大神的相关系列的文章!还有https://github.com/tomdalling/opengl-series。这里面的代码包含全面,真正想学习opengl的可以去看看!而且比我写的代码精致得多!

译序

早前学OpenGL的时候还是1.x版本,用的都是glVertexglNormal等固定管线API。后来工作需要接触DirectX9,shader也只是可选项而已,跟固定管线一起混用着。现在工作内容是手机游戏,又转到OpenGL ES,发现OpenGL的世界已经完全不同了,OpenGL ES 2.0版本开始就不再支持固定管线,只支持可编程管线。



国内很多资料教程参差不齐,旧式接口满天飞。在知乎看到这一系列教程,觉着挺好,就想着一边学顺便翻译下。毕竟手游市场的机遇和竞争压力都在同比猛涨,多了解OpenGL ES肯定没有坏处。浮躁功利的环境下更需要怀着一颗宁静致远的心去提高自身功底,长路漫漫,与君共勉。

欢迎大家,这是现代OpenGL教程系列的第一篇。所有代码都是开源的,你可以在GitHub上下载:https://github.com/tomdalling/opengl-series

通过这篇教程,你将会学到如何在Windows下用Visual Studio 2013或Mac下用Xcode搭建OpenGL 3.2工程。该应用包含一个顶点着色器(vertex shader),一个片段着色器(fragment shader)和使用VAO和VBO来绘制的三角形。该工程使用GLEW来访问OpenGL API,用GLFW来处理窗口创建和输入,还有使用GLM进行矩阵/矢量相关的数学运算。

这听上去有点无聊,但搭建这样的工程确实挺麻烦的,尤其对于初学者。只要解决完这问题,我们就可以开始玩些有趣的东西了。

[TOC]

获取代码

所有例子代码的zip打包可以从这里获取:https://github.com/tomdalling/opengl-series/archive/master.zip

这一系列文章中所使用的代码都存放在:https://github.com/tomdalling/opengl-series。你可以在页面中下载zip,加入你会git的话,也可以复制该仓库。

本文代码你可以在source/01_project_skeleton目录里找到。使用OS X系统的,可以打开根目录里的opengl-series.xcodeproj,选择本文工程。使用Windows系统的,可以在Visual Studio 2013里打开opengl-series.sln,选择相应工程。

工程里已包含所有依赖,所以你不需要再安装或者配置额外的东西。如果有任何编译或运行上的问题,请联系我。

关于兼容性的提醒

本文使用OpenGL 3.2,但我会尝试保持如下兼容:

  • 向后兼容OpenGL 2.1
  • 向前兼容OpenGL 3.X和4.X
  • 兼容Android和iOS的OpenGL ES 2.0

因为OpenGL和GLSL存在许多不同版本,本文代码不一定能做到100%上述兼容。我希望能兼容99%,并且不同版本之间只要轻微修改即可。

想要了解OpenGL和GLSL不同版本间的区别,这里很好得罗列了兼容列表

Visual Studio下安装

代码在Windows 7 32位系统,Visual Studio Express 2013(免费)下创建和测试。你应该可以打开解决方案并成功编译所有工程。如果有问题请联系我,或者将补丁发我,我会更新工程。

Xcode下安装

Xcode工程实在OSX 10.10系统,Xcode 6.1下创建并测试的。打开Xcode工程应该可以成功编译所有目标。加入你无法成功编译请联系我。

Linux下安装

Linux是基于SpartanJ。我在Ubuntu 12.04下简单测试通过。

  • 安装GLM,GLFW和GLEW:
    sudo aptitude install libglm-dev libglew-dev libglfw-dev
  • 进入工程目录:cd platforms/linux/01_project_skeleto
  • 运行makefile:make
  • 运行可执行文件:bin/01_project_skeleton-debug

GLEW, GLFW和GLM介绍

现在你有了工程,就让我们开始介绍下工程所用到的开源库和为啥需要这些。

The OpenGL Extension Wrangler (GLEW)是用来访问OpenGL 3.2 API函数的。不幸的是你不能简单的使用#include <GL/gl.h>来访问OpenGL接口,除非你想用旧版本的OpenGL。在现代OpenGL中,API函数是在运行时(run time)确定的,而非编译期(compile time)。GLEW可以在运行时加载OpenGL API。

GLFW允许我们跨平台创建窗口,接受鼠标键盘消息。OpenGL不处理这些窗口创建和输入,所以就需要我们自己动手。我选择GLFW是因为它很小,并且容易理解。

OpenGL Mathematics (GLM)是一个数学库,用来处理矢量和矩阵等几乎其它所有东西。旧版本OpenGL提供了类似glRotateglTranslateglScale等函数,在现代OpenGL中,这些函数已经不存在了,我们需要自己处理所有的数学运算。GLM能在后续教程里提供很多矢量和矩阵运算上帮助。

在这系列的所有教程中,我们还编写了一个小型库tdogl用来重用C++代码。这篇教程会包含tdogl::Shadertdogl::Program用来加载,编译和链接shaders。

什么是Shaders?

Shaders在现代OpenGL中是个很重要的概念。应用程序离不开它,除非你理解了,否则这些代码也没有任何意义。

Shaders是一段GLSL小程序,运行在GPU上而非CPU。它们使用OpenGL Shading Language (GLSL)语言编写,看上去像C或C++,但却是另外一种不同的语言。使用shader就像你写个普通程序一样:写代码,编译,最后链接在一起才生成最终的程序。

Shaders并不是个很好的名字,因为它不仅仅只做着色。只要记得它们是个用不同的语言写的,运行在显卡上的小程序就行。

在旧版本的OpenGL中,shaders是可选的。在现代OpenGL中,为了能在屏幕上显示出物体,shaders是必须的。

为可能近距离了解shaders和图形渲染管线,我推荐Durian Software的相关文章The Graphics Pipeline chapter

  主程序 Shader程序
语言 C++ GLSL
主函数 int main(int, char**); void main();
运行于 CPU GPU
需要编译?
需要链接?

那shaders实际上干了啥?这取决于是哪种shader。

Vertex Shaders

Vertex shader主要用来将点(x,y,z坐标)变换成不同的点。顶点只是几何形状中的一个点,一个点叫vectex,多个点叫vertices(发音为ver-tuh-seez)。在本教程中,我们的三角形需要三个顶点(vertices)组成。

Vertex Shader的GLSL代码如下:

1
2
3
4
5
6
7
8
#version 150

in vec3 vert;

void main() {
    // does not alter the vertices at all
    gl_Position = vec4(vert, 1);
}

第一行#version 150告诉OpenGL这个shader使用GLSL版本1.50.

第二行in vec3 vert;告诉shader需要那一个顶点作为输入,放入变量vert

第三行定义函数main,这是shader运行入口。这看上去像C,但GLSL中main不需要带任何参数,并且不用返回void。

第四行gl_Position = vec4(vert, 1);将输入的顶点直接输出,变量gl_Position是OpenGL定义的全局变量,用来存储vertex shader的输出。所有vertex shaders都需要对gl_Position进行赋值。

gl_Position是4D坐标(vec4),但vert是3D坐标(vec3),所以我们需要将vert转换为4D坐标vec4(vert, 1)。第二个的参数1是赋值给第四维坐标。我们会在后续教程中学到更多关于4D坐标的东西。但现在,我们只要知道第四维坐标是1即可,i可以忽略它就把它当做3D坐标来对待。

Vertex Shader在本文中没有做任何事,后续我们会修改它来处理动画,摄像机和其它东西。

Fragment Shaders

Fragment shader的主要功能是计算每个需要绘制的像素点的颜色。

一个”fragment”基本上就是一个像素,所以你可以认为片段着色器(fragment shader)就是像素着色器(pixel shader)。在本文中每个片段都是一像素,但这并不总是这样的。你可以更改某个OpenGL设置,以便得到比像素更小的片段,之后的文章我们会讲到这个。

本文所使用的fragment shader代码如下:

1
2
3
4
5
6
7
8
#version 150

out vec4 finalColor;

void main() {
    //set every drawn pixel to white
    finalColor = vec4(1.0, 1.0, 1.0, 1.0);
}

再次,第一行#version 150告诉OpenGL这个shader使用的是GLSL 1.50。

第二行finalColor = vec4(1.0, 1.0, 1.0, 1.0);将输出变量设为白色。vec4(1.0, 1.0, 1.0, 1.0)是创建一个RGBA颜色,并且红绿蓝和alpha都设为最大值,即白色。

现在,就能用shader在OpenGL中绘制出了纯白色。在之后的文章中,我们还会加入不同颜色和贴图。贴图就是你3D模型上的图像。

编译和链接Shaders

在C++中,你需要对你的.cpp文件进行编译,然后链接到一起组成最终的程序。OpenGL的shaders也是这么回事。

在这篇文章中用到了两个可复用的类,是用来处理shaders的编译和链接:tdogl::Shadertdogl::Program。这两个类代码不多,并且有详细的注释,我建议你阅读源码并且去链接OpenGL是如何工作的。

什么是VBO和VAO?

当shaders运行在GPU,其它代码运行在CPU时,你需要有种方式将数据从CPU传给GPU。在本文中,我们传送了一个三角的三个顶点数据,但在更大的工程中3D模型会有成千上万个顶点,颜色,贴图坐标和其它东西。

这就是我们为什么需要Vertex Buffer Objects (VBOs)和Vertex Array Objects (VAOs)。VBO和VAO用来将C++程序的数据传给shaders来渲染。

在旧版本的OpenGL中,是通过glVertexglTexCoordglNormal函数把每帧数据发送给GPU的。在现代OpenGL中,所有数据必须通过VBO在渲染之前发送给显卡。当你需要渲染某些数据时,通过设置VAO来描述该获取哪些VBO数据推送给shader变量。

Vertex Buffer Objects (VBOs)

第一步我们需要从内存里上传三角形的三个顶点到显存中。这就是VBO该干的事。VBO其实就是显存的“缓冲区(buffers)” - 一串包含各种二进制数据的字节区域。你能上传3D坐标,颜色,甚至是你喜欢的音乐和诗歌。VBO不关心这些数据是啥,因为它只是对内存进行复制。

Vertex Array Objects (VAOs)

第二步我们要用VBO的数据在shaders中渲染三角形。请记住VBO只是一块数据,它不清楚这些数据的类型。而告诉OpenGL这缓冲区里是啥类型数据,这事就归VAO管。

VAO对VBO和shader变量进行了连接。它描述了VBO所包含的数据类型,还有该传递数据给哪个shader变量。在OpenGL所有不准确的技术名词中,“Vertex Array Object”是最烂的一个,因为它根本没有解释VAO该干的事。

你回头看下本文的vertex shader(在文章的前面),你就能发现我们只有一个输入变量vert。在本文中,我们用VAO来说明“hi,OpenGL,这里的VBO有3D顶点,我想要你在vertex shader时,发三个顶点数据给vert变量。”

在后续的文章中,我们会用VAO来说“hi,OpenGL,这里的VBO有3D顶点,颜色,贴图坐标,我想要你在shader时,发顶点数据给vert变量,发颜色数据给vertColor变量,发贴图坐标给vertTexCoord变量。”

给使用上个OpenGL版本的用户的提醒

假如你在旧版本的OpenGL中使用了VBO但没有用到VAO,你可能会不认同VAO的描述。你会争论说“顶点属性”可以用glVertexAttribPointer将VBO和shaders连接起来,而不是用VAO。这取决于你是否认为顶点属性应该是VAO“内置(inside)”的(我是这么认为的),或者说它们是否是VAO外置的一个全局状态。3.2内核和我用的AIT驱动中,VAO不是可选项 - 没有VAO的封装glEnableVertexAttribArrayglVertexAttribPointerglDrawArrays都会导致GL_INVALID_OPERATION错误。这就是为啥我认为顶点属性应该内置于VAO,而非全局状态的原因。3.2内核手册也说VAO是必须的,但我只听说ATI驱动会抛错误。下面描述引用自OpenGL 3.2内核手册

所有与顶点处理有关的数据定义都应该封装在VAO里。
一般VAO边界包含所有更改vertex array状态的命令,比如VertexAttribPointer和EnableVertexAttribArray;所有使用vertex array进行绘制的命令,比如DrawArrays和DrawElements;所有对vertex array状态进行查询的命令(见第6章)。

不管怎样,我也知道为啥会有人认为顶点属性应该放在VAO外部。glVertexAttribPointer出现早于VAO,在这段时间里顶点属性一直被认为是全局状态。你应该能看得出VAO是一种改变全局状态的有效方法。我更倾向于认为是这样:假如你没有创建VAO,那OpenGL通过了一个默认的全局VAO。所以当你使用glVertexAttribPointer时,你仍然是在VAO内修改顶点属性,只不过现在从默认的VAO变成你自己创建的VAO。

这里有更多的讨论:http://www.opengl.org/discussion_boards/showthread.php/174577-Questions-on-VAOs

代码解释

终于!理论已经说完了,我们开始编码。OpenGL对于初学者而言不是特别友好,但如果你理解了之前所介绍的概念(shaders,VBO,VAO)那你就没啥问题。

打开main.cpp,我们从main()函数开始。

首先,我们初始化GLFW:

1
2
3
glfwSetErrorCallback(OnError);
if(!glfwInit())
    throw std::runtime_error("glfwInit failed");

glfwSetErrorCallback(OnError)这一行告诉GLFW当错误发生时调用OnError函数。OnError函数会抛一个包含错误信息的异常,我们能从中发现哪里出错了。

然后我们用GLFW创建一个窗口。

1
2
3
4
5
6
7
8
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
gWindow = glfwCreateWindow((int)SCREEN_SIZE.x, (int)SCREEN_SIZE.y, "OpenGL Tutorial", NULL, NULL);
if(!gWindow)
    throw std::runtime_error("glfwCreateWindow failed. Can your hardware handle OpenGL 3.2?");

该窗口包含一个向前兼容的OpenGL 3.2内核上下文。假如glfwCreateWindow失败了,你应该降低OpenGL版本。

创建窗口最后一步,我们应该设置一个“当前”OpenGL上下文给刚创建的窗口:

1
glfwMakeContextCurrent(gWindow);

无论我们调用哪个OpenGL函数,都会影响到“当前上下文”。我们只会用到一个上下文,所以设置完后,就别管它了。理论上来说,我们可以有多个窗口,且每个窗口都可以有自己的上下文。

现在我们窗口有了OpenGL上下文变量,我们需要初始化GLEW以便访问OpenGL接口。

1
2
3
glewExperimental = GL_TRUE; //stops glew crashing on OSX :-/
if(glewInit() != GLEW_OK)
    throw std::runtime_error("glewInit failed");

这里的GLEW与OpenGL内核有点小问题,设置glewExperimental就可以修复,但希望再未来永远不要发生。

我们也可以用GLEW再次确认3.2版本是否存在:

1
2
if(!GLEW_VERSION_3_2)
    throw std::runtime_error("OpenGL 3.2 API is not available.");

LoadShaders函数中,我们使用本教程提供的tdogl::Shadertdogl::Program两个类编译和链接了vertex shader和fragment shader。

1
2
3
4
std::vector<tdogl::Shader> shaders;
shaders.push_back(tdogl::Shader::shaderFromFile(ResourcePath("vertex-shader.txt"), GL_VERTEX_SHADER));
shaders.push_back(tdogl::Shader::shaderFromFile(ResourcePath("fragment-shader.txt"), GL_FRAGMENT_SHADER));
gProgram = new tdogl::Program(shaders);

LoadTriangle函数中,我们创建了一个VAO和VBO。这是第一步,创建和绑定新的VAO:

1
2
glGenVertexArrays(1, &gVAO);
glBindVertexArray(gVAO);

然后我们创建和绑定新的VBO:

1
2
glGenBuffers(1, &gVBO);
glBindBuffer(GL_ARRAY_BUFFER, gVBO);

接着,我们上传一些数据到VBO中。这些数据就是三个顶点,每个顶点包含三个GLfloat

1
2
3
4
5
6
7
GLfloat vertexData[] = {
    //  X     Y     Z
     0.0f, 0.8f, 0.0f,
    -0.8f,-0.8f, 0.0f,
     0.8f,-0.8f, 0.0f,
};
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW);

现在缓冲区包含了三角形的三个顶点,是时候开始设置VAO了。首先,我们应该启用shader程序中的vert变量。这些变量能被开启或关闭,默认情况下是关闭的,所以我们需要开启它。vert变量是一个“属性变量(attribute variable)”,这也是为何OpenGL函数名称中有带“Attrib”。我们可以在后续的文章中看到更多类型。

1
glEnableVertexAttribArray(gProgram->attrib("vert"));

VAO设置最复杂的部分就是下个函数:glVertexAttribPointer。让我们先调用该函数,等会解释。

1
glVertexAttribPointer(gProgram->attrib("vert"), 3, GL_FLOAT, GL_FALSE, 0, NULL);

第一个参数,gProgram->attrib("vert"),这就是那个需要上传数据的shder变量。在这个例子中,我们需要发数据给vertshader变量。

第二个参数,3表明每个顶点需要三个数字。

第三个参数,GL_FLOAT说明三个数字是GLfloat类型。这非常重要,因为GLdouble类型的数据大小跟它是不同的。

第四个参数,GL_FALSE说明我们不需要对浮点数进行“归一化”,假如我们使用了归一化,那这个值会被限定为最小0,最大1。我们不需要对我们的顶点进行限制,所以这个参数为false。

第五个参数,0,该参数可以在顶点之间有间隔时使用,设置参数为0,表示数据之间没有间隔。

第六个参数,NULL,假如我们的数据不是从缓冲区头部开始的话,可以设置这个参数来指定。设置该参数为NULL,表示我们的数据从VBO的第一个字节开始。

现在VBO和VAO都设置完成,我们需要对它们进行解绑定,防止一不小心被哪里给更改了。

1
2
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);

到此,shader,VBO和VAO都准备好了。我们可以开始在Render函数里绘制了。

首先,我们先清空下屏幕,让它变成纯黑色:

1
2
glClearColor(0, 0, 0, 1); // black
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

然后告诉OpenGL我们要开始使用VAO和shader了:

1
2
glUseProgram(gProgram->object());
glBindVertexArray(gVAO);

最后,我们绘制出三角形:

1
glDrawArrays(GL_TRIANGLES, 0, 3);

调用glDrawArrays函数说明我们需要绘制三角形,从第0个顶点开始,有3个顶点被发送到shader。OpenGL会在当前VAO范围内确定该从哪里获取顶点。

顶点将会从VBO中取出并发送到vertex shader。然后三角形内的每个像素会发送给fragment shader。接着fragment shader将每个像素变成白色。欢呼!

现在绘制结束了,为了安全起见,我们需要将shader和VAO进行解绑定:

1
2
glBindVertexArray(0);
glUseProgram(0);

最后一件事,在我们看到三角形之前需要切换帧缓冲:

1
glfwSwapBuffers(gWindow);

在帧缓冲被交换前,我们会绘制到一个不可见的离屏(off-screen)帧缓冲区。当我们调用glfwSwapBuffers时,离屏缓冲会变成屏幕缓冲,所以我们就能在窗口上看见内容了。

进一步阅读

在后续文章中,我们会对三角形进行贴图。之后,你会学到一点矩阵变换知识,就可以使用vertex shader来实现3D立方体旋转。

在这之后,我们开始创建3D场景并提交多个物体。

更多现代OpenGL资料

不幸的是,我不得不跳过很多内容,防止本教程的篇幅过长。后面还有很多好的现代OpenGL资料能满足你的求知欲:


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

现代OpenGL教程 01 - 入门指南 的相关文章

随机推荐

  • VB+SQLite组合,真香!(一)

    微信公众号 网管小贾 个人博客 www sysadm cc 嗨 你好 我是网管小贾 当你看到这个标题时 是不是感觉很奇怪呢 老掉牙的VB和 玩具 数据库SQLite搞在一起是个什么玩意 待我慢慢道来哈 SQLite众所周知 是个文件数据库
  • 非常厉害的全文检索技术Elasticsearch

    目录 一 Elasticsearch是什么 二 关于安装 三 kibana的安装和使用 最后 今天是刘小爱自学Java的第157天 感谢你的观看 谢谢你 全文检索技术Elasticsearch的学习 牵扯到的知识点太多太多了 首先要创建一个
  • Cadence Allegro PCB设计88问解析(九) 之 Allegro中封装(footprint)3D模型添加

    一个学习信号完整性的layout工程师 今天整理下PCB封装的3D 模型添加 此步骤并不是所有的公司使用 因为我们平常给器件添加一个实际的高度 就已经OK了 只不过我们在看整版的3D模型是 每个器件都是方方正正的 不太美观 所以有的人要求完
  • JavaScript基础

    编程之修 重在积累 而非资质 资质虽然重要 可是后天的努力更不可缺少 直接量 编程世界中的直接量 就是表面上可以见到的数据值 常见的直接量有数字 小数 字符串 字符串的出现必然带着双引号 单引号也可以 被很好地包裹住 而数字则是光秃秃的 如
  • 解决Opencv高低版本不兼容问题

    分享一下我老师大神的人工智能教程 零基础 通俗易懂 http blog csdn net jiangjunshow 也欢迎大家转载本篇文章 分享知识 造福人民 实现我们中华民族伟大复兴 目前OpenCV版本已更新到2 4 由此出现了一系列问
  • Qt之QSS中替代background-position的方法。

    学过css的朋友 应该都多少了解一些雪碧图相关的知识 雪碧图 用的就是background position来确定选取的素材位置 比如 素材中每张扑克的宽高分别为49px 66px 如果我要选用红桃8 我就会在css中写 backgroun
  • Gradle sync failed: A problem occurred configuring project ‘:app‘解决方法

    在terminal 中输入 gradlew i 查看详细信息 输出信息 NDK is missing a platforms directory If you are using NDK verify the ndk dir is set
  • vue项目中使用iconfont阿里图标库

    1 进入icon 官网 iconfont 阿里巴巴矢量图标库 2 根据搜索选择自己想要的图片 添加到项目中或者新建项目 如下图 3 添加之后如下图 点击下载至本地 4 下载本地解压后的文件如下图 5 在vue项目中 css新建一个文件夹 把
  • JS时间格式和时间戳的相互转换

    时间戳转化为日期的方式 var timestamp 1527521052 var newDate new Date newDate setTime timestamp 1000 Mon May 28 2018 console log new
  • 计算机视觉知识点-图像增强

    图像增强技术通过对训练图像进行一系列随机更改以生成相似但不同的训练示例来扩展训练数据集的规模 随机更改训练示例可以减少模型对某些属性的依赖 从而提高模型的泛化能力 我们可以以不同的方式裁剪图像 以使感兴趣的对象出现在不同的位置 从而减少了模
  • YoungTalk STM32入门第12步——CRC校验和芯片ID

    1 CRC 循环冗余校验 计算单元 2 96位芯片唯一序列号 1 CRC 循环冗余校验 计算单元 CRC计算单元使用一个固定的多项式发生器 从一个32位的数据字产生一个CRC码 在众多应用中 基于CRC的技术被用于验证数据传输或者存储的一致
  • Qt -信号槽实现原理

    一 简介 QT信号槽的实现实质是什么 回调函数 简要说一下信号与槽的底层原理 信号与槽的实现是借助了Qt 的元对象系统 元对象系统有一个元对象编译器 程序编译之前会有一个预处理过程 预处理将一个类 对象中的信号 槽的字符串值分别保存在一个容
  • 小白学股票基金_1

    债券要集资 利息比银行同等期限的高 但是不能随意提取现金 如果未到期就提取就不能按约定的利息给于 不可买卖 可以抵压 股票为单向交易 只能在低时进入买涨 T 1交易当天不能卖出 固定的开收盘时间 易受庄家控制 全资动作 多少钱买多少股 必须
  • lmg_Model Links and Torrents

    lmg Model Links and Torrents Changelog MDY 4 bit GPU Model Requirements 4 bit CPU llama cpp RAM Requirements LLaMA 16 bi
  • GraphicsLayer知识点

    require esri layers GraphicsLayer function GraphicsLayer code goes here 包含一个或多个图形特征的图层 每个地图都包含默认的GraphicsLayer 可以使用map g
  • 如何访问虚拟机中的Web服务

    需求 1 在虚拟机Vmware中安装了CentOS6 5 虚拟机使用NAT的方式 2 在CentOS中安装了APACHE 并且使用 http 192 168 237 128可以正常访问 3 想在其他windows机器上访问该虚拟机的web服
  • linux系统的系统性学习 (持续更新)

    分类 系统启动过程 第一步 内核的引导 第二步 运行 init 第三步 系统初始化 第四步 建立终端 第五步 用户登录系统 关机 查看系统基本信息 CPU相关 内存相关 查看网络信息 用户 服务 进程相关 磁盘管理 df 命令 du 命令
  • Thrift、Dubbo、Spring Cloud 和 gRPC

    何为RPC RPC Remote Procedure Call 远程过程调用 是一种进程间通信方式 是一种技术的思想 而不是规范 它允许程序调用另一个地址空间 通常是共享网络的另一台机器上 的过程或函数 而不用程序员显式编码这个远程调用的细
  • 计算机常用函数及写法,计算机常用的函数公式有哪些?

    01 计算机常用的函数公式包括RANK函数 COUNTIF函数 IF函数 ABS函数 AND函数 AVERAGE函数 COLUMN 函数等 RANK函数是Excel计算序数的主要工具 它的语法为 RANK number ref order
  • 现代OpenGL教程 01 - 入门指南

    文章转载自 http huangwei pro 2015 05 modern opengl1 以下是我学习opengl得到的启示最多的一篇文章 我强烈地建议大家去读一下这位大神的相关系列的文章 还有https github com tomd