首页 > 其他分享 >OpenGL 纹理详解

OpenGL 纹理详解

时间:2023-11-06 18:12:08浏览次数:32  
标签:OpenGL int 纹理 详解 坐标 顶点 GL 我们

1. 纹理

在OpenGL中,纹理是一种常用的技术,用于将图像或图案映射到3D模型的表面上,以增加图形的细节和真实感

2. 纹理坐标

纹理坐标在x和y轴上,范围为0到1之间(注意我们使用的是2D纹理图像)。使用纹理坐标获取纹理颜色叫做采样(Sampling)。纹理坐标起始于(0, 0),也就是纹理图片的左下角,终始于(1, 1),即纹理图片的右上角。下面的图片展示了我们是如何把纹理坐标映射到三角形上的。

 

 当图片绘制大小与图片实际大小不一致时,势必会涉及到缩放。此时而缩放时我们要采取什么插值方式,是需要我们指定给GL的,也就是说,我们要指定采样方式。

3. 采样方式

环绕方式

之前我们有说过,纹理的坐标区域是[0,1],且通常左下角为纹理坐标的(0,0)点。现在我们设想一下下面的情况:

 

 

当我们给出顶点坐标是横纵左边均是0.5的四个象限的点时,我们的渲染区域即是第一个图中的粉色区域。

此时我们有一张图像,即是第四个图样子的图片。

我们知道,纹理坐标系是[0,1]的,如果我们与顶点坐标系中我们指定的四个顶点一一对应起来的时候,那就应该是渲染出第二幅图的样子。

但是如果我们并不是想图像A充满我们的粉色区域怎么办?我们想让图像只有粉色区域的1/2大小,并且居中平铺,这时候怎么办呢?

试想如果大小1/2且居中对齐,那么我们纹理坐标系的(1,1)点应该对应顶点坐标系的(0.25,0.25)。这点没有问题对吧。但实际我们应用只应用顶点坐标,那么我们要换算一下顶点坐标系中(0.5,0.5)对应的是纹理坐标系中的那个点呢?换算完成后应该是(1.5,1.5)。这里如何换算可以结合第三幅图的样子考虑下。这样我们换算完四个顶点坐标分别对应的纹理坐标值后传个顶点着色器就好。

另外我们只会渲染出我们顶点数据所渲染的图形,超出边届的将会被剪裁掉。

事实上,我们只是想绘制一个比顶点区域要小的图片,至于平铺是我们选择的一种环绕方式而已。

然而GL实际为我们提供了四种环绕方式:

 那么知道了这几种环绕方式,在GL中我们要如何设置环绕方式呢?

 当然,如果我们指定边缘颜色的环绕模式,我们还要指定边缘颜色。

 4. 纹理过滤

 

渲染一个图像,我们不可能保证绘制的实际大小即是图片的实际大小,事实上一般情况下,我们都需要进行缩放。我们知道,GL中我们只指定顶点数据,而中间点都是GL内部自己采用插值器进行计算的。那么当进行缩放时,我们就要告诉GL应该采用的插值方式。指定插值方式,又叫做纹理过滤

 

那么缩放就涉及到图像的放大和缩小。我们先想一下放大图像应该采取什么纹理滤镜。

 

这里我们先讨论两个较为重要的纹理滤镜:GL_NEARESTGL_LINEAR

 

GL_NEAREST(也叫邻近过滤,Nearest Neighbor Filtering)是OpenGL默认的纹理过滤方式。当设置为GL_NEAREST的时候,OpenGL会选择中心点最接近纹理坐标的那个像素。下图中你可以看到四个像素,加号代表纹理坐标。左上角那个纹理像素的中心距离纹理坐标最近,所以它会被选择为样本颜色:

 GL_LINEAR(也叫线性过滤,(Bi)linear Filtering)它会基于纹理坐标附近的纹理像素,计算出一个插值,近似出这些纹理像素之间的颜色。一个纹理像素的中心距离纹理坐标越近,那么这个纹理像素的颜色对最终的样本颜色的贡献越大。下图中你可以看到返回的颜色是邻近像素的混合色:

 那么这两种纹理过滤方式有怎样的视觉效果呢?让我们看看在一个很大的物体上应用一张低分辨率的纹理会发生什么吧(纹理被放大了,每个纹理像素都能看到):

 5. 多级渐远纹理

上述中,我们叙述了放大的纹理滤镜,但是如果是缩小呢?我们当然也可以采取之前提到的两种纹理滤镜。但当我们缩小的倍数足够小时,计算插值将会是一个耗时过程,此外缩小本身就会丢失很多细节,这时如果我们仍使用原分辨率的纹理进行缩放并绘制,无疑在内存上也是浪费。

 

