<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>iPhoneGeek 爱疯极客 &#187; OpenGL ES</title>
	<atom:link href="http://www.iphone-geek.cn/tag/opengl-es/feed" rel="self" type="application/rss+xml" />
	<link>http://www.iphone-geek.cn</link>
	<description>iPhone 新闻，编程，技巧与提示，代码，教程</description>
	<lastBuildDate>Sun, 25 Jul 2010 13:49:13 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8.5</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>从零开始学习OpenGL ES之七 &#8211; 变换和矩阵</title>
		<link>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0opengl-es%e4%b9%8b%e4%b8%83-%e5%8f%98%e6%8d%a2%e5%92%8c%e7%9f%a9%e9%98%b5</link>
		<comments>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0opengl-es%e4%b9%8b%e4%b8%83-%e5%8f%98%e6%8d%a2%e5%92%8c%e7%9f%a9%e9%98%b5#comments</comments>
		<pubDate>Thu, 25 Mar 2010 04:35:35 +0000</pubDate>
		<dc:creator>bagusflyer</dc:creator>
				<category><![CDATA[图形图像]]></category>
		<category><![CDATA[编程]]></category>
		<category><![CDATA[OpenGL ES]]></category>
		<category><![CDATA[教程]]></category>

		<guid isPermaLink="false">http://www.iphone-geek.cn/?p=628</guid>
		<description><![CDATA[今天的主题是我一度谈之色变的。概念上讲，它是3D编程中最为困难的部分。

首先，你应该理解 3D  几何和笛卡尔坐标系他。你还应该理解由顶点构成的三角形组成的OpenGL虚拟世界的物体，各顶点定义了三维空间的特定点，你还应理解怎样使用这些信息在  iPhone上使用OpenGL ES进行绘制。如果你不理解这些概念，我建议你回头再看看我的前六篇文章。

为在交互式程序如游戏中使用这些虚拟世界中的物体，必须要有一种方法来改变物体间的相对位置以及物体与观察者之间的相对位置。要有一种方法不但可以移动，而且可以旋转和改变物体的大小。
还必须要有一种方法将虚拟的三维坐标转换成电脑屏幕的二维坐标。所有这些都是通过所谓变换来实现的。实现变换的内部机制是就是矩阵。

尽管你不需要懂得太多有关矩阵和矩阵的数学知识就可以实现许多OpenGL的功能，但对这些观念的基本理解有很大的帮助。

内建变换以及单元矩阵

你已经见识许多OpenGL的常用变换。其中有一个在每个程序中都可以见到，它就是 glLoadIdentity()，我们在 drawView:  方法的开始处调用它来进行状态复位。还见识过 glRotatef()，用来使二十面体旋转，另外还有 glTranslatef() 使物体在虚拟世界中移动。

我们首先看看glLoadIdentity()。此函数加载单元矩阵。我们将稍后讨论此特殊矩阵，但是加载单元矩阵本质上就是将虚拟世界进行复位。它清除了先前应用的任何变换。在绘制开始前调用  glLoadIdentity() 是一个很正常的习惯，因为你可以知道起点在什么地方 &#8211; 原点，从而可以预料变换的结果。
想知道如果你不调用glLoadIdentity()会产生什么结果，下载 第四部分的Xcode项目，在drawView:中为glLoadIdentity()调用加上注释，运行。发生了什么？

应该慢速旋转的二十面体急速地离开，对吗？像小飞鼠一样飞向天空离开视线。

发生这种现象的原因是在项目中我们使用了两个变换。二十面体的顶点是围绕原点定义的，所以我们使用转移变换将其移动三个单位远离观察者使整个物体可见。我们使用的第二个变换是旋转变换，它使多面体旋转。当使用了glLoadIdentity()时，每一帧开始时都是回到原点将多面体移离观察者三个单位，也就是说它每次都终止于同一位置z  = -3.0。类似地，旋转值是随时间增长而增加的，使二十面体以均匀的步调旋转。由于前一个旋转值在开始旋转前被  glLoadIdentity()调用清除掉所以旋转是以匀速进行的。
不调用glLoadIdentity()，尽管第一次二十面体还是移离观察者三个单位并旋转一个小的角度。但从第二帧开始（几十毫秒后），二十面体将继续后移三个单位而且其旋转角度rot叠加已经旋转的角度。这将发生在每一帧上，这意味着二十面体每帧都会移离三个单位而且旋转的速度都会增加。

尽管我们可以不调用  glLoadIdentity()而对其结果进行补偿，但是由于我们许多时候无法预料转换的结果，所以最好的方法还是让转换开始于已知的位置（通常是原点）并且未经过旋转或尺寸变化，这就是我们总是先调用  glLoadIdentity()的原因了。

常见变换

除了glTranslatef()和 glRotatef()外，还有 glScalef()，它使绘制物体的尺寸增大或减小。在OpenGL  ES中还有其他一些变换功能，但这三个（与glLoadIdentity()一起使用）是最常见的。其他功能主要用于将三维虚拟世界转换为二维表示的过程，这个过程称为透视 。我们稍后涉及一下透视，但大部分情况下，除了设置视口外我们不需要直接与透视打交道。

这些常用的变换非常实用。你可以仅使用这四个调用就完成整个游戏。但是有时你可能需要自己控制转换。你想要自己控制转换的一个原因是这些常用变换必须按顺序分别调用，每次调用都是一次具有一定开销的矩阵乘法（稍后讨论）。如果你自己进行转换，你可以将多个转换组合成一个矩阵，从而减少每帧都要进行的矩阵乘法操作。

由于你可以向量化你的矩阵乘法调用，所以你可以通过定义自己的矩阵来获取最大性能。据我所知，关于此点iPhone  并无文档记录，但作为基本规则OpenGL  ES将对向量间或顶点和矩阵间的乘法进行硬件加速，但两个转换矩阵间的乘法并无加速。通过矩阵乘法向量化，你可以获得比让OpenGL进行矩阵乘法更好的性能。由于通常矩阵与矩阵的乘法调用的数量远小于向量/顶点间的矩阵乘法调用的数量，所以这并不会带来巨大的性能提升，但在一个复杂的3D程序中，每个方面小的额外性能提升都很有好处。

矩阵

这里我明显不是指电影“黑客帝国”（&#8221;The Matrix&#8221;），我们将要在随后的篇幅中介绍矩阵。

不幸的是没人可以告诉我矩阵是什么。


实际上，矩阵不过是一个二维数组。就这么简单。没什么神秘的。下面是一个矩阵示例：



这是一个 3&#215;3 矩阵，它有三行和三列。顶点和向量实际由一个1&#215;3的矩阵表示：



一个顶点还可以由一个 3&#215;1 数组而不是一个 1&#215;3 数组表示，但是我们这里使用1&#215;3 格式（后面解释原因）。甚至一个数据元素技术上也可以用一个 1&#215;1  矩阵表示，尽管这并不是一个十分有用的矩阵。
还有什么可以用数组来表示？坐标系统。还记得向量？向量是想象的从原点指向空间一点的直线。现在，记住笛卡尔坐标系统具有三个轴：



那么，指向X轴反向的归一向量是什么样的？记住：归一向量是长度为一的向量，所以一个沿X轴正向的归一向量是这样的：



注意我们是使用3&#215;1  矩阵而不是像处理顶点一样使用1&#215;3矩阵来表示向量。实际上这并不重要，只要按处理顶点相反的方式处理向量就行。向量中的三个数值适用于同一个坐标轴。我知道，这可能还不太好理解，但我很快就会解释到。Y轴向上的向量看上去像这样：



Z轴上的向量像这样:



现在我们将这三个向量矩阵按照它们在顶点（x然后是y再后是z）中的顺序放入矩阵中，像这样：



有一个特殊矩阵称为单元矩阵。听起来很熟悉？当你调用glLoadIdentity()时， 你就加载了一个单元矩阵1。 这里我解释一下为什么它是一个特殊矩阵。矩阵可以通过相乘而组合在一起。如果你将矩阵乘以一个单元矩阵，其结果就是原始矩阵，就像数字乘以一。你可以通过将所有除行号和列号相等的元素外的值设置为0来设定一个任意尺寸的单元矩阵，行号和列号相等处的值为1.0.

矩阵相乘

矩阵相乘是矩阵组合的关键。如果你有一个定义了转移到矩阵和一个定义了旋转的矩阵，如果将它们相乘，你将得到一个既定义了旋转又定义了转移的矩阵。让我们看看一个简单的矩阵相乘的简单例子。看看下面两个矩阵的图像：




矩阵相乘的结果是另一个与等式左边矩阵一样尺寸的矩阵。矩阵乘法是不可置换的。顺序是很重要的。将矩阵a乘以矩阵b的结果不一定与矩阵b乘以矩阵a的结果相同（尽管在某些情况下有可能相同）。

有关矩阵相乘还有一件事需要注意：并不是所有的矩阵都可以相乘的。它们并不需要具有同样的尺寸，但等式中右边的矩阵的行数必须与等式中左边矩阵的列数相同。所以，你可以将一个3&#215;3的矩阵与另一个3&#215;3的矩阵相乘，或者你可以将一个1&#215;3的矩阵与一个3&#215;6的矩阵相乘，但你不能将一个2&#215;4的矩阵与另一个2&#215;4的矩阵相乘，因为2&#215;4矩阵的列数与另一个2&#215;4矩阵的行数并不相同。

要得出矩阵相乘的结果，我们首先建立一个与等式中左边矩阵同样尺寸的空矩阵：



对矩阵中的各点，我们从左边矩阵中取出相应行，再从右边矩阵中取出相应列。所以，对于结果矩阵中的左上方的点，我们取出等式中左边矩阵的第一行以及等式中右边矩阵的第一列，像这样：



然后，将左边矩阵行中第一个值乘以右边矩阵列的第一个值，左边矩阵行第二个值乘以右边矩阵列的第二个值，左边矩阵行第三个值乘以右边矩阵列的第三个值，然后再将它们相加。像这样：



对结果矩阵中的各点重复以上步骤，得到以下结果：



将矩阵（蓝色）乘以单元矩阵（红色），其结果就是原始矩阵。由于单元矩阵代表没有经过任何变换的坐标系统，所以结果完全合理。这也同样适用于顶点。我们将一个顶点乘以一个矩阵：


现在，我们假定我们希望旋转一个物体。我们要做的就是定义一个描述了被旋转的坐标系统的矩阵。在场景中，我们实际上是旋转了世界坐标，然后将物体绘制其上。如果说我们希望绕Z轴旋转一个物体，那么Z轴将保持不变，而X和Y轴将变化。尽管有些不那么直观，要定义一个绕z轴旋转的坐标系统，我们需要调整3&#215;3矩阵中的x和y向量，换句话说，我们必须修改第一和第二列。



所以需要调整通过修改X轴向量的X值和Y向量的Y值为旋转角度的余弦值。余弦是三角形角度对应的相邻两边之比。我们还需要修改X轴向量的Y值为相同角度的正弦值的负值，以及Y轴向量的X值为相同角度的正弦值。用矩阵来表示可能更容易理解：



现在如果将世界坐标中所有物体的各顶点都乘以这个矩阵，那么物体将被旋转。一旦对物体的所有顶点都进行此操作，那么该物体将沿Z轴旋转n度。

如果你还不太理解也不要紧。其实你并不需要真正理解使用矩阵的数学原理。它们都是已经解决了的难题，你可以通过Google找到任何变换所需的矩阵。实际上，你可以在OpenGL的文档中找到大部分。所以如果你不能完全理解为什么这个矩阵导致绕Z轴的旋转，也完全不必气恼。

一个3&#215;3的矩阵可以描述绕任意轴旋转任何角度的情况。然而，为表示可能遇到的任何变换，我们仍然需要第四行/列。第四列用来保存变换信息，第四行用来表示透视变换。因为需要理解同原坐标以及透视空间，而这些对于成为一个出色的OpenGL程序员并不重要，所以我不打算在这里介绍透视变换的数学原理了。要乘以一个4&#215;4矩阵，我们需要填充一个附加值，通常我们称其为W。W应该为1。在进行乘法运算后，忽略W值。我不打算介绍向量的矩阵乘法，因为OpenGL已经对此进行了硬件加速，所以通常不需要进行手工处理，但理解其基本步骤是很好的建议。

OpenGL ES矩阵

OpenGL ES [...]]]></description>
			<content:encoded><![CDATA[<p>今天的主题是我一度谈之色变的。概念上讲，它是3D编程中最为困难的部分。</p>
<p><br class="spacer_" /></p>
<p>首先，你应该理解 3D  几何和笛卡尔坐标系他。你还应该理解由顶点构成的三角形组成的OpenGL虚拟世界的物体，各顶点定义了三维空间的特定点，你还应理解怎样使用这些信息在  iPhone上使用OpenGL ES进行绘制。如果你不理解这些概念，我建议你回头再看看我的前六篇文章。</p>
<p><br class="spacer_" /></p>
<p>为在交互式程序如游戏中使用这些虚拟世界中的物体，必须要有一种方法来改变物体间的相对位置以及物体与观察者之间的相对位置。要有一种方法不但可以移动，而且可以旋转和改变物体的大小。</p>
<p>还必须要有一种方法将虚拟的三维坐标转换成电脑屏幕的二维坐标。所有这些都是通过所谓<em>变换</em>来实现的。实现变换的内部机制是就是<em>矩阵</em>。</p>
<p><br class="spacer_" /></p>
<p>尽管你不需要懂得太多有关矩阵和矩阵的数学知识就可以实现许多OpenGL的功能，但对这些观念的基本理解有很大的帮助。</p>
<h3><span id="more-628"></span></h3>
<h3>内建变换以及单元矩阵</h3>
<p><br class="spacer_" /></p>
<p>你已经见识许多OpenGL的常用变换。其中有一个在每个程序中都可以见到，它就是 glLoadIdentity()，我们在 drawView:  方法的开始处调用它来进行状态复位。还见识过 glRotatef()，用来使二十面体旋转，另外还有 glTranslatef() 使物体在虚拟世界中移动。</p>
<p><br class="spacer_" /></p>
<p>我们首先看看glLoadIdentity()。此函数加载<em>单元矩阵。</em>我们将稍后讨论此特殊矩阵，但是加载单元矩阵本质上就是将虚拟世界进行复位。它清除了先前应用的任何变换。在绘制开始前调用  glLoadIdentity() 是一个很正常的习惯，因为你可以知道起点在什么地方 &#8211; 原点，从而可以预料变换的结果。</p>
<p>想知道如果你不调用glLoadIdentity()会产生什么结果，下载 <a href="../wp-content/uploads/2009/12/Part4ProjectFinal.zip">第四部分的Xcode项目</a>，在drawView:中为glLoadIdentity()调用加上注释，运行。发生了什么？</p>
<p><br class="spacer_" /></p>
<p>应该慢速旋转的二十面体急速地离开，对吗？像小飞鼠一样飞向天空离开视线。</p>
<p><br class="spacer_" /></p>
<p>发生这种现象的原因是在项目中我们使用了两个变换。二十面体的顶点是围绕原点定义的，所以我们使用<em>转移变换</em>将其移动三个单位远离观察者使整个物体可见。我们使用的第二个变换是<em>旋转变换</em>，它使多面体旋转。当使用了glLoadIdentity()时，每一帧开始时都是回到原点将多面体移离观察者三个单位，也就是说它每次都终止于同一位置z  = -3.0。类似地，旋转值是随时间增长而增加的，使二十面体以均匀的步调旋转。由于前一个旋转值在开始旋转前被  glLoadIdentity()调用清除掉所以旋转是以匀速进行的。</p>
<p>不调用glLoadIdentity()，尽管第一次二十面体还是移离观察者三个单位并旋转一个小的角度。但从第二帧开始（几十毫秒后），二十面体将继续后移三个单位而且其旋转角度rot叠加已经旋转的角度。这将发生在每一帧上，这意味着二十面体每帧都会移离三个单位而且旋转的速度都会增加。</p>
<p><br class="spacer_" /></p>
<p>尽管我们可以不调用  glLoadIdentity()而对其结果进行补偿，但是由于我们许多时候无法预料转换的结果，所以最好的方法还是让转换开始于已知的位置（通常是原点）并且未经过旋转或尺寸变化，这就是我们总是先调用  glLoadIdentity()的原因了。</p>
<h3></h3>
<h3>常见变换</h3>
<p><br class="spacer_" /></p>
<p>除了glTranslatef()和 glRotatef()外，还有 glScalef()，它使绘制物体的尺寸增大或减小。在OpenGL  ES中还有其他一些变换功能，但这三个（与glLoadIdentity()一起使用）是最常见的。其他功能主要用于将三维虚拟世界转换为二维表示的过程，这个过程称为<em>透视</em> 。我们稍后涉及一下透视，但大部分情况下，除了设置视口外我们不需要直接与透视打交道。</p>
<p><br class="spacer_" /></p>
<p>这些常用的变换非常实用。你可以仅使用这四个调用就完成整个游戏。但是有时你可能需要自己控制转换。你想要自己控制转换的一个原因是这些常用变换必须按顺序分别调用，每次调用都是一次具有一定开销的<em>矩阵乘法</em>（稍后讨论）。如果你自己进行转换，你可以将多个转换组合成一个矩阵，从而减少每帧都要进行的矩阵乘法操作。</p>
<p><br class="spacer_" /></p>
<p>由于你可以<em>向量化</em>你的矩阵乘法调用，所以你可以通过定义自己的矩阵来获取最大性能。据我所知，关于此点iPhone  并无文档记录，但作为基本规则OpenGL  ES将对向量间或顶点和矩阵间的乘法进行硬件加速，但两个转换矩阵间的乘法并无加速。通过矩阵乘法向量化，你可以获得比让OpenGL进行矩阵乘法更好的性能。由于通常矩阵与矩阵的乘法调用的数量远小于向量/顶点间的矩阵乘法调用的数量，所以这并不会带来巨大的性能提升，但在一个复杂的3D程序中，每个方面小的额外性能提升都很有好处。</p>
<h3></h3>
<h3>矩阵</h3>
<p><br class="spacer_" /></p>
<p>这里我明显不是指电影“黑客帝国”（&#8221;The Matrix&#8221;），我们将要在随后的篇幅中介绍矩阵。</p>
<p><br class="spacer_" /></p>
<blockquote><p><em>不幸的是没人可以告诉我矩阵是什么。</em></p>
</blockquote>
<p><br class="spacer_" /></p>
<p>实际上，矩阵不过是一个二维数组。就这么简单。没什么神秘的。下面是一个矩阵示例：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/simplematrix.png"><img title="simplematrix" src="../wp-content/uploads/2009/12/simplematrix.png" alt="simplematrix" width="385" height="279" /></a></p>
<p><br class="spacer_" /></p>
<p>这是一个 3&#215;3 矩阵，它有三行和三列。顶点和向量实际由一个1&#215;3的矩阵表示：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/vertexmatrix.png"><img title="vertexmatrix" src="../wp-content/uploads/2009/12/vertexmatrix.png" alt="vertexmatrix" width="283" height="72" /></a></p>
<p><br class="spacer_" /></p>
<p>一个顶点还可以由一个 3&#215;1 数组而不是一个 1&#215;3 数组表示，但是我们这里使用1&#215;3 格式（后面解释原因）。甚至一个数据元素技术上也可以用一个 1&#215;1  矩阵表示，尽管这并不是一个十分有用的矩阵。</p>
<p>还有什么可以用数组来表示？坐标系统。还记得向量？向量是想象的从原点指向空间一点的直线。现在，记住笛卡尔坐标系统具有三个轴：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/cartesian1.png"><img title="cartesian" src="../wp-content/uploads/2009/12/cartesian1.png" alt="cartesian" width="654" height="538" /></a></p>
<p><br class="spacer_" /></p>
<p>那么，指向X轴反向的归一向量是什么样的？记住：归一向量是长度为一的向量，所以一个沿X轴正向的归一向量是这样的：</p>
<p><br class="spacer_" /></p>
<p><img title="xaxisvector" src="../wp-content/uploads/2009/12/xaxisvector.png" alt="xaxisvector" width="129" height="192" /></p>
<p><br class="spacer_" /></p>
<p>注意我们是使用3&#215;1  矩阵而不是像处理顶点一样使用1&#215;3矩阵来表示向量。实际上这并不重要，只要按处理顶点相反的方式处理向量就行。向量中的三个数值适用于同一个坐标轴。我知道，这可能还不太好理解，但我很快就会解释到。Y轴向上的向量看上去像这样：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/yaxisvector.png"><img title="yaxisvector" src="../wp-content/uploads/2009/12/yaxisvector.png" alt="yaxisvector" width="129" height="196" /></a></p>
<p><br class="spacer_" /></p>
<p>Z轴上的向量像这样:</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/zaxisvector.png"><img title="zaxisvector" src="../wp-content/uploads/2009/12/zaxisvector.png" alt="zaxisvector" width="129" height="195" /></a></p>
<p><br class="spacer_" /></p>
<p>现在我们将这三个向量矩阵按照它们在顶点（x然后是y再后是z）中的顺序放入矩阵中，像这样：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/3x3identity.png"><img title="3x3identity" src="../wp-content/uploads/2009/12/3x3identity.png" alt="3x3identity" width="243" height="195" /></a></p>
<p><br class="spacer_" /></p>
<p>有一个特殊矩阵称为<em>单元矩阵。</em>听起来很熟悉？当你调用glLoadIdentity()时， 你就加载了一个单元矩阵<sup>1。</sup> 这里我解释一下为什么它是一个特殊矩阵。矩阵可以通过相乘而组合在一起。如果你将矩阵乘以一个单元矩阵，其结果就是原始矩阵，就像数字乘以一。你可以通过将所有除行号和列号相等的元素外的值设置为0来设定一个任意尺寸的单元矩阵，行号和列号相等处的值为1.0.</p>
<p><br class="spacer_" /></p>
<h3>矩阵相乘</h3>
<p><br class="spacer_" /></p>
<p>矩阵相乘是矩阵组合的关键。如果你有一个定义了转移到矩阵和一个定义了旋转的矩阵，如果将它们相乘，你将得到一个既定义了旋转又定义了转移的矩阵。让我们看看一个简单的矩阵相乘的简单例子。看看下面两个矩阵的图像：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/simplemultiply.png"><img title="simplemultiply" src="../wp-content/uploads/2009/12/simplemultiply.png" alt="simplemultiply" width="637" height="192" /></a></p>
<p><br class="spacer_" /></p>
<p><br class="spacer_" /></p>
<p>矩阵相乘的结果是另一个与等式左边矩阵一样尺寸的矩阵。矩阵乘法是不可置换的。顺序是很重要的。将矩阵a乘以矩阵b的结果不一定与矩阵b乘以矩阵a的结果相同（尽管在某些情况下有可能相同）。</p>
<p><br class="spacer_" /></p>
<p>有关矩阵相乘还有一件事需要注意：并不是所有的矩阵都可以相乘的。它们并不需要具有同样的尺寸，但等式中右边的矩阵的行数必须与等式中左边矩阵的列数相同。所以，你可以将一个3&#215;3的矩阵与另一个3&#215;3的矩阵相乘，或者你可以将一个1&#215;3的矩阵与一个3&#215;6的矩阵相乘，但你不能将一个2&#215;4的矩阵与另一个2&#215;4的矩阵相乘，因为2&#215;4矩阵的列数与另一个2&#215;4矩阵的行数并不相同。</p>
<p><br class="spacer_" /></p>
<p>要得出矩阵相乘的结果，我们首先建立一个与等式中左边矩阵同样尺寸的空矩阵：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/empty3x3matrix.png"><img title="empty3x3matrix" src="../wp-content/uploads/2009/12/empty3x3matrix.png" alt="empty3x3matrix" width="243" height="196" /></a></p>
<p><br class="spacer_" /></p>
<p>对矩阵中的各点，我们从左边矩阵中取出相应行，再从右边矩阵中取出相应列。所以，对于结果矩阵中的左上方的点，我们取出等式中左边矩阵的第一行以及等式中右边矩阵的第一列，像这样：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/multbreakdown.png"><img title="multbreakdown" src="../wp-content/uploads/2009/12/multbreakdown.png" alt="multbreakdown" width="651" height="140" /></a></p>
<p><br class="spacer_" /></p>
<p>然后，将左边矩阵行中第一个值乘以右边矩阵列的第一个值，左边矩阵行第二个值乘以右边矩阵列的第二个值，左边矩阵行第三个值乘以右边矩阵列的第三个值，然后再将它们相加。像这样：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/firstspotcalc.png"><img title="firstspotcalc" src="../wp-content/uploads/2009/12/firstspotcalc.png" alt="firstspotcalc" width="549" height="72" /></a></p>
<p><br class="spacer_" /></p>
<p>对结果矩阵中的各点重复以上步骤，得到以下结果：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/simplematrixfinal.png"><img title="simplematrixfinal" src="../wp-content/uploads/2009/12/simplematrixfinal.png" alt="simplematrixfinal" width="656" height="149" /></a></p>
<p><br class="spacer_" /></p>
<p>将矩阵（蓝色）乘以单元矩阵（红色），其结果就是原始矩阵。由于单元矩阵代表没有经过任何变换的坐标系统，所以结果完全合理。这也同样适用于顶点。我们将一个顶点乘以一个矩阵：</p>
<p><a href="../wp-content/uploads/2009/12/vertexmult.png"><img title="vertexmult" src="../wp-content/uploads/2009/12/vertexmult.png" alt="vertexmult" width="654" height="171" /></a></p>
<p><br class="spacer_" /></p>
<p>现在，我们假定我们希望旋转一个物体。我们要做的就是定义一个描述了被旋转的坐标系统的矩阵。在场景中，我们实际上是旋转了世界坐标，然后将物体绘制其上。如果说我们希望绕Z轴旋转一个物体，那么Z轴将保持不变，而X和Y轴将变化。尽管有些不那么直观，要定义一个绕z轴旋转的坐标系统，我们需要调整3&#215;3矩阵中的x和y向量，换句话说，我们必须修改第一和第二列。</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/vectoraxes.png"><img title="vectoraxes" src="../wp-content/uploads/2009/12/vectoraxes.png" alt="vectoraxes" width="243" height="235" /></a></p>
<p><br class="spacer_" /></p>
<p>所以需要调整通过修改X轴向量的X值和Y向量的Y值为旋转角度的余弦值。余弦是三角形角度对应的相邻两边之比。我们还需要修改X轴向量的Y值为相同角度的正弦值的负值，以及Y轴向量的X值为相同角度的正弦值。用矩阵来表示可能更容易理解：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/zrotation.png"><img title="zrotation" src="../wp-content/uploads/2009/12/zrotation.png" alt="zrotation" width="396" height="195" /></a></p>
<p><br class="spacer_" /></p>
<p>现在如果将世界坐标中所有物体的各顶点都乘以这个矩阵，那么物体将被旋转。一旦对物体的所有顶点都进行此操作，那么该物体将沿Z轴旋转n度。</p>
<p><br class="spacer_" /></p>
<p>如果你还不太理解也不要紧。其实你并不需要真正理解使用矩阵的数学原理。它们都是已经解决了的难题，你可以通过Google找到任何变换所需的矩阵。实际上，你可以在OpenGL的文档中找到大部分。所以如果你不能完全理解为什么这个矩阵导致绕Z轴的旋转，也完全不必气恼。</p>
<p><br class="spacer_" /></p>
<p>一个3&#215;3的矩阵可以描述绕任意轴旋转任何角度的情况。然而，为表示可能遇到的任何变换，我们仍然需要第四行/列。第四列用来保存变换信息，第四行用来表示透视变换。因为需要理解同原坐标以及透视空间，而这些对于成为一个出色的OpenGL程序员并不重要，所以我不打算在这里介绍透视变换的数学原理了。要乘以一个4&#215;4矩阵，我们需要填充一个附加值，通常我们称其为W。W应该为1。在进行乘法运算后，忽略W值。我不打算介绍向量的矩阵乘法，因为OpenGL已经对此进行了硬件加速，所以通常不需要进行手工处理，但理解其基本步骤是很好的建议。</p>
<p><br class="spacer_" /></p>
<h3>OpenGL ES矩阵</h3>
<p><br class="spacer_" /></p>
<p>OpenGL ES 中有两套矩阵，都是4&#215;4的GLfloat矩阵。一个叫 <em>modelview matrix</em> ，你大部分时间都会与之打交道。它是你用来对虚拟世界进行变换的矩阵。要对虚拟世界中的物体进行旋转，转移或尺寸变化，你都需要对此矩阵进行修改。</p>
<p><br class="spacer_" /></p>
<p>另一个矩阵用来创建根据设定的视口对世界坐标进行描述的二维表示。此矩阵称为 <em>projection matrix</em> 。在绝大部分时间内，你都不需要接触该矩阵。</p>
<p><br class="spacer_" /></p>
<p>任何时刻这两个矩阵中只能有一个是活动的，任何与矩阵相关的调用，包括 glLoadIdentity(), glRotatef(),  glTranslatef(), 和 glScalef() 只影响活动矩阵。当你调用  glLoadIdentity时，活动矩阵设置为单元矩阵。其他三个调用则创建一个转移/尺寸变换/旋转矩阵，并将该矩阵乘以活动矩阵，将活动矩阵的结果替换为矩阵乘法得到的结果。</p>
<p><br class="spacer_" /></p>
<p>在大部分情况下，你仅需在程序开始时将 modelview 矩阵设置为活动。实际上，如果你看过我的OpenGL ES 模板，你将在setupView:  方法中看到下面代码：</p>
<p><br class="spacer_" /></p>
<p>glMatrixMode(GL_MODELVIEW);</p>
<p><br class="spacer_" /></p>
<p>OpenGL ES的矩阵定义为一个由16 个GLfloat组成的数组，像这样：</p>
<pre>        GLfloat     matrix[16];</pre>
<p>它们也可以表示为一个二维C数组，像这样：</p>
<pre>        GLfloat     matrix[4][4];</pre>
<p>两种方法导致同样的内存被分配，所以完全是个人习惯问题，尽管前者更为普遍。</p>
<h4>主题</h4>
<p><br class="spacer_" /></p>
<p>好，我相信现在你已经有了足够的理论基础，想开始进行实践了。首先使用我的模板 <a href="http://www.topsyproxy.com/browse.php/be203aea/0b376beO/i8vd3d3L/mlubmVyb/G9vcC5ia/XovY29kZ/S9FbXB0e/SBPcGVuR/0wgRVMgQ/XBwbGljY/XRpb24ue/mlw/b0/">Open</a><a href="../wp-content/uploads/2009/12/Empty%20OpenGL%20ES%20Application.zip">Empty%20OpenGL%20ES%20Application</a> 创建一个项目，替换 drawView: 和 setupView:</p>
<pre>- (void)drawView:(GLView*)view;
{

    static GLfloat  rot = 0.0;
    static GLfloat  scale = 1.0;
    static GLfloat  yPos = 0.0;
    static BOOL     scaleIncreasing = YES;

    // This is the same result as using Vertex3D, just faster to type and
    // can be made const this way
    static const Vertex3D vertices[]= {
        {0, -0.525731, 0.850651},             // vertices[0]
        {0.850651, 0, 0.525731},              // vertices[1]
        {0.850651, 0, -0.525731},             // vertices[2]
        {-0.850651, 0, -0.525731},            // vertices[3]
        {-0.850651, 0, 0.525731},             // vertices[4]
        {-0.525731, 0.850651, 0},             // vertices[5]
        {0.525731, 0.850651, 0},              // vertices[6]
        {0.525731, -0.850651, 0},             // vertices[7]
        {-0.525731, -0.850651, 0},            // vertices[8]
        {0, -0.525731, -0.850651},            // vertices[9]
        {0, 0.525731, -0.850651},             // vertices[10]
        {0, 0.525731, 0.850651}               // vertices[11]
    };

    static const Color3D colors[] = {
        {1.0, 0.0, 0.0, 1.0},
        {1.0, 0.5, 0.0, 1.0},
        {1.0, 1.0, 0.0, 1.0},
        {0.5, 1.0, 0.0, 1.0},
        {0.0, 1.0, 0.0, 1.0},
        {0.0, 1.0, 0.5, 1.0},
        {0.0, 1.0, 1.0, 1.0},
        {0.0, 0.5, 1.0, 1.0},
        {0.0, 0.0, 1.0, 1.0},
        {0.5, 0.0, 1.0, 1.0},
        {1.0, 0.0, 1.0, 1.0},
        {1.0, 0.0, 0.5, 1.0}
    };

    static const GLubyte icosahedronFaces[] = {
        1, 2, 6,
        1, 7, 2,
        3, 4, 5,
        4, 3, 8,
        6, 5, 11,
        5, 6, 10,
        9, 10, 2,
        10, 9, 3,
        7, 8, 9,
        8, 7, 0,
        11, 0, 1,
        0, 11, 4,
        6, 2, 10,
        1, 6, 11,
        3, 5, 10,
        5, 4, 11,
        2, 7, 9,
        7, 1, 0,
        3, 9, 8,
        4, 8, 0,
    };

    static const Vector3D normals[] = {
        {0.000000, -0.417775, 0.675974},
        {0.675973, 0.000000, 0.417775},
        {0.675973, -0.000000, -0.417775},
        {-0.675973, 0.000000, -0.417775},
        {-0.675973, -0.000000, 0.417775},
        {-0.417775, 0.675974, 0.000000},
        {0.417775, 0.675973, -0.000000},
        {0.417775, -0.675974, 0.000000},
        {-0.417775, -0.675974, 0.000000},
        {0.000000, -0.417775, -0.675973},
        {0.000000, 0.417775, -0.675974},
        {0.000000, 0.417775, 0.675973},
    };

    glLoadIdentity();
    glTranslatef(0.0f,yPos,-3);
    glRotatef(rot,1.0f,1.0f,1.0f);
    glScalef(scale, scale, scale);

    glClearColor(0.0, 0.0, 0.05, 1.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_COLOR_ARRAY);
    glEnable(GL_COLOR_MATERIAL);
    glEnableClientState(GL_NORMAL_ARRAY);
    glVertexPointer(3, GL_FLOAT, 0, vertices);
    glColorPointer(4, GL_FLOAT, 0, colors);
    glNormalPointer(GL_FLOAT, 0, normals);
    glDrawElements(GL_TRIANGLES, 60, GL_UNSIGNED_BYTE, icosahedronFaces);

    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_COLOR_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);
    glDisable(GL_COLOR_MATERIAL);
    static NSTimeInterval lastDrawTime;
    if (lastDrawTime)
    {
        NSTimeInterval timeSinceLastDraw =
           [NSDate timeIntervalSinceReferenceDate] - lastDrawTime;
        rot+=50 * timeSinceLastDraw;  

        if (scaleIncreasing)
        {
            scale += timeSinceLastDraw;
            yPos += timeSinceLastDraw;
            if (scale &gt; 2.0)
                scaleIncreasing = NO;
        }
        else
        {
            scale -= timeSinceLastDraw;
            yPos -= timeSinceLastDraw;
            if (scale &lt; 1.0)
                scaleIncreasing = YES;

        }
    }
    lastDrawTime = [NSDate timeIntervalSinceReferenceDate];
}
-(void)setupView:(GLView*)view
{
    const GLfloat zNear = 0.01, zFar = 1000.0, fieldOfView = 45.0;
    GLfloat size;
    glEnable(GL_DEPTH_TEST);
    glMatrixMode(GL_PROJECTION);
    size = zNear * tanf(DEGREES_TO_RADIANS(fieldOfView) / 2.0);
    CGRect rect = view.bounds;
    glFrustumf(-size, size, -size / (rect.size.width / rect.size.height), size /
               (rect.size.width / rect.size.height), zNear, zFar);
    glViewport(0, 0, rect.size.width, rect.size.height);
    glMatrixMode(GL_MODELVIEW);

    // Enable lighting
    glEnable(GL_LIGHTING);

    // Turn the first light on
    glEnable(GL_LIGHT0);

    // Define the ambient component of the first light
    static const Color3D light0Ambient[] = {{0.3, 0.3, 0.3, 1.0}};
    glLightfv(GL_LIGHT0, GL_AMBIENT, (const GLfloat *)light0Ambient);

    // Define the diffuse component of the first light
    static const Color3D light0Diffuse[] = {{0.4, 0.4, 0.4, 1.0}};
    glLightfv(GL_LIGHT0, GL_DIFFUSE, (const GLfloat *)light0Diffuse);

    // Define the specular component of the first light
    static const Color3D light0Specular[] = {{0.7, 0.7, 0.7, 1.0}};
    glLightfv(GL_LIGHT0, GL_SPECULAR, (const GLfloat *)light0Specular);

    // Define the position of the first light
    // const GLfloat light0Position[] = {10.0, 10.0, 10.0};
    static const Vertex3D light0Position[] = {{10.0, 10.0, 10.0}};
    glLightfv(GL_LIGHT0, GL_POSITION, (const GLfloat *)light0Position); 

    // Calculate light vector so it points at the object
    static const Vertex3D objectPoint[] = {{0.0, 0.0, -3.0}};
    const Vertex3D lightVector =
        Vector3DMakeWithStartAndEndPoints(light0Position[0], objectPoint[0]);
    glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, (GLfloat *)&amp;lightVector);

    // Define a cutoff angle. This defines a 90° field of vision, since the cutoff
    // is number of degrees to each side of an imaginary line drawn from the light's
    // position along the vector supplied in GL_SPOT_DIRECTION above
    glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 25.0);

    glLoadIdentity();
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
}</pre>
<p>以上代码使用我们的老朋友 二十面体。与以前一样它旋转，但它同时还使用转移变换沿着Y轴上下移动，并且使用尺寸变换矩阵增加和减小尺寸。它使用了所有三个  modelview 变换；我们加载单元矩阵，使用OpenGL ES内部变换函数改变尺寸，旋转并且移动 。</p>
<p><br class="spacer_" /></p>
<p>让我们用自定义的矩阵替换内部函数。在进行修改前，先运行一下看看我们的程序到底应该是怎样工作的。</p>
<p><br class="spacer_" /></p>
<h4>定义矩阵</h4>
<p><br class="spacer_" /></p>
<p>我们自定义一个矩阵。</p>
<pre>typedef GLfloat Matrix3D[16];</pre>
<h3>自定义单元矩阵</h3>
<p><br class="spacer_" /></p>
<p>首先，我们自定义一个单元矩阵。像这样：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/4x4identity.png"><img title="4x4identity" src="../wp-content/uploads/2009/12/4x4identity.png" alt="4x4identity" width="291" height="222" /></a></p>
<p><br class="spacer_" /></p>
<p>下面是代码：</p>
<pre>static inline void Matrix3DSetIdentity(Matrix3D matrix)
{
    matrix[0] = matrix[5] =  matrix[10] = matrix[15] = 1.0;
    matrix[1] = matrix[2] = matrix[3] = matrix[4] = 0.0;
    matrix[6] = matrix[7] = matrix[8] = matrix[9] = 0.0;
    matrix[11] = matrix[12] = matrix[13] = matrix[14] = 0.0;
}</pre>
<p>第一印象这好像不太对。看上去我们传递的是Matrix3D的值。然而我们使用的是 typedef<sup>2</sup> 数组，由于C99数组与指针等价，数组是通过参考而不是值传递的，所以我们只是赋值给个数组中的值而不需要直接传递指针。</p>
<p><br class="spacer_" /></p>
<p>我使用了内嵌函数以消除函数调用的开销。但是这会有一定的副作用（主要是增加了代码的尺寸），本文中的代码与普通C函数一样。注意在C和Objective-C程序中，  static 关键字是正确的（而且是很好的方法），但是如果你使用 C++ 或者 Objective-C++，那么你应该移除它。 <a href="http://gcc.gnu.org/onlinedocs/gcc-4.0.4/gcc/Inline.html">GCC 手册</a> 推荐在C内嵌函数中使用static，这是因为它允许编译器移除未被使用的内嵌函数所生成的汇编码。然而如果你使用 C++ 或者 Objective-C++，关键字  static 可能会影响链接器的行为而不会提供真正的好处。</p>
<p><br class="spacer_" /></p>
<p>好，现在我们使用新函数替代 glLoadIdentity()。删除 glLoadIdentity()然后使用下列代码进行替换：</p>
<pre>    static Matrix3D    identityMatrix;
    Matrix3DSetIdentity(identityMatrix);
    glLoadMatrixf(identityMatrix);</pre>
