Shader 只是进行一些简单的封装,主要功能:

    1、编译着色程序

    2、绑定 Uniform 数据

    3、根据着色程序的顶点属性传递顶点数据到 GPU

  着色程序的编译

  1. GLuint Shader::createShaderProgram(const char* vsname, const char* psname)
  2. {
  3. std::string vShaderSource, fShaderSource;
  4. std::ifstream vShaderFile, fShaderFile;
  5. vShaderFile.exceptions(std::ifstream::badbit);
  6. fShaderFile.exceptions(std::ifstream::badbit);
  7.  
  8. try {
  9. vShaderFile.open(PathHelper::fullPath(vsname), std::ios::in);
  10. fShaderFile.open(PathHelper::fullPath(psname), std::ios::in);
  11.  
  12. std::stringstream vShaderStream, fShaderStream;
  13.  
  14. vShaderStream << vShaderFile.rdbuf();
  15. fShaderStream << fShaderFile.rdbuf();
  16.  
  17. vShaderSource = vShaderStream.str();
  18. fShaderSource = fShaderStream.str();
  19.  
  20. vShaderFile.close();
  21. fShaderFile.close();
  22. }
  23. catch ( std::ifstream::failure e ) {
  24. throw std::exception("Error shader: file not succesfully read");
  25. }
  26. const GLchar* vShaderCode = vShaderSource.c_str();
  27. const GLchar* fShaderCode = fShaderSource.c_str();
  28.  
  29. /* 创建顶点作色器 */
  30. GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
  31. glShaderSource(vertexShader, , &vShaderCode, NULL);
  32. glCompileShader(vertexShader);
  33.  
  34. GLint success;
  35. GLchar infoLog[];
  36.  
  37. glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
  38. if ( !success ) {
  39. glGetShaderInfoLog(vertexShader, , NULL, infoLog);
  40. throw std::exception("");
  41. }
  42.  
  43. /* 创建片段着色器 */
  44. GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
  45. glShaderSource(fragmentShader, , &fShaderCode, NULL);
  46. glCompileShader(fragmentShader);
  47.  
  48. glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
  49. if ( !success ) {
  50. glGetShaderInfoLog(fragmentShader, , NULL, infoLog);
  51. throw std::exception("");
  52. }
  53.  
  54. /* 创建着色程序 */
  55. GLuint shaderProgram = glCreateProgram();
  56. glAttachShader(shaderProgram, vertexShader);
  57. glAttachShader(shaderProgram, fragmentShader);
  58. glLinkProgram(shaderProgram);
  59.  
  60. glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
  61. if ( !success ) {
  62. glGetProgramInfoLog(shaderProgram, , NULL, infoLog);
  63. throw std::exception("");
  64. }
  65. glDeleteShader(vertexShader);
  66. glDeleteShader(fragmentShader);
  67.  
  68. /* 使用着色程序 */
  69. return shaderProgram;
  70. }

  Simple2D 只支持顶点着色器和片段着色器,暂不支持其他着色器。

  OpenGL 绘制方式

  使用openGL图形库绘制,都需要通过openGL接口向图像显卡提交顶点数据,显卡根据提交的数据绘制出相应的图形。

  其中有四种方式:

    1、立即模式

    2、显示列表

    3、顶点数组

    4、现代VAO、ABO

  立即模式和显示列表是 OpenGL 传统模式的绘制方法(现在都 2017 年了,应该没有人用这种方式了吧?),后两种是现代方式绘制。前面渲染器用的就是第四种方式:现代 VBO 和 VAO。

    VBO 即 Vertex Buffer Object,是一个在高速视频卡中的内存缓冲,用来保存顶点数据,也可用于包含诸如归一化向量、纹理和索引等数据。

    VAO 即 Vertex Array Object ,是一个包含一个或多个VBO的对象,被设计用来存储一个完整被渲染对象所需的信息。

    这里不再对其进行介绍,感兴趣的可以点击这个链接:https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/

  

  本次渲染器用的是第三种方式,为了更好地理解这种绘制方式,下面举一个例子,假设顶点着色器的顶点属性为

  1.   layout(location = ) in vec3 Position;
  2.   layout(location = ) in vec2 Texcoord;
  3.   layout(location = ) in vec4 Color;

  这里随机给定一些顶点数据,包含有位置、颜色和纹理坐标

  1. GLfloat vertexes[] = {
  2.   0.0f, 0.0f, 0.0f,
  3.   0.0f, 1.0f, 0.0f,
  4.   1.0f, 1.0f, 0.0f,
  5.   1.0f, 0.0f, 0.0f,
  6. };
  7. GLfloat colors[] = {
  8.   1.0f, 1.0f, 1.0f, 1.0f,
  9.   1.0f, 1.0f, 1.0f, 1.0f,
  10.   1.0f, 1.0f, 1.0f, 1.0f,
  11.   1.0f, 1.0f, 1.0f, 1.0f
  12. };
  13. GLfloat texCoordes[] = {
  14.   0.0f, 1.0f,
  15.   0.0f, 0.0f,
  16.   1.0f, 0.0f,
  17.   1.0f, 1.0f
  18. };

  接下来使用函数 glVertexAttribPointer 把顶点数据传递到 GPU。

  1. void glVertexAttribPointer( GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride,const GLvoid * pointer);
  1. glEnableVertexAttribArray();
  2. glEnableVertexAttribArray();
  3. glEnableVertexAttribArray();
  4. glVertexAttribPointer(, 3, GL_FLOAT, GL_FALSE, , vertexes);
  5. glVertexAttribPointer(, 2, GL_FLOAT, GL_FALSE, , colores);
  6. glVertexAttribPointer(, 4, GL_FLOAT, GL_FALSE, , texCoordes);

  然后调用 glDrawArrays 函数进行绘制(使用了顶点索引的可调用 glDrawElements),上面的例子中每个顶点属性的数据储存在不同的数组中。如果你想把数据都储存在一个数组中,就如下面一样

  1. GLfloat data[] = {
  2.   0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
  3.   0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f,
  4.   1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f,
  5.   1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f
  6. };

  这样称之为交错数组,而且使用函数 glVertexAttribPointer 的参数需要作出相应的改变

  1. glEnableVertexAttribArray();
  2. glEnableVertexAttribArray();
  3. glEnableVertexAttribArray();
  4. glVertexAttribPointer(, 3, GL_FLOAT, GL_FALSE, 9 * sizeof(GLfloat), (char*)data + sizeof(GLfloat) * 0);
    glVertexAttribPointer(, 2, GL_FLOAT, GL_FALSE, 9 * sizeof(GLfloat), (char*)data + sizeof(GLfloat) * 3);
    glVertexAttribPointer(, 4, GL_FLOAT, GL_FALSE, 9 * sizeof(GLfloat), (char*)data + sizeof(GLfloat) * 5);

  第五个参数是跨度,即所有顶点属性的大小,3 + 2 + 4 = 9,最后一个参数是数据的指针,不同顶点属性的数据要加上适当的偏移。最后调用绘制函数即可。

  通过上面的例子可知,每个顶点着色器的顶点属性都不一定一样,所以在传递顶点数据到 GPU 时所执行的操作不一样。所以在创建 Shader 前需要设置 Shader 的顶点属性数组,通过顶点属性数组来执行传递数据到 GPU 的操作。

  分析 glVertexAttribPointer 函数的参数,定义下面顶点属性的结构

  1. struct VertexAttribute
  2. {
  3. int layout;
  4. int size;
  5. int type;
  6. int stride;
  7. int offset;
  8. };

  

  Shader 默认有两种顶点属性,分别是 位置-颜色 和 位置-纹理坐标-颜色

  1. enum CustomVertexAttribute
  2. {
  3. CVA_UNKNOEWN,
  4. CVA_V3F_C4F,
  5. CVA_V3F_T2F_C4F
  6. };

  

  设置着色器的顶点属性数组,默认最多存在8个顶点属性

  1. void Shader::setVertexAttribute(CustomVertexAttribute cva)
  2. {
  3. if ( cva == CustomVertexAttribute::CVA_V3F_C4F ) {
  4. VertexAttribute vertexAttributes[] = {
  5. { , , GL_FLOAT, * sizeof(GL_FLOAT), * sizeof(GL_FLOAT) },
  6. { , , GL_FLOAT, * sizeof(GL_FLOAT), * sizeof(GL_FLOAT) }
  7. };
  8. this->setVertexAttribute(vertexAttributes, sizeof(vertexAttributes) / sizeof(VertexAttribute));
  9. }
  10. else if(cva == CustomVertexAttribute::CVA_V3F_T2F_C4F){
  11. VertexAttribute vertexAttributes[] = {
  12. { , , GL_FLOAT, * sizeof(GL_FLOAT), * sizeof(GL_FLOAT) },
  13. { , , GL_FLOAT, * sizeof(GL_FLOAT), * sizeof(GL_FLOAT) },
  14. { , , GL_FLOAT, * sizeof(GL_FLOAT), * sizeof(GL_FLOAT) }
  15. };
  16. this->setVertexAttribute(vertexAttributes, sizeof(vertexAttributes) / sizeof(VertexAttribute));
  17. }
  18. }
  19.  
  20. void Shader::setVertexAttribute(VertexAttribute* attribute, int count)
  21. {
  22. assert(count < );
  23. for ( nVertexAttributeCount = ; nVertexAttributeCount < count; nVertexAttributeCount++ ) {
  24. vertexAttributes[nVertexAttributeCount] = attribute[nVertexAttributeCount];
  25. }
  26. }

  在设置好 Shader 的顶点属性数组后,就可以正确的传递顶点数据到 GPU 了

  1. void Shader::bindVertexDataToGPU(void* data)
  2. {
  3. void* data_offset = nullptr;
  4. for ( int i = ; i < nVertexAttributeCount; i++ ) {
  5. data_offset = static_cast< char* > ( data ) +vertexAttributes[i].offset;
  6.  
  7. /* 上传顶点数据 */
  8. glVertexAttribPointer(
  9. vertexAttributes[i].layout,
  10. vertexAttributes[i].size,
  11. vertexAttributes[i].type,
  12. GL_FALSE,
  13. vertexAttributes[i].stride,
  14. data_offset);
  15.  
  16. glEnableVertexAttribArray(vertexAttributes[i].layout);
  17. }
  18. }

  函数接受顶点数据的指针,然后根据顶点属性数组调用 glVertexAttribPointer 函数设置顶点属性数组的数据格式和位置,最后调用绘制函数绘制即可。

  绑定 Uniform 数据

  假如你要绑定一个整数 2 到着色器,你可以通过两步完成:

    1、调用函数 glGetUniformLocation 获取着色器中 Uniform 变量的绑定点 location。

    2、调用 glUniform 为当前着色程序对象指定Uniform变量的值。

  由于只是绑定一个整型数 2,可以使用下面的代码绑定

  1. glUniform1i(glGetUniformLocation(shaderProgram, "valueName"), 2);

  值得注意的是,C 语言没有函数重载,所以会有很多名字相同后缀不同的函数版本存在。其中函数名中包含数字(1、2、3、4)表示接受这个数字个用于更改uniform变量的值,i表示32位整形,f表示32位浮点型,ub表示8位无符号byte,ui表示32位无符号整形,v表示接受相应的指针类型。

  下面列举了这些函数

  1. void glUniform1f(GLint location, GLfloat v0);
  2. void glUniform2f(GLint location, GLfloat v0, GLfloat v1);
  3. void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
  4. void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
  5. void glUniform1i(GLint location, GLint v0);
  6. void glUniform2i(GLint location, GLint v0, GLint v1);
  7. void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2);
  8. void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3);
  9.  
  10. void glUniform1fv(GLint location, GLsizei count, const GLfloat *value);
  11. void glUniform2fv(GLint location, GLsizei count, const GLfloat *value);
  12. void glUniform3fv(GLint location, GLsizei count, const GLfloat *value);
  13. void glUniform4fv(GLint location, GLsizei count, const GLfloat *value);
  14. void glUniform1iv(GLint location, GLsizei count, const GLint *value);
  15. void glUniform2iv(GLint location, GLsizei count, const GLint *value);
  16. void glUniform3iv(GLint location, GLsizei count, const GLint *value);
  17. void glUniform4iv(GLint location, GLsizei count, const GLint *value);
  18.  
  19. void glUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
  20. void glUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
  21. void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);

  这次 Shader 类对 Uniform 的绑定做一点简单的封装,新定义一个 Uniform 类,实现 Uniform 数据的绑定(一个 Uniform 类对象表示着色器中的一个 Uniform 数据,如果着色器存在多个 Uniform 数据,则 Shader 也相应有 Uniform 对象数组)。首先定义 Uniform 类型枚举

  1. enum UniformType
  2. {
  3. UT_1I, UT_1F,
  4. UT_2I, UT_2F,
  5. UT_3I, UT_3F,
  6. UT_4I, UT_4F,
  7.  
  8. UT_TEXTURE
  9. };

  这次绑定 Uniform 数据不包括数组和矩阵,只是一些绑定简单的数据。

   要绑定一个 Uniform 数据,需要绑定点 location 和值 value,故 Uniform 类成员属性如下

  1. int nLocation;
  2.  
  3. UniformType uniformType;
  4. float fV0, fV1, fV2, fV3;

  当绑定一个 float 数据时使用 fV0,如果绑定 vec2 数据则使用 fV0 和 fV1(后两个不使用),其他情况类似。

  下面看具体的绑定函数

  1. bool bind(int tex = )
  2. {
  3. switch ( uniformType ) {
  4. case Simple2D::Uniform::UT_1I:
  5. glUniform1i(nLocation, fV0);
  6. break;
  7. case Simple2D::Uniform::UT_1F:
  8. glUniform1f(nLocation, fV0);
  9. break;
  10. case Simple2D::Uniform::UT_2I:
  11. glUniform2i(nLocation, fV0, fV1);
  12. break;
  13. case Simple2D::Uniform::UT_2F:
  14. glUniform2f(nLocation, fV0, fV1);
  15. break;
  16. case Simple2D::Uniform::UT_3I:
  17. glUniform3i(nLocation, fV0, fV1, fV2);
  18. break;
  19. case Simple2D::Uniform::UT_3F:
  20. glUniform3f(nLocation, fV0, fV1, fV2);
  21. break;
  22. case Simple2D::Uniform::UT_4I:
  23. glUniform4i(nLocation, fV0, fV1, fV2, fV3);
  24. break;
  25. case Simple2D::Uniform::UT_4F:
  26. glUniform4f(nLocation, fV0, fV1, fV2, fV3);
  27. break;
  28. case Simple2D::Uniform::UT_TEXTURE:
  29. glActiveTexture(GL_TEXTURE0 + tex);
  30. glBindTexture(GL_TEXTURE_2D, fV0);
  31. glUniform1i(nLocation, tex);
  32. return true;
  33. }
  34. return false;
  35. }

 