如何创建多级渐远纹理呢?我们可以使用glGenerateMipmaps函数。

那么多级渐远纹理有几种模式呢:

 像放大时使用的纹理滤镜一样,我们应该像下面这样设置缩小的纹理滤镜:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
6. 加载与创建纹理
  1 #include <glad/glad.h>
  2 #include <GLFW/glfw3.h>
  3 #include <math.h>
  4 #include <iostream>
  5 #define STB_IMAGE_IMPLEMENTATION
  6 #include "stb_image.h"
  7 
  8 void framebuffer_size_callback(GLFWwindow* window, int width, int height);
  9 void processInput(GLFWwindow *window);
 10 GLFWwindow * configOpenGL();
 11 void loadImg(const char * path,unsigned int * texture,unsigned int uniteLoc);
 12 void configVAO(unsigned int * VAO,unsigned int * VBO,unsigned int * EBO);
 13 void finishiRenderLoop();
 14 // settings
 15 const unsigned int SCR_WIDTH = 800;
 16 const unsigned int SCR_HEIGHT = 600;
 17 
 18 
 19 const char *vertexShaderSource = "#version 330 core\n"
 20 "layout (location = 0) in vec2 aPos;\n"
 21 "layout (location = 1) in vec3 aColor;\n"
 22 "layout (location = 2) in float show;\n"
 23 "layout (location = 3) in vec2 aTexCoord;\n"
 24 "out vec3 ourColor;\n"
 25 "out float Img;\n"
 26 "out vec2 TexCoord;\n"
 27 "void main()\n"
 28 "{\n"
 29 "   gl_Position = vec4(aPos,0.0, 1.0);\n"
 30 "   ourColor = aColor;\n"
 31 "   Img = show;\n"
 32 "   TexCoord = aTexCoord;\n"
 33 "}\0";
 34 const char *fragmentShaderSource = "#version 330 core\n"
 35 "out vec4 FragColor;\n"
 36 "in vec3 ourColor;\n"
 37 "in float Img;\n"
 38 "in vec2 TexCoord;\n"
 39 "uniform sampler2D ourTexture;\n"
 40 "uniform sampler2D avatarTexture;\n"
 41 "uniform float factor;\n"
 42 "void main()\n"
 43 "{\n"
 44 "if (Img == 1.0f) {\n"
 45 "FragColor = mix(texture(ourTexture, TexCoord),texture(avatarTexture, TexCoord),factor) * vec4(ourColor, 1.0);\n"
 46 "} else {\n"
 47 "FragColor = vec4(ourColor, 1.0);\n"
 48 "}\n"
 49 "}\n\0";
 50 
 51 int main()
 52 {
 53     GLFWwindow * window = configOpenGL();
 54     
 55     ///创建一个顶点着色器
 56     int vertexShader = glCreateShader(GL_VERTEX_SHADER);
 57     
 58     ///附着源码并编译
 59     glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
 60     glCompileShader(vertexShader);
 61     
 62     ///检查编译是否成功
 63     int success;
 64     char infoLog[512];
 65     glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
 66     if (!success)
 67     {
 68         glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
 69         std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
 70     }
 71     
 72     ///创建一个片段着色器
 73     int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
 74     
 75     ///附着源码并编译
 76     glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
 77     glCompileShader(fragmentShader);
 78     
 79     ///检查编译是否成功
 80     glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
 81     if (!success)
 82     {
 83         glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
 84         std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
 85     }
 86     
 87     ///创建着色器程序
 88     int shaderProgram = glCreateProgram();
 89     
 90     ///链接着色器
 91     glAttachShader(shaderProgram, vertexShader);
 92     glAttachShader(shaderProgram, fragmentShader);
 93     glLinkProgram(shaderProgram);
 94     
 95     ///检查链接是否成功
 96     glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
 97     if (!success) {
 98         glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
 99         std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
100     }
101     
102     ///释放着色器
103     glDeleteShader(vertexShader);
104     glDeleteShader(fragmentShader);
105 
106     unsigned int VAO,VBO,EBO;
107     
108     ///配置VAO
109     configVAO(&VAO,&VBO,&EBO);
110     
111     ///设置纹理单元的位置(想要设置着色器程序的值,必先激活着色器程序)
112     glUseProgram(shaderProgram);
113     glUniform1i(glGetUniformLocation(shaderProgram,"ourTexture"),0);
114     glUniform1i(glGetUniformLocation(shaderProgram,"avatarTexture"),1);
115     
116     while (!glfwWindowShouldClose(window))
117     {
118         processInput(window);
119         
120         ///设置清屏颜色
121         glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
122         ///清屏
123         glClear(GL_COLOR_BUFFER_BIT);
124         
125         ///使用指定着色器程序(由于上面已经激活过着色器程序,所以此处不用再次激活)
126 //        glUseProgram(shaderProgram);
127         
128         ///改变
129         float timeValue = glfwGetTime();
130         float factor = sin(timeValue) / 2.0f + 0.5f;
131         glad_glUniform1f(glGetUniformLocation(shaderProgram,"factor"),factor);
132         
133         ///绑定定点数组对象
134         glBindVertexArray(VAO);
135         ///以索引绘制顶点数据
136 //        glDrawArrays(GL_TRIANGLES, 0, 3);
137         glDrawElements(GL_TRIANGLES,30,GL_UNSIGNED_INT,0);
138         
139         ///交换颜色缓冲
140         glfwSwapBuffers(window);
141         ///拉取用户事件
142         glfwPollEvents();
143     }
144     
145     ///释放对象
146     glDeleteVertexArrays(1, &VAO);
147     glDeleteBuffers(1, &VBO);
148     glDeleteBuffers(1, &EBO);
149     
150     finishiRenderLoop();
151     
152     return 0;
153 }
154 
155 GLFWwindow* configOpenGL() {
156     ///初始化glfw
157     glfwInit();
158     
159     ///设置版本号
160     glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
161     glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
162     
163     ///设置核心模式
164     glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
165     
166 #ifdef __APPLE__
167     ///设置对Mac OS X的兼容
168     glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
169 #endif
170     
171     ///创建window
172     GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
173     if (window == NULL) {
174         std::cout << "Failed to create GLFW window" << std::endl;
175         glfwTerminate();
176         return NULL;
177     }
178     ///将window设置成当前上下文
179     glfwMakeContextCurrent(window);
180     ///设置窗口事件更新触发的回调
181     glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
182     
183     ///初始化GLAD
184     if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
185         std::cout << "Failed to initialize GLAD" << std::endl;
186         return NULL;
187     }
188     return window;
189 }
190 
191 void configVAO(unsigned int * VAO,unsigned int * VBO,unsigned int * EBO) {
192     ///顶点数据
193     float vertices[] = {
194         //顶点坐标-2 //颜色-3 //是否绘制图片-1 //纹理坐标-2
195         0.5f, 0.5f,1.0f,1.0f,0.0f,1.0f,1.5f,1.5f,    // 右上角
196         0.5f, -0.5f,0.0f,1.0f,1.0f,1.0f,1.5f,-0.5f,  // 右下角
197         -0.5f, -0.5f,1.0f,0.0f,1.0f,1.0f,-0.5f,-0.5f,  // 左下角
198         -0.5f, 0.5f,1.0f,1.0f,1.0f,1.0f,-0.5f,1.5f,    // 左上角
199         1.0f,1.0f,0.0f,0.0f,1.0f,0.0f,0.0f,0.0f,
200         1.0f,-1.0f,1.0f,0.0f,0.0f,0.0f,0.0f,0.0f,
201         -1.0f,-1.0f,0.0f,1.0f,0.0f,0.0f,0.0f,0.0f,
202         -1.0f,1.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,
203     };
204     
205     ///索引数据
206     unsigned int indices[] = {
207         0,1,3,
208         1,2,3,
209         0,4,5,
210         0,1,5,
211         1,5,6,
212         1,2,6,
213         2,6,7,
214         2,3,7,
215         3,7,4,
216         3,0,4,
217     };
218     
219     ///创建顶点数组对象
220     glGenVertexArrays(1, VAO);
221     
222     ///创建顶点缓冲对象
223     glGenBuffers(1, VBO);
224     ///创建索引缓冲对象
225     glGenBuffers(1, EBO);
226     
227     ///绑定定点数组对象至上下文
228     glBindVertexArray(*VAO);
229     
230     ///绑定定点缓冲对象至上下文
231     glBindBuffer(GL_ARRAY_BUFFER, *VBO);
232     
233     ///把顶点数组复制到顶点缓冲对象中
234     glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
235     glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
236     glEnableVertexAttribArray(0);
237     glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(2 * sizeof(float)));
238     glEnableVertexAttribArray(1);
239     glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(5 * sizeof(float)));
240     glEnableVertexAttribArray(2);
241     glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
242     glEnableVertexAttribArray(3);
243     ///绑定索引缓冲对象至上下文
244     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, *EBO);
245     ///把索引数据复制到索引缓冲对象中
246     glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
247     
248     ///加载图片
249     unsigned int texture,avatar;
250     loadImg("/Users/momo/Desktop/Wicky/Learn\ OpenGL/入门/Demos/6.纹理/OpenGL_Template/container.jpg", &texture,0);
251     loadImg("/Users/momo/Desktop/Wicky/Learn\ OpenGL/入门/Demos/6.纹理/OpenGL_Template/avatar.jpeg", &avatar, 1);
252     
253     ///解除顶点数组对象的绑定
254     glBindVertexArray(0);
255     ///解除顶点缓冲对象的绑定
256     glBindBuffer(GL_ARRAY_BUFFER, 0);
257     ///解除索引缓冲对象的绑定
258     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,0);
259 }
260 
261 void loadImg(const char * path,unsigned int * texture,unsigned int uniteLoc) {
262     ///设置图片加载时上下翻转
263     stbi_set_flip_vertically_on_load(true);
264     
265     ///加载图片
266     int width, height, nrChannels;
267     unsigned char *data = stbi_load(path, &width, &height, &nrChannels, 0);
268     
269     ///生成纹理对象并绑定至上下文中的2D纹理
270     glGenTextures(1, texture);
271     glActiveTexture(GL_TEXTURE0 + uniteLoc);
272     glBindTexture(GL_TEXTURE_2D, *texture);
273     
274     ///设置纹理环绕及过滤模式
275     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
276     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
277     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
278     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
279     
280     ///加载纹理数据并设置多级渐远纹理
281     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
282     glGenerateMipmap(GL_TEXTURE_2D);
283     
284     ///释放图像数据
285     stbi_image_free(data);
286 }
287 
288 void finishiRenderLoop() {
289     ///释放窗口资源
290     glfwTerminate();
291 }
292 
293 ///处理输入
294 void processInput(GLFWwindow *window)
295 {
296     if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
297         glfwSetWindowShouldClose(window, true);
298 }
299 
300 ///窗口事件更新回调
301 void framebuffer_size_callback(GLFWwindow* window, int width, int height)
302 {
303     ///设置视口大小
304     glViewport(0, 0, width, height);
305 }

