<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Learnopengl on 随记</title><link>https://blog.liuyi2026.asia/zh/tags/learnopengl/</link><description>Recent content in Learnopengl 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/learnopengl/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>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>