函数的最后实现的是绑定纹理的功能,设置纹理到着色器,我们可以使用 glActiveTexture 激活纹理单元,传入我们需要使用的纹理单元:

  1.  glActiveTexture(GL_TEXTURE0); // 在绑定纹理之前先激活纹理单元
  2.  glBindTexture(GL_TEXTURE_2D, texture);
    glUniform1i(location, 0);

激活纹理单元之后,接下来的 glBindTexture 函数调用会绑定这个纹理到当前激活的纹理单元,最后的 glUniform1i 函数传入纹理单元序号 0(纹理单元 GL_TEXTURE8 的序号为 8) 参数实现将纹理设置到着色器。纹理单元 GL_TEXTURE0 默认总是被激活,所以我们只有一张纹理时使用 glBindTexture 的时候,无需激活任何纹理单元。

OpenG L至少保证有 16 个纹理单元供你使用,也就是说你可以激活从 GL_TEXTURE0 到 GL_TEXTURE15。它们都是按顺序定义的,所以我们也可以通过GL_TEXTURE0 + 8 的方式获得 GL_TEXTURE8。

如果我要使用 Shader 绑定一个 vec2 数据,希望可以通过这样的代码实现:

  1. shader->getUniformByName("valueName")->setValue(, );

主要是 getUniformByName 和 setValue 函数的实现:

  1. Uniform* Shader::getUniformByName(const char* name)
  2. {
  3. int location = glGetUniformLocation(program, name);
  4. assert(location != -);
  5.  
  6. auto it = mUniforms.find(location);
  7. if ( it != mUniforms.end() ) {
  8. return &it->second;
  9. }
  10.  
  11. return &mUniforms.insert(std::make_pair(location, Uniform(location))).first->second;
  12. }