7. 加载图像

那么,我们先考虑如何加载图片数据。这里我们使用std_image.h进行图像加载。
接下来我们用std_image为我们提供的函数加载图像数据:

 8. 生成纹理

接下来我们基本就是用GL统一的模式去创建对象了:

这里我们单独讲一下glTexImage2D这个函数。

  • 第一个参数指定了纹理目标(Target)。设置为GL_TEXTURE_2D意味着会生成与当前绑定的纹理对象在同一个目标上的纹理(任何绑定到GL_TEXTURE_1D和GL_TEXTURE_3D的纹理不会受到影响)。
  • 第二个参数为纹理指定多级渐远纹理的级别,如果你希望单独手动设置每个多级渐远纹理的级别的话。这里我们填0,也就是基本级别。
  • 第三个参数告诉OpenGL我们希望把纹理储存为何种格式。我们的图像只有RGB值,因此我们也把纹理储存为RGB值。
  • 第四个和第五个参数设置最终的纹理的宽度和高度。我们之前加载图像的时候储存了它们,所以我们使用对应的变量。
  • 下个参数应该总是被设为0(历史遗留的问题)。
  • 第七第八个参数定义了源图的格式和数据类型。我们使用RGB值加载这个图像,并把它们储存为char(byte)数组,我们将会传入对应值。
  • 最后一个参数是真正的图像数据。

