<?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; 教程</title>
	<atom:link href="http://www.iphone-geek.cn/tag/%e6%95%99%e7%a8%8b/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>调试教程 – 使用UIRecorder Instrument将测试自动化</title>
		<link>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e8%b0%83%e8%af%95%e6%95%99%e7%a8%8b-%e2%80%93-%e4%bd%bf%e7%94%a8uirecorder-instrument%e5%b0%86%e6%b5%8b%e8%af%95%e8%87%aa%e5%8a%a8%e5%8c%96</link>
		<comments>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e8%b0%83%e8%af%95%e6%95%99%e7%a8%8b-%e2%80%93-%e4%bd%bf%e7%94%a8uirecorder-instrument%e5%b0%86%e6%b5%8b%e8%af%95%e8%87%aa%e5%8a%a8%e5%8c%96#comments</comments>
		<pubDate>Mon, 19 Jul 2010 01:52:31 +0000</pubDate>
		<dc:creator>bagusflyer</dc:creator>
				<category><![CDATA[基础]]></category>
		<category><![CDATA[编程]]></category>
		<category><![CDATA[调试]]></category>
		<category><![CDATA[教程]]></category>

		<guid isPermaLink="false">http://www.iphone-geek.cn/?p=970</guid>
		<description><![CDATA[（注：这是我以前收集的一篇文章，找不到出处了，稍作修改在此发布）

如果你发现程序的bug，但每次进行测试都需要许多步骤才能重现问题，那么本教程适合你。通常，测试和调试是非常繁琐的事情，iPhone程序的开发尤其如此。

比如说，你有个程序需要经过5个步骤才能到达另一个view。如果你有一个bug发生在这个需要5步之多的view上，通常情况下调试的步骤如下：

运行程序
触击 view 1
触击view 2
触击 view 3
触击 view 4
触击 view 5
(程序崩溃)
修改代码
重复以上步骤


如你所见，这是多么痛苦的事情啊。 Kendall Gelner 在 360iDev 上有关使用Instruments 和 XCode进行调试的若干提示的演讲中，对我而言最为有用的就是将测试自动化。我演示一下应该怎么做:
1. 打开希望测试/调试的程序
2. 在模拟器中运行
3. 打开Instrument – 位于 /Developer/Applications/Instruments
 
 



 
4. 选择 UIRecorder 并按Choose - 你将看到下面窗口


 
5.  然后需要将此工具与iPhone模拟器进程链接。按下下拉菜单选择 Default Target -&#62; Attach to Process -&#62; iPhone Simulator.


6. 再按下 Drive &#38; Record 并在模拟器中完成测试所需的所有步骤。至此，UI Recorder记录下了你的一举一动。完成时，按下Stop按钮。 注意： 在完成记录后请不要移动模拟器，因为这有可能会使弄乱整个处理过程。
7. 修改你的代码…
8. 按下 Drive &#38; Record [...]]]></description>
			<content:encoded><![CDATA[<p>（注：这是我以前收集的一篇文章，找不到出处了，稍作修改在此发布）</p>
<p><br class="spacer_" /></p>
<p>如果你发现程序的bug，但每次进行测试都需要许多步骤才能重现问题，那么本教程适合你。通常，测试和调试是非常繁琐的事情，iPhone程序的开发尤其如此。</p>
<p><span id="more-970"></span><br class="spacer_" /></p>
<p>比如说，你有个程序需要经过5个步骤才能到达另一个view。如果你有一个bug发生在这个需要5步之多的view上，通常情况下调试的步骤如下：</p>
<ul>
<li>运行程序</li>
<li>触击 view 1</li>
<li>触击view 2</li>
<li>触击 view 3</li>
<li>触击 view 4</li>
<li>触击 view 5</li>
<li>(程序崩溃)</li>
<li>修改代码</li>
<li>重复以上步骤</li>
</ul>
<p><br class="spacer_" /></p>
<p>如你所见，这是多么痛苦的事情啊。 <a href="http://twitter.com/kendalldevdiary">Kendall Gelner</a> 在 <a href="http://www.360idev.com/">360iDev</a> 上有关使用Instruments 和 XCode进行调试的若干提示的演讲中，对我而言最为有用的就是将测试自动化。我演示一下应该怎么做:</p>
<p style="padding-left: 30px;">1. 打开希望测试/调试的程序</p>
<p style="padding-left: 30px;">2. 在模拟器中运行</p>
<p style="padding-left: 30px;">3. 打开<strong>Instrument</strong> – 位于 /Developer/<a id="KonaLink2" style="position: static; text-decoration: underline !important;" onclick="adlinkMouseClick(event,this,2);" onmouseover="adlinkMouseOver(event,this,2);" onmouseout="adlinkMouseOut(event,this,2);" href="http://www.iphone-geek.cn/wp-admin/#" target="_new"><span style="position: static; color: #346200 !important; font-weight: 400;"><span style="position: static; font-family: Helvetica, Arial, sans-serif; color: #346200 !important; font-weight: 400;">Applications</span></span></a>/Instruments</p>
<ol> </ol>
<ol> </ol>
<p><br class="spacer_" /></p>
<p style="text-align: center;"><strong><a href="http://icodeblog.com/wp-content/uploads/2009/10/screenshot_01.png"><img class="aligncenter" style="zoom: 1; display: inline;" title="screenshot_01" src="http://icodeblog.com/wp-content/uploads/2009/10/screenshot_01-300x229.png" alt="screenshot_01" width="300" height="229" /></a></strong></p>
<p style="text-align: center;"><strong><br />
 </strong></p>
<p style="padding-left: 30px;">4. 选择 <strong>UIRecorder</strong> 并按<strong>Choose </strong>- 你将看到下面窗口</p>
<p style="text-align: center;"><strong><a href="http://icodeblog.com/wp-content/uploads/2009/10/screenshot_02.png"><img class="aligncenter" style="zoom: 1; display: inline;" title="screenshot_02" src="http://icodeblog.com/wp-content/uploads/2009/10/screenshot_02-300x211.png" alt="screenshot_02" width="300" height="211" /></a></strong></p>
<p style="text-align: center;"><strong><br />
 </strong></p>
<p style="padding-left: 30px;">5.  然后需要将此工具与iPhone模拟器进程链接。按下下拉菜单选择 <strong>Default Target</strong> -&gt; <strong>Attach to Process </strong>-&gt; <strong>iPhone Simulator</strong>.</p>
<p style="text-align: center;"><a href="http://icodeblog.com/wp-content/uploads/2009/10/ss_03.png"><img class="aligncenter" style="zoom: 1; display: inline;" title="ss_03" src="http://icodeblog.com/wp-content/uploads/2009/10/ss_03-300x136.png" alt="ss_03" width="300" height="136" /></a></p>
<p><br class="spacer_" /></p>
<p style="padding-left: 30px;">6. 再按下 <strong>Drive &amp; Record</strong> 并在模拟器中完成测试所需的所有步骤。至此，UI Recorder记录下了你的一举一动。完成时，按下<strong>Stop</strong>按钮。 <span style="color: #ff0000;"><strong>注意：</strong></span> 在完成记录后请不要移动模拟器，因为这有可能会使弄乱整个处理过程。</p>
<p style="padding-left: 30px;">7. 修改你的代码…</p>
<p style="padding-left: 30px;">8. 按下 <strong>Drive &amp; Record </strong>按钮亲眼目睹一下有什么神奇的事情发生吧。测试自动进行了！你应该看到它自动模拟了你刚才进行的所有动作。</p>
<p><br class="spacer_" /></p>
]]></content:encoded>
			<wfw:commentRss>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e8%b0%83%e8%af%95%e6%95%99%e7%a8%8b-%e2%80%93-%e4%bd%bf%e7%94%a8uirecorder-instrument%e5%b0%86%e6%b5%8b%e8%af%95%e8%87%aa%e5%8a%a8%e5%8c%96/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>创建离线iPhone Web App</title>
		<link>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e5%88%9b%e5%bb%ba%e7%a6%bb%e7%ba%bfiphone-web-app</link>
		<comments>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e5%88%9b%e5%bb%ba%e7%a6%bb%e7%ba%bfiphone-web-app#comments</comments>
		<pubDate>Tue, 18 May 2010 05:09:21 +0000</pubDate>
		<dc:creator>bagusflyer</dc:creator>
				<category><![CDATA[编程]]></category>
		<category><![CDATA[网络]]></category>
		<category><![CDATA[Web App]]></category>
		<category><![CDATA[教程]]></category>
		<category><![CDATA[浏览器]]></category>

		<guid isPermaLink="false">http://www.iphone-geek.cn/?p=952</guid>
		<description><![CDATA[创建Web App的好处很多，比如：


可以运行在任何平台上 （当然要考虑浏览器的兼容性和屏幕大小问题） 
不需要学习iPhone编程语言 
最大的好处是不需要经过App Store批准 


随着Internet技术的迅速发展，使得Web App越来越强大，从简单的应用甚至到游戏，都可能通过Web App实现。但是，它要求随时连接到Internet，大大降低了Web App的实用性。随着Safari对HTML 5的支持，使得Web App的离线运行成为可能。这意味着你可以在没有Internet连接的情况下运行你的应用程序。

下面的例子改编自How to Make an HTML5 iPhone App，它介绍了创建一个离线“积木”游戏的全过程，我加上了创建用户自定义的桌面图标和启动画面的方法，使它看上去完全像一个真正的iPhone应用程序，而这是完全不需要通过Apple的App Store批准的。


基本要求

网页编程技术：HTML(5)， CSS 以及 JavaScript。

准备工作

由于我们要利用HTML5的离线缓存技术，我们需要访问网页服务器以便对文件的HTTP Headers进行修改（稍后我们会详细讨论）。

Apache服务器很容易通过修改 .htaccess 文件完成你需要的工作。这里是一篇教程关于 m使用htaccess修改HTTP header。

然后你需要打开Safari浏览器的调试窗以帮助你进行调试。在Settings > Safari > Developer中打开调试终端，它能够帮助你发现一些潜在的Javascript错误。记住在完成调试后，关闭调试窗。



应用程序缓存

HTML5 有关离线的标准参考这里。

应用程序缓存允许浏览器预先确定网页所需的所有文件，然后它将保存这些文件。格式是绝对地址如http://yourwebserver.com/picture.png 或者相对地址(/picture.png)，浏览器将离线保存这些文件。

你还可以列出不需要缓存的URL。最主要的部分是清单文件（需要离线缓存文件的清单）在Http Header中的filetype必须设置为text/manifest。


屏幕尺寸

在设计iPhone Web App时必须注意，当处于app模式时，屏幕尺寸为 320px X 460px，而在web模式时为 320px X 356px。这也许会影响你的用户界面设计。




代码

12345678910111213141516&#60;!DOCTYPE html&#62;
&#60;html manifest=&#34;tetris.manifest&#34;&#62;
&#60;head&#62;
&#160; &#160; &#60;meta name=&#34;viewport&#34; content=&#34;user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0&#34;/&#62;
&#160; &#160; &#60;meta name=&#34;apple-mobile-web-app-capable&#34; content=&#34;yes&#34; [...]]]></description>
			<content:encoded><![CDATA[<p>创建Web App的好处很多，比如：</p>
<p><br class="spacer_" /></p>
<ul>
<li>可以运行在任何平台上 （当然要考虑浏览器的兼容性和屏幕大小问题） </li>
<li>不需要学习iPhone编程语言 </li>
<li>最大的好处是不需要经过App Store批准 </li>
</ul>
<p><br class="spacer_" /></p>
<p>随着Internet技术的迅速发展，使得Web App越来越强大，从简单的应用甚至到游戏，都可能通过Web App实现。但是，它要求随时连接到Internet，大大降低了Web App的实用性。随着Safari对HTML 5的支持，使得Web App的离线运行成为可能。这意味着你可以在没有Internet连接的情况下运行你的应用程序。</p>
<p><br class="spacer_" /></p>
<p>下面的例子改编自<a href="http://sixrevisions.com/web-development/html5-iphone-app/">How to Make an HTML5 iPhone App</a>，它介绍了创建一个离线“积木”游戏的全过程，我加上了创建用户自定义的桌面图标和启动画面的方法，使它看上去完全像一个真正的iPhone应用程序，而这是完全不需要通过Apple的App Store批准的。</p>
<p><span id="more-952"></span></p>
<p><br class="spacer_" /></p>
<h3>基本要求</h3>
<p><br class="spacer_" /></p>
<p>网页编程技术：HTML(5)， CSS 以及 JavaScript。</p>
<p><br class="spacer_" /></p>
<h3>准备工作</h3>
<p><br class="spacer_" /></p>
<p>由于我们要利用HTML5的离线缓存技术，我们需要访问网页服务器以便对文件的<a href="http://en.wikipedia.org/wiki/List_of_HTTP_headers">HTTP Headers</a>进行修改（稍后我们会详细讨论）。</p>
<p><br class="spacer_" /></p>
<p>Apache服务器很容易通过修改 .htaccess 文件完成你需要的工作。这里是一篇教程关于 <a href="http://www.askapache.com/htaccess/using-http-headers-with-htaccess.html">m使用htaccess修改HTTP header</a>。</p>
<p><br class="spacer_" /></p>
<p>然后你需要打开Safari浏览器的调试窗以帮助你进行调试。在Settings > Safari > Developer中打开调试终端，它能够帮助你发现一些潜在的Javascript错误。记住在完成调试后，关闭调试窗。</p>
<p><br class="spacer_" /></p>
<p><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="image" src="http://www.iphone-geek.cn/wp-content/uploads/2010/05/image3.png" border="0" alt="image" width="324" height="484" /></p>
<p><br class="spacer_" /></p>
<h3>应用程序缓存</h3>
<p><br class="spacer_" /></p>
<p>HTML5 有关离线的标准参考这里。</p>
<p><br class="spacer_" /></p>
<p>应用程序缓存允许浏览器预先确定网页所需的所有文件，然后它将保存这些文件。格式是绝对地址如<a href="http://yourwebserver.com/picture.png">http://yourwebserver.com/picture.png</a> 或者相对地址(/picture.png)，浏览器将离线保存这些文件。</p>
<p><br class="spacer_" /></p>
<p>你还可以列出不需要缓存的URL。最主要的部分是清单文件（需要离线缓存文件的清单）在Http Header中的filetype必须设置为text/manifest。</p>
<p><br class="spacer_" /></p>
<p><br class="spacer_" /></p>
<h3>屏幕尺寸</h3>
<p><br class="spacer_" /></p>
<p>在设计iPhone Web App时必须注意，当处于app模式时，屏幕尺寸为 320px X 460px，而在web模式时为 320px X 356px。这也许会影响你的用户界面设计。</p>
<p><br class="spacer_" /></p>
<p><a href="http://www.iphone-geek.cn/wp-content/uploads/2010/05/image4.png"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="image" src="http://www.iphone-geek.cn/wp-content/uploads/2010/05/image_thumb.png" border="0" alt="image" width="554" height="417" /></a></p>
<p><br class="spacer_" /></p>
<h3></h3>
<h3>代码</h3>
<p><br class="spacer_" /></p>
<div class="codecolorer-container html4strict 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 /></div></td><td><div class="html4strict codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap"><span style="color: #00bbdd;">&lt;!DOCTYPE html&gt;</span><br />
<span style="color: #009900;">&lt;<a href="http://december.com/html/4/element/html.html"><span style="color: #000000; font-weight: bold;">html</span></a> manifest<span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;tetris.manifest&quot;</span>&gt;</span><br />
<span style="color: #009900;">&lt;<a href="http://december.com/html/4/element/head.html"><span style="color: #000000; font-weight: bold;">head</span></a>&gt;</span><br />
&nbsp; &nbsp; <span style="color: #009900;">&lt;<a href="http://december.com/html/4/element/meta.html"><span style="color: #000000; font-weight: bold;">meta</span></a> <span style="color: #000066;">name</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;viewport&quot;</span> <span style="color: #000066;">content</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0&quot;</span><span style="color: #66cc66;">/</span>&gt;</span><br />
&nbsp; &nbsp; <span style="color: #009900;">&lt;<a href="http://december.com/html/4/element/meta.html"><span style="color: #000000; font-weight: bold;">meta</span></a> <span style="color: #000066;">name</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;apple-mobile-web-app-capable&quot;</span> <span style="color: #000066;">content</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;yes&quot;</span> <span style="color: #66cc66;">/</span>&gt;</span><br />
&nbsp; &nbsp; <span style="color: #009900;">&lt;<a href="http://december.com/html/4/element/meta.html"><span style="color: #000000; font-weight: bold;">meta</span></a> <span style="color: #000066;">name</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;apple-mobile-web-app-status-bar-style&quot;</span> <span style="color: #000066;">content</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;black&quot;</span> <span style="color: #66cc66;">/</span>&gt;</span><br />
&nbsp; &nbsp; <span style="color: #009900;">&lt;<a href="http://december.com/html/4/element/link.html"><span style="color: #000000; font-weight: bold;">link</span></a> <span style="color: #000066;">rel</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;apple-touch-icon&quot;</span> <span style="color: #000066;">href</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;iphon_tetris_icon.png&quot;</span><span style="color: #66cc66;">/</span>&gt;</span><br />
&nbsp; &nbsp; <span style="color: #009900;">&lt;<a href="http://december.com/html/4/element/link.html"><span style="color: #000000; font-weight: bold;">link</span></a> <span style="color: #000066;">rel</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;apple-touch-startup-image&quot;</span> <span style="color: #000066;">href</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;startup.png&quot;</span> <span style="color: #66cc66;">/</span>&gt;</span><br />
&nbsp; &nbsp; <span style="color: #009900;">&lt;<a href="http://december.com/html/4/element/link.html"><span style="color: #000000; font-weight: bold;">link</span></a> <span style="color: #000066;">rel</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;stylesheet&quot;</span> <span style="color: #000066;">href</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;tetris.css&quot;</span> <span style="color: #000066;">type</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;text/css&quot;</span> <span style="color: #000066;">media</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;screen, mobile&quot;</span> <span style="color: #000066;">title</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;main&quot;</span> <span style="color: #000066;">charset</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;utf-8&quot;</span>&gt;</span><br />
&nbsp; &nbsp; <span style="color: #009900;">&lt;<a href="http://december.com/html/4/element/title.html"><span style="color: #000000; font-weight: bold;">title</span></a>&gt;</span>offline Tetris<span style="color: #009900;">&lt;<span style="color: #66cc66;">/</span><a href="http://december.com/html/4/element/title.html"><span style="color: #000000; font-weight: bold;">title</span></a>&gt;</span><br />
<span style="color: #009900;">&lt;<span style="color: #66cc66;">/</span><a href="http://december.com/html/4/element/head.html"><span style="color: #000000; font-weight: bold;">head</span></a>&gt;</span><br />
<span style="color: #009900;">&lt;<a href="http://december.com/html/4/element/body.html"><span style="color: #000000; font-weight: bold;">body</span></a>&gt;</span><br />
&nbsp; &nbsp;<span style="color: #808080; font-style: italic;">&lt;!-- Put your Markup Here --&gt;</span><br />
&nbsp; &nbsp;<span style="color: #009900;">&lt;<a href="http://december.com/html/4/element/script.html"><span style="color: #000000; font-weight: bold;">script</span></a> <span style="color: #000066;">type</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;text/javascript&quot;</span> <span style="color: #000066;">src</span><span style="color: #66cc66;">=</span><span style="color: #ff0000;">&quot;tetris.js&quot;</span>&gt;&lt;<span style="color: #66cc66;">/</span><a href="http://december.com/html/4/element/script.html"><span style="color: #000000; font-weight: bold;">script</span></a>&gt;</span><br />
<span style="color: #009900;">&lt;<span style="color: #66cc66;">/</span><a href="http://december.com/html/4/element/body.html"><span style="color: #000000; font-weight: bold;">body</span></a>&gt;</span><br />
<span style="color: #009900;">&lt;<span style="color: #66cc66;">/</span><a href="http://december.com/html/4/element/html.html"><span style="color: #000000; font-weight: bold;">html</span></a>&gt;</span></div></td></tr></tbody></table></div>
<p><html>中的 manifest=&#8221;cache.manifest&#8221; 属性将告诉浏览器我们可能需要对文件进行离线缓存。</p>
<p><br class="spacer_" /></p>
<p>下面是Apple专用的HTML 5属性：</p>
<ul>
<li>
<div class="codecolorer-container text 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 /></div></td><td><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">apple-mobile-web-app-capable</div></td></tr></tbody></table></div>
<p>: 设置为离线模式 </li>
<li>
<div class="codecolorer-container text 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 /></div></td><td><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">apple-mobile-web-app-status-bar-style</div></td></tr></tbody></table></div>
<p>: 当处于离线时隐藏状态条，导航条 </li>
<li>
<div class="codecolorer-container text 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 /></div></td><td><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">apple-touch-icon</div></td></tr></tbody></table></div>
<p>:指向作为图标的图像 </li>
<li>
<div class="codecolorer-container text 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 /></div></td><td><div class="text codecolorer" style="padding:5px;font:normal 12px/1.4em Monaco, Lucida Console, monospace;white-space:nowrap">apple-touch-startup-image</div></td></tr></tbody></table></div>
<p>: 指向作为启动图像的url</li>
</ul>
<p><br class="spacer_" /></p>
<h3>CSS</h3>
<p><br class="spacer_" /></p>
<p>这几乎与普通网页完全一样。还有一些webkit专用的CSS能实现一些特殊效果如动画等。</p>
<p><br class="spacer_" /></p>
<p>我们使用的CSS如下：</p>
<pre>body {
    overflow:hidden;
    background: #d7d7d7;
    margin:0;
    padding:0;
}
#tetris {
    width: 320px;
    height: 460px;
    background:#000;
}</pre>
<h3>JavaScript</h3>
<p><br class="spacer_" /></p>
<p>我使用的是 <a href="http://www.daltonridenhour.com">Dalton Ridenhour</a> 的Javascript，源代码在 <a href="http://github.com/daltonridenhour/DOM-Tetris">Github</a>。原JS是为普通网页设计的，我们仅需要修改一下拿掉对键盘的支持。</p>
<p><br class="spacer_" /></p>
<p>通常JS在iPhone上完全能正常工作，当然有一些例外。请参阅 <a href="http://www.quirksmode.org/blog/archives/2008/08/iphone_events.html">events on the iPhone</a> 。</p>
<p><br class="spacer_" /></p>
<p>下面是完成的网页，你可以进行测试，看看它是怎样离线运行的：</p>
<p><br class="spacer_" /></p>
<p><a href="http://tetris.alexkessinger.net">http://tetris.alexkessinger.net</a></p>
<p><br class="spacer_" /></p>
<h3>设定图标和启动画面</h3>
<p><br class="spacer_" /></p>
<p>使用iPhone Safari浏览器，很容易将应用程序的图标添加到你iPhone上的Home Screen。</p>
<p><br class="spacer_" /></p>
<ul>
<li>首先，创建一个57&#215;57的png图标。将其放在网站的文档根目录下（注意不是服务器的文档根目录）</li>
</ul>
<p><br class="spacer_" /></p>
<p><a href="http://www.iphone-geek.cn/wp-content/uploads/2010/05/image5.png"><img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="image" src="http://www.iphone-geek.cn/wp-content/uploads/2010/05/image_thumb1.png" border="0" alt="image" width="61" height="61" /></a></p>
<p><br class="spacer_" /></p>
<ul>
<li>在html中加入（见上面HTML代码部分） ，指向图标文件。</li>
<li>在html中加入（见上面HTML代码部分） ，指向启动画面图像文件。</li>
<li>打开浏览器，在地址栏输入你Web App的URL，按下浏览器下方的加号，选择“Add To Home Screen”,然后输入Web App的名称，比如Tetris。</li>
</ul>
<p><br class="spacer_" /></p>
<p>这样一个积木游戏的图标就添加到了你的iPhone屏幕上，启动后即使你的iPhone没有在线，也可以正常运行。</p>
<p><br class="spacer_" /></p>
<h3>我的设想</h3>
<p><br class="spacer_" /></p>
<p>这样的Web App看上去与普通程序没什么大区别，不过却需要用户自己添加图标，这虽然不是什么大问题，但还不是很完美。我想要是在你的网页上添加一项“安装到iPhone/iPod Touch/iPad”，在按下后可以自动添加图标，那么一切就完美了。我现在还没有解决方法，大家有什么建议吗？</p>
]]></content:encoded>
			<wfw:commentRss>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e5%88%9b%e5%bb%ba%e7%a6%bb%e7%ba%bfiphone-web-app/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>iPad 编程教程 &#8211; Hello World++</title>
		<link>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/ipad-%e7%bc%96%e7%a8%8b%e6%95%99%e7%a8%8b-hello-world</link>
		<comments>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/ipad-%e7%bc%96%e7%a8%8b%e6%95%99%e7%a8%8b-hello-world#comments</comments>
		<pubDate>Wed, 07 Apr 2010 05:07:44 +0000</pubDate>
		<dc:creator>bagusflyer</dc:creator>
				<category><![CDATA[用户界面]]></category>
		<category><![CDATA[编程]]></category>
		<category><![CDATA[iPad]]></category>
		<category><![CDATA[教程]]></category>

		<guid isPermaLink="false">http://www.iphone-geek.cn/?p=923</guid>
		<description><![CDATA[概述

随着iPad的发布，我想很多人开始绞尽脑汁试图在新一轮的“淘金热”中抢占一块市场。iCodeBlog将推出一系列iPad教程帮助大家。

由于iPad使用iPhone同样的SDK，所有代码看上去完全一样。实际上，看看最新和修改了的API类，你就会发现大部分是有关用户界面的。这是一个好消息，因为我们都对iPhone编程有了一定的经验。

此教程被称为“Hello World”，实际上它远远不止这些。首先，我假定你们已经具有iPhone/Objective-C的编程经验。


我们要做什么

今天的教程，我将展示怎样创建一个iPad项目，使用UISplitViewController显示两个单独的窗格。我还将涉及一些新的设计/UI模式并介绍iPad编程。

此项目将基于使用UITableView显示水果列表教程 。我们将扩展其中一个范例，如下所示：



本文将使用UISplitViewController在左窗格显示一个UITableView，而在右窗格显示一个具有UIImageView子视窗的UIView。实际上创建这样一个项目非常简单，因为项目模板提供了大部分初始代码。
开始


从http://developer.apple.com/iphone/下载SDK 3.2。iPad模拟器包括在了SDK中。 
下载本项目所需的资源文件 iPadHelloWorldResources.zip 。 

创建项目

创建iPad项目与iPhone项目没有分别。打开XCode并选择File-&#62;New Project，你将发现有一个新的Split View-Based Application选项。选择它并命名为iPadHelloWorld。

这将创建一个UITableView位于左窗格，UIView位于右窗格的应用程序。它还甚至提供了一些示例数据。下列文件将被加入到项目中。



各文件的简单介绍如下：


iPadHelloWorldAppDelegate – 类似于 app delegate。在 application:didFinishLaunchingWithOptions 方法中，创建了具有MasterViewController和 DetailViewController的UISplitViewController。 
MasterViewController – UITableViewController。它将处理左方视窗。 
DetailViewController – 它将处理右方视窗。当用户选择左方列表中不同行时，我们将在此视窗中做相应更新。 


按“Build and Run”测试一下。

注意：当你运行此程序时，你只能看到主视窗，这是因为模拟器运行于垂直模式。选取 “Hardware -&#62; Rotate Left/Right”将使模拟器旋转。你还可以通过按下键盘上的CMD-&#62;Arrow Left/Right 键获得同样效果。

 

导入项目图像

下一步你需要导入项目需要的图像。将下载的资源文件解压，并拖入称为“Resources-iPad”的文件夹下。
XCode 将提示你复制这些文件，选择“Yes”并按下OK。



请包括四个图像文件以及fruits.plist。
显示水果列表

显示水果列表与显示其他UITableView中的数据并无区别。打开MasterViewController.h 加入水果数组定义。
#import   

@class DetailViewController;   

@interface MasterViewController : UITableViewController {
    DetailViewController *detailViewController;
    NSArray * fruits;
} 

@property [...]]]></description>
			<content:encoded><![CDATA[<h3>概述</h3>
<p><br class="spacer_" /></p>
<p>随着iPad的发布，我想很多人开始绞尽脑汁试图在新一轮的“淘金热”中抢占一块市场。iCodeBlog将推出一系列iPad教程帮助大家。</p>
<p><br class="spacer_" /></p>
<p>由于iPad使用iPhone同样的SDK，所有代码看上去完全一样。实际上，看看<a href="http://developer.apple.com/iphone/prerelease/library/releasenotes/General/WhatsNewIniPhoneOS/Articles/iPhoneOS3_2.html#//apple_ref/doc/uid/TP40009337-SW1">最新和修改了的API类</a>，你就会发现大部分是有关用户界面的。这是一个好消息，因为我们都对iPhone编程有了一定的经验。</p>
<p><br class="spacer_" /></p>
<p>此教程被称为“Hello World”，实际上它远远不止这些。首先，我假定你们已经具有iPhone/Objective-C的编程经验。</p>
<p><br class="spacer_" /></p>
<p><span id="more-923"></span></p>
<h3>我们要做什么</h3>
<p><br class="spacer_" /></p>
<p>今天的教程，我将展示怎样创建一个iPad项目，使用UISplitViewController显示两个单独的窗格。我还将涉及一些新的设计/UI模式并介绍iPad编程。</p>
<p><br class="spacer_" /></p>
<p>此项目将基于<a href="http://icodeblog.com/2008/08/08/iphone-programming-tutorial-populating-uitableview-with-an-nsarray/">使用UITableView显示水果列表教程</a> 。我们将扩展其中一个范例，如下所示：</p>
<p><br class="spacer_" /></p>
<p><a href="http://icodeblog.com/wp-content/uploads/2010/02/watermelon-ss.png"><img title="watermelon-ss" src="http://icodeblog.com/wp-content/uploads/2010/02/watermelon-ss.png" alt="" width="500" height="387" /></a></p>
<p><br class="spacer_" /></p>
<p>本文将使用UISplitViewController在左窗格显示一个UITableView，而在右窗格显示一个具有UIImageView子视窗的UIView。实际上创建这样一个项目非常简单，因为项目模板提供了大部分初始代码。</p>
<h3>开始</h3>
<p><br class="spacer_" /></p>
<ol>
<li>从<a href="http://developer.apple.com/iphone/">http://developer.apple.com/iphone/</a>下载SDK 3.2。iPad模拟器包括在了SDK中。 </li>
<li>下载本项目所需的资源文件 <a href="http://icodeblog.com/wp-content/uploads/2010/02/iPadHelloWorldResources.zip">iPadHelloWorldResources.zip</a> 。 </li>
</ol>
<h3>创建项目</h3>
<p><br class="spacer_" /></p>
<p>创建iPad项目与iPhone项目没有分别。打开XCode并选择File-&gt;New Project，你将发现有一个新的Split View-Based Application选项。选择它并命名为iPadHelloWorld。</p>
<p><a href="http://icodeblog.com/wp-content/uploads/2010/02/Screen-shot-2010-02-01-at-12.37.12-PM.png"><img title="Screen shot 2010-02-01  at 12.37.12 PM" src="http://icodeblog.com/wp-content/uploads/2010/02/Screen-shot-2010-02-01-at-12.37.12-PM.png" alt="" width="544" height="418" /></a></p>
<p>这将创建一个UITableView位于左窗格，UIView位于右窗格的应用程序。它还甚至提供了一些示例数据。下列文件将被加入到项目中。</p>
<p><br class="spacer_" /></p>
<p><a href="http://icodeblog.com/wp-content/uploads/2010/02/Screen-shot-2010-02-02-at-11.02.17-AM.png"><img title="Screen shot 2010-02-02  at 11.02.17 AM" src="http://icodeblog.com/wp-content/uploads/2010/02/Screen-shot-2010-02-02-at-11.02.17-AM.png" alt="" width="247" height="349" /></a></p>
<p><br class="spacer_" /></p>
<p>各文件的简单介绍如下：</p>
<p><br class="spacer_" /></p>
<ul>
<li>iPadHelloWorldAppDelegate – 类似于 app delegate。在 application:didFinishLaunchingWithOptions 方法中，创建了具有MasterViewController和 DetailViewController的UISplitViewController。 </li>
<li>MasterViewController – UITableViewController。它将处理左方视窗。 </li>
<li>DetailViewController – 它将处理右方视窗。当用户选择左方列表中不同行时，我们将在此视窗中做相应更新。 </li>
</ul>
<p><br class="spacer_" /></p>
<p>按“Build and Run”测试一下。</p>
<p><br class="spacer_" /></p>
<blockquote><p><strong>注意：当你运行此程序时，你只能看到主视窗，这是因为模拟器运行于垂直模式。选取 “Hardware -&gt; Rotate Left/Right”将使模拟器旋转。你还可以通过按下键盘上的CMD-&gt;Arrow Left/Right 键获得同样效果。</strong></p>
<p><strong><br />
 </strong></p>
</blockquote>
<h3>导入项目图像</h3>
<p><br class="spacer_" /></p>
<p>下一步你需要导入项目需要的图像。将下载的资源文件解压，并拖入称为“Resources-iPad”的文件夹下。</p>
<p>XCode 将提示你复制这些文件，选择“Yes”并按下OK。</p>
<p><br class="spacer_" /></p>
<p><a href="http://icodeblog.com/wp-content/uploads/2010/02/Screen-shot-2010-02-02-at-11.30.31-AM.png"><img style="display: block; float: none; margin-left: auto; margin-right: auto" title="Screen shot 2010-02-02  at 11.30.31 AM" src="http://icodeblog.com/wp-content/uploads/2010/02/Screen-shot-2010-02-02-at-11.30.31-AM.png" alt="" width="450" height="413" /></a></p>
<p><br class="spacer_" /></p>
<p>请包括四个图像文件以及fruits.plist。</p>
<h3>显示水果列表</h3>
<p><br class="spacer_" /></p>
<p>显示水果列表与显示其他UITableView中的数据并无区别。打开MasterViewController.h 加入水果数组定义。</p>
<pre>#import   

@class DetailViewController;   

@interface MasterViewController : UITableViewController {
    DetailViewController *detailViewController;
    NSArray * fruits;
} 

@property (nonatomic, retain) IBOutlet DetailViewController *detailViewController;
@property (nonatomic, retain) NSMutableArray *fruits;  

@end </pre>
<p>如你所见，这里没有任何新的东西。我们只是简单的定义了一个水果数组，并为其创建属性。</p>
<p><br class="spacer_" /></p>
<p>我们将从plist中加载水果数据。当你不想使用数据库时，从plist文件加载数据是一种方便而快捷的方法。</p>
<p><br class="spacer_" /></p>
<p>打开MasterViewController.m 在 viewDidLoad 方法中加入下列代码：</p>
<pre>- (void)viewDidLoad {
    [super viewDidLoad];
    self.fruits = [[NSArray arrayWithContentsOfFile:[[NSBundle mainBundle]
        pathForResource:@"fruits" ofType:@"plist"]] retain];
}</pre>
<p>fruits.plist文件实际上是一个写入文件的数组。如果你打开它，你就会发现它十分类似于XML。在为数组赋值后，我们要实现UITableView delegate 和 datasource 方法以显示列表。</p>
<p><strong> </strong></p>
<p><br class="spacer_" /></p>
<p><strong>UITableView datasource 方法</strong></p>
<pre>- (NSInteger)tableView:(UITableView *)aTableView
    numberOfRowsInSection:(NSInteger)section {
    // Return the number of rows in the section.
    return [fruits count];
}   </pre>
<pre>- (UITableViewCell *)tableView:(UITableView *)tableView
    cellForRowAtIndexPath:(NSIndexPath *)indexPath {  
    // Dequeue or create a cell of the appropriate type.
    static NSString *CellIdentifier = @"CellIdentifier";  
    UITableViewCell *cell =
        [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
           reuseIdentifier:CellIdentifier] autorelease];
        cell.accessoryType = UITableViewCellAccessoryNone;
    }   // Get the object to display and set the value in the cell.
    cell.textLabel.text = [self.fruits objectAtIndex:indexPath.row];
    return cell;
}</pre>
<p>没有什么特殊的东西… 我们首先告诉tableview 我们需要 fruits.count (等于4) 行。然后在各tableview单元中显示水果的名称。</p>
<p><br class="spacer_" /></p>
<p><strong>UITableView delegate 方法</strong></p>
<p><strong><br />
 </strong></p>
<pre>- (void)tableView:(UITableView *)aTableView
    didSelectRowAtIndexPath:(NSIndexPath *)indexPath {  
    /* When a row is selected, set the detail view controller's detail item
       to the item associated with the selected row.
     */
    detailViewController.detailItem = [self.fruits objectAtIndex: indexPath.row];
}
</pre>
<p>这里我们简单地将detailViewController 的detailItem 属性设置为所需水果。我们将在随后介绍一下属性，现在我们需要知道的是它的类型是 <strong>id。</strong></p>
<p><strong> </strong></p>
<p>现在，按下“Build and Run”运行一下。你应该看到如下画面：</p>
<p><br class="spacer_" /></p>
<p><a href="http://icodeblog.com/wp-content/uploads/2010/02/tableview.png"><img title="tableview" src="http://icodeblog.com/wp-content/uploads/2010/02/tableview.png" alt="" width="500" height="387" /></a></p>
<p><br class="spacer_" /></p>
<p>它列出了水果的列表，但当你进行选择时，没有任何变化（当然detailView的标题会改变）。</p>
<p><br class="spacer_" /></p>
<p>下面，我们需要实现显示相应水果图像的代码。</p>
<p><br class="spacer_" /></p>
<h3>显示水果</h3>
<p><br class="spacer_" /></p>
<p>显示所选水果实际上非常简单。首先我们要在detailView中加入一个UIImageView。打开DetailViewController.h 加入下列代码：</p>
<pre>@interface DetailViewController : UIViewController  {  
    UIPopoverController *popoverController;
    UINavigationBar *navigationBar;  
    id detailItem;  
    IBOutlet UIImageView * fruitImageView;
}   

@property (nonatomic, retain) UIPopoverController *popoverController;
@property (nonatomic, retain) IBOutlet UINavigationBar *navigationBar; 
@property (nonatomic, retain) id detailItem;  
@property (nonatomic, retain) IBOutlet UIImageView * fruitImageView;   

@end</pre>
<p>除了添加IBOutlet UIImageView外的所有代码都是由项目模板生成的。添加此行代码后，在Interface Builder中打开DetailView.xib。</p>
<p><br class="spacer_" /></p>
<p>在视窗中加入一个尺寸为500&#215;500的UIImageView。</p>
<p><br class="spacer_" /></p>
<p><a href="http://icodeblog.com/wp-content/uploads/2010/02/notitle.png"><img title="notitle" src="http://icodeblog.com/wp-content/uploads/2010/02/notitle.png" alt="" width="500" height="672" /></a></p>
<p><br class="spacer_" /></p>
<p>现在，选择File’s Owner 打开连接观察器 (Tools -&gt; connection inspector)。将imageView IBOutlet拖动到UIImageView 然后释放，将UIImageView 连接到你的outlet。</p>
<p><br class="spacer_" /></p>
<blockquote><p><strong>注意：如果你不希望图像变形，请将图像视窗的内容模式（content mode）设置为(在属性观察器attributes inspector中) Aspect Fit。</strong></p>
</blockquote>
<p><br class="spacer_" /></p>
<p>连接了imageview后，需要编写代码进行图像更新。关闭Interface Builder打开 DetailViewController.m 在setDetailItem 方法中加入下列代码：</p>
<pre>- (void)setDetailItem:(id)newDetailItem {
    if (detailItem != newDetailItem) {
        [detailItem release];
        detailItem = [newDetailItem retain];   // Update the view.
        navigationBar.topItem.title = detailItem;
	NSString * imageName = [NSString stringWithFormat:@"%@.png",detailItem];
	[self.fruitImageView setImage:[UIImage imageNamed:imageName]];
    }   if (popoverController != nil) {
        [popoverController dismissPopoverAnimated:YES];
    }
}</pre>
<p>大部分代码是由项目模板生成的，所以我不过多讨论。我添加的代码是根据水果的名称加载图像并随后设置其image属性。运行！下面是最终产品：</p>
<p><br class="spacer_" /></p>
<p><a href="http://icodeblog.com/wp-content/uploads/2010/02/cherry.png"><img title="cherry" src="http://icodeblog.com/wp-content/uploads/2010/02/cherry.png" alt="" width="500" height="387" /></a></p>
<h3>SplitViewController的另一个很酷的特性</h3>
<p><br class="spacer_" /></p>
<p>当出于垂直模式时，SplitViewController提供另一个称为UIPopOverView的新的UI元素。我们以后将就此提供教程。下面我仅给出示范图：</p>
<p><br class="spacer_" /></p>
<p><a href="http://icodeblog.com/wp-content/uploads/2010/02/popover.png"><img title="popover" src="http://icodeblog.com/wp-content/uploads/2010/02/popover.png" alt="" width="500" height="645" /></a></p>
<p><br class="spacer_" /></p>
<p>但设备处于垂直位置时，它将自动旋转视图并在“Master List”按钮按下时提供UIPopoverView。（另外，此按钮本身也是可以定制的）。</p>
<p><br class="spacer_" /></p>
<p>源码下载 <a href="http://icodeblog.com/wp-content/uploads/2010/02/iPadHelloWorld.zip">iPadHelloWorld.zip</a>.</p>
<p><br class="spacer_" /></p>
<h5>原文见：<a href="http://icodeblog.com/2010/04/05/ipad-programming-tutorial-hello-world/">iPad Programming Tutorial – Hello World++</a></h5>
]]></content:encoded>
			<wfw:commentRss>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/ipad-%e7%bc%96%e7%a8%8b%e6%95%99%e7%a8%8b-hello-world/feed</wfw:commentRss>
		<slash:comments>3</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%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>怎样编写Apple Push Notification服务器</title>
		<link>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e6%80%8e%e6%a0%b7%e7%bc%96%e5%86%99apple-push-notification%e6%9c%8d%e5%8a%a1%e5%99%a8</link>
		<comments>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e6%80%8e%e6%a0%b7%e7%bc%96%e5%86%99apple-push-notification%e6%9c%8d%e5%8a%a1%e5%99%a8#comments</comments>
		<pubDate>Fri, 29 Jan 2010 04:32:45 +0000</pubDate>
		<dc:creator>bagusflyer</dc:creator>
				<category><![CDATA[编程]]></category>
		<category><![CDATA[网络]]></category>
		<category><![CDATA[Push]]></category>
		<category><![CDATA[教程]]></category>

		<guid isPermaLink="false">http://www.iphone-geek.cn/?p=417</guid>
		<description><![CDATA[


iPhone OS 3.0一个引入注目的新特性是push notifications（推送通知），它允许向已安装相关应用程序的各设备直接发送消息。苹果在新闻提示或IM应用中展示了此特性，它也十分完美地适合于我们的服务器监视服务程序Server Density。



我们的程序提供一个选项，当你设定的某个服务器事件发生时，通知会直接发送到你的iPhone上。这是非常有用的因为它提醒用户立即打开我们的程序查看引起此警示的服务器详情。

Apple提供了有关实现和处理设备上提示消息的  iPhone OS 的详细代码文档 ，但它只包括消息提供者服务器端编程指南。

作为消息提供者，我们需要与 Apple推送通知服务 (APNS)连接以发送消息到iPhone。为减少电池使用，一个设备仅需维持与APNS的一个连接。

本教程将从代码的层面介绍关于怎样建立一个推送通知服务器以连接APNS并使用推送通知到我们的服务器监视iPhone程序上。我们是使用PHP进行开发的，我们的示例都是PHP 5兼容的。
  
基本结构

使用唯一的SSL许可证连接到APNS

循环通过你需要发送到消息
为各消息构建有效载荷
断开与 APNS的连接

远程通知数据的流程是单向的。提供者将包括客户程序设备令牌和有效载荷的数据打包，发送到APNS，然后APNS再将通知发送给最终设备。


- Apple 文档

  
限制

有效载荷限制为256字节 &#8211; 它包括了消息主体以及你希望传送带其他属性。推送通知并不适于传送大量的数据。例如，我们仅仅传送一条短消息通知服务器监视的事件已经被触发了。
APNS并不提供消息发送成功与否的回馈状态。一个原因是如果一个设备无法联系那么发送给它的消息将被存于队列中，然而只有最新发送的消息被存于队列中 &#8211; 覆盖了先前发送但不成功的消息。
推送通知不适合用于发送紧急通知，因为消息仅在设备具有wifi或手机服务连接的情况下才能被发送，这也是为什么我们推荐与其它方法如email或SMS一起使用的原因。
用来与APNS通讯的SSL许可证（下面将讨论）是在程序层生成的。本教程涉及到实现方法仅适于单个iPhone程序，所以如果你有多个程序，那么你需要修改代码使之适合于使用多个许可证。

  
设备令牌

每条推送消息都必须针对某特定设备。这是通过使用在你的iPhone程序中由APNS产生的唯一deviceToken（设备令牌）来实现的。一旦获取了此令牌，你需要将其存储于服务器而不是你的iPhone程序内。它看上去像这样：
c9d4c07c fbbc26d6 ef87a44d 53e16983 1096a5d5 fd825475 56659ddd f715defc

在我们的 Server Density iPhone 程序中，我们在程序启动时调用相应的令牌生成方法，然后通过 HTTP API 调用 传回给我们的服务器 。这将使得deviceToken存储于服务器的有关用户的数据库中，从而我们可以使用它与持有此设备的用户进行通讯。
  
反馈服务

Apple 还提供了一个 反馈服务 ，你应该定期查询。它提供了一个以前使用过但不再有效的（例如用户卸载了你的iPhone程序）设备令牌列表。你可以从你的数据库中删除这些设备令牌。

本教程不涉及反馈服务的使用。
  
许可证

要进行推送服务的第一件事就是获取推送许可证。它用来对你通过SSL与APNS通讯进行识别。

在Mac上生成 Apple推送通知SSL许可证：

登录到 iPhone Developer Connection Portal 并点击 App IDs
创建一个不使用通配符的 App ID 。通配符 ID 不能用于推送通知服务。例如，我们的iPhone程序ID像这样：  AB123346CD.com.serverdensity.iphone
点击App ID旁的“Configure”，然后按下按钮生产 推送通知许可证。根据“向导”指导的步骤生成一个签名并上传，最后下载生成的许可证。此步骤在 Apple文档中 也有谈到。
通过双击.cer文件将你的 aps_developer_identity.cer 引入Keychain中。
在Mac上启动 Keychain助手，然后在login [...]]]></description>
			<content:encoded><![CDATA[<div><!--end meta--></div>
<div>
<div>
<p><a href="http://www.apple.com/iphone/softwareupdate/">iPhone OS 3.0</a>一个引入注目的新特性是<a href="http://developer.apple.com/iphone/program/sdk/apns.html">push notifications</a>（推送通知），它允许向已安装相关应用程序的各设备直接发送消息。苹果在新闻提示或IM应用中展示了此特性，它也十分完美地适合于我们的服务器监视服务程序<a href="http://www.serverdensity.com/">Server Density</a>。</p>
<p><br class="spacer_" /></p>
<p><a href="http://boxedice.files.wordpress.com/2009/07/sd-iphone-push-alert.png"><img title="Server monitoring iPhone application alert view" src="http://boxedice.files.wordpress.com/2009/07/sd-iphone-push-alert.png?w=200&amp;h=300" alt="Server monitoring iPhone application alert view" width="200" height="300" /></a></p>
<p><span id="more-417"></span></p>
<p>我们的程序提供一个选项，当你设定的某个服务器事件发生时，通知会直接发送到你的iPhone上。这是非常有用的因为它提醒用户立即打开我们的程序查看引起此警示的服务器详情。</p>
<p><br class="spacer_" /></p>
<p>Apple提供了有关实现和处理设备上提示消息的 <a href="http://developer.apple.com/iphone/library/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/IPhoneOSClientImp/IPhoneOSClientImp.html"> iPhone OS 的详细代码文档</a> ，但它只包括消息提供者服务器端编程指南。</p>
<p><br class="spacer_" /></p>
<p>作为消息提供者，我们需要与 Apple推送通知服务 (APNS)连接以发送消息到iPhone。为减少电池使用，一个设备仅需维持与APNS的一个连接。</p>
<p><br class="spacer_" /></p>
<p>本教程将从代码的层面介绍关于怎样建立一个推送通知服务器以连接APNS并使用推送通知到我们的服务器监视iPhone程序上。我们是使用PHP进行开发的，我们的示例都是PHP 5兼容的。</p>
<h3><strong> </strong> </h3>
<h3><strong>基本结构</strong></h3>
<p><br class="spacer_" /></p>
<p>使用唯一的SSL许可证连接到APNS</p>
<ol>
<li>循环通过你需要发送到消息</li>
<li>为各消息构建有效载荷</li>
<li>断开与 APNS的连接</li>
</ol>
<blockquote><p>远程通知数据的流程是单向的。提供者将包括客户程序设备令牌和有效载荷的数据打包，发送到APNS，然后APNS再将通知发送给最终设备。</p>
</blockquote>
<p><br class="spacer_" /></p>
<p>- <a href="http://developer.apple.com/iphone/library/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/ApplePushService/ApplePushService.html">Apple 文档</a></p>
<p><a href="http://boxedice.files.wordpress.com/2009/07/remote_notif_simple.jpg"><img title="APNS Flow" src="http://boxedice.files.wordpress.com/2009/07/remote_notif_simple.jpg?w=600&amp;h=82" alt="APNS Flow" width="600" height="82" /></a></p>
<h3><strong> </strong> </h3>
<h3><strong>限制</strong></h3>
<ul>
<li>有效载荷限制为256字节 &#8211; 它包括了消息主体以及你希望传送带其他属性。推送通知并不适于传送大量的数据。例如，我们仅仅传送一条短消息通知服务器监视的事件已经被触发了。</li>
<li>APNS并不提供消息发送成功与否的回馈状态。一个原因是如果一个设备无法联系那么发送给它的消息将被存于队列中，然而只有最新发送的消息被存于队列中 &#8211; 覆盖了先前发送但不成功的消息。</li>
<li>推送通知不适合用于发送紧急通知，因为消息仅在设备具有wifi或手机服务连接的情况下才能被发送，这也是为什么我们推荐与其它方法如email或SMS一起使用的原因。</li>
<li>用来与APNS通讯的SSL许可证（下面将讨论）是在程序层生成的。本教程涉及到实现方法仅适于单个iPhone程序，所以如果你有多个程序，那么你需要修改代码使之适合于使用多个许可证。</li>
</ul>
<h3><strong> </strong> </h3>
<h3><strong>设备令牌</strong></h3>
<p><br class="spacer_" /></p>
<p>每条推送消息都必须针对某特定设备。这是通过使用在你的iPhone程序中由APNS产生的唯一deviceToken（设备令牌）来实现的。一旦获取了此令牌，你需要将其存储于服务器而不是你的iPhone程序内。它看上去像这样：</p>
<p>c9d4c07c fbbc26d6 ef87a44d 53e16983 1096a5d5 fd825475 56659ddd f715defc</p>
<p><br class="spacer_" /></p>
<p>在我们的 Server Density iPhone 程序中，我们在程序启动时调用相应的令牌生成方法，然后通过 <a href="http://www.serverdensity.com/docs/api/functions/#iphone-setdevicetoken">HTTP API 调用</a> 传回给我们的服务器 。这将使得deviceToken存储于服务器的有关用户的数据库中，从而我们可以使用它与持有此设备的用户进行通讯。</p>
<h3><strong> </strong> </h3>
<h3><strong>反馈服务</strong></h3>
<p><br class="spacer_" /></p>
<p>Apple 还提供了一个 <a href="http://developer.apple.com/iphone/library/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingWIthAPS/CommunicatingWIthAPS.html#//apple_ref/doc/uid/TP40008194-CH101-SW3">反馈服务</a> ，你应该定期查询。它提供了一个以前使用过但不再有效的（例如用户卸载了你的iPhone程序）设备令牌列表。你可以从你的数据库中删除这些设备令牌。</p>
<p><br class="spacer_" /></p>
<p>本教程不涉及反馈服务的使用。</p>
<h3><strong> </strong> </h3>
<h3><strong>许可证</strong></h3>
<p><br class="spacer_" /></p>
<p>要进行推送服务的第一件事就是获取推送许可证。它用来对你通过SSL与APNS通讯进行识别。</p>
<p><br class="spacer_" /></p>
<p>在Mac上生成 Apple推送通知SSL许可证：</p>
<ol>
<li>登录到 <a href="http://developer.apple.com/iphone/manage/overview/index.action">iPhone Developer Connection Portal</a> 并点击 App IDs</li>
<li>创建一个不使用通配符的 App ID 。通配符 ID 不能用于推送通知服务。例如，我们的iPhone程序ID像这样：  AB123346CD.com.serverdensity.iphone</li>
<li>点击App ID旁的“Configure”，然后按下按钮生产 推送通知许可证。根据“向导”指导的步骤生成一个签名并上传，最后下载生成的许可证。此步骤在 <a href="http://developer.apple.com/iphone/prerelease/library/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/ProvisioningDevelopment/ProvisioningDevelopment.html#//apple_ref/doc/uid/TP40008194-CH104-SW4">Apple文档中</a> 也有谈到。</li>
<li>通过双击.cer文件将你的 aps_developer_identity.cer 引入Keychain中。</li>
<li>在Mac上启动 Keychain助手，然后在login keychain中选择 Certificates分类。你将看到一个可扩展选项“Apple Development Push Services”</li>
<li>扩展此选项然后右击“Apple Development Push Services” > Export “Apple Development Push Services ID123”。保存为 apns-dev-cert.p12 文件。</li>
<li>扩展“Apple Development Push Services” 对“Private Key”做同样操作，保存为 apns-dev-key.p12 文件。</li>
<li>需要通过终端命令将这些文件转换为PEM格式：
<pre>openssl pkcs12 -clcerts -nokeys -out apns-dev-cert.pem -in apns-dev-cert.p12
openssl pkcs12 -nocerts -out apns-dev-key.pem -in apns-dev-key.p12</pre>
</li>
<li>如果你想要移除密码，要么在导出/转换时不要设定或者执行：
<pre>openssl rsa -in apns-dev-key.pem -out apns-dev-key-noenc.pem</pre>
</li>
<li>最后，你需要将键和许可文件合成为apns-dev.pem文件，此文件在连接到APNS时需要使用：
<pre>cat apns-dev-cert.pem apns-dev-key-noenc.pem > apns-dev.pem</pre>
</li>
</ol>
<p><br class="spacer_" /></p>
<p>将此文件保存为一个易记的名字，你有可能以后会用到它。上述步骤同样适合于生成产品许可证。</p>
<h3><strong> </strong> </h3>
<h3><strong>载荷内容</strong></h3>
<p><br class="spacer_" /></p>
<p><a href="http://developer.apple.com/iphone/library/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/ApplePushService/ApplePushService.html#//apple_ref/doc/uid/TP40008194-CH100-SW1">载荷</a> 格式化为遵循 RFC 4627标准的JSON格式。它由以下几部分组成：</p>
<ul>
<li>提示 &#8211; 显示于设备上的文本串</li>
<li>标识 &#8211; 设备屏幕中程序图标上显示的整数</li>
<li>声音 &#8211;  显示消息在设备的同时发出的声音的文本名字</li>
<li>本教程仅处理发送简单提示文本串，但也可以发送包括诸如显示自定义按钮等在内的各种选项的字典集。</li>
</ul>
<h3><strong> </strong> </h3>
<h3><strong>创建载荷</strong></h3>
<p><br class="spacer_" /></p>
<p>使用 PHP 很容易根据数组并 <a href="http://www.php.net/manual/en/function.json-encode.php">转换成 JSON</a>而创建载荷:</p>
<pre>$payload['aps'] = array('alert' => 'This is the alert text', 'badge' => 1, 'sound' => 'default');
$payload = json_encode($payload);</pre>
<p><br class="spacer_" /></p>
<p>显示 $payload 的内容可以看到传送到<span style="font-family: Georgia;">APNS 的</span> JSON字符串：</p>
<pre>{
     "aps" : { "alert" : "This is the alert text", "badge" : 1, "sound" : "default" }
}</pre>
<p><br class="spacer_" /></p>
<p>这将使消息显示于设备上，触发提升声音并将“1”置于程序图标上。默认按钮“Close”和“View”同时会显示于弹出窗口上。</p>
<p><br class="spacer_" /></p>
<p>对于 Server Density iPhone程序而言，让用户按下“View”直接进入产生此提示的服务器是很重要的，所以我们增加了额外的自定义值：</p>
<pre>$payload['aps'] = array('alert' => 'This is the alert text', 'badge' => 1, 'sound' => 'default');
$payload['server'] = array('serverId' => $serverId, 'name' => $name);
$output = json_encode($payload);</pre>
<p><br class="spacer_" /></p>
<p>当用户按下“View”后，自定义server值将被传递到设备中的程序。JSON 值如下：</p>
<pre>{
     "aps" : { "alert" : "This is the alert text", "badge" : 1, "sound" : "default" },
     "server" : { "serverId" : 1, "name" : "Server name")
}</pre>
<p><br class="spacer_" /></p>
<p>256字节的限制适用于整个载荷，包括自定义字典集。</p>
<h3><strong> </strong> </h3>
<h3><strong>原生接口</strong></h3>
<p><br class="spacer_" /></p>
<p>在Server Density中，一旦产生了一条提示，将建立一个载荷并插入队列中。因此有必要时我们可以同时发送多个载荷。</p>
<p><br class="spacer_" /></p>
<p>Apple推荐使用这种方法，因为如果你在发送各载荷时频繁连接和断开，APNS有可能会封锁你的IP。</p>
<p>如Apple <a href="http://developer.apple.com/iphone/library/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingWIthAPS/CommunicatingWIthAPS.html#//apple_ref/doc/uid/TP40008194-CH101-SW4">描述</a>:</p>
<blockquote><p>原生接口使用原生socket，具有二进制内容，采用数据流技术，不产生回馈。</p>
</blockquote>
<p><a href="http://boxedice.files.wordpress.com/2009/07/aps_provider_binary.jpg"><img title="APNS Binary Format" src="http://boxedice.files.wordpress.com/2009/07/aps_provider_binary.jpg?w=522&amp;h=111" alt="APNS Binary Format" width="522" height="111" /></a></p>
<h3><strong> </strong> </h3>
<h3><strong>打开连接</strong></h3>
<p><br class="spacer_" /></p>
<p>打开连接的 PHP 5代码如下：</p>
<pre>$apnsHost = 'gateway.sandbox.push.apple.com';
$apnsPort = 2195;
$apnsCert = 'apns-dev.pem';

$streamContext = stream_context_create();
stream_context_set_option($streamContext, 'ssl', 'local_cert', $apnsCert);

$apns = stream_socket_client('ssl://' . $apnsHost . ':' . $apnsPort, $error, $errorString, 2,
STREAM_CLIENT_CONNECT, $streamContext);</pre>
<p><br class="spacer_" /></p>
<p>如果发送错误，你可以参考$errorString。它也包括了SSL许可证不正确时的详细信息。</p>
<p><br class="spacer_" /></p>
<p>许可证文件处于执行的PHP代码的当前工作目录下，如果需要你可指定其绝对路径。</p>
<p>注意测试时应该使用开发许可证及sandbox。成品主机名为 gateway.push.apple.com ，而且你必须使用不同的产品许可证。</p>
<h3><strong> </strong> </h3>
<h3><strong>发送载荷</strong></h3>
<p><br class="spacer_" /></p>
<p>在此，我们循环整个载荷队列进行发送。构建发送到APNS的二进制内容简单示例如下：</p>
<pre>$apnsMessage = chr(0) . chr(0) . chr(32) . pack('H*', str_replace(' ', '', $deviceToken)) . chr(0) .
chr(strlen($payload)) . $payload;
fwrite($apns, $apnsMessage);</pre>
<p><br class="spacer_" /></p>
<p>注意 $deviceToken 是从数据库中提取并去除空格得到的。我们还应该检查是否$payload超过256个字节。</p>
<p><br class="spacer_" /></p>
<p>$apnsMessage 包括了正确的二进制载荷，而fwrite 将载荷写入当前活动的数据流连接中。</p>
<p><br class="spacer_" /></p>
<p>完成后，应关闭连接：</p>
<pre>socket_close($apns);
fclose($apns);</pre>
<p><strong> </strong> </p>
<p><strong>php-apns</strong></p>
<p><br class="spacer_" /></p>
<p>有一个开源服务器库<a href="http://code.google.com/p/php-apns/">php-apns</a>实现了以上所有功能，它依赖于 <a href="http://www.danga.com/memcached/">memcached</a>。我们不想使用任何第三方代码，所以完全自己编写了自己的服务器。我们使用自定义cron系统，几秒钟运行一次。</p>
<h5> </h5>
<h5>原文见：<a href="http://blog.boxedice.com/2009/07/10/how-to-build-an-apple-push-notification-provider-server-tutorial/">How to build an Apple Push Notification provider server (tutorial)</a></h5>
</div>
</div>
]]></content:encoded>
			<wfw:commentRss>http://www.iphone-geek.cn/%e7%bc%96%e7%a8%8b/%e6%80%8e%e6%a0%b7%e7%bc%96%e5%86%99apple-push-notification%e6%9c%8d%e5%8a%a1%e5%99%a8/feed</wfw:commentRss>
		<slash:comments>2</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>
	</channel>
</rss>
