<?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 MU</title><link>https://choy-mutao.github.io/graphics_11/opengl/</link><description>Recent content in OpenGL on MU</description><generator>Hugo</generator><language>zh-cn</language><atom:link href="https://choy-mutao.github.io/graphics_11/opengl/index.xml" rel="self" type="application/rss+xml"/><item><title>新建一个窗口</title><link>https://choy-mutao.github.io/graphics_11/opengl/tut01/zh/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://choy-mutao.github.io/graphics_11/opengl/tut01/zh/</guid><description>简介 欢迎来到第一课！
在学习 OpenGL 之前，我们将先学习如何生成，运行，和玩转（最重要的一点）课程中的代 码。
预备知识 不需要特别的预备知识。如果你有 C、Java、Lisp、Javascript 等编程语言的经验，那么 理解课程代码会更快；但这不是必需的；如果没有，那么也仅仅是同时学两样东西（编程语 言+OpenGL）会稍微复杂点而已。
课程全部用“傻瓜式 C++”编写：我费了很大劲尽量让代码简单些。代码里没有模板 （template）、类或指针。就是说，即使只懂 Java，也能理解所有内容。
忘记一切 如前面所说，我们不需要预备知识；但请暂时把『老式 OpenGL』先忘了吧（例如 glBegin()这类东西）。在这里，你将学习新式 OpenGL（OpenGL 3 和 4），而多数网上教 程还在讲『老式 OpenGL』（OpenGL 1 和 2）。所以，在你的脑袋乱成一锅粥之前，把它们 都搁在一边吧。
生成课程中的代码 所有课程代码都能在 Windows、Linux、和 Mac 上生成，而且过程大体相同：
更新驱动 下载 C++编译器。 安装 CMake 下载全部课程代码 用 CMake 创建工程 编译工程 试试这些例子！ 各平台的详细代码生成过程将会在后面一一给出，不过具体每个平台可能会有差异。如果你 不确定，可以去看看 Windows 平台的生成说明，然后按照需改动一下来适应你自己的 平台。
在 Windows 平台上生成代码 更新驱动应该很轻松。直接去 NVIDIA 或者 AMD 的官网下载。若不清楚 GPU 的型号:控 制面板-&amp;gt;系统和安全-&amp;gt;系统-&amp;gt;设备管理器-&amp;gt;显示适配器。如果是 Intel 集成显卡，一般 由电脑厂商（Dell、HP 等）提供驱动。
建议用 Visual Studio Express 来编译。 这里可以免费下载。 若喜欢用 MinGW，推 荐Qt Creator。安装哪个都行。下列步骤是用 Visual Studio 讲解的，其他 IDE 也差不多。</description></item><item><title>画第一个三角形</title><link>https://choy-mutao.github.io/graphics_11/opengl/tut02/zh/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://choy-mutao.github.io/graphics_11/opengl/tut02/zh/</guid><description>描述 这将又是一篇长教程。
用 OpenGL 3 实现复杂的东西很方便；为此付出的代价是，画一个简单的三角形变得比较麻 烦。
不要忘了，定期复制粘贴，跑一下代码。
如果程序启动时崩溃了，很可能是你从错误的目录下运行了它。请仔细地阅读第一课中讲到的如何配置Visual Studio！
顶点数组对象(VAO) 你需要创建一个顶点数组对象，并将它设为当前对象（细节暂不深入）：
GLuint VertexArrayID; glGenVertexArrays(1, &amp;amp;VertexArrayID); glBindVertexArray(VertexArrayID); 当窗口创建成功后（即 [OpenGL 上下文] 创建后），马上做这一步工作；必须在任何其他 OpenGL 调用前完成。
若想进一步了解顶点数组对象（VAO），可以参考其他教程；但这不是很重要。
屏幕坐标系 三点定义一个三角形。当我们在三维图形学中谈论“点（point）”时，我们经常说“顶点 （Vertex）”。一个顶点有三个坐标：X，Y 和 Z。你可以用以下方式来想象这三个坐标：
X 在你的右方 Y 在你的上方 Z 是你背后的方向（是的，背后，而不是你的前方）这里有一 个更形象的方法：使用 [右手定则]
X 是你的拇指 Y 是你的食指 Z 是你的中指。如果你把你的拇指指向右边，食指指向天空， 那么中指将指向你的背后。让 Z 指往这个方向很奇怪，为什么要这样呢？简单的说：因为 基于右手定则的坐标系被广泛使用了 100 多年，它会给你很多有用的数学工具；而唯一的 缺点只是 Z 方向不直观。
补充：注意，你可以自由地移动你的手：你的 X，Y 和 Z 轴也将跟着移动（详见后文） 。
我们需要三个三维点来组成一个三角形；现在开始：
// An array of 3 vectors which represents 3 vertices static const GLfloat g_vertex_buffer_data[] = { -1.</description></item><item><title>矩阵</title><link>https://choy-mutao.github.io/graphics_11/opengl/tut03/zh/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://choy-mutao.github.io/graphics_11/opengl/tut03/zh/</guid><description>描述 引擎完全没有推动飞船。飞船静止在原处，而引擎推动了环绕着飞船的宇宙。
《飞出个未来》(一部美国科幻动画片)
这一课是所有课程中最重要的。请至少看八遍。
齐次坐标（Homogeneous coordinates） 目前为止，我们仍然把三维顶点视为三元组(x, y, z)。现在引入一个新的分量 w，得到向 量(x, y, z, w)。
请先记住以下两点（稍后我们会给出解释）：
若 w==1，则向量(x, y, z, 1)为空间中的点。
若 w==0，则向量(x, y, z, 0)为方向。
（事实上，要永远记着。）
这有什么不同呢？对于旋转，二者没什么不同。当你旋转点和方向时，结果是一样的。但对 于平移（将点沿着某个方向移动），情况就不同了。『平移一个方向』是毫无意义的。
齐次坐标使我们能用同一个公式对点和方向作运算。
变换矩阵（Transformation matrices） 矩阵简介 简而言之，矩阵就是一个行、列数固定的，纵横排列的数表。比如，一个 2×3 矩阵看起来 像这样：
三维图形学中我们只用到 4×4 矩阵，它能对顶点(x, y, z, w)作变换。这一变换是用矩阵 左乘顶点来实现的：
矩阵 x 顶点（记住顺序！！矩阵左乘顶点，顶点用列向量表示）= 变换后的顶点
这看上去复杂，实则不然。左手指着 a，右手指着 x，得到 ax。 左手移向右边一个数 b， 右手移向下一个数 y，得到 by。依次类推，得到 cz、dw。最后求和 ax + by + cz + dw， 就得到了新的 x！每一行都这么算下去，就得到了新的(x, y, z, w)向量。
这种重复无聊的计算就让计算机代劳吧。</description></item><item><title>彩色立方体</title><link>https://choy-mutao.github.io/graphics_11/opengl/tut04/zh/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://choy-mutao.github.io/graphics_11/opengl/tut04/zh/</guid><description>彩色立方体 欢迎来到第四课！你将学到：
画立方体，代替单调的三角形 加上绚丽的色彩 学习深度缓存（Z-Buffer） 画立方体 立方体有六个方形表面，而OpenGL只支持画三角形，因此需要画12个三角形，每面两个。我们用定义三角形顶点的方式来定义这些顶点。
// Our vertices. Tree consecutive floats give a 3D vertex; Three consecutive vertices give a triangle. // A cube has 6 faces with 2 triangles each, so this makes 6*2=12 triangles, and 12*3 vertices static const GLfloat g_vertex_buffer_data[] = { -1.0f,-1.0f,-1.0f, // triangle 1 : begin -1.0f,-1.0f, 1.0f, -1.0f, 1.0f, 1.0f, // triangle 1 : end 1.0f, 1.0f,-1.0f, // triangle 2 : begin -1.</description></item><item><title>纹理立方体</title><link>https://choy-mutao.github.io/graphics_11/opengl/tut05/zh/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://choy-mutao.github.io/graphics_11/opengl/tut05/zh/</guid><description>第五课：纹理立方体 本课学习如下几点：
什么是UV坐标 怎样自行加载纹理 怎样在OpenGL中使用纹理 什么是滤波？什么是mipmap？怎样使用？ 怎样利用GLFW更加有效地加载纹理？ 什么是alpha通道？ 关于UV坐标 给一个模型贴纹理时，需要通过某种方式告诉OpenGL用哪一块图像来填充三角形。这是借助UV坐标来实现的。
每个顶点除了位置坐标外还有两个浮点数坐标：U和V。这两个坐标用于获取纹理，如下图所示：
注意纹理是怎样在三角形上扭曲的。
自行加载.BMP图片 了解BMP文件格式并不重要：很多库可以帮你做这个。但BMP格式极为简单，可以帮助你理解那些库的工作原理。所以，我们从头开始写一个BMP文件加载器，以便你理解其工作原理，不过（在实际工程中）千万别再用这个实验品。
如下是加载函数的声明：
GLuint loadBMP_custom(const char * imagepath); 使用方式如下：
GLuint image = loadBMP_custom(&amp;#34;./my_texture.bmp&amp;#34;); 接下来看看如何读取BMP文件。
首先需要一些数据。读取文件时将设置这些变量。
// Data read from the header of the BMP file unsigned char header[54]; // Each BMP file begins by a 54-bytes header unsigned int dataPos; // Position in the file where the actual data begins unsigned int width, height; unsigned int imageSize; // = width*height*3 // Actual RGB data unsigned char * data; 现在正式开始打开文件。</description></item><item><title>键盘和鼠标</title><link>https://choy-mutao.github.io/graphics_11/opengl/tut06/zh/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://choy-mutao.github.io/graphics_11/opengl/tut06/zh/</guid><description>键盘和鼠标 欢迎来到第六课！
我们将学习如何通过鼠标和键盘来移动相机，就像在第一人称射击游戏中一样。
接口 这段代码在整个课程中多次被使用，因此把它单独放在一个文件中 ：common/controls.cpp，然后在 common/controls.hpp 中声明函数接口，这样 tutorial06.cpp 就能使用它们了。
和前节课比，tutorial06.cpp 里的代码变动很小。主要的变化是：每一帧都计算 MVP（投 影视图矩阵）矩阵，而不像之前那样只算一次。现在把这段代码加到主循环中：
do{ // ... // Compute the MVP matrix from keyboard and mouse input computeMatricesFromInputs(); glm::mat4 ProjectionMatrix = getProjectionMatrix(); glm::mat4 ViewMatrix = getViewMatrix(); glm::mat4 ModelMatrix = glm::mat4(1.0); glm::mat4 MVP = ProjectionMatrix * ViewMatrix * ModelMatrix; // ... } 这段代码需要 3 个新函数：
computeMatricesFromInputs()读键盘和鼠标操作，然后计算投影视图矩阵。这就是奇妙 所在。 getProjectionMatrix()返回计算好的投影矩阵。 getViewMatrix()返回计算好的视图矩阵。 这只是一种实现方式，当然，如果你不喜欢这些函数，勇敢地去改写它们。
来看看 controls.cpp 在做什么。
实际代码 我们需要几个变量。
// position glm::vec3 position = glm::vec3( 0, 0, 5 ); // horizontal angle : toward -Z float horizontalAngle = 3.</description></item><item><title>模型加载</title><link>https://choy-mutao.github.io/graphics_11/opengl/tut07/zh/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://choy-mutao.github.io/graphics_11/opengl/tut07/zh/</guid><description>第七课：模型加载 目前为止，我们一直在硬编码描述立方体。你一定觉得这样做很笨拙、不方便。
本课将学习从文件中加载3D模型。和加载纹理类似，我们先写一个小的、功能有限的加载器，接着再为大家介绍几个比我们写的更好的、实用的库。
为了让课程尽可能简单，我们将采用简单、常用的OBJ格式。同样也是出于简单原则，我们只处理每个顶点有一个UV坐标和一个法向量的OBJ文件（目前你不需要知道什么是法向量）。
加载OBJ模型 加载函数在common/objloader.hpp中声明，在common/objloader.cpp中实现。函数原型如下：
bool loadOBJ( const char * path, std::vector &amp;amp; out_vertices, std::vector &amp;amp; out_uvs, std::vector &amp;amp; out_normals ) 我们让loadOBJ读取文件路径，把数据写入out_vertices/out_uvs/out_normals。如果出错则返回false。std::vector是C++中的数组，可存放glm::vec3类型的数据，数组大小可任意修改，不过std::vector和数学中的向量（vector）是两码事。其实它只是个数组。最后提一点，符号&amp;amp;意思是这个函数将会直接修改这些数组。
OBJ文件示例 OBJ文件看起来大概像这样：
# Blender3D v249 OBJ File: untitled.blend # www.blender3d.org mtllib cube.mtl v 1.000000 -1.000000 -1.000000 v 1.000000 -1.000000 1.000000 v -1.000000 -1.000000 1.000000 v -1.000000 -1.000000 -1.000000 v 1.000000 1.000000 -1.000000 v 0.999999 1.000000 1.000001 v -1.000000 1.000000 1.000000 v -1.000000 1.000000 -1.000000 vt 0.748573 0.750412 vt 0.</description></item><item><title>基础光照模型</title><link>https://choy-mutao.github.io/graphics_11/opengl/tut08/zh/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://choy-mutao.github.io/graphics_11/opengl/tut08/zh/</guid><description>基础光照模型 在第八课中，我们将学习光照模型的基础知识。包括：
物体离光源越近会越亮 直视反射光时会有高亮（镜面反射） 当光没有直接照射物体时，物体会更暗（漫反射） 用环境光简化计算 不包括：
阴影。这是个宽阔的主题，大到需要专题教程了。 类镜面反射（包括水） 任何复杂的光与物质的相互作用，像次表面散射（比如蜡） 各向异性材料（比如拉丝的金属） 追求真实感的，基于物理的光照模型 环境光遮蔽（在洞穴里会更黑） 颜色溢出（一块红色的地毯会映得白色天花板带红色） 透明度 任何种类的全局光照（它包括了上面的所有） 总而言之：只讲基础。
法向 过去的几个教程中我们一直在处理法向，但是并不知道法向到底是什么。
三角形法向 一个平面的法向是一个长度为 1 并且垂直于这个平面的向量。
一个三角形的法向是一个长度为 1 并且垂直于这个三角形的向量。通过简单地将三角形两 条边进行叉乘计算（向量 a 和 b 的叉乘结果是一个同时垂直于 a 和 b 的向量，记得？） ，然后归一化：使长度为 1。伪代码如下：
triangle ( v1, v2, v3 ) edge1 = v2-v1 edge2 = v3-v1 triangle.normal = cross(edge1, edge2).normalize() 不要将法向(normal)和 normalize()函数混淆。Normalize()函数是让一个向量（任意向量 ，不一定必须是 normal）除以其长度，从而使新长度为 1。法向(normal)则是某一类向量 的名字。
顶点法向 引申开来：顶点的法向，是包含该顶点的所有三角形的法向的均值。这很方便——因为在顶点 着色器中，我们处理顶点，而不是三角形；所以在顶点处有信息是很好的。并且在 OpenGL 中，我们没有任何办法获得三角形信息。伪代码如下：
vertex v1, v2, v3, .... triangle tr1, tr2, tr3 // all share vertex v1 v1.</description></item><item><title>VBO索引</title><link>https://choy-mutao.github.io/graphics_11/opengl/tut09/zh/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://choy-mutao.github.io/graphics_11/opengl/tut09/zh/</guid><description>第九课：VBO索引 索引的原理 目前为止，建立VBO时我们总是重复存储一些共享的顶点和边。
本课将介绍索引技术。借助索引，我们可以重复使用一个顶点。这是用*索引缓冲区（index buffer）*来实现的。
索引缓冲区存储的是整数；每个三角形有三个整数索引，用索引就可以在各种属性缓冲区（顶点坐标、颜色、UV坐标、其他UV坐标、法向缓冲区等）中找到顶点的信息。这有点像OBJ文件格式，但有一点相差甚远：索引缓冲区只有一个。这意味着若两个三角形共用一个顶点，那这个顶点的所有属性对两个三角形来说都是一样的。
共享vs分开 来看看法向的例子。下图中，艺术家创建了两个三角形，试图模拟一个平滑曲面。可以把两个三角形的法向融合成一个顶点的法向。为方便观看，我画了一条红线表示平滑曲面。
然而在第二幅图中，美工想画的是“缝隙”或“边缘”。若融合了法向，就意味着色器会像前例一样进行平滑插值，生成一个平滑的表面：
因此在这种情况下，把顶点的法向分开存储反而更好；在OpenGL中，唯一实现方法是：把顶点连同其属性完整复制一份。
OpenGL中的索引VBO 索引的用法很简单。首先，需要创建一个额外的缓冲区存放索引。代码与之前一样，不过参数是ELEMENT_ARRAY_BUFFER，而非ARRAY_BUFFER。
std::vector&amp;lt;unsigned int&amp;gt; indices; // fill &amp;#34;indices&amp;#34; as needed // Generate a buffer for the indices GLuint elementbuffer; glGenBuffers(1, &amp;amp;elementbuffer); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer); glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), &amp;amp;indices[0], GL_STATIC_DRAW); 只需把glDrawArrays替换为如下语句，即可绘制模型：
// Index buffer glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer); // Draw the triangles ! glDrawElements( GL_TRIANGLES, // mode indices.size(), // count GL_UNSIGNED_INT, // type (void*)0 // element array buffer offset ); （小提示：最好使用unsigned short，不要用unsigned int。这样更节省空间，速度也更快。）</description></item><item><title>透明</title><link>https://choy-mutao.github.io/graphics_11/opengl/tut10/zh/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://choy-mutao.github.io/graphics_11/opengl/tut10/zh/</guid><description>第十课：透明 alpha通道 alpha通道的概念很简单。之前是写RGB结果，现在改为写RGBA：
// Ouput data : it&amp;#39;s now a vec4 out vec4 color; 前三个分量仍可以通过混合操作符（swizzle operator）.xyz访问，最后一个分量通过.a访问：
color.a = 0.3; 不太直观，但alpha = 不透明度；因此alpha = 1代表完全不透明，alpha = 0为完全透明。
这里我们简单地将alpha硬编码为0.3；但更常见的做法是用一个uniform变量表示它，或从RGBA纹理中读取（TGA格式支持alpha通道，而GLFW支持TGA）。
结果如下。既然我们能“看透”模型表面，请确保关闭隐面消除（glDisable(GL_CULL_FACE) ）。否则就发现模型没有了“背”面。 顺序很重要！ 上一个截图看上去还行，但这仅仅是运气好罢了。
###问题所在### 这里我画了一红一绿两个alpha值为50%的正方形。从中可以看出顺序的重要性，最终的颜色显著影响了眼睛对深度的感知。
我们的场景中也出现了同样的现象。试着稍稍改变一下视角：
事实证明这个问题十分棘手。游戏中透明的东西不多，对吧？
###常见解决方案###
常见解决方案即对所有的透明三角形排序。是的，所有的透明三角形。
绘制场景的不透明部分，让深度缓冲区能丢弃被遮挡的透明三角形。 对透明三角形按深度从近到远排序。 绘制透明三角形。 可以用C语言的qsort函数或者C++的std::sort函数来排序。细节就不多说了，因为……
###警告###
这么做可以解决问题（下一节还会介绍它），但：
填充速率会被限制，即，每个片断会写10、20次，也许更多。这对力不从心的内存总线来说太沉重了。通常，深度缓冲区可以自动丢弃“远”片断；但这时，我们显式地对片断进行排序，故深度缓冲区实际上没发挥作用。 这些操作，每个像素上都会做4遍（我们用了4倍多重采样抗锯齿（MSAA）），除非用了什么高明的优化。 透明三角形排序很耗时 若要逐个三角形地切换纹理，或者更糟糕地，要切换着色器——性能会大打折扣。别这么干。 一个足够好的解决方案是：
限制透明多边形的数量 对所有透明多边形使用同一个着色器和纹理 若这些透明多边形必须看起来很不同，请用纹理区分！ 若不排序，效果也还行，那最好别排序。 ###顺序无关透明 如果你的引擎确实需要顶尖的透明效果，这有一些技术值得研究一番：
2001年Depth Peeling论文：像素级精细度，但速度不快 Dual Depth Peeling：小幅改进 桶排序相关的几篇论文。把fragment存到数组，在shader中进行深度排序。 ATI Mecha Demo：又好又快，但实现起来有难度，需要最新的硬件。用链表存储fragment。 Cyril Crassin实现的ATI Mecha：实现难度更大 注意，即便是《小小大星球》（Little Big Planet）这种最新的端游，也只用了一层透明。
混合函数 要让之前的代码运行，得设置好混合函数。In order for the previous code to work, you need to setup your blend function.</description></item><item><title>2D文本</title><link>https://choy-mutao.github.io/graphics_11/opengl/tut11/zh/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://choy-mutao.github.io/graphics_11/opengl/tut11/zh/</guid><description>第十一课：2D文本
本课将学习如何在三维场景之上绘制二维文本。本例是一个简单的计时器：
API 我们将实现这些简单的接口（位于common/text2D.h）：
void initText2D(const char * texturePath); void printText2D(const char * text, int x, int y, int size); void cleanupText2D(); 为了让代码在640*480和1080p分辨率下都能正常工作，x和y的范围分别设为[0-800]和[0-600]。顶点着色器将根据实际屏幕大小做对它做调整。
完整的实现代码请参阅common/text2D.cpp。
纹理 initText2D简单地读取一个纹理和一些着色器，很好理解。来看看纹理：
该纹理由CBFG生成。CBFG是诸多从字体生成纹理的工具之一。把纹理加载到Paint.NET，加上红色背景（仅为了观察方便；本教程中的红色背景，都代表透明）。
printText2D()在屏幕的适当位置，生成一个纹理坐标正确的四边形。
绘制 首先，填充这些缓冲区：
std::vector&amp;lt;glm::vec2&amp;gt; vertices; std::vector&amp;lt;glm::vec2&amp;gt; UVs; 文本中的每个字母，都要计算其四边形包围盒的顶点坐标，然后添加两个三角形（组成一个四边形）：
for ( unsigned int i=0 ; i&amp;lt;length ; i++ ){ glm::vec2 vertex_up_left??? = glm::vec2( x+i*size???? , y+size ); glm::vec2 vertex_up_right?? = glm::vec2( x+i*size+size, y+size ); glm::vec2 vertex_down_right = glm::vec2( x+i*size+size, y????? ); glm::vec2 vertex_down_left? = glm::vec2( x+i*size?</description></item><item><title>OpenGL扩展</title><link>https://choy-mutao.github.io/graphics_11/opengl/tut12/zh/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://choy-mutao.github.io/graphics_11/opengl/tut12/zh/</guid><description>第十二课：OpenGL扩展 扩展 GPU的性能随着更新换代一直在提高，支持渲染更多的三角形和像素点。然而，原始性能不是我们唯一关心的。NVIDIA, AMD和Intel也通过增加功能来改善他们的显卡。来看一些例子。
ARB_fragment_program 回溯到2002年，GPU都没有顶点着色器或片断着色器：所有的一切都硬编码在芯片中。这被称为固定功能流水线（Fixed-Function Pipeline (FFP)）。同样地，当时最新的OpenGL 1.3中也没有接口可以创建、操作和使用所谓的“着色器”，因为它根本不存在。接着NVIDIA决定用实际代码描述渲染过程，来取代数以百计的标记和状态量。这就是ARB_fragment_program的由来。当时还没有GLSL，但你可以写这样的程序：
!!ARBfp1.0 MOV result.color, fragment.color; END 但若要显式地令OpenGL使用这些代码，你需要一些还不在OpenGL里的特殊函数。在进行解释前，再举个例子。
ARB_debug_output 好，你说『ARB_fragment_program太老了，所以我不需要扩展这东西』？其实有不少新的扩展非常方便。其中一个便是ARB_debug_output，它提供了一个不存在于OpenGL 3.3中的，但你可以/应该用到的功能。它定义了像GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB或GL_DEBUG_SEVERITY_MEDIUM_ARB之类的字符串，和DebugMessageCallbackARB这样的函数。这个扩展的伟大之处在于，当你写了一些不正确的代码，例如：
glEnable(GL_TEXTURE); // Incorrect ! You probably meant GL_TEXTURE_2D ! 你能得到错误消息和错误的精确位置。总结：
即便在现在的OpenGL 3.3中，扩展仍旧十分有用。 请使用ARB_debug_output ！下文有链接。 获取扩展 – 复杂的方式 『手动』查找一个扩展的方法是使用以下代码片断 (转自OpenGL.org wiki)：
int NumberOfExtensions; glGetIntegerv(GL_NUM_EXTENSIONS, &amp;amp;NumberOfExtensions); for(i=0; i&amp;lt;NumberOfExtensions; i++) { const GLubyte *ccc=glGetStringi(GL_EXTENSIONS, i); if ( strcmp(ccc, (const GLubyte *)&amp;#34;GL_ARB_debug_output&amp;#34;) == 0 ){ // The extension is supported by our hardware and driver // Try to get the &amp;#34;glDebugMessageCallbackARB&amp;#34; function : glDebugMessageCallbackARB = (PFNGLDEBUGMESSAGECALLBACKARBPROC) wglGetProcAddress(&amp;#34;glDebugMessageCallbackARB&amp;#34;); } } 获得所有的扩展 – 简单的方式 上面的方式太复杂。若用GLEW, GLee, gl3w这些库，就简单多了。例如，有了GLEW，你只需要在创建窗口后调用glewInit()，不少方便的变量就创建好了：</description></item><item><title>法线贴图</title><link>https://choy-mutao.github.io/graphics_11/opengl/tut13/zh/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://choy-mutao.github.io/graphics_11/opengl/tut13/zh/</guid><description>第十三课：法线贴图 欢迎来到第十三课！今天讲法线贴图（normal mapping）。
学完第八课：基本光照模型后，我们知道了如何用三角形法线得到不错的光照效果。需要注意的是，截至目前，每个顶点仅有一个法线：在三角形三个顶点间，法线是平滑过渡的；而颜色（纹理的采样）恰与此相反。
法线纹理 法线纹理看起来像这样：
每个纹素的RGB值实际上表示的是XYZ向量：颜色的分量取值范围为0到1，而向量的分量取值范围是-1到1；可以建立从纹素到法线的简单映射：
normal = (2*color)-1 // on each component 法线纹理整体呈蓝色，因为法线基本是朝上的（上方即Z轴正向。OpenGL中Y轴=上，有所不同。这种不兼容很蠢，但没人想为此重写现有的工具，我们将就用吧。后面介绍详情。）
法线纹理的映射方式和颜色纹理相似。麻烦的是如何将法线从各三角形局部坐标系（切线坐标系tangent space，亦称图像坐标系image space）变换到模型坐标系（计算光照采用的坐标系）。
切线和双切线（Tangent and Bitangent） 想必大家对矩阵已经十分熟悉了；大家知道，定义一个坐标系（本例是切线坐标系）需要三个向量。现在Up向量已经有了，即法线：可用Blender计算，或做一个简单的叉乘。下图中蓝色箭头代表法线（法线贴图整体颜色也恰好是蓝色）。
然后是切线T：垂直于平面的向量。切线有很多个：
这么多切线中该选哪一个呢？理论上，任何一个都可以。不过我们得和相邻顶点保持一致，以免导致边缘出现瑕疵。一个通行的办法是将切线方向和纹理坐标系对齐：
定义一组基需要三个向量，因此我们还得计算双切线B（本来可以随便选一条切线，但选定垂直于其他两条轴的切线，计算会方便些）。
算法如下：若把三角形的两条边记为deltaPos1和deltaPos2，deltaUV1和deltaUV2是对应的UV坐标下的差值；此问题可用如下方程表示：
deltaPos1 = deltaUV1.x * T + deltaUV1.y * B deltaPos2 = deltaUV2.x * T + deltaUV2.y * B 求解T和B就得到了切线和双切线！（代码见下文）
已知T、B、N向量之后，即可得下面这个漂亮的矩阵，完成从模型坐标系到切线坐标系的变换：
有了TBN矩阵，我们就能把法线（从法线纹理中提取而来）变换到模型坐标系。
可我们需要的却是与之相反的变换：从切线坐标系到模型坐标系，法线保持不变。所有计算均在切线坐标系中进行，不会对其他计算产生影响。
既然要进行逆向的变换，那只需对以上矩阵求逆即可。这个矩阵（正交阵，即各向量相互正交，请看后面“延伸阅读”小节）的逆矩阵恰好也就是其转置矩阵，计算十分简单：
invTBN = transpose(TBN) 亦即：
准备VBO ###计算切线和双切线### 我们需要为整个模型计算切线、双切线和法线。用一个单独的函数完成这项工作：
void computeTangentBasis( // inputs std::vector&amp;lt;glm::vec3&amp;gt; &amp;amp; vertices, std::vector&amp;lt;glm::vec2&amp;gt; &amp;amp; uvs, std::vector&amp;lt;glm::vec3&amp;gt; &amp;amp; normals, // outputs std::vector&amp;lt;glm::vec3&amp;gt; &amp;amp; tangents, std::vector&amp;lt;glm::vec3&amp;gt; &amp;amp; bitangents ){ 为每个三角形计算边（deltaPos）和deltaUV</description></item><item><title>渲染到纹理</title><link>https://choy-mutao.github.io/graphics_11/opengl/tut14/zh/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://choy-mutao.github.io/graphics_11/opengl/tut14/zh/</guid><description>第十四课：渲染到纹理 “渲染到纹理”是一系列特效方法之一。基本思想是：像通常那样渲染一个场景——只是这次是渲染到可以重用的纹理中。
应用包括：游戏（in-game）相机、后期处理（post-processing）以及你能想象到一切.
渲染到纹理 我们有三个任务：创建要渲染的纹理对象；将纹理渲染到对象上；使用生成的纹理。
###创建渲染目标（Render Target）###
我们要渲染的对象叫做帧缓存。它像一个容器，用来存纹理和一个可选的深度缓冲区(depth buffer)。在OpenGL中我们可以像创建其他对象一样创建它:
// The framebuffer, which regroups 0, 1, or more textures, and 0 or 1 depth buffer. GLuint FramebufferName = 0; glGenFramebuffers(1, &amp;amp;amp;FramebufferName); glBindFramebuffer(GL_FRAMEBUFFER, FramebufferName); 现在需要创建纹理，纹理中包含着色器的RGB输出。这段代码非常的经典：
// The texture we&amp;#39;re going to render to GLuint renderedTexture; glGenTextures(1, &amp;amp;amp;renderedTexture); // &amp;#34;Bind&amp;#34; the newly created texture : all future texture functions will modify this texture glBindTexture(GL_TEXTURE_2D, renderedTexture); // Give an empty image to OpenGL ( the last &amp;amp;quot;0&amp;amp;quot; ) glTexImage2D(GL_TEXTURE_2D, 0,GL_RGB, 1024, 768, 0,GL_RGB, GL_UNSIGNED_BYTE, 0); // Poor filtering.</description></item><item><title>光照贴图（Lightmap）</title><link>https://choy-mutao.github.io/graphics_11/opengl/tut15/zh/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://choy-mutao.github.io/graphics_11/opengl/tut15/zh/</guid><description>第十五课：光照贴图（Lightmap） 简介 这堂课是视频课程，没有介绍新的OpenGL相关技术/语法。不过，大家会学习如何利用现有知识，生成高质量的阴影。
本课介绍了用Blender创建简单场景的方法；还介绍了如何烘培（bake）光照贴图（lightmap），以便在你的项目中使用。
无需Blender预备知识，我会讲解包括快捷键的所有内容
关于光照贴图 光照图是永久、一次性地烘焙好的。也就是说光照图是完全静态的，你不能在运行时移动光源，连删除都不行。
但对于阳光这种光源来说，光照图还是大有用武之地的；在不会打碎灯泡的室内场景中，也是可以的。2009年发布的《镜之边缘》（Mirror Edge）室内、室外场景中大量采用了光照图。
更重要的是，光照图很容易配置，速度无可匹敌。
视频 这是个1024x768 高清视频。
Youku 标清含中文字幕 Vimeo 高清原版视频
附录 用OpenGL渲染时，你大概会注意到一些瑕疵（这里故意把瑕疵放大了）：
这是由mipmap造成的。从远处观察时，mipmap对纹素做了混合。纹理背景中的黑色像素点和光照图中的像素点混合在了一起。为了避免这一点，可以采取如下措施：
让Blender在UV图的limits上生成一个margin。这个margin参数位于bake面板。要想效果更好，可以把margin值设为20个纹素。 获取纹理时，加上一个偏离（bias）： color = texture2D( myTextureSampler, UV, -2.0 ).rgb; -2是偏离量。这个值是通过不断尝试得出的。上面的截图中bias值为+2，也就是说OpenGL将在原本的mipmap层次上再加两层（因此，纹素大小变为原来的1/16，瑕疵也随之变小了）。-
后期处理中可将背景填充为黑色，这一点我后面还会再讲。</description></item><item><title>阴影贴图（Shadow mapping）</title><link>https://choy-mutao.github.io/graphics_11/opengl/tut16/zh/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://choy-mutao.github.io/graphics_11/opengl/tut16/zh/</guid><description>第十六课：阴影贴图（Shadow mapping） 第十五课中已经学习了如何创建光照贴图。光照贴图可用于静态对象的光照，其阴影效果也很不错，但无法处理运动的对象。
阴影贴图是目前（截止2012年）最好的生成动态阴影的方法。此法最大的优点是易于实现，缺点是想完全正确地实现不大容易。
本课首先介绍基本算法，探究其缺陷，然后实现一些优化。由于撰写本文时（2012），阴影贴图技术还在被广泛地研究；我们将提供一些指导，以便你根据自身需要，进一步改善你的阴影贴图。
基本的阴影贴图 基本的阴影贴图算法包含两个步骤。首先，从光源的视角将场景渲染一次，只计算每个片断的深度。接着从正常的视角把场景再渲染一次，渲染时要测试当前片断是否位于阴影中。
“是否在阴影中”的测试实际上非常简单。如果当前采样点比阴影贴图中的同一点离光源更远，那说明场景中有一个物体比当前采样点离光源更近；即当前片断位于阴影中。
下图可以帮你理解上述原理：
###渲染阴影贴图### 本课只考虑平行光——一种位于无限远处，其光线可视为相互平行的光源。故可用正交投影矩阵来渲染阴影贴图。正交投影矩阵和一般的透视投影矩阵差不多，只不过未考虑透视——因此无论距离相机多远，物体的大小看起来都是一样的。
###设置渲染目标和MVP矩阵### 十四课中，大家学习了把场景渲染到纹理，以便稍后从shader中访问的方法。
这里采用了一幅1024x1024、16位深度的纹理来存储阴影贴图。对于阴影贴图来说，通常16位绰绰有余;你可以自由地试试别的数值。注意，这里采用的是深度纹理，而非深度渲染缓冲区（这个要留到后面进行采样）。
// The framebuffer, which regroups 0, 1, or more textures, and 0 or 1 depth buffer. GLuint FramebufferName = 0; glGenFramebuffers(1, &amp;amp;FramebufferName); glBindFramebuffer(GL_FRAMEBUFFER, FramebufferName); // Depth texture. Slower than a depth buffer, but you can sample it later in your shader GLuint depthTexture; glGenTextures(1, &amp;amp;depthTexture); glBindTexture(GL_TEXTURE_2D, depthTexture); glTexImage2D(GL_TEXTURE_2D, 0,GL_DEPTH_COMPONENT16, 1024, 1024, 0,GL_DEPTH_COMPONENT, GL_FLOAT, 0); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthTexture, 0); glDrawBuffer(GL_NONE); // No color buffer is drawn to.</description></item><item><title>旋转</title><link>https://choy-mutao.github.io/graphics_11/opengl/tut17/zh/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://choy-mutao.github.io/graphics_11/opengl/tut17/zh/</guid><description>##第十七课：旋转
虽然本课有些超出OpenGL的范围，但是解决了一个常见问题：怎样表示旋转？
《第三课：矩阵》中，我们了解到矩阵可以让点绕某个轴旋转。矩阵可以简洁地表示顶点的变换，但使用难度较大：例如，从最终结果中获取旋转轴就很麻烦。
本课将展示两种最常见的表示旋转的方法：欧拉角（Euler angles）和四元数（Quaternion）。最重要的是，本课将详细解释为何要尽量使用四元数。
旋转与朝向（orientation） 阅读有关旋转的文献时，你可能会为其中的术语感到困惑。本课中：
“朝向”是状态：该物体的朝向为……
“旋转”是操作：旋转该物体
也就是说，当实施旋转操作时，就改变了物体的朝向。 两者形式相同，因此容易混淆。闲话少叙，开始进入正题……
欧拉角 欧拉角是表示朝向的最简方法，只需存储绕X、Y、Z轴旋转的角度，非常容易理解。你可以用vec3来存储一个欧拉角：
vec3 EulerAngles( RotationAroundXInRadians, RotationAroundYInRadians, RotationAroundZInRadians); 这三个旋转是依次施加的，通常的顺序是：Y-Z-X（但并非一定要按照这种顺序）。顺序不同，产生的结果也不同。
一个欧拉角的简单应用就是用于设置角色的朝向。通常，游戏角色不会绕X和Z轴旋转，仅仅绕竖直的Y轴旋转。因此，无需处理三个朝向，只需用一个float型变量表示方向即可。
另外一个使用欧拉角的例子是FPS相机：用一个角度表示头部朝向（绕Y轴），一个角度表示俯仰（绕X轴）。参见common/controls.cpp的示例。
不过，面对更加复杂的情况时，欧拉角就显得力不从心了。例如：
对两个朝向进行插值比较困难。简单地对X、Y、Z角度进行插值得到的结果不太理想。 实施多次旋转很复杂且不精确：必须计算出最终的旋转矩阵，然后据此推测书欧拉角。 “臭名昭著”的“万向节死锁”(Gimbal Lock)问题有时会让旋转“卡死”。其他一些奇异状态还会导致模型方向翻转。 不同的角度可产生同样的旋转（例如-180°和180°） 容易出错——如上所述，一般的旋转顺序是YZX，如果用了非YZX顺序的库，就有麻烦了。 某些操作很复杂：如绕指定的轴旋转N角度。 四元数是表示旋转的好工具，可解决上述问题。
四元数 四元数由4个数[x y z w]构成，表示了如下的旋转：
// RotationAngle is in radians x = RotationAxis.x * sin(RotationAngle / 2) y = RotationAxis.y * sin(RotationAngle / 2) z = RotationAxis.z * sin(RotationAngle / 2) w = cos(RotationAngle / 2) RotationAxis，顾名思义即旋转轴。RotationAngle是旋转的角度。
因此，四元数实际上存储了一个旋转轴和一个旋转角度。这让旋转的组合变简单了。
###解读四元数###
四元数的形式当然不如欧拉角直观，不过还是能看懂的：xyz分量大致代表了各个轴上的旋转分量，而w=acos(旋转角度/2)。举个例子，假设你在调试器中看到了这样的值[ 0.</description></item><item><title>Billbard和粒子</title><link>https://choy-mutao.github.io/graphics_11/opengl/tut18/zh/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://choy-mutao.github.io/graphics_11/opengl/tut18/zh/</guid><description>Billbard 和粒子 公告板是 3D 世界中的 2D 元素。它既不是最顶层的 2D 菜单，也不是可以随意转动的 3D 平面，而是介于两者之间的一种元素，比如游戏中的血条。
公告板的独特之处在于：它位于某个特定位置，朝向是自动计算的，这样它就能始终面向相 机（观察者）。
方案 1:2D 法 2D 法十分简单。只需计算出点在屏幕空间的坐标，然后在该处显示 2D 文本（参见第十一 课）即可。
// Everything here is explained in Tutorial 3 ! There&amp;#39;s nothing new. glm::vec4 BillboardPos_worldspace(x,y,z, 1.0f); glm::vec4 BillboardPos_screenspace = ProjectionMatrix * ViewMatrix * BillboardPos_worldspace; BillboardPos_screenspace /= BillboardPos_screenspace.w; if (BillboardPos_screenspace.z &amp;lt; 0.0f){ // Object is behind the camera, don&amp;#39;t display it. } 就这么搞定了！
2D 法优点是简单易行，无论点与相机距离远近，公告板始终保持大小不变。但此法总是把 文本显示在最顶层，有可能会遮挡其他物体，影响渲染效果。
方案 2:3D 法 与 2D 法相比，3D 法常常效果更好，也没复杂多少。我们的目的就是无论相机如何移动， 都要让公告板网格正对着相机：</description></item></channel></rss>