有了图像数据,我们还要指定纹理坐标到顶点坐标数据中。同时我们要修改顶点着色器和片段着色器。并设置顶点属性。与前文中绘制三角形时设置的基本相同。这里我们只介绍如何在片段着色器中使用我们的纹理。

9. 纹理单元
GL中,一个纹理的位置值被称为一个纹理单元。而GL中默认的纹理单元是0,且这个纹理单元是GL中默认激活的。所以上述代码中,我们绑定纹理的时候,并没有指定纹理单元,就是使用的默认的0这个单元。所以在片段着色器中声明的采样器,默认也是对应的纹理单元0。所以我们取到的也就是这个默认的纹理单元。

当使用多个纹理时,首先我们要激活对应的纹理单元,然后在纹理单元中绑定纹理。在真正开始渲染之前,即进入渲染循环之前,我们还要告诉片段着色器每一个采样器对应的是哪个纹理单元。

所以我们使用纹理的代码大概是这个样子的:

那么我们在片段着色器中,如果想要进行混合的话应该使用mix函数。

FragColor = mix(texture(ourTexture, TexCoord),texture(avatarTexture, TexCoord),factor) * vec4(ourColor, 1.0);

 

标签:OpenGL,int,纹理,详解,坐标,顶点,GL,我们
From: https://www.cnblogs.com/rmb999/p/17813320.html