<p><br class="spacer_" /></p>
<p>我们定义了一个 Matrix3D，赋予其单元矩阵值，然后使用 glLoadMatrixf()加载此矩阵，这使用单元矩阵替代了活动矩阵（即本文情况下的  modelview 矩阵)。这与glLoadIdentity()调用完全一样。运行，你将看到与以前一样的结果。</p>
<p><br class="spacer_" /></p>
<p>现在，你明白了 glLoadIdentity() 是怎样工作的。继续。</p>
<h3>矩阵乘法</h3>
<p><br class="spacer_" /></p>
<p>在开始任何更多的变换前，我们需要编写一个两个矩阵相乘的函数。记住矩阵相乘是将两个矩阵合成一个矩阵的方法。我们需要编写一个通用矩阵相乘的方法，它允许任何尺寸的数组并且使用循环进行计算，但是我们最好不要使用循环。循环通常会带来一些开销。由于OpenGL  ES中的矩阵总是为 4&#215;4，最快的方法是分别进行计算。下面是矩阵乘法的代码：</p>
<pre>static inline void Matrix3DMultiply(Matrix3D m1, Matrix3D m2, Matrix3D result)
{
    result[0] = m1[0] * m2[0] + m1[4] * m2[1] + m1[8] * m2[2] + m1[12] * m2[3];
    result[1] = m1[1] * m2[0] + m1[5] * m2[1] + m1[9] * m2[2] + m1[13] * m2[3];
    result[2] = m1[2] * m2[0] + m1[6] * m2[1] + m1[10] * m2[2] + m1[14] * m2[3];
    result[3] = m1[3] * m2[0] + m1[7] * m2[1] + m1[11] * m2[2] + m1[15] * m2[3];

    result[4] = m1[0] * m2[4] + m1[4] * m2[5] + m1[8] * m2[6] + m1[12] * m2[7];
    result[5] = m1[1] * m2[4] + m1[5] * m2[5] + m1[9] * m2[6] + m1[13] * m2[7];
    result[6] = m1[2] * m2[4] + m1[6] * m2[5] + m1[10] * m2[6] + m1[14] * m2[7];
    result[7] = m1[3] * m2[4] + m1[7] * m2[5] + m1[11] * m2[6] + m1[15] * m2[7];

    result[8] = m1[0] * m2[8] + m1[4] * m2[9] + m1[8] * m2[10] + m1[12] * m2[11];
    result[9] = m1[1] * m2[8] + m1[5] * m2[9] + m1[9] * m2[10] + m1[13] * m2[11];
    result[10] = m1[2] * m2[8] + m1[6] * m2[9] + m1[10] * m2[10] + m1[14] * m2[11];
    result[11] = m1[3] * m2[8] + m1[7] * m2[9] + m1[11] * m2[10] + m1[15] * m2[11];

    result[12] = m1[0] * m2[12] + m1[4] * m2[13] + m1[8] * m2[14] + m1[12] * m2[15];
    result[13] = m1[1] * m2[12] + m1[5] * m2[13] + m1[9] * m2[14] + m1[13] * m2[15];
    result[14] = m1[2] * m2[12] + m1[6] * m2[13] + m1[10] * m2[14] + m1[14] * m2[15];
    result[15] = m1[3] * m2[12] + m1[7] * m2[13] + m1[11] * m2[14] + m1[15] * m2[15];
}
</pre>
<p>此函数不分配任何内存，它只是将两个数组相乘的结果赋予结果数组。结果数组并非两个相乘数组中的任何一个，这是由于如果将结果赋予两个数组中的任意一个，在再次使用时会产生不正确的结果。</p>
<p><br class="spacer_" /></p>
<p>但是，等一下… 这实际不是更快一些吗，至少在iPhone上如此。iPhone具有4个向量处理器，它们使得进行浮点运算比使用iPhone  CPU更快。然而使用向量处理器要求编写 ARM6 汇编码因为没有C函数库可以访问向量处理器。幸运的是已经有人找到通过使用向量处理器进行矩阵相乘的方法了。<a href="http://code.google.com/p/vfpmathlibrary/">VFP 数学库</a> 包含了许多向量运算功能而且它的许可证要求很宽容。所以我将在我们的代码中使用 VFP  数学库进行矩阵相乘，当运行在设备上向量版将被使用而在模拟器上则使用普通版（注意我已经包括了VFP数学库的许可证）：</p>
<pre>/*
   These define the vectorized version of the
   matrix multiply function and are based on the Matrix4Mul method from
   the vfp-math-library. This code has been modified, but is still subject to
   the original license terms and ownership as follow:

 VFP math library for the iPhone / iPod touch

 Copyright (c) 2007-2008 Wolfgang Engel and Matthias Grundmann
 http://code.google.com/p/vfpmathlibrary/

 This software is provided 'as-is', without any express or implied warranty.
 In no event will the authors be held liable for any damages arising
 from the use of this software.
 Permission is granted to anyone to use this software for any purpose,
 including commercial applications, and to alter it and redistribute it freely,
 subject to the following restrictions:

 1. The origin of this software must not be misrepresented; you must
 not claim that you wrote the original software. If you use this
 software in a product, an acknowledgment in the product documentation
 would be appreciated but is not required.

 2. Altered source versions must be plainly marked as such, and must
 not be misrepresented as being the original software.

 3. This notice may not be removed or altered from any source distribution.
 */
#if TARGET_OS_IPHONE &amp;&amp; !TARGET_IPHONE_SIMULATOR
#define VFP_CLOBBER_S0_S31 "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", "s8",  \
"s9", "s10", "s11", "s12", "s13", "s14", "s15", "s16",  \
"s17", "s18", "s19", "s20", "s21", "s22", "s23", "s24",  \
"s25", "s26", "s27", "s28", "s29", "s30", "s31"
#define VFP_VECTOR_LENGTH(VEC_LENGTH) "fmrx    r0, fpscr                    \n\t" \
"bic     r0, r0, #0x00370000               \n\t" \
"orr     r0, r0, #0x000" #VEC_LENGTH "0000 \n\t" \
"fmxr    fpscr, r0                         \n\t"
#define VFP_VECTOR_LENGTH_ZERO "fmrx    r0, fpscr            \n\t" \
"bic     r0, r0, #0x00370000  \n\t" \
"fmxr    fpscr, r0            \n\t"
#endif
static inline void Matrix3DMultiply(Matrix3D m1, Matrix3D m2, Matrix3D result)
{
#if TARGET_OS_IPHONE &amp;&amp; !TARGET_IPHONE_SIMULATOR
    __asm__ __volatile__ ( VFP_VECTOR_LENGTH(3)

                  // Interleaving loads and adds/muls for faster calculation.
                  // Let A:=src_ptr_1, B:=src_ptr_2, then
                  // function computes A*B as (B^T * A^T)^T.

                  // Load the whole matrix into memory.
                  "fldmias  %2, {s8-s23}    \n\t"
                  // Load first column to scalar bank.
                  "fldmias  %1!, {s0-s3}    \n\t"
                  // First column times matrix.
                  "fmuls s24, s8, s0        \n\t"
                  "fmacs s24, s12, s1       \n\t"

                  // Load second column to scalar bank.
                  "fldmias %1!,  {s4-s7}    \n\t"

                  "fmacs s24, s16, s2       \n\t"
                  "fmacs s24, s20, s3       \n\t"
                  // Save first column.
                  "fstmias  %0!, {s24-s27}  \n\t" 

                  // Second column times matrix.
                  "fmuls s28, s8, s4        \n\t"
                  "fmacs s28, s12, s5       \n\t"

                  // Load third column to scalar bank.
                  "fldmias  %1!, {s0-s3}    \n\t"

                  "fmacs s28, s16, s6       \n\t"
                  "fmacs s28, s20, s7       \n\t"
                  // Save second column.
                  "fstmias  %0!, {s28-s31}  \n\t" 

                  // Third column times matrix.
                  "fmuls s24, s8, s0        \n\t"
                  "fmacs s24, s12, s1       \n\t"

                  // Load fourth column to scalar bank.
                  "fldmias %1,  {s4-s7}    \n\t"

                  "fmacs s24, s16, s2       \n\t"
                  "fmacs s24, s20, s3       \n\t"
                  // Save third column.
                  "fstmias  %0!, {s24-s27}  \n\t" 

                  // Fourth column times matrix.
                  "fmuls s28, s8, s4        \n\t"
                  "fmacs s28, s12, s5       \n\t"
                  "fmacs s28, s16, s6       \n\t"
                  "fmacs s28, s20, s7       \n\t"
                  // Save fourth column.
                  "fstmias  %0!, {s28-s31}  \n\t" 

                  VFP_VECTOR_LENGTH_ZERO
                  : "=r" (result), "=r" (m2)
                  : "r" (m1), "0" (result), "1" (m2)
                  : "r0", "cc", "memory", VFP_CLOBBER_S0_S31
                  );
#else
    result[0] = m1[0] * m2[0] + m1[4] * m2[1] + m1[8] * m2[2] + m1[12] * m2[3];
    result[1] = m1[1] * m2[0] + m1[5] * m2[1] + m1[9] * m2[2] + m1[13] * m2[3];
    result[2] = m1[2] * m2[0] + m1[6] * m2[1] + m1[10] * m2[2] + m1[14] * m2[3];
    result[3] = m1[3] * m2[0] + m1[7] * m2[1] + m1[11] * m2[2] + m1[15] * m2[3];

    result[4] = m1[0] * m2[4] + m1[4] * m2[5] + m1[8] * m2[6] + m1[12] * m2[7];
    result[5] = m1[1] * m2[4] + m1[5] * m2[5] + m1[9] * m2[6] + m1[13] * m2[7];
    result[6] = m1[2] * m2[4] + m1[6] * m2[5] + m1[10] * m2[6] + m1[14] * m2[7];
    result[7] = m1[3] * m2[4] + m1[7] * m2[5] + m1[11] * m2[6] + m1[15] * m2[7];

    result[8] = m1[0] * m2[8] + m1[4] * m2[9] + m1[8] * m2[10] + m1[12] * m2[11];
    result[9] = m1[1] * m2[8] + m1[5] * m2[9] + m1[9] * m2[10] + m1[13] * m2[11];
    result[10] = m1[2] * m2[8] + m1[6] * m2[9] + m1[10] * m2[10] + m1[14] * m2[11];
    result[11] = m1[3] * m2[8] + m1[7] * m2[9] + m1[11] * m2[10] + m1[15] * m2[11];

    result[12] = m1[0] * m2[12] + m1[4] * m2[13] + m1[8] * m2[14] + m1[12] * m2[15];
    result[13] = m1[1] * m2[12] + m1[5] * m2[13] + m1[9] * m2[14] + m1[13] * m2[15];
    result[14] = m1[2] * m2[12] + m1[6] * m2[13] + m1[10] * m2[14] + m1[14] * m2[15];
    result[15] = m1[3] * m2[12] + m1[7] * m2[13] + m1[11] * m2[14] + m1[15] * m2[15];
#endif
}</pre>
<p>既然我们有将矩阵相乘的能力，我们就可以合并多个矩阵。由于我们的矩阵相乘的方法是硬件加速的，而OpenGL  ES不支持矩阵与矩阵相乘的硬件加速，所以我们的方法应该比内嵌的变换方法要快一些<sup>3</sup> 。我们现在开始进行转移变换。</p>
<p><br class="spacer_" /></p>
<h4>自定义转移</h4>
<p><br class="spacer_" /></p>
<p>如果你还记得，我们使用4&#215;4矩阵而不是使用3&#215;3矩阵的一个原因是我们需要额外的一列用来保存转移信息。转移矩阵看上去像这样：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/translatematrix.png"><img title="translatematrix" src="../wp-content/uploads/2009/12/translatematrix.png" alt="translatematrix" width="291" height="222" /></a></p>
<p><br class="spacer_" /></p>
<p>我们可以将其转换为函数：</p>
<pre>static inline void Matrix3DSetTranslation(Matrix3D matrix, GLfloat xTranslate,
    GLfloat yTranslate, GLfloat zTranslate)
{
    matrix[0] = matrix[5] =  matrix[10] = matrix[15] = 1.0;
    matrix[1] = matrix[2] = matrix[3] = matrix[4] = 0.0;
    matrix[6] = matrix[7] = matrix[8] = matrix[9] = 0.0;
    matrix[11] = 0.0;
    matrix[12] = xTranslate;
    matrix[13] = yTranslate;
    matrix[14] = zTranslate;
}</pre>
<p>现在，我们怎样将其引入到 drawView: 方法中? 我们可以删除  glTranslatef()，替换为定义另一个矩阵并赋予适当的转移值，然后将其与当前矩阵相乘最后将结果加载到OpenGL的代码，对吗？</p>
<pre>    static Matrix3D    identityMatrix;
    Matrix3DSetIdentity(identityMatrix);
    static Matrix3D    translateMatrix;
    Matrix3DSetTranslation(translateMatrix, 0.0, yPos, -3.0);
    static Matrix3D    resultMatrix;
    Matrix3DMultiply(identityMatrix, translateMatrix, resultMatrix);
    glLoadMatrixf(resultMatrix);</pre>
<p>是的，这可以工作，但它做了一些不必要的工作。记住，如果你将矩阵与单元矩阵相乘，其结果一定是矩阵本身。所以当使用自定义矩阵时，如果要进行任何变换，我们不再需要首先加载单元矩阵。我们只需要创建变换矩阵并加载它：</p>
<pre>    static Matrix3D    translateMatrix;
    Matrix3DSetTranslation(translateMatrix, 0.0, yPos, -3.0);
    glLoadMatrixf(translateMatrix);</pre>
<p>由于不需要加载单元矩阵，此方法可以省去一些工作。另外，注意我将Matrix3D定义为static。我们不希望经常分配和解除内存分配。我们知道当程序运行时每一秒钟都需要用到此矩阵许多次，所以将其定义为static可以省去分配和解除内存分配的开销。</p>
<h3>自定义尺寸变换</h3>
<p><br class="spacer_" /></p>
<p>一个用于物体尺寸变换的矩阵像这样：</p>
<p><a href="../wp-content/uploads/2009/12/scalematrix.png"><img title="scalematrix" src="../wp-content/uploads/2009/12/scalematrix.png" alt="scalematrix" width="291" height="226" /></a></p>
<p><br class="spacer_" /></p>
<p>x, y, 或 z  为1.0表示在相应方向上尺寸无变化。3个值都为1.0代表单元矩阵。如果你赋值为2.0，则物在相应轴上尺寸加倍。我们可以将尺寸变换矩阵转化为OpenGL  ES矩阵，像这样：</p>
<pre>static inline void Matrix3DSetScaling(Matrix3D matrix, GLfloat xScale,
    GLfloat yScale, GLfloat zScale)
{
    matrix[1] = matrix[2] = matrix[3] = matrix[4] = 0.0;
    matrix[6] = matrix[7] = matrix[8] = matrix[9] = 0.0;
    matrix[11] = matrix[12] = matrix[13] = matrix[14] = 0.0;
    matrix[0] = xScale;
    matrix[5] = yScale;
    matrix[10] = zScale;
    matrix[15] = 1.0;
}</pre>
<p>现在，因为我们需要使用超过一种变换，我们将使用矩阵乘法。要进行尺寸变换和旋转，我们需要将这两个矩阵相乘。删除先前代码中的 glScalef()  替换为下列代码：</p>
<pre>    static Matrix3D    translateMatrix;
    Matrix3DSetTranslation(translateMatrix, 0.0, yPos, -3.0);
    static Matrix3D    scaleMatrix;
    Matrix3DSetScaling(scaleMatrix, scale, scale, scale);
    static Matrix3D     resultMatrix;
    Matrix3DMultiply(translateMatrix, scaleMatrix, resultMatrix);
    glLoadMatrixf(resultMatrix);</pre>
