<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Opengl on 随记</title><link>https://blog.liuyi2026.asia/zh/tags/opengl/</link><description>Recent content in Opengl on 随记</description><generator>Hugo</generator><language>zh</language><lastBuildDate>Thu, 07 May 2026 11:30:58 +0800</lastBuildDate><atom:link href="https://blog.liuyi2026.asia/zh/tags/opengl/index.xml" rel="self" type="application/rss+xml"/><item><title>01颜色</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/learnopengl/02%E7%81%AF%E5%85%89/01%E9%A2%9C%E8%89%B2/</link><pubDate>Thu, 07 May 2026 11:30:58 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/learnopengl/02%E7%81%AF%E5%85%89/01%E9%A2%9C%E8%89%B2/</guid><description>&lt;h1 id="颜色">颜色&lt;a class="anchor" href="#%e9%a2%9c%e8%89%b2">#&lt;/a>&lt;/h1>
&lt;p>我们在前几章中简单地使用和处理了颜色，但从未对它们进行过正式的定义。在这里，我们将讨论颜色的概念，并为接下来的光照章节构建场景。&lt;/p>
&lt;p>在现实世界中，颜色可以取任何已知的颜色值，每个物体都有其独特的颜色。而在数字世界中，我们需要将（无限的）真实颜色映射到（有限的）数字值，因此并非所有现实世界的颜色都能用数字方式表示。颜色通常使用 &lt;code>red&lt;/code> 、 &lt;code>green&lt;/code> 、 &lt;code>blue&lt;/code> 三个分量进行数字表示，简写为 &lt;code>RGB&lt;/code> 。通过对这三个值在 &lt;code>[0,1]&lt;/code> 范围内进行不同的组合，我们几乎可以表示任何颜色。例如，要得到_珊瑚色_ ，我们可以定义一个颜色向量：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-css" data-lang="css">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">glm&lt;/span>::&lt;span style="color:#a6e22e">vec3&lt;/span> &lt;span style="color:#f92672">coral&lt;/span>&lt;span style="color:#f92672">(&lt;/span>&lt;span style="color:#f92672">1&lt;/span>.&lt;span style="color:#a6e22e">0f&lt;/span>&lt;span style="color:#f92672">,&lt;/span> &lt;span style="color:#f92672">0&lt;/span>.&lt;span style="color:#a6e22e">5f&lt;/span>&lt;span style="color:#f92672">,&lt;/span> &lt;span style="color:#f92672">0&lt;/span>.&lt;span style="color:#a6e22e">31f&lt;/span>&lt;span style="color:#f92672">);&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>我们在现实生活中看到的物体颜色并非它本身的颜色，而是物体反射的颜色。物体未吸收（反射）的颜色就是我们感知到的颜色。例如，太阳光被感知为白光，它是多种不同颜色混合而成的（如图所示）。如果我们用这种白光照射一个蓝色的玩具，它会吸收除蓝色以外的所有白色光。由于玩具不吸收蓝色光，所以蓝色光会被反射。这些反射光进入我们的眼睛，使我们觉得玩具是蓝色的。下图展示了一个珊瑚色玩具反射多种不同强度颜色的光：&lt;/p>
&lt;p>&lt;img src="https://learnopengl.com/img/lighting/light_reflection.png" alt="" />&lt;/p>
&lt;p>你可以看到，白色的阳光是由所有可见颜色组成的，物体吸收了其中的大部分颜色。它只反射那些代表物体颜色的颜色，而这些颜色的组合就构成了我们所感知到的颜色（在本例中是珊瑚色）。&lt;/p>
&lt;p>从技术上讲，这有点复杂，但我们会在 PBR 章节中讨论这个问题。&lt;/p>
&lt;p>这些颜色反射规则直接适用于图形领域。在 OpenGL 中定义光源时，我们需要给它赋予颜色。上一段中我们用了白色，所以光源也用白色。如果我们把光源的颜色值乘以物体的颜色值，得到的颜色就是物体的反射颜色（也就是它的感知颜色）。让我们再来看看之前的例子（这次用珊瑚色），看看如何在图形领域计算它的感知颜色。我们通过将光源颜色向量和物体颜色向量逐个分量相乘来得到结果颜色向量：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>glm&lt;span style="color:#f92672">::&lt;/span>vec3 lightColor(&lt;span style="color:#ae81ff">1.0f&lt;/span>, &lt;span style="color:#ae81ff">1.0f&lt;/span>, &lt;span style="color:#ae81ff">1.0f&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>glm&lt;span style="color:#f92672">::&lt;/span>vec3 toyColor(&lt;span style="color:#ae81ff">1.0f&lt;/span>, &lt;span style="color:#ae81ff">0.5f&lt;/span>, &lt;span style="color:#ae81ff">0.31f&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>glm&lt;span style="color:#f92672">::&lt;/span>vec3 result &lt;span style="color:#f92672">=&lt;/span> lightColor &lt;span style="color:#f92672">*&lt;/span> toyColor; &lt;span style="color:#75715e">// = (1.0f, 0.5f, 0.31f);
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>我们可以看到，玩具的颜色 &lt;em>吸收了_大部分白光，但根据其自身的色值，反射出几种红色、绿色和蓝色光。这模拟了现实生活中颜色的运作方式。因此，我们可以将物体的颜色定义为_它从光源反射的各种颜色分量的量&lt;/em> 。那么，如果我们使用绿光会发生什么呢？&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>glm&lt;span style="color:#f92672">::&lt;/span>vec3 lightColor(&lt;span style="color:#ae81ff">0.0f&lt;/span>, &lt;span style="color:#ae81ff">1.0f&lt;/span>, &lt;span style="color:#ae81ff">0.0f&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>glm&lt;span style="color:#f92672">::&lt;/span>vec3 toyColor(&lt;span style="color:#ae81ff">1.0f&lt;/span>, &lt;span style="color:#ae81ff">0.5f&lt;/span>, &lt;span style="color:#ae81ff">0.31f&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>glm&lt;span style="color:#f92672">::&lt;/span>vec3 result &lt;span style="color:#f92672">=&lt;/span> lightColor &lt;span style="color:#f92672">*&lt;/span> toyColor; &lt;span style="color:#75715e">// = (0.0f, 0.5f, 0.0f);
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>正如我们所见，这个玩具本身不吸收或反射红光和蓝光。它吸收了光线中一半的绿色成分，但也反射了另一半绿色成分。因此，我们感知到的玩具颜色是深绿色。我们可以看到，如果使用绿光，只有绿色成分会被反射并被感知；红色和蓝色成分则无法被感知。结果，珊瑚物体突然变成了深绿色。让我们再用深橄榄绿色的光来做一个例子：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>glm&lt;span style="color:#f92672">::&lt;/span>vec3 lightColor(&lt;span style="color:#ae81ff">0.33f&lt;/span>, &lt;span style="color:#ae81ff">0.42f&lt;/span>, &lt;span style="color:#ae81ff">0.18f&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>glm&lt;span style="color:#f92672">::&lt;/span>vec3 toyColor(&lt;span style="color:#ae81ff">1.0f&lt;/span>, &lt;span style="color:#ae81ff">0.5f&lt;/span>, &lt;span style="color:#ae81ff">0.31f&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>glm&lt;span style="color:#f92672">::&lt;/span>vec3 result &lt;span style="color:#f92672">=&lt;/span> lightColor &lt;span style="color:#f92672">*&lt;/span> toyColor; &lt;span style="color:#75715e">// = (0.33f, 0.21f, 0.06f);
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>正如你所看到的，我们可以使用不同的光色来给物体赋予有趣的颜色。玩转色彩并不难，可以尽情发挥创意。&lt;/p>
&lt;p>好了，关于颜色就说到这里，让我们开始搭建一个可以进行实验的场景吧。&lt;/p>
&lt;h1 id="灯光场景">灯光场景&lt;a class="anchor" href="#%e7%81%af%e5%85%89%e5%9c%ba%e6%99%af">#&lt;/a>&lt;/h1>
&lt;p>在接下来的章节中，我们将通过模拟真实世界的光照并大量运用色彩来创建有趣的视觉效果。由于现在我们将使用光源，因此我们需要将它们显示为场景中的视觉对象，并添加至少一个对象来模拟光照。&lt;/p>
&lt;p>首先，我们需要一个用来照射物体的模型，我们将使用前几章中提到的那个著名的容器立方体。我们还需要一个光源模型来表示光源在 3D 场景中的位置。为了简单起见，我们也用一个立方体来表示光源（我们已经有了 
&lt;a target="_blank" href="https://learnopengl.com/code_viewer.php?code=getting-started/cube_vertices_pos">顶点数据，&lt;/a> 对吧？）。&lt;/p></description></item><item><title>23批量渲染对象</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/23%E6%89%B9%E9%87%8F%E6%B8%B2%E6%9F%93%E5%AF%B9%E8%B1%A1/</link><pubDate>Tue, 05 May 2026 23:13:37 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/23%E6%89%B9%E9%87%8F%E6%B8%B2%E6%9F%93%E5%AF%B9%E8%B1%A1/</guid><description>&lt;p>核心内容是如何通过修改 &lt;mark>MVP（模型-视图-投影）矩阵&lt;/mark> 在不同位置多次绘制同一个物体，并初步探讨了性能优化的思路。&lt;/p>
&lt;h1 id="第-23-集在-opengl-中渲染多个物体">第 23 集：在 OpenGL 中渲染多个物体&lt;a class="anchor" href="#%e7%ac%ac-23-%e9%9b%86%e5%9c%a8-opengl-%e4%b8%ad%e6%b8%b2%e6%9f%93%e5%a4%9a%e4%b8%aa%e7%89%a9%e4%bd%93">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>
&lt;p>&lt;mark>[00:00:00 - 01:25] 核心目标与现状回顾&lt;/mark>&lt;/p>
&lt;ul>
&lt;li>回顾之前只能在屏幕上渲染一个 Logo。&lt;/li>
&lt;li>目标：在不同位置渲染两个或多个相同的物体。&lt;/li>
&lt;li>解决 ImGui 鼠标位置偏移的小技巧：切换 GPU 驱动（由 Intel 切换至 Nvidia）解决了一些驱动层面的显示问题。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;mark>渲染原理深究：Shader、VAO 与 Draw Call&lt;/mark>&lt;/p>
&lt;ul>
&lt;li>&lt;mark>Shader (Program)：&lt;/mark> 告诉 GPU “如何”绘制数据，而不仅仅是光照。&lt;/li>
&lt;li>&lt;mark>Vertex Array (VAO)：&lt;/mark> 包含实际的顶点数据（位置、纹理坐标等）。&lt;/li>
&lt;li>&lt;mark>Index Buffer：&lt;/mark> 决定顶点的组合顺序。&lt;/li>
&lt;li>&lt;mark>Draw Call：&lt;/mark> 调用 &lt;code>glDrawElements&lt;/code>，结合 Shader 和数据完成最终的光栅化。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;mark>如何改变物体位置？（两种方案）&lt;/mark>&lt;/p>
&lt;ul>
&lt;li>&lt;mark>方案 A（低效）：&lt;/mark> 为每个物体创建独立的顶点缓冲区（Vertex Buffer）。缺点：浪费显存，频繁切换 Buffer 速度慢。&lt;/li>
&lt;li>&lt;mark>方案 B（推荐）：&lt;/mark> 保持顶点数据不变，但在每次绘制前修改 &lt;mark>Uniform 变量&lt;/mark>（即 Model 矩阵）。通过矩阵变换将同一组顶点映射到屏幕的不同位置。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;mark>顶点坐标的标准化归零&lt;/mark>&lt;/p>
&lt;ul>
&lt;li>为了方便平移，将顶点的原始坐标从 &lt;code>[100, 200]&lt;/code> 修改为以原点为中心的 &lt;code>[-50, 50]&lt;/code>。&lt;/li>
&lt;li>这样做的好处是物体的 &lt;mark>Origin（原点）&lt;/mark> 位于其中心，应用平移矩阵时坐标计算更直观。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">// 定义三角形的顶点坐标（CPU 内存）
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		&lt;span style="color:#66d9ef">float&lt;/span> positions[] &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">50.0f&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">50.0f&lt;/span>,&lt;span style="color:#ae81ff">0.0f&lt;/span>,&lt;span style="color:#ae81ff">0.0f&lt;/span>,&lt;span style="color:#75715e">//0
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>			&lt;span style="color:#ae81ff">50.0f&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">50.0f&lt;/span>,&lt;span style="color:#ae81ff">1.0f&lt;/span>,&lt;span style="color:#ae81ff">0.0f&lt;/span>,&lt;span style="color:#75715e">//1
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>			&lt;span style="color:#ae81ff">50.0f&lt;/span>, &lt;span style="color:#ae81ff">50.0f&lt;/span>,&lt;span style="color:#ae81ff">1.0f&lt;/span>,&lt;span style="color:#ae81ff">1.0f&lt;/span>,&lt;span style="color:#75715e">//2
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#75715e">//0.5f, 0.5f,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>			&lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">50.0f&lt;/span>, &lt;span style="color:#ae81ff">50.0f&lt;/span>,&lt;span style="color:#ae81ff">0.0f&lt;/span>,&lt;span style="color:#ae81ff">1.0f&lt;/span>,&lt;span style="color:#75715e">//3
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>			&lt;span style="color:#75715e">//-0.5f, -0.5f,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">//while之前的代码
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		glm&lt;span style="color:#f92672">::&lt;/span>vec3 translationA(&lt;span style="color:#ae81ff">200&lt;/span>, &lt;span style="color:#ae81ff">200&lt;/span>, &lt;span style="color:#ae81ff">0&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		glm&lt;span style="color:#f92672">::&lt;/span>vec3 translationB(&lt;span style="color:#ae81ff">400&lt;/span>, &lt;span style="color:#ae81ff">200&lt;/span>, &lt;span style="color:#ae81ff">0&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">//while代码块内的代码
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#66d9ef">if&lt;/span> (r &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">1.0f&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>				increment &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">0.05f&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#66d9ef">else&lt;/span> &lt;span style="color:#a6e22e">if&lt;/span> (r &lt;span style="color:#f92672">&amp;lt;&lt;/span> &lt;span style="color:#ae81ff">0.0f&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>				increment &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0.05f&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			r &lt;span style="color:#f92672">+=&lt;/span> increment;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#75715e">//绘图前重新绑定
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>			shader.Bind();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>				&lt;span style="color:#75715e">//=======imgui添加============
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>				&lt;span style="color:#75715e">//imgui:这里吧mvp相关代码移到while循环中
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>				&lt;span style="color:#75715e">//向右向上移动200
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>				glm&lt;span style="color:#f92672">::&lt;/span>mat4 model &lt;span style="color:#f92672">=&lt;/span> glm&lt;span style="color:#f92672">::&lt;/span>translate(glm&lt;span style="color:#f92672">::&lt;/span>mat4(&lt;span style="color:#ae81ff">1.0f&lt;/span>), translationA);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>				glm&lt;span style="color:#f92672">::&lt;/span>mat4 mvp &lt;span style="color:#f92672">=&lt;/span> proj &lt;span style="color:#f92672">*&lt;/span> view &lt;span style="color:#f92672">*&lt;/span> model;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>				shader.SetUniformMat4f(&lt;span style="color:#e6db74">&amp;#34;u_MVP&amp;#34;&lt;/span>, mvp);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>				&lt;span style="color:#75715e">//=======imgui添加============
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>				&lt;span style="color:#75715e">//在u_Color的位置上设置数值
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>				&lt;span style="color:#75715e">//shader.SetUniform4f(&amp;#34;u_Color&amp;#34;, r, 0.3f, 0.0f, 1.0f);
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>				&lt;span style="color:#75715e">//========设置uniform========
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>				renderer.Draw(va, ib, shader);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>				&lt;span style="color:#75715e">//批量渲染对象
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>				glm&lt;span style="color:#f92672">::&lt;/span>mat4 model &lt;span style="color:#f92672">=&lt;/span> glm&lt;span style="color:#f92672">::&lt;/span>translate(glm&lt;span style="color:#f92672">::&lt;/span>mat4(&lt;span style="color:#ae81ff">1.0f&lt;/span>), translationB);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>				glm&lt;span style="color:#f92672">::&lt;/span>mat4 mvp &lt;span style="color:#f92672">=&lt;/span> proj &lt;span style="color:#f92672">*&lt;/span> view &lt;span style="color:#f92672">*&lt;/span> model;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>				shader.SetUniformMat4f(&lt;span style="color:#e6db74">&amp;#34;u_MVP&amp;#34;&lt;/span>, mvp);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>				renderer.Draw(va, ib, shader);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#75715e">//=======imgui添加============
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>			&lt;span style="color:#75715e">//{}//小窗口前面的代码
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>				
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;img src="img/ly-20260506220636032.png" alt="" />&lt;/p></description></item><item><title>22关于ImGui</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/22%E5%85%B3%E4%BA%8EImGui/</link><pubDate>Tue, 28 Apr 2026 22:49:11 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/22%E5%85%B3%E4%BA%8EImGui/</guid><description>&lt;p>第 22 集是 Cherno 系列中非常爽的一集，因为它让你告别了“硬编码”坐标的痛苦，转而使用&lt;mark>可视化 UI&lt;/mark> 来直接操控显卡里的数据。&lt;/p>
&lt;h1 id="代码">代码&lt;a class="anchor" href="#%e4%bb%a3%e7%a0%81">#&lt;/a>&lt;/h1>
&lt;p>
&lt;a target="_blank" href="https://github.com/ocornut/imgui">https://github.com/ocornut/imgui&lt;/a> 下载1.6版本，和视频中保持一致&lt;/p>
&lt;p>根目录下所有.h和.cpp文件，以及&lt;code>examples\opengl3_example&lt;/code>文件夹中的.h和.cpp文件都复制到项目中 &lt;del>不要include main.cpp文件&lt;/del> 。&lt;/p>
&lt;p>&lt;img src="img/ly-20260503213446407.png" alt="" />&lt;/p>
&lt;p>修改&lt;code>imgui_impl_glfw_gl3.cpp&lt;/code>中开头的 &lt;code>#include &amp;lt;GL/gl3w.h&amp;gt;&lt;/code> 为&lt;code>#include &amp;lt;GL/glew.h&amp;gt; &lt;/code>&lt;/p>
&lt;h2 id="添加头文件">添加头文件&lt;a class="anchor" href="#%e6%b7%bb%e5%8a%a0%e5%a4%b4%e6%96%87%e4%bb%b6">#&lt;/a>&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;imgui_1.60/imgui.h&amp;#34;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;imgui_1.60/imgui_impl_glfw_gl3.h&amp;#34;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="shader后的修改">shader后的修改&lt;a class="anchor" href="#shader%e5%90%8e%e7%9a%84%e4%bf%ae%e6%94%b9">#&lt;/a>&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">//=======imgui添加============
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		&lt;span style="color:#75715e">//imGui创建上下文
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		&lt;span style="color:#75715e">// Setup ImGui binding
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		ImGui&lt;span style="color:#f92672">::&lt;/span>CreateContext();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">//windows ,告诉 ImGui 你的程序窗口在哪里
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		&lt;span style="color:#75715e">//允许 ImGui 自动接管窗口的回调函数（比如鼠标点击、滚轮滚动）
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		ImGui_ImplGlfwGL3_Init(window, true);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">//设置 UI 的主题配色
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		ImGui&lt;span style="color:#f92672">::&lt;/span>StyleColorsDark();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">//解决白屏问题2：在进入 while 循环前，手动清一次屏并交换缓冲
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		&lt;span style="color:#75715e">//设置“清除颜色”
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		glClearColor(&lt;span style="color:#ae81ff">0.2f&lt;/span>, &lt;span style="color:#ae81ff">0.3f&lt;/span>, &lt;span style="color:#ae81ff">0.3f&lt;/span>, &lt;span style="color:#ae81ff">1.0f&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">//用上面选好的“油漆”填满整个颜色缓冲区（Color Buffer）
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		glClear(GL_COLOR_BUFFER_BIT);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">//交换缓冲区
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		glfwSwapBuffers(window); &lt;span style="color:#75715e">// 这一步把“深绿色”推送到显卡
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		&lt;span style="color:#75715e">//解决白屏问题3：最后才显示窗口
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		glfwShowWindow(window);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">//=======imgui添加============
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		&lt;span style="color:#66d9ef">bool&lt;/span> show_demo_window &lt;span style="color:#f92672">=&lt;/span> true;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#66d9ef">bool&lt;/span> show_another_window &lt;span style="color:#f92672">=&lt;/span> false;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		ImVec4 clear_color &lt;span style="color:#f92672">=&lt;/span> ImVec4(&lt;span style="color:#ae81ff">0.45f&lt;/span>, &lt;span style="color:#ae81ff">0.55f&lt;/span>, &lt;span style="color:#ae81ff">0.60f&lt;/span>, &lt;span style="color:#ae81ff">1.00f&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		glm&lt;span style="color:#f92672">::&lt;/span>vec3 translation(&lt;span style="color:#ae81ff">200&lt;/span>, &lt;span style="color:#ae81ff">200&lt;/span>, &lt;span style="color:#ae81ff">0&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">// 游戏/渲染主循环
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		&lt;span style="color:#66d9ef">while&lt;/span> (&lt;span style="color:#f92672">!&lt;/span>glfwWindowShouldClose(window))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#75715e">// 清理屏幕颜色缓冲区
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>			renderer.Clear();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#75715e">//=======imgui添加============
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>			ImGui_ImplGlfwGL3_NewFrame();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#75715e">//必须先绑定program，因为vao不负责着色器程序的切换
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>			&lt;span style="color:#75715e">//va.Bind();
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#66d9ef">if&lt;/span> (r &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">1.0f&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>				increment &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">0.05f&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#66d9ef">else&lt;/span> &lt;span style="color:#a6e22e">if&lt;/span> (r &lt;span style="color:#f92672">&amp;lt;&lt;/span> &lt;span style="color:#ae81ff">0.0f&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>				increment &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0.05f&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			r &lt;span style="color:#f92672">+=&lt;/span> increment;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#75715e">//=======imgui添加============
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>			&lt;span style="color:#75715e">//绘图前重新绑定
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>			shader.Bind();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#75715e">//imgui:这里吧mvp相关代码移到while循环中
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>			&lt;span style="color:#75715e">//向右向上移动200
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>			glm&lt;span style="color:#f92672">::&lt;/span>mat4 model &lt;span style="color:#f92672">=&lt;/span> glm&lt;span style="color:#f92672">::&lt;/span>translate(glm&lt;span style="color:#f92672">::&lt;/span>mat4(&lt;span style="color:#ae81ff">1.0f&lt;/span>), translation);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			glm&lt;span style="color:#f92672">::&lt;/span>mat4 mvp &lt;span style="color:#f92672">=&lt;/span> proj &lt;span style="color:#f92672">*&lt;/span> view &lt;span style="color:#f92672">*&lt;/span> model;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			shader.SetUniformMat4f(&lt;span style="color:#e6db74">&amp;#34;u_MVP&amp;#34;&lt;/span>, mvp);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#75715e">//=======imgui添加============
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>			&lt;span style="color:#75715e">//在u_Color的位置上设置数值
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>			&lt;span style="color:#75715e">//shader.SetUniform4f(&amp;#34;u_Color&amp;#34;, r, 0.3f, 0.0f, 1.0f);
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>			&lt;span style="color:#75715e">//========设置uniform========
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			renderer.Draw(va, ib, shader);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#75715e">//=======imgui添加============
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>			&lt;span style="color:#75715e">//小窗口
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>			{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>				&lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">float&lt;/span> f &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0.0f&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>				&lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> counter &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>				ImGui&lt;span style="color:#f92672">::&lt;/span>Text(&lt;span style="color:#e6db74">&amp;#34;Hello, world!&amp;#34;&lt;/span>); &lt;span style="color:#75715e">// Display some text (you can use a format string too)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>				&lt;span style="color:#75715e">//这里传入translation.x的地址，imgui会在这个地址上修改值
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>				&lt;span style="color:#75715e">//第一个参数是标签，表示在imgui界面上显示的名字，第二个参数是要修改的值的地址，后面是这个值的范围
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>				ImGui&lt;span style="color:#f92672">::&lt;/span>SliderFloat3(&lt;span style="color:#e6db74">&amp;#34;Translation&amp;#34;&lt;/span>, &lt;span style="color:#f92672">&amp;amp;&lt;/span>translation.x, &lt;span style="color:#ae81ff">0.0f&lt;/span>, &lt;span style="color:#ae81ff">960.0f&lt;/span>); &lt;span style="color:#75715e">// Edit 1 float using a slider from 0.0f to 1.0f 
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>				ImGui&lt;span style="color:#f92672">::&lt;/span>SliderFloat(&lt;span style="color:#e6db74">&amp;#34;float&amp;#34;&lt;/span>, &lt;span style="color:#f92672">&amp;amp;&lt;/span>f, &lt;span style="color:#ae81ff">0.0f&lt;/span>, &lt;span style="color:#ae81ff">1.0f&lt;/span>); &lt;span style="color:#75715e">// Edit 1 float using a slider from 0.0f to 1.0f 
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>				ImGui&lt;span style="color:#f92672">::&lt;/span>ColorEdit3(&lt;span style="color:#e6db74">&amp;#34;clear color&amp;#34;&lt;/span>, (&lt;span style="color:#66d9ef">float&lt;/span>&lt;span style="color:#f92672">*&lt;/span>)&lt;span style="color:#f92672">&amp;amp;&lt;/span>clear_color); &lt;span style="color:#75715e">// Edit 3 floats representing a color
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>				ImGui&lt;span style="color:#f92672">::&lt;/span>Checkbox(&lt;span style="color:#e6db74">&amp;#34;Demo Window&amp;#34;&lt;/span>, &lt;span style="color:#f92672">&amp;amp;&lt;/span>show_demo_window); &lt;span style="color:#75715e">// Edit bools storing our windows open/close state
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>				ImGui&lt;span style="color:#f92672">::&lt;/span>Checkbox(&lt;span style="color:#e6db74">&amp;#34;Another Window&amp;#34;&lt;/span>, &lt;span style="color:#f92672">&amp;amp;&lt;/span>show_another_window);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>				&lt;span style="color:#66d9ef">if&lt;/span> (ImGui&lt;span style="color:#f92672">::&lt;/span>Button(&lt;span style="color:#e6db74">&amp;#34;Button&amp;#34;&lt;/span>)) &lt;span style="color:#75715e">// Buttons return true when clicked (NB: most widgets return true when edited/activated)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>					counter&lt;span style="color:#f92672">++&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>				ImGui&lt;span style="color:#f92672">::&lt;/span>SameLine();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>				ImGui&lt;span style="color:#f92672">::&lt;/span>Text(&lt;span style="color:#e6db74">&amp;#34;counter = %d&amp;#34;&lt;/span>, counter);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>				ImGui&lt;span style="color:#f92672">::&lt;/span>Text(&lt;span style="color:#e6db74">&amp;#34;Application average %.3f ms/frame (%.1f FPS)&amp;#34;&lt;/span>, &lt;span style="color:#ae81ff">1000.0f&lt;/span> &lt;span style="color:#f92672">/&lt;/span> ImGui&lt;span style="color:#f92672">::&lt;/span>GetIO().Framerate, ImGui&lt;span style="color:#f92672">::&lt;/span>GetIO().Framerate);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#75715e">//imgui:这里吧mvp相关代码移到while循环中
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>			&lt;span style="color:#75715e">//向右向上移动200
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>			glm&lt;span style="color:#f92672">::&lt;/span>mat4 model &lt;span style="color:#f92672">=&lt;/span> glm&lt;span style="color:#f92672">::&lt;/span>translate(glm&lt;span style="color:#f92672">::&lt;/span>mat4(&lt;span style="color:#ae81ff">1.0f&lt;/span>), translation);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			glm&lt;span style="color:#f92672">::&lt;/span>mat4 mvp &lt;span style="color:#f92672">=&lt;/span> proj &lt;span style="color:#f92672">*&lt;/span> view &lt;span style="color:#f92672">*&lt;/span> model;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			shader.SetUniformMat4f(&lt;span style="color:#e6db74">&amp;#34;u_MVP&amp;#34;&lt;/span>, mvp);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#75715e">//=======imgui添加============
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>			ImGui&lt;span style="color:#f92672">::&lt;/span>Render();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			ImGui_ImplGlfwGL3_RenderDrawData(ImGui&lt;span style="color:#f92672">::&lt;/span>GetDrawData());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#75715e">// 交换前后缓冲区以刷新画面
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>			glfwSwapBuffers(window);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#75715e">// 轮询并处理窗口事件（如键盘输入、关闭动作）
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>			glfwPollEvents();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">//最后解绑vao
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		&lt;span style="color:#75715e">//glBindVertexArray(0);
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//=======imgui添加============
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	ImGui_ImplGlfwGL3_Shutdown();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">// 退出前清理资源
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">//glfwTerminate会破坏Context，导致析构函数中的glGetError()
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">//返回一个OpenGL错误
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	glfwTerminate();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;img src="img/ly-20260505220751808.png" alt="" />&lt;/p></description></item><item><title>21关于模型视图投影矩阵</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/21%E5%85%B3%E4%BA%8E%E6%A8%A1%E5%9E%8B%E8%A7%86%E5%9B%BE%E6%8A%95%E5%BD%B1%E7%9F%A9%E9%98%B5/</link><pubDate>Mon, 27 Apr 2026 22:05:18 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/21%E5%85%B3%E4%BA%8E%E6%A8%A1%E5%9E%8B%E8%A7%86%E5%9B%BE%E6%8A%95%E5%BD%B1%E7%9F%A9%E9%98%B5/</guid><description>&lt;p>第 21 集通常是 Cherno 教程中从“固定画面”转向“动态交互”的关键点，也是将 &lt;mark>M、V、P&lt;/mark> 三者真正融合进代码的时刻。&lt;/p>
&lt;p>按照&lt;mark>程序运行的时间线&lt;/mark>，我们可以把这套复杂的逻辑分为以下阶段：&lt;/p>
&lt;h1 id="1-初始化阶段建立尺子-p">1. 初始化阶段：建立“尺子” (P)&lt;a class="anchor" href="#1-%e5%88%9d%e5%a7%8b%e5%8c%96%e9%98%b6%e6%ae%b5%e5%bb%ba%e7%ab%8b%e5%b0%ba%e5%ad%90-p">#&lt;/a>&lt;/h1>
&lt;p>在程序启动时，你先定义了&lt;mark>投影矩阵 (Projection Matrix)&lt;/mark>。&lt;/p>
&lt;ul>
&lt;li>&lt;mark>动作&lt;/mark>：&lt;code>glm::ortho(0.0f, 960.0f, 0.0f, 540.0f, -1.0f, 1.0f)&lt;/code>。&lt;/li>
&lt;li>&lt;mark>目的&lt;/mark>：建立一套“像素坐标系”。它就像一把尺子，告诉 GPU：“以后我给你 这个数，你就把它当成屏幕的最右边。”&lt;/li>
&lt;li>&lt;mark>状态&lt;/mark>：此时你还没决定看哪，也没决定物体在哪。&lt;/li>
&lt;/ul>
&lt;h1 id="2-空间布局阶段摆放相机与物体-v--m">2. 空间布局阶段：摆放相机与物体 (V &amp;amp; M)&lt;a class="anchor" href="#2-%e7%a9%ba%e9%97%b4%e5%b8%83%e5%b1%80%e9%98%b6%e6%ae%b5%e6%91%86%e6%94%be%e7%9b%b8%e6%9c%ba%e4%b8%8e%e7%89%a9%e4%bd%93-v--m">#&lt;/a>&lt;/h1>
&lt;p>在每一帧渲染开始前，你需要在 CPU 中计算当前的位置。&lt;/p>
&lt;ul>
&lt;li>&lt;mark>视图矩阵 (View Matrix)&lt;/mark>：
&lt;ul>
&lt;li>&lt;mark>动作&lt;/mark>：&lt;code>glm::translate(glm::mat4(1.0f), glm::vec3(x, y, z))&lt;/code>（通常是反向平移）。&lt;/li>
&lt;li>&lt;mark>目的&lt;/mark>：模拟摄像机的移动。如果你想让相机往右移，代码里其实是把整个世界往左拽。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;mark>模型矩阵 (Model Matrix)&lt;/mark>：
&lt;ul>
&lt;li>&lt;mark>动作&lt;/mark>：&lt;code>glm::translate&lt;/code> 或 &lt;code>glm::rotate&lt;/code>。&lt;/li>
&lt;li>&lt;mark>目的&lt;/mark>：决定这个特定的物体（比如那个 logo）在世界里的具体坐标、旋转角度和缩放比例。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="分工">分工&lt;a class="anchor" href="#%e5%88%86%e5%b7%a5">#&lt;/a>&lt;/h2>
&lt;p>&lt;mark>旋转、缩放、平移，这“三兄弟”通常全部都在 M（Model 矩阵）里完成。&lt;/mark>&lt;/p>
&lt;p>虽然在数学上，你可以把它们放进任何矩阵，但在图形学的标准逻辑（即第 21 集所讲的逻辑）中，它们分工非常明确：&lt;/p>
&lt;h3 id="1-m-model-矩阵--物体自己的属性">1. M (Model 矩阵) —— 物体自己的“属性”&lt;a class="anchor" href="#1-m-model-%e7%9f%a9%e9%98%b5--%e7%89%a9%e4%bd%93%e8%87%aa%e5%b7%b1%e7%9a%84%e5%b1%9e%e6%80%a7">#&lt;/a>&lt;/h3>
&lt;p>&lt;mark>适用范围：旋转、缩放、平移（全占了）。&lt;/mark>&lt;/p>
&lt;ul>
&lt;li>&lt;mark>平移 (Translation)&lt;/mark>：你想把方块放到屏幕左边还是右边？&lt;/li>
&lt;li>&lt;mark>旋转 (Rotation)&lt;/mark>：你想让方块站着还是躺着？&lt;/li>
&lt;li>&lt;mark>缩放 (Scaling)&lt;/mark>：你想让方块变大还是变小？&lt;/li>
&lt;/ul>
&lt;p>&lt;mark>核心逻辑&lt;/mark>：Model 矩阵负责定义&lt;mark>物体在世界里的样子&lt;/mark>。一个 logo 无论它怎么转、怎么挪，那都是它作为一个“物体”的行为。&lt;/p></description></item><item><title>20关于投影矩阵</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/20%E5%85%B3%E4%BA%8E%E6%8A%95%E5%BD%B1%E7%9F%A9%E9%98%B5/</link><pubDate>Fri, 24 Apr 2026 16:44:10 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/20%E5%85%B3%E4%BA%8E%E6%8A%95%E5%BD%B1%E7%9F%A9%E9%98%B5/</guid><description>&lt;p>既然已经学到了第 20 集（通常是 &lt;mark>MVP 矩阵&lt;/mark> 的收尾和调试），我们可以把这套复杂的“坐标变换流水线”按时间顺序梳理一遍。&lt;/p>
&lt;p>这不仅是代码的运行顺序，也是你大脑中理解 OpenGL 坐标变换的“心路历程”：&lt;/p>
&lt;h1 id="诞生模型空间-model-space">诞生：模型空间 (Model Space)&lt;a class="anchor" href="#%e8%af%9e%e7%94%9f%e6%a8%a1%e5%9e%8b%e7%a9%ba%e9%97%b4-model-space">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>&lt;mark>时间点&lt;/mark>：你在 &lt;code>main&lt;/code> 函数里定义 &lt;code>float positions[]&lt;/code> 的那一刻。&lt;/li>
&lt;li>&lt;mark>状态&lt;/mark>：此时的坐标是纯粹的几何数据。比如矩形宽 &lt;code>100&lt;/code>，高 &lt;code>100&lt;/code>。&lt;/li>
&lt;li>&lt;mark>你的认知&lt;/mark>：这些数字没有单位，它们只是相对于物体中心（原点）的位置。&lt;/li>
&lt;/ul>
&lt;h1 id="变换世界空间-world-space--m-model-matrix">变换：世界空间 (World Space) —— M (Model Matrix)&lt;a class="anchor" href="#%e5%8f%98%e6%8d%a2%e4%b8%96%e7%95%8c%e7%a9%ba%e9%97%b4-world-space--m-model-matrix">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>&lt;mark>时间点&lt;/mark>：在渲染循环中，当你想要移动、旋转这个矩形时。&lt;/li>
&lt;li>&lt;mark>动作&lt;/mark>：给模型乘以一个 &lt;mark>Model 矩阵&lt;/mark>。&lt;/li>
&lt;li>&lt;mark>结果&lt;/mark>：矩形被“摆放”到了虚拟世界里的某个位置（比如 ）。&lt;/li>
&lt;/ul>
&lt;h1 id="观察摄像机空间-view-space--v-view-matrix">观察：摄像机空间 (View Space) —— V (View Matrix)&lt;a class="anchor" href="#%e8%a7%82%e5%af%9f%e6%91%84%e5%83%8f%e6%9c%ba%e7%a9%ba%e9%97%b4-view-space--v-view-matrix">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>&lt;mark>时间点&lt;/mark>：你想决定从哪个角度看这个世界。&lt;/li>
&lt;li>&lt;mark>动作&lt;/mark>：乘以 &lt;mark>View 矩阵&lt;/mark>（就像把摄像机架好）。&lt;/li>
&lt;li>&lt;mark>结果&lt;/mark>：所有物体的坐标现在变成了“相对于摄像机”的位置。如果摄像机向右移，物体在坐标系中就向左移。&lt;/li>
&lt;/ul>
&lt;h1 id="关键投影变换-projection-space--p-projection-matrix">关键：投影变换 (Projection Space) —— P (Projection Matrix)&lt;a class="anchor" href="#%e5%85%b3%e9%94%ae%e6%8a%95%e5%bd%b1%e5%8f%98%e6%8d%a2-projection-space--p-projection-matrix">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>&lt;mark>时间点&lt;/mark>：&lt;mark>这就是你在第 19-20 集重点攻克的部分。&lt;/mark>&lt;/li>
&lt;li>&lt;mark>动作&lt;/mark>：乘以你定义的 &lt;code>glm::ortho&lt;/code>（正交投影）矩阵。&lt;/li>
&lt;li>&lt;mark>核心逻辑&lt;/mark>：
&lt;ul>
&lt;li>&lt;mark>矩阵的工作&lt;/mark>：它把你在正交矩阵里定义的范围（比如 
 

 
 
 
 
 
 
 &lt;span class="katex">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mn>0&lt;/mn>&lt;mo>∼&lt;/mo>&lt;mn>800&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">0 \sim 800&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>
 ）&lt;mark>强行压缩&lt;/mark>到 
 

 
 
 
 
 
 
 &lt;span class="katex">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mo stretchy="false">[&lt;/mo>&lt;mo>−&lt;/mo>&lt;mn>1.0&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>1.0&lt;/mn>&lt;mo stretchy="false">]&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">[-1.0, 1.0]&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>
 。&lt;/li>
&lt;li>&lt;mark>Shader 执行&lt;/mark>：在 Vertex Shader 里执行 &lt;code>u_MVP * position&lt;/code>。&lt;/li>
&lt;li>&lt;mark>赋值&lt;/mark>：结果存入 &lt;code>gl_Position&lt;/code>。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="正交投影和透视投影">正交投影和透视投影&lt;a class="anchor" href="#%e6%ad%a3%e4%ba%a4%e6%8a%95%e5%bd%b1%e5%92%8c%e9%80%8f%e8%a7%86%e6%8a%95%e5%bd%b1">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>正交投影：渲染UI（用户界面），2D游戏（通常）等&lt;/li>
&lt;li>透视投影：3D，第一视角&lt;/li>
&lt;/ul>
&lt;p>&lt;img src="img/ly-20260426000611720.png" alt="" />&lt;/p></description></item><item><title>19关于数学库</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/19%E5%85%B3%E4%BA%8E%E6%95%B0%E5%AD%A6%E5%BA%93/</link><pubDate>Tue, 07 Apr 2026 23:00:29 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/19%E5%85%B3%E4%BA%8E%E6%95%B0%E5%AD%A6%E5%BA%93/</guid><description>&lt;h1 id="总结">总结&lt;a class="anchor" href="#%e6%80%bb%e7%bb%93">#&lt;/a>&lt;/h1>
&lt;p>引入 &lt;mark>数学库 (Math Library)&lt;/mark>，特别是处理&lt;mark>向量（Vector）和矩阵（Matrix）运算&lt;/mark>。对于图形学来说，数学就是“画笔”，而矩阵就是“坐标转换器”。&lt;/p>
&lt;p>以下是按时间线整理的核心知识点总结：&lt;/p>
&lt;h2 id="为什么需要第三方数学库">为什么需要第三方数学库？&lt;a class="anchor" href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e9%9c%80%e8%a6%81%e7%ac%ac%e4%b8%89%e6%96%b9%e6%95%b0%e5%ad%a6%e5%ba%93">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>&lt;mark>原生局限&lt;/mark>：C++ 标准库没有提供专门针对图形学的向量（如 &lt;code>vec3&lt;/code>）和矩阵（如 &lt;code>mat4&lt;/code>）运算。&lt;/li>
&lt;li>&lt;mark>GLSL 同步&lt;/mark>：我们需要在 CPU 端计算好矩阵，然后传递给 Shader。如果 CPU 端的数学库逻辑（比如数据排布）能与 Shader 中的 GLSL 保持一致，开发效率会极高。&lt;/li>
&lt;li>&lt;mark>GLM 的选择&lt;/mark>：Cherno 选择了 &lt;mark>GLM (OpenGL Mathematics)&lt;/mark>。它是一个 &lt;mark>Header-only&lt;/mark>（仅头文件）的库，设计思路完全模仿 GLSL，且与 OpenGL 兼容性完美。&lt;/li>
&lt;/ul>
&lt;p>&lt;code>https://github.com/g-truc/glm/releases/tag/1.0.3&lt;/code>&lt;/p>
&lt;p>项目属性中设置：&lt;/p>
&lt;p>&lt;img src="img/ly-20260411231545098.png" alt="" />&lt;/p>
&lt;p>之后这里把&lt;code>Texture.cpp&lt;/code>的头文件前几行改为&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;Texture.h&amp;#34;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;Renderer.h&amp;#34;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;iostream&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;stb_image/stb_image.h&amp;#34;&lt;/span>&lt;span style="color:#75715e"> &lt;/span>&lt;span style="color:#75715e">//去除了vendor/
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ol>
&lt;li>把 glm这个文件夹 (ui-右键)&lt;code>include&lt;/code>&lt;/li>
&lt;li>把 &lt;code>glm/detail/glm.cpp&lt;/code> 和 &lt;code>glm/glm.cppm&lt;/code> (ui-右键)&lt;code>exclude project &lt;/code>&lt;/li>
&lt;/ol>
&lt;h2 id="向量与矩阵的基本概念">向量与矩阵的基本概念&lt;a class="anchor" href="#%e5%90%91%e9%87%8f%e4%b8%8e%e7%9f%a9%e9%98%b5%e7%9a%84%e5%9f%ba%e6%9c%ac%e6%a6%82%e5%bf%b5">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>&lt;mark>向量 (Vector)&lt;/mark>：&lt;mark>不仅代表位置，还代表方向&lt;/mark>。在 2D 中是 &lt;code>vec2&lt;/code>，3D 中是 &lt;code>vec3&lt;/code>，带上齐次坐标则是 &lt;code>vec4&lt;/code>。&lt;/li>
&lt;li>&lt;mark>矩阵 (Matrix)&lt;/mark>：图形学中的“魔法方阵”。&lt;code>mat4&lt;/code>（4x4 矩阵）可以同时包含 &lt;mark>旋转 (Rotation)&lt;/mark>、&lt;mark>平移 (Translation)&lt;/mark> 和 &lt;mark>缩放 (Scale)&lt;/mark> 信息。&lt;/li>
&lt;/ul>
&lt;h2 id="正交投影矩阵-orthographic-projection">正交投影矩阵 (Orthographic Projection)&lt;a class="anchor" href="#%e6%ad%a3%e4%ba%a4%e6%8a%95%e5%bd%b1%e7%9f%a9%e9%98%b5-orthographic-projection">#&lt;/a>&lt;/h2>
&lt;p>这是本集的实战重点。Cherno 引入了 &lt;code>glm::ortho&lt;/code> 函数：&lt;/p></description></item><item><title>18关于混合</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/18%E5%85%B3%E4%BA%8E%E6%B7%B7%E5%90%88/</link><pubDate>Mon, 06 Apr 2026 23:21:41 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/18%E5%85%B3%E4%BA%8E%E6%B7%B7%E5%90%88/</guid><description>&lt;p>这一集是关于如何处理==透明度（Alpha Channel）==的关键。当两个物体重叠时，GPU 如何决定最终像素的颜色？这就是“混合”要解决的问题。&lt;/p>
&lt;h1 id="问题的引入为什么我的图片有黑边">问题的引入：为什么我的图片有“黑边”？&lt;a class="anchor" href="#%e9%97%ae%e9%a2%98%e7%9a%84%e5%bc%95%e5%85%a5%e4%b8%ba%e4%bb%80%e4%b9%88%e6%88%91%e7%9a%84%e5%9b%be%e7%89%87%e6%9c%89%e9%bb%91%e8%be%b9">#&lt;/a>&lt;/h1>
&lt;p>在加载带透明通道的 &lt;code>.png&lt;/code> 纹理时，如果不开启混合，透明区域通常会显示为&lt;mark>纯黑色&lt;/mark>或&lt;mark>纯白色&lt;/mark>。这是因为：&lt;/p>
&lt;ul>
&lt;li>默认情况下，OpenGL 只是简单地用新像素 ==覆盖（Overwrite）==旧像素。&lt;/li>
&lt;li>即使你的纹理有 Alpha 值（例如 
 

 
 
 
 
 
 
 &lt;span class="katex">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mn>0.0&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">0.0&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>
 ），如果不告诉 OpenGL 如何处理它，它依然会把这个“透明”的像素颜色画上去。&lt;/li>
&lt;/ul>
&lt;h1 id="开启混合-enable-blending">开启混合 (Enable Blending)&lt;a class="anchor" href="#%e5%bc%80%e5%90%af%e6%b7%b7%e5%90%88-enable-blending">#&lt;/a>&lt;/h1>
&lt;p>OpenGL 是一个状态机，混合功能默认是关闭的。你必须手动开启：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-scss" data-lang="scss">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">GLCall&lt;/span>&lt;span style="color:#f92672">(&lt;/span>&lt;span style="color:#f92672">glEnable&lt;/span>&lt;span style="color:#f92672">(&lt;/span>&lt;span style="color:#f92672">GL_BLEND&lt;/span>&lt;span style="color:#f92672">))&lt;/span>;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>开启后，你需要定义&lt;mark>混合函数（Blend Function）&lt;/mark>，即告诉 OpenGL：“拿新颜色（源）和旧颜色（目标）怎么算？”&lt;/p>
&lt;h1 id="核心公式混合方程式">核心公式：混合方程式&lt;a class="anchor" href="#%e6%a0%b8%e5%bf%83%e5%85%ac%e5%bc%8f%e6%b7%b7%e5%90%88%e6%96%b9%e7%a8%8b%e5%bc%8f">#&lt;/a>&lt;/h1>
&lt;p>这是这一集最硬核的数学部分。OpenGL 计算最终像素颜色的公式如下：&lt;/p>

 
 
 
 
 
 &lt;div class="math-block" style="margin: 1rem 0; overflow-x: auto;">
 
 &lt;span class="katex">&lt;math xmlns="http://www.w3.org/1998/Math/MathML" display="block">&lt;semantics>&lt;mrow>&lt;msub>&lt;mi>C&lt;/mi>&lt;mrow>&lt;mi>r&lt;/mi>&lt;mi>e&lt;/mi>&lt;mi>s&lt;/mi>&lt;mi>u&lt;/mi>&lt;mi>l&lt;/mi>&lt;mi>t&lt;/mi>&lt;/mrow>&lt;/msub>&lt;mo>=&lt;/mo>&lt;mo stretchy="false">(&lt;/mo>&lt;msub>&lt;mi>C&lt;/mi>&lt;mrow>&lt;mi>s&lt;/mi>&lt;mi>r&lt;/mi>&lt;mi>c&lt;/mi>&lt;/mrow>&lt;/msub>&lt;mo>∗&lt;/mo>&lt;msub>&lt;mi>F&lt;/mi>&lt;mrow>&lt;mi>s&lt;/mi>&lt;mi>r&lt;/mi>&lt;mi>c&lt;/mi>&lt;/mrow>&lt;/msub>&lt;mo stretchy="false">)&lt;/mo>&lt;mo>+&lt;/mo>&lt;mo stretchy="false">(&lt;/mo>&lt;msub>&lt;mi>C&lt;/mi>&lt;mrow>&lt;mi>d&lt;/mi>&lt;mi>e&lt;/mi>&lt;mi>s&lt;/mi>&lt;mi>t&lt;/mi>&lt;/mrow>&lt;/msub>&lt;mo>∗&lt;/mo>&lt;msub>&lt;mi>F&lt;/mi>&lt;mrow>&lt;mi>d&lt;/mi>&lt;mi>e&lt;/mi>&lt;mi>s&lt;/mi>&lt;mi>t&lt;/mi>&lt;/mrow>&lt;/msub>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">C_{result} = (C_{src} * F_{src}) + (C_{dest} * F_{dest})&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>
 
 &lt;/div>
&lt;ul>
&lt;li>&lt;mark>(Source Color)&lt;/mark>：即将画上去的颜色（来自 Fragment Shader）。&lt;/li>
&lt;li>&lt;mark>(Source Factor)&lt;/mark>：源颜色的权重。&lt;/li>
&lt;li>&lt;mark>(Destination Color)&lt;/mark>：已经在颜色缓冲区里的颜色（背景色）。&lt;/li>
&lt;li>&lt;mark>(Destination Factor)&lt;/mark>：目标颜色的权重。&lt;/li>
&lt;/ul>
&lt;h1 id="最常用的配置实现透明效果">最常用的配置：实现透明效果&lt;a class="anchor" href="#%e6%9c%80%e5%b8%b8%e7%94%a8%e7%9a%84%e9%85%8d%e7%bd%ae%e5%ae%9e%e7%8e%b0%e9%80%8f%e6%98%8e%e6%95%88%e6%9e%9c">#&lt;/a>&lt;/h1>
&lt;p>为了实现自然的透明（即：新物体的透明度越高，透出的背景越多），Cherno 给出了最经典的配置方案：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-scss" data-lang="scss">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">GLCall&lt;/span>&lt;span style="color:#f92672">(&lt;/span>&lt;span style="color:#f92672">glBlendFunc&lt;/span>&lt;span style="color:#f92672">(&lt;/span>&lt;span style="color:#f92672">GL_SRC_ALPHA&lt;/span>&lt;span style="color:#f92672">,&lt;/span> &lt;span style="color:#f92672">GL_ONE_MINUS_SRC_ALPHA&lt;/span>&lt;span style="color:#f92672">))&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">//&lt;/span>&lt;span style="color:#f92672">函数glBlendFunc第一个参数默认值为&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">：&lt;/span>&lt;span style="color:#f92672">GL_ONE&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">，&lt;/span>&lt;span style="color:#f92672">第二个参数默认值为&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">：&lt;/span>&lt;span style="color:#f92672">GL_ZERO&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;mark>逻辑拆解：&lt;/mark>&lt;/p>
&lt;ol>
&lt;li>
 

 
 
 
 
 
 
 &lt;span class="katex">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mi>F&lt;/mi>&lt;mrow>&lt;mi>s&lt;/mi>&lt;mi>r&lt;/mi>&lt;mi>c&lt;/mi>&lt;/mrow>&lt;/msub>&lt;mo>=&lt;/mo>&lt;mi>G&lt;/mi>&lt;mi>L&lt;/mi>&lt;mi mathvariant="normal">_&lt;/mi>&lt;mi>S&lt;/mi>&lt;mi>R&lt;/mi>&lt;mi>C&lt;/mi>&lt;mi mathvariant="normal">_&lt;/mi>&lt;mi>A&lt;/mi>&lt;mi>L&lt;/mi>&lt;mi>P&lt;/mi>&lt;mi>H&lt;/mi>&lt;mi>A&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">F_{src} = GL\_SRC\_ALPHA&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>
 源颜色的权重是它自己的 
 

 
 
 
 
 
 
 &lt;span class="katex">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>A&lt;/mi>&lt;mi>l&lt;/mi>&lt;mi>p&lt;/mi>&lt;mi>h&lt;/mi>&lt;mi>a&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">Alpha&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>
 值。&lt;/li>
&lt;li>
 

 
 
 
 
 
 
 &lt;span class="katex">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;msub>&lt;mi>F&lt;/mi>&lt;mrow>&lt;mi>d&lt;/mi>&lt;mi>e&lt;/mi>&lt;mi>s&lt;/mi>&lt;mi>t&lt;/mi>&lt;/mrow>&lt;/msub>&lt;mo>=&lt;/mo>&lt;mi>G&lt;/mi>&lt;mi>L&lt;/mi>&lt;mi mathvariant="normal">_&lt;/mi>&lt;mi>O&lt;/mi>&lt;mi>N&lt;/mi>&lt;mi>E&lt;/mi>&lt;mi mathvariant="normal">_&lt;/mi>&lt;mi>M&lt;/mi>&lt;mi>I&lt;/mi>&lt;mi>N&lt;/mi>&lt;mi>U&lt;/mi>&lt;mi>S&lt;/mi>&lt;mi mathvariant="normal">_&lt;/mi>&lt;mi>S&lt;/mi>&lt;mi>R&lt;/mi>&lt;mi>C&lt;/mi>&lt;mi mathvariant="normal">_&lt;/mi>&lt;mi>A&lt;/mi>&lt;mi>L&lt;/mi>&lt;mi>P&lt;/mi>&lt;mi>H&lt;/mi>&lt;mi>A&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">F_{dest} = GL\_ONE\_MINUS\_SRC\_ALPHA&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>
 背景颜色的权重是 
 

 
 
 
 
 
 
 &lt;span class="katex">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mn>1&lt;/mn>&lt;mo>−&lt;/mo>&lt;mi>A&lt;/mi>&lt;mi>l&lt;/mi>&lt;mi>p&lt;/mi>&lt;mi>h&lt;/mi>&lt;mi>a&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">1 - Alpha&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>
 。&lt;/li>
&lt;/ol>
&lt;p>&lt;mark>举个例子：&lt;/mark> 如果你画一个 Alpha 为 
 

 
 
 
 
 
 
 &lt;span class="katex">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mn>0.3&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">0.3&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>
 （ 
 

 
 
 
 
 
 
 &lt;span class="katex">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mn>30&lt;/mn>&lt;mi mathvariant="normal">%&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">30\%&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>
 不透明）的红色方块在黑色背景上：&lt;/p></description></item><item><title>17关于纹理</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/17%E5%85%B3%E4%BA%8E%E7%BA%B9%E7%90%86/</link><pubDate>Sun, 29 Mar 2026 23:43:31 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/17%E5%85%B3%E4%BA%8E%E7%BA%B9%E7%90%86/</guid><description>&lt;p>第 17 集是 OpenGL 系列中视觉效果实现飞跃的一集。在此之前，你只能画出单色的三角形；学完纹理后，你就能把&lt;mark>图片&lt;/mark>贴在你的几何图形上。&lt;/p>
&lt;p>即便你现在还不学习具体的 API，了解其背后的“映射逻辑”对你理解未来的 3D 渲染至关重要。&lt;/p>
&lt;h1 id="简述">简述&lt;a class="anchor" href="#%e7%ae%80%e8%bf%b0">#&lt;/a>&lt;/h1>
&lt;h2 id="纹理的基本原理坐标映射">纹理的基本原理：坐标映射&lt;a class="anchor" href="#%e7%ba%b9%e7%90%86%e7%9a%84%e5%9f%ba%e6%9c%ac%e5%8e%9f%e7%90%86%e5%9d%90%e6%a0%87%e6%98%a0%e5%b0%84">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>&lt;mark>核心概念&lt;/mark>：纹理坐标（UV 坐标）。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;mark>UV 坐标系&lt;/mark>：&lt;/p>
&lt;ul>
&lt;li>它是归一化的，
 

 
 
 
 
 
 
 &lt;span class="katex">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>u&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">u&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>
 和 
 

 
 
 
 
 
 
 &lt;span class="katex">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>v&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">v&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>
 的范围都是 
 

 
 
 
 
 
 
 &lt;span class="katex">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mn>0.0&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">0.0&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>
 到 
 

 
 
 
 
 
 
 &lt;span class="katex">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mn>1.0&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">1.0&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>
 。&lt;/li>
&lt;li>左下角是 
 

 
 
 
 
 
 
 &lt;span class="katex">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mo stretchy="false">(&lt;/mo>&lt;mn>0&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>0&lt;/mn>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">(0, 0)&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>
 ，右上角是 
 

 
 
 
 
 
 
 &lt;span class="katex">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mo stretchy="false">(&lt;/mo>&lt;mn>1&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>1&lt;/mn>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">(1, 1)&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>
 。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;mark>映射逻辑&lt;/mark>：你在顶点数据中多加两个 float（），告诉 GPU：“这个顶点对应图片上的哪个点”。GPU 会自动在顶点之间进行插值，算出中间每个像素该涂什么颜色。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="图像加载与-stb_image">图像加载与 stb_image&lt;a class="anchor" href="#%e5%9b%be%e5%83%8f%e5%8a%a0%e8%bd%bd%e4%b8%8e-stb_image">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>&lt;mark>痛点&lt;/mark>：OpenGL 本身不认识 &lt;code>.png&lt;/code> 或 &lt;code>.jpg&lt;/code> 这种压缩格式。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;mark>解决方案&lt;/mark>：引入一个极简的开源库 &lt;mark>&lt;code>stb_image&lt;/code>&lt;/mark>。&lt;/p>
&lt;ul>
&lt;li>这是一个单头文件库，非常符合 Cherno 的极简风格。&lt;/li>
&lt;li>它的作用是&lt;mark>将磁盘上的图片文件解码成内存中的 RGBA 字节数组（即一串 &lt;code>unsigned char&lt;/code>）&lt;/mark>。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="opengl-纹理对象-texture-object">OpenGL 纹理对象 (Texture Object)&lt;a class="anchor" href="#opengl-%e7%ba%b9%e7%90%86%e5%af%b9%e8%b1%a1-texture-object">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>&lt;mark>创建与绑定&lt;/mark>：&lt;/p>
&lt;ul>
&lt;li>类似于 &lt;code>VertexBuffer&lt;/code>，你需要 &lt;code>glGenTextures&lt;/code> 生成一个 ID，然后 &lt;code>glBindTexture&lt;/code>。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;mark>关键参数设置 (Parameters)&lt;/mark>：&lt;/p>
&lt;ul>
&lt;li>&lt;mark>过滤 (Filtering)&lt;/mark>：当图片被放大或缩小时，像素该怎么补全？（&lt;code>GL_LINEAR&lt;/code> 线性平滑，或 &lt;code>GL_NEAREST&lt;/code> 像素风）。&lt;/li>
&lt;li>&lt;mark>包装 (Wrapping)&lt;/mark>：当 UV 坐标超过 
 

 
 
 
 
 
 
 &lt;span class="katex">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mn>1.0&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">1.0&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>
 时，图片是重复（Repeat）还是拉伸（Clamp）？&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;mark>数据上传&lt;/mark>：使用 &lt;code>glTexImage2D&lt;/code> 将 CPU 内存中的像素数组发送到 GPU 显存。&lt;/p></description></item><item><title>13-16抽象OpenGL成类</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/13-16%E6%8A%BD%E8%B1%A1OpenGL%E6%88%90%E7%B1%BB/</link><pubDate>Wed, 25 Mar 2026 18:01:15 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/13-16%E6%8A%BD%E8%B1%A1OpenGL%E6%88%90%E7%B1%BB/</guid><description>&lt;p>将零散的 OpenGL 原生 API 封装成 &lt;mark>C++ 类&lt;/mark>。这不仅是为了让 &lt;code>main&lt;/code> 函数变干净，更是为了构建一个可复用的&lt;mark>渲染引擎底层&lt;/mark>。&lt;/p>
&lt;h1 id="13-16抽象opengl成类">13-16抽象OpenGL成类&lt;a class="anchor" href="#13-16%e6%8a%bd%e8%b1%a1opengl%e6%88%90%e7%b1%bb">#&lt;/a>&lt;/h1>
&lt;h2 id="为什么要抽象工程思维">为什么要抽象？（工程思维）&lt;a class="anchor" href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e8%a6%81%e6%8a%bd%e8%b1%a1%e5%b7%a5%e7%a8%8b%e6%80%9d%e7%bb%b4">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>&lt;mark>现状&lt;/mark>：&lt;code>main.cpp&lt;/code> 已经膨胀到几百行，充斥着大量的 &lt;code>glGen&lt;/code>、&lt;code>glBind&lt;/code>。&lt;/li>
&lt;li>&lt;mark>目标&lt;/mark>：我们不希望在业务逻辑里看到底层的 &lt;code>unsigned int ID&lt;/code>。我们要操作的是“对象”（Object）。&lt;/li>
&lt;li>&lt;mark>类划分预想&lt;/mark>：
&lt;ul>
&lt;li>&lt;mark>VertexBuffer&lt;/mark>：管理 &lt;code>GL_ARRAY_BUFFER&lt;/code>。&lt;/li>
&lt;li>&lt;mark>IndexBuffer&lt;/mark>：管理 &lt;code>GL_ELEMENT_ARRAY_BUFFER&lt;/code>。&lt;/li>
&lt;li>&lt;mark>VertexArray&lt;/mark>：管理属性布局（Layout）和 VAO。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="封装-vertexbuffer-类">封装 VertexBuffer 类&lt;a class="anchor" href="#%e5%b0%81%e8%a3%85-vertexbuffer-%e7%b1%bb">#&lt;/a>&lt;/h2>
&lt;p>这是最基础的一步。将 VBO 的创建、绑定和销毁封装起来。&lt;/p>
&lt;ul>
&lt;li>&lt;mark>构造函数&lt;/mark>：接收数据和大小，直接 &lt;code>glGen&lt;/code> 并 &lt;code>glBufferData&lt;/code>。&lt;/li>
&lt;li>&lt;mark>析构函数&lt;/mark>：调用 &lt;code>glDeleteBuffers&lt;/code>，实现 &lt;mark>RAII&lt;/mark>（资源获取即初始化）机制，防止内存泄漏。&lt;/li>
&lt;li>&lt;mark>关键方法&lt;/mark>：&lt;code>Bind()&lt;/code> 和 &lt;code>Unbind()&lt;/code>。&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// 你的博客可以展示这个极简结构
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">VertexBuffer&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">private&lt;/span>&lt;span style="color:#f92672">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> m_RendererID; &lt;span style="color:#75715e">// Cherno 喜欢用 m_ 前缀表示成员变量
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">public&lt;/span>&lt;span style="color:#f92672">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> VertexBuffer(&lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span>&lt;span style="color:#f92672">*&lt;/span> data, &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> size);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">~&lt;/span>VertexBuffer();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">Bind&lt;/span>() &lt;span style="color:#66d9ef">const&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">Unbind&lt;/span>() &lt;span style="color:#66d9ef">const&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="封装-indexbuffer-类">封装 IndexBuffer 类&lt;a class="anchor" href="#%e5%b0%81%e8%a3%85-indexbuffer-%e7%b1%bb">#&lt;/a>&lt;/h2>
&lt;p>与 VertexBuffer 几乎一模一样，但有两点不同：&lt;/p>
&lt;ol>
&lt;li>&lt;mark>目标类型&lt;/mark>：固定为 &lt;code>GL_ELEMENT_ARRAY_BUFFER&lt;/code>。&lt;/li>
&lt;li>&lt;mark>计数器&lt;/mark>：增加一个 &lt;code>m_Count&lt;/code> 变量，记录有多少个索引（Indices），因为 &lt;code>glDrawElements&lt;/code> 绘图时需要这个数字。&lt;/li>
&lt;/ol>
&lt;h1 id="代码">代码&lt;a class="anchor" href="#%e4%bb%a3%e7%a0%81">#&lt;/a>&lt;/h1>
&lt;h2 id="render-渲染器">Render 渲染器&lt;a class="anchor" href="#render-%e6%b8%b2%e6%9f%93%e5%99%a8">#&lt;/a>&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//Renderer.h
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//编译器第一次遇到这个文件，正常读取内容。
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//编译器会记下这个文件的物理路径。
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//当后续代码再次尝试 #include 这个路径的文件时，编译器直接跳过，不再读取。
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#pragma once
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;GL/glew.h&amp;gt;&lt;/span>&lt;span style="color:#75715e"> 
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;VertexArray.h&amp;#34;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;IndexBuffer.h&amp;#34;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;Shader.h&amp;#34;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//_debugbreak() 是msvc特有的
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//__FILE__和__LINE__ 是所有编译器都支持的
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//#x：字符串化操作符。它会将你传入的代码直接转成字符串
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//__FILE__ 和 __LINE__：编译器内置宏，自动获取当前代码所在的文件名和行号
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#define ASSERT(x) if(!(x)) __debugbreak();
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#define GLCall(x) GLClearError();\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">	x;\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">	ASSERT(GLLogCall(#x,__FILE__,__LINE__));
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">GLClearError&lt;/span>();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">bool&lt;/span> &lt;span style="color:#a6e22e">GLLogCall&lt;/span>(&lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#66d9ef">char&lt;/span>&lt;span style="color:#f92672">*&lt;/span> function,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#66d9ef">char&lt;/span>&lt;span style="color:#f92672">*&lt;/span> file, &lt;span style="color:#66d9ef">int&lt;/span> line);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">Renderer&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span>&lt;span style="color:#f92672">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">void&lt;/span> Clear() &lt;span style="color:#66d9ef">const&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">Draw&lt;/span>(&lt;span style="color:#66d9ef">const&lt;/span> VertexArray&lt;span style="color:#f92672">&amp;amp;&lt;/span> va, &lt;span style="color:#66d9ef">const&lt;/span> IndexBuffer&lt;span style="color:#f92672">&amp;amp;&lt;/span> ib, &lt;span style="color:#66d9ef">const&lt;/span> Shader&lt;span style="color:#f92672">&amp;amp;&lt;/span> shader) &lt;span style="color:#66d9ef">const&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//Renderer.cpp
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;Renderer.h&amp;#34;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;iostream&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">GLClearError&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//直到不是GL_NO_ERROR，就退出while循环
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#66d9ef">while&lt;/span> (glGetError() &lt;span style="color:#f92672">!=&lt;/span> GL_NO_ERROR);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">bool&lt;/span> &lt;span style="color:#a6e22e">GLLogCall&lt;/span>(&lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#66d9ef">char&lt;/span>&lt;span style="color:#f92672">*&lt;/span> function,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#66d9ef">char&lt;/span>&lt;span style="color:#f92672">*&lt;/span> file, &lt;span style="color:#66d9ef">int&lt;/span> line)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">while&lt;/span> (GLenum error &lt;span style="color:#f92672">=&lt;/span> glGetError())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		std&lt;span style="color:#f92672">::&lt;/span>cout &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;[OpenGL Error](&amp;#34;&lt;/span> &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> error &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;): &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> function &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> &lt;span style="color:#e6db74">&amp;#34; &amp;#34;&lt;/span> &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> file &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;:&amp;#34;&lt;/span> &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> line &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>endl;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#66d9ef">return&lt;/span> false;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">return&lt;/span> true;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> Renderer&lt;span style="color:#f92672">::&lt;/span>Clear() &lt;span style="color:#66d9ef">const&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	GLCall(glClear(GL_COLOR_BUFFER_BIT));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> Renderer&lt;span style="color:#f92672">::&lt;/span>Draw(&lt;span style="color:#66d9ef">const&lt;/span> VertexArray&lt;span style="color:#f92672">&amp;amp;&lt;/span> va, &lt;span style="color:#66d9ef">const&lt;/span> IndexBuffer&lt;span style="color:#f92672">&amp;amp;&lt;/span> ib, &lt;span style="color:#66d9ef">const&lt;/span> Shader&lt;span style="color:#f92672">&amp;amp;&lt;/span> shader) &lt;span style="color:#66d9ef">const&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//绘图前重新绑定
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	shader.Bind();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//必须先绑定program，因为vao不负责着色器程序的切换
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	va.Bind();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//这个可以不用，已经在va中录制了
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">//ib.Bind();
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	GLCall(glDrawElements(GL_TRIANGLES, ib.GetCount(), GL_UNSIGNED_INT, &lt;span style="color:#66d9ef">nullptr&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//绘制后经常不需要再解绑，因为这会消耗性能，而且
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">//我们经常会在下一帧继续使用它们并覆盖 
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="vertextbuffer-顶点缓冲区">VertextBuffer 顶点缓冲区&lt;a class="anchor" href="#vertextbuffer-%e9%a1%b6%e7%82%b9%e7%bc%93%e5%86%b2%e5%8c%ba">#&lt;/a>&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//VertextBuffer.h
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#pragma once
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//专门处理顶点缓冲区的生成、绑定、解绑及内存释放
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">VertexBuffer&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">private&lt;/span>&lt;span style="color:#f92672">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//一个相关的渲染器id
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> m_RendererID;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span>&lt;span style="color:#f92672">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//size：大小
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	VertexBuffer(&lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span>&lt;span style="color:#f92672">*&lt;/span> data, &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> size);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#f92672">~&lt;/span>VertexBuffer();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">Bind&lt;/span>() &lt;span style="color:#66d9ef">const&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">Unbind&lt;/span>() &lt;span style="color:#66d9ef">const&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//VertexBuffer.cpp
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;VertexBuffer.h&amp;#34;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;Render.h&amp;#34;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>VertexBuffer&lt;span style="color:#f92672">::&lt;/span>VertexBuffer(&lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span>&lt;span style="color:#f92672">*&lt;/span> data, &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> size)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{ 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">// 生成一个缓冲区 ID
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	GLCall(glGenBuffers(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#f92672">&amp;amp;&lt;/span>m_RendererID));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">// 绑定该 ID 到顶点缓冲区插槽
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	GLCall(glBindBuffer(GL_ARRAY_BUFFER, m_RendererID));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">// 将顶点数据从 CPU 内存拷贝到 GPU 显存，设为静态读取模式
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	GLCall(glBufferData(GL_ARRAY_BUFFER, size,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		data, GL_STATIC_DRAW));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>VertexBuffer&lt;span style="color:#f92672">::~&lt;/span>VertexBuffer()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	GLCall(glDeleteBuffers(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#f92672">&amp;amp;&lt;/span>m_RendererID));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> VertexBuffer&lt;span style="color:#f92672">::&lt;/span>Bind() &lt;span style="color:#66d9ef">const&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">// 绑定该 ID 到顶点缓冲区插槽
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	GLCall(glBindBuffer(GL_ARRAY_BUFFER, m_RendererID));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> VertexBuffer&lt;span style="color:#f92672">::&lt;/span>Unbind() &lt;span style="color:#66d9ef">const&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">// 绑定该 ID 到顶点缓冲区插槽
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	GLCall(glBindBuffer(GL_ARRAY_BUFFER, &lt;span style="color:#ae81ff">0&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="indexbuffer-索引缓冲区">IndexBuffer 索引缓冲区&lt;a class="anchor" href="#indexbuffer-%e7%b4%a2%e5%bc%95%e7%bc%93%e5%86%b2%e5%8c%ba">#&lt;/a>&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//IndexBuffer.h
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#pragma once
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//专门处理索引缓冲区的生成、绑定、解绑及内存释放
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//索引缓冲区：可以用来只绘制某个面（如果顶点缓冲区
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//包括了很多顶点的话）
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">IndexBuffer&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">private&lt;/span>&lt;span style="color:#f92672">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//一个相关的渲染器id
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> m_RendererID;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> m_Count;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span>&lt;span style="color:#f92672">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//count：个数
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	IndexBuffer(&lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span>&lt;span style="color:#f92672">*&lt;/span> data, &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> count);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#f92672">~&lt;/span>IndexBuffer();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">Bind&lt;/span>() &lt;span style="color:#66d9ef">const&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">Unbind&lt;/span>() &lt;span style="color:#66d9ef">const&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">inline&lt;/span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">GetCount&lt;/span>() &lt;span style="color:#66d9ef">const&lt;/span> { &lt;span style="color:#66d9ef">return&lt;/span> m_Count; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//IndexBuffer.cpp
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;IndexBuffer.h&amp;#34;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;Render.h&amp;#34;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>IndexBuffer&lt;span style="color:#f92672">::&lt;/span>IndexBuffer(&lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span>&lt;span style="color:#f92672">*&lt;/span> data, &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> count)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#f92672">:&lt;/span>m_Count(count)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{ 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	ASSERT(&lt;span style="color:#66d9ef">sizeof&lt;/span>(&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span>) &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#66d9ef">sizeof&lt;/span>(GLuint));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">// 生成一个缓冲区 ID
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	GLCall(glGenBuffers(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#f92672">&amp;amp;&lt;/span>m_RendererID));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">// 绑定该 ID 到元素数组缓冲区 (Element Array Buffer)插槽，
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">//也称索引缓冲区对象
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	GLCall(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_RendererID));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">// 将顶点数据从 CPU 内存拷贝到 GPU 显存，设为静态读取模式
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	GLCall(glBufferData(GL_ELEMENT_ARRAY_BUFFER, count &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#66d9ef">sizeof&lt;/span>(&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		data, GL_STATIC_DRAW));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>IndexBuffer&lt;span style="color:#f92672">::~&lt;/span>IndexBuffer()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	GLCall(glDeleteBuffers(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#f92672">&amp;amp;&lt;/span>m_RendererID));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> IndexBuffer&lt;span style="color:#f92672">::&lt;/span>Bind() &lt;span style="color:#66d9ef">const&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">// 绑定该 ID 到元素数组缓冲区插槽
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	GLCall(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_RendererID));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> IndexBuffer&lt;span style="color:#f92672">::&lt;/span>Unbind() &lt;span style="color:#66d9ef">const&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">// 绑定该 ID 到元素数组缓冲区插槽
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	GLCall(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, &lt;span style="color:#ae81ff">0&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="vertexbufferlayout-顶点缓冲区布局设置">VertexBufferLayout 顶点缓冲区布局设置&lt;a class="anchor" href="#vertexbufferlayout-%e9%a1%b6%e7%82%b9%e7%bc%93%e5%86%b2%e5%8c%ba%e5%b8%83%e5%b1%80%e8%ae%be%e7%bd%ae">#&lt;/a>&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//VertexBufferLayout.h
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#pragma once
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;vector&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;GL/glew.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;Renderer.h&amp;#34;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">struct&lt;/span> &lt;span style="color:#a6e22e">VertexBufferElement&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> type;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">int&lt;/span> count;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">char&lt;/span> normalized;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">GetSizeOfType&lt;/span>(&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> type)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#66d9ef">switch&lt;/span> (type)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#66d9ef">case&lt;/span> GL_FLOAT: &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#ae81ff">4&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#66d9ef">case&lt;/span> GL_UNSIGNED_INT: &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#ae81ff">4&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#66d9ef">case&lt;/span> GL_UNSIGNED_BYTE: &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		ASSERT(false);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">VertexBufferLayout&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">private&lt;/span>&lt;span style="color:#f92672">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	std&lt;span style="color:#f92672">::&lt;/span>vector&lt;span style="color:#f92672">&amp;lt;&lt;/span>VertexBufferElement&lt;span style="color:#f92672">&amp;gt;&lt;/span> m_Elements;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> m_Stride;&lt;span style="color:#75715e">//步长
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#f92672">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	VertexBufferLayout()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#f92672">:&lt;/span>m_Stride(&lt;span style="color:#ae81ff">0&lt;/span>) {}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//解释一下，第n个push的就是第n组属性的布局
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#66d9ef">template&lt;/span>&lt;span style="color:#f92672">&amp;lt;&lt;/span>&lt;span style="color:#66d9ef">typename&lt;/span> T&lt;span style="color:#f92672">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">void&lt;/span> Push(&lt;span style="color:#66d9ef">int&lt;/span> cout)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#66d9ef">static_assert&lt;/span>(false);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//位置 ($x, y, z$)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#66d9ef">template&lt;/span>&lt;span style="color:#f92672">&amp;lt;&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">void&lt;/span> Push&lt;span style="color:#f92672">&amp;lt;&lt;/span>&lt;span style="color:#66d9ef">float&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>(&lt;span style="color:#66d9ef">int&lt;/span> count)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		m_Elements.push_back({ GL_FLOAT,count,GL_FALSE });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		m_Stride &lt;span style="color:#f92672">+=&lt;/span> count &lt;span style="color:#f92672">*&lt;/span> VertexBufferElement&lt;span style="color:#f92672">::&lt;/span>GetSizeOfType(GL_FLOAT);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//ID、索引
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#66d9ef">template&lt;/span>&lt;span style="color:#f92672">&amp;lt;&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">void&lt;/span> Push&lt;span style="color:#f92672">&amp;lt;&lt;/span>&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#f92672">&amp;gt;&lt;/span>(&lt;span style="color:#66d9ef">int&lt;/span> count)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		m_Elements.push_back({ GL_UNSIGNED_INT,count,GL_FALSE });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		m_Stride &lt;span style="color:#f92672">+=&lt;/span> count &lt;span style="color:#f92672">*&lt;/span> VertexBufferElement&lt;span style="color:#f92672">::&lt;/span>GetSizeOfType(GL_UNSIGNED_INT);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//颜色 (R, G, B, A)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">//如果你设置 GL_TRUE (归一化)：OpenGL 会自动帮你做除法。比如你传 255，Shader 拿到的是 $255 / 255 = 1.0$；你传 128，Shader 拿到的是 $0.5$
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#66d9ef">template&lt;/span>&lt;span style="color:#f92672">&amp;lt;&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">void&lt;/span> Push&lt;span style="color:#f92672">&amp;lt;&lt;/span>&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">char&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>(&lt;span style="color:#66d9ef">int&lt;/span> count)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		m_Elements.push_back({ GL_UNSIGNED_BYTE,count,GL_TRUE });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		m_Stride &lt;span style="color:#f92672">+=&lt;/span> count &lt;span style="color:#f92672">*&lt;/span> VertexBufferElement&lt;span style="color:#f92672">::&lt;/span>GetSizeOfType(GL_UNSIGNED_BYTE);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">inline&lt;/span> &lt;span style="color:#66d9ef">const&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>vector&lt;span style="color:#f92672">&amp;lt;&lt;/span>VertexBufferElement&lt;span style="color:#f92672">&amp;gt;&lt;/span> GetElements() &lt;span style="color:#66d9ef">const&lt;/span> { &lt;span style="color:#66d9ef">return&lt;/span> m_Elements; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">inline&lt;/span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">GetStrde&lt;/span>() &lt;span style="color:#66d9ef">const&lt;/span> { &lt;span style="color:#66d9ef">return&lt;/span> m_Stride; }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="vertexarray-顶点数组属性绑定">VertexArray 顶点数组（属性）绑定&lt;a class="anchor" href="#vertexarray-%e9%a1%b6%e7%82%b9%e6%95%b0%e7%bb%84%e5%b1%9e%e6%80%a7%e7%bb%91%e5%ae%9a">#&lt;/a>&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//VertexArray.h
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#pragma once
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;VertexBuffer.h&amp;#34;&lt;/span>&lt;span style="color:#75715e"> 
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">//#include &amp;#34;VertexBufferLayout.h&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//它告诉编译器：“哥们，后面会有一个类叫 VertexBufferLayout，我现在先提一嘴，你先别管它长啥样，只要知道它是个‘类’就行。”
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">VertexBufferLayout&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">VertexArray&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">private&lt;/span>&lt;span style="color:#f92672">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> m_RendererID;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span>&lt;span style="color:#f92672">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	VertexArray();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#f92672">~&lt;/span>VertexArray();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">AddBuffer&lt;/span>(&lt;span style="color:#66d9ef">const&lt;/span> VertexBuffer&lt;span style="color:#f92672">&amp;amp;&lt;/span> vb, &lt;span style="color:#66d9ef">const&lt;/span> VertexBufferLayout&lt;span style="color:#f92672">&amp;amp;&lt;/span> layout);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">Bind&lt;/span>() &lt;span style="color:#66d9ef">const&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">Unbind&lt;/span>() &lt;span style="color:#66d9ef">const&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//VertexArray.cpp
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;VertexArray.h&amp;#34;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">//这里要真正调用 layout.GetElements()。这时候编译器必须看到具体的类定义，才能知道 GetElements() 函数长什么样。
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;VertexBufferLayout.h&amp;#34;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>VertexArray&lt;span style="color:#f92672">::&lt;/span>VertexArray()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{ 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	glGenVertexArrays(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#f92672">&amp;amp;&lt;/span>m_RendererID);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>VertexArray&lt;span style="color:#f92672">::~&lt;/span>VertexArray()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	glDeleteVertexArrays(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#f92672">&amp;amp;&lt;/span>m_RendererID);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> VertexArray&lt;span style="color:#f92672">::&lt;/span>AddBuffer(&lt;span style="color:#66d9ef">const&lt;/span> VertexBuffer&lt;span style="color:#f92672">&amp;amp;&lt;/span> vb, &lt;span style="color:#66d9ef">const&lt;/span> VertexBufferLayout&lt;span style="color:#f92672">&amp;amp;&lt;/span> layout)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	Bind();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	vb.Bind();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#66d9ef">auto&lt;/span>&lt;span style="color:#f92672">&amp;amp;&lt;/span> elements &lt;span style="color:#f92672">=&lt;/span> layout.GetElements();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> offset &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">for&lt;/span> (&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> i&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>;i&lt;span style="color:#f92672">&amp;lt;&lt;/span>elements.size();i&lt;span style="color:#f92672">++&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#66d9ef">auto&lt;/span>&lt;span style="color:#f92672">&amp;amp;&lt;/span> element &lt;span style="color:#f92672">=&lt;/span> elements[i];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">// 启用索引为 i 的(顶点)属性 
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		glEnableVertexAttribArray(i);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">//1. 打标签：它把当前 GL_ARRAY_BUFFER 里的数据流，贴上了“i号”的标签。2. 定规则：它告诉 GPU，当你（Shader）想要 location = 0 的数据时，请按照“每 count 个 float 为一组”的规则去缓存里抓取。
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		glVertexAttribPointer(i, element.count, element.type, element.normalized, layout.GetStrde(), (&lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span>&lt;span style="color:#f92672">*&lt;/span>)offset);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		offset &lt;span style="color:#f92672">+=&lt;/span> element.count &lt;span style="color:#f92672">*&lt;/span> VertexBufferElement&lt;span style="color:#f92672">::&lt;/span>GetSizeOfType(element.type);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> VertexArray&lt;span style="color:#f92672">::&lt;/span>Bind() &lt;span style="color:#66d9ef">const&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	glBindVertexArray(m_RendererID);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> VertexArray&lt;span style="color:#f92672">::&lt;/span>Unbind() &lt;span style="color:#66d9ef">const&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	glBindVertexArray(&lt;span style="color:#ae81ff">0&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//这里是否要禁用呢，但是视频里面没有存count，所以也不知道总共有几个
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">/*for (unsigned int i = 0; i &amp;lt; elements.size(); i++)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">	{
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">		glDisableVertexAttribArray(i);
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">	}*/&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="shader着色器">Shader着色器&lt;a class="anchor" href="#shader%e7%9d%80%e8%89%b2%e5%99%a8">#&lt;/a>&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//Shader.h
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#pragma once
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;string&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;unordered_map&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">struct&lt;/span> &lt;span style="color:#a6e22e">ShaderProgramSource&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	std&lt;span style="color:#f92672">::&lt;/span>string VertexSource;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	std&lt;span style="color:#f92672">::&lt;/span>string FragmentSource;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">Shader&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">private&lt;/span>&lt;span style="color:#f92672">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	std&lt;span style="color:#f92672">::&lt;/span>string m_FilePath;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> m_RendererID;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	std&lt;span style="color:#f92672">::&lt;/span>unordered_map&lt;span style="color:#f92672">&amp;lt;&lt;/span>std&lt;span style="color:#f92672">::&lt;/span>string, &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span> m_UniformLocationCache;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">// caching for uniforms
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">public&lt;/span>&lt;span style="color:#f92672">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	Shader(&lt;span style="color:#66d9ef">const&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>string&lt;span style="color:#f92672">&amp;amp;&lt;/span> filepath);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#f92672">~&lt;/span>Shader();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">Bind&lt;/span>() &lt;span style="color:#66d9ef">const&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">Unbind&lt;/span>() &lt;span style="color:#66d9ef">const&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">SetUniform4f&lt;/span>(&lt;span style="color:#66d9ef">const&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>string&lt;span style="color:#f92672">&amp;amp;&lt;/span> name, &lt;span style="color:#66d9ef">float&lt;/span> v0, &lt;span style="color:#66d9ef">float&lt;/span> v1,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#66d9ef">float&lt;/span> v2, &lt;span style="color:#66d9ef">float&lt;/span> v3);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">private&lt;/span>&lt;span style="color:#f92672">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">bool&lt;/span> CompileShader();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">GetUniformLocation&lt;/span>(&lt;span style="color:#66d9ef">const&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>string&lt;span style="color:#f92672">&amp;amp;&lt;/span> name);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	ShaderProgramSource &lt;span style="color:#a6e22e">ParseShader&lt;/span>(&lt;span style="color:#66d9ef">const&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>string&lt;span style="color:#f92672">&amp;amp;&lt;/span> filepath);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">CompileShader&lt;/span>(&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> type,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#66d9ef">const&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>string&lt;span style="color:#f92672">&amp;amp;&lt;/span> source);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//创建着色器程序对象：编译并链接顶点着色器和片段着色器
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">CreateShader&lt;/span>(&lt;span style="color:#66d9ef">const&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>string&lt;span style="color:#f92672">&amp;amp;&lt;/span> vertexShader,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#66d9ef">const&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>string&lt;span style="color:#f92672">&amp;amp;&lt;/span> fragmentShader);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//Shader.cpp
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#pragma once
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;Shader.h&amp;#34;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;iostream&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;fstream&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;sstream&amp;gt;&lt;/span>&lt;span style="color:#75715e"> 
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;Render.h&amp;#34;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Shader&lt;span style="color:#f92672">::&lt;/span>Shader(&lt;span style="color:#66d9ef">const&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>string&lt;span style="color:#f92672">&amp;amp;&lt;/span> filepath)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#f92672">:&lt;/span>m_FilePath(filepath),m_RendererID(&lt;span style="color:#ae81ff">0&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	ShaderProgramSource source &lt;span style="color:#f92672">=&lt;/span> ParseShader(filepath);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	m_RendererID &lt;span style="color:#f92672">=&lt;/span> CreateShader(source.VertexSource, source.FragmentSource);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Shader&lt;span style="color:#f92672">::~&lt;/span>Shader()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	glDeleteProgram(m_RendererID);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> Shader&lt;span style="color:#f92672">::&lt;/span>Bind() &lt;span style="color:#66d9ef">const&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	glUseProgram(m_RendererID);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> Shader&lt;span style="color:#f92672">::&lt;/span>Unbind() &lt;span style="color:#66d9ef">const&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		glUseProgram(&lt;span style="color:#ae81ff">0&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ShaderProgramSource Shader&lt;span style="color:#f92672">::&lt;/span>ParseShader(&lt;span style="color:#66d9ef">const&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>string&lt;span style="color:#f92672">&amp;amp;&lt;/span> filepath)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">enum&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">ShaderType&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		NONE &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>, VERTEX &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>, FRAGMENT &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//input file stream
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	std&lt;span style="color:#f92672">::&lt;/span>ifstream stream(filepath);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	std&lt;span style="color:#f92672">::&lt;/span>string line;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	std&lt;span style="color:#f92672">::&lt;/span>stringstream ss[&lt;span style="color:#ae81ff">2&lt;/span>];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	ShaderType type &lt;span style="color:#f92672">=&lt;/span> ShaderType&lt;span style="color:#f92672">::&lt;/span>NONE;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">while&lt;/span> (getline(stream, line))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#66d9ef">if&lt;/span> (line.find(&lt;span style="color:#e6db74">&amp;#34;#shader&amp;#34;&lt;/span>) &lt;span style="color:#f92672">!=&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>string&lt;span style="color:#f92672">::&lt;/span>npos)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#66d9ef">if&lt;/span> (line.find(&lt;span style="color:#e6db74">&amp;#34;vertex&amp;#34;&lt;/span>) &lt;span style="color:#f92672">!=&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>string&lt;span style="color:#f92672">::&lt;/span>npos)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>				type &lt;span style="color:#f92672">=&lt;/span> ShaderType&lt;span style="color:#f92672">::&lt;/span>VERTEX;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#66d9ef">else&lt;/span> &lt;span style="color:#a6e22e">if&lt;/span> (line.find(&lt;span style="color:#e6db74">&amp;#34;fragment&amp;#34;&lt;/span>) &lt;span style="color:#f92672">!=&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>string&lt;span style="color:#f92672">::&lt;/span>npos)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>				type &lt;span style="color:#f92672">=&lt;/span> ShaderType&lt;span style="color:#f92672">::&lt;/span>FRAGMENT;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#66d9ef">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			ss[(&lt;span style="color:#66d9ef">int&lt;/span>)type] &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> line &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> &lt;span style="color:#e6db74">&amp;#39;\n&amp;#39;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">return&lt;/span> { ss[&lt;span style="color:#ae81ff">0&lt;/span>].str(),ss[&lt;span style="color:#ae81ff">1&lt;/span>].str() };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//GLenum也就是unsigned int,这里不用GLenum是为了解耦
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> Shader&lt;span style="color:#f92672">::&lt;/span>CompileShader(&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> type,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">const&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>string&lt;span style="color:#f92672">&amp;amp;&lt;/span> source)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//创建着色器对象，向 OpenGL 申请一个空的“容器”来存放你的代码
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">//把光标放在 glCreateShader( 的左括号后面，然后按下 Ctrl + Shift + Space。会弹出一个小黑框，显示：GLuint glCreateShader(GLenum type)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">//到glew.h搜索，发现typedef unsigned int GLenum;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> id &lt;span style="color:#f92672">=&lt;/span> glCreateShader(type);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//返回一个指向该字符串首地址的只读指针（const char*）
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#66d9ef">char&lt;/span>&lt;span style="color:#f92672">*&lt;/span> src &lt;span style="color:#f92672">=&lt;/span> source.c_str();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//有了容器后，你需要把写好的字符串代码塞进去（拷贝）
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">//3种用法
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">//1. glShaderSource(id, 1, &amp;amp;src, nullptr);	只有一个字符串，且它是以 \0 结尾的。
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">//2. glShaderSource(id, 1, &amp;amp;src, &amp;amp;len);	只有一个字符串，长度由 len 指定。
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">//3. glShaderSource(id, 2, strings, lengths);	有两个字符串片段（数组形式），长度分别由 lengths[0] 和 lengths[1] 指定。
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	glShaderSource(id, &lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#f92672">&amp;amp;&lt;/span>src, &lt;span style="color:#66d9ef">nullptr&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//将你的 GLSL 代码翻译成显卡能理解的机器指令
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	glCompileShader(id);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//错误处理
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#66d9ef">int&lt;/span> result;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">// 查询编译状态：询问 OpenGL 这个 shader 编译成功了吗？
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">// GL_COMPILE_STATUS 会把结果存入 result 中（成功为 GL_TRUE，失败为 GL_FALSE）
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	glGetShaderiv(id, GL_COMPILE_STATUS, &lt;span style="color:#f92672">&amp;amp;&lt;/span>result);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">if&lt;/span> (result &lt;span style="color:#f92672">==&lt;/span> GL_FALSE)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#66d9ef">int&lt;/span> length;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">//获取错误日志长度：如果编译失败，先问一下错误信息一共有多少个字符
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		glGetShaderiv(id, GL_INFO_LOG_LENGTH, &lt;span style="color:#f92672">&amp;amp;&lt;/span>length);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">//在栈上申请内存
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		&lt;span style="color:#75715e">//如果申请的内存太大，alloca会导致栈溢出（1MB）
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		&lt;span style="color:#66d9ef">char&lt;/span>&lt;span style="color:#f92672">*&lt;/span> message &lt;span style="color:#f92672">=&lt;/span> (&lt;span style="color:#66d9ef">char&lt;/span>&lt;span style="color:#f92672">*&lt;/span>)alloca(length &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#66d9ef">sizeof&lt;/span>(&lt;span style="color:#66d9ef">char&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">//提取错误信息：把显卡驱动里的具体报错文字拷贝到我们刚才申请的 message 内存中
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		glGetShaderInfoLog(id, length, &lt;span style="color:#f92672">&amp;amp;&lt;/span>length, message);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		std&lt;span style="color:#f92672">::&lt;/span>cout &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Failed to compile &amp;#34;&lt;/span> &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> ((type &lt;span style="color:#f92672">==&lt;/span> GL_VERTEX_SHADER) &lt;span style="color:#f92672">?&lt;/span> &lt;span style="color:#e6db74">&amp;#34;vertex&amp;#34;&lt;/span> &lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#e6db74">&amp;#34;fragment&amp;#34;&lt;/span>) &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> &lt;span style="color:#e6db74">&amp;#34; shader!&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		std&lt;span style="color:#f92672">::&lt;/span>cout &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> message &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>endl;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">//清理：编译失败了，这个 shader 对象也就没用了，删掉它防止内存泄漏
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		glDeleteShader(id);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">return&lt;/span> id;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> Shader&lt;span style="color:#f92672">::&lt;/span>CreateShader(&lt;span style="color:#66d9ef">const&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>string&lt;span style="color:#f92672">&amp;amp;&lt;/span> vertexShader,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">const&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>string&lt;span style="color:#f92672">&amp;amp;&lt;/span> fragmentShader)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//在 GPU 中申请一个空的“程序对象”
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> program &lt;span style="color:#f92672">=&lt;/span> glCreateProgram();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//编译顶点着色器
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> vs &lt;span style="color:#f92672">=&lt;/span> CompileShader(GL_VERTEX_SHADER,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		vertexShader);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//编译片段着色器
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> fs &lt;span style="color:#f92672">=&lt;/span> CompileShader(GL_FRAGMENT_SHADER,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		fragmentShader);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//把已经编译好的顶点着色器和片段着色器附加到程序对象中
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	glAttachShader(program, vs);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	glAttachShader(program, fs);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//链接
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	glLinkProgram(program);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//验证当前的程序是否能在当前的 OpenGL 状态下执行（
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">// 检查顶点着色器的输出是否与片段着色器的输入匹配，并
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">// 生成最终的可执行二进制代码。）
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	glValidateProgram(program);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//删除临时的中间产物（类似编译完 C++ 后删除 .obj
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	glDeleteShader(vs);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	glDeleteShader(fs);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">return&lt;/span> program;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> Shader&lt;span style="color:#f92672">::&lt;/span>SetUniform4f(&lt;span style="color:#66d9ef">const&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>string&lt;span style="color:#f92672">&amp;amp;&lt;/span> name, &lt;span style="color:#66d9ef">float&lt;/span> v0, &lt;span style="color:#66d9ef">float&lt;/span> v1, &lt;span style="color:#66d9ef">float&lt;/span> v2, &lt;span style="color:#66d9ef">float&lt;/span> v3)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	glUniform4f(GetUniformLocation(name), v0, v1, v2, v3);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> Shader&lt;span style="color:#f92672">::&lt;/span>GetUniformLocation(&lt;span style="color:#66d9ef">const&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>string&lt;span style="color:#f92672">&amp;amp;&lt;/span> name)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">if&lt;/span> (m_UniformLocationCache.find(name) &lt;span style="color:#f92672">!=&lt;/span> m_UniformLocationCache.end())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#66d9ef">return&lt;/span> m_UniformLocationCache[name];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> location&lt;span style="color:#f92672">=&lt;/span>glGetUniformLocation(m_RendererID, name.c_str());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">if&lt;/span> (location &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		std&lt;span style="color:#f92672">::&lt;/span>cout &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Warning: uniform &amp;#39;&amp;#34;&lt;/span> &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> name &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&amp;#39; doesn&amp;#39;t exist!&amp;#34;&lt;/span> &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>endl;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	m_UniformLocationCache[name] &lt;span style="color:#f92672">=&lt;/span> location;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">return&lt;/span> location;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="main-主文件">Main 主文件&lt;a class="anchor" href="#main-%e4%b8%bb%e6%96%87%e4%bb%b6">#&lt;/a>&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//Main.cpp
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#75715e">#ifdef LY_EP16
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;GL/glew.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;GLFW/glfw3.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;iostream&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;fstream&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;sstream&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;string&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;Renderer.h&amp;#34;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;VertexBuffer.h&amp;#34;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;VertexBufferLayout.h&amp;#34;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;IndexBuffer.h&amp;#34;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;VertexArray.h&amp;#34;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;#34;Shader.h&amp;#34;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">main&lt;/span>(&lt;span style="color:#66d9ef">void&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#pragma region 一些初始化
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		GLFWwindow&lt;span style="color:#f92672">*&lt;/span> window;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">// 初始化 GLFW 库，失败则退出
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		&lt;span style="color:#66d9ef">if&lt;/span> (&lt;span style="color:#f92672">!&lt;/span>glfwInit())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">//强制指定使用 Core Profile（核心模式），如果
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		&lt;span style="color:#75715e">//没有手动写着色器则不会渲染；如果不是核心模式，
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		&lt;span style="color:#75715e">//在固定管线中默认颜色是白色，且默认顶点在NDC标准
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		&lt;span style="color:#75715e">//设备坐标系中
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">//这是兼容配置模式,会让VAO0成为默认对象
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		&lt;span style="color:#75715e">//glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE);
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, &lt;span style="color:#ae81ff">3&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, &lt;span style="color:#ae81ff">3&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">// 创建窗口对象
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		window &lt;span style="color:#f92672">=&lt;/span> glfwCreateWindow(&lt;span style="color:#ae81ff">640&lt;/span>, &lt;span style="color:#ae81ff">480&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;Hello World&amp;#34;&lt;/span>, NULL, NULL);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#66d9ef">if&lt;/span> (&lt;span style="color:#f92672">!&lt;/span>window)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#75715e">// 创建失败则清理并退出
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>			glfwTerminate();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">// 将当前窗口的上下文设置为 OpenGL 渲染的目标
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		glfwMakeContextCurrent(window);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		glfwSwapInterval(&lt;span style="color:#ae81ff">2&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">// 初始化 GLEW 以加载 OpenGL 函数指针，需在有上下文后执行
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		&lt;span style="color:#66d9ef">if&lt;/span> (glewInit() &lt;span style="color:#f92672">!=&lt;/span> GLEW_OK)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			std&lt;span style="color:#f92672">::&lt;/span>cout &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Error!&amp;#34;&lt;/span> &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>endl;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#pragma endregion
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">// 定义三角形的顶点坐标（CPU 内存）
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		&lt;span style="color:#66d9ef">float&lt;/span> positions[] &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">0.5f&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">0.5f&lt;/span>,&lt;span style="color:#75715e">//0
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>			&lt;span style="color:#ae81ff">0.5f&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">0.5f&lt;/span>,&lt;span style="color:#75715e">//1
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>			&lt;span style="color:#ae81ff">0.5f&lt;/span>, &lt;span style="color:#ae81ff">0.5f&lt;/span>,&lt;span style="color:#75715e">//2
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#75715e">//0.5f, 0.5f,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>			&lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">0.5f&lt;/span>, &lt;span style="color:#ae81ff">0.5f&lt;/span>,&lt;span style="color:#75715e">//3
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>			&lt;span style="color:#75715e">//-0.5f, -0.5f,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">//
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> indices[] &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#ae81ff">0&lt;/span>,&lt;span style="color:#ae81ff">1&lt;/span>,&lt;span style="color:#ae81ff">2&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#ae81ff">2&lt;/span>,&lt;span style="color:#ae81ff">3&lt;/span>,&lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		VertexArray va;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">//申请创建一个GPU上的缓冲区，绑定并复制进去数据构造函
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		&lt;span style="color:#75715e">//数中已经绑定了，这样glVertexAttribPointer才有效果
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		VertexBuffer vb(positions, &lt;span style="color:#ae81ff">4&lt;/span> &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">2&lt;/span> &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#66d9ef">sizeof&lt;/span>(&lt;span style="color:#66d9ef">float&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		VertexBufferLayout layout;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		layout.Push&lt;span style="color:#f92672">&amp;lt;&lt;/span>&lt;span style="color:#66d9ef">float&lt;/span>&lt;span style="color:#f92672">&amp;gt;&lt;/span>(&lt;span style="color:#ae81ff">2&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		va.AddBuffer(vb, layout);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">//申请创建一个GPU上的缓冲区，绑定并复制进去数据
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		IndexBuffer ib(indices, &lt;span style="color:#ae81ff">6&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		Shader shader(&lt;span style="color:#e6db74">&amp;#34;res/shaders/Basic.shader&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		shader.Bind();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		shader.SetUniform4f(&lt;span style="color:#e6db74">&amp;#34;u_Color&amp;#34;&lt;/span>, &lt;span style="color:#ae81ff">0.8f&lt;/span>, &lt;span style="color:#ae81ff">0.3f&lt;/span>, &lt;span style="color:#ae81ff">0.8f&lt;/span>, &lt;span style="color:#ae81ff">1.0f&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">//===这里故意把他解绑了（假设他去绑定别的去了）===
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		vb.Unbind();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		shader.Unbind();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		va.Unbind();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">//element_array_buffer 和vertexAttribArray不能
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		&lt;span style="color:#75715e">//在解绑vao之前处理，否则就记录进去了
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		ib.Unbind();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">//========================================
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#66d9ef">float&lt;/span> r &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0.0f&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#66d9ef">float&lt;/span> increment &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0.05f&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		Renderer renderer;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">// 游戏/渲染主循环
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		&lt;span style="color:#66d9ef">while&lt;/span> (&lt;span style="color:#f92672">!&lt;/span>glfwWindowShouldClose(window))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#75715e">// 清理屏幕颜色缓冲区
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>			renderer.Clear();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#75715e">//必须先绑定program，因为vao不负责着色器程序的切换
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>			&lt;span style="color:#75715e">//va.Bind();
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#66d9ef">if&lt;/span> (r &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">1.0f&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>				increment &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">0.05f&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#66d9ef">else&lt;/span> &lt;span style="color:#66d9ef">if&lt;/span> (r &lt;span style="color:#f92672">&amp;lt;&lt;/span> &lt;span style="color:#ae81ff">0.0f&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>				increment &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0.05f&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			r &lt;span style="color:#f92672">+=&lt;/span> increment;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#75715e">//绘图前重新绑定
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>			shader.Bind();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#75715e">//在u_Color的位置上设置数值
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>			shader.SetUniform4f(&lt;span style="color:#e6db74">&amp;#34;u_Color&amp;#34;&lt;/span>, r, &lt;span style="color:#ae81ff">0.3f&lt;/span>, &lt;span style="color:#ae81ff">0.0f&lt;/span>, &lt;span style="color:#ae81ff">1.0f&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#75715e">//========设置uniform========
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			renderer.Draw(va, ib, shader);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#75715e">// 交换前后缓冲区以刷新画面
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>			glfwSwapBuffers(window);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#75715e">// 轮询并处理窗口事件（如键盘输入、关闭动作）
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>			glfwPollEvents();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">//最后解绑vao
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		&lt;span style="color:#75715e">//glBindVertexArray(0);
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">// 退出前清理资源
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">//glfwTerminate会破坏Context，导致析构函数中的glGetError()
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">//返回一个OpenGL错误
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	glfwTerminate();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#endif&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>12顶点数组</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/12%E9%A1%B6%E7%82%B9%E6%95%B0%E7%BB%84/</link><pubDate>Wed, 25 Mar 2026 13:30:24 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/12%E9%A1%B6%E7%82%B9%E6%95%B0%E7%BB%84/</guid><description>&lt;p>第 12 集是整个系列中非常关键的&lt;mark>架构转折点&lt;/mark>。Cherno 在这一集深入讲解了 &lt;mark>Vertex Array Object (VAO)&lt;/mark>。&lt;/p>
&lt;p>如果你之前觉得代码里那堆 &lt;code>glVertexAttribPointer&lt;/code> 既乱又难记，那么这一集就是为你准备的“整理收纳柜”。&lt;/p>
&lt;h1 id="总述">总述&lt;a class="anchor" href="#%e6%80%bb%e8%bf%b0">#&lt;/a>&lt;/h1>
&lt;h2 id="为什么要用-vao痛点分析">为什么要用 VAO？（痛点分析）&lt;a class="anchor" href="#%e4%b8%ba%e4%bb%80%e4%b9%88%e8%a6%81%e7%94%a8-vao%e7%97%9b%e7%82%b9%e5%88%86%e6%9e%90">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>&lt;mark>现状&lt;/mark>：在核心模式（Core Profile）下，OpenGL 要求必须绑定一个 VAO 才能绘图。&lt;/li>
&lt;li>&lt;mark>问题&lt;/mark>：如果你有多个模型（比如一个三角形、一个正方形），每个模型都有自己的 &lt;code>VBO&lt;/code>、&lt;code>IBO&lt;/code> 和复杂的 &lt;code>VertexBufferLayout&lt;/code>（顶点布局）。&lt;/li>
&lt;li>&lt;mark>后果&lt;/mark>：每次切换模型画图，你都要重新写一遍 &lt;code>glBindBuffer&lt;/code>、&lt;code>glEnableVertexAttribArray&lt;/code> 和 &lt;code>glVertexAttribPointer&lt;/code>。这太啰嗦了！&lt;/li>
&lt;/ul>
&lt;h2 id="vao-的本质状态的存档">VAO 的本质：状态的“存档”&lt;a class="anchor" href="#vao-%e7%9a%84%e6%9c%ac%e8%b4%a8%e7%8a%b6%e6%80%81%e7%9a%84%e5%ad%98%e6%a1%a3">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>&lt;mark>定义&lt;/mark>：VAO 就像是一个&lt;mark>配置记录仪&lt;/mark>。它不会存储实际的顶点坐标数据，但它会&lt;mark>记住&lt;/mark>：&lt;/p>
&lt;ol>
&lt;li>哪个 &lt;mark>VBO&lt;/mark> 与它绑定了。&lt;/li>
&lt;li>&lt;mark>属性布局&lt;/mark>（多少个 float、偏移量是多少、是否归一化）。&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>
&lt;p>&lt;mark>优势&lt;/mark>：一旦你设置好了 VAO，下次画这个模型时，只需要一行 &lt;code>glBindVertexArray(vaoID)&lt;/code>，所有的 VBO 绑定和属性设置就会&lt;mark>瞬间还原&lt;/mark>。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="兼容性大坑core-vs-compatibility">兼容性大坑：Core vs Compatibility&lt;a class="anchor" href="#%e5%85%bc%e5%ae%b9%e6%80%a7%e5%a4%a7%e5%9d%91core-vs-compatibility">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>&lt;mark>Compatibility Profile（兼容模式）&lt;/mark>：&lt;em>OpenGL 会默认帮你创建一个“隐藏的 VAO (ID为0)”。所以你之前不写 VAO 也能画出图&lt;/em>。&lt;/li>
&lt;li>&lt;mark>Core Profile（核心模式）&lt;/mark>：必须显式地 &lt;code>glGenVertexArrays&lt;/code>。如果不绑定 VAO 就直接调用 &lt;code>glVertexAttribPointer&lt;/code>，程序会直接崩溃或报错。&lt;/li>
&lt;li>&lt;mark>Cherno 的建议&lt;/mark>：永远手动创建 VAO，这样你的代码在任何显卡驱动和模式下都是健壮的。&lt;/li>
&lt;/ul>
&lt;h2 id="编码实践如何创建-vao">编码实践：如何创建 VAO&lt;a class="anchor" href="#%e7%bc%96%e7%a0%81%e5%ae%9e%e8%b7%b5%e5%a6%82%e4%bd%95%e5%88%9b%e5%bb%ba-vao">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>&lt;mark>标准流程&lt;/mark>：&lt;/p>
&lt;ol>
&lt;li>&lt;code>unsigned int vao; glGenVertexArrays(1, &amp;amp;vao);&lt;/code>&lt;/li>
&lt;li>&lt;code>glBindVertexArray(vao);&lt;/code>&lt;/li>
&lt;li>&lt;code>glBindBuffer(GL_ARRAY_BUFFER, vbo);&lt;/code>&lt;/li>
&lt;li>&lt;code>glVertexAttribPointer(...);&lt;/code>&lt;/li>
&lt;li>&lt;code>glEnableVertexAttribArray(0);&lt;/code>&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>
&lt;p>&lt;mark>注意&lt;/mark>：顺序很重要！必须先绑定 VAO，再去绑定 VBO 和设置属性，这样 VAO 才能“录制”下这些操作。&lt;/p></description></item><item><title>11uniform</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/11uniform/</link><pubDate>Wed, 25 Mar 2026 07:52:27 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/11uniform/</guid><description>&lt;p>在之前的章节里，颜色是硬编码在着色器里的，而这一集教你如何从 CPU（C++ 代码）动态地控制 GPU 里的变量。&lt;/p>
&lt;h1 id="总结">总结&lt;a class="anchor" href="#%e6%80%bb%e7%bb%93">#&lt;/a>&lt;/h1>
&lt;h2 id="什么是-uniform">什么是 Uniform？&lt;a class="anchor" href="#%e4%bb%80%e4%b9%88%e6%98%af-uniform">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>&lt;mark>定义&lt;/mark>：Uniform 是从 CPU 向 Shader 传递数据的桥梁。&lt;/li>
&lt;li>&lt;mark>特性&lt;/mark>：
&lt;ul>
&lt;li>&lt;mark>“统一”性&lt;/mark>：在一个 Draw Call（绘制指令）中，所有的顶点和像素看到的 Uniform 值都是完全一样的。&lt;/li>
&lt;li>&lt;mark>对比&lt;/mark>：与 &lt;code>attributes&lt;/code>（顶点属性）不同，属性是每个顶点都可能不同的（如坐标），而 Uniform 是全局一致的。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;mark>用途&lt;/mark>：最常用于传递颜色、变换矩阵（旋转/平移/缩放）、纹理采样器等。&lt;/li>
&lt;/ul>
&lt;blockquote class='book-hint '>
&lt;p>uniform变量依赖 &lt;em>PCIe 总线&lt;/em> 进行 CPU 到 GPU 的数据拷贝。在 GPU 内部，Uniform 并不像顶点属性（Attribute）那样存在于一个巨大的流缓冲区中。它存储在 GPU 专门划拨的一块==常量寄存器（Constant Registers）或统一常量缓冲区（Uniform Storage）==中。在 Shader 运行期间，Uniform 是只读的 &lt;del>Uniform 的“只读”是指 单个 Draw Call 执行期间 它是静态的。在不同的 Draw Call 之间，你可以随意修改它。&lt;/del>&lt;/p>&lt;/blockquote>&lt;h2 id="在-shader-中声明-uniform">在 Shader 中声明 Uniform&lt;a class="anchor" href="#%e5%9c%a8-shader-%e4%b8%ad%e5%a3%b0%e6%98%8e-uniform">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>&lt;mark>语法&lt;/mark>：在 GLSL 中使用 &lt;code>uniform&lt;/code> 关键字。&lt;/li>
&lt;li>&lt;mark>命名规范&lt;/mark>：Cherno 推荐使用 &lt;code>u_&lt;/code> 前缀（如 &lt;code>u_Color&lt;/code>），以便在代码中快速区分。&lt;/li>
&lt;li>&lt;mark>代码示例&lt;/mark>：&lt;/li>
&lt;/ul>
&lt;pre tabindex="0">&lt;code class="language-Fragment" data-lang="Fragment"> #shader fragment
 #version 330 core
 layout(location = 0) out vec4 color;
 uniform vec4 u_Color; // 声明 Uniform
 void main() {
 color = u_Color; // 使用该变量
 };&lt;/code>&lt;/pre>&lt;h2 id="获取-uniform-的位置-location">获取 Uniform 的位置 (Location)&lt;a class="anchor" href="#%e8%8e%b7%e5%8f%96-uniform-%e7%9a%84%e4%bd%8d%e7%bd%ae-location">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>&lt;mark>核心函数&lt;/mark>：&lt;code>glGetUniformLocation(program, &amp;quot;u_Color&amp;quot;)&lt;/code>。&lt;/p></description></item><item><title>10错误处理</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/10%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86/</link><pubDate>Tue, 24 Mar 2026 23:05:53 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/10%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86/</guid><description>&lt;p>如何&lt;mark>自动化地捕获 OpenGL 的报错&lt;/mark>。由于 OpenGL 是一个状态机且基于 C 语言 API，它不会像现代 C++ 那样抛出异常，所以需要特殊的调试技巧。&lt;/p>
&lt;h1 id="传统方案glgeterror">传统方案：&lt;code>glGetError&lt;/code>&lt;a class="anchor" href="#%e4%bc%a0%e7%bb%9f%e6%96%b9%e6%a1%88glgeterror">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>&lt;mark>现状&lt;/mark>：OpenGL 的函数执行失败时，屏幕通常只是黑屏，没有任何提示。&lt;/li>
&lt;li>&lt;mark>原始手段&lt;/mark>：调用 &lt;code>glGetError()&lt;/code>。它会返回一个错误代码（如 1280 代表 &lt;code>GL_INVALID_ENUM&lt;/code>）。&lt;/li>
&lt;li>&lt;mark>痛点&lt;/mark>：这个函数只能一次返回&lt;mark>一个&lt;/mark>错误。如果产生了多个错误，你得在一个 &lt;code>while&lt;/code> 循环里不断调用它才能清空错误队列。&lt;/li>
&lt;/ul>
&lt;h1 id="进阶方案封装-glclearerror-与-gllogcall">进阶方案：封装 &lt;code>GLClearError&lt;/code> 与 &lt;code>GLLogCall&lt;/code>&lt;a class="anchor" href="#%e8%bf%9b%e9%98%b6%e6%96%b9%e6%a1%88%e5%b0%81%e8%a3%85-glclearerror-%e4%b8%8e-gllogcall">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>&lt;mark>思路&lt;/mark>：为了不让主代码被 &lt;code>glGetError&lt;/code> 淹没，Cherno 进行了宏封装。&lt;/li>
&lt;li>&lt;mark>&lt;code>GLCheckError()&lt;/code>&lt;/mark>：在执行 OpenGL 指令&lt;mark>前&lt;/mark>调用，用来清空之前的旧错误。&lt;/li>
&lt;li>&lt;mark>&lt;code>GLLogCall()&lt;/code>&lt;/mark>：在指令执行&lt;mark>后&lt;/mark>调用，检查是否有新错误产生，并打印出&lt;mark>文件名&lt;/mark>、&lt;mark>行号&lt;/mark>和&lt;mark>具体函数名&lt;/mark>。&lt;/li>
&lt;/ul>
&lt;h1 id="神奇的宏assert-与--运算符">神奇的宏：&lt;code>ASSERT&lt;/code> 与 &lt;code>#&lt;/code> 运算符&lt;a class="anchor" href="#%e7%a5%9e%e5%a5%87%e7%9a%84%e5%ae%8fassert-%e4%b8%8e--%e8%bf%90%e7%ae%97%e7%ac%a6">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>
&lt;p>&lt;mark>自动化&lt;/mark>：定义一个 &lt;code>GLCall(x)&lt;/code> 宏。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-scss" data-lang="scss">&lt;span style="display:flex;">&lt;span>#define &lt;span style="color:#f92672">GLCall&lt;/span>&lt;span style="color:#f92672">(&lt;/span>&lt;span style="color:#f92672">x&lt;/span>&lt;span style="color:#f92672">)&lt;/span> &lt;span style="color:#f92672">GLClearError&lt;/span>&lt;span style="color:#f92672">()&lt;/span>;&lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">x&lt;/span>;&lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">ASSERT&lt;/span>&lt;span style="color:#f92672">(&lt;/span>&lt;span style="color:#f92672">GLLogCall&lt;/span>&lt;span style="color:#f92672">(&lt;/span>#x&lt;span style="color:#f92672">,&lt;/span> &lt;span style="color:#f92672">__FILE__&lt;/span>&lt;span style="color:#f92672">,&lt;/span> &lt;span style="color:#f92672">__LINE__&lt;/span>&lt;span style="color:#f92672">))&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>&lt;mark>技巧&lt;/mark>：&lt;/p>
&lt;ul>
&lt;li>&lt;code>#x&lt;/code>：将代码内容转为字符串。&lt;/li>
&lt;li>&lt;code>__FILE__&lt;/code> 和 &lt;code>__LINE__&lt;/code>：C++ 内置宏，自动获取报错位置。&lt;/li>
&lt;li>&lt;code>ASSERT&lt;/code>：如果报错，直接让程序停在出问题的那一行，而不是跑飞。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h1 id="现代方案gldebugmessagecallback">现代方案：&lt;code>glDebugMessageCallback&lt;/code>&lt;a class="anchor" href="#%e7%8e%b0%e4%bb%a3%e6%96%b9%e6%a1%88gldebugmessagecallback">#&lt;/a>&lt;/h1>
&lt;p>&lt;del>视频没讲到这个&lt;/del>&lt;/p>
&lt;ul>
&lt;li>&lt;mark>新特性&lt;/mark>：在 OpenGL 4.3+ 版本中引入。&lt;/li>
&lt;li>&lt;mark>优势&lt;/mark>：这是“被动式”报错。你只需在初始化时设置一个回调函数，一旦 OpenGL 出错，驱动程序会自动跳进你的函数。&lt;/li>
&lt;li>&lt;mark>局限性&lt;/mark>：虽然更现代，但某些旧显卡驱动支持不佳。Cherno 推荐在学习阶段使用 &lt;code>GLCall&lt;/code> 宏，因为它兼容性最强且更直观。&lt;/li>
&lt;/ul>
&lt;h1 id="实战演示故意写错代码">实战演示：故意写错代码&lt;a class="anchor" href="#%e5%ae%9e%e6%88%98%e6%bc%94%e7%a4%ba%e6%95%85%e6%84%8f%e5%86%99%e9%94%99%e4%bb%a3%e7%a0%81">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>&lt;mark>验证&lt;/mark>：Cherno 故意在 &lt;code>glDrawElements&lt;/code> 里传了一个错误的枚举值。&lt;/li>
&lt;li>&lt;mark>结果&lt;/mark>：控制台瞬间精准报出了：
&lt;ol>
&lt;li>哪个函数错了（&lt;code>glDrawElements&lt;/code>）。&lt;/li>
&lt;li>在哪个文件哪一行。&lt;/li>
&lt;li>错误代码是什么。&lt;/li>
&lt;/ol>
&lt;/li>
&lt;/ul>
&lt;h1 id="-博客总结金句重点">💡 博客总结（金句/重点）&lt;a class="anchor" href="#-%e5%8d%9a%e5%ae%a2%e6%80%bb%e7%bb%93%e9%87%91%e5%8f%a5%e9%87%8d%e7%82%b9">#&lt;/a>&lt;/h1>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>知识点&lt;/th>
 &lt;th>关键点&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;mark>为什么 OpenGL 不报错？&lt;/mark>&lt;/td>
 &lt;td>它是 C 风格 API，不提供异常机制，需手动查询状态。&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;mark>&lt;code>glGetError&lt;/code> 特点&lt;/mark>&lt;/td>
 &lt;td>错误码是累积的，必须循环读取直到返回 &lt;code>GL_NO_ERROR&lt;/code>。&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;mark>&lt;code>GLCall&lt;/code> 宏的作用&lt;/mark>&lt;/td>
 &lt;td>它可以包裹任何 &lt;code>gl&lt;/code> 函数，实现一站式“清空-执行-检查”。&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;mark>调试效率&lt;/mark>&lt;/td>
 &lt;td>配合 &lt;code>__debugbreak()&lt;/code>（Windows 特有）可以让调试器直接定位到崩掉的那行源码。&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>&lt;mark>在发布版本（Release）中通常要关掉这些报错检查&lt;/mark>。因为频繁地通过总线询问 GPU 状态会严重拖慢帧率（FPS）。&lt;/p></description></item><item><title>09顶点缓冲区</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/09%E9%A1%B6%E7%82%B9%E7%BC%93%E5%86%B2%E5%8C%BA/</link><pubDate>Tue, 24 Mar 2026 19:10:23 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/09%E9%A1%B6%E7%82%B9%E7%BC%93%E5%86%B2%E5%8C%BA/</guid><description>&lt;h1 id="总述">总述&lt;a class="anchor" href="#%e6%80%bb%e8%bf%b0">#&lt;/a>&lt;/h1>
&lt;h2 id="核心痛点重复的顶点数据">核心痛点：重复的顶点数据&lt;a class="anchor" href="#%e6%a0%b8%e5%bf%83%e7%97%9b%e7%82%b9%e9%87%8d%e5%a4%8d%e7%9a%84%e9%a1%b6%e7%82%b9%e6%95%b0%e6%8d%ae">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>&lt;mark>现状分析&lt;/mark>：绘制一个矩形需要两个三角形（6 个顶点）。&lt;/li>
&lt;li>&lt;mark>资源浪费&lt;/mark>：矩形只有 4 个角，使用 &lt;code>glDrawArrays&lt;/code> 必须重复定义其中 2 个顶点（坐标、纹理、颜色等完全一致）。&lt;/li>
&lt;li>&lt;mark>影响&lt;/mark>：随着模型复杂化，这种冗余会成倍消耗显存（VRAM）和带宽。&lt;/li>
&lt;/ul>
&lt;h2 id="解决方案index-buffer-原理">解决方案：Index Buffer 原理&lt;a class="anchor" href="#%e8%a7%a3%e5%86%b3%e6%96%b9%e6%a1%88index-buffer-%e5%8e%9f%e7%90%86">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>&lt;mark>定义&lt;/mark>：索引缓冲区（IBO/EBO）存储的是指向顶点数组的&lt;mark>整数索引&lt;/mark>。&lt;/li>
&lt;li>&lt;mark>逻辑&lt;/mark>：
&lt;ol>
&lt;li>&lt;mark>Vertex Buffer&lt;/mark>：只存储 4 个唯一的顶点坐标。&lt;/li>
&lt;li>&lt;mark>Index Buffer&lt;/mark>：存储顺序（如 &lt;code>0, 1, 2, 2, 3, 0&lt;/code>），告诉 GPU 如何连接这些点。&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>&lt;mark>优势&lt;/mark>：一个浮点数顶点属性通常 12-32 字节，而一个索引（&lt;code>unsigned int&lt;/code> 或 &lt;code>short&lt;/code>）仅需 4 或 2 字节。&lt;/li>
&lt;/ul>
&lt;h2 id="编码实践创建与绑定-ibo">编码实践：创建与绑定 IBO&lt;a class="anchor" href="#%e7%bc%96%e7%a0%81%e5%ae%9e%e8%b7%b5%e5%88%9b%e5%bb%ba%e4%b8%8e%e7%bb%91%e5%ae%9a-ibo">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>&lt;mark>创建对象&lt;/mark>：&lt;code>glGenBuffers(1, &amp;amp;ibo);&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;mark>绑定目标&lt;/mark>：必须指定为 &lt;mark>&lt;code>GL_ELEMENT_ARRAY_BUFFER&lt;/code>&lt;/mark>。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;mark>填充数据&lt;/mark>：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> indices[] &lt;span style="color:#f92672">=&lt;/span> { &lt;span style="color:#ae81ff">0&lt;/span>, &lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">2&lt;/span>, &lt;span style="color:#ae81ff">2&lt;/span>, &lt;span style="color:#ae81ff">3&lt;/span>, &lt;span style="color:#ae81ff">0&lt;/span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//索引缓冲区：index buffer object
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> ibo;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// 生成一个缓冲区 ID
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>glGenBuffers(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#f92672">&amp;amp;&lt;/span>ibo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// 绑定该 ID 到元素数组缓冲区 (Element Array Buffer)插槽，
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//也称索引缓冲区对象
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// 将顶点数据从 CPU 内存拷贝到 GPU 显存，设为静态读取模式
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>glBufferData(GL_ELEMENT_ARRAY_BUFFER, &lt;span style="color:#ae81ff">6&lt;/span> &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#66d9ef">sizeof&lt;/span>(&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	indices, GL_STATIC_DRAW);&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>&lt;mark>注意&lt;/mark>：索引必须是正整数，且建议使用 &lt;code>unsigned int&lt;/code> 以保证最大兼容性。&lt;/p></description></item><item><title>08处理着色器</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/08%E5%A4%84%E7%90%86%E7%9D%80%E8%89%B2%E5%99%A8/</link><pubDate>Tue, 24 Mar 2026 16:37:11 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/08%E5%A4%84%E7%90%86%E7%9D%80%E8%89%B2%E5%99%A8/</guid><description>&lt;p>主要是添加了&lt;code>struct ShaderProgramSource&lt;/code> 和&lt;code>static ShaderProgramSource ParseShader(const std::string&amp;amp; filepath)&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#ifdef LY_EP08
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;iostream&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;GL/glew.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;GLFW/glfw3.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;fstream&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;sstream&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;string&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">struct&lt;/span> &lt;span style="color:#a6e22e">ShaderProgramSource&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	std&lt;span style="color:#f92672">::&lt;/span>string VertexSource;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	std&lt;span style="color:#f92672">::&lt;/span>string FragmentSource;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">static&lt;/span> ShaderProgramSource &lt;span style="color:#a6e22e">ParseShader&lt;/span>(&lt;span style="color:#66d9ef">const&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>string&lt;span style="color:#f92672">&amp;amp;&lt;/span> filepath)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">enum&lt;/span> &lt;span style="color:#66d9ef">class&lt;/span> &lt;span style="color:#a6e22e">ShaderType&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		NONE &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>,VERTEX &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>,FRAGMENT &lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//input file stream
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	std&lt;span style="color:#f92672">::&lt;/span>ifstream stream(filepath);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	std&lt;span style="color:#f92672">::&lt;/span>string line;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	std&lt;span style="color:#f92672">::&lt;/span>stringstream ss[&lt;span style="color:#ae81ff">2&lt;/span>];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	ShaderType type &lt;span style="color:#f92672">=&lt;/span> ShaderType&lt;span style="color:#f92672">::&lt;/span>NONE;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">while&lt;/span> (getline(stream, line))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#66d9ef">if&lt;/span> (line.find(&lt;span style="color:#e6db74">&amp;#34;#shader&amp;#34;&lt;/span>) &lt;span style="color:#f92672">!=&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>string&lt;span style="color:#f92672">::&lt;/span>npos)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#66d9ef">if&lt;/span> (line.find(&lt;span style="color:#e6db74">&amp;#34;vertex&amp;#34;&lt;/span>) &lt;span style="color:#f92672">!=&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>string&lt;span style="color:#f92672">::&lt;/span>npos)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>				type &lt;span style="color:#f92672">=&lt;/span> ShaderType&lt;span style="color:#f92672">::&lt;/span>VERTEX;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			&lt;span style="color:#66d9ef">else&lt;/span> &lt;span style="color:#66d9ef">if&lt;/span> (line.find(&lt;span style="color:#e6db74">&amp;#34;fragment&amp;#34;&lt;/span>) &lt;span style="color:#f92672">!=&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>string&lt;span style="color:#f92672">::&lt;/span>npos)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>				type &lt;span style="color:#f92672">=&lt;/span> ShaderType&lt;span style="color:#f92672">::&lt;/span>FRAGMENT;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#66d9ef">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>			ss[(&lt;span style="color:#66d9ef">int&lt;/span>)type] &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> line &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> &lt;span style="color:#e6db74">&amp;#39;\n&amp;#39;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">return&lt;/span> { ss[&lt;span style="color:#ae81ff">0&lt;/span>].str(),ss[&lt;span style="color:#ae81ff">1&lt;/span>].str() };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//GLenum也就是unsigned int,这里不用GLenum是为了解耦
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">CompileShader&lt;/span>(&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> type,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">const&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>string&lt;span style="color:#f92672">&amp;amp;&lt;/span> source)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//创建着色器对象，向 OpenGL 申请一个空的“容器”来存放你的代码
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">//把光标放在 glCreateShader( 的左括号后面，然后按下 Ctrl + Shift + Space。会弹出一个小黑框，显示：GLuint glCreateShader(GLenum type)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">//到glew.h搜索，发现typedef unsigned int GLenum;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> id &lt;span style="color:#f92672">=&lt;/span> glCreateShader(type);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//返回一个指向该字符串首地址的只读指针（const char*）
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#66d9ef">char&lt;/span>&lt;span style="color:#f92672">*&lt;/span> src &lt;span style="color:#f92672">=&lt;/span> source.c_str();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//有了容器后，你需要把写好的字符串代码塞进去（拷贝）
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">//3种用法
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">//1. glShaderSource(id, 1, &amp;amp;src, nullptr);	只有一个字符串，且它是以 \0 结尾的。
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">//2. glShaderSource(id, 1, &amp;amp;src, &amp;amp;len);	只有一个字符串，长度由 len 指定。
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">//3. glShaderSource(id, 2, strings, lengths);	有两个字符串片段（数组形式），长度分别由 lengths[0] 和 lengths[1] 指定。
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	glShaderSource(id, &lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#f92672">&amp;amp;&lt;/span>src, &lt;span style="color:#66d9ef">nullptr&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//将你的 GLSL 代码翻译成显卡能理解的机器指令
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	glCompileShader(id);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//错误处理
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#66d9ef">int&lt;/span> result;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">// 查询编译状态：询问 OpenGL 这个 shader 编译成功了吗？
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">// GL_COMPILE_STATUS 会把结果存入 result 中（成功为 GL_TRUE，失败为 GL_FALSE）
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	glGetShaderiv(id, GL_COMPILE_STATUS, &lt;span style="color:#f92672">&amp;amp;&lt;/span>result);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">if&lt;/span> (result &lt;span style="color:#f92672">==&lt;/span> GL_FALSE)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#66d9ef">int&lt;/span> length;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">//获取错误日志长度：如果编译失败，先问一下错误信息一共有多少个字符
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		glGetShaderiv(id, GL_INFO_LOG_LENGTH, &lt;span style="color:#f92672">&amp;amp;&lt;/span>length);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">//在栈上申请内存
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		&lt;span style="color:#75715e">//如果申请的内存太大，alloca会导致栈溢出（1MB）
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		&lt;span style="color:#66d9ef">char&lt;/span>&lt;span style="color:#f92672">*&lt;/span> message &lt;span style="color:#f92672">=&lt;/span> (&lt;span style="color:#66d9ef">char&lt;/span>&lt;span style="color:#f92672">*&lt;/span>)alloca(length &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#66d9ef">sizeof&lt;/span>(&lt;span style="color:#66d9ef">char&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">//提取错误信息：把显卡驱动里的具体报错文字拷贝到我们刚才申请的 message 内存中
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		glGetShaderInfoLog(id, length, &lt;span style="color:#f92672">&amp;amp;&lt;/span>length, message);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		std&lt;span style="color:#f92672">::&lt;/span>cout &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Failed to compile &amp;#34;&lt;/span> &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> ((type &lt;span style="color:#f92672">==&lt;/span> GL_VERTEX_SHADER) &lt;span style="color:#f92672">?&lt;/span> &lt;span style="color:#e6db74">&amp;#34;vertex&amp;#34;&lt;/span> &lt;span style="color:#f92672">:&lt;/span> &lt;span style="color:#e6db74">&amp;#34;fragment&amp;#34;&lt;/span>) &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> &lt;span style="color:#e6db74">&amp;#34; shader!&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		std&lt;span style="color:#f92672">::&lt;/span>cout &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> message &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>endl;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">//清理：编译失败了，这个 shader 对象也就没用了，删掉它防止内存泄漏
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		glDeleteShader(id);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">return&lt;/span> id;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">CreateShader&lt;/span>(&lt;span style="color:#66d9ef">const&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>string&lt;span style="color:#f92672">&amp;amp;&lt;/span> vertexShader,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">const&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>string&lt;span style="color:#f92672">&amp;amp;&lt;/span> fragmentShader)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//在 GPU 中申请一个空的“程序对象”
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> program &lt;span style="color:#f92672">=&lt;/span> glCreateProgram();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//编译顶点着色器
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> vs &lt;span style="color:#f92672">=&lt;/span> CompileShader(GL_VERTEX_SHADER,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		vertexShader);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//编译片段着色器
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> fs &lt;span style="color:#f92672">=&lt;/span> CompileShader(GL_FRAGMENT_SHADER,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		fragmentShader);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//把已经编译好的顶点着色器和片段着色器附加到程序对象中
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	glAttachShader(program, vs);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	glAttachShader(program, fs);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//链接
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	glLinkProgram(program);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//验证当前的程序是否能在当前的 OpenGL 状态下执行（
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">// 检查顶点着色器的输出是否与片段着色器的输入匹配，并
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">// 生成最终的可执行二进制代码。）
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	glValidateProgram(program);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//删除临时的中间产物（类似编译完 C++ 后删除 .obj
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	glDeleteShader(vs);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	glDeleteShader(fs);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">return&lt;/span> program;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">main&lt;/span>(&lt;span style="color:#66d9ef">void&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#pragma region 一些初始化
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	GLFWwindow&lt;span style="color:#f92672">*&lt;/span> window;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">// 初始化 GLFW 库，失败则退出
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#66d9ef">if&lt;/span> (&lt;span style="color:#f92672">!&lt;/span>glfwInit())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//强制指定使用 Core Profile（核心模式），如果
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">//没有手动写着色器则不会渲染；如果不是核心模式，
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">//在固定管线中默认颜色是白色，且默认顶点在NDC标准
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">//设备坐标系中
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">//glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">//glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">//glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">// 创建窗口对象
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	window &lt;span style="color:#f92672">=&lt;/span> glfwCreateWindow(&lt;span style="color:#ae81ff">640&lt;/span>, &lt;span style="color:#ae81ff">480&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;Hello World&amp;#34;&lt;/span>, NULL, NULL);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">if&lt;/span> (&lt;span style="color:#f92672">!&lt;/span>window)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">// 创建失败则清理并退出
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		glfwTerminate();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">// 将当前窗口的上下文设置为 OpenGL 渲染的目标
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	glfwMakeContextCurrent(window);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">// 初始化 GLEW 以加载 OpenGL 函数指针，需在有上下文后执行
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#66d9ef">if&lt;/span> (glewInit() &lt;span style="color:#f92672">!=&lt;/span> GLEW_OK)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		std&lt;span style="color:#f92672">::&lt;/span>cout &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Error!&amp;#34;&lt;/span> &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>endl;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#pragma endregion
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">// 定义三角形的顶点坐标（CPU 内存）
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#66d9ef">float&lt;/span> positions[&lt;span style="color:#ae81ff">6&lt;/span>] &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">0.5f&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">0.5&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#ae81ff">0.0f&lt;/span>, &lt;span style="color:#ae81ff">0.5f&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#ae81ff">0.5f&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">0.5f&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> buffer;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">// 生成一个缓冲区 ID
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	glGenBuffers(&lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#f92672">&amp;amp;&lt;/span>buffer);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">// 绑定该 ID 到顶点缓冲区插槽
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	glBindBuffer(GL_ARRAY_BUFFER, buffer);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">// 将顶点数据从 CPU 内存拷贝到 GPU 显存，设为静态读取模式
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	glBufferData(GL_ARRAY_BUFFER, &lt;span style="color:#ae81ff">6&lt;/span> &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#66d9ef">sizeof&lt;/span>(&lt;span style="color:#66d9ef">float&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		positions, GL_STATIC_DRAW);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">// 启用索引为 0 的(顶点)属性 
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	glEnableVertexAttribArray(&lt;span style="color:#ae81ff">0&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//1. 打标签：它把当前 GL_ARRAY_BUFFER 里的数据流，贴上了“0号”的标签。2. 定规则：它告诉 GPU，当你（Shader）想要 location = 0 的数据时，请按照“每 2 个 float 为一组”的规则去缓存里抓取。
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	glVertexAttribPointer(&lt;span style="color:#ae81ff">0&lt;/span>, &lt;span style="color:#ae81ff">2&lt;/span>, GL_FLOAT, GL_FALSE, &lt;span style="color:#66d9ef">sizeof&lt;/span>(&lt;span style="color:#66d9ef">float&lt;/span>) &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">2&lt;/span>, (&lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span>&lt;span style="color:#f92672">*&lt;/span>)&lt;span style="color:#ae81ff">0&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">//这是一个相对路径，相对于 工作目录
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">//如果不是在visual studio中运行，就会相对于 可执行文件 所在的目录
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">//如果在visual studio中，工作目录被设置为$(ProjectDir) (右键目录-属性-调试-woking directory)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	ShaderProgramSource source &lt;span style="color:#f92672">=&lt;/span> ParseShader(&lt;span style="color:#e6db74">&amp;#34;res/shaders/Basic.shader&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	std&lt;span style="color:#f92672">::&lt;/span>cout &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> source.VertexSource &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>endl;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	std&lt;span style="color:#f92672">::&lt;/span>cout &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> source.FragmentSource &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>endl;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">// 将字符串源码编译并链接成一个完整的程序对象
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">int&lt;/span> program &lt;span style="color:#f92672">=&lt;/span> CreateShader(source.VertexSource, source.FragmentSource);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">// 告诉 OpenGL 状态机：接下来的所有绘制指令（如 glDrawArrays）
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#75715e">// 都请使用这个编译好的着色器程序进行渲染
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	glUseProgram(program);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">// 游戏/渲染主循环
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	&lt;span style="color:#66d9ef">while&lt;/span> (&lt;span style="color:#f92672">!&lt;/span>glfwWindowShouldClose(window))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">// 清理屏幕颜色缓冲区
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		glClear(GL_COLOR_BUFFER_BIT);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">// 读取当前绑定的缓冲区数据并绘制三角形：从
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		&lt;span style="color:#75715e">// 索引0即第1个顶点开始画，读取三个顶点(
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		&lt;span style="color:#75715e">//并行地读第1个顶点的0号属性_以及_第1个顶点的1号属性) 
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		glDrawArrays(GL_TRIANGLES, &lt;span style="color:#ae81ff">0&lt;/span>, &lt;span style="color:#ae81ff">3&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">// 交换前后缓冲区以刷新画面
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		glfwSwapBuffers(window);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>		&lt;span style="color:#75715e">// 轮询并处理窗口事件（如键盘输入、关闭动作）
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>		glfwPollEvents();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">// 手动通知显卡驱动释放该程序占用的显存
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	glDeleteProgram(program);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#75715e">// 退出前清理资源
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>	glfwTerminate();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>	&lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#endif&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>着色器&lt;/p></description></item><item><title>07编写一个着色器</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/07%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AA%E7%9D%80%E8%89%B2%E5%99%A8/</link><pubDate>Tue, 24 Mar 2026 12:31:46 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/07%E7%BC%96%E5%86%99%E4%B8%80%E4%B8%AA%E7%9D%80%E8%89%B2%E5%99%A8/</guid><description>&lt;h1 id="概述">概述&lt;a class="anchor" href="#%e6%a6%82%e8%bf%b0">#&lt;/a>&lt;/h1>
&lt;p>编写最基础的&lt;mark>顶点着色器 (Vertex Shader)&lt;/mark> 和&lt;mark>片段着色器 (Fragment Shader)&lt;/mark>，并将它们链接成一个可以在显卡上运行的着色器程序。&lt;/p>
&lt;ul>
&lt;li>&lt;mark>课程引入&lt;/mark> 回顾上一集关于着色器的理论概念，明确本集目标：编写实际代码让上一集绘制的三角形呈现特定的颜色（不再是默认的白色或黑色）。&lt;/li>
&lt;li>&lt;mark>创建基础字符串结构&lt;/mark> 演示如何在 C++ 中以字符串形式编写 GLSL 代码。由于目前还没有文件读取系统，Cherno 直接在 &lt;code>main&lt;/code> 函数中使用原始字符串（Raw Strings）来定义顶点和片段着色器的源码。&lt;/li>
&lt;li>&lt;mark>编写顶点着色器 (Vertex Shader)&lt;/mark>
&lt;ul>
&lt;li>指定版本：&lt;code>#version 330 core&lt;/code>。&lt;/li>
&lt;li>定义输入属性：&lt;code>layout(location = 0) in vec4 position;&lt;/code>&lt;/li>
&lt;li>主函数 &lt;code>main&lt;/code>：将输入的坐标赋值给内置变量 &lt;code>gl_Position&lt;/code>。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;mark>编写片段着色器 (Fragment Shader)&lt;/mark>
&lt;ul>
&lt;li>定义输出颜色：&lt;code>layout(location = 0) out vec4 color;&lt;/code>&lt;/li>
&lt;li>主函数 &lt;code>main&lt;/code>：为像素指定颜色值（例如红色 &lt;code>vec4(1.0, 0.0, 0.0, 1.0)&lt;/code>）。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;mark>创建编译与链接函数 &lt;code>CreateShader&lt;/code>&lt;/mark> 开始实现逻辑：如何将上述两个字符串交给 OpenGL。
&lt;ul>
&lt;li>调用 &lt;code>glCreateProgram&lt;/code> 创建一个程序容器。&lt;/li>
&lt;li>调用自定义的 &lt;code>CompileShader&lt;/code> 函数分别编译顶点和片段部分。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;mark>实现 &lt;code>CompileShader&lt;/code> 函数&lt;/mark> 这是核心步骤：
&lt;ol>
&lt;li>&lt;code>glCreateShader&lt;/code>：根据类型创建着色器对象。&lt;/li>
&lt;li>&lt;code>glShaderSource&lt;/code>：将 C++ 字符串传递给 OpenGL。&lt;/li>
&lt;li>&lt;code>glCompileShader&lt;/code>：调用显卡驱动进行编译。&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>&lt;mark>错误处理与调试 (Shader Debugging)&lt;/mark> 这是新手最容易卡住的地方。Cherno 详细演示了如何使用 &lt;code>glGetShaderiv&lt;/code> 检查编译状态，并用 &lt;code>glGetShaderInfoLog&lt;/code> 打印出显卡报错信息。&lt;/li>
&lt;li>&lt;mark>链接着色器程序 (Linking)&lt;/mark> 使用 &lt;code>glAttachShader&lt;/code> 将编译好的两个着色器附加到程序上，然后调用 &lt;code>glLinkProgram&lt;/code> 进行链接，最后用 &lt;code>glValidateProgram&lt;/code> 验证。&lt;/li>
&lt;li>&lt;mark>清理工作&lt;/mark> 链接成功后，调用 &lt;code>glDeleteShader&lt;/code> 删除临时的中间产物（类似编译完 C++ 后删除 &lt;code>.obj&lt;/code> 文件），以节省资源。&lt;/li>
&lt;li>&lt;mark>实际运行测试&lt;/mark> 将编写好的 &lt;code>CreateShader&lt;/code> 函数集成到渲染循环之前。&lt;/li>
&lt;li>&lt;mark>结果展示与总结&lt;/mark> 屏幕上成功出现了一个红色的三角形。Cherno 总结了着色器在现代渲染管线中的重要角色。&lt;/li>
&lt;/ul>
&lt;p>&lt;mark>学习要点提示：&lt;/mark>&lt;/p></description></item><item><title>06着色器原理</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/06%E7%9D%80%E8%89%B2%E5%99%A8%E5%8E%9F%E7%90%86/</link><pubDate>Mon, 23 Mar 2026 16:29:24 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/06%E7%9D%80%E8%89%B2%E5%99%A8%E5%8E%9F%E7%90%86/</guid><description>&lt;h1 id="什么是着色器-shaders">什么是着色器 (Shaders)&lt;a class="anchor" href="#%e4%bb%80%e4%b9%88%e6%98%af%e7%9d%80%e8%89%b2%e5%99%a8-shaders">#&lt;/a>&lt;/h1>
&lt;p>着色器本质上是&lt;em>运行在 GPU（显卡）上的程序&lt;/em>。在 OpenGL 绘图流程中，我们需要&lt;em>在 GPU 上运行代码来处理我们发送过去的顶点数据&lt;/em>。&lt;/p>
&lt;ul>
&lt;li>当你调用 glCompileShader(id) 时，显卡驱动（运行在 CPU 上）会获取这个字符串。&lt;em>&lt;mark>显卡驱动&lt;/mark>里内置了一个编译器&lt;/em>。它会&lt;em>将你的 GLSL 源代码翻译成该显卡硬件能听懂的机器码（微指令）&lt;/em>。由于不同品牌的显卡（NVIDIA, AMD, Intel）底层指令集完全不同，所以 Shader 必须在你的电脑上“现场编译”，而不能像 C++ 那样预先编译成 .exe。&lt;em>编译好的二进制指令会被发送并存储在 GPU 的显存中&lt;/em>。当你执行 glDrawArrays 时，GPU 会激活这些指令，大规模并行地处理你传进去的顶点数据。&lt;/li>
&lt;li>我们需要显卡的能力在屏幕上绘制图形，所以需要程序完全在显卡上运行，当然部分代码需要在CPU上运行&lt;/li>
&lt;li>着色器的本质，就是告诉GPU如何处理CPU发送给他的数据&lt;/li>
&lt;/ul>
&lt;h1 id="渲染管线概述-the-graphics-pipeline">渲染管线概述 (The Graphics Pipeline)&lt;a class="anchor" href="#%e6%b8%b2%e6%9f%93%e7%ae%a1%e7%ba%bf%e6%a6%82%e8%bf%b0-the-graphics-pipeline">#&lt;/a>&lt;/h1>
&lt;p>解释了&lt;em>数据从 CPU 发送到 GPU 后的处理过程&lt;/em>。&lt;em>渲染管线&lt;/em>是一系列&lt;em>将 3D 坐标转换为屏幕上 2D 像素&lt;/em>的步骤。&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>渲染&lt;em>管道&lt;/em>：在CPU上写了一堆数据，之后向显卡发送了一些数据，然后发送了DrawCall指令（发送指令前绑定了一些状态），最后进入了着色器的阶段，GPU实际处理DrawCall指令并在屏幕上绘制一些东西&lt;/p>&lt;/blockquote>&lt;h1 id="着色器">着色器&lt;a class="anchor" href="#%e7%9d%80%e8%89%b2%e5%99%a8">#&lt;/a>&lt;/h1>
&lt;p>顶点着色器和片段着色器是顺着管道的两种不同的着色器类型，发出DrawCall指令后，顶点着色器被执行，然后再经过一些阶段后 是片段着色器&lt;/p>
&lt;p>&lt;em>&lt;mark>顶点着色器&lt;/mark>&lt;/em> &lt;del>把 3D 空间的坐标转换成屏幕上的 2D 坐标（术语叫标准化设备坐标）&lt;/del> -&amp;gt;&lt;em>几何着色器&lt;/em> &lt;del>增减图形：可以把一个点变成一个三角形，或者把一个三角形删掉。这里，几何着色器根据这 3 个点，计算出了第 4 个点的位置，并把它们重新组合。输出：它“发射”出了足够的顶点，组成了两个独立的三角形图元。&lt;/del> -&amp;gt;图元组装 &lt;del>把处理好的点“连线”&lt;/del> -&amp;gt;光栅化 &lt;del>计算这个三角形到底盖住了屏幕上哪些像素格子&lt;/del> -&amp;gt;&lt;em>&lt;mark>片段着色器&lt;/mark>&lt;/em> &lt;del>根据光照、纹理、材质，给这个像素算出一个最终的 RGBA 值&lt;/del> -&amp;gt;测试与混合 &lt;del>深度测试 (Depth Test)：检查这个像素是不是被别的物体挡住了。模板测试：一些特殊遮罩效果。混合 (Blending)：如果是半透明的，就把它和背景颜色融合&lt;/del>&lt;/p></description></item><item><title>05顶点属性和内存布局</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/05%E9%A1%B6%E7%82%B9%E5%B1%9E%E6%80%A7%E5%92%8C%E5%86%85%E5%AD%98%E5%B8%83%E5%B1%80/</link><pubDate>Sun, 22 Mar 2026 18:53:01 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/05%E9%A1%B6%E7%82%B9%E5%B1%9E%E6%80%A7%E5%92%8C%E5%86%85%E5%AD%98%E5%B8%83%E5%B1%80/</guid><description>&lt;p>在第 04 集里你把数据塞进了显存，但 GPU 面对那一串二进制数字是“瞎”的。这一集的核心就是&lt;mark>给数据贴标签&lt;/mark>，告诉 GPU 如何读取这些坐标。&lt;/p>
&lt;ul>
&lt;li>&lt;mark>问题的引入：为什么屏幕还是黑的？&lt;/mark>
&lt;ul>
&lt;li>数据（Positions）已经在 VRAM 里了，但 GPU 不知道这堆 &lt;code>float&lt;/code> 代表什么。&lt;/li>
&lt;li>是一组 坐标？还是 颜色？或者是贴图坐标？&lt;/li>
&lt;li>我们需要一份“说明书”来定义数据的&lt;mark>布局 (Layout)&lt;/mark>。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;mark>核心函数：&lt;code>glVertexAttribPointer&lt;/code> 详解&lt;/mark>
&lt;ul>
&lt;li>这是 OpenGL 中参数最复杂的函数，Cherno 逐一拆解了这 6 个参数：
&lt;ol>
&lt;li>&lt;mark>index&lt;/mark>: 属性的索引（例如：0号属性给位置，1号属性给颜色，2号属性给纹理）。&lt;/li>
&lt;li>&lt;mark>size&lt;/mark>: 每个属性包含几个分量（位置是3，纹理是2）。&lt;/li>
&lt;li>&lt;mark>type&lt;/mark>: 数据类型（&lt;code>GL_FLOAT&lt;/code>）。&lt;/li>
&lt;li>&lt;mark>normalized&lt;/mark>: 是否归一化（通常选 &lt;code>GL_FALSE&lt;/code>）。&lt;/li>
&lt;li>&lt;mark>stride (步长)&lt;/mark>: &lt;mark>最关键参数&lt;/mark>。从&lt;em>某个属性的起始点&lt;/em>到&lt;em>下一个属性起始点&lt;/em>的字节数。（这里第一组到第二组，位置index从0-&amp;gt;5，纹理index从3-&amp;gt;8，这里是&lt;code>5 * sizeof(float)&lt;/code>）&lt;/li>
&lt;li>&lt;mark>pointer (偏移量)&lt;/mark>: 在单个顶点数据块内，该属性距离(这个块的)起始位置的字节偏移。&lt;/li>
&lt;/ol>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//这里举一个特殊的例子，LearnOpenGL中的
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>&lt;span style="color:#66d9ef">float&lt;/span> vertices[] &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">0.5f&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">0.5f&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">0.5f&lt;/span>, &lt;span style="color:#ae81ff">0.0f&lt;/span>, &lt;span style="color:#ae81ff">0.0f&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ae81ff">0.5f&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">0.5f&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">0.5f&lt;/span>, &lt;span style="color:#ae81ff">1.0f&lt;/span>, &lt;span style="color:#ae81ff">0.0f&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ae81ff">0.5f&lt;/span>, &lt;span style="color:#ae81ff">0.5f&lt;/span>, &lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">0.5f&lt;/span>, &lt;span style="color:#ae81ff">1.0f&lt;/span>, &lt;span style="color:#ae81ff">1.0f&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//配置位置属性
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>glVertexAttribPointer(&lt;span style="color:#ae81ff">0&lt;/span>, &lt;span style="color:#ae81ff">3&lt;/span>, GL_FLOAT, GL_FALSE, &lt;span style="color:#ae81ff">5&lt;/span> &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#66d9ef">sizeof&lt;/span>(&lt;span style="color:#66d9ef">float&lt;/span>), (&lt;span style="color:#66d9ef">void&lt;/span>&lt;span style="color:#f92672">*&lt;/span>)&lt;span style="color:#ae81ff">0&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>glEnableVertexAttribArray(&lt;span style="color:#ae81ff">0&lt;/span>); 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//配置纹理属性
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>glVertexAttribPointer(&lt;span style="color:#ae81ff">2&lt;/span>, &lt;span style="color:#ae81ff">2&lt;/span>, GL_FLOAT, GL_FALSE, &lt;span style="color:#ae81ff">5&lt;/span> &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#66d9ef">sizeof&lt;/span>(&lt;span style="color:#66d9ef">float&lt;/span>), (&lt;span style="color:#66d9ef">void&lt;/span>&lt;span style="color:#f92672">*&lt;/span>)(&lt;span style="color:#ae81ff">3&lt;/span> &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#66d9ef">sizeof&lt;/span>(&lt;span style="color:#66d9ef">float&lt;/span>)));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//第二个2，和上面第一个参数，也就是 vs文件中的location一致，
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//表示要启用 2 号属性（对应纹理）
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>glEnableVertexAttribArray(&lt;span style="color:#ae81ff">2&lt;/span>);&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>&lt;mark>内存布局的可视化演示&lt;/mark>
&lt;ul>
&lt;li>Cherno 使用画图工具展示了数据在内存中是如何排列的。&lt;/li>
&lt;li>如果每个顶点只有 2 个 &lt;code>float&lt;/code> 坐标，那么 &lt;mark>Stride&lt;/mark> 就是 &lt;code>2 * sizeof(float)&lt;/code>。&lt;/li>
&lt;li>如果以后加上纹理坐标（又多 2 个 &lt;code>float&lt;/code>），&lt;mark>Stride&lt;/mark> 就会变成 &lt;code>4 * sizeof(float)&lt;/code>。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;mark>启用顶点属性 (&lt;code>glEnableVertexAttribArray&lt;/code>)&lt;/mark>
&lt;ul>
&lt;li>仅仅定义了“说明书”还不够，必须通过这个函数&lt;mark>手动开启对应的属性索引&lt;/mark>（本例中是 0）。&lt;/li>
&lt;li>默认情况下，为了性能，所有属性槽位都是关闭的。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;mark>链接缓冲区与属性&lt;/mark>
&lt;ul>
&lt;li>强调了一个隐式逻辑：&lt;code>glVertexAttribPointer&lt;/code> 关联的是&lt;mark>当前绑定在 &lt;code>GL_ARRAY_BUFFER&lt;/code> 上的那个 ID&lt;/mark>。&lt;/li>
&lt;li>这再次体现了 OpenGL “状态机”的特性。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;mark>总结与运行结果&lt;/mark>
&lt;ul>
&lt;li>加上这两行关键代码后，配合默认或简单的着色器，屏幕上终于能画出三角形的轮廓了。&lt;/li>
&lt;li>预告：下一集将进入 &lt;mark>Shader (着色器)&lt;/mark> 的世界，那是真正赋予图形灵魂的地方。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>核心操作流程（接续你的代码）&lt;/li>
&lt;/ul>
&lt;p>在你的 &lt;code>glBufferData&lt;/code> 之后，必须补充这两行，否则 &lt;code>glDrawArrays&lt;/code> 找不到数据：&lt;/p></description></item><item><title>04顶点缓冲区和绘制三角形</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/04%E9%A1%B6%E7%82%B9%E7%BC%93%E5%86%B2%E5%8C%BA%E5%92%8C%E7%BB%98%E5%88%B6%E4%B8%89%E8%A7%92%E5%BD%A2/</link><pubDate>Sun, 22 Mar 2026 16:24:13 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/04%E9%A1%B6%E7%82%B9%E7%BC%93%E5%86%B2%E5%8C%BA%E5%92%8C%E7%BB%98%E5%88%B6%E4%B8%89%E8%A7%92%E5%BD%A2/</guid><description>&lt;h1 id="总述">总述&lt;a class="anchor" href="#%e6%80%bb%e8%bf%b0">#&lt;/a>&lt;/h1>
&lt;p>这一集（时长 20:06）标志着你正式从“配置环境”跨越到了“图形编程”。这里讲解了现代 OpenGL 的灵魂：&lt;mark>如何把 CPU 内存里的数据塞进 GPU 显存里&lt;/mark>。&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;mark>什么是顶点缓冲区 (Vertex Buffer / VBO)？&lt;/mark>&lt;/p>
&lt;ul>
&lt;li>&lt;mark>核心定义&lt;/mark>：它本质上是 GPU 显存中的一块内存区域，用于存储顶点的各种数据（坐标、颜色等）。&lt;/li>
&lt;li>&lt;mark>为什么要用它？&lt;/mark>：CPU 到 GPU 的总线传输相对较慢。我们希望一次性把大量数据推送到显存，然后让 GPU 在内部高速读取，而不是每帧都由 CPU “手把手”教 GPU 怎么画。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;mark>定义顶点数据&lt;/mark>&lt;/p>
&lt;ul>
&lt;li>在 C++ 中创建一个浮点数数组 &lt;code>float positions[6]&lt;/code>，存入三个顶点的 坐标。&lt;/li>
&lt;li>注意：此时这些数据还在 &lt;mark>RAM（内存）&lt;/mark> 中，GPU 还看不见它们。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;mark>生成缓冲区 ID (&lt;code>glGenBuffers&lt;/code>)&lt;/mark>&lt;/p>
&lt;ul>
&lt;li>OpenGL 是基于状态机的。我们不直接操作对象指针，而是操作 &lt;mark>ID (unsigned int)&lt;/mark>。&lt;/li>
&lt;li>调用 &lt;code>glGenBuffers(1, &amp;amp;buffer)&lt;/code>：向 OpenGL 申请一个 ID，代表我们要创建的一个缓冲区。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;mark>绑定缓冲区 (&lt;code>glBindBuffer&lt;/code>)&lt;/mark>&lt;/p>
&lt;ul>
&lt;li>这是初学者最容易晕的地方。OpenGL 像是一个只有一个插槽的播放器。&lt;/li>
&lt;li>调用 &lt;code>glBindBuffer(GL_ARRAY_BUFFER, buffer)&lt;/code>：告诉 OpenGL，“从现在起，所有关于 &lt;code>GL_ARRAY_BUFFER&lt;/code> 的操作，都请针对我刚刚申请的这个 &lt;code>buffer&lt;/code> 进行”。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;mark>填充数据 (&lt;code>glBufferData&lt;/code>)&lt;/mark>&lt;/p>
&lt;ul>
&lt;li>这是真正发生“搬运”动作的代码。&lt;/li>
&lt;li>参数解释：
&lt;ol>
&lt;li>目标：&lt;code>GL_ARRAY_BUFFER&lt;/code>。&lt;/li>
&lt;li>大小：&lt;code>6 * sizeof(float)&lt;/code>。&lt;/li>
&lt;li>数据指针：&lt;code>positions&lt;/code>。&lt;/li>
&lt;li>使用方式：&lt;code>GL_STATIC_DRAW&lt;/code>（告诉显卡：这些数据我画一次后基本不动了，请优化存储）。&lt;/li>
&lt;/ol>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;mark>为什么屏幕上还是没东西？&lt;/mark>&lt;/p></description></item><item><title>01-03基础配置</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/01-03%E5%9F%BA%E7%A1%80%E9%85%8D%E7%BD%AE/</link><pubDate>Sat, 21 Mar 2026 19:43:45 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/cherno_opengl/01-03%E5%9F%BA%E7%A1%80%E9%85%8D%E7%BD%AE/</guid><description>&lt;h1 id="01-03基础配置">01-03基础配置&lt;a class="anchor" href="#01-03%e5%9f%ba%e7%a1%80%e9%85%8d%e7%bd%ae">#&lt;/a>&lt;/h1>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>16:04&lt;span style="color:#f92672">]&lt;/span> 01-欢迎来到 OpenGL-Welcome to OpenGL
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>22:03&lt;span style="color:#f92672">]&lt;/span> 02-在 C++ 中设置 OpenGL 并创建窗口-Setting up OpenGL and Creating a Window in C++
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>18:21&lt;span style="color:#f92672">]&lt;/span> 03-在 C++ 中使用现代 OpenGL-Using Modern OpenGL in C++
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>20:06&lt;span style="color:#f92672">]&lt;/span> 04-顶点缓冲区与在 OpenGL 中绘制三角形-Vertex Buffers and Drawing a Triangle in OpenGL
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>18:54&lt;span style="color:#f92672">]&lt;/span> 05-OpenGL 中的顶点属性与布局-Vertex Attributes and Layouts in OpenGL
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>17:37&lt;span style="color:#f92672">]&lt;/span> 06-OpenGL 中的着色器工作原理-How Shaders Work in OpenGL
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>28:21&lt;span style="color:#f92672">]&lt;/span> 07-在 OpenGL 中编写着色器-Writing a Shader in OpenGL
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>21:15&lt;span style="color:#f92672">]&lt;/span> 08-我是如何处理 OpenGL 中的着色器的-How I Deal with Shaders in OpenGL
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>16:54&lt;span style="color:#f92672">]&lt;/span> 09-OpenGL 中的索引缓冲区-Index Buffers in OpenGL
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>23:42&lt;span style="color:#f92672">]&lt;/span> 10-处理 OpenGL 中的错误-Dealing with Errors in OpenGL
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>11:27&lt;span style="color:#f92672">]&lt;/span> 11-OpenGL 中的 Uniform 变量-Uniforms in OpenGL
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>21:50&lt;span style="color:#f92672">]&lt;/span> 12-OpenGL 中的顶点数组-Vertex Arrays in OpenGL
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>26:46&lt;span style="color:#f92672">]&lt;/span> 13-将 OpenGL 抽象为类-Abstracting OpenGL into Classes
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>30:07&lt;span style="color:#f92672">]&lt;/span> 14-OpenGL 中的缓冲区布局抽象-Buffer Layout Abstraction in OpenGL
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>21:56&lt;span style="color:#f92672">]&lt;/span> 15-OpenGL 中的着色器抽象-Shader Abstraction in OpenGL
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>14:43&lt;span style="color:#f92672">]&lt;/span> 16-在 OpenGL 中编写基础渲染器-Writing a Basic Renderer in OpenGL
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>31:44&lt;span style="color:#f92672">]&lt;/span> 17-OpenGL 中的纹理-Textures in OpenGL
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>12:37&lt;span style="color:#f92672">]&lt;/span> 18-OpenGL 中的混合-Blending in OpenGL
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>18:07&lt;span style="color:#f92672">]&lt;/span> 19-OpenGL 中的数学-Maths in OpenGL
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>20:10&lt;span style="color:#f92672">]&lt;/span> 20-OpenGL 中的投影矩阵-Projection Matrices in OpenGL
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>15:53&lt;span style="color:#f92672">]&lt;/span> 21-OpenGL 中的 MVP 矩阵-Model View Projection Matrices in OpenGL
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>14:36&lt;span style="color:#f92672">]&lt;/span> 22-在 OpenGL 中使用 ImGui-ImGui in OpenGL
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>12:21&lt;span style="color:#f92672">]&lt;/span> 23-在 OpenGL 中渲染多个物体-Rendering Multiple Objects in OpenGL
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>16:52&lt;span style="color:#f92672">]&lt;/span> 24-为 OpenGL 设置测试框架-Setting up a Test Framework &lt;span style="color:#66d9ef">for&lt;/span> OpenGL
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>22:46&lt;span style="color:#f92672">]&lt;/span> 25-在 OpenGL 中创建测试-Creating Tests in OpenGL
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>28:13&lt;span style="color:#f92672">]&lt;/span> 26-在 OpenGL 中进行纹理测试-Creating a Texture Test in OpenGL
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>11:37&lt;span style="color:#f92672">]&lt;/span> 27-如何让你的 UNIFORMS 更快-How to make your UNIFORMS FASTER in OpenGL
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>12:25&lt;span style="color:#f92672">]&lt;/span> 28-批量渲染：简介-Batch Rendering - An Introduction
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>09:15&lt;span style="color:#f92672">]&lt;/span> 29-批量渲染：颜色-Batch Rendering - Colors
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>15:51&lt;span style="color:#f92672">]&lt;/span> 30-批量渲染：纹理-Batch Rendering - Textures
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">[&lt;/span>23:17&lt;span style="color:#f92672">]&lt;/span> 31-批量渲染：动态几何体-Batch Rendering - Dynamic Geometry&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h1 id="01welcomtoopengl">01WelcomToOpengl&lt;a class="anchor" href="#01welcomtoopengl">#&lt;/a>&lt;/h1>
&lt;ul>
&lt;li>课程初衷与背景
&lt;ul>
&lt;li>作者介绍了为什么要制作这个系列：市面上很多教程只教“怎么做”，而不教“为什么”。&lt;/li>
&lt;li>本系列的目标是不仅让你写出代码，还要让你理解 OpenGL 的底层工作原理及其与 GPU 的交互逻辑。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>什么是 OpenGL？
&lt;ul>
&lt;li>&lt;mark>核心定义&lt;/mark>：OpenGL 本质上是一个&lt;mark>规范（Specification）&lt;/mark> &lt;del>接口，API&lt;/del> ，而不是一个具体的库。它&lt;mark>规定了函数应该如何命名、参数是什么以及预期的行为&lt;/mark>。 &lt;del>允许我们实际访问我们的GPU，GraphicsProcessingUnit，图形处理单元。OpenGL是其中之一的接口，还有Direct3D，Vulkan，Metal等&lt;/del>&lt;/li>
&lt;li>&lt;mark>实现者&lt;/mark>：&lt;em>具体的实现通常由显卡厂商（NVIDIA, AMD, Intel）编写在驱动程序中&lt;/em>。&lt;/li>
&lt;li>跨平台，Windows、Linux、Mac、Android、IOS&lt;/li>
&lt;li>&lt;mark>状态机（State Machine）&lt;/mark>：初步引入 &lt;mark>OpenGL 是一个状态机&lt;/mark>的概念，你&lt;mark>设置好状态（如颜色、缓冲区），然后发出指令进行渲染&lt;/mark>。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>现代 OpenGL vs 传统 OpenGL
&lt;ul>
&lt;li>&lt;mark>Legacy OpenGL (固定管线)&lt;/mark>：简单但不够灵活，很多功能已经废弃（如 &lt;code>glBegin&lt;/code>, &lt;code>glEnd&lt;/code>）。&lt;/li>
&lt;li>&lt;mark>Modern OpenGL (可编程管线)&lt;/mark>：通过 &lt;mark>着色器 (Shaders)&lt;/mark> 控制渲染过程。虽然代码量显著增加，但提供了&lt;mark>极大的灵活性和性能优化&lt;/mark>空间。&lt;/li>
&lt;li>Shader（着色器）是程序，在==&lt;em>GPU&lt;/em>==上运行的代码程序&lt;/li>
&lt;li>本系列将专注于 &lt;mark>Modern OpenGL (版本 3.3 及以上)&lt;/mark>。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>学习 OpenGL 的难点与心态
&lt;ul>
&lt;li>强调了学习曲线：初期需要写==大量的“模板代码”（Boilerplate code）==才能在屏幕上画出一个简单的三角形。&lt;/li>
&lt;li>&lt;mark>图形管线 (Graphics Pipeline)&lt;/mark>：解释了&lt;mark>数据如何从 CPU 传输到 GPU&lt;/mark>，并&lt;mark>经过顶点处理、光栅化最终变成像素&lt;/mark>的过程。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>开发环境与工具链预告
&lt;ul>
&lt;li>虽然是跨平台的，但本系列主要在 Windows 下使用 &lt;mark>Visual Studio&lt;/mark> 演示。&lt;/li>
&lt;li>提到后续会使用的关键第三方库：&lt;mark>GLFW&lt;/mark>（窗口管理）和 &lt;mark>GLEW/GLAD&lt;/mark>（访问 OpenGL 扩展函数）。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>总结与后续计划
&lt;ul>
&lt;li>鼓励初学者不要被前几集的复杂配置和概念吓退。&lt;/li>
&lt;li>下一集将正式进入 C++ 环境配置，动手创建第一个窗口。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h1 id="02设置opengl和创建窗口">02设置Opengl和创建窗口&lt;a class="anchor" href="#02%e8%ae%be%e7%bd%aeopengl%e5%92%8c%e5%88%9b%e5%bb%ba%e7%aa%97%e5%8f%a3">#&lt;/a>&lt;/h1>
&lt;p>主要讲解如何在 Windows 环境下使用 Visual Studio 搭建 OpenGL 开发环境，并编写代码成功弹出一个窗口。这是所有图形编程的起点。&lt;/p></description></item><item><title>09Camera</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/learnopengl/01%E5%85%A5%E9%97%A8/09Camera/</link><pubDate>Thu, 19 Mar 2026 08:01:16 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/learnopengl/01%E5%85%A5%E9%97%A8/09Camera/</guid><description>&lt;h1 id="camera">Camera&lt;a class="anchor" href="#camera">#&lt;/a>&lt;/h1>
&lt;p>上一章我们讨论了视图矩阵以及如何利用视图矩阵在场景中移动（我们稍微向后移动了一下）。OpenGL 本身并不支持 &lt;em>摄像机&lt;/em> 的概念，但我们可以尝试通过&lt;mark>反向移动场景中的所有物体来模拟摄像机，从而产生&lt;em>移动&lt;/em>的错觉&lt;/mark>。&lt;/p>
&lt;p>本章我们将讨论如何在 OpenGL 中设置摄像机。我们将介绍一种&lt;mark>飞行式摄像机&lt;/mark>，它允许您&lt;mark>在 3D 场景中自由移动&lt;/mark>。我们还&lt;mark>将讨论键盘和鼠标输入&lt;/mark>，最后介绍&lt;mark>自定义摄像机类&lt;/mark>。&lt;/p>
&lt;h1 id="相机视图空间-cameraview-space">相机/视图空间 Camera/View space&lt;a class="anchor" href="#%e7%9b%b8%e6%9c%ba%e8%a7%86%e5%9b%be%e7%a9%ba%e9%97%b4-cameraview-space">#&lt;/a>&lt;/h1>
&lt;p>当我们讨论相机/视图空间时，指的是从相机视角（即场景原点）观察到的所有顶点坐标：视图矩阵将所有&lt;mark>世界坐标&lt;/mark>转换为&lt;mark>相对于&lt;em>相机位置和方向&lt;/em>的视图坐标&lt;/mark>。要&lt;em>定义一个相机&lt;/em>，我们需要它在世界空间中的位置、它的朝向、一个指向右侧的向量和一个指向上方的向量。细心的读者可能会注意到，我们实际上要&lt;mark>创建一个以&lt;em>相机位置&lt;/em>为原点的、具有&lt;em>三个相互垂直的单位轴&lt;/em>的坐标系&lt;/mark>。 &lt;del>本章前面的目的，那转换成这个坐标系的目的又是什么呢？&amp;mdash;&amp;gt;(重要★★)：世界上所有的物体坐标都重新量一遍，量出它们==&lt;em>相对&lt;/em>于你&lt;em>眼睛&lt;/em>的&lt;em>距离和角度&lt;/em>==。&lt;/del>&lt;br>
&lt;del>注意下面这个公式：&lt;/del>&lt;br>
&lt;/p>

 
 
 
 
 
 &lt;div class="math-block" style="margin: 1rem 0; overflow-x: auto;">
 
 &lt;span class="katex">&lt;math xmlns="http://www.w3.org/1998/Math/MathML" display="block">&lt;semantics>&lt;mtable rowspacing="0.16em" columnalign="center" columnspacing="1em">&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mrow>&lt;mi>L&lt;/mi>&lt;mi>o&lt;/mi>&lt;mi>o&lt;/mi>&lt;mi>k&lt;/mi>&lt;mi>A&lt;/mi>&lt;mi>t&lt;/mi>&lt;mo>=&lt;/mo>&lt;mrow>&lt;mo fence="true">[&lt;/mo>&lt;mtable rowspacing="0.16em" columnalign="center center center center" columnspacing="1em">&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mstyle mathcolor="red">&lt;msub>&lt;mi>R&lt;/mi>&lt;mi>x&lt;/mi>&lt;/msub>&lt;/mstyle>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mstyle mathcolor="red">&lt;msub>&lt;mi>R&lt;/mi>&lt;mi>y&lt;/mi>&lt;/msub>&lt;/mstyle>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mstyle mathcolor="red">&lt;msub>&lt;mi>R&lt;/mi>&lt;mi>z&lt;/mi>&lt;/msub>&lt;/mstyle>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mn>0&lt;/mn>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mstyle mathcolor="green">&lt;msub>&lt;mi>U&lt;/mi>&lt;mi>x&lt;/mi>&lt;/msub>&lt;/mstyle>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mstyle mathcolor="green">&lt;msub>&lt;mi>U&lt;/mi>&lt;mi>y&lt;/mi>&lt;/msub>&lt;/mstyle>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mstyle mathcolor="green">&lt;msub>&lt;mi>U&lt;/mi>&lt;mi>z&lt;/mi>&lt;/msub>&lt;/mstyle>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mn>0&lt;/mn>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mstyle mathcolor="blue">&lt;msub>&lt;mi>D&lt;/mi>&lt;mi>x&lt;/mi>&lt;/msub>&lt;/mstyle>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mstyle mathcolor="blue">&lt;msub>&lt;mi>D&lt;/mi>&lt;mi>y&lt;/mi>&lt;/msub>&lt;/mstyle>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mstyle mathcolor="blue">&lt;msub>&lt;mi>D&lt;/mi>&lt;mi>z&lt;/mi>&lt;/msub>&lt;/mstyle>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mn>0&lt;/mn>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mn>0&lt;/mn>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mn>0&lt;/mn>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mn>0&lt;/mn>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mn>1&lt;/mn>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;/mtable>&lt;mo fence="true">]&lt;/mo>&lt;/mrow>&lt;mo>∗&lt;/mo>&lt;mrow>&lt;mo fence="true">[&lt;/mo>&lt;mtable rowspacing="0.16em" columnalign="center center center center" columnspacing="1em">&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mn>1&lt;/mn>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mn>0&lt;/mn>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mn>0&lt;/mn>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mrow>&lt;mo>−&lt;/mo>&lt;mstyle mathcolor="purple">&lt;msub>&lt;mi>P&lt;/mi>&lt;mi>x&lt;/mi>&lt;/msub>&lt;/mstyle>&lt;/mrow>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mn>0&lt;/mn>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mn>1&lt;/mn>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mn>0&lt;/mn>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mrow>&lt;mo>−&lt;/mo>&lt;mstyle mathcolor="purple">&lt;msub>&lt;mi>P&lt;/mi>&lt;mi>y&lt;/mi>&lt;/msub>&lt;/mstyle>&lt;/mrow>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mn>0&lt;/mn>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mn>0&lt;/mn>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mn>1&lt;/mn>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mrow>&lt;mo>−&lt;/mo>&lt;mstyle mathcolor="purple">&lt;msub>&lt;mi>P&lt;/mi>&lt;mi>z&lt;/mi>&lt;/msub>&lt;/mstyle>&lt;/mrow>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mn>0&lt;/mn>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mn>0&lt;/mn>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mn>0&lt;/mn>&lt;/mstyle>&lt;/mtd>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mn>1&lt;/mn>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;/mtable>&lt;mo fence="true">]&lt;/mo>&lt;/mrow>&lt;/mrow>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;/mtable>&lt;annotation encoding="application/x-tex">\begin{array}{c} LookAt = \begin{bmatrix} \color{red}{R_x} &amp;amp; \color{red}{R_y} &amp;amp; \color{red}{R_z} &amp;amp; 0 \\ \color{green}{U_x} &amp;amp; \color{green}{U_y} &amp;amp; \color{green}{U_z} &amp;amp; 0 \\ \color{blue}{D_x} &amp;amp; \color{blue}{D_y} &amp;amp; \color{blue}{D_z} &amp;amp; 0 \\ 0 &amp;amp; 0 &amp;amp; 0 &amp;amp; 1 \end{bmatrix} * \begin{bmatrix} 1 &amp;amp; 0 &amp;amp; 0 &amp;amp; -\color{purple}{P_x} \\ 0 &amp;amp; 1 &amp;amp; 0 &amp;amp; -\color{purple}{P_y} \\ 0 &amp;amp; 0 &amp;amp; 1 &amp;amp; -\color{purple}{P_z} \\ 0 &amp;amp; 0 &amp;amp; 0 &amp;amp; 1 \end{bmatrix} \end{array}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>
 
 &lt;/div>
&lt;p>左边这个矩阵，是用以观察点为原点的坐标系算出的三个点，来形成新坐标系。由于矩阵 乘法是先进行右边的换算，P表示cameraPos，所以就是说，先乘以右边矩阵，把世界坐标系的原点移到和cameraPos重叠；再乘以左边矩阵，得出在新坐标系下以cameraPos为原点，他的坐标该是多少（也就是换算成了原坐标系下的每个点，在新坐标系下与cameraPos的x,y,z的距离）&lt;/p></description></item><item><title>08Coordinate-Systems</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/learnopengl/01%E5%85%A5%E9%97%A8/08Coordinate-Systems/</link><pubDate>Wed, 18 Mar 2026 16:01:39 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/learnopengl/01%E5%85%A5%E9%97%A8/08Coordinate-Systems/</guid><description>&lt;h1 id="坐标系-coordinate-systems">坐标系 Coordinate Systems&lt;a class="anchor" href="#%e5%9d%90%e6%a0%87%e7%b3%bb-coordinate-systems">#&lt;/a>&lt;/h1>
&lt;p>上一章我们学习了如何利用变换矩阵来变换所有顶点，从而更好地使用矩阵。OpenGL 要求所有我们希望可见的顶点在每次&lt;mark>顶点着色器运行后&lt;/mark>都处于&lt;mark>归一化设备坐标系&lt;/mark>中。也就是说，每个顶点的 &lt;code>x&lt;/code> 、 &lt;code>y&lt;/code> 和 &lt;code>z&lt;/code> 坐标应该介于 &lt;code>-1.0&lt;/code> 和 &lt;code>1.0&lt;/code> 之间；超出此范围的坐标将不可见。通常的做法是，我们指定一个自定义范围（或空间）内的坐标，然后&lt;mark>在顶点着色器中将这些坐标变换为&lt;em>归一化设备坐标 (NDC)&lt;/em>&lt;/mark>。之后，这些 NDC 坐标会被传递给光栅化器，由光栅化器将其转换为屏幕上的二维坐标/像素。&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>顶点着色器：处理的是“点”（数学上的位置）。
光栅化器：把“点”连接成“面”，并把这个“面”切碎成一个个对应屏幕像素的“小方块”（片段）。 &lt;del>确定哪些像素属于这个图形&lt;/del>
片段着色器：给这些被选中的“小方块”涂上最终的颜色。&lt;/p>&lt;/blockquote>&lt;p>将坐标系转换为 NDC 坐标系通常是分步进行的，即先将对象的顶点转换到多个坐标系，最后再转换到 NDC &lt;em>坐标&lt;/em> 系。这样做的好处是，&lt;mark>某些操作/计算在某些坐标系中更容易进行&lt;/mark>，这一点很快就会显现出来。总共有 5 个对我们来说比较重要的坐标系：&lt;/p>
&lt;ul>
&lt;li>&lt;em>局部空间（或对象空间）&lt;/em> &lt;del>Local Space，也称物体空间&lt;/del>&lt;/li>
&lt;li>&lt;em>世界空间&lt;/em> &lt;del>World Space，乘以 Model Matrix (模型矩阵)后得到。把所有物体放到同一个大地图里，有了各自的相对位置&lt;/del>&lt;/li>
&lt;li>&lt;em>视野空间（或眼睛空间）&lt;/em> &lt;del>乘以 View Matrix (观察矩阵)后得到。状态： 以摄像机为原点(0,0,0)，看所有物体在哪里。类比： 摄影师扛起摄像机对准桌子。现在的坐标不再是“距离片场中心多远”，而是“距离镜头多远、在镜头画面的左边还是右边”&lt;/del>&lt;/li>
&lt;li>&lt;em>裁剪空间&lt;/em> &lt;del>乘以 Projection Matrix (投影矩阵)后得到。状态： 决定哪些东西在镜头里，哪些在镜头外。类比： 摄影师决定用“广角镜头”还是“长焦镜头”。在这个空间里，超出镜头范围的物体（比如演员伸到画面外的手）会被直接剪掉。透视除法也发生在这个阶段之后。&lt;/del>&lt;/li>
&lt;li>&lt;em>屏幕空间&lt;/em> &lt;del>Viewport Transform (视口变换)。状态： 将坐标映射到显示器的实际像素（如 
 

 
 
 
 
 
 
 &lt;span class="katex">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mn>1920&lt;/mn>&lt;mo>×&lt;/mo>&lt;mn>1080&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">1920 \times 1080&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>
 ）。类比： 电影最后投映在电影院的银幕上。&lt;em>原本的比例坐标变成了具体的像素点位置&lt;/em>&lt;/del>&lt;/li>
&lt;/ul>
&lt;p>这些都是顶点在最终变成片段之前会经历的不同状态。&lt;/p>
&lt;p>你现在可能对空间或坐标系究竟是什么感到很困惑，所以我们首先会&lt;mark>以更宏观的方式解释它们&lt;/mark>，展示&lt;mark>整体情况&lt;/mark>以及&lt;mark>每个特定空间所代表的含义&lt;/mark>。&lt;/p>
&lt;h1 id="全球概况-the-global-picture">全球概况 The global picture&lt;a class="anchor" href="#%e5%85%a8%e7%90%83%e6%a6%82%e5%86%b5-the-global-picture">#&lt;/a>&lt;/h1>
&lt;p>为了将坐标从一个空间转换到另一个坐标空间，我们将使用几个变换矩阵，其中最重要的包括==&lt;em>模型矩阵&lt;/em>&lt;mark>、&lt;/mark>&lt;em>视图矩阵&lt;/em>&lt;mark>和&lt;/mark>&lt;em>投影矩阵&lt;/em>&lt;mark>。我们的顶点坐标首先在&lt;em>局部空间&lt;/em>中作为局部坐标出现，然后进一步转换为&lt;/mark>*世界坐标 &lt;del>World Coordinates&lt;/del> 、视图坐标 &lt;del>View Coordinates)&lt;/del> 、裁剪坐标 &lt;del>Clip Coordinates&lt;/del> ，最终转换为屏幕坐标 &lt;del>Screen Coordinates&lt;/del> *==。下图展示了这一过程，并说明了每个变换的作用：&lt;/p></description></item><item><title>07Transformations</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/learnopengl/01%E5%85%A5%E9%97%A8/07Transformations/</link><pubDate>Tue, 17 Mar 2026 08:58:33 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/learnopengl/01%E5%85%A5%E9%97%A8/07Transformations/</guid><description>&lt;h1 id="转变-transformations">转变 Transformations&lt;a class="anchor" href="#%e8%bd%ac%e5%8f%98-transformations">#&lt;/a>&lt;/h1>
&lt;p>我们现在知道如何创建物体，给它们着色，或者用纹理赋予它们精细的外观，但它们仍然不够有趣，因为它们都是静态物体。我们可以&lt;mark>尝试通过改变它们的顶点并在每一帧重新配置它们的缓冲区来让它们动起来&lt;/mark>，但这很麻烦，而且会消耗大量的处理能力。其实有更好的方法来变换物体，那就是&lt;mark>使用（多个）矩阵对象&lt;/mark>。但这并不意味着我们要讨论功夫和庞大的数字人工世界。&lt;/p>
&lt;p>矩阵是非常强大的数学结构，乍一看似乎令人生畏，但一旦你熟悉了它们，就会发现它们非常有用。在讨论矩阵时，我们需要&lt;mark>稍微了解一些数学知识&lt;/mark>，对于数学基础较好的读者，我还会提供一些额外的阅读资源。&lt;/p>
&lt;p>然而，要充分理解&lt;mark>变换&lt;/mark>，我们首先需要深入研究&lt;mark>向量&lt;/mark>，然后再讨论&lt;mark>矩阵&lt;/mark>。本章的重点是为你提供一些我们后续会用到的&lt;mark>基本数学知识&lt;/mark>。如果这些内容比较难懂，请&lt;mark>尽量理解&lt;/mark>它们，并在需要时再回到本章复习相关概念。&lt;/p>
&lt;h1 id="向量-vectors">向量 Vectors&lt;a class="anchor" href="#%e5%90%91%e9%87%8f-vectors">#&lt;/a>&lt;/h1>
&lt;p>从最基本的定义来看，&lt;mark>向量就是方向&lt;/mark>，仅此而已。&lt;mark>向量具有&lt;em>方向和大小&lt;/em>（也称为强度或长度）&lt;/mark>。你可以把向量想象成藏宝图上的指示：“向左走10步，然后向北走3步，再向右走5步”；这里，“向左”是方向，“10步”是向量的大小。因此，藏宝图上的指示包含了3个向量。向量可以是任意维度，但我们通常处理的是&lt;mark>2到4维的向量&lt;/mark>。&lt;mark>二维向量&lt;/mark>表示平面上的一个方向（想想二维图形），&lt;mark>三维向量&lt;/mark>可以表示&lt;mark>三维空间&lt;/mark>中的任何方向。&lt;/p>
&lt;p>下面你会看到三个向量，每个向量在二维图中用 
 

 
 
 
 
 
 
 &lt;span class="katex">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mo stretchy="false">(&lt;/mo>&lt;mi>x&lt;/mi>&lt;mo separator="true">,&lt;/mo>&lt;mi>y&lt;/mi>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">(x,y)&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>
 表示为箭头。由于二维（而非三维）表示向量更直观，你可以将这些二维向量视为 
 

 
 
 
 
 
 
 &lt;span class="katex">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mi>z&lt;/mi>&lt;/mrow>&lt;annotation encoding="application/x-tex">z&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>
 坐标为 
 

 
 
 
 
 
 
 &lt;span class="katex">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mn>0&lt;/mn>&lt;/mrow>&lt;annotation encoding="application/x-tex">0&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>
 的三维向量。由于向量表示方向，向量的原点不会改变其值。在下面的图中我们可以看到，向量 
 

 
 
 
 
 
 
 &lt;span class="katex">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mstyle mathcolor="red">&lt;mover accent="true">&lt;mi>v&lt;/mi>&lt;mo>ˉ&lt;/mo>&lt;/mover>&lt;/mstyle>&lt;/mrow>&lt;annotation encoding="application/x-tex">\color{red}{\bar{v}}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>
 和 
 

 
 
 
 
 
 
 &lt;span class="katex">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mstyle mathcolor="blue">&lt;mover accent="true">&lt;mi>w&lt;/mi>&lt;mo>ˉ&lt;/mo>&lt;/mover>&lt;/mstyle>&lt;/mrow>&lt;annotation encoding="application/x-tex">\color{blue}{\bar{w}}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>
 即使原点不同，它们的值也相等：&lt;/p>
&lt;p>&lt;img src="img/ly-20260317155945571.png" alt="" />&lt;/p>
&lt;p>数学家在描述向量时，通常倾向于使用&lt;mark>头顶带横线的字符符号，例如 
 

 
 
 
 
 
 
 &lt;span class="katex">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mover accent="true">&lt;mi>v&lt;/mi>&lt;mo>ˉ&lt;/mo>&lt;/mover>&lt;/mrow>&lt;annotation encoding="application/x-tex">{\bar{v}}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>
 &lt;/mark> 此外，在&lt;mark>公式中表示向量时，通常也采用以下方式&lt;/mark>：&lt;/p>

 
 
 
 
 
 &lt;div class="math-block" style="margin: 1rem 0; overflow-x: auto;">
 
 &lt;span class="katex">&lt;math xmlns="http://www.w3.org/1998/Math/MathML" display="block">&lt;semantics>&lt;mrow>&lt;mover accent="true">&lt;mi>v&lt;/mi>&lt;mo>ˉ&lt;/mo>&lt;/mover>&lt;mo>=&lt;/mo>&lt;mrow>&lt;mo fence="true">(&lt;/mo>&lt;mtable rowspacing="0.16em" columnalign="center" columnspacing="1em">&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mstyle mathcolor="red">&lt;mi>x&lt;/mi>&lt;/mstyle>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mstyle mathcolor="green">&lt;mi>y&lt;/mi>&lt;/mstyle>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mstyle mathcolor="blue">&lt;mi>z&lt;/mi>&lt;/mstyle>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;/mtable>&lt;mo fence="true">)&lt;/mo>&lt;/mrow>&lt;/mrow>&lt;annotation encoding="application/x-tex">\bar{v} = \begin{pmatrix} \color{red}x \\ \color{green}y \\ \color{blue}z \end{pmatrix}&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>
 
 &lt;/div>
&lt;p>由于&lt;mark>向量被定义为方向，因此有时很难将其可视化为位置&lt;/mark>。如果我们想&lt;mark>将向量可视化为位置，我们可以将&lt;em>方向向量&lt;/em>的&lt;em>原点&lt;/em>想象为 
 

 
 
 
 
 
 
 &lt;span class="katex">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mo stretchy="false">(&lt;/mo>&lt;mn>0&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>0&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>0&lt;/mn>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">(0,0,0)&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>
 &lt;/mark> ，然后&lt;mark>指向某个特定方向&lt;/mark>来确定该点，从而使其成为&lt;mark>位置向量&lt;/mark>（我们也可以指定不同的原点，然后说：“该向量从该原点指向空间中的该点”）。位置向量 
 

 
 
 
 
 
 
 &lt;span class="katex">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mo stretchy="false">(&lt;/mo>&lt;mn>3&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>5&lt;/mn>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">(3,5)&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>
 则指向原点为 
 

 
 
 
 
 
 
 &lt;span class="katex">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mo stretchy="false">(&lt;/mo>&lt;mn>0&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>0&lt;/mn>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">(0,0)&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>
 的图上的 
 

 
 
 
 
 
 
 &lt;span class="katex">&lt;math xmlns="http://www.w3.org/1998/Math/MathML">&lt;semantics>&lt;mrow>&lt;mo stretchy="false">(&lt;/mo>&lt;mn>3&lt;/mn>&lt;mo separator="true">,&lt;/mo>&lt;mn>5&lt;/mn>&lt;mo stretchy="false">)&lt;/mo>&lt;/mrow>&lt;annotation encoding="application/x-tex">(3,5)&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>
 点。因此，我们可以&lt;mark>利用向量来描述二维和三维空间中的&lt;em>方向&lt;/em>和&lt;em>位置&lt;/em>&lt;/mark>。&lt;/p>
&lt;p>就像对普通数字一样，我们也可以对向量定义几种运算（其中一些你已经见过了）。&lt;/p>
&lt;h2 id="标量向量运算-scalar-vector-operations">标量向量运算 Scalar vector operations&lt;a class="anchor" href="#%e6%a0%87%e9%87%8f%e5%90%91%e9%87%8f%e8%bf%90%e7%ae%97-scalar-vector-operations">#&lt;/a>&lt;/h2>
&lt;p>标量是一个数字。当对向量进行加/减/乘/除运算时，我们只需将向量的每个元素分别乘以或减去该标量即可。例如，加法运算如下所示：&lt;/p>

 
 
 
 
 
 &lt;div class="math-block" style="margin: 1rem 0; overflow-x: auto;">
 
 &lt;span class="katex">&lt;math xmlns="http://www.w3.org/1998/Math/MathML" display="block">&lt;semantics>&lt;mrow>&lt;mrow>&lt;mo fence="true">(&lt;/mo>&lt;mtable rowspacing="0.16em" columnalign="center" columnspacing="1em">&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mstyle mathcolor="red">&lt;mn>1&lt;/mn>&lt;/mstyle>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mstyle mathcolor="green">&lt;mn>2&lt;/mn>&lt;/mstyle>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mstyle mathcolor="blue">&lt;mn>3&lt;/mn>&lt;/mstyle>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;/mtable>&lt;mo fence="true">)&lt;/mo>&lt;/mrow>&lt;mo>+&lt;/mo>&lt;mi>x&lt;/mi>&lt;mo>→&lt;/mo>&lt;mrow>&lt;mo fence="true">(&lt;/mo>&lt;mtable rowspacing="0.16em" columnalign="center" columnspacing="1em">&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mstyle mathcolor="red">&lt;mn>1&lt;/mn>&lt;/mstyle>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mstyle mathcolor="green">&lt;mn>2&lt;/mn>&lt;/mstyle>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mstyle mathcolor="blue">&lt;mn>3&lt;/mn>&lt;/mstyle>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;/mtable>&lt;mo fence="true">)&lt;/mo>&lt;/mrow>&lt;mo>+&lt;/mo>&lt;mrow>&lt;mo fence="true">(&lt;/mo>&lt;mtable rowspacing="0.16em" columnalign="center" columnspacing="1em">&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mi>x&lt;/mi>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mi>x&lt;/mi>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mi>x&lt;/mi>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;/mtable>&lt;mo fence="true">)&lt;/mo>&lt;/mrow>&lt;mo>=&lt;/mo>&lt;mrow>&lt;mo fence="true">(&lt;/mo>&lt;mtable rowspacing="0.16em" columnalign="center" columnspacing="1em">&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mstyle mathcolor="red">&lt;mn>1&lt;/mn>&lt;mo>+&lt;/mo>&lt;mi>x&lt;/mi>&lt;/mstyle>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mstyle mathcolor="green">&lt;mn>2&lt;/mn>&lt;mo>+&lt;/mo>&lt;mi>x&lt;/mi>&lt;/mstyle>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;mtr>&lt;mtd>&lt;mstyle scriptlevel="0" displaystyle="false">&lt;mstyle mathcolor="blue">&lt;mn>3&lt;/mn>&lt;mo>+&lt;/mo>&lt;mi>x&lt;/mi>&lt;/mstyle>&lt;/mstyle>&lt;/mtd>&lt;/mtr>&lt;/mtable>&lt;mo fence="true">)&lt;/mo>&lt;/mrow>&lt;/mrow>&lt;annotation encoding="application/x-tex">
\begin{pmatrix} \color{red}1 \\ \color{green}2 \\ \color{blue}3 \end{pmatrix} + x \rightarrow 
\begin{pmatrix} \color{red}1 \\ \color{green}2 \\ \color{blue}3 \end{pmatrix} + 
\begin{pmatrix} x \\ x \\ x \end{pmatrix} = 
\begin{pmatrix} \color{red}1+x \\ \color{green}2+x \\ \color{blue}3+x \end{pmatrix}
&lt;/annotation>&lt;/semantics>&lt;/math>&lt;/span>
 
 &lt;/div>
&lt;p>其中 + 可以是 + 、 − 、 ⋅ 或 ÷ ，其中 ⋅ 是乘法运算符。&lt;/p></description></item><item><title>06Textures</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/learnopengl/01%E5%85%A5%E9%97%A8/06Textures/</link><pubDate>Mon, 16 Mar 2026 10:29:53 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/learnopengl/01%E5%85%A5%E9%97%A8/06Textures/</guid><description>&lt;h2 id="textures">Textures&lt;a class="anchor" href="#textures">#&lt;/a>&lt;/h2>
&lt;p>我们了解到，为了增加物体的细节，我们可以&lt;mark>为每个顶点设置颜色，从而创建一些有趣的图像&lt;/mark>。然而，为了获得相当逼真的效果，我们需要大量的顶点才能指定多种颜色。这会带来相当大的额外开销，因为每个模型都需要更多的顶点，并且每个顶点都需要一个颜色属性。&lt;/p>
&lt;p>&lt;mark>艺术家和程序员通常更喜欢使用纹理&lt;/mark>。纹理是一种二维图像（甚至还有一维和三维纹理），用于为物体添加细节；可以把纹理想象成&lt;mark>一张印有精美砖块图案（例如）的纸，将其整齐地折叠覆盖在你的三维房屋上，使房屋看起来像是有石头外墙一样&lt;/mark>。因为我们可以&lt;mark>在单个图像中添加大量细节，所以无需指定额外的顶点&lt;/mark>，就能使物体看起来极其精细。&lt;/p>
&lt;blockquote class='book-hint '>
&lt;p>除了图像之外，&lt;mark>纹理还可以用来存储大量任意数据，以便发送到着色器&lt;/mark>，但我们将把这留到另一个话题中讨论。&lt;/p>&lt;/blockquote>&lt;p>下面你会看到一张 
&lt;a target="_blank" href="https://learnopengl.com/img/textures/wall.jpg">砖墙&lt;/a>的纹理图像，它映射到上一章中的三角形上。&lt;/p>
&lt;p>&lt;img src="img/ly-20260316152107963.png" alt="" />&lt;/p>
&lt;p>为了将纹理映射到三角形，我们需要告诉三角形的每个顶点它对应纹理的哪一部分。因此，&lt;mark>每个顶点都应该&lt;em>关联&lt;/em>一个纹理坐标&lt;/mark>，该坐标指定要从纹理图像的哪一部分进行采样。然后，&lt;mark>片段插值会处理其他片段的剩余部分&lt;/mark>。 &lt;del>先画屏幕(三角形)，然后再去纹理图片那里采样&lt;/del>&lt;/p>
&lt;p>纹理坐标在 &lt;code>x&lt;/code> 轴和 &lt;code>y&lt;/code> 轴方向上的取值范围均为 &lt;code>0&lt;/code> 到 &lt;code>1&lt;/code> （请记住，我们使用的是&lt;mark>二维纹理图像&lt;/mark>）。&lt;mark>&lt;em>使用纹理坐标获取纹理颜色的过程称为采样&lt;/em>&lt;/mark>。纹理坐标从纹理图像左下角的 &lt;code>(0,0)&lt;/code> 开始，到纹理图像右上角的 &lt;code>(1,1)&lt;/code> 结束。下图展示了如何将纹理坐标映射到三角形：&lt;/p>
&lt;p>&lt;img src="img/ly-20260316152057967.png" alt="" />&lt;/p>
&lt;p>我们为三角形指定了 3 个纹理坐标点。我们希望三角形的左下角与纹理的左下角对应，因此我们使用纹理坐标 &lt;code>(0,0)&lt;/code> 作为三角形左下角顶点的坐标。右下角也一样，使用纹理坐标 &lt;code>(1,0)&lt;/code> 。三角形的顶部应与纹理图像的顶部中心对应，因此我们使用纹理坐标 &lt;code>(0.5,1.0)&lt;/code> 。我们只需将这 3 个纹理坐标传递给顶点着色器，顶点着色器再将这些坐标传递给片段着色器，片段着色器会对每个片段的所有纹理坐标进行插值。&lt;/p>
&lt;p>最终得到的纹理坐标如下所示：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">float&lt;/span> texCoords[] &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ae81ff">0.0f&lt;/span>, &lt;span style="color:#ae81ff">0.0f&lt;/span>, &lt;span style="color:#75715e">// lower-left corner 
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#ae81ff">1.0f&lt;/span>, &lt;span style="color:#ae81ff">0.0f&lt;/span>, &lt;span style="color:#75715e">// lower-right corner
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#ae81ff">0.5f&lt;/span>, &lt;span style="color:#ae81ff">1.0f&lt;/span> &lt;span style="color:#75715e">// top-center corner
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>};&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>纹理采样有多种不同的实现方式，其定义也比较宽泛。因此，我们的任务是&lt;mark>告诉 OpenGL 应该如何对纹理 &lt;em>进行采样&lt;/em>&lt;/mark> 。&lt;/p>
&lt;h2 id="texture-wrapping">Texture Wrapping&lt;a class="anchor" href="#texture-wrapping">#&lt;/a>&lt;/h2>
&lt;p>纹理坐标通常介于 &lt;code>(0,0)&lt;/code> 和 &lt;code>(1,1)&lt;/code> 之间，但如果我们指定超出此范围的坐标会发生什么？OpenGL 的默认行为是重复纹理图像（我们基本上忽略浮点纹理坐标的整数部分），但 OpenGL 还提供了更多选项：&lt;/p>
&lt;ul>
&lt;li>GL_REPEAT ：纹理的默认行为。重复纹理图像。 &lt;del>图片不断重复。坐标 1.1 看起来和 0.1 一样（忽略整数部分）&lt;/del>&lt;/li>
&lt;li>GL_MIRRORED_REPEAT ：与 GL_REPEAT 相同，但每次重复都会镜像图像。 &lt;del>图片重复，但一次正、一次反。边缘处能完美衔接，没有裂缝感。&lt;/del>&lt;/li>
&lt;li>GL_CLAMP_TO_EDGE ：将坐标限制在 &lt;code>0&lt;/code> 到 &lt;code>1&lt;/code> 之间。结果是，较高的坐标会被限制在边缘，从而形成拉伸的边缘图案。 &lt;del>坐标只要大于 1.0，就永远取 1.0 那一像素的颜色。结果是边缘被无限拉伸成直线。&lt;/del>&lt;/li>
&lt;li>GL_CLAMP_TO_BORDER ：超出范围的坐标现在会赋予用户指定的边框颜色。 &lt;del>图片外面是一片纯色（你可以自己定颜色，比如黑色或透明）。&lt;/del>&lt;/li>
&lt;/ul>
&lt;p>当使用超出默认范围的纹理坐标时，每个选项都会产生不同的视觉效果。让我们看看它们在示例纹理图像上的效果（原图由 Hólger Rezende 提供）：&lt;/p></description></item><item><title>05Shaders</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/learnopengl/01%E5%85%A5%E9%97%A8/05Shaders/</link><pubDate>Sun, 15 Mar 2026 19:43:26 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/learnopengl/01%E5%85%A5%E9%97%A8/05Shaders/</guid><description>&lt;h2 id="着色器">着色器&lt;a class="anchor" href="#%e7%9d%80%e8%89%b2%e5%99%a8">#&lt;/a>&lt;/h2>
&lt;p>正如 
&lt;a target="_blank" href="https://learnopengl.com/Getting-started/Hello-Triangle">“你好，三角形”&lt;/a> 一章中所述，着色器是运行在 GPU 上的小型程序。这些程序在图形管线的每个特定阶段运行。从本质上讲，&lt;mark>着色器就是将输入转换为输出的程序&lt;/mark>。着色器也是高度隔离的程序，&lt;mark>它们之间不允许通信；它们之间&lt;em>唯一的通信方式是通过输入和输出&lt;/em>&lt;/mark>。&lt;/p>
&lt;p>上一章我们简要介绍了着色器及其正确使用方法。现在我们将更全面地解释着色器，特别是 OpenGL 着色语言 &lt;del>OpenGL Shading Language&lt;/del> 。&lt;/p>
&lt;h2 id="glsl">GLSL&lt;a class="anchor" href="#glsl">#&lt;/a>&lt;/h2>
&lt;p>着色器是用类似 C 语言的 GLSL 编写的。GLSL 专为图形处理而设计，包含专门&lt;mark>针对向量和矩阵操作&lt;/mark>的实用功能。&lt;/p>
&lt;p>着色器总是&lt;mark>以版本声明开始&lt;/mark>，&lt;mark>后面跟着输入输出变量列表&lt;/mark>、&lt;mark>uniform 变量以及主函数&lt;/mark>。每个着色器的入口点都在其主函数中，该函数&lt;mark>处理所有输入变量并将结果输出到输出变量&lt;/mark>中。如果您不了解 uniform 变量，请不要担心，我们稍后会讲解。&lt;/p>
&lt;p>着色器通常具有以下结构：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-typescript" data-lang="typescript">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">#&lt;/span>&lt;span style="color:#a6e22e">version&lt;/span> &lt;span style="color:#a6e22e">version_number&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">in&lt;/span> &lt;span style="color:#66d9ef">type&lt;/span> &lt;span style="color:#a6e22e">in_variable_name&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">in&lt;/span> &lt;span style="color:#66d9ef">type&lt;/span> &lt;span style="color:#a6e22e">in_variable_name&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">out&lt;/span> &lt;span style="color:#66d9ef">type&lt;/span> &lt;span style="color:#a6e22e">out_variable_name&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#a6e22e">uniform&lt;/span> &lt;span style="color:#66d9ef">type&lt;/span> &lt;span style="color:#a6e22e">uniform_name&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">main&lt;/span>()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// process input(s) and do some weird graphics stuff
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> ...
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// output processed stuff to output variable
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#a6e22e">out_variable_name&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">weird_stuff_we_processed&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;mark>当我们专门讨论顶点着色器时，每个输入变量也称为顶点属性&lt;/mark>。我们可以声明的&lt;mark>顶点属性数量受硬件限制&lt;/mark>。OpenGL 保证始终&lt;mark>至少有 16 个 4 分量顶点属性可用&lt;/mark>，但某些硬件可能允许更多，您可以通过查询 GL_MAX_VERTEX_ATTRIBS 来获取这些信息：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> nrAttributes;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &lt;span style="color:#f92672">&amp;amp;&lt;/span>nrAttributes);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>std&lt;span style="color:#f92672">::&lt;/span>cout &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> &lt;span style="color:#e6db74">&amp;#34;Maximum nr of vertex attributes supported: &amp;#34;&lt;/span> &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> nrAttributes &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> std&lt;span style="color:#f92672">::&lt;/span>endl;&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这通常会返回最小值 &lt;code>16&lt;/code> &lt;del>我的是32&lt;/del> ，这对于大多数用途来说应该绰绰有余。&lt;/p></description></item><item><title>03HelloWindow</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/learnopengl/01%E5%85%A5%E9%97%A8/03HelloWindow/</link><pubDate>Fri, 13 Mar 2026 21:44:30 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/learnopengl/01%E5%85%A5%E9%97%A8/03HelloWindow/</guid><description>&lt;h1 id="hellowindow">Hello，Window&lt;a class="anchor" href="#hellowindow">#&lt;/a>&lt;/h1>
&lt;p>我们来看看能不能让 GLFW 运行起来。首先，创建一个 &lt;code>.cpp&lt;/code> 文件，并在新创建的文件顶部添加以下包含语句。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;glad/glad.h&amp;gt;&lt;/span>&lt;span style="color:#75715e">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#include&lt;/span> &lt;span style="color:#75715e">&amp;lt;GLFW/glfw3.h&amp;gt;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>请务必在引入 GLFW 之前引入 GLAD。GLAD 的头文件包含了后台所需的 OpenGL 头文件（例如 &lt;code>GL/gl.h&lt;/code> ），因此请确保在其他需要 OpenGL 的头文件（例如 GLFW）之前引入 GLAD。&lt;/p>
&lt;p>接下来，我们创建主函数，并在其中实例化 GLFW 窗口：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-scss" data-lang="scss">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">int&lt;/span> &lt;span style="color:#f92672">main&lt;/span>&lt;span style="color:#f92672">()&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">glfwInit&lt;/span>&lt;span style="color:#f92672">()&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">glfwWindowHint&lt;/span>&lt;span style="color:#f92672">(&lt;/span>&lt;span style="color:#f92672">GLFW_CONTEXT_VERSION_MAJOR&lt;/span>&lt;span style="color:#f92672">,&lt;/span> &lt;span style="color:#f92672">3&lt;/span>&lt;span style="color:#f92672">)&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">glfwWindowHint&lt;/span>&lt;span style="color:#f92672">(&lt;/span>&lt;span style="color:#f92672">GLFW_CONTEXT_VERSION_MINOR&lt;/span>&lt;span style="color:#f92672">,&lt;/span> &lt;span style="color:#f92672">3&lt;/span>&lt;span style="color:#f92672">)&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">glfwWindowHint&lt;/span>&lt;span style="color:#f92672">(&lt;/span>&lt;span style="color:#f92672">GLFW_OPENGL_PROFILE&lt;/span>&lt;span style="color:#f92672">,&lt;/span> &lt;span style="color:#f92672">GLFW_OPENGL_CORE_PROFILE&lt;/span>&lt;span style="color:#f92672">)&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">//glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> 
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">return&lt;/span> &lt;span style="color:#f92672">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>在主函数中，我们首先使用 `glfwInit` 初始化 GLFW，之后可以使用 `glfwWindowHint` 配置 GLFW。`glfwWindowHint` 的第一个参数指定要配置的选项，我们可以从一个以 &lt;code>GLFW_&lt;/code> 为前缀的枚举列表中选择。第二个参数是一个整数，用于设置选项的值。所有可用选项及其对应值的列表可以在 
&lt;a target="_blank" href="http://www.glfw.org/docs/latest/window.html#window_hints">GLFW 的窗口处理&lt;/a>文档中找到。如果您现在尝试运行应用程序并出现大量_未定义引用_错误，则表示您没有成功链接 GLFW 库。&lt;/p>
&lt;p>由于本书重点介绍 OpenGL 3.3 版本，我们需要告知 GLFW 我们要使用的 OpenGL 版本为 3.3。这样，GLFW 在创建 OpenGL 上下文时就能做出正确的配置。这确保了当用户没有安装正确的 OpenGL 版本时，GLFW 不会运行。我们将主版本号和次版本号都设置为 &lt;code>3&lt;/code> 此外，我们还告知 GLFW 我们要==显式使用核心配置文件。使用核心配置文件意味着我们将获得 OpenGL 功能的一个较小子集，而不会使用我们不再需要的向后兼容功能。==请注意，在 macOS 系统上，您需要在初始化代码中添加 &lt;code>glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);&lt;/code> 才能使其生效。&lt;/p></description></item><item><title>02创建窗口</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/learnopengl/01%E5%85%A5%E9%97%A8/02%E5%88%9B%E5%BB%BA%E7%AA%97%E5%8F%A3/</link><pubDate>Fri, 13 Mar 2026 17:11:47 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/learnopengl/01%E5%85%A5%E9%97%A8/02%E5%88%9B%E5%BB%BA%E7%AA%97%E5%8F%A3/</guid><description>&lt;p>在开始创建精美图形之前，我们需要做的第一件事是&lt;mark>创建一个 OpenGL 上下文&lt;/mark>和一个&lt;mark>用于绘制的应用程序窗口&lt;/mark>。然而，这些操作因操作系统而异，OpenGL 也刻意将这些操作抽象化。这意味着我们&lt;mark>需要自行创建窗口、定义上下文并处理用户输入&lt;/mark>。&lt;/p>
&lt;p>幸运的是，有很多库可以提供我们所需的功能，其中一些是专门针对 OpenGL 的。&lt;mark>这些库省去了我们所有与操作系统相关的繁琐工作&lt;/mark>，并为我们&lt;mark>提供了一个窗口&lt;/mark>和一个 &lt;mark>OpenGL 上下文&lt;/mark>来进行渲染。一些比较流行的库包括 &lt;mark>GLUT、SDL、SFML 和 GLFW&lt;/mark>。在 LearnOpenGL 课程中，我们将使用 &lt;mark>GLFW&lt;/mark> 。当然，您也可以使用其他任何库，大多数库的设置都与 GLFW 的设置类似。&lt;/p>
&lt;h1 id="glfw">GLFW&lt;a class="anchor" href="#glfw">#&lt;/a>&lt;/h1>
&lt;p>GLFW 是一个用 C 语言编写的库，专门针对 OpenGL。&lt;mark>GLFW 提供了在屏幕上渲染内容所需的基本功能。它允许我们创建 OpenGL 上下文、定义窗口参数并处理用户输入&lt;/mark>，这些功能足以满足我们的需求。&lt;/p>
&lt;p>本章及下一章的重点是&lt;mark>启动并运行 GLFW，确保它能正确创建 OpenGL 上下文，并显示一个简单的窗口&lt;/mark>供我们进行实验。本章将采用循序渐进的方式，讲解&lt;mark>如何获取、构建和链接 GLFW 库&lt;/mark>。截至本文撰写之时，我们将使用 Microsoft Visual Studio 2019 IDE（请注意，在更新的 Visual Studio 版本中，此过程相同）。如果您未使用 Visual Studio（或更早的版本），也无需担心，在大多数其他 IDE 中，此过程也类似。&lt;/p>
&lt;h1 id="建造-glfw">建造 GLFW&lt;a class="anchor" href="#%e5%bb%ba%e9%80%a0-glfw">#&lt;/a>&lt;/h1>
&lt;p>您可以从 GLFW 的网页 
&lt;a target="_blank" href="http://www.glfw.org/download.html">下载&lt;/a>页面获取它。GLFW 已经提供了适用于 Visual Studio 2012 到 2019 的预编译二进制文件和头文件，但为了完整起见，&lt;mark>我们将从源代码自行编译 GLFW&lt;/mark>。这样做是为了让您体验自行编译开源库的过程，因为并非所有库都有预编译的二进制文件。那么，让我们下载_源代码包_吧。&lt;/p>
&lt;p>我们将把所有库都构建成 64 位二进制文件，因此如果您使用的是预编译的二进制文件，请确保获取 64 位二进制文件。 &lt;del>我这里用的是32位的&lt;/del>&lt;/p>
&lt;p>下载完源代码包后，请解压缩并打开其内容。我们只需要其中的几个项目：&lt;/p>
&lt;ul>
&lt;li>编译后生成的库。&lt;/li>
&lt;li>&lt;code>include&lt;/code>文件夹。&lt;/li>
&lt;/ul>
&lt;p>&lt;mark>从源代码编译库可以确保生成的库完美适配您的 CPU/操作系统&lt;/mark>，这是预编译二进制文件并不总是能提供的（有时，您的系统甚至没有可用的预编译二进制文件）。然而，向开源社区提供源代码的问题在于，&lt;mark>并非所有人都使用相同的 IDE 或构建系统&lt;/mark>来开发应用程序，这意味着提供的项目/解决方案文件可能与其他人的配置不兼容。因此，&lt;mark>其他人必须使用提供的 .c/.cpp 和 .h/.hpp 文件来配置自己的项目/解决方案，这非常繁琐。正是出于这些原因，才有了 CMake 这个工具&lt;/mark>。&lt;/p></description></item><item><title>01OpenGL</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/learnopengl/01%E5%85%A5%E9%97%A8/01OpenGL/</link><pubDate>Fri, 13 Mar 2026 15:58:58 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/learnopengl/01%E5%85%A5%E9%97%A8/01OpenGL/</guid><description>&lt;p>在开始我们的探索之旅前，我们首先应该明确 OpenGL 究竟是什么。OpenGL 主要&lt;mark>被视为一个 API（应用程序编程接口）&lt;/mark>，它&lt;mark>提供了一系列可用于操作图形和图像的函数&lt;/mark>。然而，&lt;mark>OpenGL 本身并非 API，而仅仅是一个规范&lt;/mark>，由 
&lt;a target="_blank" href="http://www.khronos.org/">Khronos Group&lt;/a> 开发和维护。&lt;/p>
&lt;p>&lt;img src="img/ly-20260313170752181.png" alt="" />&lt;/p>
&lt;p>OpenGL 规范明确规定了每个函数的输出结果及其执行方式。开发者需要根据规范 &lt;em>实现&lt;/em> 这些函数，并找到相应的解决方案。由于 &lt;mark>OpenGL 规范并未提供具体的实现细节&lt;/mark>，因此&lt;mark>实际开发的 OpenGL 版本可以采用不同的实现方式&lt;/mark>，只要其&lt;mark>结果符合规范&lt;/mark>（从而对用户而言相同）即可。&lt;/p>
&lt;p>实际&lt;mark>开发 OpenGL 库的是显卡制造商&lt;/mark>。你购买的&lt;mark>每张显卡都支持特定版本的 OpenGL&lt;/mark>，这些版本是&lt;mark>专门为该显卡（系列）开发的&lt;/mark>。在苹果系统中，OpenGL 库由苹果公司维护；而在 Linux 系统中，则存在着&lt;mark>显卡供应商提供的版本以及爱好者对这些库的修改版本&lt;/mark>。这也意味着，如果 OpenGL 出现任何异常行为，很可能是显卡制造商（或开发/维护该库的人员）的问题。&lt;/p>
&lt;p>由于大多数 OpenGL 实现都是由显卡制造商开发的，因此一旦实现中出现错误，通常可以通过&lt;mark>更新显卡驱动程序&lt;/mark>来解决；这些驱动程序包含显卡支持的最新 OpenGL 版本。这也是为什么始终建议定期更新显卡驱动程序的原因之一。&lt;/p>
&lt;p>Khronos 公开托管了所有 OpenGL 版本的规范文档。感兴趣的读者可以 
&lt;a target="_blank" href="https://www.opengl.org/registry/doc/glspec33.core.20100311.withchanges.pdf">在这里&lt;/a>找到 OpenGL 3.3 版本的规范（我们将使用这个版本），如果您想深入了解 OpenGL 的细节，这份规范值得一读（请注意，其中大部分内容仅描述结果，而非具体实现）。这些规范也为理解其函数的&lt;mark>具体&lt;/mark>工作原理提供了极佳的参考。&lt;/p>
&lt;h1 id="核心模式与即时模式">核心模式与即时模式&lt;a class="anchor" href="#%e6%a0%b8%e5%bf%83%e6%a8%a1%e5%bc%8f%e4%b8%8e%e5%8d%b3%e6%97%b6%e6%a8%a1%e5%bc%8f">#&lt;/a>&lt;/h1>
&lt;p>过去，使用 OpenGL 意味着在&lt;mark>即时模式（通常称为固定功能管线）&lt;mark>下进行开发，这是一种易于使用的图形绘制方法。OpenGL 的大部分功能都隐藏在库内部，&lt;mark>开发者对 OpenGL 的计算方式几乎没有控制权&lt;/mark>。随着开发者对灵活性的需求不断增长，规范也随之变得更加灵活，开发者获得了对图形的更多控制权。即时模式虽然易于使用和理解，但效率极低。因此，从 &lt;mark>3.2&lt;/mark> 版本开始，规范逐渐&lt;/mark>弃用即时模式&lt;/mark>的功能，并&lt;mark>鼓励&lt;/mark>开发者使用 &lt;mark>OpenGL 的核心配置文件模式&lt;/mark>进行开发。核心配置文件模式是 OpenGL 规范的一个分支，它移除了所有旧的、已弃用的功能。&lt;/p>
&lt;p>&lt;mark>使用 OpenGL 的核心配置文件时&lt;/mark>，OpenGL 会强制我们&lt;mark>使用现代编程实践&lt;/mark>。每&lt;em>当我们尝试使用 OpenGL 已弃用的函数时，OpenGL 都会抛出错误并停止绘制&lt;/em>。学习现代方法的优势在于其&lt;mark>灵活性和高效性&lt;/mark>。然而，它也更难学习。立即模式抽象了 OpenGL 执行的许多 &lt;mark>实际&lt;/mark>操作，虽然易于学习，但很难理解 OpenGL 的实际运行机制。现代方法&lt;mark>要求开发者真正理解 OpenGL 和图形编程&lt;/mark>，虽然这有点难度，但它提供了更大的灵活性、更高的效率，最重要的是：更深入地理解图形编程。&lt;/p>
&lt;p>这也是本书以核心配置文件 OpenGL 3.3 版本为目标的原因。虽然难度更高，但绝对值得付出努力。&lt;/p></description></item><item><title>000介绍</title><link>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/learnopengl/000%E4%BB%8B%E7%BB%8D/</link><pubDate>Fri, 13 Mar 2026 15:26:20 +0800</pubDate><guid>https://blog.liuyi2026.asia/zh/docs/technology/Opengl/learnopengl/000%E4%BB%8B%E7%BB%8D/</guid><description>&lt;p>欢迎来到这本在线 OpenGL 学习书籍！无论您学习 OpenGL 的目的是&lt;mark>学术研究、职业发展还是纯粹出于兴趣爱好&lt;/mark>，本书都将使用 &lt;mark>现代&lt;/mark> （核心配置文件）OpenGL，为您讲解 OpenGL 的&lt;mark>基础知识、中级知识以及所有高级知识&lt;/mark>。本书旨在以清晰易懂的方式和示例，向您展示现代 OpenGL 的方方面面，同时为后续学习提供有用的参考资料。&lt;/p>
&lt;h1 id="那么为什么要读这些章节呢">那么，为什么要读这些章节呢？&lt;a class="anchor" href="#%e9%82%a3%e4%b9%88%e4%b8%ba%e4%bb%80%e4%b9%88%e8%a6%81%e8%af%bb%e8%bf%99%e4%ba%9b%e7%ab%a0%e8%8a%82%e5%91%a2">#&lt;/a>&lt;/h1>
&lt;p>互联网上有成千上万篇关于学习 OpenGL 的文档、书籍和资源，然而，其中大多数资源只关注 OpenGL 的即时模式（通常被称为旧版 OpenGL），内容不完整，缺乏完善的文档，或者不符合你的学习偏好。因此，我的目标是提供一个&lt;mark>既完整又易于理解&lt;/mark>的平台。&lt;/p>
&lt;p>&lt;img src="img/ly-20260313155024155.png" alt="" />&lt;/p>
&lt;p>如果您喜欢阅读提供&lt;mark>循序渐进&lt;/mark>的指导、&lt;mark>清晰示例&lt;/mark>且不会让您被海量细节淹没的内容，那么这本书可能正合您意。本书各章节旨在&lt;mark>让没有任何图形编程经验的读者也能理解&lt;/mark>，同时也能&lt;mark>为经验丰富的用户带来阅读乐趣&lt;/mark>。我们还会探讨一些&lt;mark>实用的概念&lt;/mark>，只需稍加创意，就能&lt;mark>将您的想法转化为真正的 3D 应用&lt;/mark>。如果您觉得以上描述与您自身情况相符，那么请继续阅读。&lt;/p>
&lt;h1 id="你将学到什么">你将学到什么？&lt;a class="anchor" href="#%e4%bd%a0%e5%b0%86%e5%ad%a6%e5%88%b0%e4%bb%80%e4%b9%88">#&lt;/a>&lt;/h1>
&lt;p>这些章节的重点是&lt;mark>现代 OpenGL&lt;/mark>。&lt;mark>学习（和使用）现代 OpenGL&lt;/mark> 需要&lt;mark>扎实的图形编程知识&lt;/mark>，以及&lt;mark>对 OpenGL 底层工作原理的了解&lt;/mark>，才能真正获得最佳体验。因此，我们将&lt;mark>首先讨论核心图形方面&lt;/mark>的内容，即 &lt;mark>OpenGL 如何将像素绘制到屏幕上，以及如何利用这些知识来创建一些炫酷的效果&lt;/mark>。&lt;/p>
&lt;p>除了核心知识之外，我们还将探讨许多可用于应用程序的实用技巧，例如：&lt;mark>场景遍历、创建精美光照、从建模程序加载自定义对象、进行炫酷的后期处理&lt;/mark>等等。我们还会推出一系列演示视频，实际创建一个&lt;mark>基于所学 OpenGL 知识的小游戏&lt;/mark>，让您真正体验图形编程的乐趣。&lt;/p>
&lt;h1 id="从哪里开始呢">从哪里开始呢？&lt;a class="anchor" href="#%e4%bb%8e%e5%93%aa%e9%87%8c%e5%bc%80%e5%a7%8b%e5%91%a2">#&lt;/a>&lt;/h1>
&lt;p>学习 OpenGL 完全免费，而且永远免费，任何想要入门图形编程的人都可以使用。所有内容都可以在左侧菜单中找到。只需点击“简介”按钮，即可开始您的学习之旅！&lt;/p></description></item></channel></rss>