相关文章

  • 队列(阻塞队列、非阻塞队列)的详解
    队列的详解什么是队列?用来存储一条条消息(线程)的容器是一个对列。队列是一种特殊的线性表,遵循先入先出、后入后出的基本原则什么是阻塞队列,什么是非阻塞队列?阻塞队列:添加元素时,超过总数则会进行等待(阻塞)。删除元素时,队列为空则会进行等待(阻塞)。非阻塞队列:不管什么情况下都......
  • Oracle创建表语句(Create table)语法详解及示例
     创建表(Createtable)语法详解1. ORACLE常用的字段类型ORACLE常用的字段类型有VARCHAR2(size)可变长度的字符串,必须规定长度CHAR(size)固定长度的字符串,不规定长度默认值为1NUMBER(p,s)数字型p是位数总长度,s是小数的长度,可存负数最长38位.不够位时会四舍五入.......
  • S参数详解
    一介绍目前常见的S参数模型文件有两种格式:Touchstone和Citifile,常用的是Touchstone格式,在各种仿真和测试仪表上得到了充分的支持,所以本文仅仅对Touchstone格式做说明。Touchstone文件,也就是我们经常看到的Snp文件,一般我们从厂家拿到的、从网络分析仪测试得到的和从......
  • Unity DOTS中ECS核心架构详解
    最近DOTS终于发布了正式的版本, 我们来分享一下DOTS中ECS的几个关键概念与结构,方便大家上手学习掌握Unity DOTS开发。 ECS中的World  Unity DOTS ECS架构中所有的Entity都是被放到了World对象里面,每个Entity在World里面都有唯一的Id号。Unity DOTS 可以同时支持很多个......
  • Java去除字符串中空格的方法详解
    1、方法str.trim();str.replace("","");str.replaceAll("","");str.replaceAll("+","");str.replaceAll("\\s*","");\\s*可以匹配空格、制表符、换页符等空白字符的其中任意一个。 2、示例packagetest;publicc......
  • 基础数据类型详解——
    1.字符集和编码字符集(CharacterSet)是一种定义了一组字符、符号和控制符号的集合,这些字符通常用于书写和表示文本信息。字符集可以包括字母、数字、标点符号、特殊符号、控制字符等。电脑如何进行存储文字信息01 <=>1010101010<=>二进制转化成十进制<=>88电脑如何进行......
  • Redis分布式缓存、AOF与RDB详解
    分布式缓存单点Redis的问题1.数据丢失问题Redis是内存存储,服务重启可能会丢失数据2.并发能力问题单节点Redis并发能力虽然不错,但也无法满足如618这样的高并发场景3.故障恢复问题如果Redis宕机,则服务不可用,需要一种自动的故障恢复手段4.存储能力问题Redis基于内存,单节点能存储的数据......
  • Spring Boot 3系列之-启动类详解
    SpringBoot是一个功能强大、灵活且易于使用的框架,它极大地简化了Spring应用程序的开发和部署流程,使得开发人员能够更专注于业务逻辑的实现。在我们的SpringBoot3系列之一(初始化项目)文章中,我们使用了Spring官方网站生成的SpringBoot项目作为示例。在该项目中,我们可以找到一个名......
  • vue 网络图片访问不到,403的解决办法(详解)
    index.html中添加 <metaname="referrer"content="no-referrer"/>原因:防盗链的机制:通过页面的referrer信息,判断访问者来源,是否本站点,然后对图片等请求作出相应no-referrer:1、整个Referer首部包含了当前请求页面的来源页面的地址,即表示当前页面是通过此来源页面里的链接进......
  • Maven详解
    一.前言   以前做过的项目中,没有真正的使用过Maven,只知道其名声很大,其作用是用来管理jar包的。最近一段时间在项目过程中使用Maven,用Maven构建的web项目,其项目结构只停留在了解阶段,没有深入的使用与理解,刚好最近看了一篇关于Maven的详解;就开始深入学习一下Maven的具体应用。......