[{"data":1,"prerenderedAt":3410},["ShallowReactive",2],{"page-\u002Fcpp\u002Fopengel\u002F纹理":3},{"id":4,"title":5,"body":6,"description":3403,"extension":3404,"meta":3405,"navigation":991,"path":3406,"seo":3407,"stem":3408,"__hash__":3409},"content\u002Fcpp\u002Fopengel\u002F纹理.md","纹理",{"type":7,"value":8,"toc":3388},"minimark",[9,18,25,41,52,58,73,91,96,111,114,155,162,166,173,226,251,266,285,296,311,314,325,335,340,347,352,355,360,367,374,389,393,411,430,435,442,457,502,505,519,533,537,548,559,571,586,606,609,612,627,641,650,661,676,679,726,737,740,749,752,869,872,880,919,922,927,942,952,962,1035,1042,1045,1964,1970,1991,1994,2007,2017,2030,2034,2041,2121,2125,2128,2132,2138,2188,2192,2198,2244,2248,2275,2296,2299,2321,2324,2338,2352,2359,2410,2441,2450,2481,2492,2503,2542,2549,2591,2593,3384],[10,11,12,13,17],"p",{},"​\t我们已经了解到，我们",[14,15,16],"strong",{},"可以为每个顶点添加颜色来增加图形的细节","，从而创建出有趣的图像。但是，如果想让图形看起来更真实，我们就必须有足够多的顶点，从而指定足够多的颜色。这将会产生很多额外开销，因为每个模型都会需求更多的顶点，每个顶点又需求一个颜色属性。",[10,19,20,21,24],{},"​\t艺术家和程序员更喜欢使用纹理(Texture)。",[14,22,23],{},"纹理是一个2D图片（甚至也有1D和3D的纹理）","，它可以用来添加物体的细节；你可以想象纹理是一张绘有砖块的纸，无缝折叠贴合到你的3D的房子上，这样你的房子看起来就像有砖墙外表了。因为我们可以在一张图片上插入非常多的细节，这样就可以让物体非常精细而不用指定额外的顶点。",[26,27,28,34],"blockquote",{},[10,29,30],{},[31,32,33],"span",{},"!NOTE",[10,35,36,37,40],{},"除了图像以外，纹理也可以被用来",[14,38,39],{},"储存大量的数据","，这些数据可以发送到着色器上，但是这不是我们现在的主题。",[10,42,43,44,51],{},"下面你会看到之前教程的那个三角形贴上了一张",[45,46,50],"a",{"href":47,"rel":48},"https:\u002F\u002Flearnopengl-cn.github.io\u002Fimg\u002F01\u002F06\u002Fwall.jpg",[49],"nofollow","砖墙","图片。",[10,53,54],{},[55,56],"img",{"alt":55,"src":57},".\u002Fassets\u002Ftextures.png",[10,59,60,61,64,65,68,69,72],{},"​\t为了能够",[14,62,63],{},"把纹理映射(Map)到三角形上","，我们",[14,66,67],{},"需要指定三角形的每个顶点各自对应纹理的哪个部分","。这样每个顶点就会关联着一个纹理坐标(Texture Coordinate)，用来",[14,70,71],{},"标明该从纹理图像的哪个部分采样","（译注：采集片段颜色）。之后在图形的其它片段上进行片段插值(Fragment Interpolation)。",[10,74,75,76,79,80,83,84,87,88],{},"​\t",[14,77,78],{},"纹理坐标在x和y轴上，范围为0到1之间（注意我们使用的是2D纹理图像）","。使用",[14,81,82],{},"纹理坐标获取纹理颜色叫做采样(Sampling)","。纹理坐标起始于(0, 0)，",[14,85,86],{},"也就是纹理图片的左下角","，终止于(1, 1)，即纹理图片的右上角。下面的图片展示了",[14,89,90],{},"我们是如何把纹理坐标映射到三角形上的。",[10,92,93],{},[55,94],{"alt":55,"src":95},".\u002Fassets\u002Ftex_coords.png",[10,97,98,99,102,103,106,107,110],{},"​\t我们",[14,100,101],{},"为三角形指定了3个纹理坐标点","。如上图所示，我们",[14,104,105],{},"希望三角形的左下角对应纹理的左下角","，因此我们**把三角形左下角顶点的纹理坐标设置为(0, 0)；同理右下方的顶点设置为(1, 0)；",[14,108,109],{},"三角形的上顶点对应于图片的","上中位置所以我们把它的纹理坐标设置为(0.5, 1.0)。**我们只要给顶点着色器传递这三个纹理坐标就行了，接下来它们会被传到片段着色器中，它会为每个片段进行纹理坐标的插值。",[10,112,113],{},"纹理坐标看起来就像这样：",[115,116,121],"pre",{"className":117,"code":118,"language":119,"meta":120,"style":120},"language-c++ shiki shiki-themes github-light github-dark","float texCoords[] = {\n    0.0f, 0.0f, \u002F\u002F 左下角\n    1.0f, 0.0f, \u002F\u002F 右下角\n    0.5f, 1.0f  \u002F\u002F 上中\n};\n","c++","",[122,123,124,131,137,143,149],"code",{"__ignoreMap":120},[31,125,128],{"class":126,"line":127},"line",1,[31,129,130],{},"float texCoords[] = {\n",[31,132,134],{"class":126,"line":133},2,[31,135,136],{},"    0.0f, 0.0f, \u002F\u002F 左下角\n",[31,138,140],{"class":126,"line":139},3,[31,141,142],{},"    1.0f, 0.0f, \u002F\u002F 右下角\n",[31,144,146],{"class":126,"line":145},4,[31,147,148],{},"    0.5f, 1.0f  \u002F\u002F 上中\n",[31,150,152],{"class":126,"line":151},5,[31,153,154],{},"};\n",[10,156,157,158,161],{},"对纹理采样的解释非常宽松，它可以采用几种不同的插值方式。所以我们需要自己告诉OpenGL该怎样对纹理",[14,159,160],{},"采样","。",[163,164,165],"h2",{"id":165},"纹理环绕方式",[10,167,168,169,172],{},"​\t纹理坐标的范围通常是从(0, 0)到(1, 1)，",[14,170,171],{},"那如果我们把纹理坐标设置在范围之外会发生什么？OpenGL默认的行为是重复这个纹理图像（我们基本上忽略浮点纹理坐标的整数部分）","，但OpenGL提供了更多的选择：",[174,175,176,190],"table",{},[177,178,179],"thead",{},[180,181,182,187],"tr",{},[183,184,186],"th",{"align":185},"left","环绕方式",[183,188,189],{"align":185},"描述",[191,192,193,202,210,218],"tbody",{},[180,194,195,199],{},[196,197,198],"td",{"align":185},"GL_REPEAT",[196,200,201],{"align":185},"对纹理的默认行为。重复纹理图像。",[180,203,204,207],{},[196,205,206],{"align":185},"GL_MIRRORED_REPEAT",[196,208,209],{"align":185},"和GL_REPEAT一样，但每次重复图片是镜像放置的。",[180,211,212,215],{},[196,213,214],{"align":185},"GL_CLAMP_TO_EDGE",[196,216,217],{"align":185},"纹理坐标会被约束在0到1之间，超出的部分会重复纹理坐标的边缘，产生一种边缘被拉伸的效果。",[180,219,220,223],{},[196,221,222],{"align":185},"GL_CLAMP_TO_BORDER",[196,224,225],{"align":185},"超出的坐标为用户指定的边缘颜色。",[10,227,228,229,232,233,236,237,240,241,232,244,232,247,250],{},"​\t前面提到的每个选项都可以使用glTexParameter*函数对单独的一个坐标轴设置（",[122,230,231],{},"s","、",[122,234,235],{},"t","（如果是使用3D纹理那么还有一个",[122,238,239],{},"r","）它们和",[122,242,243],{},"x",[122,245,246],{},"y",[122,248,249],{},"z","是等价的）：",[115,252,254],{"className":117,"code":253,"language":119,"meta":120,"style":120},"glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);\nglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);\n",[122,255,256,261],{"__ignoreMap":120},[31,257,258],{"class":126,"line":127},[31,259,260],{},"glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);\n",[31,262,263],{"class":126,"line":133},[31,264,265],{},"glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);\n",[10,267,268,269,272,273,276,277,280,281,284],{},"​\t第一个参数指定了纹理目标；",[14,270,271],{},"我们使用的是2D纹理，因此纹理目标是GL_TEXTURE_2D","。第二个参数需要我们指定设置的选项与应用的纹理轴。我们打算配置的是",[122,274,275],{},"WRAP","选项，并且指定",[122,278,279],{},"S","和",[122,282,283],{},"T","轴。最后一个参数需要我们传递一个环绕方式(Wrapping)，在这个例子中OpenGL会给当前激活的纹理设定纹理环绕方式为GL_MIRRORED_REPEAT。",[10,286,287,288,291,292,295],{},"​\t如果我们",[14,289,290],{},"选择GL_CLAMP_TO_BORDER选项，我们还需要指定一个边缘的颜色","。这需要使用glTexParameter函数的",[122,293,294],{},"fv","后缀形式，用GL_TEXTURE_BORDER_COLOR作为它的选项，并且传递一个float数组作为边缘的颜色值：",[115,297,299],{"className":117,"code":298,"language":119,"meta":120,"style":120},"float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };\nglTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);\n",[122,300,301,306],{"__ignoreMap":120},[31,302,303],{"class":126,"line":127},[31,304,305],{},"float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };\n",[31,307,308],{"class":126,"line":133},[31,309,310],{},"glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);\n",[163,312,313],{"id":313},"纹理过滤",[10,315,316,317,320,321,324],{},"​\t纹理坐标",[14,318,319],{},"不依赖于分辨率(Resolution)，它可以是任意浮点值","，所以OpenGL需要知道怎样",[14,322,323],{},"将纹理像素(Texture Pixel，也叫Texel，译注1)映射到纹理坐标","。当你有一个很大的物体但是纹理的分辨率很低的时候这就变得很重要了。你可能已经猜到了，OpenGL也有对于纹理过滤(Texture Filtering)的选项。纹理过滤有很多个选项，但是现在我们只讨论最重要的两种：GL_NEAREST和GL_LINEAR。",[10,326,75,327,330,331,334],{},[14,328,329],{},"GL_NEAREST（也叫邻近过滤，Nearest Neighbor Filtering）是OpenGL默认的纹理过滤方式","。当设置为GL_NEAREST的时候，OpenGL",[14,332,333],{},"会选择中心点最接近纹理坐标的那个像素","。下图中你可以看到四个像素，加号代表纹理坐标**。左上角那个纹理像素的中心距离纹理坐标最近，所以它会被选择为样本颜色：**",[10,336,337],{},[55,338],{"alt":55,"src":339},".\u002Fassets\u002Ffilter_nearest.png",[10,341,342,343,346],{},"​\tGL_LINEAR（也叫线性过滤，(Bi)linear Filtering）它会",[14,344,345],{},"基于纹理坐标附近的纹理像素，计算出一个插值","，近似出这些纹理像素之间的颜色。一个纹理像素的中心距离纹理坐标越近，那么这个纹理像素的颜色对最终的样本颜色的贡献越大。下图中你可以看到返回的颜色是邻近像素的混合色：",[10,348,349],{},[55,350],{"alt":55,"src":351},".\u002Fassets\u002Ffilter_linear.png",[10,353,354],{},"​\t那么这两种纹理过滤方式有怎样的视觉效果呢？让我们看看在一个很大的物体上应用一张低分辨率的纹理会发生什么吧（纹理被放大了，每个纹理像素都能看到）：",[10,356,357],{},[55,358],{"alt":55,"src":359},".\u002Fassets\u002Ftexture_filtering.png",[10,361,362,363,366],{},"​\tGL_NEAREST",[14,364,365],{},"产生了颗粒状的图案，我们能够清晰看到组成纹理的像素","，而GL_LINEAR能够产生更平滑的图案，很难看出单个的纹理像素。GL_LINEAR可以产生更真实的输出，但有些开发者更喜欢8-bit风格，所以他们会用GL_NEAREST选项。",[10,368,369,370,373],{},"​\t当进行",[14,371,372],{},"放大(Magnify)和缩小(Minify)操作的时候可以设置纹理过滤的选项","，比如你可以在纹理被缩小的时候使用邻近过滤，被放大时使用线性过滤。我们需要使用glTexParameter*函数为放大和缩小指定过滤方式。这段代码看起来会和纹理环绕方式的设置很相似：",[115,375,377],{"className":117,"code":376,"language":119,"meta":120,"style":120},"glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);\nglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n",[122,378,379,384],{"__ignoreMap":120},[31,380,381],{"class":126,"line":127},[31,382,383],{},"glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);\n",[31,385,386],{"class":126,"line":133},[31,387,388],{},"glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n",[390,391,392],"h3",{"id":392},"多级渐远纹理",[10,394,395,396,399,400,403,404,407,408],{},"​\t想象一下，假设我们",[14,397,398],{},"有一个包含着上千物体的大房间","，每个物体上都有纹理。",[14,401,402],{},"有些物体会很远，但其纹理会拥有与近处物体同样高的分辨率。由于远处的物体可能只产生很少的片段","，OpenGL从",[14,405,406],{},"高分辨率纹理中为这些片段获取正确的颜色值就很困难","，因为它需要对一个跨过纹理很大部分的片段只拾取一个纹理颜色。",[14,409,410],{},"在小物体上这会产生不真实的感觉，更不用说对它们使用高分辨率纹理浪费内存的问题了。",[10,412,413,414,417,418,421,422,425,426,429],{},"​\tOpenGL使用一种叫做",[14,415,416],{},"多级渐远纹理(Mipmap)的概念来解决这个问题","，它简单来说就是",[14,419,420],{},"一系列的纹理图像，后一个纹理图像是前一个的二分之一","。多级渐远纹理背后的理念很简单：",[14,423,424],{},"距观察者的距离超过一定的阈值，OpenGL会使用不同的多级渐远纹理","，即最适合物体的距离的那个。由于距离远，",[14,427,428],{},"解析度不高也不会被用户注意到。同时，多级渐远纹理另一加分之处是它的性能非常好","。让我们看一下多级渐远纹理是什么样子的：",[10,431,432],{},[55,433],{"alt":55,"src":434},".\u002Fassets\u002Fmipmaps.png",[10,436,437,438,441],{},"​\t手工为每个纹理图像创建一系列多级渐远纹理很麻烦，",[14,439,440],{},"幸好OpenGL有一个glGenerateMipmap函数","，在创建完一个纹理后调用它OpenGL就会承担接下来的所有工作了。后面的教程中你会看到该如何使用它。",[10,443,444,445,448,449,452,453,456],{},"​\t在",[14,446,447],{},"渲染中切换多级渐远纹理级别(Level)时","，OpenGL在",[14,450,451],{},"两个不同级别的多级渐远纹理层之间会产生不真实的生硬边界","。就像普通的纹理过滤一样，",[14,454,455],{},"切换多级渐远纹理级别时你也可以在两个不同多级渐远纹理级别之间使用NEAREST和LINEAR过滤","。为了指定不同多级渐远纹理级别之间的过滤方式，你可以使用下面四个选项中的一个代替原有的过滤方式：",[174,458,459,468],{},[177,460,461],{},[180,462,463,466],{},[183,464,465],{"align":185},"过滤方式",[183,467,189],{"align":185},[191,469,470,478,486,494],{},[180,471,472,475],{},[196,473,474],{"align":185},"GL_NEAREST_MIPMAP_NEAREST",[196,476,477],{"align":185},"使用最邻近的多级渐远纹理来匹配像素大小，并使用邻近插值进行纹理采样",[180,479,480,483],{},[196,481,482],{"align":185},"GL_LINEAR_MIPMAP_NEAREST",[196,484,485],{"align":185},"使用最邻近的多级渐远纹理级别，并使用线性插值进行采样",[180,487,488,491],{},[196,489,490],{"align":185},"GL_NEAREST_MIPMAP_LINEAR",[196,492,493],{"align":185},"在两个最匹配像素大小的多级渐远纹理之间进行线性插值，使用邻近插值进行采样",[180,495,496,499],{},[196,497,498],{"align":185},"GL_LINEAR_MIPMAP_LINEAR",[196,500,501],{"align":185},"在两个邻近的多级渐远纹理之间使用线性插值，并使用线性插值进行采样",[10,503,504],{},"​\t就像纹理过滤一样，我们可以使用glTexParameteri将过滤方式设置为前面四种提到的方法之一：",[115,506,508],{"className":117,"code":507,"language":119,"meta":120,"style":120},"glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);\nglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n",[122,509,510,515],{"__ignoreMap":120},[31,511,512],{"class":126,"line":127},[31,513,514],{},"glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);\n",[31,516,517],{"class":126,"line":133},[31,518,388],{},[10,520,521,522,525,526,529,530,161],{},"​\t一个常见的错误是，",[14,523,524],{},"将放大过滤的选项设置为多级渐远纹理过滤选项之一","。这样没有任何效果，因为",[14,527,528],{},"多级渐远纹理主要是使用在纹理被缩小的情况下的","：纹理放大不会使用多级渐远纹理，",[14,531,532],{},"为放大过滤设置多级渐远纹理的选项会产生一个GL_INVALID_ENUM错误代码",[534,535,536],"h1",{"id":536},"加载与创建纹理",[10,538,539,540,543,544,547],{},"​\t使用纹理之前要做的第一件事是把它们加载到我们的应用中。纹理图像可能被储存为各种各样的格式，每种都有自己的数据结构和排列，所以我们如何才能把这些图像加载到应用中呢？一个解决方案是选一个需要的文件格式，比如",[122,541,542],{},".PNG","，",[14,545,546],{},"然后自己写一个图像加载器，把图像转化为字节序列","。写自己的图像加载器虽然不难，但仍然挺麻烦的，而且如果要支持更多文件格式呢？你就不得不为每种你希望支持的格式写加载器了。",[10,549,550,551,554,555,558],{},"​\t另一个解决方案也许是一种更好的选择，",[14,552,553],{},"使用一个支持多种流行格式的图像加载库来为我们解决这个问题","。比如说我们要用的",[122,556,557],{},"stb_image.h","库。或许这里我应该尝试一下opencv库。。。",[10,560,561,562,567,568,570],{},"​\t下面的教程中，我们会使用一张",[45,563,566],{"href":564,"rel":565},"https:\u002F\u002Flearnopengl-cn.github.io\u002Fimg\u002F01\u002F06\u002Fcontainer.jpg",[49],"木箱","的图片。要使用",[122,569,557],{},"加载图片，我们需要使用它的stbi_load函数：",[115,572,574],{"className":117,"code":573,"language":119,"meta":120,"style":120},"int width, height, nrChannels;\nunsigned char *data = stbi_load(\"container.jpg\", &width, &height, &nrChannels, 0);\n",[122,575,576,581],{"__ignoreMap":120},[31,577,578],{"class":126,"line":127},[31,579,580],{},"int width, height, nrChannels;\n",[31,582,583],{"class":126,"line":133},[31,584,585],{},"unsigned char *data = stbi_load(\"container.jpg\", &width, &height, &nrChannels, 0);\n",[10,587,588,589,592,593,595,596,232,599,280,602,605],{},"​\t这个函数首先接受一个图像文件的位置作为输入。接下来它需要三个",[122,590,591],{},"int","作为它的第二、第三和第四个参数，",[122,594,557],{},"将会用图像的",[14,597,598],{},"宽度",[14,600,601],{},"高度",[14,603,604],{},"颜色通道的个数","填充这三个变量。我们之后生成纹理的时候会用到的图像的宽度和高度的。",[163,607,608],{"id":608},"生成纹理",[10,610,611],{},"​\t和之前生成的OpenGL对象一样，纹理也是使用ID引用的。让我们来创建一个：",[115,613,615],{"className":117,"code":614,"language":119,"meta":120,"style":120},"unsigned int texture;\nglGenTextures(1, &texture);\n",[122,616,617,622],{"__ignoreMap":120},[31,618,619],{"class":126,"line":127},[31,620,621],{},"unsigned int texture;\n",[31,623,624],{"class":126,"line":133},[31,625,626],{},"glGenTextures(1, &texture);\n",[10,628,629,630,633,634,637,638,640],{},"​\tglGenTextures函数",[14,631,632],{},"首先需要输入生成纹理的数量","，然后把它们储存在第二个参数的",[122,635,636],{},"unsigned int","数组中（我们的例子中只是单独的一个",[122,639,636],{},"），就像其他对象一样，我们需要绑定它，让之后任何的纹理指令都可以配置当前绑定的纹理：",[115,642,644],{"className":117,"code":643,"language":119,"meta":120,"style":120},"glBindTexture(GL_TEXTURE_2D, texture);\n",[122,645,646],{"__ignoreMap":120},[31,647,648],{"class":126,"line":127},[31,649,643],{},[10,651,652,653,656,657,660],{},"​\t现在纹理已经绑定了，",[14,654,655],{},"我们可以使用前面载入的图片数据生成一个纹理了","。纹理可以",[14,658,659],{},"通过glTexImage2D来生成","：",[115,662,664],{"className":117,"code":663,"language":119,"meta":120,"style":120},"glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);\nglGenerateMipmap(GL_TEXTURE_2D);\n",[122,665,666,671],{"__ignoreMap":120},[31,667,668],{"class":126,"line":127},[31,669,670],{},"glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);\n",[31,672,673],{"class":126,"line":133},[31,674,675],{},"glGenerateMipmap(GL_TEXTURE_2D);\n",[10,677,678],{},"函数很长，参数也不少，所以我们一个一个地讲解：",[680,681,682,686,693,703,709,716,723],"ul",{},[683,684,685],"li",{},"第一个参数指定了纹理目标(Target)。设置为GL_TEXTURE_2D意味着会生成与当前绑定的纹理对象在同一个目标上的纹理（任何绑定到GL_TEXTURE_1D和GL_TEXTURE_3D的纹理不会受到影响）。",[683,687,688,689,692],{},"第二个参数为纹理",[14,690,691],{},"指定多级渐远纹理的级别","，如果你希望单独手动设置每个多级渐远纹理的级别的话。这里我们填0，也就是基本级别。",[683,694,695,696,699,700,702],{},"第三个参数告诉OpenGL我们希望把纹理储存为何种格式。我们的图像只有",[122,697,698],{},"RGB","值，因此我们也把纹理储存为",[122,701,698],{},"值。",[683,704,705,706,161],{},"第四个和第五个参数设置最终的",[14,707,708],{},"纹理的宽度和高度。我们之前加载图像的时候储存了它们，所以我们使用对应的变量",[683,710,711,712,715],{},"下个参数应该总是被设为",[122,713,714],{},"0","（历史遗留的问题）。",[683,717,718,719,722],{},"第七第八个参数定义了源图的格式和数据类型。我们使用RGB值加载这个图像，并把它们储存为",[122,720,721],{},"char","(byte)数组，我们将会传入对应值。",[683,724,725],{},"最后一个参数是真正的图像数据。",[10,727,728,729,732,733,736],{},"​\t当调用glTexImage2D时，当前",[14,730,731],{},"绑定的纹理对象就会被附加上纹理图像","。然而，目前",[14,734,735],{},"只有基本级别(Base-level)的纹理图像被加载了，如果要使用多级渐远纹理，我们必须手动设置所有不同的图像（不断递增第二个参数）","。或者，直接在生成纹理之后调用glGenerateMipmap。这会为当前绑定的纹理自动生成所有需要的多级渐远纹理。",[10,738,739],{},"​\t生成了纹理和相应的多级渐远纹理后，释放图像的内存是一个很好的习惯。",[115,741,743],{"className":117,"code":742,"language":119,"meta":120,"style":120},"stbi_image_free(data);\n",[122,744,745],{"__ignoreMap":120},[31,746,747],{"class":126,"line":127},[31,748,742],{},[10,750,751],{},"​\t生成一个纹理的过程应该看起来像这样：",[115,753,755],{"className":117,"code":754,"language":119,"meta":120,"style":120},"unsigned int texture;\nglGenTextures(1, &texture);\nglBindTexture(GL_TEXTURE_2D, texture);\n\u002F\u002F 为当前绑定的纹理对象设置环绕、过滤方式\nglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);   \nglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);\nglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);\nglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n\u002F\u002F 加载并生成纹理\nint width, height, nrChannels;\nunsigned char *data = stbi_load(\"container.jpg\", &width, &height, &nrChannels, 0);\nif (data)\n{\n    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);\n    glGenerateMipmap(GL_TEXTURE_2D);\n}\nelse\n{\n    std::cout \u003C\u003C \"Failed to load texture\" \u003C\u003C std::endl;\n}\nstbi_image_free(data);\n",[122,756,757,761,765,769,774,779,785,791,796,802,807,812,818,824,830,836,842,848,853,859,864],{"__ignoreMap":120},[31,758,759],{"class":126,"line":127},[31,760,621],{},[31,762,763],{"class":126,"line":133},[31,764,626],{},[31,766,767],{"class":126,"line":139},[31,768,643],{},[31,770,771],{"class":126,"line":145},[31,772,773],{},"\u002F\u002F 为当前绑定的纹理对象设置环绕、过滤方式\n",[31,775,776],{"class":126,"line":151},[31,777,778],{},"glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);   \n",[31,780,782],{"class":126,"line":781},6,[31,783,784],{},"glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);\n",[31,786,788],{"class":126,"line":787},7,[31,789,790],{},"glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);\n",[31,792,794],{"class":126,"line":793},8,[31,795,388],{},[31,797,799],{"class":126,"line":798},9,[31,800,801],{},"\u002F\u002F 加载并生成纹理\n",[31,803,805],{"class":126,"line":804},10,[31,806,580],{},[31,808,810],{"class":126,"line":809},11,[31,811,585],{},[31,813,815],{"class":126,"line":814},12,[31,816,817],{},"if (data)\n",[31,819,821],{"class":126,"line":820},13,[31,822,823],{},"{\n",[31,825,827],{"class":126,"line":826},14,[31,828,829],{},"    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);\n",[31,831,833],{"class":126,"line":832},15,[31,834,835],{},"    glGenerateMipmap(GL_TEXTURE_2D);\n",[31,837,839],{"class":126,"line":838},16,[31,840,841],{},"}\n",[31,843,845],{"class":126,"line":844},17,[31,846,847],{},"else\n",[31,849,851],{"class":126,"line":850},18,[31,852,823],{},[31,854,856],{"class":126,"line":855},19,[31,857,858],{},"    std::cout \u003C\u003C \"Failed to load texture\" \u003C\u003C std::endl;\n",[31,860,862],{"class":126,"line":861},20,[31,863,841],{},[31,865,867],{"class":126,"line":866},21,[31,868,742],{},[163,870,871],{"id":871},"应用纹理",[10,873,874,875,879],{},"后面的这部分我们会使用glDrawElements绘制[「你好，三角形」](",[45,876,877],{"href":877,"rel":878},"https:\u002F\u002Flearnopengl-cn.github.io\u002F01",[49]," Getting started\u002F04 Hello Triangle\u002F)教程最后一部分的矩形。我们需要告知OpenGL如何采样纹理，所以我们必须使用纹理坐标更新顶点数据：",[115,881,883],{"className":117,"code":882,"language":119,"meta":120,"style":120},"float vertices[] = {\n\u002F\u002F     ---- 位置 ----       ---- 颜色 ----     - 纹理坐标 -\n     0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f,   \u002F\u002F 右上\n     0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f,   \u002F\u002F 右下\n    -0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f,   \u002F\u002F 左下\n    -0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f    \u002F\u002F 左上\n};\n",[122,884,885,890,895,900,905,910,915],{"__ignoreMap":120},[31,886,887],{"class":126,"line":127},[31,888,889],{},"float vertices[] = {\n",[31,891,892],{"class":126,"line":133},[31,893,894],{},"\u002F\u002F     ---- 位置 ----       ---- 颜色 ----     - 纹理坐标 -\n",[31,896,897],{"class":126,"line":139},[31,898,899],{},"     0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f,   \u002F\u002F 右上\n",[31,901,902],{"class":126,"line":145},[31,903,904],{},"     0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f,   \u002F\u002F 右下\n",[31,906,907],{"class":126,"line":151},[31,908,909],{},"    -0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f,   \u002F\u002F 左下\n",[31,911,912],{"class":126,"line":781},[31,913,914],{},"    -0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f    \u002F\u002F 左上\n",[31,916,917],{"class":126,"line":787},[31,918,154],{},[10,920,921],{},"​\t由于我们添加了一个额外的顶点属性，我们必须告诉OpenGL我们新的顶点格式：",[10,923,924],{},[55,925],{"alt":55,"src":926},".\u002Fassets\u002Fvertex_attribute_pointer_interleaved_textures.png",[115,928,930],{"className":117,"code":929,"language":119,"meta":120,"style":120},"glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));\nglEnableVertexAttribArray(2);\n",[122,931,932,937],{"__ignoreMap":120},[31,933,934],{"class":126,"line":127},[31,935,936],{},"glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));\n",[31,938,939],{"class":126,"line":133},[31,940,941],{},"glEnableVertexAttribArray(2);\n",[10,943,944,945,948,949,161],{},"​\t注意，我们",[14,946,947],{},"同样需要调整前面两个顶点属性的步长参数","为",[122,950,951],{},"8 * sizeof(float)",[10,953,954,955,958,959,660],{},"​\t接着我们需要",[14,956,957],{},"调整顶点着色器使其能够接受顶点坐标为一个顶点属性","，并把",[14,960,961],{},"坐标传给片段着色器",[115,963,965],{"className":117,"code":964,"language":119,"meta":120,"style":120},"#version 330 core\nlayout (location = 0) in vec3 aPos;\nlayout (location = 1) in vec3 aColor;\nlayout (location = 2) in vec2 aTexCoord;\n\nout vec3 ourColor;\nout vec2 TexCoord;\n\nvoid main()\n{\n    gl_Position = vec4(aPos, 1.0);\n    ourColor = aColor;\n    TexCoord = aTexCoord;\n}\n",[122,966,967,972,977,982,987,993,998,1003,1007,1012,1016,1021,1026,1031],{"__ignoreMap":120},[31,968,969],{"class":126,"line":127},[31,970,971],{},"#version 330 core\n",[31,973,974],{"class":126,"line":133},[31,975,976],{},"layout (location = 0) in vec3 aPos;\n",[31,978,979],{"class":126,"line":139},[31,980,981],{},"layout (location = 1) in vec3 aColor;\n",[31,983,984],{"class":126,"line":145},[31,985,986],{},"layout (location = 2) in vec2 aTexCoord;\n",[31,988,989],{"class":126,"line":151},[31,990,992],{"emptyLinePlaceholder":991},true,"\n",[31,994,995],{"class":126,"line":781},[31,996,997],{},"out vec3 ourColor;\n",[31,999,1000],{"class":126,"line":787},[31,1001,1002],{},"out vec2 TexCoord;\n",[31,1004,1005],{"class":126,"line":793},[31,1006,992],{"emptyLinePlaceholder":991},[31,1008,1009],{"class":126,"line":798},[31,1010,1011],{},"void main()\n",[31,1013,1014],{"class":126,"line":804},[31,1015,823],{},[31,1017,1018],{"class":126,"line":809},[31,1019,1020],{},"    gl_Position = vec4(aPos, 1.0);\n",[31,1022,1023],{"class":126,"line":814},[31,1024,1025],{},"    ourColor = aColor;\n",[31,1027,1028],{"class":126,"line":820},[31,1029,1030],{},"    TexCoord = aTexCoord;\n",[31,1032,1033],{"class":126,"line":826},[31,1034,841],{},[10,1036,1037,1038,1041],{},"片段着色器应该接下来会把输出变量",[122,1039,1040],{},"TexCoord","作为输入变量。",[10,1043,1044],{},"完整代码：",[115,1046,1048],{"className":117,"code":1047,"language":119,"meta":120,"style":120},"#include \u003Cglad\u002Fglad.h>\n#include \u003CGLFW\u002Fglfw3.h>\n#include \u003Copencv4\u002Fopencv2\u002Fopencv.hpp>\n#include \u003Ciostream>\n#include \u003Ccmath>\n#include \u003Cfilesystem>\n#include \"utils\u002Fshader.h\"\n\n\u002F\u002F 窗口大小改变时的回调函数\nvoid framebuffer_size_callback(GLFWwindow* window, int width, int height) {\n    glViewport(0, 0, width, height);\n}\n\nstd::string getCurrentWorkingDirectory() {\n    namespace fs = std::filesystem;\n    try {\n        \u002F\u002F current_path() 返回当前工作目录\n        return fs::current_path().string();\n    } catch (const fs::filesystem_error& e) {\n        std::cerr \u003C\u003C \"Filesystem Error: \" \u003C\u003C e.what() \u003C\u003C std::endl;\n        return \"\";\n    }\n}\n\n\nint main() {\n\n    auto path = getCurrentWorkingDirectory().append(\"\u002FAssets\u002Fimage.png\");\n    cv::Mat image = cv::imread(path);\n    cv::cvtColor(image, image, cv::COLOR_BGR2RGB);\n\n    \u002F\u002F 转换为字节数组\n    std::vector\u003Cuchar> imageData;\n    if (image.empty()) {\n        std::cerr \u003C\u003C \"Could not read the image: \" \u003C\u003C path \u003C\u003C std::endl;\n        return 1;\n    }\n    cv::imencode(\".png\", image, imageData);\n\n    \n    \u002F\u002F 初始化GLFW\n    glfwInit();\n    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);\n    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);\n    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);\n\n    \u002F\u002F 创建窗口\n    GLFWwindow* window = glfwCreateWindow(800, 600, \"OpenGL Window\", NULL, NULL);\n    if (window == NULL) {\n        std::cout \u003C\u003C \"Failed to create GLFW window\" \u003C\u003C std::endl;\n        glfwTerminate();\n        return -1;\n    }\n    \n    glfwMakeContextCurrent(window);\n    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);\n\n    \u002F\u002F 初始化GLAD\n    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {\n        std::cout \u003C\u003C \"Failed to initialize GLAD\" \u003C\u003C std::endl;\n        return -1;\n    }\n\n    float vertices[] = {\n    \u002F\u002F     ---- 位置 ----       ---- 颜色 ----     - 纹理坐标 -\n        0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f,   \u002F\u002F 右上\n        0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f,   \u002F\u002F 右下\n        -0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f,   \u002F\u002F 左下\n        -0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f    \u002F\u002F 左上\n    };\n    \n    unsigned int indices[] = {\n        0, 1, 3, \u002F\u002F 第一个三角形\n        1, 2, 3  \u002F\u002F 第二个三角形\n    };\n    float texCoords[] = {\n    0.0f, 0.0f, \u002F\u002F 左下角\n    1.0f, 0.0f, \u002F\u002F 右下角\n    0.5f, 1.0f  \u002F\u002F 上中\n    };\n\n    \u002F\u002F 纹理\n    unsigned int texture;\n    glGenTextures(1, &texture);\n    glBindTexture(GL_TEXTURE_2D, texture);\n    \n    \u002F\u002F 设置纹理参数\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n    \n    \u002F\u002F 使用原始图像数据而不是编码后的数据\n    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.cols, image.rows, 0, GL_RGB, GL_UNSIGNED_BYTE, image.data);\n    glGenerateMipmap(GL_TEXTURE_2D);\n\n\n    \u002F\u002F 创建VAO\n    unsigned int VAO;\n    glGenVertexArrays(1, &VAO);\n    glBindVertexArray(VAO);\n\n    \u002F\u002F 创建VBO\n    unsigned int VBO;\n    glGenBuffers(1, &VBO);\n    glBindBuffer(GL_ARRAY_BUFFER, VBO);\n    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);\n\n    \u002F\u002F 创建EBO\n    unsigned int EBO;\n    glGenBuffers(1, &EBO);\n    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);\n    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);\n\n    int nrAttributes;\n    glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);\n    std::cout \u003C\u003C \"Maximum nr of vertex attributes supported: \" \u003C\u003C nrAttributes \u003C\u003C std::endl;\n\n    \u002F\u002F 位置属性\n    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);\n    glEnableVertexAttribArray(0);\n    \u002F\u002F 颜色属性\n    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));\n    glEnableVertexAttribArray(1);\n    \u002F\u002F 纹理属性\n    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));\n    glEnableVertexAttribArray(2);\n\n    \u002F\u002F 构建并编译着色器程序\n    Shader ourShader(\"shader\u002Fcustom.vert\", \"shader\u002Ffragment.vert\");\n\n    \u002F\u002F 渲染循环\n    while (!glfwWindowShouldClose(window)) {\n        \u002F\u002F 处理输入\n        glfwPollEvents();\n        \n        \u002F\u002F 渲染指令\n        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);\n        glClear(GL_COLOR_BUFFER_BIT);\n        \n        \u002F\u002F 使用着色器程序\n        ourShader.use();\n        \n        \u002F\u002F 绑定纹理\n        glBindTexture(GL_TEXTURE_2D, texture);\n        \n        glBindVertexArray(VAO);\n        \n        \u002F\u002F 绘制矩形（使用EBO）\n        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);\n        \n        \u002F\u002F 交换缓冲区和检查IO事件\n        glfwSwapBuffers(window);\n    }\n\n    \u002F\u002F 清理资源\n    glDeleteVertexArrays(1, &VAO);\n    glDeleteBuffers(1, &VBO);\n    glDeleteBuffers(1, &EBO);\n    glDeleteTextures(1, &texture);\n    glfwTerminate();\n    return 0;\n}\n",[122,1049,1050,1055,1060,1065,1070,1075,1080,1085,1089,1094,1099,1104,1108,1112,1117,1122,1127,1132,1137,1142,1147,1152,1158,1163,1168,1173,1179,1184,1190,1196,1202,1207,1213,1219,1225,1231,1237,1242,1248,1253,1259,1265,1271,1277,1283,1289,1294,1300,1306,1312,1318,1324,1330,1335,1340,1346,1352,1357,1363,1369,1375,1380,1385,1390,1396,1402,1408,1414,1420,1426,1432,1437,1443,1449,1455,1460,1466,1471,1476,1481,1486,1491,1497,1503,1509,1515,1520,1526,1532,1538,1544,1550,1555,1561,1567,1572,1577,1582,1588,1594,1600,1606,1611,1617,1623,1629,1635,1641,1646,1652,1658,1664,1670,1676,1681,1687,1693,1699,1704,1710,1716,1722,1728,1734,1740,1746,1752,1758,1763,1769,1775,1780,1786,1792,1798,1804,1810,1816,1822,1828,1833,1839,1845,1850,1856,1862,1867,1873,1878,1884,1890,1895,1901,1907,1912,1917,1923,1929,1935,1941,1947,1953,1959],{"__ignoreMap":120},[31,1051,1052],{"class":126,"line":127},[31,1053,1054],{},"#include \u003Cglad\u002Fglad.h>\n",[31,1056,1057],{"class":126,"line":133},[31,1058,1059],{},"#include \u003CGLFW\u002Fglfw3.h>\n",[31,1061,1062],{"class":126,"line":139},[31,1063,1064],{},"#include \u003Copencv4\u002Fopencv2\u002Fopencv.hpp>\n",[31,1066,1067],{"class":126,"line":145},[31,1068,1069],{},"#include \u003Ciostream>\n",[31,1071,1072],{"class":126,"line":151},[31,1073,1074],{},"#include \u003Ccmath>\n",[31,1076,1077],{"class":126,"line":781},[31,1078,1079],{},"#include \u003Cfilesystem>\n",[31,1081,1082],{"class":126,"line":787},[31,1083,1084],{},"#include \"utils\u002Fshader.h\"\n",[31,1086,1087],{"class":126,"line":793},[31,1088,992],{"emptyLinePlaceholder":991},[31,1090,1091],{"class":126,"line":798},[31,1092,1093],{},"\u002F\u002F 窗口大小改变时的回调函数\n",[31,1095,1096],{"class":126,"line":804},[31,1097,1098],{},"void framebuffer_size_callback(GLFWwindow* window, int width, int height) {\n",[31,1100,1101],{"class":126,"line":809},[31,1102,1103],{},"    glViewport(0, 0, width, height);\n",[31,1105,1106],{"class":126,"line":814},[31,1107,841],{},[31,1109,1110],{"class":126,"line":820},[31,1111,992],{"emptyLinePlaceholder":991},[31,1113,1114],{"class":126,"line":826},[31,1115,1116],{},"std::string getCurrentWorkingDirectory() {\n",[31,1118,1119],{"class":126,"line":832},[31,1120,1121],{},"    namespace fs = std::filesystem;\n",[31,1123,1124],{"class":126,"line":838},[31,1125,1126],{},"    try {\n",[31,1128,1129],{"class":126,"line":844},[31,1130,1131],{},"        \u002F\u002F current_path() 返回当前工作目录\n",[31,1133,1134],{"class":126,"line":850},[31,1135,1136],{},"        return fs::current_path().string();\n",[31,1138,1139],{"class":126,"line":855},[31,1140,1141],{},"    } catch (const fs::filesystem_error& e) {\n",[31,1143,1144],{"class":126,"line":861},[31,1145,1146],{},"        std::cerr \u003C\u003C \"Filesystem Error: \" \u003C\u003C e.what() \u003C\u003C std::endl;\n",[31,1148,1149],{"class":126,"line":866},[31,1150,1151],{},"        return \"\";\n",[31,1153,1155],{"class":126,"line":1154},22,[31,1156,1157],{},"    }\n",[31,1159,1161],{"class":126,"line":1160},23,[31,1162,841],{},[31,1164,1166],{"class":126,"line":1165},24,[31,1167,992],{"emptyLinePlaceholder":991},[31,1169,1171],{"class":126,"line":1170},25,[31,1172,992],{"emptyLinePlaceholder":991},[31,1174,1176],{"class":126,"line":1175},26,[31,1177,1178],{},"int main() {\n",[31,1180,1182],{"class":126,"line":1181},27,[31,1183,992],{"emptyLinePlaceholder":991},[31,1185,1187],{"class":126,"line":1186},28,[31,1188,1189],{},"    auto path = getCurrentWorkingDirectory().append(\"\u002FAssets\u002Fimage.png\");\n",[31,1191,1193],{"class":126,"line":1192},29,[31,1194,1195],{},"    cv::Mat image = cv::imread(path);\n",[31,1197,1199],{"class":126,"line":1198},30,[31,1200,1201],{},"    cv::cvtColor(image, image, cv::COLOR_BGR2RGB);\n",[31,1203,1205],{"class":126,"line":1204},31,[31,1206,992],{"emptyLinePlaceholder":991},[31,1208,1210],{"class":126,"line":1209},32,[31,1211,1212],{},"    \u002F\u002F 转换为字节数组\n",[31,1214,1216],{"class":126,"line":1215},33,[31,1217,1218],{},"    std::vector\u003Cuchar> imageData;\n",[31,1220,1222],{"class":126,"line":1221},34,[31,1223,1224],{},"    if (image.empty()) {\n",[31,1226,1228],{"class":126,"line":1227},35,[31,1229,1230],{},"        std::cerr \u003C\u003C \"Could not read the image: \" \u003C\u003C path \u003C\u003C std::endl;\n",[31,1232,1234],{"class":126,"line":1233},36,[31,1235,1236],{},"        return 1;\n",[31,1238,1240],{"class":126,"line":1239},37,[31,1241,1157],{},[31,1243,1245],{"class":126,"line":1244},38,[31,1246,1247],{},"    cv::imencode(\".png\", image, imageData);\n",[31,1249,1251],{"class":126,"line":1250},39,[31,1252,992],{"emptyLinePlaceholder":991},[31,1254,1256],{"class":126,"line":1255},40,[31,1257,1258],{},"    \n",[31,1260,1262],{"class":126,"line":1261},41,[31,1263,1264],{},"    \u002F\u002F 初始化GLFW\n",[31,1266,1268],{"class":126,"line":1267},42,[31,1269,1270],{},"    glfwInit();\n",[31,1272,1274],{"class":126,"line":1273},43,[31,1275,1276],{},"    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);\n",[31,1278,1280],{"class":126,"line":1279},44,[31,1281,1282],{},"    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);\n",[31,1284,1286],{"class":126,"line":1285},45,[31,1287,1288],{},"    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);\n",[31,1290,1292],{"class":126,"line":1291},46,[31,1293,992],{"emptyLinePlaceholder":991},[31,1295,1297],{"class":126,"line":1296},47,[31,1298,1299],{},"    \u002F\u002F 创建窗口\n",[31,1301,1303],{"class":126,"line":1302},48,[31,1304,1305],{},"    GLFWwindow* window = glfwCreateWindow(800, 600, \"OpenGL Window\", NULL, NULL);\n",[31,1307,1309],{"class":126,"line":1308},49,[31,1310,1311],{},"    if (window == NULL) {\n",[31,1313,1315],{"class":126,"line":1314},50,[31,1316,1317],{},"        std::cout \u003C\u003C \"Failed to create GLFW window\" \u003C\u003C std::endl;\n",[31,1319,1321],{"class":126,"line":1320},51,[31,1322,1323],{},"        glfwTerminate();\n",[31,1325,1327],{"class":126,"line":1326},52,[31,1328,1329],{},"        return -1;\n",[31,1331,1333],{"class":126,"line":1332},53,[31,1334,1157],{},[31,1336,1338],{"class":126,"line":1337},54,[31,1339,1258],{},[31,1341,1343],{"class":126,"line":1342},55,[31,1344,1345],{},"    glfwMakeContextCurrent(window);\n",[31,1347,1349],{"class":126,"line":1348},56,[31,1350,1351],{},"    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);\n",[31,1353,1355],{"class":126,"line":1354},57,[31,1356,992],{"emptyLinePlaceholder":991},[31,1358,1360],{"class":126,"line":1359},58,[31,1361,1362],{},"    \u002F\u002F 初始化GLAD\n",[31,1364,1366],{"class":126,"line":1365},59,[31,1367,1368],{},"    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {\n",[31,1370,1372],{"class":126,"line":1371},60,[31,1373,1374],{},"        std::cout \u003C\u003C \"Failed to initialize GLAD\" \u003C\u003C std::endl;\n",[31,1376,1378],{"class":126,"line":1377},61,[31,1379,1329],{},[31,1381,1383],{"class":126,"line":1382},62,[31,1384,1157],{},[31,1386,1388],{"class":126,"line":1387},63,[31,1389,992],{"emptyLinePlaceholder":991},[31,1391,1393],{"class":126,"line":1392},64,[31,1394,1395],{},"    float vertices[] = {\n",[31,1397,1399],{"class":126,"line":1398},65,[31,1400,1401],{},"    \u002F\u002F     ---- 位置 ----       ---- 颜色 ----     - 纹理坐标 -\n",[31,1403,1405],{"class":126,"line":1404},66,[31,1406,1407],{},"        0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f,   \u002F\u002F 右上\n",[31,1409,1411],{"class":126,"line":1410},67,[31,1412,1413],{},"        0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f,   \u002F\u002F 右下\n",[31,1415,1417],{"class":126,"line":1416},68,[31,1418,1419],{},"        -0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f,   \u002F\u002F 左下\n",[31,1421,1423],{"class":126,"line":1422},69,[31,1424,1425],{},"        -0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f    \u002F\u002F 左上\n",[31,1427,1429],{"class":126,"line":1428},70,[31,1430,1431],{},"    };\n",[31,1433,1435],{"class":126,"line":1434},71,[31,1436,1258],{},[31,1438,1440],{"class":126,"line":1439},72,[31,1441,1442],{},"    unsigned int indices[] = {\n",[31,1444,1446],{"class":126,"line":1445},73,[31,1447,1448],{},"        0, 1, 3, \u002F\u002F 第一个三角形\n",[31,1450,1452],{"class":126,"line":1451},74,[31,1453,1454],{},"        1, 2, 3  \u002F\u002F 第二个三角形\n",[31,1456,1458],{"class":126,"line":1457},75,[31,1459,1431],{},[31,1461,1463],{"class":126,"line":1462},76,[31,1464,1465],{},"    float texCoords[] = {\n",[31,1467,1469],{"class":126,"line":1468},77,[31,1470,136],{},[31,1472,1474],{"class":126,"line":1473},78,[31,1475,142],{},[31,1477,1479],{"class":126,"line":1478},79,[31,1480,148],{},[31,1482,1484],{"class":126,"line":1483},80,[31,1485,1431],{},[31,1487,1489],{"class":126,"line":1488},81,[31,1490,992],{"emptyLinePlaceholder":991},[31,1492,1494],{"class":126,"line":1493},82,[31,1495,1496],{},"    \u002F\u002F 纹理\n",[31,1498,1500],{"class":126,"line":1499},83,[31,1501,1502],{},"    unsigned int texture;\n",[31,1504,1506],{"class":126,"line":1505},84,[31,1507,1508],{},"    glGenTextures(1, &texture);\n",[31,1510,1512],{"class":126,"line":1511},85,[31,1513,1514],{},"    glBindTexture(GL_TEXTURE_2D, texture);\n",[31,1516,1518],{"class":126,"line":1517},86,[31,1519,1258],{},[31,1521,1523],{"class":126,"line":1522},87,[31,1524,1525],{},"    \u002F\u002F 设置纹理参数\n",[31,1527,1529],{"class":126,"line":1528},88,[31,1530,1531],{},"    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);\n",[31,1533,1535],{"class":126,"line":1534},89,[31,1536,1537],{},"    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);\n",[31,1539,1541],{"class":126,"line":1540},90,[31,1542,1543],{},"    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);\n",[31,1545,1547],{"class":126,"line":1546},91,[31,1548,1549],{},"    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n",[31,1551,1553],{"class":126,"line":1552},92,[31,1554,1258],{},[31,1556,1558],{"class":126,"line":1557},93,[31,1559,1560],{},"    \u002F\u002F 使用原始图像数据而不是编码后的数据\n",[31,1562,1564],{"class":126,"line":1563},94,[31,1565,1566],{},"    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.cols, image.rows, 0, GL_RGB, GL_UNSIGNED_BYTE, image.data);\n",[31,1568,1570],{"class":126,"line":1569},95,[31,1571,835],{},[31,1573,1575],{"class":126,"line":1574},96,[31,1576,992],{"emptyLinePlaceholder":991},[31,1578,1580],{"class":126,"line":1579},97,[31,1581,992],{"emptyLinePlaceholder":991},[31,1583,1585],{"class":126,"line":1584},98,[31,1586,1587],{},"    \u002F\u002F 创建VAO\n",[31,1589,1591],{"class":126,"line":1590},99,[31,1592,1593],{},"    unsigned int VAO;\n",[31,1595,1597],{"class":126,"line":1596},100,[31,1598,1599],{},"    glGenVertexArrays(1, &VAO);\n",[31,1601,1603],{"class":126,"line":1602},101,[31,1604,1605],{},"    glBindVertexArray(VAO);\n",[31,1607,1609],{"class":126,"line":1608},102,[31,1610,992],{"emptyLinePlaceholder":991},[31,1612,1614],{"class":126,"line":1613},103,[31,1615,1616],{},"    \u002F\u002F 创建VBO\n",[31,1618,1620],{"class":126,"line":1619},104,[31,1621,1622],{},"    unsigned int VBO;\n",[31,1624,1626],{"class":126,"line":1625},105,[31,1627,1628],{},"    glGenBuffers(1, &VBO);\n",[31,1630,1632],{"class":126,"line":1631},106,[31,1633,1634],{},"    glBindBuffer(GL_ARRAY_BUFFER, VBO);\n",[31,1636,1638],{"class":126,"line":1637},107,[31,1639,1640],{},"    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);\n",[31,1642,1644],{"class":126,"line":1643},108,[31,1645,992],{"emptyLinePlaceholder":991},[31,1647,1649],{"class":126,"line":1648},109,[31,1650,1651],{},"    \u002F\u002F 创建EBO\n",[31,1653,1655],{"class":126,"line":1654},110,[31,1656,1657],{},"    unsigned int EBO;\n",[31,1659,1661],{"class":126,"line":1660},111,[31,1662,1663],{},"    glGenBuffers(1, &EBO);\n",[31,1665,1667],{"class":126,"line":1666},112,[31,1668,1669],{},"    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);\n",[31,1671,1673],{"class":126,"line":1672},113,[31,1674,1675],{},"    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);\n",[31,1677,1679],{"class":126,"line":1678},114,[31,1680,992],{"emptyLinePlaceholder":991},[31,1682,1684],{"class":126,"line":1683},115,[31,1685,1686],{},"    int nrAttributes;\n",[31,1688,1690],{"class":126,"line":1689},116,[31,1691,1692],{},"    glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);\n",[31,1694,1696],{"class":126,"line":1695},117,[31,1697,1698],{},"    std::cout \u003C\u003C \"Maximum nr of vertex attributes supported: \" \u003C\u003C nrAttributes \u003C\u003C std::endl;\n",[31,1700,1702],{"class":126,"line":1701},118,[31,1703,992],{"emptyLinePlaceholder":991},[31,1705,1707],{"class":126,"line":1706},119,[31,1708,1709],{},"    \u002F\u002F 位置属性\n",[31,1711,1713],{"class":126,"line":1712},120,[31,1714,1715],{},"    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);\n",[31,1717,1719],{"class":126,"line":1718},121,[31,1720,1721],{},"    glEnableVertexAttribArray(0);\n",[31,1723,1725],{"class":126,"line":1724},122,[31,1726,1727],{},"    \u002F\u002F 颜色属性\n",[31,1729,1731],{"class":126,"line":1730},123,[31,1732,1733],{},"    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));\n",[31,1735,1737],{"class":126,"line":1736},124,[31,1738,1739],{},"    glEnableVertexAttribArray(1);\n",[31,1741,1743],{"class":126,"line":1742},125,[31,1744,1745],{},"    \u002F\u002F 纹理属性\n",[31,1747,1749],{"class":126,"line":1748},126,[31,1750,1751],{},"    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));\n",[31,1753,1755],{"class":126,"line":1754},127,[31,1756,1757],{},"    glEnableVertexAttribArray(2);\n",[31,1759,1761],{"class":126,"line":1760},128,[31,1762,992],{"emptyLinePlaceholder":991},[31,1764,1766],{"class":126,"line":1765},129,[31,1767,1768],{},"    \u002F\u002F 构建并编译着色器程序\n",[31,1770,1772],{"class":126,"line":1771},130,[31,1773,1774],{},"    Shader ourShader(\"shader\u002Fcustom.vert\", \"shader\u002Ffragment.vert\");\n",[31,1776,1778],{"class":126,"line":1777},131,[31,1779,992],{"emptyLinePlaceholder":991},[31,1781,1783],{"class":126,"line":1782},132,[31,1784,1785],{},"    \u002F\u002F 渲染循环\n",[31,1787,1789],{"class":126,"line":1788},133,[31,1790,1791],{},"    while (!glfwWindowShouldClose(window)) {\n",[31,1793,1795],{"class":126,"line":1794},134,[31,1796,1797],{},"        \u002F\u002F 处理输入\n",[31,1799,1801],{"class":126,"line":1800},135,[31,1802,1803],{},"        glfwPollEvents();\n",[31,1805,1807],{"class":126,"line":1806},136,[31,1808,1809],{},"        \n",[31,1811,1813],{"class":126,"line":1812},137,[31,1814,1815],{},"        \u002F\u002F 渲染指令\n",[31,1817,1819],{"class":126,"line":1818},138,[31,1820,1821],{},"        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);\n",[31,1823,1825],{"class":126,"line":1824},139,[31,1826,1827],{},"        glClear(GL_COLOR_BUFFER_BIT);\n",[31,1829,1831],{"class":126,"line":1830},140,[31,1832,1809],{},[31,1834,1836],{"class":126,"line":1835},141,[31,1837,1838],{},"        \u002F\u002F 使用着色器程序\n",[31,1840,1842],{"class":126,"line":1841},142,[31,1843,1844],{},"        ourShader.use();\n",[31,1846,1848],{"class":126,"line":1847},143,[31,1849,1809],{},[31,1851,1853],{"class":126,"line":1852},144,[31,1854,1855],{},"        \u002F\u002F 绑定纹理\n",[31,1857,1859],{"class":126,"line":1858},145,[31,1860,1861],{},"        glBindTexture(GL_TEXTURE_2D, texture);\n",[31,1863,1865],{"class":126,"line":1864},146,[31,1866,1809],{},[31,1868,1870],{"class":126,"line":1869},147,[31,1871,1872],{},"        glBindVertexArray(VAO);\n",[31,1874,1876],{"class":126,"line":1875},148,[31,1877,1809],{},[31,1879,1881],{"class":126,"line":1880},149,[31,1882,1883],{},"        \u002F\u002F 绘制矩形（使用EBO）\n",[31,1885,1887],{"class":126,"line":1886},150,[31,1888,1889],{},"        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);\n",[31,1891,1893],{"class":126,"line":1892},151,[31,1894,1809],{},[31,1896,1898],{"class":126,"line":1897},152,[31,1899,1900],{},"        \u002F\u002F 交换缓冲区和检查IO事件\n",[31,1902,1904],{"class":126,"line":1903},153,[31,1905,1906],{},"        glfwSwapBuffers(window);\n",[31,1908,1910],{"class":126,"line":1909},154,[31,1911,1157],{},[31,1913,1915],{"class":126,"line":1914},155,[31,1916,992],{"emptyLinePlaceholder":991},[31,1918,1920],{"class":126,"line":1919},156,[31,1921,1922],{},"    \u002F\u002F 清理资源\n",[31,1924,1926],{"class":126,"line":1925},157,[31,1927,1928],{},"    glDeleteVertexArrays(1, &VAO);\n",[31,1930,1932],{"class":126,"line":1931},158,[31,1933,1934],{},"    glDeleteBuffers(1, &VBO);\n",[31,1936,1938],{"class":126,"line":1937},159,[31,1939,1940],{},"    glDeleteBuffers(1, &EBO);\n",[31,1942,1944],{"class":126,"line":1943},160,[31,1945,1946],{},"    glDeleteTextures(1, &texture);\n",[31,1948,1950],{"class":126,"line":1949},161,[31,1951,1952],{},"    glfwTerminate();\n",[31,1954,1956],{"class":126,"line":1955},162,[31,1957,1958],{},"    return 0;\n",[31,1960,1962],{"class":126,"line":1961},163,[31,1963,841],{},[10,1965,75,1966,1969],{},[14,1967,1968],{},"纹理采样参数 (Texture Sampling Parameters)"," 的标准做法，它定义了当 GPU 渲染时，应该如何处理和过滤纹理图像。",[115,1971,1973],{"className":117,"code":1972,"language":119,"meta":120,"style":120},"    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n",[122,1974,1975,1979,1983,1987],{"__ignoreMap":120},[31,1976,1977],{"class":126,"line":127},[31,1978,1531],{},[31,1980,1981],{"class":126,"line":133},[31,1982,1537],{},[31,1984,1985],{"class":126,"line":139},[31,1986,1543],{},[31,1988,1989],{"class":126,"line":145},[31,1990,1549],{},[10,1992,1993],{},"​\t\t\tglTexParameteri(target, pname, param);",[10,1995,1996,1999,2000,2003,2004,161],{},[122,1997,1998],{},"target",": 总是 ",[122,2001,2002],{},"GL_TEXTURE_2D","，因为你正在",[14,2005,2006],{},"配置一个二维纹理",[10,2008,2009,2012,2013,2016],{},[122,2010,2011],{},"pname",": 你要设置的",[14,2014,2015],{},"参数的名称","（如环绕方式或过滤方式）。",[10,2018,2019,2022,2023,2025,2026,2029],{},[122,2020,2021],{},"param",": 该参数的值（如 ",[122,2024,198],{}," 或 ",[122,2027,2028],{},"GL_LINEAR","）。",[163,2031,2033],{"id":2032},"纹理环绕模式-wrapping","纹理环绕模式 (Wrapping)",[10,2035,2036,2037,2040],{},"​\t环绕模式定义了当纹理坐标（通常记为 s 和 t，对应 x 和 y 轴）超出 ",[31,2038,2039],{},"0,1"," 范围时，OpenGL 应该如何处理。",[174,2042,2043,2064],{},[177,2044,2045],{},[180,2046,2047,2050,2056,2061],{},[183,2048,2049],{},"代码",[183,2051,2052,2053,2055],{},"参数名称 (",[122,2054,2011],{},")",[183,2057,2058,2059,2055],{},"参数值 (",[122,2060,2021],{},[183,2062,2063],{},"含义",[191,2065,2066,2095],{},[180,2067,2068,2073,2078,2082],{},[196,2069,2070],{},[122,2071,2072],{},"glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);",[196,2074,2075],{},[122,2076,2077],{},"GL_TEXTURE_WRAP_S",[196,2079,2080],{},[122,2081,198],{},[196,2083,2084,2087,2088,2090,2091,2094],{},[14,2085,2086],{},"S 轴环绕："," 当 s 坐标超出 ",[31,2089,2039],{}," 范围时，纹理会",[14,2092,2093],{},"重复","平铺。",[180,2096,2097,2102,2107,2111],{},[196,2098,2099],{},[122,2100,2101],{},"glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);",[196,2103,2104],{},[122,2105,2106],{},"GL_TEXTURE_WRAP_T",[196,2108,2109],{},[122,2110,198],{},[196,2112,2113,2116,2117,2090,2119,2094],{},[14,2114,2115],{},"T 轴环绕："," 当 t 坐标超出 ",[31,2118,2039],{},[14,2120,2093],{},[163,2122,2124],{"id":2123},"_2-纹理过滤模式-filtering","2. 纹理过滤模式 (Filtering)",[10,2126,2127],{},"​\t过滤模式定义了当纹理被放大或缩小时，GPU 如何计算最终的像素颜色。",[390,2129,2131],{"id":2130},"a-缩小过滤-minification","A. 缩小过滤 (Minification)",[10,2133,2134,2135,2029],{},"​\t当纹理映射到一个比自身小的区域时（即纹理被",[14,2136,2137],{},"缩小",[174,2139,2140,2156],{},[177,2141,2142],{},[180,2143,2144,2146,2150,2154],{},[183,2145,2049],{},[183,2147,2052,2148,2055],{},[122,2149,2011],{},[183,2151,2058,2152,2055],{},[122,2153,2021],{},[183,2155,2063],{},[191,2157,2158],{},[180,2159,2160,2165,2170,2174],{},[196,2161,2162],{},[122,2163,2164],{},"glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);",[196,2166,2167],{},[122,2168,2169],{},"GL_TEXTURE_MIN_FILTER",[196,2171,2172],{},[122,2173,2028],{},[196,2175,2176,2179,2180,2183,2184,2187],{},[14,2177,2178],{},"缩小过滤："," 使用 ",[14,2181,2182],{},"线性过滤","。它会获取最接近纹理点的 4 个像素的颜色，然后进行",[14,2185,2186],{},"混合加权平均","，以获得更平滑的缩小结果。",[390,2189,2191],{"id":2190},"b-放大过滤-magnification","B. 放大过滤 (Magnification)",[10,2193,2194,2195,2029],{},"当纹理映射到一个比自身大的区域时（即纹理被",[14,2196,2197],{},"放大",[174,2199,2200,2216],{},[177,2201,2202],{},[180,2203,2204,2206,2210,2214],{},[183,2205,2049],{},[183,2207,2052,2208,2055],{},[122,2209,2011],{},[183,2211,2058,2212,2055],{},[122,2213,2021],{},[183,2215,2063],{},[191,2217,2218],{},[180,2219,2220,2225,2230,2234],{},[196,2221,2222],{},[122,2223,2224],{},"glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);",[196,2226,2227],{},[122,2228,2229],{},"GL_TEXTURE_MAG_FILTER",[196,2231,2232],{},[122,2233,2028],{},[196,2235,2236,2179,2239,2183,2241,2243],{},[14,2237,2238],{},"放大过滤：",[14,2240,2182],{},[14,2242,2186],{},"，以获得更平滑的放大结果。",[390,2245,2247],{"id":2246},"常见过滤模式","常见过滤模式：",[680,2249,2250,2267],{},[683,2251,2252,2258,2259,2262,2263,2266],{},[14,2253,2254,2257],{},[122,2255,2256],{},"GL_NEAREST"," (临近过滤):"," 简单地选取最近的那个纹素（像素），速度快，但图像在放大时会呈现明显的",[14,2260,2261],{},"锯齿状","或",[14,2264,2265],{},"马赛克","效果。",[683,2268,2269,2274],{},[14,2270,2271,2273],{},[122,2272,2028],{}," (线性过滤):"," 选取最接近的 4 个纹素并进行平滑插值，速度略慢，但图像更平滑、自然。",[10,2276,2277,2280,2281,2283,2284,2287,2288,2290,2291,2295],{},[14,2278,2279],{},"注意："," 对于缩小过滤 (",[122,2282,2169],{},")，你还可以使用 ",[14,2285,2286],{},"Mipmap"," 相关的选项（如 ",[122,2289,498],{},"），这能显著改善物体在远处时的渲染质量和消除锯齿。在你的代码中，",[14,2292,2293],{},[122,2294,2028],{}," 意味着你没有使用 Mipmap。",[163,2297,2298],{"id":2298},"纹理单元",[10,2300,2301,2302,2305,2306,161,2309,2312,2313,2316,2317,2320],{},"​\t你可能会奇怪为什么",[122,2303,2304],{},"sampler2D","变量是个uniform，",[14,2307,2308],{},"我们却不用glUniform给它赋值",[14,2310,2311],{},"使用glUniform1i，我们可以给纹理采样器分配一个位置值","，这样的话",[14,2314,2315],{},"我们能够在一个片段着色器中设置多个纹理","。一个纹理的位置值通常称为一个纹理单元(Texture Unit)。一个",[14,2318,2319],{},"纹理的默认纹理单元是0","，它是默认的激活纹理单元，所以教程前面部分我们没有分配一个位置值。",[10,2322,2323],{},"​\t纹理单元的主要目的是让我们在着色器中可以使用多于一个的纹理。通过把纹理单元赋值给采样器，我们可以一次绑定多个纹理，只要我们首先激活对应的纹理单元。就像glBindTexture一样，我们可以使用glActiveTexture激活纹理单元，传入我们需要使用的纹理单元：",[115,2325,2327],{"className":117,"code":2326,"language":119,"meta":120,"style":120},"glActiveTexture(GL_TEXTURE0); \u002F\u002F 在绑定纹理之前先激活纹理单元\nglBindTexture(GL_TEXTURE_2D, texture);\n",[122,2328,2329,2334],{"__ignoreMap":120},[31,2330,2331],{"class":126,"line":127},[31,2332,2333],{},"glActiveTexture(GL_TEXTURE0); \u002F\u002F 在绑定纹理之前先激活纹理单元\n",[31,2335,2336],{"class":126,"line":133},[31,2337,643],{},[10,2339,2340,2341,2344,2345,161],{},"​\t激活纹理单元之后，",[14,2342,2343],{},"接下来的glBindTexture函数调用会绑定这个纹理到当前激活的纹理单元","，纹理单元GL_TEXTURE0默认总是被激活，所以我们在前面的例子里",[14,2346,2347,2348,2351],{},"当我们使用",[122,2349,2350],{},"glBindTexture","的时候，无需激活任何纹理单元",[10,2353,2354,2355,2358],{},"​\t我们仍然需要",[14,2356,2357],{},"编辑片段着色器来接收另一个采样器","。这应该相对来说非常直接了：",[115,2360,2362],{"className":117,"code":2361,"language":119,"meta":120,"style":120},"#version 330 core\nout vec4 FragColor;\nin vec3 ourColor;\nin vec2 TexCoord;\nuniform sampler2D ourTexture;\nuniform sampler2D ourTexture2;\nvoid main()\n{\n    FragColor = mix(texture(ourTexture, TexCoord), texture(ourTexture2, TexCoord), 0.5) * vec4(ourColor, 1.0);\n}\n",[122,2363,2364,2368,2373,2378,2383,2388,2393,2397,2401,2406],{"__ignoreMap":120},[31,2365,2366],{"class":126,"line":127},[31,2367,971],{},[31,2369,2370],{"class":126,"line":133},[31,2371,2372],{},"out vec4 FragColor;\n",[31,2374,2375],{"class":126,"line":139},[31,2376,2377],{},"in vec3 ourColor;\n",[31,2379,2380],{"class":126,"line":145},[31,2381,2382],{},"in vec2 TexCoord;\n",[31,2384,2385],{"class":126,"line":151},[31,2386,2387],{},"uniform sampler2D ourTexture;\n",[31,2389,2390],{"class":126,"line":781},[31,2391,2392],{},"uniform sampler2D ourTexture2;\n",[31,2394,2395],{"class":126,"line":787},[31,2396,1011],{},[31,2398,2399],{"class":126,"line":793},[31,2400,823],{},[31,2402,2403],{"class":126,"line":798},[31,2404,2405],{},"    FragColor = mix(texture(ourTexture, TexCoord), texture(ourTexture2, TexCoord), 0.5) * vec4(ourColor, 1.0);\n",[31,2407,2408],{"class":126,"line":804},[31,2409,841],{},[10,2411,2412,2413,2416,2417,2420,2421,2424,2425,2428,2429,2432,2433,2436,2437,2440],{},"​\t最终输出颜色",[14,2414,2415],{},"现在是两个纹理的结合","。GLSL内建的",[14,2418,2419],{},"mix函数需要接受两个值作为参数","，并对它们根据第三个参数进行线性插值。如果第三个值是",[122,2422,2423],{},"0.0","，它会返回第一个输入；如果是",[122,2426,2427],{},"1.0","，会返回第二个输入值。",[122,2430,2431],{},"0.2","会返回",[122,2434,2435],{},"80%","的第一个输入颜色和",[122,2438,2439],{},"20%","的第二个输入颜色，即返回两个纹理的混合色。",[10,2442,2443,2444,2449],{},"​\t我们现在需要载入并创建另一个纹理；你应该对这些步骤很熟悉了。记得创建另一个纹理对象，载入图片，使用glTexImage2D生成最终纹理。对于第二个纹理我们使用一张",[45,2445,2448],{"href":2446,"rel":2447},"https:\u002F\u002Flearnopengl-cn.github.io\u002Fimg\u002F01\u002F06\u002Fawesomeface.png",[49],"你学习OpenGL时的面部表情","图片：",[115,2451,2453],{"className":117,"code":2452,"language":119,"meta":120,"style":120},"unsigned char *data = stbi_load(\"awesomeface.png\", &width, &height, &nrChannels, 0);\nif (data)\n{\n    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);\n    glGenerateMipmap(GL_TEXTURE_2D);\n}\n",[122,2454,2455,2460,2464,2468,2473,2477],{"__ignoreMap":120},[31,2456,2457],{"class":126,"line":127},[31,2458,2459],{},"unsigned char *data = stbi_load(\"awesomeface.png\", &width, &height, &nrChannels, 0);\n",[31,2461,2462],{"class":126,"line":133},[31,2463,817],{},[31,2465,2466],{"class":126,"line":139},[31,2467,823],{},[31,2469,2470],{"class":126,"line":145},[31,2471,2472],{},"    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);\n",[31,2474,2475],{"class":126,"line":151},[31,2476,835],{},[31,2478,2479],{"class":126,"line":781},[31,2480,841],{},[10,2482,2483,2484,2487,2488,2491],{},"​\t注意，我们现在要读取一张包含alpha（透明度）通道的",[122,2485,2486],{},".png","图片，这意味着",[14,2489,2490],{},"我们现在需要使用GL_RGBA参数","，指定该图片数据包含了alpha通道；否则OpenGL将无法正确解析图片数据。",[10,2493,2494,2495,2498,2499,2502],{},"​\t为了",[14,2496,2497],{},"使用第二个纹理（以及第一个）","，我们必须改变一点渲染流程，",[14,2500,2501],{},"先绑定两个纹理到对应的纹理单元","，然后定义哪个uniform采样器对应哪个纹理单元：",[115,2504,2506],{"className":117,"code":2505,"language":119,"meta":120,"style":120},"glActiveTexture(GL_TEXTURE0);\nglBindTexture(GL_TEXTURE_2D, texture1);\nglActiveTexture(GL_TEXTURE1);\nglBindTexture(GL_TEXTURE_2D, texture2);\n\nglBindVertexArray(VAO);\nglDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);\n",[122,2507,2508,2513,2518,2523,2528,2532,2537],{"__ignoreMap":120},[31,2509,2510],{"class":126,"line":127},[31,2511,2512],{},"glActiveTexture(GL_TEXTURE0);\n",[31,2514,2515],{"class":126,"line":133},[31,2516,2517],{},"glBindTexture(GL_TEXTURE_2D, texture1);\n",[31,2519,2520],{"class":126,"line":139},[31,2521,2522],{},"glActiveTexture(GL_TEXTURE1);\n",[31,2524,2525],{"class":126,"line":145},[31,2526,2527],{},"glBindTexture(GL_TEXTURE_2D, texture2);\n",[31,2529,2530],{"class":126,"line":151},[31,2531,992],{"emptyLinePlaceholder":991},[31,2533,2534],{"class":126,"line":781},[31,2535,2536],{},"glBindVertexArray(VAO);\n",[31,2538,2539],{"class":126,"line":787},[31,2540,2541],{},"glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);\n",[10,2543,2544,2545,2548],{},"​\t我们还要",[14,2546,2547],{},"通过使用glUniform1i设置每个采样器的方式告诉OpenGL每个着色器采样器属于哪个纹理单元","。我们只需要设置一次即可，所以这个会放在渲染循环的前面：",[115,2550,2552],{"className":117,"code":2551,"language":119,"meta":120,"style":120},"ourShader.use(); \u002F\u002F 不要忘记在设置uniform变量之前激活着色器程序！\nglUniform1i(glGetUniformLocation(ourShader.ID, \"texture1\"), 0); \u002F\u002F 手动设置\nourShader.setInt(\"texture2\", 1); \u002F\u002F 或者使用着色器类设置\n\nwhile(...) \n{\n    [...]\n}\n",[122,2553,2554,2559,2564,2569,2573,2578,2582,2587],{"__ignoreMap":120},[31,2555,2556],{"class":126,"line":127},[31,2557,2558],{},"ourShader.use(); \u002F\u002F 不要忘记在设置uniform变量之前激活着色器程序！\n",[31,2560,2561],{"class":126,"line":133},[31,2562,2563],{},"glUniform1i(glGetUniformLocation(ourShader.ID, \"texture1\"), 0); \u002F\u002F 手动设置\n",[31,2565,2566],{"class":126,"line":139},[31,2567,2568],{},"ourShader.setInt(\"texture2\", 1); \u002F\u002F 或者使用着色器类设置\n",[31,2570,2571],{"class":126,"line":145},[31,2572,992],{"emptyLinePlaceholder":991},[31,2574,2575],{"class":126,"line":151},[31,2576,2577],{},"while(...) \n",[31,2579,2580],{"class":126,"line":781},[31,2581,823],{},[31,2583,2584],{"class":126,"line":787},[31,2585,2586],{},"    [...]\n",[31,2588,2589],{"class":126,"line":793},[31,2590,841],{},[10,2592,1044],{},[115,2594,2596],{"className":117,"code":2595,"language":119,"meta":120,"style":120},"#include \u003Cglad\u002Fglad.h>\n#include \u003CGLFW\u002Fglfw3.h>\n#include \u003Copencv4\u002Fopencv2\u002Fopencv.hpp>\n#include \u003Ciostream>\n#include \u003Ccmath>\n#include \u003Cfilesystem>\n#include \"utils\u002Fshader.h\"\n\n\u002F\u002F 窗口大小改变时的回调函数\nvoid framebuffer_size_callback(GLFWwindow* window, int width, int height) {\n    glViewport(0, 0, width, height);\n}\n\nstd::string getCurrentWorkingDirectory() {\n    namespace fs = std::filesystem;\n    try {\n        \u002F\u002F current_path() 返回当前工作目录\n        return fs::current_path().string();\n    } catch (const fs::filesystem_error& e) {\n        std::cerr \u003C\u003C \"Filesystem Error: \" \u003C\u003C e.what() \u003C\u003C std::endl;\n        return \"\";\n    }\n}\n\n\nint main() {\n\n    auto path = getCurrentWorkingDirectory().append(\"\u002FAssets\u002Fimage.png\");\n    auto path2 = getCurrentWorkingDirectory().append(\"\u002FAssets\u002Fimage2.png\");\n    cv::Mat image = cv::imread(path);\n    cv::Mat image2 = cv::imread(path2);\n    cv::cvtColor(image, image, cv::COLOR_BGR2RGB);\n    cv::cvtColor(image2, image2, cv::COLOR_BGR2RGB);\n\n    \u002F\u002F 转换为字节数组\n    std::vector\u003Cuchar> imageData;\n    std::vector\u003Cuchar> imageData2;\n    if (image.empty()) {\n        std::cerr \u003C\u003C \"Could not read the image: \" \u003C\u003C path \u003C\u003C std::endl;\n        return 1;\n    }\n\n    cv::imencode(\".png\", image, imageData);\n    cv::imencode(\".png\", image2, imageData2);\n\n\n    \n    \u002F\u002F 初始化GLFW\n    glfwInit();\n    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);\n    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);\n    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);\n\n    \u002F\u002F 创建窗口\n    GLFWwindow* window = glfwCreateWindow(800, 600, \"OpenGL Window\", NULL, NULL);\n    if (window == NULL) {\n        std::cout \u003C\u003C \"Failed to create GLFW window\" \u003C\u003C std::endl;\n        glfwTerminate();\n        return -1;\n    }\n    \n    glfwMakeContextCurrent(window);\n    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);\n\n    \u002F\u002F 初始化GLAD\n    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {\n        std::cout \u003C\u003C \"Failed to initialize GLAD\" \u003C\u003C std::endl;\n        return -1;\n    }\n\n    float vertices[] = {\n    \u002F\u002F     ---- 位置 ----       ---- 颜色 ----     - 纹理坐标 -\n        0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f,   \u002F\u002F 右上\n        0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f,   \u002F\u002F 右下\n        -0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f,   \u002F\u002F 左下\n        -0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f    \u002F\u002F 左上\n    };\n    \n    unsigned int indices[] = {\n        0, 1, 3, \u002F\u002F 第一个三角形\n        1, 2, 3  \u002F\u002F 第二个三角形\n    };\n    float texCoords[] = {\n    0.0f, 0.0f, \u002F\u002F 左下角\n    1.0f, 0.0f, \u002F\u002F 右下角\n    0.5f, 1.0f  \u002F\u002F 上中\n    };\n\n    \u002F\u002F 纹理\n    unsigned int texture;\n    unsigned int texture2;\n    glGenTextures(1, &texture); \u002F\u002F 生成纹理\n    glGenTextures(1, &texture2); \u002F\u002F 生成纹理\n    \n    \u002F\u002F 配置第一个纹理\n    glActiveTexture(GL_TEXTURE0);\n    glBindTexture(GL_TEXTURE_2D, texture); \u002F\u002F 绑定纹理\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); \u002F\u002F 设置纹理环绕方式\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); \u002F\u002F 设置纹理环绕方式\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); \u002F\u002F 设置纹理过滤方式\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); \u002F\u002F 设置纹理过滤方式\n    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.cols, image.rows, 0, GL_RGB, GL_UNSIGNED_BYTE, image.data); \u002F\u002F 设置纹理数据\n    glGenerateMipmap(GL_TEXTURE_2D); \u002F\u002F 生成mipmap\n\n    \u002F\u002F 配置第二个纹理\n    glActiveTexture(GL_TEXTURE1);\n    glBindTexture(GL_TEXTURE_2D, texture2); \u002F\u002F 绑定纹理\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); \u002F\u002F 设置纹理环绕方式\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); \u002F\u002F 设置纹理环绕方式   \n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); \u002F\u002F 设置纹理过滤方式\n    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); \u002F\u002F 设置纹理过滤方式\n    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image2.cols, image2.rows, 0, GL_RGB, GL_UNSIGNED_BYTE, image2.data); \u002F\u002F 设置纹理数据\n    glGenerateMipmap(GL_TEXTURE_2D); \u002F\u002F 生成mipmap\n\n\n    \u002F\u002F 创建VAO\n    unsigned int VAO;\n    glGenVertexArrays(1, &VAO);\n    glBindVertexArray(VAO); \u002F\u002F 绑定VAO\n\n    \u002F\u002F 创建VBO\n    unsigned int VBO;\n    glGenBuffers(1, &VBO);\n    glBindBuffer(GL_ARRAY_BUFFER, VBO); \u002F\u002F 绑定VBO\n    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);\n\n    \u002F\u002F 创建EBO\n    unsigned int EBO;\n    glGenBuffers(1, &EBO);\n    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); \u002F\u002F 绑定EBO\n    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);\n\n    int nrAttributes;\n    glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);\n    std::cout \u003C\u003C \"Maximum nr of vertex attributes supported: \" \u003C\u003C nrAttributes \u003C\u003C std::endl;\n\n    \u002F\u002F 位置属性\n    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);\n    glEnableVertexAttribArray(0); \u002F\u002F 启用顶点属性\n\n    \u002F\u002F 颜色属性\n    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));\n    glEnableVertexAttribArray(1);\n    \n    \u002F\u002F 纹理属性\n    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));\n    glEnableVertexAttribArray(2);\n\n    \u002F\u002F 构建并编译着色器程序\n    Shader ourShader(\"shader\u002Fcustom.vert\", \"shader\u002Ffragment.vert\");\n\n    \u002F\u002F 渲染循环\n    while (!glfwWindowShouldClose(window)) {\n        \u002F\u002F 处理输入\n        glfwPollEvents();\n        \n        \u002F\u002F 渲染指令\n        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);\n        glClear(GL_COLOR_BUFFER_BIT);\n        \n        \u002F\u002F 使用着色器程序\n        ourShader.use();\n\n        ourShader.setInt(\"ourTexture\", 0);\n        ourShader.setInt(\"ourTexture2\", 1);\n        \n        glBindVertexArray(VAO);\n        \n        \u002F\u002F 绘制矩形（使用EBO）\n        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);\n        \n        \u002F\u002F 交换缓冲区和检查IO事件\n        glfwSwapBuffers(window);\n    }\n\n    \u002F\u002F 清理资源\n    glDeleteVertexArrays(1, &VAO);\n    glDeleteBuffers(1, &VBO);\n    glDeleteBuffers(1, &EBO);\n    glDeleteTextures(1, &texture);\n    glDeleteTextures(1, &texture2);\n    glfwTerminate();\n    return 0;\n}\n",[122,2597,2598,2602,2606,2610,2614,2618,2622,2626,2630,2634,2638,2642,2646,2650,2654,2658,2662,2666,2670,2674,2678,2682,2686,2690,2694,2698,2702,2706,2710,2715,2719,2724,2728,2733,2737,2741,2745,2750,2754,2758,2762,2766,2770,2774,2779,2783,2787,2791,2795,2799,2803,2807,2811,2815,2819,2823,2827,2831,2835,2839,2843,2847,2851,2855,2859,2863,2867,2871,2875,2879,2883,2887,2891,2895,2899,2903,2907,2911,2915,2919,2923,2927,2931,2935,2939,2943,2947,2951,2955,2959,2963,2968,2973,2978,2982,2987,2992,2997,3002,3007,3012,3017,3022,3027,3031,3036,3041,3046,3050,3055,3059,3063,3068,3072,3076,3080,3084,3088,3092,3097,3101,3105,3109,3113,3118,3122,3126,3130,3134,3138,3143,3147,3151,3155,3159,3163,3167,3171,3175,3180,3184,3188,3192,3196,3200,3204,3208,3212,3216,3220,3224,3228,3232,3236,3240,3244,3248,3252,3256,3260,3264,3268,3272,3276,3282,3288,3293,3298,3303,3308,3313,3318,3323,3328,3333,3338,3343,3348,3353,3358,3363,3369,3374,3379],{"__ignoreMap":120},[31,2599,2600],{"class":126,"line":127},[31,2601,1054],{},[31,2603,2604],{"class":126,"line":133},[31,2605,1059],{},[31,2607,2608],{"class":126,"line":139},[31,2609,1064],{},[31,2611,2612],{"class":126,"line":145},[31,2613,1069],{},[31,2615,2616],{"class":126,"line":151},[31,2617,1074],{},[31,2619,2620],{"class":126,"line":781},[31,2621,1079],{},[31,2623,2624],{"class":126,"line":787},[31,2625,1084],{},[31,2627,2628],{"class":126,"line":793},[31,2629,992],{"emptyLinePlaceholder":991},[31,2631,2632],{"class":126,"line":798},[31,2633,1093],{},[31,2635,2636],{"class":126,"line":804},[31,2637,1098],{},[31,2639,2640],{"class":126,"line":809},[31,2641,1103],{},[31,2643,2644],{"class":126,"line":814},[31,2645,841],{},[31,2647,2648],{"class":126,"line":820},[31,2649,992],{"emptyLinePlaceholder":991},[31,2651,2652],{"class":126,"line":826},[31,2653,1116],{},[31,2655,2656],{"class":126,"line":832},[31,2657,1121],{},[31,2659,2660],{"class":126,"line":838},[31,2661,1126],{},[31,2663,2664],{"class":126,"line":844},[31,2665,1131],{},[31,2667,2668],{"class":126,"line":850},[31,2669,1136],{},[31,2671,2672],{"class":126,"line":855},[31,2673,1141],{},[31,2675,2676],{"class":126,"line":861},[31,2677,1146],{},[31,2679,2680],{"class":126,"line":866},[31,2681,1151],{},[31,2683,2684],{"class":126,"line":1154},[31,2685,1157],{},[31,2687,2688],{"class":126,"line":1160},[31,2689,841],{},[31,2691,2692],{"class":126,"line":1165},[31,2693,992],{"emptyLinePlaceholder":991},[31,2695,2696],{"class":126,"line":1170},[31,2697,992],{"emptyLinePlaceholder":991},[31,2699,2700],{"class":126,"line":1175},[31,2701,1178],{},[31,2703,2704],{"class":126,"line":1181},[31,2705,992],{"emptyLinePlaceholder":991},[31,2707,2708],{"class":126,"line":1186},[31,2709,1189],{},[31,2711,2712],{"class":126,"line":1192},[31,2713,2714],{},"    auto path2 = getCurrentWorkingDirectory().append(\"\u002FAssets\u002Fimage2.png\");\n",[31,2716,2717],{"class":126,"line":1198},[31,2718,1195],{},[31,2720,2721],{"class":126,"line":1204},[31,2722,2723],{},"    cv::Mat image2 = cv::imread(path2);\n",[31,2725,2726],{"class":126,"line":1209},[31,2727,1201],{},[31,2729,2730],{"class":126,"line":1215},[31,2731,2732],{},"    cv::cvtColor(image2, image2, cv::COLOR_BGR2RGB);\n",[31,2734,2735],{"class":126,"line":1221},[31,2736,992],{"emptyLinePlaceholder":991},[31,2738,2739],{"class":126,"line":1227},[31,2740,1212],{},[31,2742,2743],{"class":126,"line":1233},[31,2744,1218],{},[31,2746,2747],{"class":126,"line":1239},[31,2748,2749],{},"    std::vector\u003Cuchar> imageData2;\n",[31,2751,2752],{"class":126,"line":1244},[31,2753,1224],{},[31,2755,2756],{"class":126,"line":1250},[31,2757,1230],{},[31,2759,2760],{"class":126,"line":1255},[31,2761,1236],{},[31,2763,2764],{"class":126,"line":1261},[31,2765,1157],{},[31,2767,2768],{"class":126,"line":1267},[31,2769,992],{"emptyLinePlaceholder":991},[31,2771,2772],{"class":126,"line":1273},[31,2773,1247],{},[31,2775,2776],{"class":126,"line":1279},[31,2777,2778],{},"    cv::imencode(\".png\", image2, imageData2);\n",[31,2780,2781],{"class":126,"line":1285},[31,2782,992],{"emptyLinePlaceholder":991},[31,2784,2785],{"class":126,"line":1291},[31,2786,992],{"emptyLinePlaceholder":991},[31,2788,2789],{"class":126,"line":1296},[31,2790,1258],{},[31,2792,2793],{"class":126,"line":1302},[31,2794,1264],{},[31,2796,2797],{"class":126,"line":1308},[31,2798,1270],{},[31,2800,2801],{"class":126,"line":1314},[31,2802,1276],{},[31,2804,2805],{"class":126,"line":1320},[31,2806,1282],{},[31,2808,2809],{"class":126,"line":1326},[31,2810,1288],{},[31,2812,2813],{"class":126,"line":1332},[31,2814,992],{"emptyLinePlaceholder":991},[31,2816,2817],{"class":126,"line":1337},[31,2818,1299],{},[31,2820,2821],{"class":126,"line":1342},[31,2822,1305],{},[31,2824,2825],{"class":126,"line":1348},[31,2826,1311],{},[31,2828,2829],{"class":126,"line":1354},[31,2830,1317],{},[31,2832,2833],{"class":126,"line":1359},[31,2834,1323],{},[31,2836,2837],{"class":126,"line":1365},[31,2838,1329],{},[31,2840,2841],{"class":126,"line":1371},[31,2842,1157],{},[31,2844,2845],{"class":126,"line":1377},[31,2846,1258],{},[31,2848,2849],{"class":126,"line":1382},[31,2850,1345],{},[31,2852,2853],{"class":126,"line":1387},[31,2854,1351],{},[31,2856,2857],{"class":126,"line":1392},[31,2858,992],{"emptyLinePlaceholder":991},[31,2860,2861],{"class":126,"line":1398},[31,2862,1362],{},[31,2864,2865],{"class":126,"line":1404},[31,2866,1368],{},[31,2868,2869],{"class":126,"line":1410},[31,2870,1374],{},[31,2872,2873],{"class":126,"line":1416},[31,2874,1329],{},[31,2876,2877],{"class":126,"line":1422},[31,2878,1157],{},[31,2880,2881],{"class":126,"line":1428},[31,2882,992],{"emptyLinePlaceholder":991},[31,2884,2885],{"class":126,"line":1434},[31,2886,1395],{},[31,2888,2889],{"class":126,"line":1439},[31,2890,1401],{},[31,2892,2893],{"class":126,"line":1445},[31,2894,1407],{},[31,2896,2897],{"class":126,"line":1451},[31,2898,1413],{},[31,2900,2901],{"class":126,"line":1457},[31,2902,1419],{},[31,2904,2905],{"class":126,"line":1462},[31,2906,1425],{},[31,2908,2909],{"class":126,"line":1468},[31,2910,1431],{},[31,2912,2913],{"class":126,"line":1473},[31,2914,1258],{},[31,2916,2917],{"class":126,"line":1478},[31,2918,1442],{},[31,2920,2921],{"class":126,"line":1483},[31,2922,1448],{},[31,2924,2925],{"class":126,"line":1488},[31,2926,1454],{},[31,2928,2929],{"class":126,"line":1493},[31,2930,1431],{},[31,2932,2933],{"class":126,"line":1499},[31,2934,1465],{},[31,2936,2937],{"class":126,"line":1505},[31,2938,136],{},[31,2940,2941],{"class":126,"line":1511},[31,2942,142],{},[31,2944,2945],{"class":126,"line":1517},[31,2946,148],{},[31,2948,2949],{"class":126,"line":1522},[31,2950,1431],{},[31,2952,2953],{"class":126,"line":1528},[31,2954,992],{"emptyLinePlaceholder":991},[31,2956,2957],{"class":126,"line":1534},[31,2958,1496],{},[31,2960,2961],{"class":126,"line":1540},[31,2962,1502],{},[31,2964,2965],{"class":126,"line":1546},[31,2966,2967],{},"    unsigned int texture2;\n",[31,2969,2970],{"class":126,"line":1552},[31,2971,2972],{},"    glGenTextures(1, &texture); \u002F\u002F 生成纹理\n",[31,2974,2975],{"class":126,"line":1557},[31,2976,2977],{},"    glGenTextures(1, &texture2); \u002F\u002F 生成纹理\n",[31,2979,2980],{"class":126,"line":1563},[31,2981,1258],{},[31,2983,2984],{"class":126,"line":1569},[31,2985,2986],{},"    \u002F\u002F 配置第一个纹理\n",[31,2988,2989],{"class":126,"line":1574},[31,2990,2991],{},"    glActiveTexture(GL_TEXTURE0);\n",[31,2993,2994],{"class":126,"line":1579},[31,2995,2996],{},"    glBindTexture(GL_TEXTURE_2D, texture); \u002F\u002F 绑定纹理\n",[31,2998,2999],{"class":126,"line":1584},[31,3000,3001],{},"    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); \u002F\u002F 设置纹理环绕方式\n",[31,3003,3004],{"class":126,"line":1590},[31,3005,3006],{},"    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); \u002F\u002F 设置纹理环绕方式\n",[31,3008,3009],{"class":126,"line":1596},[31,3010,3011],{},"    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); \u002F\u002F 设置纹理过滤方式\n",[31,3013,3014],{"class":126,"line":1602},[31,3015,3016],{},"    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); \u002F\u002F 设置纹理过滤方式\n",[31,3018,3019],{"class":126,"line":1608},[31,3020,3021],{},"    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.cols, image.rows, 0, GL_RGB, GL_UNSIGNED_BYTE, image.data); \u002F\u002F 设置纹理数据\n",[31,3023,3024],{"class":126,"line":1613},[31,3025,3026],{},"    glGenerateMipmap(GL_TEXTURE_2D); \u002F\u002F 生成mipmap\n",[31,3028,3029],{"class":126,"line":1619},[31,3030,992],{"emptyLinePlaceholder":991},[31,3032,3033],{"class":126,"line":1625},[31,3034,3035],{},"    \u002F\u002F 配置第二个纹理\n",[31,3037,3038],{"class":126,"line":1631},[31,3039,3040],{},"    glActiveTexture(GL_TEXTURE1);\n",[31,3042,3043],{"class":126,"line":1637},[31,3044,3045],{},"    glBindTexture(GL_TEXTURE_2D, texture2); \u002F\u002F 绑定纹理\n",[31,3047,3048],{"class":126,"line":1643},[31,3049,3001],{},[31,3051,3052],{"class":126,"line":1648},[31,3053,3054],{},"    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); \u002F\u002F 设置纹理环绕方式   \n",[31,3056,3057],{"class":126,"line":1654},[31,3058,3011],{},[31,3060,3061],{"class":126,"line":1660},[31,3062,3016],{},[31,3064,3065],{"class":126,"line":1666},[31,3066,3067],{},"    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image2.cols, image2.rows, 0, GL_RGB, GL_UNSIGNED_BYTE, image2.data); \u002F\u002F 设置纹理数据\n",[31,3069,3070],{"class":126,"line":1672},[31,3071,3026],{},[31,3073,3074],{"class":126,"line":1678},[31,3075,992],{"emptyLinePlaceholder":991},[31,3077,3078],{"class":126,"line":1683},[31,3079,992],{"emptyLinePlaceholder":991},[31,3081,3082],{"class":126,"line":1689},[31,3083,1587],{},[31,3085,3086],{"class":126,"line":1695},[31,3087,1593],{},[31,3089,3090],{"class":126,"line":1701},[31,3091,1599],{},[31,3093,3094],{"class":126,"line":1706},[31,3095,3096],{},"    glBindVertexArray(VAO); \u002F\u002F 绑定VAO\n",[31,3098,3099],{"class":126,"line":1712},[31,3100,992],{"emptyLinePlaceholder":991},[31,3102,3103],{"class":126,"line":1718},[31,3104,1616],{},[31,3106,3107],{"class":126,"line":1724},[31,3108,1622],{},[31,3110,3111],{"class":126,"line":1730},[31,3112,1628],{},[31,3114,3115],{"class":126,"line":1736},[31,3116,3117],{},"    glBindBuffer(GL_ARRAY_BUFFER, VBO); \u002F\u002F 绑定VBO\n",[31,3119,3120],{"class":126,"line":1742},[31,3121,1640],{},[31,3123,3124],{"class":126,"line":1748},[31,3125,992],{"emptyLinePlaceholder":991},[31,3127,3128],{"class":126,"line":1754},[31,3129,1651],{},[31,3131,3132],{"class":126,"line":1760},[31,3133,1657],{},[31,3135,3136],{"class":126,"line":1765},[31,3137,1663],{},[31,3139,3140],{"class":126,"line":1771},[31,3141,3142],{},"    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); \u002F\u002F 绑定EBO\n",[31,3144,3145],{"class":126,"line":1777},[31,3146,1675],{},[31,3148,3149],{"class":126,"line":1782},[31,3150,992],{"emptyLinePlaceholder":991},[31,3152,3153],{"class":126,"line":1788},[31,3154,1686],{},[31,3156,3157],{"class":126,"line":1794},[31,3158,1692],{},[31,3160,3161],{"class":126,"line":1800},[31,3162,1698],{},[31,3164,3165],{"class":126,"line":1806},[31,3166,992],{"emptyLinePlaceholder":991},[31,3168,3169],{"class":126,"line":1812},[31,3170,1709],{},[31,3172,3173],{"class":126,"line":1818},[31,3174,1715],{},[31,3176,3177],{"class":126,"line":1824},[31,3178,3179],{},"    glEnableVertexAttribArray(0); \u002F\u002F 启用顶点属性\n",[31,3181,3182],{"class":126,"line":1830},[31,3183,992],{"emptyLinePlaceholder":991},[31,3185,3186],{"class":126,"line":1835},[31,3187,1727],{},[31,3189,3190],{"class":126,"line":1841},[31,3191,1733],{},[31,3193,3194],{"class":126,"line":1847},[31,3195,1739],{},[31,3197,3198],{"class":126,"line":1852},[31,3199,1258],{},[31,3201,3202],{"class":126,"line":1858},[31,3203,1745],{},[31,3205,3206],{"class":126,"line":1864},[31,3207,1751],{},[31,3209,3210],{"class":126,"line":1869},[31,3211,1757],{},[31,3213,3214],{"class":126,"line":1875},[31,3215,992],{"emptyLinePlaceholder":991},[31,3217,3218],{"class":126,"line":1880},[31,3219,1768],{},[31,3221,3222],{"class":126,"line":1886},[31,3223,1774],{},[31,3225,3226],{"class":126,"line":1892},[31,3227,992],{"emptyLinePlaceholder":991},[31,3229,3230],{"class":126,"line":1897},[31,3231,1785],{},[31,3233,3234],{"class":126,"line":1903},[31,3235,1791],{},[31,3237,3238],{"class":126,"line":1909},[31,3239,1797],{},[31,3241,3242],{"class":126,"line":1914},[31,3243,1803],{},[31,3245,3246],{"class":126,"line":1919},[31,3247,1809],{},[31,3249,3250],{"class":126,"line":1925},[31,3251,1815],{},[31,3253,3254],{"class":126,"line":1931},[31,3255,1821],{},[31,3257,3258],{"class":126,"line":1937},[31,3259,1827],{},[31,3261,3262],{"class":126,"line":1943},[31,3263,1809],{},[31,3265,3266],{"class":126,"line":1949},[31,3267,1838],{},[31,3269,3270],{"class":126,"line":1955},[31,3271,1844],{},[31,3273,3274],{"class":126,"line":1961},[31,3275,992],{"emptyLinePlaceholder":991},[31,3277,3279],{"class":126,"line":3278},164,[31,3280,3281],{},"        ourShader.setInt(\"ourTexture\", 0);\n",[31,3283,3285],{"class":126,"line":3284},165,[31,3286,3287],{},"        ourShader.setInt(\"ourTexture2\", 1);\n",[31,3289,3291],{"class":126,"line":3290},166,[31,3292,1809],{},[31,3294,3296],{"class":126,"line":3295},167,[31,3297,1872],{},[31,3299,3301],{"class":126,"line":3300},168,[31,3302,1809],{},[31,3304,3306],{"class":126,"line":3305},169,[31,3307,1883],{},[31,3309,3311],{"class":126,"line":3310},170,[31,3312,1889],{},[31,3314,3316],{"class":126,"line":3315},171,[31,3317,1809],{},[31,3319,3321],{"class":126,"line":3320},172,[31,3322,1900],{},[31,3324,3326],{"class":126,"line":3325},173,[31,3327,1906],{},[31,3329,3331],{"class":126,"line":3330},174,[31,3332,1157],{},[31,3334,3336],{"class":126,"line":3335},175,[31,3337,992],{"emptyLinePlaceholder":991},[31,3339,3341],{"class":126,"line":3340},176,[31,3342,1922],{},[31,3344,3346],{"class":126,"line":3345},177,[31,3347,1928],{},[31,3349,3351],{"class":126,"line":3350},178,[31,3352,1934],{},[31,3354,3356],{"class":126,"line":3355},179,[31,3357,1940],{},[31,3359,3361],{"class":126,"line":3360},180,[31,3362,1946],{},[31,3364,3366],{"class":126,"line":3365},181,[31,3367,3368],{},"    glDeleteTextures(1, &texture2);\n",[31,3370,3372],{"class":126,"line":3371},182,[31,3373,1952],{},[31,3375,3377],{"class":126,"line":3376},183,[31,3378,1958],{},[31,3380,3382],{"class":126,"line":3381},184,[31,3383,841],{},[3385,3386,3387],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":120,"searchDepth":133,"depth":133,"links":3389},[3390,3391,3394,3395,3396,3397,3402],{"id":165,"depth":133,"text":165},{"id":313,"depth":133,"text":313,"children":3392},[3393],{"id":392,"depth":139,"text":392},{"id":608,"depth":133,"text":608},{"id":871,"depth":133,"text":871},{"id":2032,"depth":133,"text":2033},{"id":2123,"depth":133,"text":2124,"children":3398},[3399,3400,3401],{"id":2130,"depth":139,"text":2131},{"id":2190,"depth":139,"text":2191},{"id":2246,"depth":139,"text":2247},{"id":2298,"depth":133,"text":2298},"​\t我们已经了解到，我们可以为每个顶点添加颜色来增加图形的细节，从而创建出有趣的图像。但是，如果想让图形看起来更真实，我们就必须有足够多的顶点，从而指定足够多的颜色。这将会产生很多额外开销，因为每个模型都会需求更多的顶点，每个顶点又需求一个颜色属性。","md",{},"\u002Fcpp\u002Fopengel\u002F纹理",{"description":3403},"cpp\u002Fopengel\u002F纹理","ePJopU8ZZbpuB92wixAjU43QWvqIoxuwfSRpmYzIGOY",1791042463753]