Shader 有一张 Uniform 数据表:

  1. std::map<int, Uniform> mUniforms;

绑定点可以索引到 Uniform 数据,函数 getUniformByName 一开始根据 Uniform 名称查找其绑定点 location,然后通过 location 查找 Uniform 数据表,有就直接返回 Uniform,否则插入一个新的 Uniform 后返回。

接下来就是将值储存到 Uniform 对象中:

  1. void setValue(int v0) { uniformType = UT_1I; fV0 = v0; }
  2. void setValue(float v0) { uniformType = UT_1F; fV0 = v0; }
  3.  
  4. void setValue(int v0, int v1) { uniformType = UT_2I; fV0 = v0; fV1 = v1; }
  5. void setValue(float v0, float v1) { uniformType = UT_2F; fV0 = v0; fV1 = v1; }
  6.  
  7. void setValue(int v0, int v1, int v2) { uniformType = UT_3I; fV0 = v0; fV1 = v1; fV2 = v2; }
  8. void setValue(float v0, float v1, float v2) { uniformType = UT_3F; fV0 = v0; fV1 = v1; fV2 = v2; }
  9.  
  10. void setValue(int v0, int v1, int v2, int v3) { uniformType = UT_4I; fV0 = v0; fV1 = v1; fV2 = v2; fV3 = v3; }
  11. void setValue(float v0, float v1, float v2, float v3) { uniformType = UT_4F; fV0 = v0; fV1 = v1; fV2 = v2; fV3 = v3; }
  12.  
  13. void setTexture(int v0) { uniformType = UT_TEXTURE; fV0 = v0; }