<p>我们创建一个矩阵并赋予其适当的转移值。然后创建尺寸变换矩阵并赋值。然后将这两个矩阵相乘将结果加载到模型视口矩阵中。</p>
<h3>自定义旋转</h3>
<p><br class="spacer_" /></p>
<p>旋转稍微有点难度。我们可以创建绕各轴旋转的矩阵。我们已经知道绕Z轴旋转的矩阵像这样：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/zrotation1.png"><img title="zrotation" src="../wp-content/uploads/2009/12/zrotation1.png" alt="zrotation" width="396" height="195" /></a></p>
<p><br class="spacer_" /></p>
<p>绕X轴像这样：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/xaxisrotation.png"><img title="xaxisrotation" src="../wp-content/uploads/2009/12/xaxisrotation.png" alt="xaxisrotation" width="393" height="192" /></a></p>
<p><br class="spacer_" /></p>
<p>绕 Y轴旋转：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/yaxisrotation.png"><img title="yaxisrotation" src="../wp-content/uploads/2009/12/yaxisrotation.png" alt="yaxisrotation" width="393" height="193" /></a></p>
<p><br class="spacer_" /></p>
<p>这三种旋转写成函数：</p>
<pre>static inline void Matrix3DSetXRotationUsingRadians(Matrix3D matrix, GLfloat degrees)
{
    matrix[0] = matrix[15] = 1.0;
    matrix[1] = matrix[2] = matrix[3] = matrix[4] = 0.0;
    matrix[7] = matrix[8] = 0.0;
    matrix[11] = matrix[12] = matrix[13] = matrix[14] = 0.0;

    matrix[5] = cosf(degrees);
    matrix[6] = -fastSinf(degrees);
    matrix[9] = -matrix[6];
    matrix[10] = matrix[5];
}
static inline void Matrix3DSetXRotationUsingDegrees(Matrix3D matrix, GLfloat degrees)
{
    Matrix3DSetXRotationUsingRadians(matrix, degrees * M_PI / 180.0);
}
static inline void Matrix3DSetYRotationUsingRadians(Matrix3D matrix, GLfloat degrees)
{
    matrix[0] = cosf(degrees);
    matrix[2] = fastSinf(degrees);
    matrix[8] = -matrix[2];
    matrix[10] = matrix[0];
    matrix[1] = matrix[3] = matrix[4] = matrix[6] = matrix[7] = 0.0;
    matrix[9] = matrix[11] = matrix[13] = matrix[12] = matrix[14] = 0.0;
    matrix[5] = matrix[15] = 1.0;
}
static inline void Matrix3DSetYRotationUsingDegrees(Matrix3D matrix, GLfloat degrees)
{
    Matrix3DSetYRotationUsingRadians(matrix, degrees * M_PI / 180.0);
}
static inline void Matrix3DSetZRotationUsingRadians(Matrix3D matrix, GLfloat degrees)
{
    matrix[0] = cosf(degrees);
    matrix[1] = fastSinf(degrees);
    matrix[4] = -matrix[1];
    matrix[5] = matrix[0];
    matrix[2] = matrix[3] = matrix[6] = matrix[7] = matrix[8] = 0.0;
    matrix[9] = matrix[11] = matrix[12] = matrix[13] = matrix[14] = 0.0;
    matrix[10] = matrix[15] = 1.0;
}
static inline void Matrix3DSetZRotationUsingDegrees(Matrix3D matrix, GLfloat degrees)
{
    Matrix3DSetZRotationUsingRadians(matrix, degrees * M_PI / 180.0);
}</pre>
<p>对于各轴的旋转有两种方法，一种是使用弧度，另一种是使用角度。这三个矩阵被称为<em>Eular angle</em> 。有一个问题是我们必须按顺序对多个轴进行旋转，当我们对这三个角度进行旋转时，我们可能会遇到一种被称作<a href="http://www.topsyproxy.com/browse.php/be203aea/0b376beO/i8vZW4ud/2lraXBlZ/GlhLm9yZ/y93aWtpL/0dpbWJhb/F9sb2Nr/b0/">gimbal  lock</a>（框架自锁）的现像，它导致其中一个轴的旋转被锁定。为防止这种现象的发生，我们必须创建一个可以处理多轴旋转的矩阵。除了可以消除自锁的问题，它还能在物体需要绕多轴旋转时节省处理器的开销。</p>
<p><br class="spacer_" /></p>
<p>说实在的，我并不打算假装理解了此方法后面的数学机制。我读过一篇有关此机制的博士论文（<a href="http://www.topsyproxy.com/browse.php/be203aea/0b376beO/i8vZW4ud/2lraXBlZ/GlhLm9yZ/y93aWtpL/1F1YXRlc/m5pb24_3/D/b0/">四元数</a>的矩阵表示），但我无法完全理解所有的数学原理，所以我们就基于信任此多旋转矩阵可以正常工作（它确实如此）。此矩阵假定了一个指定的角度N以及一个由3个浮点数值表示的向量。此向量的各元素将被N乘，从而导致绕对应轴的旋转：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/multirotatematrix.png"><img title="multirotatematrix" src="../wp-content/uploads/2009/12/multirotatematrix.png" alt="multirotatematrix" width="664" height="122" /></a></p>
<p><br class="spacer_" /></p>
<p>此矩阵要求向量是以单元向量（即法线）方式被传递的，所以我们在为矩阵赋值前必须保证这点。表示为OpenGL矩阵，像这样：</p>
<pre>static inline void Matrix3DSetRotationByRadians(Matrix3D matrix, GLfloat angle,
    GLfloat x, GLfloat y, GLfloat z)
{
    GLfloat mag = sqrtf((x*x) + (y*y) + (z*z));
    if (mag == 0.0)
    {
        x = 1.0;
        y = 0.0;
        z = 0.0;
    }
    else if (mag != 1.0)
    {
        x /= mag;
        y /= mag;
        z /= mag;
    }

    GLfloat c = cosf(angle);
    GLfloat s = fastSinf(angle);
    matrix[3] = matrix[7] = matrix[11] = matrix[12] = matrix[13] = matrix[14] = 0.0;
    matrix[15] = 1.0;

    matrix[0] = (x*x)*(1-c) + c;
    matrix[1] = (y*x)*(1-c) + (z*s);
    matrix[2] = (x*z)*(1-c) - (y*s);
    matrix[4] = (x*y)*(1-c)-(z*s);
    matrix[5] = (y*y)*(1-c)+c;
    matrix[6] = (y*z)*(1-c)+(x*s);
    matrix[8] = (x*z)*(1-c)+(y*s);
    matrix[9] = (y*z)*(1-c)-(x*s);
    matrix[10] = (z*z)*(1-c)+c;

}
static inline void Matrix3DSetRotationByDegrees(Matrix3D matrix, GLfloat angle,
    GLfloat x, GLfloat y, GLfloat z)
{
    Matrix3DSetRotationByRadians(matrix, angle * M_PI / 180.0, x, y, z);
}</pre>
<p><br class="spacer_" /></p>
<p>此多轴旋转的版本以glRotatef()一样地工作。</p>
<p><br class="spacer_" /></p>
<p>现在我们替换了3个内嵌函数，下面是使用自定义矩阵和变换函数的 drawView: 方法。新矩阵代码以<strong>粗体</strong>表示：</p>
<pre>- (void)drawView:(GLView*)view;
{

    static GLfloat  rot = 0.0;
    static GLfloat  scale = 1.0;
    static GLfloat  yPos = 0.0;
    static BOOL     scaleIncreasing = YES;

    // This is the same result as using Vertex3D, just faster to type and
    // can be made const this way
    static const Vertex3D vertices[]= {
        {0, -0.525731, 0.850651},             // vertices[0]
        {0.850651, 0, 0.525731},              // vertices[1]
        {0.850651, 0, -0.525731},             // vertices[2]
        {-0.850651, 0, -0.525731},            // vertices[3]
        {-0.850651, 0, 0.525731},             // vertices[4]
        {-0.525731, 0.850651, 0},             // vertices[5]
        {0.525731, 0.850651, 0},              // vertices[6]
        {0.525731, -0.850651, 0},             // vertices[7]
        {-0.525731, -0.850651, 0},            // vertices[8]
        {0, -0.525731, -0.850651},            // vertices[9]
        {0, 0.525731, -0.850651},             // vertices[10]
        {0, 0.525731, 0.850651}               // vertices[11]
    };

    static const Color3D colors[] = {
        {1.0, 0.0, 0.0, 1.0},
        {1.0, 0.5, 0.0, 1.0},
        {1.0, 1.0, 0.0, 1.0},
        {0.5, 1.0, 0.0, 1.0},
        {0.0, 1.0, 0.0, 1.0},
        {0.0, 1.0, 0.5, 1.0},
        {0.0, 1.0, 1.0, 1.0},
        {0.0, 0.5, 1.0, 1.0},
        {0.0, 0.0, 1.0, 1.0},
        {0.5, 0.0, 1.0, 1.0},
        {1.0, 0.0, 1.0, 1.0},
        {1.0, 0.0, 0.5, 1.0}
    };

    static const GLubyte icosahedronFaces[] = {
        1, 2, 6,
        1, 7, 2,
        3, 4, 5,
        4, 3, 8,
        6, 5, 11,
        5, 6, 10,
        9, 10, 2,
        10, 9, 3,
        7, 8, 9,
        8, 7, 0,
        11, 0, 1,
        0, 11, 4,
        6, 2, 10,
        1, 6, 11,
        3, 5, 10,
        5, 4, 11,
        2, 7, 9,
        7, 1, 0,
        3, 9, 8,
        4, 8, 0,
    };

    static const Vector3D normals[] = {
        {0.000000, -0.417775, 0.675974},
        {0.675973, 0.000000, 0.417775},
        {0.675973, -0.000000, -0.417775},
        {-0.675973, 0.000000, -0.417775},
        {-0.675973, -0.000000, 0.417775},
        {-0.417775, 0.675974, 0.000000},
        {0.417775, 0.675973, -0.000000},
        {0.417775, -0.675974, 0.000000},
        {-0.417775, -0.675974, 0.000000},
        {0.000000, -0.417775, -0.675973},
        {0.000000, 0.417775, -0.675974},
        {0.000000, 0.417775, 0.675973},
    };

    <strong>static Matrix3D    translateMatrix;
    Matrix3DSetTranslation(translateMatrix, 0.0, yPos, -3.0);
    static Matrix3D    scaleMatrix;
    Matrix3DSetScaling(scaleMatrix, scale, scale, scale);
    static Matrix3D     tempMatrix;
    Matrix3DMultiply(translateMatrix, scaleMatrix, tempMatrix);
    static Matrix3D    rotationMatrix;
    Matrix3DSetRotationByDegrees(rotationMatrix, rot, 1.0f, 1.0f, 1.0f);
    static Matrix3D    finalMatrix;
    Matrix3DMultiply(tempMatrix, rotationMatrix, finalMatrix);
    glLoadMatrixf(finalMatrix);</strong>

    glClearColor(0.0, 0.0, 0.05, 1.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_COLOR_ARRAY);
    glEnable(GL_COLOR_MATERIAL);
    glEnableClientState(GL_NORMAL_ARRAY);
    glVertexPointer(3, GL_FLOAT, 0, vertices);
    glColorPointer(4, GL_FLOAT, 0, colors);
    glNormalPointer(GL_FLOAT, 0, normals);
    glDrawElements(GL_TRIANGLES, 60, GL_UNSIGNED_BYTE, icosahedronFaces);

    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_COLOR_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);
    glDisable(GL_COLOR_MATERIAL);
    static NSTimeInterval lastDrawTime;
    if (lastDrawTime)
    {
        NSTimeInterval timeSinceLastDraw =
            [NSDate timeIntervalSinceReferenceDate] - lastDrawTime;
        rot+=50 * timeSinceLastDraw;  

        if (scaleIncreasing)
        {
            scale += timeSinceLastDraw;
            yPos += timeSinceLastDraw;
            if (scale &gt; 2.0)
                scaleIncreasing = NO;
        }
        else
        {
            scale -= timeSinceLastDraw;
            yPos -= timeSinceLastDraw;
            if (scale &lt; 1.0)
                scaleIncreasing = YES;

        }
    }
    lastDrawTime = [NSDate timeIntervalSinceReferenceDate];
}</pre>
<h4>诡异但很奇妙的自定义矩阵</h4>
<p><br class="spacer_" /></p>
<p>到目前为止，我仅仅展示了怎样重建OpenGL存在的功能，我们所作的只是基于矩阵相乘是硬件加速的这一原理对性能进行了一番小小的提升，但在99%的情况下，还不足以让我们如此大费周章。</p>
<p><br class="spacer_" /></p>
<p>但是，手工处理矩阵变换还有其他好处。例如，你可以创建OpenGL ES本身不支持的变换。比如，你可以进行<em>剪切变换。 </em>剪切本质上就是将物体沿两轴扭转。如果你对一个塔的轴进行扭转，那么它就会成为比萨斜塔。下面是剪切矩阵：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/shearmatrix.png"><img title="shearmatrix" src="../wp-content/uploads/2009/12/shearmatrix.png" alt="shearmatrix" width="427" height="196" /></a></p>
<p><br class="spacer_" /></p>
<p>下面是代码：</p>
<pre>static inline void Matrix3DSetShear(Matrix3D matrix, GLfloat xShear, GLfloat yShear)
{
    matrix[0] = matrix[5] =  matrix[10] = matrix[15] = 1.0;
    matrix[1] = matrix[2] = matrix[3] = 0.0;
    matrix[6] = matrix[7] = matrix[8] = matrix[9] = 0.0;
    matrix[11] = matrix[12] = matrix[13] = matrix[14] = 0.0;
    matrix[1] = xShear;
    matrix[4] = yShear;
}
</pre>
<p>如果我们应用剪切矩阵，我们将得到：</p>
<p><br class="spacer_" /></p>
<p><a href="../wp-content/uploads/2009/12/iPhone-SimulatorScreenSnapz0012.jpg"><img title="iPhone SimulatorScreenSnapz001" src="../wp-content/uploads/2009/12/iPhone-SimulatorScreenSnapz0012.jpg" alt="iPhone SimulatorScreenSnapz001" width="312" height="600" /></a></p>
<p><br class="spacer_" /></p>
<p>尝试一下用内嵌函数实现。你可以组合矩阵调用。例如，创建一个函数不需要任何矩阵乘法生成转移和尺寸变换。</p>
<p><br class="spacer_" /></p>
<h3>结论</h3>
<p><br class="spacer_" /></p>
<p>矩阵是一个很大而且经常让人误解的课题，许多人（包括我自己）都在殚精竭虑去理解它。希望本文能给你足够的帮助，而且还提供了一个矩阵相关的库，你可以用在自己的项目中。如果你希望下载自己尝试一下，请下载<a href="http://www.topsyproxy.com/browse.php/be203aea/0b376beO/i8vd3d3L/mlubmVyb/G9vcC5ia/XovY29kZ/S9QYXJ0N/lByb2plY/3Quemlw/b0/">fee</a><a href="../wp-content/uploads/2009/12/Part6Project.zip">Part6Project</a>l。我定义了两个常量，一个用来打开/关闭自定义变换，另一个用来打开/关闭剪切变换。它们在  <em>GLViewController.h </em>中:</p>
<pre>#define USE_CUSTOM_MATRICES 1
#define USE_SHEAR_TRANSFORM 1</pre>
<p>1为打开，0为关闭。另外我还更新了 <a href="http://www.topsyproxy.com/browse.php/be203aea/0b376beO/i8vd3d3L/mlubmVyb/G9vcC5ia/XovY29kZ/S9FbXB0e/SBPcGVuR/0wgRVMgQ/XBwbGljY/XRpb24ue/mlw/b0/">OpenGL  ES Xcode Tem</a><a href="../wp-content/uploads/2009/12/Empty%20OpenGL%20ES%20Application.zip">Empty%20OpenGL%20ES%20Application</a> 项目使其包括了新的矩阵函数，包括向量化矩阵乘法函数。如果你无法完全消化，你也无需担心。对于开发OpenGL  ES程序的99%的人来说，你不需要完全理解透视空间，同质坐标或线性变换，只需大体上的了解即可。</p>
<p><br class="spacer_" /></p>
<p><em>感谢 <a href="http://www.snappytouch.com/">Snappy Touch</a> 的 <a>Noel  Llopis</a>。</em></p>
<hr />
<p><strong>注脚</strong></p>
<ol>
<li>实际上，当你调用glLoadIdentity()时, 你加载了一个 4&#215;4 的单元矩阵。 </li>
<li>如果你希望使用 struct 而不是  typedef 时，你必须记住要以引用方式传递参数。struct并不会像数组一样自动以引用方式被传递。 </li>
<li>使用Shark测试，drawView: 方法从.7% 的处理时间降低为 .1%  的处理时间。对于此方法而言，速度有本质的提高，但对程序总体而言，影响不大。</li>
</ol>
<p><br class="spacer_" /></p>
<h6>原文见：<a href="http://www.topsyproxy.com/browse.php/be203aea/0b376beO/i8vaXBob/25lZGV2Z/WxvcG1lb/nQuYmxvZ/3Nwb3QuY/29tLzIwM/DkvMDYvb/3BlbmdsL/WVzLWZyb/20tZ3Jvd/W5kLXVwL/XBhcnQtN/18wNC5od/G1s/b0/">OpenGL  ES From the Ground Up, Part 7: Transformations and Matrices</a></h6>
]]></content:encoded>
			<wfw:commentRss>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0opengl-es%e4%b9%8b%e4%b8%83-%e5%8f%98%e6%8d%a2%e5%92%8c%e7%9f%a9%e9%98%b5/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>OpenGL ES纹理尺寸限制的处理方法</title>
		<link>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/opengl-es%e7%ba%b9%e7%90%86%e5%b0%ba%e5%af%b8%e9%99%90%e5%88%b6%e7%9a%84%e5%a4%84%e7%90%86%e6%96%b9%e6%b3%95</link>
		<comments>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/opengl-es%e7%ba%b9%e7%90%86%e5%b0%ba%e5%af%b8%e9%99%90%e5%88%b6%e7%9a%84%e5%a4%84%e7%90%86%e6%96%b9%e6%b3%95#comments</comments>
		<pubDate>Tue, 09 Mar 2010 03:13:34 +0000</pubDate>
		<dc:creator>bagusflyer</dc:creator>
				<category><![CDATA[图形图像]]></category>
		<category><![CDATA[编程]]></category>
		<category><![CDATA[OpenGL ES]]></category>
		<category><![CDATA[代码片段]]></category>

		<guid isPermaLink="false">http://www.iphone-geek.cn/?p=916</guid>
		<description><![CDATA[大家都知道，OpenGL ES对纹理的尺寸有限制，就是长和宽都必须是2的整数次幂。（实际上OpenGL都有此限制，但有一些扩展可以解决此问题）。因此处理方案有两种：


将纹理尺寸限制为2的整数次幂。比如，我有一个480&#215;320的背景图案，我可以用Photoshop将画布设置为512&#215;512，在纹理映射时只使用480&#215;320部分。当然我也可以将多个图案合成在一个纹理中，在纹理映射时根据图案的位置进行映射。 
仍然使用正常的图像尺寸，但在使用时进行转换。下面是源代码：


1234567891011121314151617181920212223242526&#160; &#160; &#160; &#160; // 首先调整纹理的长和宽为2的整数次幂 &#160; &#160; &#160; &#160; 
&#160; &#160; &#160; &#160; if&#40; &#40;_width != 1&#41; &#38;&#38; &#40;_width &#38; &#40;_width - 1&#41;&#41; &#41;
&#160; &#160; &#160; &#160; &#123;
&#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; i = 1;
&#160; &#160; &#160; &#160; &#160; &#160; &#160; &#160; while&#40;&#40;sizeToFit ? 2 * i : i&#41; [...]]]></description>
			<content:encoded><![CDATA[<p>大家都知道，OpenGL ES对纹理的尺寸有限制，就是长和宽都必须是2的整数次幂。（实际上OpenGL都有此限制，但有一些扩展可以解决此问题）。因此处理方案有两种：</p>
<p><br class="spacer_" /></p>
<ol>
<li>将纹理尺寸限制为2的整数次幂。比如，我有一个480&#215;320的背景图案，我可以用Photoshop将画布设置为512&#215;512，在纹理映射时只使用480&#215;320部分。当然我也可以将多个图案合成在一个纹理中，在纹理映射时根据图案的位置进行映射。 </li>
<li>仍然使用正常的图像尺寸，但在使用时进行转换。下面是源代码：</li>
</ol>
<p><br class="spacer_" /></p>
<p><div class="codecolorer-container cpp mac-classic" style="overflow:auto;white-space:nowrap;border: 1px solid #9F9F9F;width:435px;"><table cellspacing="0" cellpadding="0"><tbody><tr><td style="padding:5px;text-align:center;color:#888888;background-color:#EEEEEE;border-right: 1px solid #9F9F9F;font: normal 12px/1.4em Monaco, Lucida Console, monospace;"><div>1<br />2<br />3<br />4<br />5<br />6<br />7<br />8<br />9<br />10<br />11<br />12<br />13<br />14<br />15<br />16<br />17<br />18<br />19<br />20<br />21<br />22<br />23<br />24<br />25<br />26<br /></div></td><td><div class="cpp codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #666666;">// 首先调整纹理的长和宽为2的整数次幂 &nbsp; &nbsp; &nbsp; &nbsp; </span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #0000ff;">if</span><span style="color: #008000;">&#40;</span> <span style="color: #008000;">&#40;</span>_width <span style="color: #000040;">!</span><span style="color: #000080;">=</span> 1<span style="color: #008000;">&#41;</span> <span style="color: #000040;">&amp;&amp;</span> <span style="color: #008000;">&#40;</span>_width <span style="color: #000040;">&amp;</span> <span style="color: #008000;">&#40;</span>_width <span style="color: #000040;">-</span> 1<span style="color: #008000;">&#41;</span><span style="color: #008000;">&#41;</span> <span style="color: #008000;">&#41;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #008000;">&#123;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; i <span style="color: #000080;">=</span> <span style="color: #0000dd;">1</span><span style="color: #008080;">;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #0000ff;">while</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#40;</span>sizeToFit <span style="color: #008080;">?</span> 2 <span style="color: #000040;">*</span> i <span style="color: #008080;">:</span> i<span style="color: #008000;">&#41;</span> <span style="color: #000080;">&lt;</span> _width<span style="color: #008000;">&#41;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; i <span style="color: #000040;">*</span><span style="color: #000080;">=</span> <span style="color: #0000dd;">2</span><span style="color: #008080;">;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _width <span style="color: #000080;">=</span> i<span style="color: #008080;">;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #008000;">&#125;</span><br />
<br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #0000ff;">if</span><span style="color: #008000;">&#40;</span> <span style="color: #008000;">&#40;</span>_height <span style="color: #000040;">!</span><span style="color: #000080;">=</span> 1<span style="color: #008000;">&#41;</span> <span style="color: #000040;">&amp;&amp;</span> <span style="color: #008000;">&#40;</span>_height <span style="color: #000040;">&amp;</span> <span style="color: #008000;">&#40;</span>_height <span style="color: #000040;">-</span> 1<span style="color: #008000;">&#41;</span><span style="color: #008000;">&#41;</span> <span style="color: #008000;">&#41;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #008000;">&#123;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; i <span style="color: #000080;">=</span> <span style="color: #0000dd;">1</span><span style="color: #008080;">;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #0000ff;">while</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#40;</span>sizeToFit <span style="color: #008080;">?</span> 2 <span style="color: #000040;">*</span> i <span style="color: #008080;">:</span> i<span style="color: #008000;">&#41;</span> <span style="color: #000080;">&lt;</span> _height<span style="color: #008000;">&#41;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; i <span style="color: #000040;">*</span><span style="color: #000080;">=</span> <span style="color: #0000dd;">2</span><span style="color: #008080;">;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _height <span style="color: #000080;">=</span> i<span style="color: #008080;">;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #008000;">&#125;</span><br />
<br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #666666;">// 如果调整后的图像尺寸大于最大纹理尺寸（1024），那么需要缩小</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #0000ff;">while</span><span style="color: #008000;">&#40;</span><span style="color: #008000;">&#40;</span>_width <span style="color: #000040;">&amp;</span>gt<span style="color: #008080;">;</span> kMaxTextureSize<span style="color: #008000;">&#41;</span> <span style="color: #000040;">||</span> <span style="color: #008000;">&#40;</span>_height <span style="color: #000040;">&amp;</span>gt<span style="color: #008080;">;</span> kMaxTextureSize<span style="color: #008000;">&#41;</span><span style="color: #008000;">&#41;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #008000;">&#123;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _width <span style="color: #000040;">/</span><span style="color: #000080;">=</span> <span style="color: #0000dd;">2</span><span style="color: #008080;">;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _height <span style="color: #000040;">/</span><span style="color: #000080;">=</span> <span style="color: #0000dd;">2</span><span style="color: #008080;">;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; transform <span style="color: #000080;">=</span> CGAffineTransformScale<span style="color: #008000;">&#40;</span>transform, 0.5, 0.5<span style="color: #008000;">&#41;</span><span style="color: #008080;">;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; imageSize.<span style="color: #007788;">x</span> <span style="color: #000040;">*</span><span style="color: #000080;">=</span> <span style="color:#800080;">0.5</span><span style="color: #008080;">;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; imageSize.<span style="color: #007788;">y</span> <span style="color: #000040;">*</span><span style="color: #000080;">=</span> <span style="color:#800080;">0.5</span><span style="color: #008080;">;</span><br />
&nbsp; &nbsp; &nbsp; &nbsp; <span style="color: #008000;">&#125;</span></div></td></tr></tbody></table></div>
]]></content:encoded>
			<wfw:commentRss>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/opengl-es%e7%ba%b9%e7%90%86%e5%b0%ba%e5%af%b8%e9%99%90%e5%88%b6%e7%9a%84%e5%a4%84%e7%90%86%e6%96%b9%e6%b3%95/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>OpenGL ES 3D物体加载示例</title>
		<link>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/opengl-es-3d%e7%89%a9%e4%bd%93%e5%8a%a0%e8%bd%bd%e7%a4%ba%e4%be%8b</link>
		<comments>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/opengl-es-3d%e7%89%a9%e4%bd%93%e5%8a%a0%e8%bd%bd%e7%a4%ba%e4%be%8b#comments</comments>
		<pubDate>Wed, 03 Mar 2010 03:50:59 +0000</pubDate>
		<dc:creator>bagusflyer</dc:creator>
				<category><![CDATA[图形图像]]></category>
		<category><![CDATA[编程]]></category>
		<category><![CDATA[OpenGL ES]]></category>
		<category><![CDATA[开源项目]]></category>

		<guid isPermaLink="false">http://www.iphone-geek.cn/?p=912</guid>
		<description><![CDATA[前几天有位朋友向我询问有关3D物体加载的示例，记得以前看到过一个示例，就找来出来与大家分享：

iPhone Wavefront Obj Loader

]]></description>
			<content:encoded><![CDATA[<p>前几天有位朋友向我询问有关3D物体加载的示例，记得以前看到过一个示例，就找来出来与大家分享：</p>
<p><br class="spacer_" /></p>
<p><a title="iPhone Wavefront Loader" href="http://code.google.com/p/iphonewavefrontloader/">iPhone Wavefront Obj Loader</a></p>
<p><br class="spacer_" /></p>
]]></content:encoded>
			<wfw:commentRss>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/opengl-es-3d%e7%89%a9%e4%bd%93%e5%8a%a0%e8%bd%bd%e7%a4%ba%e4%be%8b/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>从零开始学习OpenGL ES之五补遗 &#8211; setupView重写</title>
		<link>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0opengl-es%e4%b9%8b%e4%ba%94%e8%a1%a5%e9%81%97-setupview%e9%87%8d%e5%86%99</link>
		<comments>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0opengl-es%e4%b9%8b%e4%ba%94%e8%a1%a5%e9%81%97-setupview%e9%87%8d%e5%86%99#comments</comments>
		<pubDate>Thu, 14 Jan 2010 02:31:50 +0000</pubDate>
		<dc:creator>bagusflyer</dc:creator>
				<category><![CDATA[图形图像]]></category>
		<category><![CDATA[编程]]></category>
		<category><![CDATA[OpenGL ES]]></category>
		<category><![CDATA[教程]]></category>

		<guid isPermaLink="false">http://www.iphone-geek.cn/?p=625</guid>
		<description><![CDATA[我在从零开始学习OpenGL ES之四 &#8211; 光效 一文中使用了一个普通GLfloat数组。由于它没有使用任何非OpenGL定义的数据结构，所以是最为普通和方便的方式。
&#160;
但在此我使用在第一部分中定义的Vertex3D, Vector3D和 Color3D数据结构重写了 setupView:方法。并不是这种方法“更好”，但是它是一种不同的方式。当我第一次学习OpenGL时，我发现使用顶点，颜色和三角形的术语比可变长度浮点数组更容易理解。如果你和我一样，那么你会发现这个版本更容易理解。
&#160;
除了使用自定义数据结构外，我还减少了环境光元素的数量并将光源向右移动了一点。然后使用Vector3DMakeWithStartAndEndPoints()将移动的光源指向二十面体。这样做使得光效更为生动一点。
-(void)setupView:(GLView*)view
{
    const GLfloat zNear = 0.01, zFar = 1000.0, fieldOfView = 45.0;
    GLfloat size;
    glEnable(GL_DEPTH_TEST);
    glMatrixMode(GL_PROJECTION);
    size = zNear * tanf(DEGREES_TO_RADIANS(fieldOfView) / 2.0);
    CGRect rect = view.bounds;
    [...]]]></description>
			<content:encoded><![CDATA[<p>我在<a title="从零开始学习OpenGL ES之四 - 光效" href="编程/从零开始学习opengl-es之四-光效" target="_blank">从零开始学习OpenGL ES之四 &#8211; 光效</a> 一文中使用了一个<span style="font-family: monospace;">普通GLfloat数组。由于它没有使用任何非OpenGL定义的数据结构，所以是最为普通和方便的方式。</span></p>
<p>&nbsp;</p>
<p>但在此我使用在<a href="编程/从零开始学习opengl-es之一-基本概念">第一部分</a>中<span style="font-family: monospace;">定义的Vertex3D</span>, <span style="font-family: monospace;">Vector3D</span>和 <span style="font-family: monospace;">Color3D数据结构</span>重写了 <span style="font-family: monospace;">setupView:</span>方法。并不是这种方法“更好”，但是它是一种不同的方式。当我第一次学习OpenGL时，我发现使用顶点，颜色和三角形的术语比可变长度浮点数组更容易理解。如果你和我一样，那么你会发现这个版本更容易理解。</p>
<p>&nbsp;</p>
<p>除了使用自定义数据结构外，我还减少了环境光元素的数量并将光源向右移动了一点。然后使用<span style="font-family: monospace;">Vector3DMakeWithStartAndEndPoints()将移动的光源指向二十面体。这样做使得光效更为生动一点。</span></p>
<pre><span>-(<span>void</span>)setupView:(GLView*)view
<span>{
    <span>const</span> GLfloat zNear = <span>0.01</span>, zFar = <span>1000.0</span>, fieldOfView = <span>45.0</span>;
    GLfloat size;
<span><span>    </span><span>glEnable</span>(</span>GL_DEPTH_TEST);
<span><span>    </span><span>glMatrixMode</span>(</span>GL_PROJECTION);
    size = zNear *<span> </span><span>tanf</span>(<span><span>DEGREES_TO_RADIANS</span>(</span>fieldOfView) / <span>2.0</span>);
    <span>CGRect</span> rect = view<span>.bounds</span>;
<span><span>    </span><span>glFrustumf</span>(</span>-size, size, -size / (rect<span>.size.width</span> / rect<span>.size.height</span>), size /
               (rect<span>.size.width</span> / rect<span>.size.height</span>), zNear, zFar);
<span><span>    </span><span>glViewport</span>(</span><span>0</span>, <span>0</span>, rect<span>.size.width</span>, rect<span>.size.height</span>);
<span><span>    </span><span>glMatrixMode</span>(</span>GL_MODELVIEW);

    <span><span>//</span> Enable lighting
</span><span><span>    </span><span>glEnable</span>(</span>GL_LIGHTING);

    <span><span>//</span> Turn the first light on
</span><span><span>    </span><span>glEnable</span>(</span>GL_LIGHT0);

    <span><span>//</span> Define the ambient component of the first light
</span>    <span>static</span> <span>const</span> Color3D light0Ambient<span><span>[</span><span>]</span></span> = <span>{<span>{<span>0.05</span>, <span>0.05</span>, <span>0.05</span>, <span>1.0</span>}</span>}</span>;
<span><span>    </span><span>glLightfv</span>(</span>GL_LIGHT0, GL_AMBIENT, (<span>const</span> GLfloat *)light0Ambient);

    <span><span>//</span> Define the diffuse component of the first light
</span>    <span>static</span> <span>const</span> Color3D light0Diffuse<span><span>[</span><span>]</span></span> = <span>{<span>{<span>0.4</span>, <span>0.4</span>, <span>0.4</span>, <span>1.0</span>}</span>}</span>;
<span><span>    </span><span>glLightfv</span>(</span>GL_LIGHT0, GL_DIFFUSE, (<span>const</span> GLfloat *)light0Diffuse);

    <span><span>//</span> Define the specular component and shininess of the first light
</span>    <span>static</span> <span>const</span> Color3D light0Specular<span><span>[</span><span>]</span></span> = <span>{<span>{<span>0.7</span>, <span>0.7</span>, <span>0.7</span>, <span>1.0</span>}</span>}</span>;
<span><span>    </span><span>glLightfv</span>(</span>GL_LIGHT0, GL_SPECULAR, (<span>const</span> GLfloat *)light0Specular);
<span><span>    </span><span>glLightf</span>(</span>GL_LIGHT0, GL_SHININESS, <span>0.4</span>);

    <span><span>//</span> Define the position of the first light
</span>   <span><span>//</span> const GLfloat light0Position[] = {10.0, 10.0, 10.0};
</span>    <span>static</span> <span>const</span> Vertex3D light0Position<span><span>[</span><span>]</span></span> = <span>{<span>{<span>10.0</span>, <span>10.0</span>, <span>10.0</span>}</span>}</span>;
<span><span>    </span><span>glLightfv</span>(</span>GL_LIGHT0, GL_POSITION, (<span>const</span> GLfloat *)light0Position); 

    <span><span>//</span> Calculate light vector so it points at the object
</span>    <span>static</span> <span>const</span> Vertex3D objectPoint<span><span>[</span><span>]</span></span> = <span>{<span>{<span>0.0</span>, <span>0.0</span>, -<span>3.0</span>}</span>}</span>;
    <span>const</span> Vertex3D lightVector =<span><span> </span><span>Vector3DMakeWithStartAndEndPoints</span>(</span>light0Position<span><span>[</span><span>0</span><span>]</span></span>, objectPoint<span><span>[</span><span>0</span><span>]</span></span>);
<span><span>    </span><span>glLightfv</span>(</span>GL_LIGHT0, GL_SPOT_DIRECTION, (GLfloat *)&amp;lightVector);

    <span><span>//</span> Define a cutoff angle. This defines a 50° field of vision, since the cutoff
</span>    <span><span>//</span> is number of degrees to each side of an imaginary line drawn from the light's
</span>    <span><span>//</span> position along the vector supplied in GL_SPOT_DIRECTION above
</span><span><span>    </span><span>glLightf</span>(</span>GL_LIGHT0, GL_SPOT_CUTOFF, <span>25.0</span>);

<span><span>    </span><span>glLoadIdentity</span>(</span>);
<span><span>    </span><span>glClearColor</span>(</span><span>0.0f</span>, <span>0.0f</span>, <span>0.0f</span>, <span>1.0f</span>);
}</span></span></pre>
<p>&nbsp;</p>
<p>你可以随意调整光的属性，增加额外的光源或二十面体，体验一下这些调整会为场景带来什么样的变化。这些东西是很难体验出来的，所以不要指望一晚上就理解了所有东西。</p>
<p>&nbsp;</p>
<p>原文见：<a href="http://iphonedevelopment.blogspot.com/2009/05/face-from-part-iv-rewritten.html"><span style="font-family: monospace;">setupView:</span> from Part IV Rewritten</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0opengl-es%e4%b9%8b%e4%ba%94%e8%a1%a5%e9%81%97-setupview%e9%87%8d%e5%86%99/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>从零开始学习OpenGL ES之六 &#8211; 纹理及纹理映射</title>
		<link>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0opengl-es%e4%b9%8b%e5%85%ad-%e7%ba%b9%e7%90%86%e5%8f%8a%e7%ba%b9%e7%90%86%e6%98%a0%e5%b0%84</link>
		<comments>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0opengl-es%e4%b9%8b%e5%85%ad-%e7%ba%b9%e7%90%86%e5%8f%8a%e7%ba%b9%e7%90%86%e6%98%a0%e5%b0%84#comments</comments>
		<pubDate>Mon, 11 Jan 2010 03:25:35 +0000</pubDate>
		<dc:creator>bagusflyer</dc:creator>
				<category><![CDATA[图形图像]]></category>
		<category><![CDATA[编程]]></category>
		<category><![CDATA[OpenGL ES]]></category>
		<category><![CDATA[教程]]></category>

		<guid isPermaLink="false">http://www.iphone-geek.cn/?p=657</guid>
		<description><![CDATA[在OpenGL ES中另一种为多边形定义颜色创建材质的方法是将纹理映射到多边形。这是一种很实用的方法，它可以产生很漂亮的外观并节省大量的处理器时间。比如说，你想在游戏中造一个砖墙。你当然可以创建一个具有几千个顶点的复杂物体来定义每块砖以及砖之间的泥灰。
&#160;
或者你可以创建一个由两个三角形构成的方块（四个顶点），然后将砖的照片映射上去。简单的几何体通过纹理映射的方法比使用材质的复杂几何体的渲染快得多。
&#160;

功能启动
&#160;
为使用纹理，我们需要打开OpenGL的一些开关以启动我们需要的一些功能：
    glEnable(GL_TEXTURE_2D);
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_SRC_COLOR);
&#160;
第一个函数打开所有两维图像的功能。这个调用是必不可缺的；如果你没有打开此功能，那么你就无法将图像映射到多边形上。它可以在需要时打开和关闭，但是通常不需要这样做。你可以启动此功能而在绘图时并不使用它，所以通常只需在setup方法中调用一次。
&#160;
下一个调用打开了混色（blending）功能。 混色提供了通过指定源和目标怎样组合而合成图像的功能。例如，它可以允许你将多个纹理映射到多边形中以产生一个有趣的新的纹理。然而在OpenGL中，“混色”是指合成任何图像或图像与多边形表面合成，所以即使你不需要将多个图像混合，你也需要打开此功能。
&#160;
最后一个调用指定了使用的混色方法。混色函数定义了源图像怎样与目标图像或表面合成。OpenGL将计算出（根据我们提供的信息）怎样将源纹理的一个像素映射到绘制此像素的目标多边形的一部分。
&#160;
一旦 OpenGL ES 决定怎样把一个像素从纹理映射到多边形，它将使用指定的混色函数来确定最终绘制的各像素的最终值。 glBlendFunc()函数决定我们将怎样进行混色运算，它采用了两个参数。第一个参数定义了怎样使用源纹理。第二个则定义了怎样使用目标颜色或纹理。在本文简单的例子中，我们希望绘制的纹理完全不透明而忽略多边形中现存的颜色或纹理，所以我们设置源为 GL_ONE，它表示源图像（被映射的纹理）中各颜色通道的值将乘以1.0或者换句话说，以完全颜色密度使用。目标设置为 GL_SRC_COLOR，它表示要使用源图像中被映射到多边形特定点的颜色。此混色函数的结果是一个完全不透明的纹理。这可能是最常用情况。我可能会在以后的文章中更详细地介绍一下混色功能，但这可能是你使用最多的一种组合，而且是今天使用的唯一混色功能。
&#160;
注意：如果你已经使用过OpenGL的混色功能，你应该知道 OpenGL ES 并不支持所有 OpenGL 支持的混色功能。下面是 OpenGL ES 支持的：GL_ZERO, GL_ONE, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, 和 GL_SRC_ALPHA_SATURATE (它仅用于源)。
&#160;
创建纹理
&#160;
一旦你启动了纹理和混色，就可以开始创建纹理了。通常纹理是在开始显示3D物体给用户前程序开始执行时或游戏每关开始加载时创建的。这不是必须的，但却是一个好的建议，因为创建纹理需要占用一些处理器时间，如果在你开始显示一些复杂的几何体时进行此项工作，会引起明显的程序停顿。
&#160;
OpenGL中的每一个图像都是一个纹理，纹理是不能直接显示给最终用户的，除非它映射到物体上。但是有一个小小的例外，就是对允许你将图像绘制于指定点的所谓点精灵（point sprites），但它有自己的一套规则，所以那是一个单独的主题。通常的情况下，任何你希望显示给用户的图像必须放置在由顶点定义的三角形中，有点像贴在上面的粘帖纸。
&#160;
生成纹理名
&#160;
为创建一个纹理，首先必须通知OpenGL ES生成一个纹理名称。这是一个令人迷惑的术语，因为纹理名实际上是一个数字：更具体的说是一个GLuint。尽管“名称”可以指任何字符串，但对于OpenGL ES纹理并不是这样。它是一个代表指定纹理的整数值。每个纹理由一个独一无二的名称表示，所以传递纹理名给OpenGL是我们区别所使用纹理的方式。.
&#160;
然而在生成纹理名之前，我们要定义一个保存单个或多个纹理名的GLuint数组：
    GLuint      texture[1];
&#160;
尽管只有一个纹理，但使用一个元素的数组而不是一个GLuint仍是一个好习惯。当然，仍然可以定义单个GLuint进行强制调用。
&#160;
在过程式程序中，纹理通常存于一个全局数组中，但在Objective-C程序中，使用例程变量保存纹理名更为常见。下面是代码：
 [...]]]></description>
			<content:encoded><![CDATA[<p>在OpenGL ES中另一种为多边形定义颜色创建材质的方法是将纹理映射到多边形。这是一种很实用的方法，它可以产生很漂亮的外观并节省大量的处理器时间。比如说，你想在游戏中造一个砖墙。你当然可以创建一个具有几千个顶点的复杂物体来定义每块砖以及砖之间的泥灰。</p>
<p>&nbsp;</p>
<p>或者你可以创建一个由两个三角形构成的方块（四个顶点），然后将砖的照片映射上去。简单的几何体通过纹理映射的方法比使用材质的复杂几何体的渲染快得多。</p>
<p>&nbsp;</p>
<p><span id="more-657"></span></p>
<h2>功能启动</h2>
<p>&nbsp;</p>
<p>为使用纹理，我们需要打开OpenGL的一些开关以启动我们需要的一些功能：</p>
<pre><span><span><span>    </span><span>glEnable</span><span>(GL_TEXTURE_2D)</span>;</span>
<span><span>    </span><span>glEnable</span><span>(GL_BLEND)</span>;</span>
<span><span>    </span><span>glBlendFunc</span><span>(GL_ONE, GL_SRC_COLOR)</span>;</span></span></pre>
<p>&nbsp;</p>
<p>第一个函数打开所有两维图像的功能。这个调用是必不可缺的；如果你没有打开此功能，那么你就无法将图像映射到多边形上。它可以在需要时打开和关闭，但是通常不需要这样做。你可以启动此功能而在绘图时并不使用它，所以通常只需在setup方法中调用一次。</p>
<p>&nbsp;</p>
<p>下一个调用打开了<strong>混色（blending）</strong>功能。 混色提供了通过指定源和目标怎样组合而合成图像的功能。例如，它可以允许你将多个纹理映射到多边形中以产生一个有趣的新的纹理。然而在OpenGL中，“混色”是指合成任何图像或图像与多边形表面合成，所以即使你不需要将多个图像混合，你也需要打开此功能。</p>
<p>&nbsp;</p>
<p>最后一个调用指定了使用的混色方法。混色函数定义了源图像怎样与目标图像或表面合成。OpenGL将计算出（根据我们提供的信息）怎样将源纹理的一个像素映射到绘制此像素的目标多边形的一部分。</p>
<p>&nbsp;</p>
<p>一旦 OpenGL ES 决定怎样把一个像素从纹理映射到多边形，它将使用指定的混色函数来确定最终绘制的各像素的最终值。 <span style="font-family: monospace;">glBlendFunc()函数决定我们将怎样进行混色运算，它采用了两个参数。第一个参数定义了怎样使用源纹理。第二个则定义了怎样使用目标颜色或纹理。在本文简单的例子中，我们希望绘制的纹理完全不透明而忽略多边形中现存的颜色或纹理，所以我们设置源为 </span><span style="font-family: monospace;">GL_ONE，它表示源图像（被映射的纹理）中各颜色通道的值将乘以1.0或者换句话说，以完全颜色密度使用。目标设置为</span> <span style="font-family: monospace;">GL_SRC_COLOR，它表示要使用源图像中被映射到多边形特定点的颜色。</span>此混色函数的结果是一个完全不透明的纹理。这可能是最常用情况。我可能会在以后的文章中更详细地介绍一下混色功能，但这可能是你使用最多的一种组合，而且是今天使用的唯一混色功能。</p>
<p>&nbsp;</p>
<blockquote><p><strong>注意：</strong>如果你已经使用过OpenGL的混色功能，你应该知道 OpenGL ES 并不支持所有 OpenGL 支持的混色功能。下面是 OpenGL ES 支持的：<span style="font-family: monospace;">GL_ZERO</span>, <span style="font-family: monospace;">GL_ONE</span>, <span style="font-family: monospace;">GL_SRC_COLOR</span>, <span style="font-family: monospace;">GL_ONE_MINUS_SRC_COLOR</span>, <span style="font-family: monospace;">GL_DST_COLOR</span>, <span style="font-family: monospace;">GL_ONE_MINUS_DST_COLOR</span>, <span style="font-family: monospace;">GL_SRC_ALPHA</span>, <span style="font-family: monospace;">GL_ONE_MINUS_SRC_ALPHA</span>, <span style="font-family: monospace;">GL_DST_ALPHA</span>, <span style="font-family: monospace;">GL_ONE_MINUS_DST_ALPHA</span>, 和 <span style="font-family: monospace;">GL_SRC_ALPHA_SATURATE</span> (它仅用于源)。</p></blockquote>
<p>&nbsp;</p>
<h2>创建纹理</h2>
<p>&nbsp;</p>
<p>一旦你启动了纹理和混色，就可以开始创建纹理了。通常纹理是在开始显示3D物体给用户前程序开始执行时或游戏每关开始加载时创建的。这不是必须的，但却是一个好的建议，因为创建纹理需要占用一些处理器时间，如果在你开始显示一些复杂的几何体时进行此项工作，会引起明显的程序停顿。</p>
<p>&nbsp;</p>
<p>OpenGL中的每一个图像都是一个<strong>纹理</strong>，纹理是不能直接显示给最终用户的，除非它<strong>映射</strong>到物体上。但是有一个小小的例外，就是对允许你将图像绘制于指定点的所谓<strong>点精灵（point sprites）</strong>，但它有自己的一套规则，所以那是一个单独的主题。通常的情况下，任何你希望显示给用户的图像必须放置在由顶点定义的三角形中，有点像贴在上面的粘帖纸。</p>
<p>&nbsp;</p>
<h3>生成纹理名</h3>
<p>&nbsp;</p>
<p>为创建一个纹理，首先必须通知OpenGL ES生成一个<strong>纹理名称</strong>。这是一个令人迷惑的术语，因为纹理名实际上是一个数字：更具体的说是一个<span style="font-family: monospace;">GLuint。尽管“名称”可以指任何字符串，但对于</span>OpenGL ES纹理并不是这样。它是一个代表指定纹理的整数值。每个纹理由一个独一无二的名称表示，所以传递纹理名给OpenGL是我们区别所使用纹理的方式。.</p>
<p>&nbsp;</p>
<p>然而在生成纹理名之前，我们要定义一个保存单个或多个纹理名的<span style="font-family: monospace;">GLuint</span>数组：</p>
<pre><span>    GLuint      texture<span><span>[</span><span>1</span><span>]</span></span>;</span></pre>
<p>&nbsp;</p>
<p>尽管只有一个纹理，但使用一个元素的数组而不是一个<span style="font-family: monospace;">GLuint仍是一个好习惯。当然，仍然可以定义单个</span><span style="font-family: monospace;">GLuint进行强制调用。</span></p>
<p>&nbsp;</p>
<p>在过程式程序中，纹理通常存于一个全局数组中，但在Objective-C程序中，使用例程变量保存纹理名更为常见。下面是代码：</p>
<pre><span><span><span>    </span><span>glGenTextures</span><span>(<span>1</span>, &amp;texture<span><span>[</span><span>0</span><span>]</span></span>)</span>;</span></span></pre>
<p>&nbsp;</p>
<p>你可以调用<span style="font-family: monospace;">glGenTextures()生成多个纹理；传递给</span>OpenGL ES的<span style="font-family: monospace;">第一个参数指示了要生成几个纹理。第二个参数需要是一个具有足够空间保存纹理名的数组。我们只有一个元素，所以只要求OpenGL ES产生一个纹理名。此调用后，</span> <span style="font-family: monospace;">texture[0]</span> 将保持纹理的名称，我们将在任何与纹理有关的地方都使用<span style="font-family: monospace;">texture[0]来表示这个特定纹理。</span></p>
<p>&nbsp;</p>
<h3>纹理绑定</h3>
<p>&nbsp;</p>
<p>在为纹理生成名称后，在为纹理提供图像数据之前，我们必须<strong>绑定</strong>纹理。绑定使得指定纹理处于活动状态。一次只能激活一个纹理。活动的或“被绑定”的纹理是绘制多边形时使用的纹理，也是新纹理数据将加载其上纹理，所以在提供图像数据前必须绑定纹理。这意味着每个纹理至少被绑定一次以为OpenGL ES提供此纹理的数据。运行时，可能再次绑定纹理（但不会再次提供图像数据）以指示绘图时要使用此纹理。纹理绑定很简单：</p>
<pre><span><span><span>    </span><span>glBindTexture</span><span>(GL_TEXTURE_2D, texture<span><span>[</span><span>0</span><span>]</span></span>)</span>;</span></span></pre>
<p>&nbsp;</p>
<p>因为我们使二维图像创建纹理，所以第一个参数永远是 <span style="font-family: monospace;">GL_TEXTURE_2D。常规</span>OpenGL支持其他类型的纹理，但目前分布在iPhone上的OpenGL ES版本只支持二维纹理，坦白地说，甚至在常规OpenGL中，二维纹理的使用也远比其他类型要多得多。</p>
<p>&nbsp;</p>
<p>第二个参数是我们需要绑定的纹理名。调用此函数后，先前生成了纹理名称的纹理将成为活动纹理。</p>
<p>&nbsp;</p>
<h3>图像配置</h3>
<p>&nbsp;</p>
<p>在第一次绑定纹理后，我们需要设置两个参数。需要的话，有一些参数<strong>可以</strong>设置，但在iPhone上，这两个参数<strong>必须</strong>设定，否则纹理将不会正常显示。</p>
<pre><span><span><span>    </span><span>glTexParameteri</span><span>(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR)</span>;</span>
<span><span>    </span><span>glTexParameteri</span><span>(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR)</span>;</span></span></pre>
<p>&nbsp;</p>
<p>必须设置这两个参数的原因是默认状态下OpenGL设置了使用所谓mipmap。今天我将不讨论mipmap，简单地说，我们不准备使用它。Mipmap是一个图像不同尺寸的组合，它允许OpenGL选择最为接近的尺寸版本以避免过多的插值计算并且在物体远离观察者时通过使用更小的纹理来更好地管理内存。感谢矢量单元和图形芯片，iPhone在图像插值方面做得很好，所以我们不需要考虑mipmap。我以后可能会专门撰写一篇文章讨论它，但我们今天讨论的是怎样让OpenGL ES通过线性插值调整图像到所需的尺寸。因为 <span style="font-family: monospace;">GL_TEXTURE_MIN_FILTER 用于纹理需要被收缩到适合多边形的尺寸的情形，而</span><span style="font-family: monospace;"> GL_TEXTURE_MAG_FILTER 则用于</span>纹理被放大到适合多边形的尺寸的情况下，所以必须进行两次调用。在两种情况下，我们传递 <span style="font-family: monospace;">GL_LINEAR</span> 以通知OpenGL以简单的线性插值方法调整图像。</p>
<p>&nbsp;</p>
<h2>加载图像数据</h2>
<p>&nbsp;</p>
<p>在我们第一次绑定纹理后，必须为OpenGL ES提供纹理的图像数据。在iPhone上，有两种基本方法加载图像数据。如果你在其他书籍上看到使用标准 C I/O方法加载数据的代码，那也是不错的选择，然而这两种方法应该覆盖了你将遇到的各种情形。</p>
<h3><span style="font-family: monospace;">UIImage方法</span></h3>
<p>如果你想使用JPEG, PNG或其他<span style="font-family: monospace;">UIImage支持的格式，那么你可以简单地</span>使用图像数据实例化一个<span style="font-family: monospace;">UIImage，然后产生图像的</span>RGBA 位图数据：</p>
<pre><span>    <span>NSString</span> *path = <span><span>[</span><span><span>[</span><span>NSBundle</span> <span><span>mainBundle</span></span><span>]</span></span> <span><span>pathForResource<span>:</span></span><span><span>@"</span>texture<span>"</span></span> <span>ofType<span>:</span></span><span><span>@"</span>png<span>"</span></span></span><span>]</span></span>;
    <span>NSData</span> *texData = <span><span>[</span><span><span>[</span><span>NSData</span> <span><span>alloc</span></span><span>]</span></span> <span><span>initWithContentsOfFile<span>:</span></span>path</span><span>]</span></span>;
    UIImage *image = <span><span>[</span><span><span>[</span>UIImage <span><span>alloc</span></span><span>]</span></span> <span><span>initWithData<span>:</span></span>texData</span><span>]</span></span>;
    <span>if</span> (image == <span>nil</span>)
<span>        </span><span>NSLog</span>(<span><span>@"</span>Do real error checking here<span>"</span></span>);

    GLuint width = CGImageGetWidth(image.CGImage);
    GLuint height = CGImageGetHeight(image.CGImage);
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    <span>void</span> *imageData = malloc( height * width * <span>4</span> );
    CGContextRef context = CGBitmapContextCreate( imageData, width, height, <span>8</span>, <span>4</span> * width,
        colorSpace, <span>kCGImageAlphaPremultipliedLast</span> | <span>kCGBitmapByteOrder32Big</span> );
<span><span>    </span><span>CGColorSpaceRelease</span><span>( colorSpace )</span>;</span>
<span><span>    </span><span>CGContextClearRect</span><span>( context, CGRectMake( <span>0</span>, <span>0</span>, width, height )</span> );</span>
<span><span>    </span><span>CGContextTranslateCTM</span><span>( context, <span>0</span>, height - height )</span>;</span>
<span><span>    </span><span>CGContextDrawImage</span><span>( context, CGRectMake( <span>0</span>, <span>0</span>, width, height )</span>, image.CGImage );</span>

<span><span>    </span><span>glTexImage2D</span><span>(GL_TEXTURE_2D, <span>0</span>, GL_RGBA, width, height, <span>0</span>, GL_RGBA,
        GL_UNSIGNED_BYTE, imageData)</span>;</span>

<span><span>    </span><span>CGContextRelease</span><span>(context)</span>;</span>

<span><span>    </span><span>free</span><span>(imageData)</span>;</span>
    <span><span>[</span>image <span><span>release</span></span><span>]</span></span>;
    <span><span>[</span>texData <span><span>release</span></span><span>]</span></span>;
</span></pre>
<p>&nbsp;</p>
<p>前面几行代码很容易理解 &#8211; 从程序包中加载一个叫做 <em>texture.png</em> 的图像。然后使用一些 core graphics 调用将位图以 RGBA 格式存放。此基本方法是让我们使用任何<span style="font-family: monospace;">UIImage支持的图像数据然后转换成OpenGL ES接受的数据格式。</span></p>
<p>&nbsp;</p>
<blockquote><p><strong>注意：</strong>只是因为 <span style="font-family: monospace;">UIImage</span> 不支持一种文件类型并不意味着你不能使用此方法。你仍然有可能通过使用Objective-C的分类来增加 <span style="font-family: monospace;">UIImage 对</span>额外的图像文件类型的支持。在 <a href="http://iphonedevelopment.blogspot.com/2009/04/creating-uiimages-from-tga-data.html" target="_blank">这篇文章</a> 我介绍了使 <span style="font-family: monospace;">UIImage</span> 支持 Targa 图像文件格式的方法。</p></blockquote>
<p>&nbsp;</p>
<p>一旦具有了正确格式的位图数据，我们就可以调用 <span style="font-family: monospace;">glTexImage2D()</span> 传递图像数据给 OpenGL ES。完成后，我们释放了一些内存，包括图像数据和实际 <span style="font-family: monospace;">UIImage 的实例。一旦你传递图像数据给</span> OpenGL ES，它就会分配内存以拥有一份自己的数据拷贝，所以你可以释放所有使用的与图像有关的内存，而且你必须这样做除非你的程序有更重要的与数据相关的任务。即使是来自压缩过图像的纹理也会占用程序相当多的内存。每个像素占用四个字节，所以忘记释放纹理图像数据的内存会导致内存很快被用尽。</p>
<p>&nbsp;</p>
<h3>PVRTC方法</h3>
<p>&nbsp;</p>
<p>iPhone的图形芯片（PowerVR MBX）对一种称为 <a href="../wp-content/uploads/2009/12/fenney03texcomp.pdf">PVRTC</a> 的压缩技术提供硬件支持，Apple推荐在开发iPhone应用程序时使用 PVRTC 纹理。他们甚至提供了一篇很好的 <a href="http://developer.apple.com/iphone/library/qa/qa2008/qa1611.html" target="_blank">技术笔记</a> 描述了怎样通过使用随开发工具安装的命令行程序将标准图像文件转换为 PVRTC 纹理的方法。</p>
<p>&nbsp;</p>
<p>你应该知道当使用 PVRTC 时与标准JPEG或PNG图像相比有可能有些图像质量的下降。是否值得在你的程序中做出一些牺牲取决于一些因素，但使用 PVRTC 纹理可以节省大量的内存空间。</p>
<p>&nbsp;</p>
<p>尽管因为没有Objective-C类可以解析 PVRTC 数据获取其宽和高<sup>1</sup>信息，你想要手工指定图像的高和宽，但加载 PVRTC 数据到当前绑定的纹理实际上甚至比加载普通图像文件更为简单。</p>
<p>&nbsp;</p>
<p>下面的例子使用默认的<span style="font-family: monospace;">texturetool</span>设置加载一个 512&#215;512 的PVRTC纹理：</p>
<pre><span>    <span>NSString</span> *path = <span><span>[</span><span><span>[</span><span>NSBundle</span> <span><span>mainBundle</span></span><span>]</span></span> <span><span>pathForResource<span>:</span></span><span><span>@"</span>texture<span>"</span></span> <span>ofType<span>:</span></span><span><span>@"</span>pvrtc<span>"</span></span></span><span>]</span></span>;
    <span>NSData</span> *texData = <span><span>[</span><span><span>[</span><span>NSData</span> <span><span>alloc</span></span><span>]</span></span> <span><span>initWithContentsOfFile<span>:</span></span>path</span><span>]</span></span>;

    <span><span>//</span> This assumes that source PVRTC image is 4 bits per pixel and RGB not RGBA
</span>    <span><span>//</span> If you use the default settings in texturetool, e.g.:
</span>    <span><span>//</span>
</span>    <span><span>//</span>      texturetool -e PVRTC -o texture.pvrtc texture.png
</span>    <span><span>//</span>
</span>    <span><span>//</span> then this code should work fine for you.
</span><span><span>    </span><span>glCompressedTexImage2D</span><span>(GL_TEXTURE_2D, <span>0</span>, GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG, <span>512</span>, <span>512</span>, <span>0</span>,
        <span><span>[</span>texData <span><span>length</span></span><span>]</span></span>, <span><span>[</span>texData <span><span>bytes</span></span><span>]</span></span>)</span>;</span></span></pre>
<p>&nbsp;</p>
<p>就这么简单。使用<span style="font-family: monospace;">glCompressedTexImage2D()从文件加载数据并传送给</span>OpeNGL ES。而随后怎样处理纹理则绝对没有任何区别。</p>
<p>&nbsp;</p>
<h3>纹理限制</h3>
<p>&nbsp;</p>
<p>用于纹理的图像宽和高必须为乘方，比如 2, 4, 8, 16, 32, 64, 128, 256, 512, 或 1024。例如图像可能为 64&#215;128 或 512&#215;512。</p>
<p>&nbsp;</p>
<p>当使用 PVRTC 压缩图像时，有一个额外的限制：源图像必须是正方形，所以你的图像应该为 2&#215;2, 4&#215;4 8&#215;8, 16&#215;16, 32&#215;32, 64&#215;64, 128&#215;128, 256&#215;256, 等等。如果你的纹理本身不是正方形，那么你只需为图像加上黑边使图像成为正方形，然后映射纹理使得你需要的部分显示在多边形上。我们现在看看纹理是怎样映射到多边形的。</p>
<p>&nbsp;</p>
<h2>纹理坐标</h2>
<p>&nbsp;</p>
<p>当纹理映射启动后绘图时，你必须为OpenGL ES提供其他数据，即顶点数组中各顶点的 <strong>纹理坐标</strong>。纹理坐标定义了图像的哪一部分将被映射到多边形。它的工作方式有点奇怪。你有一个正方形或长方形的纹理，其左下角为二维平面的原点，高和宽的单位为一。像这样：</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/texture_coords.png"><img class="alignnone size-full wp-image-659" title="texture_coords" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/texture_coords.png" alt="texture_coords" width="401" height="318" /></a></div>
<p>&nbsp;</p>
<p>这就是我们的“纹理坐标系统”，不使用<em>x</em> 和 <em>y</em> 来代表二维空间，我们使用 <em>s</em> 和 <em>t</em> 作为纹理坐标轴，但原理上是一样的。</p>
<p>&nbsp;</p>
<p>除了 <em>s</em> 和 <em>t</em> 轴外，被映射的纹理在多边形同样有两个轴，它们称为 <em>u</em> 和 <em>v</em>轴。这是源于许多3D图像程序中的<em>UV 映射</em> 的术语。</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/uvst.png"><img class="alignnone size-full wp-image-660" title="uvst" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/uvst.png" alt="uvst" width="307" height="306" /></a></div>
<p>&nbsp;</p>
<p>好，我们明白了纹理坐标系统，我们现在讨论怎样使用这些纹理坐标。当我们指定顶点数组中的顶点时，我们需要在另一个数组中提供纹理坐标，它称为<strong>纹理坐标数组</strong>。 每个顶点，我们将传递两个 <span style="font-family: monospace;">GLfloat</span>s (s, t) 来指定顶点在上图所示坐标系统的位置。让我们看看一个可能是最为简单的例子，将整个图像映射到一个由三角形条组成的正方形上。首先，我们创建一个由四个顶点组成的顶点数组：</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/trianglestrip.png"><img class="alignnone size-full wp-image-661" title="trianglestrip" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/trianglestrip.png" alt="trianglestrip" width="320" height="323" /></a></div>
<p>&nbsp;</p>
<p>现在将两个框图叠在一起，所使用的坐标数组的值变得很明显：</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/overlay.png"><img class="alignnone size-full wp-image-662" title="overlay" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/overlay.png" alt="overlay" width="431" height="318" /></a></div>
<p>将其转化为 <span style="font-family: monospace;">GLfloat</span>数组：</p>
<pre><span>    <span>static</span> <span>const</span> GLfloat texCoords<span><span>[</span><span>]</span></span> = <span>{
        <span>0.0</span>, <span>1.0</span>,
        <span>1.0</span>, <span>1.0</span>,
        <span>0.0</span>, <span>0.0</span>,
        <span>1.0</span>, <span>0.0</span>
    }</span>;</span></pre>
<p>&nbsp;</p>
<p>为使用纹理坐标数组，我们必须启动它（正如你预料的那样）。使用 <span style="font-family: monospace;">glEnableClientState()：</span></p>
<pre><span><span><span>    </span><span>glEnableClientState</span><span>(GL_TEXTURE_COORD_ARRAY)</span>;</span></span></pre>
<p>为传递纹理坐标，调用 <span style="font-family: monospace;">glTexCoordPointer()</span>:</p>
<pre><span><span><span>    </span><span>glTexCoordPointer</span><span>(<span>2</span>, GL_FLOAT, <span>0</span>, texCoords)</span>;</span></span></pre>
<p>&nbsp;</p>
<p>就是这样。我们汇总一下把代码置于 <span style="font-family: monospace;">drawView:</span> 方法中。它假设纹理已经被绑定和加载了。</p>
<pre><span>- (<span>void</span>)drawView:(GLView*)view;
<span>{
    <span>static</span> GLfloat rot = <span>0.0</span>;

<span><span>    </span><span>glColor4f</span>(</span><span>0.0</span>, <span>0.0</span>, <span>0.0</span>, <span>0.0</span>);
<span><span>    </span><span>glClear</span>(</span>GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

<span><span>    </span><span>glEnableClientState</span>(</span>GL_VERTEX_ARRAY);
<span><span>    </span><span>glEnableClientState</span>(</span>GL_NORMAL_ARRAY);
<span><span>    </span><span>glEnableClientState</span>(</span>GL_TEXTURE_COORD_ARRAY);

    <span>static</span> <span>const</span> Vertex3D vertices<span><span>[</span><span>]</span></span> = <span>{
        <span>{-<span>1.0</span>,  <span>1.0</span>, -<span>0.0</span>}</span>,
        <span>{ <span>1.0</span>,  <span>1.0</span>, -<span>0.0</span>}</span>,
        <span>{-<span>1.0</span>, -<span>1.0</span>, -<span>0.0</span>}</span>,
        <span>{ <span>1.0</span>, -<span>1.0</span>, -<span>0.0</span>}</span>
    }</span>;
    <span>static</span> <span>const</span> Vector3D normals<span><span>[</span><span>]</span></span> = <span>{
        <span>{<span>0.0</span>, <span>0.0</span>, <span>1.0</span>}</span>,
        <span>{<span>0.0</span>, <span>0.0</span>, <span>1.0</span>}</span>,
        <span>{<span>0.0</span>, <span>0.0</span>, <span>1.0</span>}</span>,
        <span>{<span>0.0</span>, <span>0.0</span>, <span>1.0</span>}</span>
    }</span>;
    <span>static</span> <span>const</span> GLfloat texCoords<span><span>[</span><span>]</span></span> = <span>{
        <span>0.0</span>, <span>1.0</span>,
        <span>1.0</span>, <span>1.0</span>,
        <span>0.0</span>, <span>0.0</span>,
        <span>1.0</span>, <span>0.0</span>
    }</span>;

<span><span>    </span><span>glLoadIdentity</span>(</span>);
<span><span>    </span><span>glTranslatef</span>(</span><span>0.0</span>, <span>0.0</span>, -<span>3.0</span>);
<span><span>    </span><span>glRotatef</span>(</span>rot, <span>1.0</span>, <span>1.0</span>, <span>1.0</span>);

<span><span>    </span><span>glBindTexture</span>(</span>GL_TEXTURE_2D, texture<span><span>[</span><span>0</span><span>]</span></span>);
<span><span>    </span><span>glVertexPointer</span>(</span><span>3</span>, GL_FLOAT, <span>0</span>, vertices);
<span><span>    </span><span>glNormalPointer</span>(</span>GL_FLOAT, <span>0</span>, normals);
<span><span>    </span><span>glTexCoordPointer</span>(</span><span>2</span>, GL_FLOAT, <span>0</span>, texCoords);
<span><span>    </span><span>glDrawArrays</span>(</span>GL_TRIANGLE_STRIP, <span>0</span>, <span>4</span>);

<span><span>    </span><span>glDisableClientState</span>(</span>GL_VERTEX_ARRAY);
<span><span>    </span><span>glDisableClientState</span>(</span>GL_NORMAL_ARRAY);
<span><span>    </span><span>glDisableClientState</span>(</span>GL_TEXTURE_COORD_ARRAY);

    <span>static</span> <span>NSTimeInterval</span> lastDrawTime;
    <span>if</span><span> <span>(</span></span>lastDrawTime)
    <span>{
        <span>NSTimeInterval</span> timeSinceLastDraw =
            <span><span>[</span><span>NSDate</span> <span><span>timeIntervalSinceReferenceDate</span></span><span>]</span></span> - lastDrawTime;
        rot+=  <span>60</span> * timeSinceLastDraw;
    }</span>
    lastDrawTime = <span><span>[</span><span>NSDate</span> <span><span>timeIntervalSinceReferenceDate</span></span><span>]</span></span>;
}</span></span></pre>
<p>&nbsp;</p>
<p>下面是我使用的纹理：</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/texture.png"><img class="alignnone size-full wp-image-663" title="texture" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/texture.png" alt="texture" width="512" height="512" /></a></div>
<p>&nbsp;</p>
<p>运行时的结果：</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/textureapp1.png"><img class="alignnone size-full wp-image-664" title="textureapp1" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/textureapp1.png" alt="textureapp1" width="208" height="400" /></a></div>
<p>&nbsp;</p>
<p>请等下：那并不正确。如果你仔细对比一下纹理图像和上面的截屏，你就会发现它们并不完全相同。截屏中图像的y轴（或t轴）完全颠倒了。它上下颠倒了，并不是旋转，而是翻转了。</p>
<p>&nbsp;</p>
<h3>T-轴翻转之谜</h3>
<p>&nbsp;</p>
<p>以OpenGL的角度来看，我们并未做错任何事情，但结果却是完全错误。原因在于iPhone的特殊性。 iPhone中用于Core Graphics的图像坐标系统并与OpenGL ES一致，其y轴在屏幕从上到下而增加。当然在OpenGL ES中正好相反，它的y轴从下向上增加。其结果就是我们早先传递给OpenGL ES中的图像数据从OpenGL ES的角度看完全颠倒了。所以，当我们使用标准的OpenGL ST映射坐标映射图像时，我们得到了一个翻转的图像。</p>
<p>&nbsp;</p>
<h4><strong>普通图像的修正</strong></h4>
<p>&nbsp;</p>
<p>当使用非PVRTC图像时，你可以在传递数据到OpenGL ES之前就翻转图像的坐标，将下面两行代码到纹理加载中创建OpenGL环境的语句之后：</p>
<pre><span>        <span>CGContextTranslateCTM (context, 0, height);
        CGContextScaleCTM (context, 1.0, -1.0);</span></span></pre>
<p>&nbsp;</p>
<p>这将翻转绘制内容的坐标系统，其产生的数据正是OpenGL ES所需要的。下面是结果：</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/rightwiththeworld.png"><img class="alignnone size-full wp-image-665" title="rightwiththeworld" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/rightwiththeworld.png" alt="rightwiththeworld" width="208" height="400" /></a></div>
<p>&nbsp;</p>
<h4><strong>PVRTC 图像的修正</strong></h4>
<p>&nbsp;</p>
<p>由于没有UIKit类可以加载或处理PVRTC 图像，所以没有一个简单的方法翻转压缩纹理的坐标系统。当然，我们还是有些方法处理这个问题。</p>
<p>&nbsp;</p>
<p>一种方法是使用诸如 <a href="http://flyingmeat.com/acorn/" target="_blank">Acorn</a> 或 Photoshop之类的程序中将图像转换为压缩纹理前简单地进行垂直翻转。这看似小诡计的方法在很多情况下是最好的解决方法，因为所有的处理都是事前进行的，所以运行时不需要额外的处理时间而且还允许压缩和未压缩图像具有同样的纹理坐标数组。</p>
<p>&nbsp;</p>
<p>另一种方法是将t轴的值减一。尽管减法是很快的，但其占用的时间还是会累积，所以在大部分情况下，尽量要避免绘图时进行的转换工作。不论是翻转图像或翻转纹理坐标，都要在显示前进行加载时进行。</p>
<p>&nbsp;</p>
<h2>更多的映射方式</h2>
<p>&nbsp;</p>
<p>上个例子中这个图像都被映射到绘制的正方形上。那是因为设定的纹理坐标所决定的。我们可以改变坐标数组仅使用源图像的中心部分。让我们看看仅使用了图像中心部分的另一个框图：</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/mapping3.png"><img class="alignnone size-full wp-image-666" title="mapping3" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/mapping3.png" alt="mapping3" width="282" height="282" /></a></div>
<p>&nbsp;</p>
<p>其坐标数组为：</p>
<pre><span>    <span>static</span> <span>const</span> GLfloat texCoords<span><span>[</span><span>]</span></span> = <span>{
        <span>0.25</span>, <span>0.75</span>,
        <span>0.75</span>, <span>0.75</span>,
        <span>0.25</span>, <span>0.25</span>,
        <span>0.75</span>, <span>0.25</span>
    }</span>;</span></pre>
<p>&nbsp;</p>
<p>运行使用了新映射到程序，屏幕上只显示了图像的中心部分：</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/middle.png"><img class="alignnone size-full wp-image-667" title="middle" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/middle.png" alt="middle" width="208" height="400" /></a></div>
<p>&nbsp;</p>
<p>类似地，如果我们只希望显示纹理的左下部：</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/lowerleft.png"><img class="alignnone size-full wp-image-668" title="lowerleft" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/lowerleft.png" alt="lowerleft" width="359" height="291" /></a></div>
<p>&nbsp;</p>
<p>坐标数组为:</p>
<pre><span>    <span>static</span> <span>const</span> GLfloat texCoords<span><span>[</span><span>]</span></span> = <span>{
        <span>0.0</span>, <span>0.5</span>,
        <span>0.5</span>, <span>0.5</span>,
        <span>0.0</span>, <span>0.0</span>,
        <span>0.5</span>, <span>0.0</span>
    }</span>;</span></pre>
<p>&nbsp;</p>
<p>显示结果：</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/lowerleft1.png"><img class="alignnone size-full wp-image-669" title="lowerleft1" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/lowerleft1.png" alt="lowerleft1" width="208" height="400" /></a></div>
<p>&nbsp;</p>
<h2>等一下，还有更多的方式</h2>
<p>&nbsp;</p>
<p>实际上，并不是真正还有更多的映射方式，只是说此功能在正方形到正方形的映射时并不很明显。同样的步骤适合于几何体中任何三角形，而且你甚至可以通过非常规方式的映射来扭曲纹理。例如，我们可以定义一个等腰三角形：</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/triangle.png"><img class="alignnone size-full wp-image-670" title="triangle" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/triangle.png" alt="triangle" width="359" height="356" /></a></div>
<p>&nbsp;</p>
<p>但将底部顶点映射到纹理的左下角：</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/weirdMapping.png"><img class="alignnone size-full wp-image-671" title="weirdMapping" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/weirdMapping.png" alt="weirdMapping" width="507" height="362" /></a></div>
<p>&nbsp;</p>
<p>这样的映射并不会改变几何体 &#8211; 它仍然是等腰三角形而不是直角三角形，但OpenGL ES将扭曲纹理使得第二个图中的三角形部分以等腰三角形的形式显示出来。代码如下：</p>
<pre><span>- (<span>void</span>)drawView:(GLView*)view;
<span>{
<span><span>    </span><span>glColor4f</span>(</span><span>0.0</span>, <span>0.0</span>, <span>0.0</span>, <span>0.0</span>);
<span><span>    </span><span>glClear</span>(</span>GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

<span><span>    </span><span>glEnableClientState</span>(</span>GL_VERTEX_ARRAY);
<span><span>    </span><span>glEnableClientState</span>(</span>GL_NORMAL_ARRAY);
<span><span>    </span><span>glEnableClientState</span>(</span>GL_TEXTURE_COORD_ARRAY);

    <span>static</span> <span>const</span> Vertex3D vertices<span><span>[</span><span>]</span></span> = <span>{
        <span>{-<span>1.0</span>,  <span>1.0</span>, -<span>0.0</span>}</span>,
        <span>{ <span>1.0</span>,  <span>1.0</span>, -<span>0.0</span>}</span>,
        <span>{ <span>0.0</span>, -<span>1.0</span>, -<span>0.0</span>}</span>,

    }</span>;
    <span>static</span> <span>const</span> Vector3D normals<span><span>[</span><span>]</span></span> = <span>{
        <span>{<span>0.0</span>, <span>0.0</span>, <span>1.0</span>}</span>,
        <span>{<span>0.0</span>, <span>0.0</span>, <span>1.0</span>}</span>,
        <span>{<span>0.0</span>, <span>0.0</span>, <span>1.0</span>}</span>,
    }</span>;
    <span>static</span> <span>const</span> GLfloat texCoords<span><span>[</span><span>]</span></span> = <span>{
        <span>0.0</span>, <span>1.0</span>,
        <span>1.0</span>, <span>0.0</span>,
        <span>0.0</span>, <span>0.0</span>,
    }</span>;

<span><span>    </span><span>glLoadIdentity</span>(</span>);
<span><span>    </span><span>glTranslatef</span>(</span><span>0.0</span>, <span>0.0</span>, -<span>3.0</span>);

<span><span>    </span><span>glBindTexture</span>(</span>GL_TEXTURE_2D, texture<span><span>[</span><span>0</span><span>]</span></span>);
<span><span>    </span><span>glVertexPointer</span>(</span><span>3</span>, GL_FLOAT, <span>0</span>, vertices);
<span><span>    </span><span>glNormalPointer</span>(</span>GL_FLOAT, <span>0</span>, normals);
<span><span>    </span><span>glTexCoordPointer</span>(</span><span>2</span>, GL_FLOAT, <span>0</span>, texCoords);
<span><span>    </span><span>glDrawArrays</span>(</span>GL_TRIANGLES, <span>0</span>, <span>3</span>);

<span><span>    </span><span>glDisableClientState</span>(</span>GL_VERTEX_ARRAY);
<span><span>    </span><span>glDisableClientState</span>(</span>GL_NORMAL_ARRAY);
<span><span>    </span><span>glDisableClientState</span>(</span>GL_TEXTURE_COORD_ARRAY);
}</span></span></pre>
<p>&nbsp;</p>
<p>运行时结果如下：</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/trianglesimul.png"><img class="alignnone size-full wp-image-672" title="trianglesimul" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/trianglesimul.png" alt="trianglesimul" width="208" height="400" /></a></div>
<p>&nbsp;</p>
<p>注意到纹理正方形左下角的弧形花纹现在处于三角形的底部了吗？总而言之，纹理上的任何一点都可以映射到多边形的任何一点。或者换而言之，你可以对任何地点（u，v）使用任何（s，t）而OpenGL ES则为你进行映射。</p>
<p>&nbsp;</p>
<h2><strong>平铺和箝位</strong></h2>
<p>&nbsp;</p>
<p>我们的纹理坐标系统在两个轴上都是从<span style="font-family: monospace;">0.0</span> 到 <span style="font-family: monospace;">1.0，如果设置超出此范围的值会怎么样？根据视图的设置方式有两种选择。</span></p>
<p>&nbsp;</p>
<h3>平铺（也叫重复）</h3>
<p>&nbsp;</p>
<p>一种选择是平铺纹理。按OpenGL的术语，也叫“重复”。如果我们将第一个纹理坐标数组的所有<span style="font-family: monospace;">1.0</span>改为<span style="font-family: monospace;">2.0</span>：</p>
<pre><span>    <span>static</span> <span>const</span> GLfloat texCoords<span><span>[</span><span>]</span></span> = <span>{
        <span>0.0</span>, <span>2.0</span>,
        <span>2.0</span>, <span>2.0</span>,
        <span>0.0</span>, <span>0.0</span>,
        <span>2.0</span>, <span>0.0</span>
    }</span>;</span></pre>
<p>&nbsp;</p>
<p>那么我们得到以下结果：</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/tiling.png"><img class="alignnone size-full wp-image-673" title="tiling" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/tiling.png" alt="tiling" width="400" height="400" /></a></div>
<p>&nbsp;</p>
<p>如果这就是你希望的结果，那么你应该在<span style="font-family: monospace;">setupView:方法中通过</span><span style="font-family: monospace;">glTexParameteri()函数启动它，像这样：</span></p>
<pre><span><span><span>    </span><span>glTexParameteri</span><span>(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)</span>;</span>
<span><span>    </span><span>glTexParameteri</span><span>(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)</span>;</span></span></pre>
<p>&nbsp;</p>
<h3>箝位</h3>
<p>&nbsp;</p>
<p>另一种可能的选择是让OpenGL ES简单地将超过<span style="font-family: monospace;">1.0的值限制为</span><span style="font-family: monospace;">1.0，任何低于</span><span style="font-family: monospace;">0.0的值限制为</span> <span style="font-family: monospace;">0.0。这实际会引起边沿像素重复，从而产生奇怪的效果。下图是使用了</span>箝位的效果：</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/clamp.png"><img class="alignnone size-full wp-image-674" title="clamp" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/clamp.png" alt="clamp" width="400" height="400" /></a></div>
<p>&nbsp;</p>
<p>如果这是你希望的效果，那么你应该在<em>setupView：</em>方法中使用下面两行代码：</p>
<pre><span><span><span>    </span><span>glTexParameteri</span><span>(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)</span>;</span>
<span><span>    </span><span>glTexParameteri</span><span>(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)</span>;</span></span></pre>
<p>&nbsp;</p>
<p>注意 <em>s</em> 和 <em>t</em> 轴是分别设置的，这样有可能在一个方向上使用平铺而在另一个方向使用箝位。</p>
<p>&nbsp;</p>
<h2><strong>结论</strong></h2>
<p>&nbsp;</p>
<p>本文介绍了OpenGL ES映射纹理到多边形的基本机制。尽管它很简单，但需要动下脑筋并自己动手才能真正理解它到底是怎样工作的，请下载<a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/texture_projects.zip">texture_projects</a> 自己测试。</p>
<p>&nbsp;</p>
<p>下次我们将介绍矩阵，希望你到时回来。</p>
<p>&nbsp;</p>
<hr /><span style="font-size: x-small;"><br />
<em><strong>注脚</strong></em></p>
<ol>
<li>实际上有一个先行版的示例代码介绍了怎样从文件中读取PVRTC头信息以决定图像的宽和高以及有关压缩图像文件的详细信息。我没有使用它是因为 a) 它还没有正式发布，如果我使用有可能违背了NDA， b) 此代码并不能读取所有的PVRTC文件（包括本文使用的图像）</li>
</ol>
<p></span></p>
<p>&nbsp;</p>
<p><em>感谢 George Sealy 和 Daniel Pasco关于t轴问题的帮助。感谢Apple Dev论坛的&#8221;Colombo&#8221;。</em></p>
<p>&nbsp;</p>
<h5>原文见：<a href="http://iphonedevelopment.blogspot.com/2009/05/opengl-es-from-ground-up-part-6_25.html">OpenGL ES From the Ground Up, Part 6: Textures and Texture Mapping</a></h5>
<p><em><br />
</em></p>
]]></content:encoded>
			<wfw:commentRss>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0opengl-es%e4%b9%8b%e5%85%ad-%e7%ba%b9%e7%90%86%e5%8f%8a%e7%ba%b9%e7%90%86%e6%98%a0%e5%b0%84/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>从零开始学习OpenGL ES之五 &#8211; 材质</title>
		<link>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0opengl-es%e4%b9%8b%e4%ba%94-%e6%9d%90%e8%b4%a8</link>
		<comments>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0opengl-es%e4%b9%8b%e4%ba%94-%e6%9d%90%e8%b4%a8#comments</comments>
		<pubDate>Sat, 09 Jan 2010 07:17:09 +0000</pubDate>
		<dc:creator>bagusflyer</dc:creator>
				<category><![CDATA[图形图像]]></category>
		<category><![CDATA[编程]]></category>
		<category><![CDATA[OpenGL ES]]></category>
		<category><![CDATA[教程]]></category>

		<guid isPermaLink="false">http://www.iphone-geek.cn/?p=613</guid>
		<description><![CDATA[在 上一篇文章，我们讨论了光效的设定以及光效的各种属性。我们还讨论了光的三要素：散射光, 环境光 和 高光。如果你还不是完全清楚，那么我们来复习一下，在定义材质时大量的用到这些要素。



&#160;
作为本文的起点，我们使用了此文中球体绘制 的项目文件。我们不再使用二十面体而是转向球体是因为球体是展示光和材质不同要素之间相互作用的最佳形状。
&#160;

颜色是什么
&#160;
这可能是对小学美术课的复习。为什么现实世界会有颜色？是什么造成的？
&#160;
我们看得见的光被称为 光的可见频谱 。根据不同的波长我们可以感知到不同的颜色。在可见光谱的一端是低波长高频率的紫色和蓝色，而在另一端是低频高波长的橘色和红色：

&#160;
电磁波在这个范围之外，因此不是“可见光”，尽管这只是人工的区分方法，它们的唯一不同在频率和波长，在于人眼的感知。尽管有各种方法感知各种电磁波的能力，但从OpenGL的角度出发，我们只关心可见光谱。
&#160;
“白色光”包括等量的所有波长。换言之，白光之所以是白色的是因为它包括了所有（或至少大部分）可见光的频率。如果你曾经做过棱镜试验，那么你可能看过如下效果：

&#160;
棱镜反射白光，各种波长被分离出来。这就是彩虹产生的原理。
&#160;
如果你看到一个物体呈蓝色，那么实际上是该物体吸收了大部分可见光谱低频部分。它吸收了红，橘，黄和绿光。根据蓝色的不同色度，它还可能吸收一些紫色和蓝色。
&#160;
但大部分蓝色的波长都被反射到你的眼睛。因为一些可见光被吸收了，由于它不再包括可见光谱中的所有波长所以反射到你眼中的光不再是白色。
&#160;
简单吧？让我们看看这些是怎样运用在OpenGL的。
&#160;
OpenGL 材质
&#160;
我们通过定义材质的反射光来定义OpenGL ES中的材质，正如现实世界中一样。如果一个材质定义为反射红光，那么在正常的白光下，它将显示红色。
&#160;
在OpenGL中 (至少在使用光滑着色处理和光效时)，材质是没有颜色的。OpenGL具有分别定义材质是怎样反射OpenGL光效三要素（环境，散射和高光）的能力。另外，它还具有指定材质 自发光（emissive） 属性的能力，关于这点我们稍后再讨论。
&#160;
指定材质
&#160;
要在OpenGL创建一个材质，我们需要一次或多次调用 glMaterialf() 或者 glMaterialfv()。类似于上一篇文章中光效的定义，由于各属性或元素需要分别通过这些调用了指定，所以我们通常必须多次调用这些函数以完全定义材质。所有未定义的元素或属性默认值为0，或者以颜色来说为黑色。
&#160;
传递给 glMaterialf() 或者 glMaterialfv() 的第一个参数总是用于指定是否材质影响多边形的前，后或两者的GL_ENUM。实际上除了为了与OpenGL兼容，第一个参数在OpenGL ES中没有什么意义，因为只有一个有效的选项： GL_FRONT_AND_BACK，它简单地表示材质适用于任何绘制的多边形。如果你还记得第一部分，那么你应该知道三角形的正面和背面是由winding（顶点的绘制次序）决定的。默认情况下，只有三角形的正面被绘制出来，但是有可能让OpenGL也绘制背面，或甚至只绘制背面，常规 OpenGL 允许你通过传递GL_FRONT，GL_BACK， 或者 GL_FRONT_AND_BACK来为正面和背面指定不同的材质。但是OpenGL ES仅支持GL_FRONT_AND_BACK。
&#160;
glMaterialf() 或 glMaterialfv()的第二个参数是指示正在设定材质的哪个元素或属性的 GL_ENUM。它们像传递给glLightfv()的值一样，比如 GL_AMBIENT ，另外还有些新的值我们稍后再谈。
&#160;
最终值是 GL_FLOAT或包括了实际属性或元素的GL_FLOAT数组的指针。
&#160;
材质的最重要元素是环境光和散射光，因为它们决定了材质是怎样反射大量光线的。我们今天使用的项目代码定义了正如太阳光或白炽灯产生的白色，它具有平均分布的各种波长和颜色的光。如果光不是白色，球体看上去会有不同的外观。例如，反射至红色材质的蓝光将产生紫色阴影。简单起见，我们只使用白色光。当然，你可以随意改变光的颜色进行试验看看光和材质是怎样交互作用的。大部分时候，它们在OpenGL ES中的表示与现实生活中完全一样。
&#160;
下面是项目在添加材质前运行时的样子：

&#160;
如你所见，它具有一些环境光和更为显著的散射光。
&#160;
环境光和散射光
&#160;
当讨论OpenGL的材质时，我们需要同时讨论环境光和散射光，这是因为这两个元素是一起工作从而决定物体被感知的颜色的。记住，散射光处于本文第一个图片中球体的顶部（亮黄色），环境光则是下方的暗黄色。材质怎样反射这两个元素决定了物体被感知的颜色。上图的效果可以通过不止一种方法获得。同样地，黄色球以同样比例反射环境光和散射光，但是场景中具有较少的环境光。
&#160;
大约 90% 或更多的情况下，将材质的环境光和散射光参数设定成一样。这样做，使它们成为决定物体阴影和外观的因素。实际上，有一种方法通过一个调用glMaterialfv()同时设定材质环境光和散射光。下面是定义材质为蓝色的示例：
    GLfloat ambientAndDiffuse[] = {0.0, 0.1, 0.9, 1.0};
    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, ambientAndDiffuse);
&#160;
正如 glColor4f()， 设置材质指示随后所有物体绘制的方式直到另一个材质被指定。如果将上叙代码放置于我们的绘制代码之前，那么运行时你将看到球体变为蓝色。由于环境光没有散射光强，其下方只是稍暗。
&#160;

&#160;
有时你希望更多地控制并希望分别设定材质对环境光和散射光的反射方式。例如，下例中，材质从环境光反射蓝色，从散射光反射红色：
  [...]]]></description>
			<content:encoded><![CDATA[<p>在 <a href="编程/从零开始学习opengl-es之四-光效" target="_blank">上一篇文章</a>，我们讨论了光效的设定以及光效的各种属性。我们还讨论了光的三要素：<strong>散射光</strong>, <strong>环境光</strong> 和 <strong>高光</strong>。如果你还不是完全清楚，那么我们来复习一下，在定义材质时大量的用到这些要素。</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/component.jpg"><img class="alignnone size-full wp-image-605" title="component" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/component.jpg" alt="component" width="250" height="250" /></a></div>
<p><span><em><br />
</em></span></p>
<p>&nbsp;</p>
<p>作为本文的起点，我们使用了<a href="http://iphonedevelopment.blogspot.com/2009/05/procedural-spheres-in-opengl-es.html" target="_blank">此文</a>中<a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/Part5Project%20Start.zip">球体绘制</a> 的项目文件。我们不再使用二十面体而是转向球体是因为球体是展示光和材质不同要素之间相互作用的最佳形状。</p>
<p>&nbsp;</p>
<p><span id="more-613"></span></p>
<h2>颜色是什么</h2>
<p>&nbsp;</p>
<p>这可能是对小学美术课的复习。为什么现实世界会有颜色？是什么造成的？</p>
<p>&nbsp;</p>
<p>我们看得见的光被称为 <a href="http://en.wikipedia.org/wiki/Visible_spectrum" target="_blank">光的可见频谱</a> 。根据不同的波长我们可以感知到不同的颜色。在可见光谱的一端是低波长高频率的紫色和蓝色，而在另一端是低频高波长的橘色和红色：</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/visible_spectrum.png"><img class="alignnone size-full wp-image-614" title="visible_spectrum" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/visible_spectrum.png" alt="visible_spectrum" width="600" height="108" /></a></div>
<p>&nbsp;</p>
<p>电磁波在这个范围之外，因此不是“可见光”，尽管这只是人工的区分方法，它们的唯一不同在频率和波长，在于人眼的感知。尽管有各种方法感知各种电磁波的能力，但从OpenGL的角度出发，我们只关心可见光谱。</p>
<p>&nbsp;</p>
<p>“白色光”包括等量的所有波长。换言之，白光之所以是白色的是因为它包括了所有（或至少大部分）可见光的频率。如果你曾经做过棱镜试验，那么你可能看过如下效果：</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/PrismAndLight.jpg"><img class="alignnone size-full wp-image-615" title="PrismAndLight" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/PrismAndLight.jpg" alt="PrismAndLight" width="449" height="195" /></a></div>
<p>&nbsp;</p>
<p>棱镜反射白光，各种波长被分离出来。这就是彩虹产生的原理。</p>
<p>&nbsp;</p>
<p>如果你看到一个物体呈蓝色，那么实际上是该物体吸收了大部分可见光谱低频部分。它吸收了红，橘，黄和绿光。根据蓝色的不同色度，它还可能吸收一些紫色和蓝色。</p>
<p>&nbsp;</p>
<p>但大部分蓝色的波长都被反射到你的眼睛。因为一些可见光被吸收了，由于它不再包括可见光谱中的所有波长所以反射到你眼中的光不再是白色。</p>
<p>&nbsp;</p>
<p>简单吧？让我们看看这些是怎样运用在OpenGL的。</p>
<p>&nbsp;</p>
<h2>OpenGL 材质</h2>
<p>&nbsp;</p>
<p>我们通过定义材质的反射光来定义OpenGL ES中的材质，正如现实世界中一样。如果一个材质定义为反射红光，那么在正常的白光下，它将显示红色。</p>
<p>&nbsp;</p>
<p>在OpenGL中 (至少在使用光滑着色处理和光效时)，材质是没有颜色的。OpenGL具有分别定义材质是怎样反射OpenGL光效三要素（环境，散射和高光）的能力。另外，它还具有指定材质 <strong>自发光（emissive）</strong> 属性的能力，关于这点我们稍后再讨论。</p>
<p>&nbsp;</p>
<h3>指定材质</h3>
<p>&nbsp;</p>
<p>要在OpenGL创建一个材质，我们需要一次或多次调用 <span style="font-family: monospace;">glMaterialf()</span> 或者 <span style="font-family: monospace;">glMaterialfv()</span>。类似于上一篇文章中光效的定义，由于各属性或元素需要分别通过这些调用了指定，所以我们通常必须多次调用这些函数以完全定义材质。所有未定义的元素或属性默认值为0，或者以颜色来说为黑色。</p>
<p>&nbsp;</p>
<p>传递给 <span style="font-family: monospace;">glMaterialf()</span> 或者 <span style="font-family: monospace;">glMaterialfv()</span> 的第一个参数总是用于指定是否材质影响多边形的前，后或两者的<span style="font-family: monospace;">GL_ENUM。实际上除了为了与OpenGL兼容，第一个参数在OpenGL ES中没有什么意义，因为只有一个有效的选项：</span> <span style="font-family: monospace;">GL_FRONT_AND_BACK，它简单地表示材质适用于</span><span style="font-family: monospace;">任何</span><span style="font-family: monospace;">绘制的多边形</span>。如果你还记得<a href="编程/从零开始学习opengl-es之一-基本概念" target="_blank">第一部分</a>，那么你应该知道三角形的正面和背面是由winding（顶点的绘制次序）决定的。默认情况下，只有三角形的正面被绘制出来，但是有可能让OpenGL也绘制背面，或甚至只绘制背面，常规 OpenGL 允许你通过传递<span style="font-family: monospace;">GL_FRONT</span>，<span style="font-family: monospace;">GL_BACK</span>， 或者 <span style="font-family: monospace;">GL_FRONT_AND_BACK来为正面和背面指定不同的材质。但是</span>OpenGL ES仅支持<span style="font-family: monospace;">GL_FRONT_AND_BACK。</span></p>
<p>&nbsp;</p>
<p><span style="font-family: monospace;">glMaterialf()</span> 或 <span style="font-family: monospace;">glMaterialfv()的第二个参数是指示正在设定材质的</span><span style="font-family: monospace;">哪个</span><span style="font-family: monospace;">元素或属性</span>的 <span style="font-family: monospace;">GL_ENUM。它们像传递给</span><span style="font-family: monospace;">glLightfv()</span>的值一样，比如<span style="font-family: monospace;"> GL_AMBIENT</span> ，另外还有些新的值我们稍后再谈。</p>
<p>&nbsp;</p>
<p>最终值是 <span style="font-family: monospace;">GL_FLOAT或</span>包括了实际属性或元素的<span style="font-family: monospace;">GL_FLOAT</span>数组的指针。</p>
<p>&nbsp;</p>
<p>材质的最重要元素是环境光和散射光，因为它们决定了材质是怎样反射大量光线的。我们今天使用的项目代码定义了正如太阳光或白炽灯产生的白色，它具有平均分布的各种波长和颜色的光。如果光不是白色，球体看上去会有不同的外观。例如，反射至红色材质的蓝光将产生紫色阴影。简单起见，我们只使用白色光。当然，你可以随意改变光的颜色进行试验看看光和材质是怎样交互作用的。大部分时候，它们在OpenGL ES中的表示与现实生活中完全一样。</p>
<p>&nbsp;</p>
<p>下面是项目在添加材质前运行时的样子：</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/iPhone-SimulatorScreenSnapz0011.jpg"><img class="alignnone size-full wp-image-616" title="iPhone SimulatorScreenSnapz001" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/iPhone-SimulatorScreenSnapz0011.jpg" alt="iPhone SimulatorScreenSnapz001" width="208" height="400" /></a></div>
<p>&nbsp;</p>
<p>如你所见，它具有一些环境光和更为显著的散射光。</p>
<p>&nbsp;</p>
<h3>环境光和散射光</h3>
<p>&nbsp;</p>
<p>当讨论OpenGL的材质时，我们需要同时讨论环境光和散射光，这是因为这两个元素是一起工作从而决定物体被感知的颜色的。记住，散射光处于本文第一个图片中球体的顶部（亮黄色），环境光则是下方的暗黄色。材质怎样反射这两个元素决定了物体被感知的颜色。上图的效果可以通过不止一种方法获得。同样地，黄色球以同样比例反射环境光和散射光，但是场景中具有较少的环境光。</p>
<p>&nbsp;</p>
<p>大约 90% 或更多的情况下，将材质的环境光和散射光参数设定成一样。这样做，使它们成为决定物体阴影和外观的因素。实际上，有一种方法通过一个调用<span style="font-family: monospace;">glMaterialfv()同时设定材质环境光和散射光。下面是定义材质为蓝色的示例：</span></p>
<pre><span>    GLfloat ambientAndDiffuse<span><span>[</span><span>]</span></span> = <span>{<span>0.0</span>, <span>0.1</span>, <span>0.9</span>, <span>1.0</span>}</span>;
<span><span>    </span><span>glMaterialfv</span><span>(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, ambientAndDiffuse)</span>;</span></span></pre>
<p>&nbsp;</p>
<p>正如 <span style="font-family: monospace;">glColor4f()</span>， 设置材质指示随后所有物体绘制的方式直到另一个材质被指定。如果将上叙代码放置于我们的绘制代码之前，那么运行时你将看到球体变为蓝色。由于环境光没有散射光强，其下方只是稍暗。</p>
<p>&nbsp;</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/iPhone-SimulatorScreenSnapz0022.jpg"><img class="alignnone size-full wp-image-617" title="iPhone SimulatorScreenSnapz002" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/iPhone-SimulatorScreenSnapz0022.jpg" alt="iPhone SimulatorScreenSnapz002" width="208" height="400" /></a></div>
<p>&nbsp;</p>
<p>有时你希望更多地控制并希望分别设定材质对环境光和散射光的反射方式。例如，下例中，材质从环境光反射蓝色，从散射光反射红色：</p>
<pre><span>    GLfloat ambient<span><span>[</span><span>]</span></span> = <span>{<span>0.0</span>, <span>0.1</span>, <span>0.9</span>, <span>1.0</span>}</span>;
<span><span>    </span><span>glMaterialfv</span><span>(GL_FRONT_AND_BACK, GL_AMBIENT, ambient)</span>;</span>
    GLfloat diffuse<span><span>[</span><span>]</span></span> = <span>{<span>0.9</span>, <span>0.0</span>, <span>0.1</span>, <span>1.0</span>}</span>;
<span><span>    </span><span>glMaterialfv</span><span>(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse)</span>;</span></span></pre>
<p>&nbsp;</p>
<p>运行结果：</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/iPhone-SimulatorScreenSnapz0031.jpg"><img class="alignnone size-full wp-image-618" title="iPhone SimulatorScreenSnapz003" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/iPhone-SimulatorScreenSnapz0031.jpg" alt="iPhone SimulatorScreenSnapz003" width="208" height="400" /></a></div>
<p>&nbsp;</p>
<p>在此情况下，它看上去像我们投射了有色光到球体上，实际上却不是这样。原因是我们反射了与环境光不一样的定向光线。</p>
<p>&nbsp;</p>
<p>大部分情况下，如果你希望产生彩色光的效果，你只需创建彩色光线然后使用<span style="font-family: monospace;">GL_AMBIENT_AND_DIFFUSE来指定材质颜色。但是有时却希望分别设置它们产生特殊效果或在不引起创建额外光线开销的情况下假造一个分离的彩色点光源。记住：你每增加一个光源，也就增加了每秒钟的运算量，所以有时候欺骗并不完全是一件坏事。</span></p>
<p>&nbsp;</p>
<h2>高光和光泽</h2>
<p>&nbsp;</p>
<p>你还可以单独设置场景中高光元素的反射方式，从而控制高光“热点”的亮度。一个叫 <span style="font-family: monospace;">GL_SHININESS的参数与材质的高光元素一起定义了高光热点的大小。如果你设定了</span>材质的 <span style="font-family: monospace;">GL_SPECULAR</span> 值，你还应该定义其反光度。反光度越高，高光反射越小，所以默认值 0.0 几乎完全淹没了散射光，因此看上去很糟糕。</p>
<p>&nbsp;</p>
<p>让我们回到蓝色球体，增加高光热点：</p>
<pre><span>    GLfloat ambientAndDiffuse<span><span>[</span><span>]</span></span> = <span>{<span>0.0</span>, <span>0.1</span>, <span>0.9</span>, <span>1.0</span>}</span>;
<span><span>    </span><span>glMaterialfv</span><span>(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, ambientAndDiffuse)</span>;</span>
    GLfloat specular<span><span>[</span><span>]</span></span> = <span>{<span>0.3</span>, <span>0.3</span>, <span>0.3</span>, <span>1.0</span>}</span>;
<span><span>    </span><span>glMaterialfv</span><span>(GL_FRONT_AND_BACK, GL_SPECULAR, specular)</span>;</span>
<span><span>    </span><span>glMaterialf</span><span>(GL_FRONT_AND_BACK, GL_SHININESS, <span>25.0</span>)</span>;</span></span></pre>
<p>&nbsp;</p>
<p>我们使用了一个较暗的白色作为球体的高光值。这个值看上去有些小，但就是这么小的值也能产生显著的效果。光线高光元素的值与材质的高光元素相乘所产生的光集中在材质反光度指定的区域。下面是上叙代码产生的结果：</p>
<p>&nbsp;</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/iPhone-SimulatorScreenSnapz004.jpg"><img class="alignnone size-full wp-image-619" title="iPhone SimulatorScreenSnapz004" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/iPhone-SimulatorScreenSnapz004.jpg" alt="iPhone SimulatorScreenSnapz004" width="208" height="400" /></a></div>
<p>&nbsp;</p>
<p>现在，球体上有一个小的区域具有更强的反射光。我们可以通过增强光或光的高光元素，或者通过增加材质的反光度使这个点更亮。我们还可以通过调整反光度改变高光的大小。材质反光度越高，高光越集中。例如，如果我们将反光度从<span style="font-family: monospace;">25.0</span> 改为 <span style="font-family: monospace;">50.0，我们将得到一个更小的热点，它使得球体显得更具有光泽。</span></p>
<p>&nbsp;</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/iPhone-SimulatorScreenSnapz005.jpg"><img class="alignnone size-full wp-image-620" title="iPhone SimulatorScreenSnapz005" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/iPhone-SimulatorScreenSnapz005.jpg" alt="iPhone SimulatorScreenSnapz005" width="208" height="400" /></a></div>
<p>&nbsp;</p>
<p>但是，有一点要小心。本文之所以改为使用球体的部分原因以及为什么球体使用较高的顶点数目的原因是高光在一个低面数的物体上看上去实在糟糕。注意一下，如果我减少球体的顶点数会发生什么：</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/iPhone-SimulatorScreenSnapz006.jpg">低面片<img class="alignnone size-full wp-image-621" title="iPhone SimulatorScreenSnapz006" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/iPhone-SimulatorScreenSnapz006.jpg" alt="iPhone SimulatorScreenSnapz006" width="208" height="400" /></a></div>
<p>&nbsp;</p>
<p>高光使三角形边缘突出，高光通常在我们的游戏中经常使用的低面片的物体上表现不佳。在常规OpenGL中，有一个称为 <strong>着色器（shader）</strong> 的机制可以用来为低面片物体产生较为理想的结果，但目前iPhone上的OpenGL ES并不支持此功能（译者注：iPhone 3GS支持OpenGL ES 2.0，有shader功能。但有一个问题就是OpenGL ES 1.1与OpenGL ES 2.0并不完全兼容）在游戏中如果你想使低面片物体漂亮的唯一方法就是完全摒弃高光元素而使用纹理映射，这将在下一篇文章中谈到。</p>
<p>&nbsp;</p>
<h2>自发光</h2>
<p>&nbsp;</p>
<p>还剩最后一个材质的重要属性，它称为自发光元素。通过设定自发光元素，使得材质看上去会发射我们指定的颜色。它并不是真正在发光。例如，其周边物体并不会被发射的光线影响。如果你希望一个物体像灯泡一样发光照亮其他物体，由于在OpenGL ES中只有光源会发光（听上去像废话），你需要将自发光元素和与物体同一位置处的实际光源结合起来。但是自发光元素可以使物体漂亮地发光。</p>
<p>&nbsp;</p>
<p>例如，我们可以为蓝色球体添加绿色的光泽：</p>
<pre><span>    GLfloat emission<span><span>[</span><span>]</span></span> = <span>{<span>0.0</span>, <span>0.4</span>, <span>0.0</span>, <span>1.0</span>}</span>;
<span><span>    </span><span>glMaterialfv</span><span>(GL_FRONT_AND_BACK, GL_EMISSION, emission)</span>;</span></span></pre>
<p>&nbsp;</p>
<p>结果如下：</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/iPhone-SimulatorScreenSnapz007.jpg"><img class="alignnone size-full wp-image-622" title="iPhone SimulatorScreenSnapz007" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/iPhone-SimulatorScreenSnapz007.jpg" alt="iPhone SimulatorScreenSnapz007" width="208" height="400" /></a></div>
<p>&nbsp;</p>
<p>自发光元素影响整个材质，因此 <span style="font-family: monospace;">GL_EMISSION的值将与落入物体指定区域的任何类型的光相叠加。请注意，甚至上图中的高光部分也成了一点蓝-绿色而不是纯白色。</span>在高光点处其效果是很微小的，但在只有环境光被反射的底部效果更为明显。它实际影响整个物体。</p>
<p>&nbsp;</p>
<h5>原文见：<a href="http://iphonedevelopment.blogspot.com/2009/05/opengl-es-from-ground-up-part-5-living.html">OpenGL ES From the Ground Up, Part 5: Living in a Material World</a></h5>
]]></content:encoded>
			<wfw:commentRss>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0opengl-es%e4%b9%8b%e4%ba%94-%e6%9d%90%e8%b4%a8/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>从零开始学习OpenGL ES之一补遗 &#8211; 代号</title>
		<link>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0opengl-es%e4%b9%8b%e4%b8%80%e8%a1%a5%e9%81%97-%e4%bb%a3%e5%8f%b7</link>
		<comments>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0opengl-es%e4%b9%8b%e4%b8%80%e8%a1%a5%e9%81%97-%e4%bb%a3%e5%8f%b7#comments</comments>
		<pubDate>Fri, 08 Jan 2010 04:02:13 +0000</pubDate>
		<dc:creator>bagusflyer</dc:creator>
				<category><![CDATA[图形图像]]></category>
		<category><![CDATA[编程]]></category>
		<category><![CDATA[OpenGL ES]]></category>
		<category><![CDATA[教程]]></category>

		<guid isPermaLink="false">http://www.iphone-geek.cn/?p=594</guid>
		<description><![CDATA[（注：本文是改写的）
&#160;
在写第四部分文章时，当我使用了 glLightfv() 和 glLightf()两种版本时，我突然意识到我还从来没有解释过OpenGL的命名规则。这部分应该属于第一部分，随OpenGL数据类型一起介绍。
&#160;
在OpenGL中，没有使用任何参数并在函数尾不具有数据类型代号的函数只有一个：
    GL_ENUM error = glGetError();
&#160;
另外，只具有一个参数（GL_ENUM）而且不具有函数尾数据类型代号的函数有：
    glEnable(GL_COLOR_ARRAY);
&#160;
大部分OpenGL函数都是适用于不止一种数据类型。这在普通版OpenGL中尤为明显，大部分函数都有至少半打变种，允许你传递诸如 GLshort, GLbyte, GLint, GLfloat, GLDouble,或 GLfixed值或参考。在OpenGL ES中，函数调用的变种和数据类型少得多。然而，OpenGL ES仍然遵循同样的命名规则，所以最好还是要理解这些数据类型代号的含义。
&#160;
函数名后的第一或者第二个字母代表了数据类型。下面是说明：


b    GLbyte
s    GLshort
i    GLint
f    GLfloat
ub   GLubyte
us   GLushort
ui   GLuint


&#160;
所以 glFoof()需要传递一个glFloat，而 glFoos()则需要一个 GLshort。
注意： 在常规 OpenGL中，后缀中可能还包括数字。这些数字代表需要的数据类型的数量。例如，函数glVertex3f()使用三个GLfloat。由于此命名模式大部分用于直接模式，而OpenGL ES并不支持，所以在OpenGL ES中你不会常见到这种命名规则，然而有些函数还是遵循这种规则的，例如， glColor3f()。保持后缀中的数字可以使这两类API最大限度地兼容。
&#160;
再回头看看第四部分，有时函数提供更通用的功能，如glLightf()。你可以调用此函数设置指定光的不同属性。例如，你可以设定点光源的截止角或者光源在3D空间的位置。点光源截止角需要一个值，但光源位置需要三个值。
&#160;
对于这类函数，通过在函数尾加上一个 v 来表示。如果你想要传递常规数据类型给OpenGL，那么你不要使用此后缀，而是直接传递数据类型值。但是，如果你想要传递不止一个OpenGL原生数据类型（ 即第一部分列表中的数据类型），那么你需要将这些值放入数组中并将数组开始的指针传递给OpenGL。当你像这样传递参考值给 OpenGL 时，你想要在函数后加上v后缀。
&#160;
所以，回到第四部分的例子，当我们设置点光源的截止角时，我们使用了没有 -v的版本：
    glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 45.0);
&#160;
而当我们传递光源位置时，它需要三个GLfloats值，所以我们使用有v的版本：
    const GLfloat light0Position[] = {0.0, 10.0, 10.0, 0.0};
  [...]]]></description>
			<content:encoded><![CDATA[<p>（注：本文是改写的）</p>
<p>&nbsp;</p>
<p>在写第四部分文章时，当我使用了 <span style="font-family: monospace;">glLightfv()</span> 和 <span style="font-family: monospace;">glLightf()</span>两种版本时，我突然意识到我还从来没有解释过OpenGL的命名规则。这部分应该属于第一部分，随OpenGL数据类型一起介绍。</p>
<p>&nbsp;</p>
<p>在OpenGL中，没有使用任何参数并在函数尾不具有数据类型代号的函数只有一个：</p>
<pre><span>    GL_ENUM error = glGetError();</span></pre>
<p>&nbsp;</p>
<p>另外，只具有一个参数（<span style="font-family: monospace;">GL_ENUM）</span>而且不具有函数尾数据类型代号的函数有：</p>
<pre><span><span><span>    </span><span>glEnable</span><span>(GL_COLOR_ARRAY)</span>;</span></span></pre>
<p>&nbsp;</p>
<p>大部分OpenGL函数都是适用于不止一种数据类型。这在普通版OpenGL中尤为明显，大部分函数都有至少半打变种，允许你传递诸如 <span style="font-family: monospace;">GLshort</span>, <span style="font-family: monospace;">GLbyte</span>, <span style="font-family: monospace;">GLint</span>, <span style="font-family: monospace;">GLfloat</span>, <span style="font-family: monospace;">GLDouble</span>,或 <span style="font-family: monospace;">GLfixed值或参考。在</span>OpenGL ES中，函数调用的变种和数据类型少得多。然而，OpenGL ES仍然遵循同样的命名规则，所以最好还是要理解这些数据类型代号的含义。</p>
<p>&nbsp;</p>
<p>函数名后的第一或者第二个字母代表了数据类型。下面是说明：</p>
<pre>
<ul>
<li><strong>b</strong>    <span style="font-family: monospace;">GLbyte</span></li>
<li><strong>s</strong>    <span style="font-family: monospace;">GLshort</span></li>
<li><strong>i</strong>    <span style="font-family: monospace;">GLint</span></li>
<li><strong>f</strong>    <span style="font-family: monospace;">GLfloat</span></li>
<li><strong>ub</strong>   <span style="font-family: monospace;">GLubyte</span></li>
<li><strong>us</strong>   <span style="font-family: monospace;">GLushort</span></li>
<li><strong>ui</strong>   <span style="font-family: monospace;">GLuint</span></li>
</ul>
</pre>
<p>&nbsp;</p>
<p>所以 <span style="font-family: monospace;">glFoof()需要传递一个</span><span style="font-family: monospace;">glFloat，</span>而 <span style="font-family: monospace;">glFoos()则需要一个</span> <span style="font-family: monospace;">GLshort</span>。</p>
<blockquote><p><strong>注意：</strong> 在常规 OpenGL中，后缀中可能还包括数字。这些数字代表需要的数据类型的数量。例如，函数<span style="font-family: monospace;">glVertex3f()使用三个</span><span style="font-family: monospace;">GLfloat</span>。由于此命名模式大部分用于直接模式，而OpenGL ES并不支持，所以在OpenGL ES中你不会常见到这种命名规则，然而有些函数还是遵循这种规则的，例如， <span style="font-family: monospae;">glColor3f()</span>。保持后缀中的数字可以使这两类API最大限度地兼容。</p></blockquote>
<p>&nbsp;</p>
<p>再回头看看第四部分，有时函数提供更通用的功能，如<span style="font-family: monospace;">glLightf()</span>。你可以调用此函数设置指定光的不同属性。例如，你可以设定点光源的截止角或者光源在3D空间的位置。点光源截止角需要一个值，但光源位置需要三个值。</p>
<p>&nbsp;</p>
<p>对于这类函数，通过在函数尾加上一个 <span style="font-family: monospace;">v</span> 来表示。如果你想要传递常规数据类型给OpenGL，那么你不要使用此后缀，而是直接传递数据类型值。但是，如果你想要传递不止一个OpenGL原生数据类型（ 即第一部分列表中的数据类型），那么你需要将这些值放入数组中并将数组开始的指针传递给OpenGL。当你像这样传递参考值给 OpenGL 时，你想要在函数后加上<span style="font-family: monospace;">v</span>后缀。</p>
<p>&nbsp;</p>
<p>所以，回到第四部分的例子，当我们设置点光源的截止角时，我们使用了没有 -v的版本：</p>
<pre><span><span><span>    </span><span>glLightf</span><span>(GL_LIGHT0, GL_SPOT_CUTOFF, <span>45.0</span>)</span>;</span></span></pre>
<p>&nbsp;</p>
<p>而当我们传递光源位置时，它需要三个<span style="font-family: monspace;">GLfloat</span>s值，所以我们使用有<span style="font-family: monospace;">v的版本：</span></p>
<pre><span>    <span>const</span> GLfloat light0Position<span><span>[</span><span>]</span></span> = <span>{<span>0.0</span>, <span>10.0</span>, <span>10.0</span>, <span>0.0</span>}</span>;
<span><span>    </span><span>glLightfv</span><span>(GL_LIGHT0, GL_POSITION, light0Position)</span>;</span></span></pre>
]]></content:encoded>
			<wfw:commentRss>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0opengl-es%e4%b9%8b%e4%b8%80%e8%a1%a5%e9%81%97-%e4%bb%a3%e5%8f%b7/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>从零开始学习OpenGL ES之四 &#8211; 光效</title>
		<link>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0opengl-es%e4%b9%8b%e5%9b%9b-%e5%85%89%e6%95%88</link>
		<comments>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0opengl-es%e4%b9%8b%e5%9b%9b-%e5%85%89%e6%95%88#comments</comments>
		<pubDate>Thu, 07 Jan 2010 04:14:13 +0000</pubDate>
		<dc:creator>bagusflyer</dc:creator>
				<category><![CDATA[图形图像]]></category>
		<category><![CDATA[编程]]></category>
		<category><![CDATA[OpenGL ES]]></category>
		<category><![CDATA[教程]]></category>

		<guid isPermaLink="false">http://www.iphone-geek.cn/?p=601</guid>
		<description><![CDATA[继续我们的iPhone OpenGL ES之旅，我们将讨论光效。目前，我们没有加入任何光效。幸运的是，OpenGL在没有设置光效的情况下仍然可以看见东西。 它只是提供一种十分单调的整体光让我们看到物体。但是如果不定义光效，物体看上去都很单调，就像你在第二部分程序中看到的那样。

&#160;

&#160;
阴影模型（Shade Model）
&#160;
在深入讨论OpenGL ES是怎样处理光线之前，重要的是要了解OpenGL ES实际上定义了两种shade model， GL_FLAT 和 GL_SMOOTH。我们将不会讨论GL_FLAT，因为这只会让你的程序看上去来自九十年代：


GL_FLAT 方式渲染的一个二十面体。15年前的实时渲染技术
&#160;
从发光的角度来看，GL_FLAT将指定三角形上的每个像素都同等对待。多边形上的每个像素都具有相同的颜色，阴影等。它提供了足够的视觉暗示使其看上去有立体感而且它的计算比每个像素按不同方法计算更为廉价，但是在这种方式下，物体看上去极为不真实。现在有人使用它可能是为了产生特殊的复古效果，但要使你的3D物体尽量真实，你应该使用 GL_SMOOTH 绘图模式，它使用了一种平滑但较快速的阴影算法，称为  Gouraud 算法。 GL_SMOOTH是默认值。
&#160;
启动光效
&#160;
我假定你继续使用第二部分的最终项目，即那个看上去不是很立体的旋转二十面体的项目。如果你手头上还没有那个项目，在这里下载。
&#160;
第一件事就是要启动光效。默认情况下，手工指定光效是被禁止的。现在我们打开这项功能。在 GLViewController.m的setupView:方法中加入黑体部分：
-(void)setupView:(GLView*)view
{
    const GLfloat zNear = 0.01, zFar = 1000.0, fieldOfView = 45.0;
    GLfloat size;
    glEnable(GL_DEPTH_TEST);
    glMatrixMode(GL_PROJECTION);
    size = zNear * tanf(DEGREES_TO_RADIANS(fieldOfView) / [...]]]></description>
			<content:encoded><![CDATA[<p>继续我们的iPhone OpenGL ES之旅，我们将讨论光效。目前，我们没有加入任何光效。幸运的是，OpenGL在没有设置光效的情况下仍然可以看见东西。 它只是提供一种十分单调的整体光让我们看到物体。但是如果不定义光效，物体看上去都很单调，就像你在<a href="编程/从零开始学习opengl-es之二-简单绘图概述">第二部分程序</a>中看到的那样。</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/iPhone-SimulatorScreenSnapz001-1.jpg"><img class="alignnone size-full wp-image-576" title="iPhone SimulatorScreenSnapz001-1" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/iPhone-SimulatorScreenSnapz001-1.jpg" alt="iPhone SimulatorScreenSnapz001-1" width="208" height="400" /></a></div>
<p>&nbsp;</p>
<p><span id="more-601"></span></p>
<p>&nbsp;</p>
<h2>阴影模型（Shade Model）</h2>
<p>&nbsp;</p>
<p>在深入讨论OpenGL ES是怎样处理光线之前，重要的是要了解OpenGL ES实际上定义了两种<strong>shade model</strong>， <span style="font-family: monospace;">GL_FLAT</span> 和 <span style="font-family: monospace;">GL_SMOOTH</span>。我们将不会讨论<span style="font-family: monospace;">GL_FLAT，</span>因为这只会让你的程序看上去来自九十年代：</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/GL_FLAT.jpg"><img class="alignnone size-full wp-image-602" title="GL_FLAT" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/GL_FLAT.jpg" alt="GL_FLAT" width="208" height="400" /></a></div>
<p><span style="font-size: x-small;"><br />
<em><span style="font-family: monospace;">GL_FLAT</span> 方式渲染的一个二十面体。15年前的实时渲染技术</em></span></p>
<p>&nbsp;</p>
<p><span style="font-family: monospace;">从发光的角度来看，GL_FLAT将指定三角形上的每个像素</span>都同等对待。多边形上的每个像素都具有相同的颜色，阴影等。它提供了足够的视觉暗示使其看上去有立体感而且它的计算比每个像素按不同方法计算更为廉价，但是在这种方式下，物体看上去极为不真实。现在有人使用它可能是为了产生特殊的复古效果，但要使你的3D物体尽量真实，你应该使用 <span style="font-family: monospace;">GL_SMOOTH</span> 绘图模式，它使用了一种平滑但较快速的阴影算法，称为  <a href="http://en.wikipedia.org/wiki/Gouraud_shading" target="_blank"><strong>Gouraud</strong></a> 算法。 <span style="font-family: monospace;">GL_SMOOTH是默认值。</span></p>
<p>&nbsp;</p>
<h2>启动光效</h2>
<p>&nbsp;</p>
<p>我假定你继续使用第二部分的最终项目，即那个看上去不是很立体的旋转二十面体的项目。如果你手头上还没有那个项目，在<a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/Part2Project.zip">这里</a>下载。</p>
<p>&nbsp;</p>
<p>第一件事就是要启动光效。默认情况下，手工指定光效是被禁止的。现在我们打开这项功能。在 <em>GLViewController.m</em>的<span style="font-family: monospace;">setupView:方法中加入黑体部分：</span></p>
<pre><span>-(<span>void</span>)setupView:(GLView*)view
<span>{
    <span>const</span> GLfloat zNear = <span>0.01</span>, zFar = <span>1000.0</span>, fieldOfView = <span>45.0</span>;
    GLfloat size;
<span><span>    </span><span>glEnable</span>(</span>GL_DEPTH_TEST);
<span><span>    </span><span>glMatrixMode</span>(</span>GL_PROJECTION);
    size = zNear *<span> </span><span>tanf</span>(<span><span>DEGREES_TO_RADIANS</span>(</span>fieldOfView) / <span>2.0</span>);
    <span>CGRect</span> rect = view<span>.bounds</span>;
<span><span>    </span><span>glFrustumf</span>(</span>-size, size, -size / (rect<span>.size.width</span> / rect<span>.size.height</span>), size /
               (rect<span>.size.width</span> / rect<span>.size.height</span>), zNear, zFar);
<span><span>    </span><span>glViewport</span>(</span><span>0</span>, <span>0</span>, rect<span>.size.width</span>, rect<span>.size.height</span>);
<span><span>    </span><span>glMatrixMode</span>(</span>GL_MODELVIEW);

<span><span>    </span><span><strong>glEnable</strong></span><strong>(</strong></span><strong>GL_LIGHTING);</strong>

<span><span>    </span><span>glLoadIdentity</span>(</span>);
<span><span>    </span><span>glClearColor</span>(</span><span>0.0f</span>, <span>0.0f</span>, <span>0.0f</span>, <span>1.0f</span>);
}</span></span></pre>
<p>&nbsp;</p>
<p>通常情况下，光效只需在设定时启动一次。不需要在绘图开始前后打开和关闭。可能有些特效的情况需要在程序执行时打开或关闭，但是大部分情况下，你只需在程序启动时打开它。此单行代码就是在OpenGL ES中启动光效。运行时会怎样？</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/lights_enabled.jpg"><img class="alignnone size-full wp-image-604" title="lights_enabled" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/lights_enabled.jpg" alt="lights_enabled" width="208" height="400" /></a></div>
<p>&nbsp;</p>
<h2>启动光效</h2>
<p>&nbsp;</p>
<p>我们启动了光效，但是没有创建任何光源。除清除缓存用的灰色外任何绘制的物体都被渲染成绝对的黑色。没有太多的改进，对吗？让我们在场景中加入光源。</p>
<p>&nbsp;</p>
<p>启动光效的方式有些奇怪。OpenGL ES允许你创建8个光源。有一个常量对应于这些光源中的一个，常量为 <span style="font-family: monospace;">GL_LIGHT0</span> 到 <span style="font-family: monospace;">GL_LIGHT7</span>。可以任意组合这些光源中的五个，尽管习惯上从 <span style="font-family: monospace;">GL_LIGHT0</span> 作为第一个光源，然后是 <span style="font-family: monospace;">GL_LIGHT1</span> 等等。下面是“打开”第一个光源<span style="font-family: monospace;">GL_LIGHT0的方法</span>：</p>
<pre>    glEnable(GL_LIGHT0);</pre>
<p>&nbsp;</p>
<p>一旦你启动了光源，你必须设置光源的一些属性。作为初学者，有三个不同的要素用来定义光源。</p>
<p>&nbsp;</p>
<h2>光效三要素</h2>
<p>&nbsp;</p>
<p>在 OpenGL ES中，光由三个元素组成，分别是<strong>环境元素（ambient component</strong>）， <strong>散射元素（diffuse component</strong>）和 <strong>高光元素（specular component</strong>）。我们使用颜色来设定光线元素，这看上去有些奇怪，但是由于它允许你同时指定各光线元素的颜色和相对强度，这个方法工作得很好。明亮的白色光定义为白色 (<span style="font-family: monospace;">{1.0, 1.0, 1.0, 1.0}</span>)，而暗白色可能定义为灰色 (<span style="font-family: monospace;">{0.3, 0.3, 0.3 1.0}</span>)。 你还可以通过改变红，绿，蓝元素的百分比来调整色偏。</p>
<p>&nbsp;</p>
<p>下图说明了各要素产生的效果。</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/component.jpg"><img class="alignnone size-full wp-image-605" title="component" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/component.jpg" alt="component" width="250" height="250" /></a></div>
<p>&nbsp;</p>
<p>高光元素定义了光线直接照射并反射到观察者从而形成了物体上的“热点”或光泽。光点的大小取决于一些因素，但是如果你看到如上图黄球所示一个区域明显的光斑，那通常就是来自于一个或多个光源的高光部分。</p>
<p>&nbsp;</p>
<p>散射元素定义了比较平均的定向光源，在物体面向光线的一面具有光泽。</p>
<p>&nbsp;</p>
<p>环境光则没有明显的光源。其光线折射与许多物体，因此无法确定其来源。环境元素平均作用于场景中的所有物体的所有面。</p>
<p>&nbsp;</p>
<h2>环境光</h2>
<p>&nbsp;</p>
<p>你的光效中有越多的环境元素，那么就越不会产生引入注目的效果。所有光线的环境元素会融合在一起产生效果，意思是场景中的总环境光效是由所有启动光源的环境光组合在一起所决定的。如果你使用了不止一个光源，那么最好是只指定一个光源的环境元素，而设定其他所有光源的环境因素为黑 (<span style="font-family: monospace;">{0.0, 0.0, 0.0, 1.0}</span>)，从而很容易地调整场景的环境光效。</p>
<p>&nbsp;</p>
<p>下面演示了怎样指定一个很暗的白色光源：</p>
<pre><span>    <span>const</span> GLfloat light0Ambient<span><span>[</span><span>]</span></span> = <span>{<span>0.05</span>, <span>0.05</span>, <span>0.05</span>, <span>1.0</span>}</span>;
<span><span>    </span><span>glLightfv</span><span>(GL_LIGHT0, GL_AMBIENT, light0Ambient)</span>;</span></span></pre>
<p>&nbsp;</p>
<p>使用像这样的很低的环境元素值使场景看上去更引入注目，但同时也意味着物体没有面向光线的面或者有其他物体挡住的物体将在场景中看得不是很清楚。</p>
<p>&nbsp;</p>
<h2>散射光</h2>
<p>&nbsp;</p>
<p>在OpenGL ES中可以设定的第二个光线元素是 <strong>散射元素（diffuse component</strong>）。在现实世界里，散射光线是诸如穿透光纤或从一堵白墙反射的光线。散射光线是发散的，因而参数较柔和的光，一般不会像直射光一样产生光斑。如果你曾经观察过职业摄影家使用摄影室灯光，你可能会看到他们使用<a href="http://en.wikipedia.org/wiki/Soft_box" target="_blank">柔光箱</a> 或者反光伞。两者都会穿透像白布之类的轻型材料并反射与轻型有色材料从而使光线发散以产生令人愉悦的照片。在OpenGL ES中，散射元素作用类似，它使光线均匀地散布到物体之上。然而，不像环境光，由于它是定向光，只有面向光线的物体面才会反射散射光，而场景中的所有多面体都会被环境光照射。</p>
<p>&nbsp;</p>
<p>下面的例子演示了设定场景中的第一个散射元素：</p>
<pre><span>    <span>const</span> GLfloat light0Diffuse<span><span>[</span><span>]</span></span> = <span>{<span>0.5</span>, <span>0.5</span>, <span>0.5</span>, <span>1.0</span>}</span>;
<span><span>    </span><span>glLightfv</span><span>(GL_LIGHT0, GL_DIFFUSE, light0Diffuse)</span>;</span></span></pre>
<p>&nbsp;</p>
<h2>高光</h2>
<p>&nbsp;</p>
<p>最后，我们讨论高光。这种类型的光是十分直接的，它们会以热点和光晕的形式反射到观察者的眼中。如果你想产生聚光灯的效果，那么应该设置一个很大的高光元素值及很小的散射和环境元素值（还需要定义其他一些参数，等下会有介绍）。</p>
<blockquote><p><strong>注意</strong>: 在下一篇文章中你将看到，光线的高光值是确定高光尺寸的唯一因素。</p></blockquote>
<p>&nbsp;</p>
<p>下面是设定高光元素的例子：</p>
<pre><span>
 <span>const</span> GLfloat light0Specular<span><span>[</span><span>]</span></span> = <span>{<span>0.7</span>, <span>0.7</span>, <span>0.7</span>, <span>1.0</span>}</span>;</span></pre>
<p>&nbsp;</p>
<h2>位置</h2>
<p>&nbsp;</p>
<p>还需要设定光效的另一个重要属性，即光源3D空间中的位置。这不会影响环境元素，但其他两个元素由于其本性，只有在OpenGL在知道了场景中物体与光的相对位置后才能计算。例如：</p>
<pre><span>    <span>const</span> GLfloat light0Position<span><span>[</span><span>]</span></span> = <span>{<span>10.0</span>, <span>10.0</span>, <span>10.0</span>, <span>0.0</span>}</span>;
<span><span>    </span><span>glLightfv</span><span>(GL_LIGHT0, GL_POSITION, light0Position)</span>;</span></span></pre>
<p>&nbsp;</p>
<p>此位置将第一个光源放置在观察者后方的右上角。</p>
<p>&nbsp;</p>
<p>这些是用于设定几乎所有光线的属性。如果你没有设定其中一个元素，那么它就采用默认值黑色 <span style="font-family: monospace;">{0.0, 0.0, 0.0, 1.0}</span>。如果你没有定义位置，那么它就处于原点，通常这不是你想要的结果。</p>
<p>&nbsp;</p>
<p>你可能想知道alpha值对光线的作用。对环境光和高光而言这是个愚蠢的问题。然而在计算散射光确定光线是怎样反射时，需要用到它。我们将在讨论材质时再解释它是怎样工作的，因为材质和光线值都将出现在方程式中。我们下次再讨论材质，现在将 alpha 设为1.0。改变其值对本文的程序不会产生任何影响，但有可能对以后的程序至少是有关散射元素的部分产生影响。</p>
<p>&nbsp;</p>
<p>还有一些光线元素你可选择使用。</p>
<p>&nbsp;</p>
<h2>创建点光源（聚光灯）</h2>
<p>&nbsp;</p>
<p>如果你希望创建一个定向点光源 (一种指向特定方向并照亮特定角度范围的光源，本质上，它与灯泡照亮各个方向相反，它只照亮一个锥台的范围)，那么你需要设定两个额外参数。设定 <span style="font-family: monospace;">GL_SPOT_DIRECTION 允许你指定光照的方向，它类似于上一篇文章中介绍的视野角度的计算。窄角度将产生很小范围的点光源，而宽角度则产生像泛光灯一样的效果。</span></p>
<p>&nbsp;</p>
<h2>指定光的方向</h2>
<p>&nbsp;</p>
<p><span style="font-family: monospace;">通过指定定义了光线指向的x，y和z值来使</span><span style="font-family: monospace;"> </span><span style="font-family: monospace;">GL_SPOT_DIRECTION的工作。然而，光线并不指向</span>你在空间中定义的那一点。你提供的三个坐标值是<strong>向量（vector）</strong>, 而非顶点。这是一个很细微但十分重要的区别。一个代表向量的数据结构与一个顶点的数据结构完全一样（都有三个 <span style="font-family: monospace;">GLfloat</span>s，其中每一个分别是笛卡尔的一个轴）。然而，向量的数据是用来表示方向而不是空间中的一点。</p>
<p>&nbsp;</p>
<p>每个人都知道两点可以定义一条线段，那么空间中的一点怎么可能指定方向？这是因为存在一个隐性的第二点作为起点，即原点。如果你从原点画一条线到向量定义的点，那就是向量代表的方向。向量还可用于表示速度和距离，一个远离原点的点表示速度越快或距离更远。在大部分的OpenGL应用中，并未使用距离原点的距离。实际上，在大部分使用向量的情况下，我们需要将向量<strong>标准化（normalize）</strong> 为长度 1.0。关于向量的标准化，随着我们继续深入将会讨论到。现在你只需知道，如果你希望定义一个定向光，那么你 必须创建一个定义了光的方向的向量。下例演示了定义一个沿z轴而下的光源：</p>
<pre><span>    <span>const</span> GLfloat light0Direction = <span>{<span>0.0</span>, <span>0.0</span>, <span>-1.0</span>}</span>;
<span><span>    </span><span>glLightfv</span><span>(GL_LIGHT0, GL_SPOT_DIRECTION, light0Direction)</span>;</span></span></pre>
<p>&nbsp;</p>
<p>现在，如果你希望光线指向一个特定物体，应该怎么办？实际上很简单，将光的位置和物体的位置传入<em>OpenGLCommon.h</em>的函数 <span style="font-family: monospace;">Vector3DMakeWithStartAndEndPoints()中，它将返回一个被光照射到的指定点的标准化向量。然后，再将其作为</span> <span style="font-family: monospace;">GL_SPOT_DIRECTION 的值。</span></p>
<p>&nbsp;</p>
<h2>指定光的角度</h2>
<p>&nbsp;</p>
<p>除非你限制光照的角度，否则指定光的方向并不会产生显著的效果。因为当你指定 <span style="font-family: monospace;">GL_SPOT_CUTOFF</span> 值时，它定义了中心线两边的角度，所以如果你指定截止角时，它必须小于180°。如果你的定义为45°，那么实际上你创建了一个总角度为90°的点光源。这意味着你可设定的 <span style="font-family: monospace;">GL_SPOT_CUTOFF 的最大值为</span> 180°。下图说明了这个概念：</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/light-field.png"><img class="alignnone size-full wp-image-606" title="light field" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/light-field.png" alt="light field" width="467" height="287" /></a></div>
<p>下例演示了怎样限制角度为 90°(使用 45° 截止角):</p>
<pre><span><span><span>     </span><span>glLightf</span><span>(GL_LIGHT0, GL_SPOT_CUTOFF, <span>45.0</span>)</span>;</span></span></pre>
<p>&nbsp;</p>
<p>还有三个光的属性可以设置。它们是配合使用的，这些超出了本文的范围。以后，我可能在有关<strong>光衰减</strong> （光线随着远离光源而减弱）的文章中讨论到。通过调整衰减值可以产生很漂亮的效果。</p>
<p>&nbsp;</p>
<h2>综合</h2>
<p>&nbsp;</p>
<p>让我们综合所学内容在<span style="font-family: monospace;">setupView:</span> 方法中设定一个光源。使用下列代码替换 <span style="font-family: monospace;">setupView:</span> 方法中原有的代码：</p>
<pre><span>-(<span>void</span>)setupView:(GLView*)view
<span>{
    <span>const</span> GLfloat zNear = <span>0.01</span>, zFar = <span>1000.0</span>, fieldOfView = <span>45.0</span>;
    GLfloat size;
<span><span>    </span><span>glEnable</span>(</span>GL_DEPTH_TEST);
<span><span>    </span><span>glMatrixMode</span>(</span>GL_PROJECTION);
    size = zNear *<span> </span><span>tanf</span>(<span><span>DEGREES_TO_RADIANS</span>(</span>fieldOfView) / <span>2.0</span>);
    <span>CGRect</span> rect = view<span>.bounds</span>;
<span><span>    </span><span>glFrustumf</span>(</span>-size, size, -size / (rect<span>.size.width</span> / rect<span>.size.height</span>), size /
               (rect<span>.size.width</span> / rect<span>.size.height</span>), zNear, zFar);
<span><span>    </span><span>glViewport</span>(</span><span>0</span>, <span>0</span>, rect<span>.size.width</span>, rect<span>.size.height</span>);
<span><span>    </span><span>glMatrixMode</span>(</span>GL_MODELVIEW);

    <span><span>//</span> Enable lighting
</span><span><span>    </span><span>glEnable</span>(</span>GL_LIGHTING);

    <span><span>//</span> Turn the first light on
</span><span><span>    </span><span>glEnable</span>(</span>GL_LIGHT0);

    <span><span>//</span> Define the ambient component of the first light
</span>    <span>const</span> GLfloat light0Ambient<span><span>[</span><span>]</span></span> = <span>{<span>0.1</span>, <span>0.1</span>, <span>0.1</span>, <span>1.0</span>}</span>;
<span><span>    </span><span>glLightfv</span>(</span>GL_LIGHT0, GL_AMBIENT, light0Ambient);

    <span><span>//</span> Define the diffuse component of the first light
</span>    <span>const</span> GLfloat light0Diffuse<span><span>[</span><span>]</span></span> = <span>{<span>0.7</span>, <span>0.7</span>, <span>0.7</span>, <span>1.0</span>}</span>;
<span><span>    </span><span>glLightfv</span>(</span>GL_LIGHT0, GL_DIFFUSE, light0Diffuse);

    <span><span>//</span> Define the specular component and shininess of the first light
</span>    <span>const</span> GLfloat light0Specular<span><span>[</span><span>]</span></span> = <span>{<span>0.7</span>, <span>0.7</span>, <span>0.7</span>, <span>1.0</span>}</span>;
    <span>const</span> GLfloat light0Shininess = <span>0.4</span>;
<span><span>    </span><span>glLightfv</span>(</span>GL_LIGHT0, GL_SPECULAR, light0Specular);
<span><span>    </span>

    <span><span>//</span> Define the position of the first light
</span>    <span>const</span> GLfloat light0Position<span><span>[</span><span>]</span></span> = <span>{<span>0.0</span>, <span>10.0</span>, <span>10.0</span>, <span>0.0</span>}</span>;
<span><span>    </span><span>glLightfv</span>(</span>GL_LIGHT0, GL_POSITION, light0Position); 

    <span><span>//</span> Define a direction vector for the light, this one points right down the Z axis
</span>    <span>const</span> GLfloat light0Direction<span><span>[</span><span>]</span></span> = <span>{<span>0.0</span>, <span>0.0</span>, -<span>1.0</span>}</span>;
<span><span>    </span><span>glLightfv</span>(</span>GL_LIGHT0, GL_SPOT_DIRECTION, light0Direction);

    <span><span>//</span> Define a cutoff angle. This defines a 90° field of vision, since the cutoff
</span>    <span><span>//</span> is number of degrees to each side of an imaginary line drawn from the light's
</span>    <span><span>//</span> position along the vector supplied in GL_SPOT_DIRECTION above
</span><span><span>    </span><span>glLightf</span>(</span>GL_LIGHT0, GL_SPOT_CUTOFF, <span>45.0</span>);

<span><span>    </span><span>glLoadIdentity</span>(</span>);
<span><span>    </span><span>glClearColor</span>(</span><span>0.0f</span>, <span>0.0f</span>, <span>0.0f</span>, <span>1.0f</span>);
}</span></span></span></pre>
<p>&nbsp;</p>
<p>很简单吧？应该一切都准备好了吧？好，我们试着运行一下看看。</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/lights_enabled.jpg"><img class="alignnone size-full wp-image-604" title="lights_enabled" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/lights_enabled.jpg" alt="lights_enabled" width="208" height="400" /></a></div>
<p>&nbsp;</p>
<p>这是什么？太糟糕了吧！我们设定了光源，可是看不到任何东西啊？屏幕上只是显示一个黑和灰的形状，它甚至看上去不像个3D形状，比以前还糟糕。</p>
<p>&nbsp;</p>
<h2>不要紧张，这很正常（注： It&#8217;s Normal在这里有两层意思，一是正常，而是法线）</h2>
<p>&nbsp;</p>
<p>Normal数学上的意思是“垂直于”。这是我们目前缺少的。法线。一个背面的法线（或多边形的法线）是一个垂直于指定多边形表面的向量（或直线）。参考下图：</p>
<p style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/normal.jpg"><img class="alignnone size-full wp-image-607" title="normal" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/normal.jpg" alt="normal" width="202" height="256" /></a></p>
<p>&nbsp;</p>
<p>OpenGL 渲染一个形状时并不需要知道法线，但在你使用定向光线时需要用到。OpenGL需要表面法线来确定光线是怎样与各多边形交互作用的。</p>
<p>&nbsp;</p>
<p>OpenGL 要求我们为各使用的顶点提供法线。计算一个三角形的表面法线是很简单的，它是三角形两边的叉积。代码如下：</p>
<pre><span><span>static</span> <span>inline</span> Vector3D<span><span> </span><span>Triangle3DCalculateSurfaceNormal</span><span>(Triangle3D triangle)</span>
<span>{
    Vector3D u =<span><span> </span><span>Vector3DMakeWithStartAndEndPoints</span>(</span>triangle<span>.v2</span>, triangle<span>.v1</span>);
    Vector3D v =<span><span> </span><span>Vector3DMakeWithStartAndEndPoints</span>(</span>triangle<span>.v3</span>, triangle<span>.v1</span>);

    Vector3D ret;
    ret<span>.x</span> = (u<span>.y</span> * v<span>.z</span>) - (u<span>.z</span> * v<span>.y</span>);
    ret<span>.y</span> = (u<span>.z</span> * v<span>.x</span>) - (u<span>.x</span> * v<span>.z</span>);
    ret<span>.z</span> = (u<span>.x</span> * v<span>.y</span>) - (u<span>.y</span> * v<span>.x</span>);
    <span>return</span> ret;
}</span></span></span></pre>
<p>&nbsp;</p>
<p><span style="font-family: monospace;">Vector3DMakeWithStartAndEndPoints()</span>取两顶点值计算其标准化向量。那么既然计算表面法线如此简单，为什么OpenGL ES不为我们完成？有两个原因，第一个和最重要的原因是，开销太大。对每个多边形而言，有许多浮点乘除计算以及调用<span style="font-family: monospace;">sqrtf()的开销。</span></p>
<p>&nbsp;</p>
<p>第二，因为我们使用 <span style="font-family: monospace;">GL_SMOOTH</span> 渲染，所以OpenGL ES需要知道<strong>顶点法线（vertex normal）</strong> 而不是表面法线（上述计算）。因为顶点法线要求你计算使用了该顶点的所有表面法线的平均向量，所以开销更大。</p>
<p>&nbsp;</p>
<p>让我们看一个例子。</p>
<p style="text-align: center;">
<p><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/vertices.png"><img class="size-full wp-image-608 aligncenter" title="vertices" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/vertices.png" alt="vertices" width="388" height="376" /></a></p>
<p>&nbsp;</p>
<p>请注意这不是一个正方体。简单起见，让我们看看一个平面的六个三角形构成的两维形状。它总共由七个顶点构成。顶点A由所有六个三角形共享，所以此顶点的顶点法线是所有七个三角形（注：六个吧？）的表面法线的平均值。平均值的计算是基于各向量元素的，即x值被平均，y值被平均，然后z值被平均，结果组合在一起构成了平均向量。</p>
<p>&nbsp;</p>
<p>所以，我们怎样计算二十面体的向量？这其实是一个很简单的形状，在计算顶点法线时并不会造成显著的延时。通常，你不会工作于这么少顶点的物体，而将处理复杂得多而且数量更多的物体。结果是，除非没有替代方法，否则你希望避免使用顶点法线的实时计算。这种情况下，我编写了一个小命令行程序，它循环处理每个顶点及三角形索引，来计算二十面体的各顶点法线。该程序将结果以C struct的形式输出到控制台，然后我复制到我的OpenGL程序中。</p>
<p>&nbsp;</p>
<blockquote><p><strong>注意：</strong>大部分3D程序都会为你提供法线的计算，但你需要小心使用 &#8211; 大部分3D文件格式存储的是表面法线而不是顶点法线，所以你至少需要计算表面法线的平均值来生成顶点法线。在以后的文章中我将介绍加载和创建3D物体，或参阅有关加载Wavefront OBJ 文件格式的<a href="http://iphonedevelopment.blogspot.com/2008/12/wavefront-obj-loader-normals-done.html" target="_blank">文章</a>。</p></blockquote>
<p>下面是我写的计算二十面体顶点法线的命令行程序：</p>
<pre><span><span>#<span>import</span> <span><span>&lt;</span>Foundation/Foundation.h<span>&gt;</span></span></span>
<span>#<span>import</span> <span><span>"</span>OpenGLCommon.h<span>"</span></span></span>

<span>int</span><span><span> </span><span>main</span> <span>(<span>int</span> argc, <span>const</span> <span>char</span> * argv<span><span>[</span><span>]</span></span>)</span> <span>{
    <span>NSAutoreleasePool</span> * pool = <span><span>[</span><span><span>[</span><span>NSAutoreleasePool</span> <span><span>alloc</span></span><span>]</span></span> <span><span>init</span></span><span>]</span></span>;

    <span>NSMutableString</span> *result = <span><span>[</span><span>NSMutableString</span> <span><span>string</span></span><span>]</span></span>;

    <span>static</span> <span>const</span> Vertex3D vertices<span><span>[</span><span>]</span></span>= <span>{
        <span>{<span>0</span>, -<span>0.525731</span>, <span>0.850651</span>}</span>,             <span><span>//</span> vertices[0]
</span>        <span>{<span>0.850651</span>, <span>0</span>, <span>0.525731</span>}</span>,              <span><span>//</span> vertices[1]
</span>        <span>{<span>0.850651</span>, <span>0</span>, -<span>0.525731</span>}</span>,             <span><span>//</span> vertices[2]
</span>        <span>{-<span>0.850651</span>, <span>0</span>, -<span>0.525731</span>}</span>,            <span><span>//</span> vertices[3]
</span>        <span>{-<span>0.850651</span>, <span>0</span>, <span>0.525731</span>}</span>,             <span><span>//</span> vertices[4]
</span>        <span>{-<span>0.525731</span>, <span>0.850651</span>, <span>0</span>}</span>,             <span><span>//</span> vertices[5]
</span>        <span>{<span>0.525731</span>, <span>0.850651</span>, <span>0</span>}</span>,              <span><span>//</span> vertices[6]
</span>        <span>{<span>0.525731</span>, -<span>0.850651</span>, <span>0</span>}</span>,             <span><span>//</span> vertices[7]
</span>        <span>{-<span>0.525731</span>, -<span>0.850651</span>, <span>0</span>}</span>,            <span><span>//</span> vertices[8]
</span>        <span>{<span>0</span>, -<span>0.525731</span>, -<span>0.850651</span>}</span>,            <span><span>//</span> vertices[9]
</span>        <span>{<span>0</span>, <span>0.525731</span>, -<span>0.850651</span>}</span>,             <span><span>//</span> vertices[10]
</span>        <span>{<span>0</span>, <span>0.525731</span>, <span>0.850651</span>}</span>               <span><span>//</span> vertices[11]
</span>    }</span>;

    <span>static</span> <span>const</span> GLubyte icosahedronFaces<span><span>[</span><span>]</span></span> = <span>{
        <span>1</span>, <span>2</span>, <span>6</span>,
        <span>1</span>, <span>7</span>, <span>2</span>,
        <span>3</span>, <span>4</span>, <span>5</span>,
        <span>4</span>, <span>3</span>, <span>8</span>,
        <span>6</span>, <span>5</span>, <span>11</span>,
        <span>5</span>, <span>6</span>, <span>10</span>,
        <span>9</span>, <span>10</span>, <span>2</span>,
        <span>10</span>, <span>9</span>, <span>3</span>,
        <span>7</span>, <span>8</span>, <span>9</span>,
        <span>8</span>, <span>7</span>, <span>0</span>,
        <span>11</span>, <span>0</span>, <span>1</span>,
        <span>0</span>, <span>11</span>, <span>4</span>,
        <span>6</span>, <span>2</span>, <span>10</span>,
        <span>1</span>, <span>6</span>, <span>11</span>,
        <span>3</span>, <span>5</span>, <span>10</span>,
        <span>5</span>, <span>4</span>, <span>11</span>,
        <span>2</span>, <span>7</span>, <span>9</span>,
        <span>7</span>, <span>1</span>, <span>0</span>,
        <span>3</span>, <span>9</span>, <span>8</span>,
        <span>4</span>, <span>8</span>, <span>0</span>,
    }</span>;

    Vector3D *surfaceNormals =<span> </span><span>calloc</span>(<span>20</span>,<span><span> </span><span>sizeof</span>(</span>Vector3D));

    <span><span>//</span> Calculate the surface normal for each triangle
</span>
    <span>for</span><span> <span>(</span></span><span>int</span> i = <span>0</span>; i &lt; <span>20</span>; i++)
    <span>{
        Vertex3D vertex1 = vertices<span><span>[</span>icosahedronFaces<span><span>[</span>(i*<span>3</span>)<span>]</span></span><span>]</span></span>;
        Vertex3D vertex2 = vertices<span><span>[</span>icosahedronFaces<span><span>[</span>(i*<span>3</span>)+<span>1</span><span>]</span></span><span>]</span></span>;
        Vertex3D vertex3 = vertices<span><span>[</span>icosahedronFaces<span><span>[</span>(i*<span>3</span>)+<span>2</span><span>]</span></span><span>]</span></span>;
        Triangle3D triangle =<span><span> </span><span>Triangle3DMake</span>(</span>vertex1, vertex2, vertex3);
        Vector3D surfaceNormal =<span><span> </span><span>Triangle3DCalculateSurfaceNormal</span>(</span>triangle);
<span><span>        </span><span>Vector3DNormalize</span>(</span>&amp;surfaceNormal);
        surfaceNormals<span><span>[</span>i<span>]</span></span> = surfaceNormal;
    }</span>

    Vertex3D *normals =<span> </span><span>calloc</span>(<span>12</span>,<span><span> </span><span>sizeof</span>(</span>Vertex3D));
    <span><span>[</span>result <span><span>appendString<span>:</span></span><span><span>@"</span>static const Vector3D normals[] = {<span>\n</span><span>"</span></span></span><span>]</span></span>;
    <span>for</span><span> <span>(</span></span><span>int</span> i = <span>0</span>; i &lt; <span>12</span>; i++)
    <span>{
        <span>int</span> faceCount = <span>0</span>;
        <span>for</span><span> <span>(</span></span><span>int</span> j = <span>0</span>; j &lt; <span>20</span>; j++)
        <span>{
            <span>BOOL</span> contains = <span>NO</span>;
            <span>for</span><span> <span>(</span></span><span>int</span> k = <span>0</span>; k &lt; <span>3</span>; k++)
            <span>{
                <span>if</span><span> <span>(</span></span>icosahedronFaces<span><span>[</span>(j * <span>3</span>) + k<span>]</span></span> == i)
                    contains = <span>YES</span>;
            }</span>
            <span>if</span><span> <span>(</span></span>contains)
            <span>{
                faceCount++;
                normals<span><span>[</span>i<span>]</span></span> =<span><span> </span><span>Vector3DAdd</span>(</span>normals<span><span>[</span>i<span>]</span></span>, surfaceNormals<span><span>[</span>j<span>]</span></span>);
            }</span>
        }</span>

        normals<span><span>[</span>i<span>]</span></span><span>.x</span> /= (GLfloat)faceCount;
        normals<span><span>[</span>i<span>]</span></span><span>.y</span> /= (GLfloat)faceCount;
        normals<span><span>[</span>i<span>]</span></span><span>.z</span> /= (GLfloat)faceCount;
        <span><span>[</span>result <span><span>appendFormat<span>:</span></span><span><span>@"</span><span>\t</span>{%f, %f, %f},<span>\n</span><span>"</span></span>, normals<span><span>[</span>i<span>]</span></span>.x, normals<span><span>[</span>i<span>]</span></span>.y, normals<span><span>[</span>i<span>]</span></span>.z</span><span>]</span></span>;
    }</span>
    <span><span>[</span>result <span><span>appendString<span>:</span></span><span><span>@"</span>};<span>\n</span><span>"</span></span></span><span>]</span></span>;
<span>    </span><span>NSLog</span>(result);
    <span><span>[</span>pool <span><span>drain</span></span><span>]</span></span>;
    <span>return</span> <span>0</span>;
}</span></span>
</span></pre>
<p>&nbsp;</p>
<p>可能有点粗糙，但它很好的完成了工作，允许我们预先计算顶点法线，所以在运行时不需要进行计算。程序输出如下：</p>
<pre>static const Vector3D normals[] = {
 {0.000000, -0.417775, 0.675974},
 {0.675973, 0.000000, 0.417775},
 {0.675973, -0.000000, -0.417775},
 {-0.675973, 0.000000, -0.417775},
 {-0.675973, -0.000000, 0.417775},
 {-0.417775, 0.675974, 0.000000},
 {0.417775, 0.675973, -0.000000},
 {0.417775, -0.675974, 0.000000},
 {-0.417775, -0.675974, 0.000000},
 {0.000000, -0.417775, -0.675973},
 {0.000000, 0.417775, -0.675974},
 {0.000000, 0.417775, 0.675973},
};</pre>
<p>&nbsp;</p>
<h2>指定顶点法线</h2>
<p>&nbsp;</p>
<p>首先我们要启动法线数组：</p>
<pre><span><span><span>    </span><span>glEnableClientState</span><span>(GL_NORMAL_ARRAY)</span>;</span></span></pre>
<p>&nbsp;</p>
<p>使用下列调用传递法线数组：</p>
<pre><span><span><span>    </span><span>glNormalPointer</span><span>(GL_FLOAT, <span>0</span>, normals)</span>;</span></span></pre>
<p>&nbsp;</p>
<p>将所有这些加到 <span style="font-family: monospace;">drawSelf:</span> 方法中：</p>
<pre><span>- (<span>void</span>)drawView:(GLView*)view;
<span>{

    <span>static</span> GLfloat rot = <span>0.0</span>;

    <span><span>//</span> This is the same result as using Vertex3D, just faster to type and
</span>    <span><span>//</span> can be made const this way
</span>    <span>static</span> <span>const</span> Vertex3D vertices<span><span>[</span><span>]</span></span>= <span>{
        <span>{<span>0</span>, -<span>0.525731</span>, <span>0.850651</span>}</span>,             <span><span>//</span> vertices[0]
</span>        <span>{<span>0.850651</span>, <span>0</span>, <span>0.525731</span>}</span>,              <span><span>//</span> vertices[1]
</span>        <span>{<span>0.850651</span>, <span>0</span>, -<span>0.525731</span>}</span>,             <span><span>//</span> vertices[2]
</span>        <span>{-<span>0.850651</span>, <span>0</span>, -<span>0.525731</span>}</span>,            <span><span>//</span> vertices[3]
</span>        <span>{-<span>0.850651</span>, <span>0</span>, <span>0.525731</span>}</span>,             <span><span>//</span> vertices[4]
</span>        <span>{-<span>0.525731</span>, <span>0.850651</span>, <span>0</span>}</span>,             <span><span>//</span> vertices[5]
</span>        <span>{<span>0.525731</span>, <span>0.850651</span>, <span>0</span>}</span>,              <span><span>//</span> vertices[6]
</span>        <span>{<span>0.525731</span>, -<span>0.850651</span>, <span>0</span>}</span>,             <span><span>//</span> vertices[7]
</span>        <span>{-<span>0.525731</span>, -<span>0.850651</span>, <span>0</span>}</span>,            <span><span>//</span> vertices[8]
</span>        <span>{<span>0</span>, -<span>0.525731</span>, -<span>0.850651</span>}</span>,            <span><span>//</span> vertices[9]
</span>        <span>{<span>0</span>, <span>0.525731</span>, -<span>0.850651</span>}</span>,             <span><span>//</span> vertices[10]
</span>        <span>{<span>0</span>, <span>0.525731</span>, <span>0.850651</span>}</span>               <span><span>//</span> vertices[11]
</span>    }</span>;

    <span>static</span> <span>const</span> Color3D colors<span><span>[</span><span>]</span></span> = <span>{
        <span>{<span>1.0</span>, <span>0.0</span>, <span>0.0</span>, <span>1.0</span>}</span>,
        <span>{<span>1.0</span>, <span>0.5</span>, <span>0.0</span>, <span>1.0</span>}</span>,
        <span>{<span>1.0</span>, <span>1.0</span>, <span>0.0</span>, <span>1.0</span>}</span>,
        <span>{<span>0.5</span>, <span>1.0</span>, <span>0.0</span>, <span>1.0</span>}</span>,
        <span>{<span>0.0</span>, <span>1.0</span>, <span>0.0</span>, <span>1.0</span>}</span>,
        <span>{<span>0.0</span>, <span>1.0</span>, <span>0.5</span>, <span>1.0</span>}</span>,
        <span>{<span>0.0</span>, <span>1.0</span>, <span>1.0</span>, <span>1.0</span>}</span>,
        <span>{<span>0.0</span>, <span>0.5</span>, <span>1.0</span>, <span>1.0</span>}</span>,
        <span>{<span>0.0</span>, <span>0.0</span>, <span>1.0</span>, <span>1.0</span>}</span>,
        <span>{<span>0.5</span>, <span>0.0</span>, <span>1.0</span>, <span>1.0</span>}</span>,
        <span>{<span>1.0</span>, <span>0.0</span>, <span>1.0</span>, <span>1.0</span>}</span>,
        <span>{<span>1.0</span>, <span>0.0</span>, <span>0.5</span>, <span>1.0</span>}</span>
    }</span>;

    <span>static</span> <span>const</span> GLubyte icosahedronFaces<span><span>[</span><span>]</span></span> = <span>{
        <span>1</span>, <span>2</span>, <span>6</span>,
        <span>1</span>, <span>7</span>, <span>2</span>,
        <span>3</span>, <span>4</span>, <span>5</span>,
        <span>4</span>, <span>3</span>, <span>8</span>,
        <span>6</span>, <span>5</span>, <span>11</span>,
        <span>5</span>, <span>6</span>, <span>10</span>,
        <span>9</span>, <span>10</span>, <span>2</span>,
        <span>10</span>, <span>9</span>, <span>3</span>,
        <span>7</span>, <span>8</span>, <span>9</span>,
        <span>8</span>, <span>7</span>, <span>0</span>,
        <span>11</span>, <span>0</span>, <span>1</span>,
        <span>0</span>, <span>11</span>, <span>4</span>,
        <span>6</span>, <span>2</span>, <span>10</span>,
        <span>1</span>, <span>6</span>, <span>11</span>,
        <span>3</span>, <span>5</span>, <span>10</span>,
        <span>5</span>, <span>4</span>, <span>11</span>,
        <span>2</span>, <span>7</span>, <span>9</span>,
        <span>7</span>, <span>1</span>, <span>0</span>,
        <span>3</span>, <span>9</span>, <span>8</span>,
        <span>4</span>, <span>8</span>, <span>0</span>,
    }</span>;

    <span>static</span> <span>const</span> Vector3D normals<span><span>[</span><span>]</span></span> = <span>{
        <span>{<span>0.000000</span>, -<span>0.417775</span>, <span>0.675974</span>}</span>,
        <span>{<span>0.675973</span>, <span>0.000000</span>, <span>0.417775</span>}</span>,
        <span>{<span>0.675973</span>, -<span>0.000000</span>, -<span>0.417775</span>}</span>,
        <span>{-<span>0.675973</span>, <span>0.000000</span>, -<span>0.417775</span>}</span>,
        <span>{-<span>0.675973</span>, -<span>0.000000</span>, <span>0.417775</span>}</span>,
        <span>{-<span>0.417775</span>, <span>0.675974</span>, <span>0.000000</span>}</span>,
        <span>{<span>0.417775</span>, <span>0.675973</span>, -<span>0.000000</span>}</span>,
        <span>{<span>0.417775</span>, -<span>0.675974</span>, <span>0.000000</span>}</span>,
        <span>{-<span>0.417775</span>, -<span>0.675974</span>, <span>0.000000</span>}</span>,
        <span>{<span>0.000000</span>, -<span>0.417775</span>, -<span>0.675973</span>}</span>,
        <span>{<span>0.000000</span>, <span>0.417775</span>, -<span>0.675974</span>}</span>,
        <span>{<span>0.000000</span>, <span>0.417775</span>, <span>0.675973</span>}</span>,
    }</span>;

<span><span>    </span><span>glLoadIdentity</span>(</span>);
<span><span>    </span><span>glTranslatef</span>(</span><span>0.0f</span>,<span>0.0f</span>,-<span>3.0f</span>);
<span><span>    </span><span>glRotatef</span>(</span>rot,<span>1.0f</span>,<span>1.0f</span>,<span>1.0f</span>);
<span><span>    </span><span>glClearColor</span>(</span><span>0.7</span>, <span>0.7</span>, <span>0.7</span>, <span>1.0</span>);
<span><span>    </span><span>glClear</span>(</span>GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
<span><span>    </span><span>glEnableClientState</span>(</span>GL_VERTEX_ARRAY);
<span><span>    </span><span>glEnableClientState</span>(</span>GL_COLOR_ARRAY);
<span><span>    </span><span>glEnableClientState</span>(</span>GL_NORMAL_ARRAY);
<span><span>    </span><span>glVertexPointer</span>(</span><span>3</span>, GL_FLOAT, <span>0</span>, vertices);
<span><span>    </span><span>glColorPointer</span>(</span><span>4</span>, GL_FLOAT, <span>0</span>, colors);
<span><span>    </span><span>glNormalPointer</span>(</span>GL_FLOAT, <span>0</span>, normals);
<span><span>    </span><span>glDrawElements</span>(</span>GL_TRIANGLES, <span>60</span>, GL_UNSIGNED_BYTE, icosahedronFaces);

<span><span>    </span><span>glDisableClientState</span>(</span>GL_VERTEX_ARRAY);
<span><span>    </span><span>glDisableClientState</span>(</span>GL_COLOR_ARRAY);
<span><span>    </span><span>glDisableClientState</span>(</span>GL_NORMAL_ARRAY);
    <span>static</span> <span>NSTimeInterval</span> lastDrawTime;
    <span>if</span><span> <span>(</span></span>lastDrawTime)
    <span>{
        <span>NSTimeInterval</span> timeSinceLastDraw = <span><span>[</span><span>NSDate</span> <span><span>timeIntervalSinceReferenceDate</span></span><span>]</span></span> - lastDrawTime;
        rot+=<span>50</span> * timeSinceLastDraw;
    }</span>
    lastDrawTime = <span><span>[</span><span>NSDate</span> <span><span>timeIntervalSinceReferenceDate</span></span><span>]</span></span>;

}</span></span></pre>
<p>&nbsp;</p>
<h2>基本完工</h2>
<p>&nbsp;</p>
<p>运行，你将看到一个真实的三维旋转物体。</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/grey3d.jpg"><img class="alignnone size-full wp-image-609" title="grey3d" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/grey3d.jpg" alt="grey3d" width="208" height="400" /></a></div>
<p>但是颜色呢？</p>
<p>&nbsp;</p>
<p>请听下回分解：OpenGL ES 材质。 当你使用光效和平滑阴影时，OpenGL期待你为多边形提供<strong>材质（material）</strong> (或纹理 （texture））。材质比在颜色数组中提供简单颜色要复杂得多。材质像光一样由许多元素构成，可以产生不同的表面效果。物体的表面效果实际上是由场景中的光和多边形的材质决定的。</p>
<p>&nbsp;</p>
<p>但是，我们不希望显示一个灰暗的二十面体。所以我介绍另一个OpenGL ES的配置参数: <span style="font-family: monospace;">GL_COLOR_MATERIAL</span>。启动它：</p>
<pre>glEnable(GL_COLOR_MATERIAL);</pre>
<p>&nbsp;</p>
<p>OpenGL 将使用我们提供的颜色数组来创建简单的材质，结果如下：</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/colorico.jpg"><img class="alignnone size-full wp-image-610" title="colorico" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/colorico.jpg" alt="colorico" width="208" height="400" /></a></div>
<p>&nbsp;</p>
<p>如果你不想输入所有代码，可下载<a href="http://gde.web.daili.my/browse.php?u=0851d20933c97a98Oi8vd3d3LmlubmVybG9vcC5iaXovY29kZS9QYXJ0NFByb2plY3RGaW5hbC56aXA%3D&amp;b=1"></a><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/Part4ProjectFinal.zip">源代码</a>。</p>
<p>&nbsp;</p>
<h5>原文见：<a href="http://iphonedevelopment.blogspot.com/2009/05/opengl-es-from-ground-up-part-4-let.html">OpenGL ES From the Ground Up, Part 4: Let There Be Light!</a></h5>
]]></content:encoded>
			<wfw:commentRss>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0opengl-es%e4%b9%8b%e5%9b%9b-%e5%85%89%e6%95%88/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>从零开始学习OpenGL ES之三 &#8211; 透视</title>
		<link>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0opengl-es%e4%b9%8b%e4%b8%89-%e9%80%8f%e8%a7%86</link>
		<comments>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0opengl-es%e4%b9%8b%e4%b8%89-%e9%80%8f%e8%a7%86#comments</comments>
		<pubDate>Fri, 01 Jan 2010 05:39:02 +0000</pubDate>
		<dc:creator>bagusflyer</dc:creator>
				<category><![CDATA[图形图像]]></category>
		<category><![CDATA[编程]]></category>
		<category><![CDATA[OpenGL ES]]></category>
		<category><![CDATA[教程]]></category>

		<guid isPermaLink="false">http://www.iphone-geek.cn/?p=580</guid>
		<description><![CDATA[现在你已经知道OpenGL是怎样绘图的了，让我们回头谈谈一个很重要的概念：OpenGL视口（viewport）。 许多人对3D编程还很陌生，那些使用过像Maya, Blender, 或 Lightwave之类3D图形程序的人都试图在OpenGL虚拟世界中找到“摄像机”。但OpenGL并不存在这样的东西。它所有的是在3D空间中定义可见的物体。虚拟世界是没有边界的，但计算机不可能处理无限的空间，所以OpenGL需要我们定义一个可以被观察者看到的空间。
如果我们从大部分3D程序具有的摄像机对象的角度出发来考虑，视口端点的中心就是摄像机。也就是观察者站的位置。它是一个观察虚拟世界的虚拟窗口。观察者可见的空间有一定限制。她看不见她身后的东西。她也看不见视角之外的东西。而且她还不能看见太远的东西。可以认为视口是通过“观察者可见”参数所确定的形状。很简单，对吗？
不幸的是，并非如此。要解释原因，我们首先需要讨论的是在OpenGL ES中具有的两种不同的视口类型：正交和透视。

正交和透视
为更好地理解，我们先看看铁路轨道，好吗？要正常工作，铁路的两条铁轨之间必须具有固定的距离。其固定的距离是由铁轨根据承载什么样的火车而决定。重要的是铁轨（以及火车的轮子）必须具有相同的距离。如果不是这样，火车根本不可能运行。
如果我们从上方观察铁轨，这个事实很明显。

但是如果你站在铁轨上向下观察会怎么样。不要说“你会被火车撞”，我假设你会足够聪明，会在没有火车开动时进行观察。

是的，铁轨看上去越远与靠近。感谢二年级美术老师，可能你已经知道这就是所谓透视（perspective）。
OpenGL可以设定的视口中的一种就是使用透视。当你这样设置视口时，物体会随着移远而越来越小，视线会在物体移离观察者时最终交汇。这是对真实视觉的模拟；人们就是以这种方式观察世界的。
另一种看设置的视口称为正交（orthogonal） 视口。这种类型的视口，视线永远不会交汇而且物体不会改变其大小。没有透视效果。对于CAD程序以及其他各种目的是十分方便的，但因为它不像人们眼睛观察的方式所以看上去是不真实的，通常也不是你所希望的。
对于正交视口，你看将摄像机置于铁轨上，但这些铁轨永远不会交汇。它们将随着远离你的视线而继续保持等距。即使你定义了一个无限大的视口（OpenGL ES并不支持），这些线仍保持等距。
正交视口的优点是容易定义。因为线永不交汇，你只需定义一个像箱子一样的3D空间，像这样：

设置正交视口
在使用glViewport()函数定义视口前，你可以通过glOrthof()通知OpenGL ES你希望使用正交视口。下面是一个简单的例子：
    CGRect rect = view.bounds;
    glOrthof(-1.0,                                    [...]]]></description>
			<content:encoded><![CDATA[<p>现在你已经知道OpenGL是怎样绘图的了，让我们回头谈谈一个很重要的概念：OpenGL<strong>视口（viewport）</strong>。 许多人对3D编程还很陌生，那些使用过像Maya, Blender, 或 Lightwave之类3D图形程序的人都试图在OpenGL虚拟世界中找到“摄像机”。但OpenGL并不存在这样的东西。它所有的是在3D空间中定义可见的物体。虚拟世界是没有边界的，但计算机不可能处理无限的空间，所以OpenGL需要我们定义一个可以被观察者看到的空间。</p>
<p>如果我们从大部分3D程序具有的摄像机对象的角度出发来考虑，视口端点的中心就是摄像机。也就是观察者站的位置。它是一个观察虚拟世界的虚拟窗口。观察者可见的空间有一定限制。她看不见她身后的东西。她也看不见视角之外的东西。而且她还不能看见太远的东西。可以认为视口是通过“观察者可见”参数所确定的形状。很简单，对吗？</p>
<p>不幸的是，并非如此。要解释原因，我们首先需要讨论的是在OpenGL ES中具有的两种不同的视口类型：正交和透视。</p>
<p><span id="more-580"></span></p>
<h2>正交和透视</h2>
<p>为更好地理解，我们先看看铁路轨道，好吗？要正常工作，铁路的两条铁轨之间必须具有固定的距离。其固定的距离是由铁轨根据承载什么样的火车而决定。重要的是铁轨（以及火车的轮子）必须具有相同的距离。如果不是这样，火车根本不可能运行。</p>
<p>如果我们从上方观察铁轨，这个事实很明显。</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/tracks.jpg"><img class="alignnone size-full wp-image-581" title="tracks" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/tracks.jpg" alt="tracks" width="400" height="196" /></a></div>
<p>但是如果你站在铁轨上向下观察会怎么样。不要说“你会被火车撞”，我假设你会足够聪明，会在没有火车开动时进行观察。</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/tracks-perspective.jpg"><img class="alignnone size-full wp-image-582" title="tracks-perspective" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/tracks-perspective.jpg" alt="tracks-perspective" width="400" height="449" /></a></div>
<p>是的，铁轨看上去越远与靠近。感谢二年级美术老师，可能你已经知道这就是所谓<strong>透视（perspective</strong>）。</p>
<p>OpenGL可以设定的视口中的一种就是使用透视。当你这样设置视口时，物体会随着移远而越来越小，视线会在物体移离观察者时最终交汇。这是对真实视觉的模拟；人们就是以这种方式观察世界的。</p>
<p>另一种看设置的视口称为<strong>正交（orthogonal）</strong> 视口。这种类型的视口，视线永远不会交汇而且物体不会改变其大小。没有透视效果。对于CAD程序以及其他各种目的是十分方便的，但因为它不像人们眼睛观察的方式所以看上去是不真实的，通常也不是你所希望的。</p>
<p>对于正交视口，你看将摄像机置于铁轨上，但这些铁轨永远不会交汇。它们将随着远离你的视线而继续保持等距。即使你定义了一个无限大的视口（OpenGL ES并不支持），这些线仍保持等距。</p>
<p>正交视口的优点是容易定义。因为线永不交汇，你只需定义一个像箱子一样的3D空间，像这样：</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/viewport.jpg"><img class="alignnone size-full wp-image-585" title="viewport" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/viewport.jpg" alt="viewport" width="543" height="338" /></a></div>
<h2>设置正交视口</h2>
<p>在使用<span style="font-family: monospace;">glViewport()函数定义视口前，你可以通过</span><span style="font-family: monospace;">glOrthof()</span><span style="font-family: monospace;">通知</span>OpenGL ES你希望使用正交视口。下面是一个简单的例子：</p>
<pre><span>    <span>CGRect</span> rect = view.bounds;
<span><span>    </span><span>glOrthof</span><span>(-<span>1.0</span>,                                          <span><span>//</span> Left
</span>              <span>1.0</span>,                                          <span><span>//</span> Right
</span>             -<span>1.0</span> / (rect.size.width / rect.size.height)</span>,   <span><span>//</span> Bottom</span>
              1.0 / <span>(rect.size.width / rect.size.height)</span>,   <span><span>//</span> Top</span>
              0.01,                                         <span><span>//</span> Near</span>
              10000.0);</span>                                     <span><span>//</span> Far
</span><span><span>    </span><span>glViewport</span><span>(<span>0</span>, <span>0</span>, rect.size.width, rect.size.height)</span>;</span></span></pre>
<p>这不难理解。首先我们获取视窗的尺寸。然后设定视口空间的宽度为两个单位，沿x轴从 -1.0 到 +1.0。很容易吧。</p>
<p>接着怎样设定底部和顶部？我们希望我们定义空间的X和Y坐标的宽高比与视窗的宽高比（也就是iPhone全屏时的宽高比）一样。由于iPhone的宽度与高度不同，我们需要确保视口的x和y坐标不同，但遵循一样的比例。</p>
<p>之后，我们定义了 <span style="font-family: monospace;">near（远）和</span> <span style="font-family: monospace;">far（近）</span> 范围来描述观察的深度。 <span style="font-family: monospace;">near</span> 参数说明了视口开始的位置。如果我们站在原点处，视口就位于我们的面前，所以习惯上使用 <span style="font-family: monospace;">.01</span> 或 <span style="font-family: monospace;">.001</span> 作为正交视口的起点。这使得视口处于原点“前方”一点点。<span style="font-family: monospace;">far</span> 可以根据你程序的需要来设定。如果你程序中的物体永远不会远过20个单位，那么你不需要将 <span style="font-family: monospace;">far设置为</span>20,000 个单位。具体的数字随程序的不同而不同。</p>
<p>调用 <span style="font-family: monospace;">glOrthof()之后，</span>我们使用视窗矩形来调用 <span style="font-family: monospace;">glViewport()。</span></p>
<p><span style="font-family: monospace;">这是比较简单的情况。<br />
</span></p>
<h2>设置透视视口</h2>
<p>另一种情况就不那么简单，这里是原因。如果物体随着远离观察者而变小，那么它和你定义的可见空间的形状有什么关系。随着视线越来越远，你可以看到更广阔的世界，所以如果你使用透视，那么你定义的空间将不是一个立方体。是的，当使用透视时可见空间的形状称为<a href="http://s2.97proxy.com:9999/browse.php?u=e4d94f27966665Oi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0ZydXN0dW0%3D&amp;b=1" target="_blank">锥台（frustum）</a>。 是的，我知道，奇怪的名字。但却是真实的。我们的锥台看上去像这样：</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/frustum.jpg"><img class="alignnone size-full wp-image-584" title="frustum" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/frustum.jpg" alt="frustum" width="499" height="467" /></a></div>
<p>请注意当我们离视口越来越远时（换句话说，当z值减小时），观察体的x和y坐标都会越来越大。</p>
<p>要设置透视视口，我们不使用 <span style="font-family: monospace;">glOrthof()，</span>我们使用一个不同的函数 <span style="font-family: monospace;">glFrustumf()</span>。此函数使用同样的六个参数。很容易理解，但我们应该怎样确定传递给 <span style="font-family: monospace;">glFrustumf()的参数</span>？</p>
<p><span style="font-family: monospace;">near</span> 和 <span style="font-family: monospace;">far</span> 容易理解。你可以同样方式理解它们。<span style="font-family: monospace;">near 使用类似 </span><span style="font-family: monospace;">.001的数值，然后根据不同程序的需要确定 </span><span style="font-family: monospace;">far</span> 值。</p>
<p>但是 <span style="font-family: monospace;">left</span>, <span style="font-family: monospace;">right</span>, <span style="font-family: monospace;">bottom</span>, 和 <span style="font-family: monospace;">top 呢？</span> 为设置这些值，我们需要一点点数学计算。</p>
<p>要计算锥台，我们首先要理解<strong>视野（field of vision</strong>）的概念，它是由两个角度定义的。让我们这样做：伸出双臂手掌合拢伸向前方。你的手臂现在指向你自己锥台的z轴，对吗？好，现在慢慢分开你的双臂。由于在你双臂展开时肩膀保持不动，你定义了一个逐渐增大的角度。这就是用于定义观察锥台的两个角度之一。它定义了视野的宽度。另一个角度的定义原理一样，只是这次你向上下展开你的双臂。如果你的双手间距只有三英寸，那么x角度将非常小。</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/narrow_field.jpg"><img class="alignnone size-full wp-image-583" title="narrow_field" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/narrow_field.jpg" alt="narrow_field" width="349" height="205" /></a></div>
<p>这称为<em>窄视野。</em></p>
<p>如果你双手分开两英尺，视野的宽度变得很大。</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/wide_field.jpg"><img class="alignnone size-full wp-image-586" title="wide_field" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/wide_field.jpg" alt="wide_field" width="424" height="336" /></a></div>
<p>这就是所谓<em> 宽视角（广角）。</em></p>
<p>如果用摄影术语描述，你可将视野当作虚拟相机的虚拟光圈的焦距。窄视野很像摄远镜头，它造就了一个缓慢增长的长锥台。宽视角就像广角镜，它造就了一个增长很快的锥台。</p>
<p>我们选择一个中间值，例如45°。 使用这个值，我们怎样计算我们的观察锥台？我们先看下两个角度中的一个。想象一下，从顶部看锥台是什么样子。下面是示意图：</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/topview.png"><img class="alignnone size-full wp-image-587" title="topview" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/topview.png" alt="topview" width="294" height="309" /></a></div>
<p>从上向下看，它就像一个砍掉一个点的三角形。但对我们而言，它已经足够接近一个三角形。你还记得三角课上的正切吗？正切函数定义为直角对边与相邻边的比率。</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/240px-TrigonometryTriangle.svg.png"><img class="alignnone size-full wp-image-588" title="240px-TrigonometryTriangle.svg" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/240px-TrigonometryTriangle.svg.png" alt="240px-TrigonometryTriangle.svg" width="240" height="180" /></a></div>
<p>但是，我们没有直角，是吗？</p>
<p>实际上，我们有两个直角… 如果我们沿z轴向下画一条直线的话：</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/split_triangle.png"><img class="alignnone size-full wp-image-589" title="split_triangle" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/split_triangle.png" alt="split_triangle" width="255" height="256" /></a></div>
<p>中心虚线就是两个直角的“相邻边”。所以，锥台远端宽度的一半就是视野角度正切的一半。如果我们将此值乘以 <span style="font-family: monospace;">near值，就可以得到 </span><span style="font-family: monospace;">right值。</span><span style="font-family: monospace;">right值取反就是 </span><span style="font-family: monospace;">left</span>。</p>
<p>我们希望视野具有与屏幕一样的长宽比，所以按照 <span style="font-family: monospace;">glOrthof()中相同的方法（将 </span><span style="font-family: monospace;">right 乘以屏幕的长宽比）</span><span style="font-family: monospace;">来</span><span style="font-family: monospace;">计算</span><span style="font-family: monospace;">top 和 bottom 值。代码如下：</span></p>
<pre><span><span>CGRect</span> rect = view.bounds;
GLfloat size = .01 *<span><span> </span><span>tanf</span><span>(DEGREES_TO_RADIANS(<span>45.0</span>)</span> / 2.0);</span> 

<span><span>glFrustumf</span><span>(-size,                                           <span><span>//</span> Left
</span>            size,                                           <span><span>//</span> Right
</span>           -size / (rect.size.width / rect.size.height)</span>,    <span><span>//</span> Bottom</span>
            size / <span>(rect.size.width / rect.size.height)</span>,    <span><span>//</span> Top</span>
            .01,                                          <span><span>//</span> Near</span>
            1000.0);</span>                                          <span><span>//</span> Far</span></span></pre>
<blockquote><p><strong>注意：</strong>关于 <span style="font-family: monospace;">glFrustum() 怎样使用传递的参数计算锥台的形状将在我们讨论矩阵时讨论。现在，我们暂且相信计算是正确的，好吗？</span></p></blockquote>
<p>让我们运用到程序中。我修改了上篇文章中最终的 <span style="font-family: monospace;">drawView:方法，我们将沿z轴向下显示了三十个二十面体。下面是新的</span><span style="font-family: monospace;"> drawView:</span> 方法：</p>
<p>- (<span>void</span>)drawView:(GLView*)view;</p>
<pre><span><span>{
    <span>static</span> GLfloat rot = <span>0.0</span>;

    <span>static</span> <span>const</span> Vertex3D vertices<span><span>[</span><span>]</span></span>= <span>{
        <span>{<span>0</span>, -<span>0.525731</span>, <span>0.850651</span>}</span>,             <span><span>//</span> vertices[0]
</span>        <span>{<span>0.850651</span>, <span>0</span>, <span>0.525731</span>}</span>,              <span><span>//</span> vertices[1]
</span>        <span>{<span>0.850651</span>, <span>0</span>, -<span>0.525731</span>}</span>,             <span><span>//</span> vertices[2]
</span>        <span>{-<span>0.850651</span>, <span>0</span>, -<span>0.525731</span>}</span>,            <span><span>//</span> vertices[3]
</span>        <span>{-<span>0.850651</span>, <span>0</span>, <span>0.525731</span>}</span>,             <span><span>//</span> vertices[4]
</span>        <span>{-<span>0.525731</span>, <span>0.850651</span>, <span>0</span>}</span>,             <span><span>//</span> vertices[5]
</span>        <span>{<span>0.525731</span>, <span>0.850651</span>, <span>0</span>}</span>,              <span><span>//</span> vertices[6]
</span>        <span>{<span>0.525731</span>, -<span>0.850651</span>, <span>0</span>}</span>,             <span><span>//</span> vertices[7]
</span>        <span>{-<span>0.525731</span>, -<span>0.850651</span>, <span>0</span>}</span>,            <span><span>//</span> vertices[8]
</span>        <span>{<span>0</span>, -<span>0.525731</span>, -<span>0.850651</span>}</span>,            <span><span>//</span> vertices[9]
</span>        <span>{<span>0</span>, <span>0.525731</span>, -<span>0.850651</span>}</span>,             <span><span>//</span> vertices[10]
</span>        <span>{<span>0</span>, <span>0.525731</span>, <span>0.850651</span>}</span>               <span><span>//</span> vertices[11]
</span>    }</span>;

    <span>static</span> <span>const</span> Color3D colors<span><span>[</span><span>]</span></span> = <span>{
        <span>{<span>1.0</span>, <span>0.0</span>, <span>0.0</span>, <span>1.0</span>}</span>,
        <span>{<span>1.0</span>, <span>0.5</span>, <span>0.0</span>, <span>1.0</span>}</span>,
        <span>{<span>1.0</span>, <span>1.0</span>, <span>0.0</span>, <span>1.0</span>}</span>,
        <span>{<span>0.5</span>, <span>1.0</span>, <span>0.0</span>, <span>1.0</span>}</span>,
        <span>{<span>0.0</span>, <span>1.0</span>, <span>0.0</span>, <span>1.0</span>}</span>,
        <span>{<span>0.0</span>, <span>1.0</span>, <span>0.5</span>, <span>1.0</span>}</span>,
        <span>{<span>0.0</span>, <span>1.0</span>, <span>1.0</span>, <span>1.0</span>}</span>,
        <span>{<span>0.0</span>, <span>0.5</span>, <span>1.0</span>, <span>1.0</span>}</span>,
        <span>{<span>0.0</span>, <span>0.0</span>, <span>1.0</span>, <span>1.0</span>}</span>,
        <span>{<span>0.5</span>, <span>0.0</span>, <span>1.0</span>, <span>1.0</span>}</span>,
        <span>{<span>1.0</span>, <span>0.0</span>, <span>1.0</span>, <span>1.0</span>}</span>,
        <span>{<span>1.0</span>, <span>0.0</span>, <span>0.5</span>, <span>1.0</span>}</span>
    }</span>;

    <span>static</span> <span>const</span> GLubyte icosahedronFaces<span><span>[</span><span>]</span></span> = <span>{
        <span>1</span>, <span>2</span>, <span>6</span>,
        <span>1</span>, <span>7</span>, <span>2</span>,
        <span>3</span>, <span>4</span>, <span>5</span>,
        <span>4</span>, <span>3</span>, <span>8</span>,
        <span>6</span>, <span>5</span>, <span>11</span>,
        <span>5</span>, <span>6</span>, <span>10</span>,
        <span>9</span>, <span>10</span>, <span>2</span>,
        <span>10</span>, <span>9</span>, <span>3</span>,
        <span>7</span>, <span>8</span>, <span>9</span>,
        <span>8</span>, <span>7</span>, <span>0</span>,
        <span>11</span>, <span>0</span>, <span>1</span>,
        <span>0</span>, <span>11</span>, <span>4</span>,
        <span>6</span>, <span>2</span>, <span>10</span>,
        <span>1</span>, <span>6</span>, <span>11</span>,
        <span>3</span>, <span>5</span>, <span>10</span>,
        <span>5</span>, <span>4</span>, <span>11</span>,
        <span>2</span>, <span>7</span>, <span>9</span>,
        <span>7</span>, <span>1</span>, <span>0</span>,
        <span>3</span>, <span>9</span>, <span>8</span>,
        <span>4</span>, <span>8</span>, <span>0</span>,
    }</span>;

<span><span>    </span><span>glLoadIdentity</span>(</span>);
<span><span>    </span><span>glClearColor</span>(</span><span>0.7</span>, <span>0.7</span>, <span>0.7</span>, <span>1.0</span>);
<span><span>    </span><span>glClear</span>(</span>GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
<span><span>    </span><span>glEnableClientState</span>(</span>GL_VERTEX_ARRAY);
<span><span>    </span><span>glEnableClientState</span>(</span>GL_COLOR_ARRAY);
<span><span>    </span><span>glVertexPointer</span>(</span><span>3</span>, GL_FLOAT, <span>0</span>, vertices);
<span><span>    </span><span>glColorPointer</span>(</span><span>4</span>, GL_FLOAT, <span>0</span>, colors);
    <span>for</span><span> <span>(</span></span><span>int</span> i = <span>1</span>; i &lt;= <span>30</span>; i++)
    <span>{
<span><span>        </span><span>glLoadIdentity</span>(</span>);
<span><span>        </span><span>glTranslatef</span>(</span><span>0.0f</span>,-<span>1.5</span>,-<span>3.0f</span> * (GLfloat)i);
<span><span>        </span><span>glRotatef</span>(</span>rot, <span>1.0</span>, <span>1.0</span>, <span>1.0</span>);
<span><span>        </span><span>glDrawElements</span>(</span>GL_TRIANGLES, <span>60</span>, GL_UNSIGNED_BYTE, icosahedronFaces);
    }</span>
<span><span>    </span><span>glDisableClientState</span>(</span>GL_VERTEX_ARRAY);
<span><span>    </span><span>glDisableClientState</span>(</span>GL_COLOR_ARRAY);
    <span>static</span> <span>NSTimeInterval</span> lastDrawTime;
    <span>if</span><span> <span>(</span></span>lastDrawTime)
    <span>{
        <span>NSTimeInterval</span> timeSinceLastDraw = <span><span>[</span><span>NSDate</span> <span><span>timeIntervalSinceReferenceDate</span></span><span>]</span></span> - lastDrawTime;
        rot+=<span>50</span> * timeSinceLastDraw;
    }</span>
    lastDrawTime = <span><span>[</span><span>NSDate</span> <span><span>timeIntervalSinceReferenceDate</span></span><span>]</span></span>;
}</span></span></pre>
<p>如果你把上述代码加入<a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/Empty_OpenGL_ES_Application.zip">OpenGL Xcode项目模板</a> 项目中（它使用<span style="font-family: monospace;">glFrustumf()设置了一个具有</span>45°视野的透视视口），你将看到下面图形：</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/frustum_simulator.jpg"><img class="alignnone size-full wp-image-590" title="frustum_simulator" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/frustum_simulator.jpg" alt="frustum_simulator" width="208" height="400" /></a></div>
<p>很好。随着几何体远离你，它们会变得越来越小，正像火车铁轨一样。</p>
<p>如果你只是将 <span style="font-family: monospace;">glFrustumf()改为</span> <span style="font-family: monospace;">glOrthof()，看上去就完全不同了：</span></p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/iPhone-SimulatorScreenSnapz0021.jpg"><img class="alignnone size-full wp-image-591" title="iPhone SimulatorScreenSnapz002" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/iPhone-SimulatorScreenSnapz0021.jpg" alt="iPhone SimulatorScreenSnapz002" width="208" height="400" /></a></div>
<p>没有透视，第一个二十面体后面的二十九个二十面体完全被第一个挡住了。因为没有透视，后面的各几何体的形状完全取决于其前方的物体。</p>
<p>好了，这是一个很沉闷的主题，事实上你现在可以完全忘却三角课学的知识了。只要复制基于视野角度计算锥台的两行代码就好，而且你可能再也不需要记住它的原理了。</p>
<h2>继续下一次激动人心的冒险旅程…</h2>
<p>下一篇文章，我们将为二十面体增加光效，使它看上去更真实。</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/lights.jpg"><img class="alignnone size-full wp-image-592" title="lights" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/lights.jpg" alt="lights" width="208" height="400" /></a></div>
]]></content:encoded>
			<wfw:commentRss>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0opengl-es%e4%b9%8b%e4%b8%89-%e9%80%8f%e8%a7%86/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>从零开始学习OpenGL ES之二 &#8211; 简单绘图概述</title>
		<link>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0opengl-es%e4%b9%8b%e4%ba%8c-%e7%ae%80%e5%8d%95%e7%bb%98%e5%9b%be%e6%a6%82%e8%bf%b0</link>
		<comments>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0opengl-es%e4%b9%8b%e4%ba%8c-%e7%ae%80%e5%8d%95%e7%bb%98%e5%9b%be%e6%a6%82%e8%bf%b0#comments</comments>
		<pubDate>Thu, 31 Dec 2009 06:11:36 +0000</pubDate>
		<dc:creator>bagusflyer</dc:creator>
				<category><![CDATA[图形图像]]></category>
		<category><![CDATA[编程]]></category>
		<category><![CDATA[OpenGL ES]]></category>
		<category><![CDATA[教程]]></category>

		<guid isPermaLink="false">http://www.iphone-geek.cn/?p=570</guid>
		<description><![CDATA[还有许多理论知识需要讨论，但与其花许多时间在复杂的数学公式或难以理解的概念上，还不如让我们开始熟悉OpenGL ES的基本绘图功能。

请下载OpenGL Xcode项目模板。我们使用此模板而不是Apple提供的模板。你可以解压到下面目录来安装它：
/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Project Templates/Application/
此模板用于全屏OpenGL程序，它具有一个OpenGL视图以及相应的视图控制器。 大部分时候你不需要动到此视图。此视图用于处理一些诸如缓存切换之类的事物，但在两处调用了其控制器类。
首先，当设定视图时，调用了一次控制器。调用视图控制器的 setupView: 方法使控制器有机会增加所需的设定工作。这里是你设定视口，添加光源以及进行其他项目相关设定的地方。现在我们将忽略此方法。此方法中已经有非常基本的设定以允许你进行简单地绘图。
控制器的 drawView: 方法根据常数kRenderingFrequency的值定期地被调用。kRenderingFrequency的初始值为15.0，表示  drawView: 方法每秒钟被调用15次。如果你希望改变渲染的频率，你可以在ConstantsAndMacros.h中找到此常数的定义。
首先加入下列代码到GLViewController.m的drawView: 方法中：
- (void)drawView:(GLView*)view;
{
    Vertex3D    vertex1 = Vertex3DMake(0.0, 1.0, -3.0);
    Vertex3D    vertex2 = Vertex3DMake(1.0, 0.0, -3.0);
    Vertex3D    vertex3 = Vertex3DMake(-1.0, 0.0, -3.0);
    Triangle3D  triangle [...]]]></description>
			<content:encoded><![CDATA[<p>还有许多理论知识需要讨论，但与其花许多时间在复杂的数学公式或难以理解的概念上，还不如让我们开始熟悉OpenGL ES的基本绘图功能。</p>
<p><span id="more-570"></span><br />
请下载<a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/Empty%20OpenGL_ES_Application.zip">OpenGL Xcode项目模板</a>。我们使用此模板而不是Apple提供的模板。你可以解压到下面目录来安装它：</p>
<pre>/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Project Templates/Application/</pre>
<p>此模板用于全屏OpenGL程序，它具有一个OpenGL视图以及相应的视图控制器。 大部分时候你不需要动到此视图。此视图用于处理一些诸如缓存切换之类的事物，但在两处调用了其控制器类。</p>
<p>首先，当设定视图时，调用了一次控制器。调用视图控制器的 <span style="font-family: monospace;">setupView:</span> 方法使控制器有机会增加所需的设定工作。这里是你设定视口，添加光源以及进行其他项目相关设定的地方。现在我们将忽略此方法。此方法中已经有非常基本的设定以允许你进行简单地绘图。</p>
<p>控制器的 <span style="font-family: monospace;">drawView:</span> 方法根据常数<span style="font-family: monospace;">kRenderingFrequency的值</span>定期地被调用。<span style="font-family: monospace;">kRenderingFrequency的初始值为</span><span style="font-family: monospace;">15.0</span>，表示  <span style="font-family: monospace;">drawView:</span> 方法每秒钟被调用15次。如果你希望改变渲染的频率，你可以在<em>ConstantsAndMacros.h</em>中找到此常数的定义。</p>
<p>首先加入下列代码到<em>GLViewController.m</em>的<span style="font-family: monospace;">drawView:</span> 方法中：</p>
<pre><span>- (<span>void</span>)drawView:(GLView*)view;
<span>{
    Vertex3D    vertex1 =<span><span> </span><span>Vertex3DMake</span>(</span><span>0.0</span>, <span>1.0</span>, -<span>3.0</span>);
    Vertex3D    vertex2 =<span><span> </span><span>Vertex3DMake</span>(</span><span>1.0</span>, <span>0.0</span>, -<span>3.0</span>);
    Vertex3D    vertex3 =<span><span> </span><span>Vertex3DMake</span>(</span>-<span>1.0</span>, <span>0.0</span>, -<span>3.0</span>);
    Triangle3D  triangle =<span><span> </span><span>Triangle3DMake</span>(</span>vertex1, vertex2, vertex3);

<span><span>    </span><span>glLoadIdentity</span>(</span>);
<span><span>    </span><span>glClearColor</span>(</span><span>0.7</span>, <span>0.7</span>, <span>0.7</span>, <span>1.0</span>);
<span><span>    </span><span>glClear</span>(</span>GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
<span><span>    </span><span>glEnableClientState</span>(</span>GL_VERTEX_ARRAY);
<span><span>    </span><span>glColor4f</span>(</span><span>1.0</span>, <span>0.0</span>, <span>0.0</span>, <span>1.0</span>);
<span><span>    </span><span>glVertexPointer</span>(</span><span>3</span>, GL_FLOAT, <span>0</span>, &amp;triangle);
<span><span>    </span><span>glDrawArrays</span>(</span>GL_TRIANGLES, <span>0</span>, <span>9</span>);
<span><span>    </span><span>glDisableClientState</span>(</span>GL_VERTEX_ARRAY);
}</span></span></pre>
<p>在讨论我们到底做了什么之前，先运行一下，你应该看到以下画面：</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/iPhone-SimulatorScreenSnapz001.jpg"><img class="alignnone size-full wp-image-571" title="iPhone SimulatorScreenSnapz001" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/iPhone-SimulatorScreenSnapz001.jpg" alt="iPhone SimulatorScreenSnapz001" width="208" height="400" /></a></div>
<p>这是个简单的方法；如果你试过了，你可能已经知道我们到底做了什么，但这里我们还是一起过一遍。因为我们的任务是画三角形，所以需要三个顶点，因此我们创建三个<a href="编程/从零开始学习opengl-es之一-基本概念">上一篇文章中讨论过的</a><span style="font-family: monospace;">Vertex3D对象：</span></p>
<pre><span>    Vertex3D    vertex1 = Vertex3DMake(<span>0.0</span>, <span>1.0</span>, -<span>3.0</span>);
    Vertex3D    vertex2 = Vertex3DMake(<span>1.0</span>, <span>0.0</span>, -<span>3.0</span>);
    Vertex3D    vertex3 = Vertex3DMake(-<span>1.0</span>, <span>0.0</span>, -<span>3.0</span>);</span></pre>
<p>你应该注意到了三个顶点的z值是一样的，其值(-3.0)是处于原点 “之后”的。因为我们还没有做任何改变，所以我们是站在原点上观察虚拟世界的，这是默认的起点位置。将三角形放置在z值为-3处，可以保证我们可以在屏幕上看到它。</p>
<p>随后，我们创建一个由这三个顶点构成的三角形。</p>
<pre><span>    Triangle3D  triangle = Triangle3DMake(vertex1, vertex2, vertex3);</span></pre>
<p>这些代码很容易理解，对吗？但是，在幕后，电脑是将其视为一个包含9个<span style="font-family: monospace;"> GLfloat</span> 的数组。如下：</p>
<pre><span>    GLfloat  triangle<span><span>[</span><span>]</span></span> = <span>{<span>0.0</span>, <span>1.0</span>, -<span>3.0</span>, <span>1.0</span>, <span>0.0</span>, -<span>3.0</span>, -<span>1.0</span>, <span>0.0</span>, -<span>3.0</span>}</span>;</span></pre>
<p>并不是完全相同 &#8211; 这里有一个很小但很重要的区别。在我们的示例中，我们传递给OpenGL的是 <span style="font-family: monospace;">Triangle3D 对象的地址（即 </span><span style="font-family: monospace;">&amp;triangle ），但在第二个使用数组的示例中，由于C数组是指针，我们传递的是数组。现在不需要考虑太多，因为这将是最后一次我用这种方式（第二种方法）定义</span>一个 <span style="font-family: monospace;">Triangle3D 对象。等一下我将解释原因，但现在让我们</span>先过一遍代码。下一步我们做的是加载单位矩阵。我将花至少一整篇文章讨论变换矩阵。我们暂且将其视为OpenGL的“复位开关”。它将清除虚拟世界中的一切旋转，移动或其他变化并将观察者置于原点。</p>
<pre><span><span><span>    </span><span>glLoadIdentity</span><span>()</span>;</span></span></pre>
<p>之后，我们告诉OpenGL所有的绘制工作是在一个灰色背景上进行的。OpenGL通常需要用四个钳位值来定义颜色。上一篇文章中有提过，钳位浮点数是0.0 到 1.0之间的浮点数。我们通过定义红，绿，蓝以及alpha元素来定义颜色， alpha值定义了颜色之后物体的透视程度。现在暂时不用管它，将其设为1.0，代表完全不透明。</p>
<pre><span><span><span>    </span><span>glClearColor</span><span>(<span>0.7</span>, <span>0.7</span>, <span>0.7</span>, <span>1.0</span>)</span>;</span>
<span><span>    </span><span>glClear</span><span>(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)</span>;</span></span></pre>
<p>在OpenGL中要定义白色，我们需要将四个元素全部设为1.0。要定义不透明的黑色，则定义红，绿，蓝为0.0，alpha为1.0。上例代码的第二行是通知OpenGL清除以前的一切图形并将其设为clear颜色。</p>
<p>你可能想知道 <span style="font-family: monospace;">glClear()的两个参数是什么意思。简单地说，它们是存储与位域中的常量。</span>OpenGL 保存了一系列 <strong>缓存（buffers）</strong>，即用于绘图各方面的内存块。将这两个值进行逻辑或是通知OpenGL清除两个不同的缓存 &#8211; <strong>颜色缓存（color buffer）</strong> 和 <strong>深度缓存（depth buffer）</strong>。 颜色缓存保存当前帧各像素的颜色。基本上就是你在屏幕上看到的。深度缓存（有时也称为“z-buffer”）保存每个潜在像素离观察者距离的信息。使用此信息可以确定一个像素是否需要被绘制出来。这两个缓存是OpenGL中最常见的缓存。还有其他一些缓存，如模板缓存（stencil buffer）和累积缓存（accumulation buffer），但现在我们暂时不讨论这些。我们现在只需记住在绘制一帧之前，必须清除这两个缓存以保证不会和以前的内容混杂。</p>
<p>然后，我们要启动OpenGL的一项称为<strong>vertex arrays</strong>（顶点数组）的特性。此特性可能只需要<span style="font-family: monospace;">setupView:方法中启动一次，但作为基本准则，我喜欢启动和禁止我使用的功能。你永远也不会知道是否另一段代码会做不同处理。如果你打开你需要的功能然后关闭它，产生问题的几率将大为减小。就本例来说，如果另一个类不使用顶点数组而使用顶点缓存的话，任何一段代码遗留了启动了的特性或没有显性启动其需要的特性，这一段或两段代码都会导致不可预知的结果。</span></p>
<pre><span><span><span>    </span><span>glEnableClientState</span><span>(GL_VERTEX_ARRAY)</span>;</span></span></pre>
<p>接下来我们设置了绘图时所需的颜色。此行代码将绘图颜色设为鲜艳的红色。</p>
<pre><span><span><span>    </span><span>glColor4f</span><span>(<span>1.0</span>, <span>0.0</span>, <span>0.0</span>, <span>1.0</span>)</span>;</span></span></pre>
<p>现在，直到下次调用 <span style="font-family: monospace;">glColor4f()前所有的图形都是以红色绘制。有一些例外的情况，例如绘制纹理形状时，但基本上，这样设定颜色可以使颜色保持。</span></p>
<p>由于我们使用顶点数组，我们必须通知OpenGL顶点的数组在什么地方。记住，顶点数组只是一个<span style="font-family: monospace;">GLfloat</span>的C数组，每三个值代表一个顶点。我们创建了<span style="font-family: monospace;">Triangle3D</span> 对象，但在内存中，它完全等同于9个连续的<span style="font-family: monospace;">GLfloat</span>， 所以我们可以传递此三角形对象的地址。</p>
<pre><span><span><span>    </span><span>glVertexPointer</span><span>(<span>3</span>, GL_FLOAT, <span>0</span>, &amp;triangle)</span>;</span></span></pre>
<p><span style="font-family: monospace;">glVertexPointer()</span> 的第一个参数指示了多少个<span style="font-family: monospace;">GLfloat代表一个顶点。</span>根据你是在进行二维或三维绘图，你可以传递2或者3。尽管我们的物体是存在于一个平面的，我们仍然将其绘制在三维虚拟世界中，因此每个顶点用三个数值表示，所以我们传递3给函数。然后，我们传递一个枚举值告诉OpenGL顶点是由<span style="font-family: monospace;">GLfloat构成。OpenGL ES允许你在当地数组中使用大部分的数据类型，但除</span><span style="font-family: monospace;">GL_FLOAT外，其他都很少见。下一个参数</span>&#8230; 现在不需要考虑下一个参数。那是以后讨论的主题。现在，它始终为0。在以后的文章中，我将讨论怎样使用此参数将同一对象以不同的数据类型混杂在一个数据结构中。</p>
<p>随后，我们通知OpenGL通过刚才提交的顶点数组来绘制三角形。</p>
<pre><span><span><span>    </span><span>glDrawArrays</span><span>(GL_TRIANGLES, <span>0</span>, <span>9</span>)</span>;</span></span></pre>
<p>你可能已经可以猜到，第一个枚举值是告诉OpenGL绘制什么。尽管OpenGL ES不支持绘制三角形之外的四边形或其他多边形，但它仍然支持一些其他绘图模式，如绘制点，线，线回路，三角形条和三角形扇。稍后我们将讨论这些绘图模式。</p>
<p>最后，我们要禁止先前启动了的特性以保证不会被其他地方的代码弄混。本例中没有其他的代码了，但通常你可以使用OpenGL绘制多个物体。</p>
<pre><span><span><span>    </span><span>glDisableClientState</span><span>(GL_VERTEX_ARRAY)</span>;</span></span></pre>
<p>好了，我们的代码可以工作了尽管它不是那么引人入胜而且不是十分高效，但它确确实实可以工作。每秒钟我们的代码被调用数次。不相信？加入下列黑体代码再次运行：</p>
<pre><span>- (<span>void</span>)drawView:(GLView*)view;
<span>{
    <span><strong>static</strong></span><strong>      GLfloat rotation = <span>0.0</span>;</strong>

    Vertex3D    vertex1 =<span><span> </span><span>Vertex3DMake</span>(</span><span>0.0</span>, <span>1.0</span>, -<span>3.0</span>);
    Vertex3D    vertex2 =<span><span> </span><span>Vertex3DMake</span>(</span><span>1.0</span>, <span>0.0</span>, -<span>3.0</span>);
    Vertex3D    vertex3 =<span><span> </span><span>Vertex3DMake</span>(</span>-<span>1.0</span>, <span>0.0</span>, -<span>3.0</span>);
    Triangle3D  triangle =<span><span> </span><span>Triangle3DMake</span>(</span>vertex1, vertex2, vertex3);

<span><span>    </span><span>glLoadIdentity</span>(</span>);
<span><span>    </span><span><strong>glRotatef</strong></span><strong>(</strong></span><strong>rotation, <span>0.0</span>, <span>0.0</span>, <span>1.0</span>);</strong>
<span><span>    </span><span>glClearColor</span>(</span><span>0.7</span>, <span>0.7</span>, <span>0.7</span>, <span>1.0</span>);
<span><span>    </span><span>glClear</span>(</span>GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
<span><span>    </span><span>glEnableClientState</span>(</span>GL_VERTEX_ARRAY);
<span><span>    </span><span>glColor4f</span>(</span><span>1.0</span>, <span>0.0</span>, <span>0.0</span>, <span>1.0</span>);
<span><span>    </span><span>glVertexPointer</span>(</span><span>3</span>, GL_FLOAT, <span>0</span>, &amp;triangle);
<span><span>    </span><span>glDrawArrays</span>(</span>GL_TRIANGLES, <span>0</span>, <span>9</span>);
<span><span>    </span><span>glDisableClientState</span>(</span>GL_VERTEX_ARRAY);

    <strong>rotation+= <span>0.5</span>;</strong>
}</span></span></pre>
<p>当你再次运行时，三角形将沿着原点缓缓转动。先不需要关注太多旋转的逻辑。我只是想告诉你我们的代码每秒钟被调用了多次。</p>
<p>如果你想画正方形怎么办？OpenGL ES并不支持正方形，所以我们只能通过三角形来定义正方形。这很简单 &#8211; 一个正方形可以通过两个三角形构成。我们要怎样调整上叙代码来绘制两个三角形？是不是可以创建两个<span style="font-family: monospace;">Triangle3D？是的，你可以这样做，但那没有效率。我们最好将两个三角形置入同一个顶点数组中。我们可以通过定义一个包含两个</span><span style="font-family: monospace;">Triangle3D对象的数组，或分配大小等于两个</span><span style="font-family: monospace;">Triangle3D</span>对象或18个<span style="font-family: monospace;">GLfloat</span>的内存.</p>
<p>这是一种方法:</p>
<pre><span>- (<span>void</span>)drawView:(GLView*)view;
<span>{
    Triangle3D  triangle<span><span>[</span><span>2</span><span>]</span></span>;
    triangle<span><span>[</span><span>0</span><span>]</span></span><span>.v1</span> =<span><span> </span><span>Vertex3DMake</span>(</span><span>0.0</span>, <span>1.0</span>, -<span>3.0</span>);
    triangle<span><span>[</span><span>0</span><span>]</span></span><span>.v2</span> =<span><span> </span><span>Vertex3DMake</span>(</span><span>1.0</span>, <span>0.0</span>, -<span>3.0</span>);
    triangle<span><span>[</span><span>0</span><span>]</span></span><span>.v3</span> =<span><span> </span><span>Vertex3DMake</span>(</span>-<span>1.0</span>, <span>0.0</span>, -<span>3.0</span>);
    triangle<span><span>[</span><span>1</span><span>]</span></span><span>.v1</span> =<span><span> </span><span>Vertex3DMake</span>(</span>-<span>1.0</span>, <span>0.0</span>, -<span>3.0</span>);
    triangle<span><span>[</span><span>1</span><span>]</span></span><span>.v2</span> =<span><span> </span><span>Vertex3DMake</span>(</span><span>1.0</span>, <span>0.0</span>, -<span>3.0</span>);
    triangle<span><span>[</span><span>1</span><span>]</span></span><span>.v3</span> =<span><span> </span><span>Vertex3DMake</span>(</span><span>0.0</span>, -<span>1.0</span>, -<span>3.0</span>);

<span><span>    </span><span>glLoadIdentity</span>(</span>);
<span><span>    </span><span>glClearColor</span>(</span><span>0.7</span>, <span>0.7</span>, <span>0.7</span>, <span>1.0</span>);
<span><span>    </span><span>glClear</span>(</span>GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
<span><span>    </span><span>glEnableClientState</span>(</span>GL_VERTEX_ARRAY);
<span><span>    </span><span>glColor4f</span>(</span><span>1.0</span>, <span>0.0</span>, <span>0.0</span>, <span>1.0</span>);
<span><span>    </span><span>glVertexPointer</span>(</span><span>3</span>, GL_FLOAT, <span>0</span>, &amp;triangle);
<span><span>    </span><span>glDrawArrays</span>(</span>GL_TRIANGLES, <span>0</span>, <span>18</span>);
<span><span>    </span><span>glDisableClientState</span>(</span>GL_VERTEX_ARRAY);
}</span></span></pre>
<p>运行，你将看到如下屏幕：</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/iPhone-SimulatorScreenSnapz002.jpg"><img class="alignnone size-full wp-image-572" title="iPhone SimulatorScreenSnapz002" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/iPhone-SimulatorScreenSnapz002.jpg" alt="iPhone SimulatorScreenSnapz002" width="208" height="400" /></a></div>
<p>由于<span style="font-family: monospace;">Vertex3DMake()方法在堆中创建新</span><span style="font-family: monospace;">的Vertex3D然后复制其值到数组中而造成额外的内存被占用，因此上述代码并不理想。</span></p>
<p>对于这样简单的情况是没有什么问题的，但是在一个更为复杂的情况下，如果定义的3D物体很大时，那么你不会希望将其分配在堆中而且你不会希望对一个顶点不止一次地进行内存分配，所以最好是养成习惯通过我们的老朋友 <span style="font-family: monospace;">malloc()</span> （我更喜欢用 <span style="font-family: monospace;">calloc()</span> ，因为它会将所有值设为0，比较易于查找错误）将顶点分配在栈中。 首先我们需要一个函数设定现存顶点的值而不是像<span style="font-family: monospace;">Vertex3DMake()一样创建一个新对象。如下：</span></p>
<pre><span><span>static</span> <span>inline</span> <span>void</span><span><span> </span><span>Vertex3DSet</span><span>(Vertex3D *vertex, <span>CGFloat</span> inX, <span>CGFloat</span> inY, <span>CGFloat</span> inZ)</span>
<span>{
    vertex-&gt;x = inX;
    vertex-&gt;y = inY;
    vertex-&gt;z = inZ;
}</span></span></span></pre>
<p>现在，我们使用新方法将两个三角形分配在栈中，重写代码：</p>
<pre><span>- (<span>void</span>)drawView:(GLView*)view;
<span>{
    Triangle3D  *triangles =<span> </span><span>malloc</span>(<span>sizeof</span>(Triangle3D) * <span>2</span>);

<span><span>    </span><span>Vertex3DSet</span>(</span>&amp;triangles<span><span>[</span><span>0</span><span>]</span></span><span>.v1</span>, <span>0.0</span>, <span>1.0</span>, -<span>3.0</span>);
<span><span>    </span><span>Vertex3DSet</span>(</span>&amp;triangles<span><span>[</span><span>0</span><span>]</span></span><span>.v2</span>, <span>1.0</span>, <span>0.0</span>, -<span>3.0</span>);
<span><span>    </span><span>Vertex3DSet</span>(</span>&amp;triangles<span><span>[</span><span>0</span><span>]</span></span><span>.v3</span>, -<span>1.0</span>, <span>0.0</span>, -<span>3.0</span>);
<span><span>    </span><span>Vertex3DSet</span>(</span>&amp;triangles<span><span>[</span><span>1</span><span>]</span></span><span>.v1</span>, -<span>1.0</span>, <span>0.0</span>, -<span>3.0</span>);
<span><span>    </span><span>Vertex3DSet</span>(</span>&amp;triangles<span><span>[</span><span>1</span><span>]</span></span><span>.v2</span>, <span>1.0</span>, <span>0.0</span>, -<span>3.0</span>);
<span><span>    </span><span>Vertex3DSet</span>(</span>&amp;triangles<span><span>[</span><span>1</span><span>]</span></span><span>.v3</span>, <span>0.0</span>, -<span>1.0</span>, -<span>3.0</span>);

<span><span>    </span><span>glLoadIdentity</span>(</span>);
<span><span>    </span><span>glClearColor</span>(</span><span>0.7</span>, <span>0.7</span>, <span>0.7</span>, <span>1.0</span>);
<span><span>    </span><span>glClear</span>(</span>GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
<span><span>    </span><span>glEnableClientState</span>(</span>GL_VERTEX_ARRAY);
<span><span>    </span><span>glColor4f</span>(</span><span>1.0</span>, <span>0.0</span>, <span>0.0</span>, <span>1.0</span>);
<span><span>    </span><span>glVertexPointer</span>(</span><span>3</span>, GL_FLOAT, <span>0</span>, triangles);
<span><span>    </span><span>glDrawArrays</span>(</span>GL_TRIANGLES, <span>0</span>, <span>18</span>);
<span><span>    </span><span>glDisableClientState</span>(</span>GL_VERTEX_ARRAY);

    <span>if</span><span> <span>(</span></span>triangles != <span>NULL</span>)
<span>        </span><span>free</span>(triangles);
}</span></span></pre>
<p style="text-align: left;">
<p>好了，我们已经讨论了许多基础知识，我们现在更深入一点。记住我说过OpenGL ES不止一种绘图方式吗？现在正方形需要6个顶点(18个 <span style="font-family: monospace;">GLfloat</span>)，实际上我们可以使用 <strong>triangle strips</strong> (<span style="font-family: monospace;">GL_TRIANGLE_STRIP</span>)方法通过四个顶点(12个 <span style="font-family: monospace;">GLfloat</span>)来绘制正方形。</p>
<p>这里是三角形条的基本概念：第一个三角形条是由前三个顶点构成(索引0, 1, 2)。第二个三角形条是由前一个三角形的两个顶点加上数组中的下一个顶点构成，继续直到整个数组结束。看下图更清楚 &#8211; 第一个三角形由顶点 1, 2, 3构成，下一个三角形由顶点 2, 3, 4构成，等等：</p>
<p style="text-align: center;">
<p><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/triangle_strip.png"><img class="size-full wp-image-573 aligncenter" title="triangle_strip" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/triangle_strip.png" alt="triangle_strip" width="400" height="135" /></a></p>
<p style="text-align: left;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/triangle_strip.png"></a></p>
<p>所以，我们的正方形是这样构成的：</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/triangle_strip_square.png"><img class="alignnone size-full wp-image-574" title="triangle_strip_square" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/triangle_strip_square.png" alt="triangle_strip_square" width="150" height="175" /></a></div>
<p>代码如下：</p>
<pre><span>- (<span>void</span>)drawView:(GLView*)view;
<span>{
    Vertex3D  *vertices =<span> </span><span>malloc</span>(<span>sizeof</span>(Vertex3D) * <span>4</span>);

<span><span>    </span><span>Vertex3DSet</span>(</span>&amp;vertices<span><span>[</span><span>0</span><span>]</span></span>, <span>0.0</span>, <span>1.0</span>, -<span>3.0</span>);
<span><span>    </span><span>Vertex3DSet</span>(</span>&amp;vertices<span><span>[</span><span>1</span><span>]</span></span>, <span>1.0</span>, <span>0.0</span>, -<span>3.0</span>);
<span><span>    </span><span>Vertex3DSet</span>(</span>&amp;vertices<span><span>[</span><span>2</span><span>]</span></span>, -<span>1.0</span>, <span>0.0</span>, -<span>3.0</span>);
<span><span>    </span><span>Vertex3DSet</span>(</span>&amp;vertices<span><span>[</span><span>3</span><span>]</span></span>, <span>0.0</span>, -<span>1.0</span>, -<span>3.0</span>);

<span><span>    </span><span>glLoadIdentity</span>(</span>);

<span><span>    </span><span>glClearColor</span>(</span><span>0.7</span>, <span>0.7</span>, <span>0.7</span>, <span>1.0</span>);
<span><span>    </span><span>glClear</span>(</span>GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
<span><span>    </span><span>glEnableClientState</span>(</span>GL_VERTEX_ARRAY);
<span><span>    </span><span>glColor4f</span>(</span><span>1.0</span>, <span>0.0</span>, <span>0.0</span>, <span>1.0</span>);
<span><span>    </span><span>glVertexPointer</span>(</span><span>3</span>, GL_FLOAT, <span>0</span>, vertices);
<span><span>    </span><span>glDrawArrays</span>(</span>GL_TRIANGLE_STRIP, <span>0</span>, <span>12</span>);
<span><span>    </span><span>glDisableClientState</span>(</span>GL_VERTEX_ARRAY);

    <span>if</span><span> <span>(</span></span>vertices != <span>NULL</span>)
<span>        </span><span>free</span>(vertices);
}</span></span></pre>
<p>我们再返回到第一段代码看看。记住我们是怎样绘制第一个三角形吗？我们使用<span style="font-family: monospace;">glColor4f()设置颜色并且说设置的颜色一直适用于随后的代码。那意味着定义于顶点数组中的物体必须用同一种颜色绘制。很有局限性，对吗？</span></p>
<p>并非如此。正如 OpenGL ES 允许你将所有顶点置于一个数组中，它还允许你将每个顶点使用的颜色置于一个<strong>颜色数组（color array）</strong>中。如果你选择使用颜色数组，那么你需要为每个顶点设置颜色（四个 <span style="font-family: monospace;">GLfloat</span>值） 。 通过下面方法启动颜色数组：</p>
<pre>glEnableClientState(GL_COLOR_ARRAY);</pre>
<p>我们可以象顶点数组一样定义一个包含四个<span style="font-family: monospace;">GLfloat成员的</span> <span style="font-family: monospace;">Color3D结构。下面是怎样为原始三角形分配不同颜色的示例：</span></p>
<pre><span>- (<span>void</span>)drawView:(GLView*)view;
<span>{
    Vertex3D    vertex1 =<span><span> </span><span>Vertex3DMake</span>(</span><span>0.0</span>, <span>1.0</span>, -<span>3.0</span>);
    Vertex3D    vertex2 =<span><span> </span><span>Vertex3DMake</span>(</span><span>1.0</span>, <span>0.0</span>, -<span>3.0</span>);
    Vertex3D    vertex3 =<span><span> </span><span>Vertex3DMake</span>(</span>-<span>1.0</span>, <span>0.0</span>, -<span>3.0</span>);
    Triangle3D  triangle =<span><span> </span><span>Triangle3DMake</span>(</span>vertex1, vertex2, vertex3);

    Color3D     *colors =<span> </span><span>malloc</span>(<span>sizeof</span>(Color3D) * <span>3</span>);
<span><span>    </span><span>Color3DSet</span>(</span>&amp;colors<span><span>[</span><span>0</span><span>]</span></span>, <span>1.0</span>, <span>0.0</span>, <span>0.0</span>, <span>1.0</span>);
<span><span>    </span><span>Color3DSet</span>(</span>&amp;colors<span><span>[</span><span>1</span><span>]</span></span>, <span>0.0</span>, <span>1.0</span>, <span>0.0</span>, <span>1.0</span>);
<span><span>    </span><span>Color3DSet</span>(</span>&amp;colors<span><span>[</span><span>2</span><span>]</span></span>, <span>0.0</span>, <span>0.0</span>, <span>1.0</span>, <span>1.0</span>);

<span><span>    </span><span>glLoadIdentity</span>(</span>);
<span><span>    </span><span>glClearColor</span>(</span><span>0.7</span>, <span>0.7</span>, <span>0.7</span>, <span>1.0</span>);
<span><span>    </span><span>glClear</span>(</span>GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
<span><span>    </span><span>glEnableClientState</span>(</span>GL_VERTEX_ARRAY);
<span><span>    </span><span>glEnableClientState</span>(</span>GL_COLOR_ARRAY);
<span><span>    </span><span>glColor4f</span>(</span><span>1.0</span>, <span>0.0</span>, <span>0.0</span>, <span>1.0</span>);
<span><span>    </span><span>glVertexPointer</span>(</span><span>3</span>, GL_FLOAT, <span>0</span>, &amp;triangle);
<span><span>    </span><span>glColorPointer</span>(</span><span>4</span>, GL_FLOAT, <span>0</span>, colors);
<span><span>    </span><span>glDrawArrays</span>(</span>GL_TRIANGLES, <span>0</span>, <span>9</span>);
<span><span>    </span><span>glDisableClientState</span>(</span>GL_VERTEX_ARRAY);
<span><span>    </span><span>glDisableClientState</span>(</span>GL_COLOR_ARRAY);

    <span>if</span><span> <span>(</span></span>colors != <span>NULL</span>)
<span>        </span><span>free</span>(colors);
}</span></span></pre>
<p>运行，屏幕如下：</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/iPhone-SimulatorScreenSnapz003.jpg"><img class="alignnone size-full wp-image-575" title="iPhone SimulatorScreenSnapz003" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/iPhone-SimulatorScreenSnapz003.jpg" alt="iPhone SimulatorScreenSnapz003" width="208" height="400" /></a></div>
<p>今天我们还要讨论一个话题。如果我们不止一次使用一个顶点（三角形条或三角形扇的相邻顶点除外），我们至今使用的方法存在一个问题，我们必须多次向OpenGL传递同一顶点。那不是什么大问题，但通常我们需要尽量减少向OpenGL传递的数据量，所以一次又一次地传递同一个4字节浮点数是非常地不理想。在一些物体中，某个顶点会用在七个甚至更多的不同三角形中，因此你的顶点数组可能会增大许多倍。</p>
<p>当处理这类复杂的几何体时，有一种方法可以避免多次传递同一个顶点，就是使用通过顶点对应于顶点数组中的索引的方法，此方法称之为<strong>元素（elements）</strong>。其原理是创建一个每个顶点只使用一次的数组。然后使用另一个使用最小的无符号整型数的数组来保存所需的唯一顶点号。换句话说，如果顶点数组具有小于256个顶点，那么你应创建一个<span style="font-family: monospace;">GLubyte</span>数组，如果大于 256，但小于 65,536，应使用 <span style="font-family: monospace;">GLushort。你可以通过映射顶点在第一个数组中的索引值</span>来创建三角形（或其他形状）。所以，如果你创建了一个具有12个顶点的数组，那么数组中的第一个顶点为0。你可以按以前一样的方法绘制图形，只不过不是调用 <span style="font-family: monospace;">glDrawArrays()，而是调用不同的函数 </span><span style="font-family: monospace;">glDrawElements()并传递整数数组。</span></p>
<p>让我们以一个真实的，如假包换的3D形状来结束我们的教程：一个二十面体。每个人都使用正方体，但我们要更怪异一点，我们要画一个二十面体。替换<span style="font-family: monospace;">drawView：</span></p>
<pre><span>- (<span>void</span>)drawView:(GLView*)view;
<span>{

    <span>static</span> GLfloat rot = <span>0.0</span>;

    <span><span>//</span> This is the same result as using Vertex3D, just faster to type and
</span>    <span><span>//</span> can be made const this way
</span>    <span>static</span> <span>const</span> Vertex3D vertices<span><span>[</span><span>]</span></span>= <span>{
        <span>{<span>0</span>, -<span>0.525731</span>, <span>0.850651</span>}</span>,             <span><span>//</span> vertices[0]
</span>        <span>{<span>0.850651</span>, <span>0</span>, <span>0.525731</span>}</span>,              <span><span>//</span> vertices[1]
</span>        <span>{<span>0.850651</span>, <span>0</span>, -<span>0.525731</span>}</span>,             <span><span>//</span> vertices[2]
</span>        <span>{-<span>0.850651</span>, <span>0</span>, -<span>0.525731</span>}</span>,            <span><span>//</span> vertices[3]
</span>        <span>{-<span>0.850651</span>, <span>0</span>, <span>0.525731</span>}</span>,             <span><span>//</span> vertices[4]
</span>        <span>{-<span>0.525731</span>, <span>0.850651</span>, <span>0</span>}</span>,             <span><span>//</span> vertices[5]
</span>        <span>{<span>0.525731</span>, <span>0.850651</span>, <span>0</span>}</span>,              <span><span>//</span> vertices[6]
</span>        <span>{<span>0.525731</span>, -<span>0.850651</span>, <span>0</span>}</span>,             <span><span>//</span> vertices[7]
</span>        <span>{-<span>0.525731</span>, -<span>0.850651</span>, <span>0</span>}</span>,            <span><span>//</span> vertices[8]
</span>        <span>{<span>0</span>, -<span>0.525731</span>, -<span>0.850651</span>}</span>,            <span><span>//</span> vertices[9]
</span>        <span>{<span>0</span>, <span>0.525731</span>, -<span>0.850651</span>}</span>,             <span><span>//</span> vertices[10]
</span>        <span>{<span>0</span>, <span>0.525731</span>, <span>0.850651</span>}</span>               <span><span>//</span> vertices[11]
</span>    }</span>;

    <span>static</span> <span>const</span> Color3D colors<span><span>[</span><span>]</span></span> = <span>{
         <span>{<span>1.0</span>, <span>0.0</span>, <span>0.0</span>, <span>1.0</span>}</span>,
         <span>{<span>1.0</span>, <span>0.5</span>, <span>0.0</span>, <span>1.0</span>}</span>,
         <span>{<span>1.0</span>, <span>1.0</span>, <span>0.0</span>, <span>1.0</span>}</span>,
         <span>{<span>0.5</span>, <span>1.0</span>, <span>0.0</span>, <span>1.0</span>}</span>,
         <span>{<span>0.0</span>, <span>1.0</span>, <span>0.0</span>, <span>1.0</span>}</span>,
         <span>{<span>0.0</span>, <span>1.0</span>, <span>0.5</span>, <span>1.0</span>}</span>,
         <span>{<span>0.0</span>, <span>1.0</span>, <span>1.0</span>, <span>1.0</span>}</span>,
         <span>{<span>0.0</span>, <span>0.5</span>, <span>1.0</span>, <span>1.0</span>}</span>,
         <span>{<span>0.0</span>, <span>0.0</span>, <span>1.0</span>, <span>1.0</span>}</span>,
         <span>{<span>0.5</span>, <span>0.0</span>, <span>1.0</span>, <span>1.0</span>}</span>,
         <span>{<span>1.0</span>, <span>0.0</span>, <span>1.0</span>, <span>1.0</span>}</span>,
         <span>{<span>1.0</span>, <span>0.0</span>, <span>0.5</span>, <span>1.0</span>}</span>
    }</span>;

    <span>static</span> <span>const</span> GLubyte icosahedronFaces<span><span>[</span><span>]</span></span> = <span>{
        <span>1</span>, <span>2</span>, <span>6</span>,
        <span>1</span>, <span>7</span>, <span>2</span>,
        <span>3</span>, <span>4</span>, <span>5</span>,
        <span>4</span>, <span>3</span>, <span>8</span>,
        <span>6</span>, <span>5</span>, <span>11</span>,
        <span>5</span>, <span>6</span>, <span>10</span>,
        <span>9</span>, <span>10</span>, <span>2</span>,
        <span>10</span>, <span>9</span>, <span>3</span>,
        <span>7</span>, <span>8</span>, <span>9</span>,
        <span>8</span>, <span>7</span>, <span>0</span>,
        <span>11</span>, <span>0</span>, <span>1</span>,
        <span>0</span>, <span>11</span>, <span>4</span>,
        <span>6</span>, <span>2</span>, <span>10</span>,
        <span>1</span>, <span>6</span>, <span>11</span>,
        <span>3</span>, <span>5</span>, <span>10</span>,
        <span>5</span>, <span>4</span>, <span>11</span>,
        <span>2</span>, <span>7</span>, <span>9</span>,
        <span>7</span>, <span>1</span>, <span>0</span>,
        <span>3</span>, <span>9</span>, <span>8</span>,
        <span>4</span>, <span>8</span>, <span>0</span>,
    }</span>;

<span><span>    </span><span>glLoadIdentity</span>(</span>);
<span><span>    </span><span>glTranslatef</span>(</span><span>0.0f</span>,<span>0.0f</span>,-<span>3.0f</span>);
<span><span>    </span><span>glRotatef</span>(</span>rot,<span>1.0f</span>,<span>1.0f</span>,<span>1.0f</span>);
<span><span>    </span><span>glClearColor</span>(</span><span>0.7</span>, <span>0.7</span>, <span>0.7</span>, <span>1.0</span>);
<span><span>    </span><span>glClear</span>(</span>GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
<span><span>    </span><span>glEnableClientState</span>(</span>GL_VERTEX_ARRAY);
<span><span>    </span><span>glEnableClientState</span>(</span>GL_COLOR_ARRAY);
<span><span>    </span><span>glVertexPointer</span>(</span><span>3</span>, GL_FLOAT, <span>0</span>, vertices);
<span><span>    </span><span>glColorPointer</span>(</span><span>4</span>, GL_FLOAT, <span>0</span>, colors);

<span><span>    </span><span>glDrawElements</span>(</span>GL_TRIANGLES, <span>60</span>, GL_UNSIGNED_BYTE, icosahedronFaces);

<span><span>    </span><span>glDisableClientState</span>(</span>GL_VERTEX_ARRAY);
<span><span>    </span><span>glDisableClientState</span>(</span>GL_COLOR_ARRAY);
    <span>static</span> <span>NSTimeInterval</span> lastDrawTime;
    <span>if</span><span> <span>(</span></span>lastDrawTime)
    <span>{
        <span>NSTimeInterval</span> timeSinceLastDraw = <span><span>[</span><span>NSDate</span> <span><span>timeIntervalSinceReferenceDate</span></span><span>]</span></span> - lastDrawTime;
        rot+=<span>50</span> * timeSinceLastDraw;
    }</span>
    lastDrawTime = <span><span>[</span><span>NSDate</span> <span><span>timeIntervalSinceReferenceDate</span></span><span>]</span></span>;
}</span></span></pre>
<p>运行，屏幕上将看到一个漂亮的旋转物体：</p>
<div style="text-align: center;"><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/iPhone-SimulatorScreenSnapz001-1.jpg"><img class="alignnone size-full wp-image-576" title="iPhone SimulatorScreenSnapz001-1" src="http://www.iphone-geek.cn/wp-content/uploads/2009/12/iPhone-SimulatorScreenSnapz001-1.jpg" alt="iPhone SimulatorScreenSnapz001-1" width="208" height="400" /></a></div>
<p>因为我们没有使用光源而且即使使用了光源我们也没有告诉OpenGL怎样进行光源反射，所以它看上去还不是一个完美的3D物体。有关光线的部分将在后续文章中讨论。</p>
<p>这里我们做了什么？首先，我们建立了一个静态变量来跟踪物体的旋转。</p>
<pre><span>    <span>static</span> GLfloat rot = <span>0.0</span>;</span></pre>
<p>然后我们定义顶点数组。我们使用了一个与前不同的方法，但结果是一样的。由于我们的几何体根本不会变化，所以我们将其定义为<span style="font-family: monospace;">const，这样就不需要每一帧都分配/清除内存：</span></p>
<pre><span>    <span>static</span> <span>const</span> Vertex3D vertices<span><span>[</span><span>]</span></span>= <span>{
        <span>{<span>0</span>, -<span>0.525731</span>, <span>0.850651</span>}</span>,             <span><span>//</span> vertices[0]
</span>        <span>{<span>0.850651</span>, <span>0</span>, <span>0.525731</span>}</span>,              <span><span>//</span> vertices[1]
</span>        <span>{<span>0.850651</span>, <span>0</span>, -<span>0.525731</span>}</span>,             <span><span>//</span> vertices[2]
</span>        <span>{-<span>0.850651</span>, <span>0</span>, -<span>0.525731</span>}</span>,            <span><span>//</span> vertices[3]
</span>        <span>{-<span>0.850651</span>, <span>0</span>, <span>0.525731</span>}</span>,             <span><span>//</span> vertices[4]
</span>        <span>{-<span>0.525731</span>, <span>0.850651</span>, <span>0</span>}</span>,             <span><span>//</span> vertices[5]
</span>        <span>{<span>0.525731</span>, <span>0.850651</span>, <span>0</span>}</span>,              <span><span>//</span> vertices[6]
</span>        <span>{<span>0.525731</span>, -<span>0.850651</span>, <span>0</span>}</span>,             <span><span>//</span> vertices[7]
</span>        <span>{-<span>0.525731</span>, -<span>0.850651</span>, <span>0</span>}</span>,            <span><span>//</span> vertices[8]
</span>        <span>{<span>0</span>, -<span>0.525731</span>, -<span>0.850651</span>}</span>,            <span><span>//</span> vertices[9]
</span>        <span>{<span>0</span>, <span>0.525731</span>, -<span>0.850651</span>}</span>,             <span><span>//</span> vertices[10]
</span>        <span>{<span>0</span>, <span>0.525731</span>, <span>0.850651</span>}</span>               <span><span>//</span> vertices[11]
</span>    }</span>;</span></pre>
<p>然后用同样方法建立一个颜色数组 。创建一个<span style="font-family: monospace;">Color3D</span> 对象数组，每项对应于前一个数组的顶点：</p>
<pre><span>    <span>static</span> <span>const</span> Color3D colors<span><span>[</span><span>]</span></span> = <span>{
         <span>{<span>1.0</span>, <span>0.0</span>, <span>0.0</span>, <span>1.0</span>}</span>,
         <span>{<span>1.0</span>, <span>0.5</span>, <span>0.0</span>, <span>1.0</span>}</span>,
         <span>{<span>1.0</span>, <span>1.0</span>, <span>0.0</span>, <span>1.0</span>}</span>,
         <span>{<span>0.5</span>, <span>1.0</span>, <span>0.0</span>, <span>1.0</span>}</span>,
         <span>{<span>0.0</span>, <span>1.0</span>, <span>0.0</span>, <span>1.0</span>}</span>,
         <span>{<span>0.0</span>, <span>1.0</span>, <span>0.5</span>, <span>1.0</span>}</span>,
         <span>{<span>0.0</span>, <span>1.0</span>, <span>1.0</span>, <span>1.0</span>}</span>,
         <span>{<span>0.0</span>, <span>0.5</span>, <span>1.0</span>, <span>1.0</span>}</span>,
         <span>{<span>0.0</span>, <span>0.0</span>, <span>1.0</span>, <span>1.0</span>}</span>,
         <span>{<span>0.5</span>, <span>0.0</span>, <span>1.0</span>, <span>1.0</span>}</span>,
         <span>{<span>1.0</span>, <span>0.0</span>, <span>1.0</span>, <span>1.0</span>}</span>,
         <span>{<span>1.0</span>, <span>0.0</span>, <span>0.5</span>, <span>1.0</span>}</span>
    }</span>;</span></pre>
<p>最后，创建二十面体。上述十二个顶点本身并未描述形状。OpenGL需要知道怎样将它们联系在一起，所以我们创建了一个整型数组（ <span style="font-family: monospace;">GLubyte</span>）指向构成各三角形的顶点。</p>
<pre><span>    <span>static</span> <span>const</span> GLubyte icosahedronFaces<span><span>[</span><span>]</span></span> = <span>{
        <span>1</span>, <span>2</span>, <span>6</span>,
        <span>1</span>, <span>7</span>, <span>2</span>,
        <span>3</span>, <span>4</span>, <span>5</span>,
        <span>4</span>, <span>3</span>, <span>8</span>,
        <span>6</span>, <span>5</span>, <span>11</span>,
        <span>5</span>, <span>6</span>, <span>10</span>,
        <span>9</span>, <span>10</span>, <span>2</span>,
        <span>10</span>, <span>9</span>, <span>3</span>,
        <span>7</span>, <span>8</span>, <span>9</span>,
        <span>8</span>, <span>7</span>, <span>0</span>,
        <span>11</span>, <span>0</span>, <span>1</span>,
        <span>0</span>, <span>11</span>, <span>4</span>,
        <span>6</span>, <span>2</span>, <span>10</span>,
        <span>1</span>, <span>6</span>, <span>11</span>,
        <span>3</span>, <span>5</span>, <span>10</span>,
        <span>5</span>, <span>4</span>, <span>11</span>,
        <span>2</span>, <span>7</span>, <span>9</span>,
        <span>7</span>, <span>1</span>, <span>0</span>,
        <span>3</span>, <span>9</span>, <span>8</span>,
        <span>4</span>, <span>8</span>, <span>0</span>,
    }</span>;</span></pre>
<p>二十面体的第一个面的三个数是1,2,6，代表绘制处于索引1 (<span style="font-family: monospace;">0.850651, 0, 0.525731</span>)， 2 (<span style="font-family: monospace;">0.850651, 0, 0.525731</span>)， 和6 (<span style="font-family: monospace;">0.525731, 0.850651, 0</span>)之间的三角形。</p>
<p>下面一段代码没有新内容，只是加载单元矩阵（所以变换复位），移动并旋转几何体，设置背景色，清除缓存，启动顶点和颜色数组，然后提供顶点数组数据给OpenGL。所有这些在前一个例子中都有涉及。</p>
<pre><span><span><span>    </span><span>glLoadIdentity</span><span>()</span>;</span>
<span><span>    </span><span>glTranslatef</span><span>(<span>0.0f</span>,<span>0.0f</span>,-<span>3.0f</span>)</span>;</span>
<span><span>    </span><span>glRotatef</span><span>(rot,<span>1.0f</span>,<span>1.0f</span>,<span>1.0f</span>)</span>;</span>
<span><span>    </span><span>glClearColor</span><span>(<span>0.7</span>, <span>0.7</span>, <span>0.7</span>, <span>1.0</span>)</span>;</span>
<span><span>    </span><span>glClear</span><span>(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)</span>;</span>
<span><span>    </span><span>glEnableClientState</span><span>(GL_VERTEX_ARRAY)</span>;</span>
<span><span>    </span><span>glEnableClientState</span><span>(GL_COLOR_ARRAY)</span>;</span>
<span><span>    </span><span>glColor4f</span><span>(<span>1.0</span>, <span>0.0</span>, <span>0.0</span>, <span>1.0</span>)</span>;</span>
<span><span>    </span><span>glVertexPointer</span><span>(<span>3</span>, GL_FLOAT, <span>0</span>, vertices)</span>;</span>
<span><span>    </span><span>glColorPointer</span><span>(<span>4</span>, GL_FLOAT, <span>0</span>, colors)</span>;</span></span></pre>
<p>然后，我们没有使用<span style="font-family: monospace;">glDrawArrays()。而是调用</span><span style="font-family: monospace;">glDrawElements()</span>：</p>
<pre><span><span><span>    </span><span>glDrawElements</span><span>(GL_TRIANGLES, <span>60</span>, GL_UNSIGNED_BYTE, icosahedronFaces)</span>;</span></span></pre>
<p>接着，执行禁止功能，根据距上一帧绘制的时间增加旋转变量值：</p>
<pre><span><span><span>    </span><span>glDisableClientState</span><span>(GL_VERTEX_ARRAY)</span>;</span>
<span><span>    </span><span>glDisableClientState</span><span>(GL_COLOR_ARRAY)</span>;</span>
    <span>static</span> <span>NSTimeInterval</span> lastDrawTime;
    <span>if</span> (lastDrawTime)
    <span>{
        <span>NSTimeInterval</span> timeSinceLastDraw = <span><span>[</span><span>NSDate</span> <span><span>timeIntervalSinceReferenceDate</span></span><span>]</span></span> - lastDrawTime;
        rot+=<span>50</span> * timeSinceLastDraw;
    }</span>
    lastDrawTime = <span><span>[</span><span>NSDate</span> <span><span>timeIntervalSinceReferenceDate</span></span><span>]</span></span>;</span></pre>
<p>记住：如果你按绘制的正确次序提供顶点，那么你应该使用<span style="font-family: monospace;">glDrawArrays()，但是如果你提供一个数组然后用另一个以索引值区分顶点次序的数组的话，那么你应该使用</span><span style="font-family: monospace;">glDrawElements()</span>。</p>
<p>请花些时间测试绘图代码，添加更多的多边形，改变颜色等。OpenGL绘图功能远超过本文涉及的内容，但你应该清楚iPhone上3D物体绘制的基本概念了：创建一块内存保存所有的顶点，传递顶点数组给OpenGL，然后由OpenGL绘制出来。</p>
<p><a href="http://www.iphone-geek.cn/wp-content/uploads/2009/12/Part2Project.zip">源码下载</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e4%bb%8e%e9%9b%b6%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0opengl-es%e4%b9%8b%e4%ba%8c-%e7%ae%80%e5%8d%95%e7%bb%98%e5%9b%be%e6%a6%82%e8%bf%b0/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
	</channel>
</rss>