最后,在 Shader 的 bindUniform 函数实现 Shader 中所有 Uniform 的绑定:

  1. void Shader::bindUniform()
  2. {
  3. int tex = ;
  4. for ( auto& ele : mUniforms ) {
  5. if ( ele.second.bind(tex) ) {
  6. tex++;
  7. }
  8. }
  9. }

就是调用 Uniform 的 bind 函数而已。

Shader 简易封装到此结束,源码在完成重构渲染器后给出。

基于OpenGL编写一个简易的2D渲染框架-09 重构渲染器-Shader的更多相关文章

  1. 基于OpenGL编写一个简易的2D渲染框架-05 渲染文本

    阅读文章前需要了解的知识:文本渲染 https://learnopengl-cn.github.io/06%20In%20Practice/02%20Text%20Rendering/ 简要步骤: 获 ...

  2. 基于OpenGL编写一个简易的2D渲染框架-06 编写一个粒子系统

    在这篇文章中,我将详细说明如何编写一个简易的粒子系统. 粒子系统可以模拟许多效果,下图便是这次的粒子系统的显示效果.为了方便演示,就弄成了一个动图. 图中,同时显示了 7 种不同粒子效果,看上去效果挺 ...

  3. 基于OpenGL编写一个简易的2D渲染框架-01 创建窗口

    最近正在学习OpenGL,我认为学习的最快方法就是做一个小项目了. 如果对OpenGL感兴趣的话,这里推荐一个很好的学习网站 https://learnopengl-cn.github.io/ 我用的 ...

  4. 基于OpenGL编写一个简易的2D渲染框架-08 重构渲染器-整体架构

    事实上,前面编写的渲染器 Renderer 非常简陋,虽然能够进行一些简单的渲染,但是它并不能满足我们的要求. 当渲染粒子系统时,需要开启混合模式,但渲染其他顶点时却不需要开启混合模式.所以同时渲染粒 ...

  5. 基于OpenGL编写一个简易的2D渲染框架-04 绘制图片

    阅读文章前需要了解的知识,纹理:https://learnopengl-cn.github.io/01%20Getting%20started/06%20Textures/ 过程简述:利用 FreeI ...

  6. 基于OpenGL编写一个简易的2D渲染框架-03 渲染基本几何图形

    阅读文章前需要了解的知识,你好,三角形:https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/ 要 ...

  7. 基于OpenGL编写一个简易的2D渲染框架-02 搭建OpenGL环境

    由于没有使用GLFW库,接下来得费一番功夫. 阅读这篇文章前请看一下这个网页:https://learnopengl-cn.github.io/01%20Getting%20started/02%20 ...

  8. 基于OpenGL编写一个简易的2D渲染框架-11 重构渲染器-Renderer

    假如要渲染一个纯色矩形在窗口上,应该怎么做? 先确定顶点的格式,一个顶点应该包含位置信息 vec3 以及颜色信息 vec4,所以顶点的结构体定义可以这样: struct Vertex { Vec3 p ...

  9. 基于OpenGL编写一个简易的2D渲染框架-10 重构渲染器-Pass

    Pass,渲染通路,一个渲染通路指的是一次像素处理和一次顶点处理,也就是指的是一次绘制.简单来说就是顶点数据在渲染管线中走一遍最后绘制. 渲染粒子系统的粒子时,需要开启 OpenGL 的混合模式,并使 ...

随机推荐

  1. task optimization之superglue分析

    开启logging (例子F:\wamp\www\git_repos\GitHub\GeneralUtility\superglue-master\examples\src\logging.cpp) ...

  2. weexpack 使用

    weexpack 的github地址:https://github.com/weexteam/weex-pack weex-toolkit: 初始化的项目是针对开发单个 Weex 页面而设计的,也就是 ...

  3. VS2017增加数据库连接串

    新装VS2017后,按照三石提供的MVC入门教程,创建MVC应用后,能生成数据库文件(在APP_DATA目录),但无法创建连接,服务器路径一直不正确. 解决方法: 1.重新打开VS2017安装界面,选 ...

  4. 我的主机是win 7 虚拟机是vmware,solaris10连接主机

    进入主机Control Panel—Administrative Tool—Services,打开服务Vmware DHCP Servise和Vmware NAT Service,启动后虚拟机上网正常 ...

  5. svn 提交代码 自动过滤技巧,自动过滤不想提交的文件和文件夹

    原文:http://www.cnblogs.com/codealone/p/3208620.html 打开SVN客户端-----常规设置------全局忽略样式,添加 bin obj debug Re ...

  6. 【Spring学习笔记-MVC-16】Spring MVC之重定向-解决中文乱码

    概述 spring MVC框架controller间跳转,需重定向,主要有如下三种: 不带参数跳转:形如:http://localhost:8080/SpringMVCTest/test/myRedi ...

  7. 【Hibernate学习笔记-5】@Formula注解的使用

    ORM映射关系:注解方式 package org.crazyit.app.domain; import javax.persistence.*; import org.hibernate.annota ...

  8. Oracle学习操作(6)函数与存储过程

    一.oracle自定义函数 1.不带参数的函数: 返回t_book表的总条数: SQL> create function getBookCount return number as begin ...

  9. 基于HttpClient JSONObject与JSONArray的使用

    package com.spring.utils; import net.sf.json.JSONArray; import net.sf.json.JSONObject; import org.ap ...

  10. 基于nginx和tengine的tcp反向代理,负载均衡 安装和配置

    先下载nginx_tcp_proxy_module模块. wget https://github.com/yaoweibin/nginx_tcp_proxy_module/archive/master ...