<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/rss2full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><rss 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/" version="2.0">

<channel>
	<title>windam.log</title>
	
	<link>http://www.windameister.org/blog</link>
	<description>learn, think, share, communication</description>
	<lastBuildDate>Fri, 04 Nov 2011 16:28:39 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/rss+xml" href="http://feeds.feedburner.com/windamlog" /><feedburner:info xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" uri="windamlog" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><item>
		<title>Crysis 地形渲染技术剖析——材质与LOD</title>
		<link>http://www.windameister.org/blog/2011/11/04/crysis-terrain-render-tech-anaylsis-material-and-lod/</link>
		<comments>http://www.windameister.org/blog/2011/11/04/crysis-terrain-render-tech-anaylsis-material-and-lod/#comments</comments>
		<pubDate>Thu, 03 Nov 2011 16:09:00 +0000</pubDate>
		<dc:creator>windam</dc:creator>
				<category><![CDATA[3D渲染]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[Crysis]]></category>
		<category><![CDATA[terrain]]></category>
		<category><![CDATA[地形渲染]]></category>

		<guid isPermaLink="false">http://www.windameister.org/blog/?p=314</guid>
		<description><![CDATA[材质 Crysis地形的一个最重要的特色是它的地表材质系统。当进入到一定距离之后，可以看到非常精细的地表材质，包括Bump Mapping，Parallax Occlusion Mapping，有了材质系统的支持，Crysis的地形相较基于传统的Texture Splatting的地形可以呈现出令人惊叹的细节。 在POM技术支撑下的Crysis地形： 基于Texture Splatting技术的地形： Crysis的地表材质技术的原理如下： 渲染包括两个步骤，首先基于颜色图和法线图，绘制基本的地形色彩与明暗，这一遍渲染结果主要用于远处地形。然后会针对不同材质，将细节通过AlphaBlend融合到之前的渲染结果上，材质细节通常只在离视野较近的区域内才渲染，因此第二个步骤的耗费并不多，根据实际情况，每帧中大约会有从一百到数百个不等的细节层Mesh被绘制。 第一遍基于颜色图渲染的效果如下： 可以看到，颜色图上提供了明暗细节，这对地形渲染中无处不在的Tiling问题起到了缓解作用。 第二遍Blend上细节之后的效果： Crysis为每个顶点赋予一个材质id，并且根据不同的材质id，将原先完整的地形网格划分成按材质组织的小块网格，（如果整块32×32的网格都是同一种材质，则无需划分），小块网格是通过索引区段的方式定义的。在Crysis中，地形顶点数据结构里Color属性的g分量被用了作材质层的ID。 下图展示了一个网格中的33×33个顶点被区分为两种材质的情况。本图为演示，将顶点颜色涂成不同的颜色，Crysis则是将将材质id通过顶点颜色传入vs。下面的网格中存在两种不同的材质需要分别渲染。 Crysis根据顶点材质id将整块Mesh拆分成以材质id分组的小块Mesh，如图： 不同材质的Mesh之间，有一个三角形的过渡带。 上图中，红色箭头表示草的材质从1过渡到0，蓝色箭头表示沙子材质从1过渡到0。 这个过渡带起到的作用正是让一个处于材质交汇带的三角形，其Alpha可以从1过渡到0。这样和相邻的隶属另一个材质正好融合到一起。 因此，材质过渡区域的三角形会被渲染2到3遍，取决于该三角形的三个顶点分属于2个材质还是3个材质。 上述的融合过程是通过下面这段代码做到的（本文只为解释原理，与真实代码有出入，Crysis的Shader代码没有加密，有心人可自己解包查阅）： 声明常量： float g_InputLayerId; 该常量通过程序传入VS常量中，表示当前渲染Mesh的LayerId。 在vs中计算顶点alpha： float alpha = 1- saturate (g_InputLayerId – Input.Color.g * 255.0f); Input.Color.g中存储的是该顶点所属的LayerId，由于是通过COLOR存储，取值范围是从0.0到1.0，因此在shader里用于计算之前，还需要乘以255.0f还原。 上式的逻辑如下： 如果InputLayerId和当前顶点存储的layerId相同，则alpha为1，否则，如g_InputLayerId与当前顶点上的layerId不同，则alpha为0。 因为可以使用不同材质做细节层的渲染，Bump Mapping，Parallax Occlusion Mapping这类技术对于地表细节的表现提升极为明显。除了Bump Map之外，Crysis还将Detail Bump Map，Specular Map等用于提升材质细节的技术纳入到地形细节层的渲染中。 LOD Crysis为了支撑8公里乘8公里的超大视距（由于Crysis中的网格Size是2米一格，一块地形拥有多达4096*4096个网格），在最高精度下，总共三千二百万个三角形。因此必须有一套合理的地形LOD方案。 Crysis的地形LOD，从地形LOD算法的分类上看，是基于Geo-Mipmapping的。不过相比最传统的Geo-Mipmapping，Crysis利用四叉树的特性，并且考虑到最新图形硬件的特点，针对地形网格做了相当多的优化。 首先解释一下Geo-Mipmapping的基本思想： Geo-Mipmapping始于Willem H. de [...]]]></description>
			<content:encoded><![CDATA[<h4>材质</h4>
<p>Crysis地形的一个最重要的特色是它的地表材质系统。当进入到一定距离之后，可以看到非常精细的地表材质，包括Bump Mapping，Parallax Occlusion Mapping，有了材质系统的支持，Crysis的地形相较基于传统的Texture Splatting的地形可以呈现出令人惊叹的细节。</p>
<p>在POM技术支撑下的Crysis地形：</p>
<p><a href="http://www.windameister.org/blog/wp-content/uploads/2011/11/clip_image002.jpg"><img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="clip_image002" src="http://www.windameister.org/blog/wp-content/uploads/2011/11/clip_image002_thumb.jpg" alt="clip_image002" width="651" height="322" border="0" /></a></p>
<p>基于Texture Splatting技术的地形：</p>
<p><a href="http://www.windameister.org/blog/wp-content/uploads/2011/11/clip_image004.jpg"><img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="clip_image004" src="http://www.windameister.org/blog/wp-content/uploads/2011/11/clip_image004_thumb.jpg" alt="clip_image004" width="650" height="416" border="0" /></a></p>
<p>Crysis的地表材质技术的原理如下：</p>
<p>渲染包括两个步骤，首先基于颜色图和法线图，绘制基本的地形色彩与明暗，这一遍渲染结果主要用于远处地形。然后会针对不同材质，将细节通过AlphaBlend融合到之前的渲染结果上，材质细节通常只在离视野较近的区域内才渲染，因此第二个步骤的耗费并不多，根据实际情况，每帧中大约会有从一百到数百个不等的细节层Mesh被绘制。<span id="more-314"></span></p>
<p>第一遍基于颜色图渲染的效果如下：</p>
<p><a href="http://www.windameister.org/blog/wp-content/uploads/2011/11/clip_image006.jpg"><img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="clip_image006" src="http://www.windameister.org/blog/wp-content/uploads/2011/11/clip_image006_thumb.jpg" alt="clip_image006" width="647" height="342" border="0" /></a></p>
<p>可以看到，颜色图上提供了明暗细节，这对地形渲染中无处不在的Tiling问题起到了缓解作用。</p>
<p>第二遍Blend上细节之后的效果：</p>
<p><a href="http://www.windameister.org/blog/wp-content/uploads/2011/11/clip_image008.jpg"><img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="clip_image008" src="http://www.windameister.org/blog/wp-content/uploads/2011/11/clip_image008_thumb.jpg" alt="clip_image008" width="651" height="336" border="0" /></a></p>
<p>Crysis为每个顶点赋予一个材质id，并且根据不同的材质id，将原先完整的地形网格划分成按材质组织的小块网格，（如果整块32×32的网格都是同一种材质，则无需划分），小块网格是通过索引区段的方式定义的。在Crysis中，地形顶点数据结构里Color属性的g分量被用了作材质层的ID。</p>
<p>下图展示了一个网格中的33×33个顶点被区分为两种材质的情况。本图为演示，将顶点颜色涂成不同的颜色，Crysis则是将将材质id通过顶点颜色传入vs。下面的网格中存在两种不同的材质需要分别渲染。</p>
<p><a href="http://www.windameister.org/blog/wp-content/uploads/2011/11/clip_image010.jpg"><img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="clip_image010" src="http://www.windameister.org/blog/wp-content/uploads/2011/11/clip_image010_thumb.jpg" alt="clip_image010" width="507" height="398" border="0" /></a></p>
<p>Crysis根据顶点材质id将整块Mesh拆分成以材质id分组的小块Mesh，如图：</p>
<p>不同材质的Mesh之间，有一个三角形的过渡带。</p>
<p><a href="http://www.windameister.org/blog/wp-content/uploads/2011/11/clip_image012.jpg"><img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="clip_image012" src="http://www.windameister.org/blog/wp-content/uploads/2011/11/clip_image012_thumb.jpg" alt="clip_image012" width="643" height="361" border="0" /></a></p>
<p>上图中，红色箭头表示草的材质从1过渡到0，蓝色箭头表示沙子材质从1过渡到0。</p>
<p>这个过渡带起到的作用正是让一个处于材质交汇带的三角形，其Alpha可以从1过渡到0。这样和相邻的隶属另一个材质正好融合到一起。</p>
<p>因此，材质过渡区域的三角形会被渲染2到3遍，取决于该三角形的三个顶点分属于2个材质还是3个材质。</p>
<p>上述的融合过程是通过下面这段代码做到的（本文只为解释原理，与真实代码有出入，Crysis的Shader代码没有加密，有心人可自己解包查阅）：</p>
<p>声明常量：</p>
<p>float g_InputLayerId;</p>
<p>该常量通过程序传入VS常量中，表示当前渲染Mesh的LayerId。</p>
<p>在vs中计算顶点alpha：</p>
<p>float alpha = 1- saturate (g_InputLayerId – Input.Color.g * 255.0f);</p>
<p>Input.Color.g中存储的是该顶点所属的LayerId，由于是通过COLOR存储，取值范围是从0.0到1.0，因此在shader里用于计算之前，还需要乘以255.0f还原。</p>
<p>上式的逻辑如下：</p>
<p>如果InputLayerId和当前顶点存储的layerId相同，则alpha为1，否则，如g_InputLayerId与当前顶点上的layerId不同，则alpha为0。</p>
<p>因为可以使用不同材质做细节层的渲染，Bump Mapping，Parallax Occlusion Mapping这类技术对于地表细节的表现提升极为明显。除了Bump Map之外，Crysis还将Detail Bump Map，Specular Map等用于提升材质细节的技术纳入到地形细节层的渲染中。</p>
<h4>LOD</h4>
<p>Crysis为了支撑8公里乘8公里的超大视距（由于Crysis中的网格Size是2米一格，一块地形拥有多达4096*4096个网格），在最高精度下，总共三千二百万个三角形。因此必须有一套合理的地形LOD方案。</p>
<p><a href="http://www.windameister.org/blog/wp-content/uploads/2011/11/clip_image014.jpg"><img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="clip_image014" src="http://www.windameister.org/blog/wp-content/uploads/2011/11/clip_image014_thumb.jpg" alt="clip_image014" width="651" height="416" border="0" /></a></p>
<p>Crysis的地形LOD，从地形LOD算法的分类上看，是基于<a href="http://www.flipcode.com/archives/Fast_Terrain_Rendering_Using_Geometrical_MipMapping.shtml">Geo-Mipmapping</a>的。不过相比最传统的Geo-Mipmapping，Crysis利用四叉树的特性，并且考虑到最新图形硬件的特点，针对地形网格做了相当多的优化。</p>
<p>首先解释一下Geo-Mipmapping的基本思想：</p>
<p>Geo-Mipmapping始于<a href="mailto:whdeboer@iname.com">Willem H. de Boer</a>的论文<a href="http://www.flipcode.com/tutorials/tut_geomipmaps.shtml">Fast Terrain Rendering Using Geometrical MipMapping</a> (2000)。</p>
<p>与近处的地形相比，离摄像机远的Block不必采用同样的细节的网格，我们可以采用一个更低精度的版本来近似。通过减少三角形的数量，获得渲染效率上的提升。</p>
<p>考虑传统意义上的纹理MipMapping技术。对每张纹理，都会创建一条mipmap链，链中的第一级贴图就是实际的纹理，而后的每一级都是将上一级的分辨率缩小一半得到的。当贴图距摄像机一定距离的时候，mipmap链中的合适的层级会被选择出来用于渲染，而不是选择最高分辨率的纹理。我们也可以将同样的原理应用到三维网格中，在地形中，等价于纹理的就是地形Block，地形Block的mipmap链则由最高分辨率版本的Block通过缩小分辨率获得。</p>
<p><a href="http://www.windameister.org/blog/wp-content/uploads/2011/11/clip_image016.jpg"><img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="clip_image016" src="http://www.windameister.org/blog/wp-content/uploads/2011/11/clip_image016_thumb.jpg" alt="clip_image016" width="383" height="356" border="0" /></a></p>
<p>如上图：（图来自于<a href="http://www.flipcode.com/archives/article_geomipmaps.pdf">这里</a>）</p>
<p>白色的点位MipMap Level 0，黑点为MipMap Level 1，依次类推。</p>
<p>传统的GeoMipMapping技术就是采用上述方案，例如对一个32×32格子的原始网格依次创建16×16，8×8格子的MipMap链。随着地形Block到摄像机距离的增加，逐渐采用越来越稀疏的版本。</p>
<p>Crysis的地形LOD的基础就是基于上述思想——其实这是废话，基本上大多数的地形LOD算法都是基于这个思想。</p>
<p>所不同的是，Crysis利用四叉树结构对GeoMipMapping做了优化。</p>
<p>Crysis渲染的基本单位是32×32的网格。也就是说，一次draw call最多向图形管线传递32×32×2个三角形。</p>
<p><strong>随着距离增加，32*32</strong><strong>的网格会降级成16*16</strong><strong>，如果四个紧挨着的Block</strong><strong>都变成16</strong><strong>×16</strong><strong>，且这四个Block</strong><strong>都是同一个父节点的子，则会合并成一个LOD</strong><strong>级别更高的32</strong><strong>×32</strong><strong>的网格。</strong></p>
<p>换一个角度看，把地形Mesh看成一棵四叉树，处于叶节点的Block拥有最高精度网格，每向上一层，精度都会缩减一半（总的分辨率变成之前的一半）。</p>
<p>这样假设叶节点层（第0层）的Mesh是一个1025*1025个网格，那么第1层就是513*513的网格，第2层257*257，第3层129*129，第4层65*65，第5层33*33。</p>
<p>如果我们选择第5层进行渲染，只需要一次Draw Call就可以将整个Mesh绘制完毕，当然在精度上也会有很大损失。因此，可以这样考虑，从根节点开始遍历，如果当前节点距离摄像机的距离很近，以至于该节点自身的细节不足够精确，并且该节点还有子节点，则我们将该节点的四个子节点而非该节点本身置入渲染队列，否则意味着该节点的细节就足以应付需求，因此可以只渲染该节点。</p>
<p>上述方式由于为整个Mesh创建了一个MipMap链，因此会增加33%的冗余顶点数据，但是利用这个原理，Crysis成功将4096×4096的地形DrawCall次数降低到了300～500次。这便是Crysis能够高效渲染超大视距地形的原理。</p>
<p>&nbsp;</p>
<p>最后谈谈针对GPU的优化：</p>
<p>现代GPU的特点是有大量的流处理器，支持高并发的运算量，顶点计算也好，像素计算也好，都是可以高度并行化的，现代GPU针对这些做了大量的工作。</p>
<p>GPU的显存越做越大，1GB的显存已经是小儿科，动辄2GB，甚至更多的显存也能在市场上见到。</p>
<p>也就是说，随着GPU的发展，运算和存储都逐渐不再是瓶颈——那么相比之下，通过PCIE总线把数据从CPU传输到显卡则成了一个制约性能的瓶颈所在了。</p>
<p>以往我们习惯于每帧Lock一个Buffer，然后通过CPU动态填充数据到Buffer里，再把这个Buffer提交到Device上去用于渲染。这种做法就会导致每帧都要传递大量的数据到显卡上去。相比之下，现代GPU的结构更倾向于让我们进行这样的渲染：在初始化阶段就将数据创建成静态的Buffer并存放于显存里，每帧渲染都利用这些现有的Buffer。</p>
<p>针对地形来说，顶点数据很好说，因为本来就不会在运行期进行修改，因此可以直接创建成静态的Buffer，而索引数据往往会引发疑惑，因为LOD时时刻刻都在变化。地形LOD因为每帧要动态更新Block的LOD状态（包括自身的LOD级别以及和相邻Block之间的接缝），因此不得不去每帧Lock索引的Buffer，动态填充。</p>
<p>但如果仔细想想，就会发现索引也是可以预计算的。（Crysis就采用了预算好的索引Buffer）</p>
<p>考虑一个32×32的网格，它的索引大小为32*32*2(三角形) * 3(索引)* 2(sizeof(WORD))= 12KB</p>
<p>考虑周围邻接Block的LOD级别高于它的情况，每条邻接边都可能是[16, 8, 4, 2, 1]个Grid，也就是有5种情况，一共有4个邻接边。这样总的数据量是：5* 5* 5 * 5 * 12KB 约等于 7.32 MB。这是我们允许跨5级LOD的极端情况，在Crysis中我没有观察到跨5级LOD的情况，而如果我们只允许跨3级LOD，也就是对于32*32的Block的邻接Block只允许[16, 8, 4]三种情况，则总的数据量约为 3 * 3 * 3 * 3 * 12KB = 972KB。</p>
<p>如果每个Block内部允许存在3级LOD，也就是[32*32, 16*16, 8*8]三种，其中16* 16的网格，其索引数据占用空间为32*32的1/4，即3KB，8*8网格的索引数据则不到1KB。</p>
<p>也就是说，即便允许Block内部3级LOD，同时允许每个Block与邻接Block跨3级LOD级别，总的索引数据量也不过在1.5MB左右。完全可以预计算好然后存在显存里。</p>
<p>在渲染当中，当Block的LOD级别被确定下来的时候，根据Block的内部LOD级别以及该Block与邻接Block的LOD差作为索引，就可以查找到需要的IndexBuffer了。</p>
<p>===分割线===</p>
<p>由于没有CryEngine的代码，分析所依赖的手段只有PIX抓帧和PerfHud，以及CryEngine的Shader，所以本文中所述的观点仅供参考，欢迎讨论。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.windameister.org/blog/2011/11/04/crysis-terrain-render-tech-anaylsis-material-and-lod/feed/</wfw:commentRss>
		<slash:comments>10</slash:comments>
		</item>
		<item>
		<title>关于地形渲染技术的杂谈</title>
		<link>http://www.windameister.org/blog/2011/08/28/some-tech-topics-on-terrain-rendering/</link>
		<comments>http://www.windameister.org/blog/2011/08/28/some-tech-topics-on-terrain-rendering/#comments</comments>
		<pubDate>Sun, 28 Aug 2011 14:28:31 +0000</pubDate>
		<dc:creator>windam</dc:creator>
				<category><![CDATA[3D渲染]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[LOD]]></category>
		<category><![CDATA[Virtual Texture]]></category>
		<category><![CDATA[地形渲染]]></category>
		<category><![CDATA[材质]]></category>
		<category><![CDATA[阴影]]></category>

		<guid isPermaLink="false">http://www.windameister.org/blog/2011/08/28/%e5%85%b3%e4%ba%8e%e5%9c%b0%e5%bd%a2%e6%b8%b2%e6%9f%93%e6%8a%80%e6%9c%af%e7%9a%84%e6%9d%82%e8%b0%88/</guid>
		<description><![CDATA[地形是3D游戏中不可或缺的基础部分，特别是对于要表现室外场景的游戏来说。随着图形硬件的发展，地形渲染技术也经历了若干变迁。主要包括几个方面：LOD，光照和阴影，材质表现。由于本人的时间精力有限不可能面面俱到，所以只能是浅谈辄止，粗浅的叙述一下相关的技术和一些思考，欢迎交流指正。 关于LOD 早些年，由于图形硬件的瓶颈在于顶点和三角形处理上，因此发展出一批目的在于尽可能减少顶点和三角形数量的算法，如ROAM。后来随着显卡性能的提升，渲染效率的瓶颈逐渐变成了总线上的数据传输速率，地形渲染随之演变成尽可能将预计算好的数据存储于显卡上，从而避免每帧传递数据，如Geo-Mipmapping。还有后续的算法在此基础上继续改进，如Chunked LOD。 除此之外，地形LOD算法还需要处理的问题包括如何消除LOD级别改变时发生的视觉上的突变。一种常见的方法就是通过Morphing。不过不知是出于效率还是工程上的考虑，Crysis的地形上并没有做morphing。 再后来，随着Shader Model 3.0中增加了Vertex Texture访问以及对Instancing的支持，地形渲染技术则有了更多的选择，例如通过VertexTexture技术，可以在VertexShader中算顶点，而Instancing技术更进一步减少了数据传输量，进一步简化了CPU端的计算和数据传输。 参考：CDLOD，这个作者正在基于DX11实现他的下一个版本的CDLOD，将会包含下面要说的Tessellation技术。 到了DX11，新引入的Tessellation技术则让引擎开发者拥有了将整个地形渲染完全搬到显卡上去做的能力。如果说SM3.0中我们只是在硬件上动态生成所需的地形顶点，那么到了DX11中，我们就可以在硬件上实现LOD了，我们可以GPU上决定每个Patch应该有多细密的网格，并且在GPU上处理好和邻接Patch的接缝问题。关于这个话题可以参考GPU Pro2中的Terrain And Ocean Rending with Hardware Tessellation。 这意味着到DX11普及的时候，以往的那些在用于CPU端计算LOD索引的很多算法，都不再被需要了。当然，上述算法中产生的核心思想仍然会继续起作用——例如利用四叉树做地形的数据管理，例如如何在LOD改变时避免视觉上看到跳变等等。 p.s.有时候难免对于国内的Win7普及率感到无奈…… 关于光照和阴影 由于地形是一种静态的模型，不会移动，相对光源的位置是不变的，因此实时渲染地形的时候，很流行的方法是采用LightMap对地形光影做预计算，而传统意义上的光照计算，则往往是在vs阶段（这里指顶点光照，若是逐像素光照，则是在ps阶段）实时进行。 由于LightMap省却了实时进行光照计算的计算量，在效率上有所提升，并且一些对计算量要求较高的技术如全局光照，如果采用实时计算会导致性能急剧下降，因此在实际中往往也是预计算并存储于LightMap中。 另一方面，LightMap不便存储动态的光照信息，当光源的角度，颜色发生改变，LightMap就需要随之更新。而这在实时游戏中往往很难做到。 目前主流的实时阴影技术基本都是基于ShadowMap的，在Doom3中使用的Shadow Volume，现在已经很少看到了。 ShadowMap的基本原理是从光源的位置和朝向绘制一张场景的深度图，成为ShadowMap，然后再在实际渲染场景的时候，将每个像素的位置到光源的距离和ShadowMap中的深度值进行比较，如果发现该像素的深度小于ShadowMap中对应位置像素的深度，则不在阴影中，反之，则在阴影中。 ShadowMap的好处在于可以方便的实现很多个物体的阴影，物体的自阴影。 但是也有几个问题： １. 边缘会出现难看的锯齿状走样，一方面因为像素的含义是深度，因此相邻像素的值是跳变的，可能一个值为7，邻接的就是200，你无法对深度做过滤；另一方面和ShadowMap的大小有关，同一个场景，使用的ShadowMap的Size越小，越会出现较大的锯齿。 ２. 如上所述，由于存储的是深度，因此ShadowMap难以判断一个像素是否处在阴影边缘中，难以给出柔和的半影过度 ３. ShadowMap只适合于平行光和SpotLight，对于点光源需要计算多达６张ShadowMap（利用DPSM可以减少到2张） 为了解决ShadowMap局限，有很多方法被发明出来，例如，采用PCF获取软边缘，不过PCF需要多次自行采样纹理，效率上有损失，另一方面PCF不是真的计算软阴影，而是通过采样的手段去找出边缘，所以无法根据距离远近产生真实的半影过度。 还有种方法是VSM（方差阴影贴图），其原理是通过车比雪夫不等式(Chebyshev)去计算一个像素在阴影中的概率： 公式来自于Variance Shadow Maps by William Donnelly_ Andrew Lauritzen† 具体来说，它在渲染深度的时候同时存储深度和深度的平方（不同的颜色通道中），然后对ShadowMap做过滤，这样得到的就逐像素的深度的期望μ和深度平方的期望，利用这两个值可以求得方差σ，然后再用车比雪夫不等式计算概率。（具体原理及实现可参考 vsm） 关于材质表现 传统的地形表现比较常见的是用多层纹理混合，也称为Texture splatting。 （图片来自Wikipedia） 这种做法可以实现多种贴图之间的混合过度。优点是实现简单，而且贴图通过反复Tiling即可构成整个地形表面，从而节省了纹理资源。 缺点则是，如果美术稍微偷点懒，就可以看到大片大片的Tiling，重复感非常明显。以魔兽世界为代表的一大批游戏引擎用的都是Texture splatting来表现地表纹理。 在Crysis中有一个在表现效果上和效率上结合得非常完美的材质系统，Crysis为了给地表提供尽可能丰富的细节，支持给顶点赋材质，这样就能够支持地表的Bump [...]]]></description>
			<content:encoded><![CDATA[<p>地形是3D游戏中不可或缺的基础部分，特别是对于要表现室外场景的游戏来说。随着图形硬件的发展，地形渲染技术也经历了若干变迁。主要包括几个方面：LOD，光照和阴影，材质表现。由于本人的时间精力有限不可能面面俱到，所以只能是浅谈辄止，粗浅的叙述一下相关的技术和一些思考，欢迎交流指正。</p>
<h4>关于LOD</h4>
<p>早些年，由于图形硬件的瓶颈在于顶点和三角形处理上，因此发展出一批目的在于尽可能减少顶点和三角形数量的算法，如<a href="https://graphics.llnl.gov/ROAM/">ROAM</a>。后来随着显卡性能的提升，渲染效率的瓶颈逐渐变成了总线上的数据传输速率，地形渲染随之演变成尽可能将预计算好的数据存储于显卡上，从而避免每帧传递数据，如<a href="http://www.flipcode.com/archives/Fast_Terrain_Rendering_Using_Geometrical_MipMapping.shtml">Geo-Mipmapping</a>。还有后续的算法在此基础上继续改进，如<a href="http://tulrich.com/geekstuff/chunklod.html">Chunked LOD</a>。</p>
<p>除此之外，地形LOD算法还需要处理的问题包括如何消除LOD级别改变时发生的视觉上的突变。一种常见的方法就是通过<a href="http://www.google.com.hk/search?hl=zh-CN&amp;newwindow=1&amp;safe=strict&amp;biw=1920&amp;bih=932&amp;q=terrain+Morphing&amp;oq=terrain+Morphing&amp;aq=f&amp;aqi=&amp;aql=&amp;gs_sm=e&amp;gs_upl=61681l63742l0l63882l11l7l0l0l0l0l0l0ll0l0">Morphing</a>。不过不知是出于效率还是工程上的考虑，Crysis的地形上并没有做morphing。</p>
<p>再后来，随着Shader Model 3.0中增加了<a href="http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter18.html">Vertex Texture</a>访问以及对Instancing的支持，地形渲染技术则有了更多的选择，例如通过VertexTexture技术，可以在VertexShader中算顶点，而Instancing技术更进一步减少了数据传输量，进一步简化了CPU端的计算和数据传输。</p>
<p>参考：<a href="http://vertexasylum.com/2010/07/11/oh-no-another-terrain-rendering-paper/">CDLOD</a>，这个作者正在基于DX11实现他的下一个版本的CDLOD，将会包含下面要说的Tessellation技术。</p>
<p>到了DX11，新引入的Tessellation技术则让引擎开发者拥有了将整个地形渲染完全搬到显卡上去做的能力。如果说SM3.0中我们只是在硬件上动态生成所需的地形顶点，那么到了DX11中，我们就可以在硬件上实现LOD了，我们可以GPU上决定每个Patch应该有多细密的网格，并且在GPU上处理好和邻接Patch的接缝问题。关于这个话题可以参考<a href="http://book.douban.com/subject/6064395/">GPU Pro2</a>中的Terrain And Ocean Rending with Hardware Tessellation。</p>
<p>这意味着到DX11普及的时候，以往的那些在用于CPU端计算LOD索引的很多算法，都不再被需要了。当然，上述算法中产生的核心思想仍然会继续起作用——例如利用四叉树做地形的数据管理，例如如何在LOD改变时避免视觉上看到跳变等等。<span id="more-289"></span></p>
<p>p.s.有时候难免对于国内的Win7普及率感到无奈……</p>
<h4>关于光照和阴影</h4>
<p>由于地形是一种静态的模型，不会移动，相对光源的位置是不变的，因此实时渲染地形的时候，很流行的方法是采用LightMap对地形光影做预计算，而传统意义上的光照计算，则往往是在vs阶段（这里指顶点光照，若是逐像素光照，则是在ps阶段）实时进行。</p>
<p>由于LightMap省却了实时进行光照计算的计算量，在效率上有所提升，并且一些对计算量要求较高的技术如全局光照，如果采用实时计算会导致性能急剧下降，因此在实际中往往也是预计算并存储于LightMap中。</p>
<p>另一方面，LightMap不便存储动态的光照信息，当光源的角度，颜色发生改变，LightMap就需要随之更新。而这在实时游戏中往往很难做到。</p>
<p>目前主流的实时阴影技术基本都是基于ShadowMap的，在Doom3中使用的<a href="http://en.wikipedia.org/wiki/Shadow_volume">Shadow Volume</a>，现在已经很少看到了。</p>
<p>ShadowMap的基本原理是从光源的位置和朝向绘制一张场景的深度图，成为ShadowMap，然后再在实际渲染场景的时候，将每个像素的位置到光源的距离和ShadowMap中的深度值进行比较，如果发现该像素的深度小于ShadowMap中对应位置像素的深度，则不在阴影中，反之，则在阴影中。</p>
<p>ShadowMap的好处在于可以方便的实现很多个物体的阴影，物体的自阴影。</p>
<p>但是也有几个问题：</p>
<p>１. 边缘会出现难看的锯齿状走样，一方面因为像素的含义是深度，因此相邻像素的值是跳变的，可能一个值为7，邻接的就是200，你无法对深度做过滤；另一方面和ShadowMap的大小有关，同一个场景，使用的ShadowMap的Size越小，越会出现较大的锯齿。</p>
<p>２. 如上所述，由于存储的是深度，因此ShadowMap难以判断一个像素是否处在阴影边缘中，难以给出柔和的半影过度</p>
<p>３. ShadowMap只适合于平行光和SpotLight，对于点光源需要计算多达６张ShadowMap（利用<a href="http://www.google.com.hk/search?hl=zh-CN&amp;newwindow=1&amp;safe=strict&amp;biw=1920&amp;bih=896&amp;q=DualParaboloidMappingInTheVertexShader&amp;oq=DualParaboloidMappingInTheVertexShader&amp;aq=f&amp;aqi=&amp;aql=&amp;gs_sm=s&amp;gs_upl=34108l34108l0l34862l1l1l0l0l0l0l0l0ll0l0">DPSM</a>可以减少到2张）</p>
<p>为了解决ShadowMap局限，有很多方法被发明出来，例如，采用PCF获取软边缘，不过PCF需要多次自行采样纹理，效率上有损失，另一方面PCF不是真的计算软阴影，而是通过采样的手段去找出边缘，所以无法根据距离远近产生真实的半影过度。</p>
<p>还有种方法是VSM（方差阴影贴图），其原理是通过车比雪夫不等式(Chebyshev)去计算一个像素在阴影中的概率：</p>
<p><a href="http://www.windameister.org/blog/wp-content/uploads/2011/08/clip_image002.jpg"><img style="background-image: none; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="clip_image002" src="http://www.windameister.org/blog/wp-content/uploads/2011/08/clip_image002_thumb.jpg" alt="clip_image002" width="244" height="64" border="0" /></a></p>
<p>公式来自于Variance Shadow Maps by William Donnelly_ Andrew Lauritzen†</p>
<p>具体来说，它在渲染深度的时候同时存储深度和深度的平方（不同的颜色通道中），然后对ShadowMap做过滤，这样得到的就逐像素的深度的期望μ和深度平方的期望，利用这两个值可以求得方差σ，然后再用车比雪夫不等式计算概率。（具体原理及实现可参考 <a href="http://www.punkuser.net/vsm/">vsm</a>）</p>
<h4>关于材质表现</h4>
<p>传统的地形表现比较常见的是用多层纹理混合，也称为<a href="http://en.wikipedia.org/wiki/Texture_splatting">Texture splatting</a>。</p>
<p><a href="http://www.windameister.org/blog/wp-content/uploads/2011/08/clip_image004.gif"><img style="background-image: none; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="clip_image004" src="http://www.windameister.org/blog/wp-content/uploads/2011/08/clip_image004_thumb.gif" alt="clip_image004" width="244" height="191" border="0" /></a> （图片来自Wikipedia）</p>
<p>这种做法可以实现多种贴图之间的混合过度。优点是实现简单，而且贴图通过反复Tiling即可构成整个地形表面，从而节省了纹理资源。</p>
<p>缺点则是，如果美术稍微偷点懒，就可以看到大片大片的Tiling，重复感非常明显。以魔兽世界为代表的一大批游戏引擎用的都是Texture splatting来表现地表纹理。</p>
<p>在Crysis中有一个在表现效果上和效率上结合得非常完美的材质系统，Crysis为了给地表提供尽可能丰富的细节，支持给顶点赋材质，这样就能够支持地表的<a href="http://en.wikipedia.org/wiki/Bump_mapping">Bump Mapping</a>以及更高级的<a href="http://en.wikipedia.org/wiki/Parallax_occlusion_mapping">POM</a>。而传统的Texture splatting在混合时不去区分实际的材质，是不可能做到这一点的。当离远之后，Crysis就不再渲染细节纹理的pass，而是只渲染地表颜色图。</p>
<p>来一张Crysis的炫技图（图片来自互联网）：</p>
<p><a href="http://www.windameister.org/blog/wp-content/uploads/2011/08/clip_image006.jpg"><img style="background-image: none; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="clip_image006" src="http://www.windameister.org/blog/wp-content/uploads/2011/08/clip_image006_thumb.jpg" alt="clip_image006" width="244" height="153" border="0" /></a></p>
<p>接下来，为了引出Virtual Texturing，先介绍下Clipmap。</p>
<p>在类似GoogleEarth之类的地图软件中可能使用了一种类似于<a href="http://www.cs.virginia.edu/~gfx/Courses/2002/BigData/papers/Texturing/Clipmap.pdf">Clipmap</a>的技术，这种技术基于mipmap，同时又对之作了扩展。</p>
<p>传统的Mipmap是从一张贴图原始的Size开始直到Size为1有一个完整的Mipmap链，比如一张1024×1024的贴图，会有从512×512直到1×1的全部的mipmap版本，同时存在于显卡中，到了纹理采样的时候根据mip级别采样不同的纹理。</p>
<p><a href="http://www.windameister.org/blog/wp-content/uploads/2011/08/clip_image008.jpg"><img style="background-image: none; margin: 0px; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="clip_image008" src="http://www.windameister.org/blog/wp-content/uploads/2011/08/clip_image008_thumb.jpg" alt="clip_image008" width="244" height="116" border="0" /></a></p>
<p>图片来自<a href="http://www.cs.virginia.edu/~gfx/Courses/2002/BigData/papers/Texturing/Clipmap.pdf">The Clipmap: A Virtual Mipmap</a></p>
<p>而Clipmap中的贴图可能很大，比如64000×64000，这样就不可能把它和它的完整的mipmap链放到显卡里去了，所以他只放最低级别的那些Mipmap到显卡里，超出显卡能力的部分也放，但是只放一部分（需要的），就好象是Clip过了一样，所以叫Clipmap （= =）如图：</p>
<p><a href="http://www.windameister.org/blog/wp-content/uploads/2011/08/clip_image010.jpg"><img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;" title="clip_image010" src="http://www.windameister.org/blog/wp-content/uploads/2011/08/clip_image010_thumb.jpg" alt="clip_image010" width="244" height="92" border="0" /></a></p>
<p>虽然我们无法把整个贴图完全加载到显卡上，但是我们观察到足够清晰细节的距离不可能太远，离远了就切到下一个mip级别，所以随着到视点距离的增加，从最高精度到最低精度有一个逐渐的变化过程。所以这种做法是可行的。</p>
<p>很多人把Clipmap和Virtual Texture搞混了。其实这是两个完全不同的技术。虽然在对Mipmap的使用方面有类似的地方。</p>
<p>我觉得未来的趋势应该是把地形和模型的贴图都统一纳入到<a href="http://en.wikipedia.org/wiki/MegaTexture">Virtual Texture</a>中管理，这个技术是由卡马克在id tech 5中引入的。在Id Tech5中，通过Virtual Texture技术可以制作没有任何重复感的地表（Virtual Texture也支持模型贴图），从此再也不愁Tiling的问题——只不过可能就得苦了美术和编辑器制作人员了，而且Virtual Texture技术存在许多在制作流程上需要克服的问题。</p>
<p>Virtual Texture说白了就是一张巨大无比的大纹理，可能有1024000 * 1024000这么大，存储在磁盘上。但是到游戏中，只加载实际需要的就可以——实际需要一方面是指mip level上的需要，远处的只需要低精度的纹理，近处才需要高精度，另一方面是指不需要加载那些不进入可视范围内的纹理（看不到的）。</p>
<p>Virtual Texture技术实际上借鉴了计算机中的Virtual Memory的理念。我们知道早期的计算机物理内存没有4GB，但是应用程序却可以通过32位指针访问到整个4GB的地址空间，好像这么大的内存都可以访问一样，但是实际的物理内存可能很小，只有512MB，这是怎么做到的呢？</p>
<p>操作系统把内存按页进行管理，每个页4KB大，并且只把应用程序需要访问的内存页放到到实际的物理内存中，如果应用程序访问一块内存，而这块内存又不在物理内存中存在，那么应用程序会暂停执行，操作系统执行换入操作，并把应用程序需要的内存页拿到物理内存里，然后再把执行权交回给应用程序，当然这一切对于应用程序的开发者来说都是透明的。</p>
<p>虚拟纹理（<a href="http://en.wikipedia.org/wiki/MegaTexture">Virtual Texture</a>）也是采用类似的方案，实际的Physical Texture（想像成对应于物理内存的实际纹理）可能只有4096 * 4096这么大（根据实际显卡的支持程度决定），但是3D游戏中各物体的uv坐标则可以寻址远超出这个物理纹理范围的大纹理，因此virtual texture也需要把物理纹理分页，比如切成256×256的小块纹理，并且根据实际需要进行换入换出操作。这里一共存在从硬盘到内存，再从内存到显卡的三级缓存，这个过程中有许多复杂的地方要处理，比如怎么知道应该加载什么mip级别的贴图，怎么知道应该加载那块贴图等等。基本的思路是通过预先渲染一个pass，将需要加载的mip级别和具体的页面索引算出来。</p>
<p>Virtual Texture目前网上已经有了开源的实现，感兴趣的朋友可以参考下 <a href="http://silverspaceship.com/src/svt/">Sparse Virtual Texture</a>，<a href="http://forum.beyond3d.com/showthread.php?t=55594">这里</a>，还有<a href="http://s09.idav.ucdavis.edu/talks/05-JP_id_Tech_5_Challenges.pdf">id Tech5的介绍</a>，<a href="http://crytek.com/cryengine/presentations/advanced-virtual-texture-topics">CryEngine对Virtual Texture的看法</a>。</p>
<p>本文中提到的技术，在实际的实现中都要解决各种实现上或者工程上的问题，不可能如我在这里夸夸其谈这样简单，所以本文只能是简述下基本思想，算是对近期一段时间的学习做个整理。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.windameister.org/blog/2011/08/28/some-tech-topics-on-terrain-rendering/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>自动构建二：CruiseControl.NET配置基础</title>
		<link>http://www.windameister.org/blog/2011/06/12/autobuild_basics_on_cruisecontrol_net_cofiguration/</link>
		<comments>http://www.windameister.org/blog/2011/06/12/autobuild_basics_on_cruisecontrol_net_cofiguration/#comments</comments>
		<pubDate>Sun, 12 Jun 2011 08:24:56 +0000</pubDate>
		<dc:creator>windam</dc:creator>
				<category><![CDATA[Windows平台开发]]></category>
		<category><![CDATA[自动构建]]></category>

		<guid isPermaLink="false">http://www.windameister.org/blog/2011/06/12/%e8%87%aa%e5%8a%a8%e6%9e%84%e5%bb%ba%e4%ba%8c%ef%bc%9acruisecontrol-net%e9%85%8d%e7%bd%ae%e5%9f%ba%e7%a1%80/</guid>
		<description><![CDATA[在软件工程领域，CruiseControl是一个基于Java的持续集成框架。它包含了大量与持续集成有关的功能，如：邮件通知，Ant自动构建，以及支持多种源码管理工具。可以利用Web页面观察当前和以往的构建流程。它允许我们对软件开发中的很多流程进行持续的自动化控制。 CruiseControl是一个自由，开源软件，使用BSD类型的license。最早的时候，该软件是由ThoughtWorks公司的员工开发出来用于该公司自身内部项目的，随后则变成了一个独立的应用软件。 CruiseControl.NET是CruiseControl的.NET版本。还有一个Ruby版本的CruiseControl，被称为CruiseControl.rb。 对于使用Windows平台，以及Visual Studio集成开发环境的团队来说，使用CruiseControl.NET的好处在于他整合了MSBuild，NAnt，VSS等微软平台下常用的软件开发中的工具，并且非常易于在.NET平台下对他的功能进行扩展。 我们可以利用MSBuild来编写构建脚本，利用CruiseControl.NET配置实现持续集成，每日构建，自动化测试，邮件通知等功能。 安装CruiseControl.NET需要先安装.NET Framework 2.0以及支持ASP.NET的Web server（比较典型的如开启了ASP.NET的IIS服务器） CruiseControl.NET提供了两种查看和管理构建流程的方式： 1. 通过Web Dashboard，也就是通过网页查看 2. 通过CCTray客户端（该客户端是一个常驻程序，可以随时点开查看当前的构建状况，并且可以在构建成功或失败后弹出提示） 安装CruiseControl.NET 从ThoughtWorks的网站下载当前最新的Release版本： http://confluence.public.thoughtworks.org/display/CCNET/Download 在服务器上安装该CCNet安装程序（服务器需要是Windows的，并且有.NET2.0运行环境与支持ASP.NET的WebServer） 配置CruiseControl.NET： 安装目录下有名为ccnet.config的xml格式的配置文件，用于配置当前的CCNet服务器。 对于一个比较简单的项目，我们的持续集成需求包括： 1. 从源代码管理服务器上取下最新的源码 2. 构建 3. 构建成功后，将文件发布到我们指定的目录 我们还可能会需要每日构建，每天夜里0:00开始一次所有项目的构建，并将结果发布到指定位置。 项目之间往往存在互相依赖，如果被依赖项发生了更改，应该有办法让所有依赖了该项目的项目都依次构建。 下面以一个简单的例子描述如何配置CruiseControl.net服务器。 打开ccnet.config文件，该文件是一个xml格式的文件，可以在配置文件中编写多个project，每个project都对应着一个&#60;project&#62; &#60;/project&#62;块。 &#60;cruisecontrol&#62; &#60;project name=”MyProj1”&#62; &#60;/project&#62; &#60;project name=”MyProj2”&#62; &#60;/project&#62; &#60;/cruisecontrol&#62; 每个Project块的配置类似下面的例子： &#60;project name=&#8221;Project 1&#8243; queue=&#8221;Q1&#8243; queuePriority=&#8221;1&#8243;&#62; &#60;workingDirectory&#62;yourWorkingDirectory&#60;/workingDirectory&#62; &#60;artifactDirectory&#62;yourArtifactDirectory&#60;/artifactDirectory&#62; &#60;category&#62;Category 1&#60;/category&#62; &#60;webURL&#62;http://server1/ccnet/server/local/project/testProject/ViewLatestBuildReport.aspx&#60;/webURL&#62; &#60;modificationDelaySeconds&#62;2&#60;/modificationDelaySeconds&#62; &#60;maxSourceControlRetries&#62;5&#60;/maxSourceControlRetries&#62; [...]]]></description>
			<content:encoded><![CDATA[<p>在软件工程领域，CruiseControl是一个基于Java的持续集成框架。它包含了大量与持续集成有关的功能，如：邮件通知，Ant自动构建，以及支持多种源码管理工具。可以利用Web页面观察当前和以往的构建流程。它允许我们对软件开发中的很多流程进行持续的自动化控制。</p>
<p>CruiseControl是一个自由，开源软件，使用BSD类型的license。最早的时候，该软件是由ThoughtWorks公司的员工开发出来用于该公司自身内部项目的，随后则变成了一个独立的应用软件。</p>
<p>CruiseControl.NET是CruiseControl的.NET版本。还有一个Ruby版本的CruiseControl，被称为CruiseControl.rb。</p>
<p>对于使用Windows平台，以及Visual Studio集成开发环境的团队来说，使用CruiseControl.NET的好处在于他整合了MSBuild，NAnt，VSS等微软平台下常用的软件开发中的工具，并且非常易于在.NET平台下对他的功能进行扩展。</p>
<p><span id="more-272"></span></p>
<p>我们可以利用MSBuild来编写构建脚本，利用CruiseControl.NET配置实现持续集成，每日构建，自动化测试，邮件通知等功能。</p>
<p>安装CruiseControl.NET需要先安装.NET Framework 2.0以及支持ASP.NET的Web server（比较典型的如开启了ASP.NET的IIS服务器）</p>
<p>CruiseControl.NET提供了两种查看和管理构建流程的方式：</p>
<p>1. 通过Web Dashboard，也就是通过网页查看</p>
<p>2. 通过CCTray客户端（该客户端是一个常驻程序，可以随时点开查看当前的构建状况，并且可以在构建成功或失败后弹出提示）</p>
<p>安装CruiseControl.NET</p>
<p>从ThoughtWorks的网站下载当前最新的Release版本：</p>
<p><a href="http://confluence.public.thoughtworks.org/display/CCNET/Download">http://confluence.public.thoughtworks.org/display/CCNET/Download</a></p>
<p>在服务器上安装该CCNet安装程序（服务器需要是Windows的，并且有.NET2.0运行环境与支持ASP.NET的WebServer）</p>
<p>配置CruiseControl.NET：</p>
<p>安装目录下有名为ccnet.config的xml格式的配置文件，用于配置当前的CCNet服务器。</p>
<p>对于一个比较简单的项目，我们的持续集成需求包括：</p>
<p>1. 从源代码管理服务器上取下最新的源码</p>
<p>2. 构建</p>
<p>3. 构建成功后，将文件发布到我们指定的目录</p>
<p>我们还可能会需要每日构建，每天夜里0:00开始一次所有项目的构建，并将结果发布到指定位置。</p>
<p>项目之间往往存在互相依赖，如果被依赖项发生了更改，应该有办法让所有依赖了该项目的项目都依次构建。</p>
<p>下面以一个简单的例子描述如何配置CruiseControl.net服务器。</p>
<p>打开ccnet.config文件，该文件是一个xml格式的文件，可以在配置文件中编写多个project，每个project都对应着一个&lt;project&gt; &lt;/project&gt;块。</p>
<p>&lt;cruisecontrol&gt;</p>
<p>&lt;project name=”MyProj1”&gt;</p>
<p>&lt;/project&gt;</p>
<p>&lt;project name=”MyProj2”&gt;</p>
<p>&lt;/project&gt;</p>
<p>&lt;/cruisecontrol&gt;</p>
<p>每个Project块的配置类似下面的例子：</p>
<p>&lt;project name=&#8221;Project 1&#8243; queue=&#8221;Q1&#8243; queuePriority=&#8221;1&#8243;&gt;</p>
<p>&lt;workingDirectory&gt;yourWorkingDirectory&lt;/workingDirectory&gt;</p>
<p>&lt;artifactDirectory&gt;yourArtifactDirectory&lt;/artifactDirectory&gt;</p>
<p>&lt;category&gt;Category 1&lt;/category&gt;</p>
<p>&lt;webURL&gt;http://server1/ccnet/server/local/project/testProject/ViewLatestBuildReport.aspx&lt;/webURL&gt;</p>
<p>&lt;modificationDelaySeconds&gt;2&lt;/modificationDelaySeconds&gt;</p>
<p>&lt;maxSourceControlRetries&gt;5&lt;/maxSourceControlRetries&gt;</p>
<p>&lt;initialState&gt;Stopped&lt;/initialState&gt;</p>
<p>&lt;startupMode&gt;UseInitialState&lt;/startupMode&gt;</p>
<p>&lt;triggers&gt;</p>
<p>&lt;!&#8211;yourFirstTriggerType .. &#8211;&gt;</p>
<p>&lt;!&#8211;yourOtherTriggerType .. &#8211;&gt;</p>
<p>&lt;/triggers&gt;</p>
<p>&lt;!&#8211; state type=&#8221;yourStateManagerType&#8221; .. &#8211;&gt;</p>
<p>&lt;!&#8211; sourcecontrol type=&#8221;yourSourceControlType&#8221; .. &#8211;&gt;</p>
<p>&lt;!&#8211; labeller type=&#8221;yourLabellerType&#8221; .. &#8211;&gt;</p>
<p>&lt;prebuild&gt;</p>
<p>&lt;!&#8211; yourFirstPrebuildTask .. &#8211;&gt;</p>
<p>&lt;!&#8211; yourOtherPrebuildTask .. &#8211;&gt;</p>
<p>&lt;/prebuild&gt;</p>
<p>&lt;tasks&gt;</p>
<p>&lt;!&#8211; yourFirstTask .. &#8211;&gt;</p>
<p>&lt;!&#8211; yourOtherTask .. &#8211;&gt;</p>
<p>&lt;/tasks&gt;</p>
<p>&lt;publishers&gt;</p>
<p>&lt;!&#8211; yourFirstPublisherTask .. &#8211;&gt;</p>
<p>&lt;!&#8211; yourOtherPublisherTask .. &#8211;&gt;</p>
<p>&lt;/publishers&gt;</p>
<p>&lt;externalLinks&gt;</p>
<p>&lt;externalLink name=&#8221;My First Link&#8221; url=&#8221;http://somewhere/&#8221; /&gt;</p>
<p>&lt;externalLink name=&#8221;My Other Link&#8221; url=&#8221;http://somewhere.else/&#8221; /&gt;</p>
<p>&lt;/externalLinks&gt;</p>
<p>&lt;parameters&gt;</p>
<p>&lt;textParameter name=&#8221;Build Name&#8221; default=&#8221;Unknown&#8221; /&gt;</p>
<p>&lt;/parameters&gt;</p>
<p>&lt;linkedSites&gt;</p>
<p>&lt;namedValue name=&#8221;ohloh&#8221; value=&#8221;5623&#8243; /&gt;</p>
<p>&lt;/linkedSites&gt;</p>
<p>&lt;/project&gt;</p>
<p>具体的属性含义可以参考文档：</p>
<p><a href="http://confluence.public.thoughtworks.org/display/CCNET/Project+Configuration+Block">http://confluence.public.thoughtworks.org/display/CCNET/Project+Configuration+Block</a></p>
<p>对于Project块，有几个比较重要的内容值得详细说明：</p>
<p>1. Queue</p>
<p>&lt;project name=&#8221;Project 1&#8243; queue=&#8221;Q1&#8243; queuePriority=&#8221;1&#8243;&gt;</p>
<p>这里有一个queue属性，以及对应的queuePriority属性。说明的是该project被放置于哪个queue中，如果不加指明，那么每个project都会被放置到一个独立的queue中。</p>
<p>Queue的作用是什么？</p>
<p>想象一下存在两个项目A和B，并且B依赖A。如果正在构建B的时候，trigger又启动了一个Build开始构建A，但是由于B依赖A，因此在构建B的过程中，需要打开A的目标文件进行读取（例如B使用A的lib文件进行link）。如果A此时恰好要写入该目标文件，则会发生文件访问冲突。进而造成A的构建流程失败。</p>
<p>我们想到或许可以将B依赖的文件拷贝一份出来，从而避免出现使用文件和写入文件冲突的情况，但是很快我们就会意识到这也行不通——因为可能我们在拷贝文件的时候（这里需要打开并读取A的输出文件），遇到了A正要写入文件的状况。</p>
<p>总结一下，对于相互之间存在依赖的项目，我们不应该让他们不受控制的并发构建，而是必须要有一个先后顺序——这里就是Queue的用处所在。</p>
<p>CruiseControl工作的时候，是以queue为单位进行的，当trigger或者人工通过force build触发了一次构建，这次构建会被置放到queue中，并且根据queuePriority决定被插入的位置。</p>
<p>这样，可以保证一系列相互之间存在依赖关系的项目在一个队列中依次进行Build，避免因相互依赖项目同时构建而导致的文件读写冲突等问题。</p>
<p>2. Triggers</p>
<p>触发器是实现我们的各种构建需求的工具。比如，每天夜里0:00产生一次Nightly Build，比如每次Check in之后都进行一次Continuous Integration Build，比如基础项目构建成功了，应当引发所有的依赖该基础项的项目都产生一次构建。等等。</p>
<p>要实现上述的功能，我们可以使用CruiseControl.NET提供的诸多类型的trigger达到目的，或者选择其中若干进行组合使用。</p>
<p>最常用的trigger之一是<a href="http://confluence.public.thoughtworks.org/display/CCNET/Interval+Trigger">Interval Trigger</a>，我们可以设置Interval的间隔时间，每隔这么长的时间，触发器就会被触发一次。配合SCM工具对源代码的检查，我们可以利用Interval Trigger实现持续集成。（每隔1分钟触发一次，检查源码仓库是否有新checkin的代码，如果有则Build）</p>
<p><a href="http://confluence.public.thoughtworks.org/display/CCNET/Project+Trigger">Project Trigger</a>可以使得当前项目获知依赖项目构建成功的消息，当制定的Project构建成功之后，Trigger会被触发</p>
<p><a href="http://confluence.public.thoughtworks.org/display/CCNET/Multiple+Trigger">Multiple Trigger</a>用于将多个Trigger用逻辑关系组合起来使用，例如，当使用And逻辑进行组合时，Multiple Trigger中只要有一个Trigger认为不应当触发构建，就不会被触发构建。</p>
<p>3. Tasks</p>
<p>当Trigger触发之后，就会开始构建流程，从而将Tasks块的所有Task依次执行一遍。</p>
<p>我们需要使用Task来完成主要的构建流程。上篇文章中曾经简单介绍过MSBuild，到了这里，我们就利用MSBuild来实现自动构建了。</p>
<p>&lt;msbuild&gt;</p>
<p>&lt;executable&gt;C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\MSBuild.exe&lt;/executable&gt;</p>
<p>&lt;workingDirectory&gt;C:\dev\ccnet&lt;/workingDirectory&gt;</p>
<p>&lt;projectFile&gt;build.xml&lt;/projectFile&gt;</p>
<p>&lt;targets&gt;Build;Test&lt;/targets&gt;</p>
<p>&lt;timeout&gt;900&lt;/timeout&gt;</p>
<p>&lt;logger&gt;C:\Program Files\CruiseControl.NET\server\ThoughtWorks.CruiseControl.MsBuild.dll&lt;/logger&gt;</p>
<p>&lt;/msbuild&gt;</p>
<p>最简单的利用MSBuild的方式如上例所示。</p>
<p>projectFile是预先编写好的MSBuild的脚本文件，targets里面填写的是要执行MSBuild脚本中的哪个Target。（关于Target，可以参考此前总结的MSBuild简介里的相关内容，以及MSBuild自身的文档）timeout时间是指此次build如果超过多长时间则认为是超时失败。</p>
<p>4. Publishers</p>
<p>Publisher用于在构建流程走完之后将最终的结果发布出去。Publisher中可以有一系列的Task，与Tasks块中的Task的用法类似，包括调用MSBuild Task，在MSBuild脚本中执行操作等。</p>
<p>我们可以利用Publisher模块，将最新版本的SDK发布到服务器，或是创建提供给用户的安装包等。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.windameister.org/blog/2011/06/12/autobuild_basics_on_cruisecontrol_net_cofiguration/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>自动构建一：MSBuild基础</title>
		<link>http://www.windameister.org/blog/2011/06/12/autobuild_basics_on_msbuild/</link>
		<comments>http://www.windameister.org/blog/2011/06/12/autobuild_basics_on_msbuild/#comments</comments>
		<pubDate>Sun, 12 Jun 2011 08:24:22 +0000</pubDate>
		<dc:creator>windam</dc:creator>
				<category><![CDATA[Windows平台开发]]></category>
		<category><![CDATA[自动构建]]></category>

		<guid isPermaLink="false">http://www.windameister.org/blog/2011/06/12/%e8%87%aa%e5%8a%a8%e6%9e%84%e5%bb%ba%e4%b8%80%ef%bc%9amsbuild%e5%9f%ba%e7%a1%80/</guid>
		<description><![CDATA[MSBuild是微软自Visual Studio 2005开始提供的构建平台，既可以用于VC#项目，也可用于VC++项目。 通常情况下，我们采用IDE本身进行项目的构建，有时，也会采用命令行方式调用IDE程序Build我们的项目。（对于VC++项目来说，就是通过命令行调用devenv进行构建） 然而，当我们希望在持续集成工具当中实现项目的自动构建时，尤其是当依赖情况或者部署情况较为复杂时，我们希望有一个简单、易维护的工具实现构建——MSBuild就是我们所需要的工具。 采用MSBuild进行构建，需要我们编写MSBuild脚本——一种规定格式的XML文件。 MSBuild基本组成部分包括：Target（目标），ItemGroup（项），PropertyGroup（属性），Task（任务）。 其中：Target目标是MSBuild执行的基本单元。 我们在命令行中调用MSBuild /t:TargetName指定需要执行的目标。 每个Target中可以包含若干Task。 Task是基本的功能单元：例如拷贝文件，删除文件，创建/删除目录，使用Exec调用外部程序，调用VCBuild构建.vcproj项目，调用Csc编译.cs文件，等等。此外，你还可以通过实现ITask接口，创建自定义的Task。 PropertyGroup作为属性，起着类似于变量一样的作用，可以定义一个属性，并在后续的位置使用该属性，例如： &#60;PropertyGroup&#62; &#60;ProjFile&#62;C:\Test\test.vcproj&#60;/ProjFile&#62; &#60;Rebuild&#62;true&#60;/Rebuild&#62; &#60;/PropertyGroup&#62; &#60;Target Name=”Build”&#62; &#60;VCBuild     Projects=”$(ProjFile)”     Rebuild=”$(Rebuild)”    /&#62; &#60;/Target&#62; ItemGroup往往作为输入，例如： &#60;ItemGroup&#62; &#60;IncFiles Include=”C:\Test\*.h” /&#62; &#60;/ItemGroup&#62; &#60;Target Name=”Build”&#62; &#60;Copy    SourceFiles=”@(IncFiles)”    DestinationFolder=”c:\output\include” /&#62; &#60;/Target&#62; &#160; 实例： 假设我们有一个项目A，依赖一个动态连接库B，我们需要先构建B，将B的头文件和lib文件拷贝到A的包含头文件路径和Lib路径下，再构建A，然后将A发布到指定的路径下。 B的项目结构如下： C:\B\B.vcproj A的项目结构如下： C:\A \DepInclude \DepLib \Src\A.vcproj \Output 对应的MSBuild脚本如下： &#60;ItemGroup&#62; &#60;BIncFiles Include=”C:\B\*.h” /&#62; &#60;BLibFiles Include=”C:\B\Release\*.lib” [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://msdn.microsoft.com/zh-cn/library/ms171451(v=vs.80).aspx">MSBuild</a>是微软自Visual Studio 2005开始提供的构建平台，既可以用于VC#项目，也可用于VC++项目。</p>
<p>通常情况下，我们采用IDE本身进行项目的构建，有时，也会采用命令行方式调用IDE程序Build我们的项目。（对于VC++项目来说，就是通过命令行调用devenv进行构建）</p>
<p>然而，当我们希望在持续集成工具当中实现项目的自动构建时，尤其是当依赖情况或者部署情况较为复杂时，我们希望有一个简单、易维护的工具实现构建——MSBuild就是我们所需要的工具。</p>
<p><span id="more-271"></span></p>
<p>采用MSBuild进行构建，需要我们编写MSBuild脚本——一种规定格式的XML文件。</p>
<p>MSBuild基本组成部分包括：Target（目标），ItemGroup（项），PropertyGroup（属性），Task（任务）。</p>
<p>其中：Target目标是MSBuild执行的基本单元。</p>
<p>我们在命令行中调用MSBuild /t:TargetName指定需要执行的目标。</p>
<p>每个Target中可以包含若干Task。</p>
<p>Task是基本的功能单元：例如拷贝文件，删除文件，创建/删除目录，使用Exec调用外部程序，调用VCBuild构建.vcproj项目，调用Csc编译.cs文件，等等。此外，你还可以通过实现ITask接口，创建自定义的Task。</p>
<p>PropertyGroup作为属性，起着类似于变量一样的作用，可以定义一个属性，并在后续的位置使用该属性，例如：</p>
<div class="codearea">
<pre>&lt;PropertyGroup&gt;
   &lt;ProjFile&gt;C:\Test\test.vcproj&lt;/ProjFile&gt;
   &lt;Rebuild&gt;true&lt;/Rebuild&gt;
&lt;/PropertyGroup&gt;

&lt;Target Name=”Build”&gt;
   &lt;VCBuild     Projects=”$(ProjFile)”     Rebuild=”$(Rebuild)”    /&gt;
&lt;/Target&gt;</pre>
</div>
<p>ItemGroup往往作为输入，例如：</p>
<div class="codearea">
<pre>&lt;ItemGroup&gt;
   &lt;IncFiles Include=”C:\Test\*.h” /&gt;
&lt;/ItemGroup&gt;

&lt;Target Name=”Build”&gt;
   &lt;Copy    SourceFiles=”@(IncFiles)”    DestinationFolder=”c:\output\include”
/&gt;
&lt;/Target&gt;</pre>
</div>
<p><!--.codearea{ color:black;  background-color:white;  line-height:18px;  border:1px solid #4f81bd;  margin:0;  width:auto !important;  width:100%;  overflow:auto;  text-align:left;  font-size:12px;  font-family: "Courier New","Consolas","Fixedsys","BitStream Vera Sans Mono", courier,monospace,serif} .codearea pre{ color:black; line-height:18px;  padding:0 0 0 12px !important; margin:0em;  background-color:#fff !important} .linewrap pre{white-space:pre-wrap;  white-space:-moz-pre-wrap;  white-space:-pre-wrap;  white-space:-o-pre-wrap;  word-wrap:break-word;  word-break:normal} .codearea pre.alt{ background-color:#f7f7ff !important} .codearea .lnum{color:#4f81bd;line-height:18px} -->&nbsp;</p>
<p>实例：</p>
<p>假设我们有一个项目A，依赖一个动态连接库B，我们需要先构建B，将B的头文件和lib文件拷贝到A的包含头文件路径和Lib路径下，再构建A，然后将A发布到指定的路径下。</p>
<p>B的项目结构如下：</p>
<p>C:\B\B.vcproj</p>
<p>A的项目结构如下：</p>
<p>C:\A</p>
<p>\DepInclude</p>
<p>\DepLib</p>
<p>\Src\A.vcproj</p>
<p>\Output</p>
<p>对应的MSBuild脚本如下：</p>
<div class="codearea">
<pre>&lt;ItemGroup&gt;
   &lt;BIncFiles Include=”C:\B\*.h” /&gt;
   &lt;BLibFiles Include=”C:\B\Release\*.lib” /&gt;
   &lt;BDllFiles Include=”C:\B\Release\*.dll” /&gt;
&lt;/ItemGroup&gt;

&lt;TargetName=”Build_B”&gt;
    &lt;VCBuild
      Projects=”C:\B\B.vcproj”
      Configuration=”Release”
      Rebuild=”true”     /&gt;
&lt;Copy   SourceFiles=”@(BIncFiles)”
  DestinationFolder=”C:\A\DepInclude”
/&gt;
&lt;Copy   SourceFiles=”@(BLibFiles)”
  DestinationFolder=”C:\A\DepLib”
/&gt;
&lt;/Target&gt;

&lt;Target Name=“Build_A” DependOnTargets=“Build_B”&gt;
    &lt;VCBuild
      Projects=”C:\A\Src\A.vcproj“
      Configuration=“Release”
      Rebuild=”true”
    /&gt;
&lt;/Target&gt;</pre>
</div>
<p><!--.codearea{ color:black;  background-color:white;  line-height:18px;  border:1px solid #4f81bd;  margin:0;  width:auto !important;  width:100%;  overflow:auto;  text-align:left;  font-size:12px;  font-family: "Courier New","Consolas","Fixedsys","BitStream Vera Sans Mono", courier,monospace,serif} .codearea pre{ color:black; line-height:18px;  padding:0 0 0 12px !important; margin:0em;  background-color:#fff !important} .linewrap pre{white-space:pre-wrap;  white-space:-moz-pre-wrap;  white-space:-pre-wrap;  white-space:-o-pre-wrap;  word-wrap:break-word;  word-break:normal} .codearea pre.alt{ background-color:#f7f7ff !important} .codearea .lnum{color:#4f81bd;line-height:18px} -->&nbsp;</p>
<p>Build_A的Target使用了一个DependOnTargets属性，该属性的含义是当前Target要依赖前续的某些Targets，那些依赖的Targets必须先执行，然后才能执行当前的Target。</p>
<p>在本例中Build_B会rebuild整个B工程，并将结果的头文件和lib文件拷贝到A项目的依赖路径当中。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.windameister.org/blog/2011/06/12/autobuild_basics_on_msbuild/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>为什么你应当从现在就开始学习投资理财</title>
		<link>http://www.windameister.org/blog/2011/03/27/why-u-should-learn-value-investing-from-now/</link>
		<comments>http://www.windameister.org/blog/2011/03/27/why-u-should-learn-value-investing-from-now/#comments</comments>
		<pubDate>Sun, 27 Mar 2011 03:41:00 +0000</pubDate>
		<dc:creator>windam</dc:creator>
				<category><![CDATA[投资理财]]></category>
		<category><![CDATA[价值投资]]></category>
		<category><![CDATA[理财]]></category>

		<guid isPermaLink="false">http://www.windameister.org/blog/2011/03/27/%e4%b8%ba%e4%bb%80%e4%b9%88%e4%bd%a0%e5%ba%94%e5%bd%93%e4%bb%8e%e7%8e%b0%e5%9c%a8%e5%b0%b1%e5%bc%80%e5%a7%8b%e5%ad%a6%e4%b9%a0%e6%8a%95%e8%b5%84%e7%90%86%e8%b4%a2/</guid>
		<description><![CDATA[用一句话来说就是，学习投资理财有很多好处，却没有任何明显的坏处。 更明确一点说，学习投资理财，可以提升自己对市场经济的认识，有助于形成正确的财富观念，在实践当中获得收益，并且是一条通往财务自由之路。 1．投资与投机。 投机不是投资。投资者会根据公司的业务状况计算一只股票的价值；而投机者则会打赌股票的价格的上涨，因为他们认为，其他人会出更高的价格来买入这支股票。投机的对象可以是证券、可以是债券、可以是金银、甚至可以是只有观赏价值的花卉： 1634年到1636年的荷兰，由于上流社会的追捧以及供求关系的不平衡，郁金香的价格被迅速抬了起来。郁金香价格暴涨吸引了许多人从欧洲各地赶到荷兰，带来了大量的资金，给郁金香交易火上浇油。在1636年12月到1637年1月之间，所有品种的郁金香价格全线上升。以一种稀有品种Gouda为例，其价格在12月9日的最低点（1.5基尔德）到12月12日的最高点（11基尔德），3天内价格上升将近10倍。超额利润招来了四面八方的投机客。也许早有人怀疑到郁金香的价格已经完全背离了作为一种花卉的常规，但是倒买倒卖所获取的暴利使得许多投机客们丧失了理智。荷兰的郁金香泡沫只维持了一个冬天，在开春之前，泡沫就崩溃了，郁金香市场一片混乱，价格急剧下降。1639年的数据显示，有些品种的郁金香的价格狂跌到最高价位的0.005%。 投资还是投机，与你买入对象的类别无关，只取决于你买入的理由。 按照格雷厄姆给出的定义，“投资操作是建立在透彻分析的基础之上的，目的是要保证本金安全并获得适当的回报”。“投资”所特意强调的安全保障不能建立在市场虚假的信息、毫无根据的臆断、内部小道消息的传播或十足的赌性上；“投资”的安全性必须取决于投资对象是否具备真正的内在价值或存在一个价值变化的空间。相比于投资者的这种“根据公认的价值标准”来判断“股票的市场价格”，投机者则是“根据市场价格来确定价值标准”。 2. 投资回报率与折现率 如果你不能承受风险，就应当满足于较低的投资回报——这是一个由来已久，且听起来十分合理的原则。由此可以得出这样的结论：投资者能够指望的回报，在一定程度上是与其承担的风险成正比的——对这种观点，格雷厄姆在《聪明的投资者》中予以否认。投资者的目标收益率，更多的是由他们乐于且能够为其投资付出的智慧所决定的：图省事且注重安全性的消极投资者，理应得到最低的报酬，而那些精明且富有经验的投资者，由于他们付出了最大的智慧和技能，则理应得到最大的回报。 在思考投资回报率的时候，我们需要引入折现率的概念：折现率是指将未来预期收益折算成现值的比率。简单来说，就是让你选择今天获取10000块钱还是3年后获取12000块钱，如果你正在考虑哪种更合算，那么你正在考虑的就是折现率。（未来的钱相当于现在的钱的比例） 如果一项投资的年收益率小于折现率，那么就可以认为是亏损的。 折现率的构成中包括：无风险利率+风险报酬率+通货膨胀率。 折现率有多种评估方法：a. 累加法，从定义出发，折现率应该包含无风险利率、风险报酬率和通货膨胀率。b. 市场比较法，通过比较同行业企业的资本收益率来评估。 c. 社会平均资产收益率法，通过社会平均资产收益率的情况来评估。 如果从定义出发，计算一下银行的利率，比如三年期定期存款的年利率是4.5%，这里的4.5%的收益率就是无风险利率。无风险利率是投资者在不冒风险的情况下就可以长期而稳定的获取的投资收益率，因此折现率必须高于无风险利率。（从折现率的角度来考虑投资收益，存在银行的钱就是在产生持续的亏损） 社会平均资产收益率是指一段时间内，全社会的总资产，通过投资获取的收益与资产之比率，这与银行存款利率是有很大差别的——银行通过收集存款人的存款，并以贷款的形式投资到各个企业中，从息差中获得收益，企业通过贷款获取经营资本，并在经营中收获利润，当你把钱存入银行之后，这笔钱所经历的每一层收益：银行的息差，企业的利润，都扩大了你收获的银行存款利率与社会平均资产收益率之间的差额——这也是为什么你的银行账户中不应当保留太多存款的理由。 如果我们将折现率就看成是社会平均资产收益率，那么，对于投资者来说，社会平均资产收益率应当是长期资本的无风险收益率，因为取得这个收益率只是与整体经济同步增长，如果低于社会平均资产收益率，则意味着投资亏损了。举例来说，如果社会全部财富有10万亿的时候个人拥有资产1万元，与社会全部财富达到11.5万亿时，个人资产达到1.15万元，两种情况下投资者应当具有相同的满意度。如果这个平均增长比率无法达到，就意味着个人拥有的财富相对社会总财富的比重减小了。 3. 积极的理财目标 由于我们所持有的货币会随着通货膨胀贬值，同时，随着GDP的增长，我们所拥有的财富也在被摊薄。也就是说，只有让自己的投资收益率高于通货膨胀率+GDP增长率，才能跑赢社会平均资产收益率。 为什么GDP增长也在摊薄你手中的货币价值？ 假如A年社会总财富为10万亿，如果你手中的货币今年为10万元，你手中的财富占社会总财富比重的10万/10万亿。如果今年的GDP增长为10%，第二年社会总财富为11万亿，而你的钱全部存银行定期，利率为3%，第二年为10.3万元。那么经过这一年，你的财富占社会总财富的比重就从10万/10万亿，下降为10.3万/11万亿了。（这里是不考虑通货膨胀因素的） 除了GDP增长会摊薄你手中的财富之外，通货膨胀也在侵蚀你的财富地位。通货膨胀是通过间接的方式发生作用的。还是以上例来说，A年的GDP是10万亿——全社会生产了价值10万亿的商品和服务，到A+1年，GDP增长到11万亿（实际价值），但是A年的货币总量从10万亿（为了简化情况，我们假设开始的时候没有任何通货膨胀，也就是说货币总量与资产总价值相等，实际情况中肯定是不可能的），增长到11.8万亿，也就是说货币总量增加了18%，这时候的11万亿是生产的商品+服务的实际价值，但是由于这一年中货币总量的增加，而且你的财富全部是以货币的形式表现的，因此你所持有的10.3万元，所占社会财富的总比重不是10.3万/11万亿，而是10.3万/11.8万亿。 通货膨胀的一个特点是只针对货币资产——如果你的财富不全是以货币的形式存在的，而是以实际资产的形式（股票，债券，黄金，房产），则一定程度上避免了通货膨胀对你的影响。 为什么不是黄金？黄金是社会总资产中的一种，且是一种“静止的资产”：相比于将资金投入到社会资本运作中，变成商品和服务，并依此收获增值，你所持有的黄金资产并不能主动为你带来增值（而往往只是在通货膨胀带来的价格上涨中取得保值的效用）。因此，长期来看，黄金不可能带来超过社会平均资产收益率的回报。（由于金本位制已经离我们远去了，因此黄金从某种意义上可以看成是一根通货膨胀的指针，虽然从短期来看，黄金的价格中存在许多投机的成分，但是长期来看，黄金的价格上涨实际上只是标识出的是货币购买力下降的程度，而这段时间内社会又创造出了大量的新财富，黄金并不能从中受益） 如果我们将手中的货币变成具有长期前景的优质公司的股票，那么我们拥有的份额就会随着公司的成长而改变。长期来看，持有几个优质公司的一部分，比持有所有公司(指数)的一部分会取得更高的收益，比持有贵金属或者消费品（货币）更好。而如何择选优质公司的股票，这需要做大量的甄别和鉴选，需要对市场，行业大环境，财务知识，公司管理者有足够的了解，才能做出有利的选择。在这方面，《股市真规则》，《聪明的投资者》可以作为学习价值投资的入门书籍。 引用九牛老师的一句话作为结语： 过去的三十年间，中国的GDP大约增长了10倍，通货膨胀了约10倍，人民币总额变成30年前的100倍。人民币年复合增长率达到18%。在这样一个背景下，只有对已有财富做积极投资才能保住财富的相对地位，否则一切纸币都会在洪流中化为乌有。]]></description>
			<content:encoded><![CDATA[<p>用一句话来说就是，<strong>学习投资理财有很多好处，却没有任何明显的坏处</strong>。</p>
<p>更明确一点说，<strong>学习投资理财，可以提升自己对市场经济的认识，有助于形成正确的财富观念，在实践当中获得收益，并且是一条通往财务自由之路</strong>。</p>
<p><span id="more-263"></span>1．投资与投机。
<p>投机不是投资。<strong>投资者会根据公司的业务状况计算一只股票的价值；而投机者则会打赌股票的价格的上涨，因为他们认为，其他人会出更高的价格来买入这支股票</strong>。投机的对象可以是证券、可以是债券、可以是金银、甚至可以是<a href="http://wiki.mbalib.com/zh-tw/%E9%83%81%E9%87%91%E9%A6%99%E6%B3%A1%E6%B2%AB%E7%BB%8F%E6%B5%8E">只有观赏价值的花卉</a>：</p>
<blockquote><p>1634年到1636年的荷兰，由于上流社会的追捧以及供求关系的不平衡，郁金香的价格被迅速抬了起来。郁金香价格暴涨吸引了许多人从欧洲各地赶到荷兰，带来了大量的资金，给郁金香交易火上浇油。在1636年12月到1637年1月之间，所有品种的郁金香价格全线上升。以一种稀有品种Gouda为例，其价格在12月9日的最低点（1.5基尔德）到12月12日的最高点（11基尔德），3天内价格上升将近10倍。超额利润招来了四面八方的投机客。也许早有人怀疑到郁金香的价格已经完全背离了作为一种花卉的常规，但是倒买倒卖所获取的暴利使得许多投机客们丧失了理智。荷兰的郁金香泡沫只维持了一个冬天，在开春之前，泡沫就崩溃了，郁金香市场一片混乱，价格急剧下降。1639年的数据显示，有些品种的郁金香的价格狂跌到最高价位的0.005%。</p>
</blockquote>
<p><strong>投资还是投机，与你买入对象的类别无关，只取决于你买入的理由</strong>。</p>
<p>按照格雷厄姆给出的定义，“<strong>投资操作是建立在透彻分析的基础之上的，目的是要保证本金安全并获得适当的回报</strong>”。“投资”所特意强调的安全保障不能建立在市场虚假的信息、毫无根据的臆断、内部小道消息的传播或十足的赌性上；“投资”的安全性必须取决于投资对象是否具备真正的内在价值或存在一个价值变化的空间。<a href="http://en.wikipedia.org/wiki/Security_Analysis_(book)">相比于投资者的这种“根据公认的价值标准”来判断“股票的市场价格”，投机者则是“根据市场价格来确定价值标准”</a>。</p>
<p>2. 投资回报率与折现率</p>
<p>如果你不能承受风险，就应当满足于较低的投资回报——这是一个由来已久，且听起来十分合理的原则。由此可以得出这样的结论：投资者能够指望的回报，在一定程度上是与其承担的风险成正比的——对这种观点，格雷厄姆在《<a href="http://book.douban.com/subject/5243775/">聪明的投资者</a>》中予以否认。投资者的目标收益率，更多的是由他们乐于且能够为其投资付出的智慧所决定的：<strong>图省事且注重安全性的消极投资者，理应得到最低的报酬，而那些精明且富有经验的投资者，由于他们付出了最大的智慧和技能，则理应得到最大的回报</strong>。</p>
<p>在思考投资回报率的时候，我们需要引入<a href="http://wiki.mbalib.com/wiki/%E6%8A%98%E7%8E%B0%E7%8E%87">折现率</a>的概念：<strong>折现率是指将未来预期收益折算成现值的比率</strong>。简单来说，就是让你选择今天获取10000块钱还是3年后获取12000块钱，如果你正在考虑哪种更合算，那么你正在考虑的就是折现率。（未来的钱相当于现在的钱的比例）</p>
<p><strong>如果一项投资的年收益率小于折现率，那么就可以认为是亏损的。</strong><strong></strong></p>
<p>折现率的构成中包括：无风险利率+风险报酬率+通货膨胀率。</p>
<p>折现率有多种评估方法：a. 累加法，从定义出发，折现率应该包含无风险利率、风险报酬率和通货膨胀率。b. 市场比较法，通过比较同行业企业的资本收益率来评估。 c. 社会平均资产收益率法，通过<strong>社会平均资产收益率</strong>的情况来评估。</p>
<p>如果从定义出发，计算一下银行的利率，比如三年期定期存款的年利率是4.5%，这里的4.5%的收益率就是无风险利率。无风险利率是投资者在不冒风险的情况下就可以长期而稳定的获取的投资收益率，因此<strong>折现率必须高于无风险利率。</strong>（从折现率的角度来考虑投资收益，存在银行的钱就是在产生持续的亏损）</p>
<p>社会平均资产收益率是指一段时间内，全社会的总资产，通过投资获取的收益与资产之比率，这与银行存款利率是有很大差别的——银行通过收集存款人的存款，并以贷款的形式投资到各个企业中，从息差中获得收益，企业通过贷款获取经营资本，并在经营中收获利润，当你把钱存入银行之后，这笔钱所经历的每一层收益：银行的息差，企业的利润，都扩大了你收获的银行存款利率与社会平均资产收益率之间的差额——<strong>这也是为什么你的银行账户中不应当保留太多存款的理由</strong>。</p>
<p>如果我们将折现率就看成是社会平均资产收益率，那么，<strong><a href="http://blog.sina.com.cn/s/blog_4978eb8f010006ux.html">对于投资者来说，社会平均资产收益率应当是长期资本的无风险收益率，因为取得这个收益率只是与整体经济同步增长，如果低于社会平均资产收益率，则意味着投资亏损了。</a></strong>举例来说，如果社会全部财富有10万亿的时候个人拥有资产1万元，与社会全部财富达到11.5万亿时，个人资产达到1.15万元，两种情况下投资者应当具有相同的满意度。如果这个平均增长比率无法达到，就意味着个人拥有的财富相对社会总财富的比重减小了。</p>
<p>3. 积极的理财目标</p>
<p>由于我们所持有的货币会随着通货膨胀贬值，同时，随着GDP的增长，我们所拥有的财富也在被摊薄。也就是说，只有让自己的<strong>投资收益率高于通货膨胀率</strong><strong>+GDP</strong><strong>增长率</strong>，才能跑赢社会平均资产收益率。</p>
<p>为什么GDP增长也在摊薄你手中的货币价值？</p>
<blockquote><p>假如A年社会总财富为10万亿，如果你手中的货币今年为10万元，你手中的财富占社会总财富比重的10万/10万亿。如果今年的GDP增长为10%，第二年社会总财富为11万亿，而你的钱全部存银行定期，利率为3%，第二年为10.3万元。那么经过这一年，你的财富占社会总财富的比重就从10万/10万亿，下降为10.3万/11万亿了。（这里是不考虑通货膨胀因素的）</p>
<p>除了GDP增长会摊薄你手中的财富之外，通货膨胀也在侵蚀你的财富地位。通货膨胀是通过间接的方式发生作用的。还是以上例来说，A年的GDP是10万亿——全社会生产了价值10万亿的商品和服务，到A+1年，GDP增长到11万亿（实际价值），但是A年的货币总量从10万亿（为了简化情况，我们假设开始的时候没有任何通货膨胀，也就是说货币总量与资产总价值相等，实际情况中肯定是不可能的），增长到11.8万亿，也就是说货币总量增加了18%，这时候的11万亿是生产的商品+服务的实际价值，但是由于这一年中货币总量的增加，而且你的财富全部是以货币的形式表现的，因此你所持有的10.3万元，所占社会财富的总比重不是10.3万/11万亿，而是10.3万/11.8万亿。</p>
</blockquote>
<p>通货膨胀的一个特点是只针对货币资产——如果你的财富不全是以货币的形式存在的，而是以实际资产的形式（股票，债券，黄金，房产），则一定程度上避免了通货膨胀对你的影响。</p>
<p>为什么不是黄金？黄金是社会总资产中的一种，且是一种“静止的资产”：相比于将资金投入到社会资本运作中，变成商品和服务，并依此收获增值，你所持有的黄金资产并不能主动为你带来增值（而往往只是在通货膨胀带来的价格上涨中取得保值的效用）。因此，长期来看，黄金不可能带来超过社会平均资产收益率的回报。（由于金本位制已经离我们远去了，因此黄金从某种意义上可以看成是一根通货膨胀的指针，虽然从短期来看，黄金的价格中存在许多投机的成分，但是<strong>长期来看，黄金的价格上涨实际上只是标识出的是货币购买力下降的程度，而这段时间内社会又创造出了大量的新财富，黄金并不能从中受益</strong>）</p>
<p>如果我们<strong>将手中的货币变成具有长期前景的优质公司的股票，那么我们拥有的份额就会随着公司的成长而改变</strong>。长期来看，持有几个优质公司的一部分，比持有所有公司(指数)的一部分会取得更高的收益，比持有贵金属或者消费品（货币）更好。而如何择选优质公司的股票，这需要做大量的甄别和鉴选，需要对市场，行业大环境，财务知识，公司管理者有足够的了解，才能做出有利的选择。在这方面，《<a href="http://book.douban.com/subject/4137173/">股市真规则</a>》，《<a href="http://book.douban.com/subject/5243775/">聪明的投资者</a>》可以作为学习价值投资的入门书籍。</p>
<p>引用九牛老师的一句话作为结语：</p>
<p><a href="http://blog.sina.com.cn/s/blog_4978eb8f0100go5l.html">过去的三十年间，中国的GDP大约增长了10倍，通货膨胀了约10倍，人民币总额变成30年前的100倍。人民币年复合增长率达到18%。在这样一个背景下，只有对已有财富做积极投资才能保住财富的相对地位，否则一切纸币都会在洪流中化为乌有。</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.windameister.org/blog/2011/03/27/why-u-should-learn-value-investing-from-now/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>从C++到C#——穿行于Native与.Net 托管代码之间</title>
		<link>http://www.windameister.org/blog/2010/12/19/from-cpp-to-csharp-cross-the-boundary-of-native-and-managed-code/</link>
		<comments>http://www.windameister.org/blog/2010/12/19/from-cpp-to-csharp-cross-the-boundary-of-native-and-managed-code/#comments</comments>
		<pubDate>Sun, 19 Dec 2010 09:52:55 +0000</pubDate>
		<dc:creator>windam</dc:creator>
				<category><![CDATA[C++ & Design]]></category>
		<category><![CDATA[Windows平台开发]]></category>
		<category><![CDATA[.NET]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[托管代码]]></category>

		<guid isPermaLink="false">http://www.windameister.org/blog/?p=245</guid>
		<description><![CDATA[微软在.Net平台中提供了大量丰富的语言，库，并为之提供了非常强大的开发工具。借用.Net平台，我们可以非常容易的开发基于Windows的应用，与采用C++相比，使用C#语言开发相同功能的软件可以极大的提升开发效率，缩短开发周期，减少Bug。 然而，往往现存有大量的积累代码是基于原生语言开发的，当我们决定采用C#时，必须考虑到与现存的软件组件交互的能力，具体而言——如何在一个以C++语言为主的项目中引入.NET组件？如何在.NET组件中使用C++代码库提供的功能？ 在游戏项目中，比较理想的情况是底层的3D引擎采用C++编写，而上层编辑器则采用C#编写，这样可以兼顾引擎的运行效率和编辑器的开发效率。一个值得一提的案例是UDK（Unreal引擎的开发包），其中就采用了C++语言与.NET组件相结合的模式，其界面上相当一部分新功能是采用.NET开发的。 下面介绍下我总结的C++与.NET互操作的方法： 1. C++ –&#62; .NET在C++语言中调用.NET 由于.NET组件被设计成COM兼容格式，我们可以像使用COM组件一样使用.NET组件。也就是说，我们可以创建.NET组件中的COM对象，并获取接口来使用它。 值得注意的是使用.NET编译出的dll模块，与采用Native语言构建的ActiveX模块或者其他COM DLL是有差别的，你无法使用regsvr32对其进行注册（没有导出DllRegisterServer, DllUnregisterServer）。 &#160; 如果希望在C++中调用.NET代码，那么在实现.NET组件时，需要设置ComVisible属性，具体而言，就是在Properties\AssemblyInfo.cs 中，将[assembly: ComVisible(false)]（默认）属性，更改为： [assembly: ComVisible(true)]，这样才能使用COM的方式访问到.NET组件中的接口。 将需要暴露给Native代码使用的功能抽象成接口，在.NET中实现该接口，并为之提供Guid（可以采用VC2005中的工具Tools/Create Guid），这样才能在Native代码中使用该接口，并用Guid创建这个接口的实例。例如下面这样： namespace XDll { [Guid("1DE25FAA-4AA1-4819-B1FF-0D507567685E ")] public interface IXSystem { } [Guid("603F977B-34CF-40ca-BB30-B6BC25D2A370")] public class ImplXSystem : IXSystem { } } 在.NET组件构建完成后，需要使用regasm工具，将其中的COM接口导出成tlb文件，以供给C++代码使用。Regasm工具通常安装在.NET Framework的目录下，例如： &#8220;C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\regasm&#8221; 命令行如下： &#8220;C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\regasm&#8221; X.dll /tlb:X.tlb 这里注意一点，安装.NET framework并不会自动将上述路径加入到系统路径中，因此运行regasm时需要键入完整的路径。 在Native代码中调用.NET代码时（VC2005开发环境），需要做以下几件事： #import &#60;mscorlib.tlb&#62; raw_interfaces_only #import “X.tlb” no_namespace [...]]]></description>
			<content:encoded><![CDATA[<p>微软在.Net平台中提供了大量丰富的语言，库，并为之提供了非常强大的开发工具。借用.Net平台，我们可以非常容易的开发基于Windows的应用，与采用C++相比，使用C#语言开发相同功能的软件可以极大的提升开发效率，缩短开发周期，减少Bug。</p>
<p>然而，往往现存有大量的积累代码是基于原生语言开发的，当我们决定采用C#时，必须考虑到与现存的软件组件交互的能力，具体而言——如何在一个以C++语言为主的项目中引入.NET组件？如何在.NET组件中使用C++代码库提供的功能？</p>
<p>在游戏项目中，比较理想的情况是底层的3D引擎采用C++编写，而上层编辑器则采用C#编写，这样可以兼顾引擎的运行效率和编辑器的开发效率。一个值得一提的案例是<a href="http://www.udk.com/">UDK</a>（Unreal引擎的开发包），其中就采用了C++语言与.NET组件相结合的模式，其界面上相当一部分新功能是采用.NET开发的。</p>
<p><span id="more-245"></span></p>
<p>下面介绍下我总结的C++与.NET互操作的方法：</p>
<p><strong>1. C++ –&gt; .NET在C++语言中调用.NET</strong></p>
<p>由于.NET组件被设计成COM兼容格式，我们可以像使用COM组件一样使用.NET组件。也就是说，我们可以创建.NET组件中的COM对象，并获取接口来使用它。</p>
<p>值得注意的是使用.NET编译出的dll模块，与采用Native语言构建的ActiveX模块或者其他COM DLL是有差别的，你无法使用regsvr32对其进行注册（没有导出DllRegisterServer, DllUnregisterServer）。</p>
<p>&nbsp;</p>
<p>如果希望在C++中调用.NET代码，那么在实现.NET组件时，需要设置ComVisible属性，具体而言，就是在Properties\AssemblyInfo.cs 中，将[assembly: ComVisible(false)]（默认）属性，更改为：</p>
<p>[assembly: ComVisible(true)]，这样才能使用COM的方式访问到.NET组件中的接口。</p>
<p>将需要暴露给Native代码使用的功能抽象成接口，在.NET中实现该接口，并为之提供Guid（可以采用VC2005中的工具Tools/Create Guid），这样才能在Native代码中使用该接口，并用Guid创建这个接口的实例。例如下面这样：</p>
<div class="codearea">
<pre><span style="color: blue;">namespace </span><span style="color: #010001;">XDll
</span>{
    [<span style="color: #010001;">Guid</span>(<span style="color: #a31515;">"1DE25FAA-4AA1-4819-B1FF-0D507567685E "</span>)]
    <span style="color: blue;">public interface </span><span style="color: #010001;">IXSystem
    </span>{

    }

    [<span style="color: #010001;">Guid</span>(<span style="color: #a31515;">"603F977B-34CF-40ca-BB30-B6BC25D2A370"</span>)]
    <span style="color: blue;">public class </span><span style="color: #010001;">ImplXSystem </span>: <span style="color: #010001;">IXSystem
    </span>{ 

    }
}</pre>
</div>
<p>在.NET组件构建完成后，需要使用<a href="http://msdn.microsoft.com/zh-cn/library/tzat5yw6(v=vs.80).aspx">regasm</a>工具，将其中的COM接口导出成tlb文件，以供给C++代码使用。Regasm工具通常安装在.NET Framework的目录下，例如：</p>
<p>&#8220;C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\regasm&#8221;</p>
<p>命令行如下：</p>
<p>&#8220;C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\regasm&#8221; X.dll /tlb:X.tlb</p>
<p>这里注意一点，安装.NET framework并不会自动将上述路径加入到系统路径中，因此运行regasm时需要键入完整的路径。</p>
<p>在Native代码中调用.NET代码时（VC2005开发环境），需要做以下几件事：</p>
<p>#import &lt;mscorlib.tlb&gt; raw_interfaces_only</p>
<p>#import “X.tlb” no_namespace named_guids</p>
<p>这两行将一些必要的接口导入，X.tlb是前述步骤中使用regasm工具导出的文件，其中包含了使用C++语言描述的.NET组件中的接口。</p>
<p>通常情况下，Visual Studio会在$CURRENTUSER$\local settings\temp\目录下生成一个后缀名为.tlh的文件，其中包含了.NET库导出的诸多CLSID_X以及IID_X等GUID，以及各COM接口，C++代码可以直接使用该文件中的GUID与接口声明。</p>
<p>接下来使用.NET组件接口与使用COM接口基本相同：</p>
<div class="codearea">
<pre><span style="color: #010001;">CoInitialize</span>(<span style="color: #010001;">NULL</span>); <span style="color: green;">// 初?始?化?COM相?关?

</span><span style="color: #010001;">IX</span>* <span style="color: #010001;">pIX</span>;
<span style="color: #010001;">CoCreateInstance</span>(<span style="color: #010001;">CLSID_X
                 </span>, <span style="color: #010001;">NULL
                 </span>, <span style="color: #010001;">CLSCTX_INPROC_SERVER
                 </span>, <span style="color: #010001;">IID_IX
                 </span>, <span style="color: blue;">reinterpret_cast</span>&lt;<span style="color: blue;">void</span>**&gt;(&amp;<span style="color: #010001;">pIX</span>));</pre>
</div>
<p>&nbsp;</p>
<p>注意这里的CLSID前缀的Guid与IID前缀的Guid的区别，前者是实现类的Guid，后者是接口的Guid，我们这里调用CoCreateInstance的意义在于，创建一个Guid为CLSID_X的实现类，并返回给我一个Guid为IID_IX的接口，我将会以这个接口去访问该实现类。</p>
<p>这里的CLSID_X与IID_IX都是在导出tlb文件时自动生成的GUID数据结构，可以在Native代码中直接使用。</p>
<p>当使用了.NET组件的程序被发布时，需要将上述tlb中的各种ID注册到目标机器的注册表的相应键值中。比如，CLSID就需要导入到目标机器的注册表的HKEY_CLASSES_ROOT\CLSID键中。这一步可以通过regasm的/regfile功能实现，该功能可以导出所有需要的信息到注册表中。</p>
<p>最后当使用完毕之后，记得需要调用创建的COM接口的Release()方法。</p>
<p>pIX-&gt;Release();</p>
<p>以及清理：</p>
<p>CoUninitialize();</p>
<p><strong>2. .NET组件调用Native代码(C++)</strong></p>
<p>由于.NET组件是出现于C++之后，微软在设计时就已经基于COM模型设计其二进制接口，因此很容易可以让以前的原生语言来调用，而C++语言则诞生得更早，没有定义二进制标准，要想让.NET组件调用，只能把自己实现成COM组件的模式。关于如何用Native语言实现一个COM组件，可以参考更加专业的参考书：<a href="http://book.douban.com/subject/1231596/">《COM技术内幕》</a>，<a href="http://book.douban.com/subject/1231481/">《COM本质论》</a>等。本文主要提及在实现COM接口时值得考虑的两种思路。</p>
<p>根据实际应用场景的不同，我们可能面临不同的技术选择：</p>
<p>场景1： <strong>.NET组件依赖于C++库，完全基于C++库构建，并且C++模块不会反向依赖.NET组件</strong></p>
<p>这种情形下，由于依赖结构简单，我们可以将整个C++库采用C++CLI封装，有一些开源库采用了这种做法，比如<a href="http://slimdx.org/">SlimDX</a>项目（这是一个为了可以在.NET中使用DirectX的项目，将DirectX采用C++CLI封装，最终提供了一套面向.NET的开发框架。）再如ManagedOgre或者OgreDotNet，都将Ogre引擎封装在C++CLI中从而提供在.NET中操作OGRE的方案。</p>
<p>场景2： <strong>.NET组件依赖于C++模块，C++模块又依赖.NET组件</strong></p>
<p>.NET中需要调用底层的一些服务做一些事情，例如采用底层接口绘制一个三维场景，置放于.NET模块的窗口中。同时可能C++模块又要依赖.NET组件，比如一个.NET控件需要在一个以C++语言为主开发的编辑器中被调用。</p>
<p>形成类似于下面这样的关系，Native Code与Managed Code互相依赖：</p>
<p><a href="http://www.windameister.org/blog/wp-content/uploads/2010/12/image.png"><img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border-width: 0px;" title="image" src="http://www.windameister.org/blog/wp-content/uploads/2010/12/image_thumb.png" border="0" alt="image" width="547" height="147" /></a></p>
<p>这里，我们可以将.NET对C++模块的依赖提取成一个.NET接口，而后C++代码实现该接口，从而消除.NET对C++代码的直接依赖，将之转变成C++模块对.NET接口的依赖。这是一种在重构中经常使用的手法。</p>
<p>举一个简单的例子来说明这个问题：</p>
<div class="codearea">
<pre><span style="color: blue;">namespace </span><span style="color: #010001;">XDLL
</span>{
    <span style="color: blue;">interface </span><span style="color: #010001;">XInterface
    </span>{
        <span style="color: blue;">void </span><span style="color: #010001;">Foo</span>();
    };

    <span style="color: blue;">public class </span><span style="color: #010001;">XModule
    </span>{
        <span style="color: blue;">private </span><span style="color: #010001;">XInterface ix</span>;

        <span style="color: #010001;">XModule</span>(<span style="color: #010001;">XInterface ix</span>)
        {
            <span style="color: blue;">this</span>.<span style="color: #010001;">ix </span>= <span style="color: #010001;">ix</span>;
        }

        <span style="color: blue;">void </span><span style="color: #010001;">Foo</span>()
        {
            <span style="color: blue;">this</span>.<span style="color: #010001;">ix</span>.<span style="color: #010001;">Foo</span>();
        }
    }
}</pre>
</div>
<p>假设我们的C#模块中有一个类XModule，需要使用C++代码提供的某种功能，我们不直接调用C++代码（这产生一个.NET到C++的直接依赖），而是调用一个自己定义的接口XInterface，这个接口会被暴露给外部，C++代码实现该接口，并在初始化过程中传入.NET组件。</p>
<p>将上述依赖关系变成下面这样：</p>
<p><a href="http://www.windameister.org/blog/wp-content/uploads/2010/12/image1.png"><img style="background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border-width: 0px;" title="image" src="http://www.windameister.org/blog/wp-content/uploads/2010/12/image_thumb1.png" border="0" alt="image" width="537" height="270" /></a></p>
<p>## 理想的情况当然是引擎用C++实现，而后底层提供一个C++CLI的封装，这样编辑器以及上层控件都采用C#开发，既可以兼顾引擎部分的运行效率与编辑器的开发效率，这样就可以保证依赖关系始终是上层编辑器直接依赖引擎。但是如果遇到某些现实状况，例如编辑器已经由C++开发，后期的部分控件界面期望采用C#开发，这样就需要采用类似于上文中的方案，将依赖关系反过来处理。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.windameister.org/blog/2010/12/19/from-cpp-to-csharp-cross-the-boundary-of-native-and-managed-code/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>在3D游戏中采用场景管理的意义</title>
		<link>http://www.windameister.org/blog/2010/11/14/value-of-scene-management-in-3d-game/</link>
		<comments>http://www.windameister.org/blog/2010/11/14/value-of-scene-management-in-3d-game/#comments</comments>
		<pubDate>Sun, 14 Nov 2010 13:13:45 +0000</pubDate>
		<dc:creator>windam</dc:creator>
				<category><![CDATA[3D渲染]]></category>
		<category><![CDATA[网络游戏]]></category>
		<category><![CDATA[3d]]></category>
		<category><![CDATA[场景管理]]></category>

		<guid isPermaLink="false">http://www.windameister.org/blog/2010/11/14/%e5%9c%a83d%e6%b8%b8%e6%88%8f%e4%b8%ad%e9%87%87%e7%94%a8%e5%9c%ba%e6%99%af%e7%ae%a1%e7%90%86%e7%9a%84%e6%84%8f%e4%b9%89/</guid>
		<description><![CDATA[我们常说3D引擎应当包含若干功能：材质，模型，动画等等，这些功能我们很好理解，模型是我们需要渲染的几何体，材质表现的是几何体如何对光照如何做出回应，动画（特别的，骨骼动画）往往是3D世界中可运动模型的基础。除了这些与渲染直接关联的概念之外，往往还有个与渲染看似无关的概念：场景管理。 游戏渲染当中常用的场景管理往往被称为Scene Graph：它的实现通常采用树状结构，构成它的基本单位是节点，一个节点可以有若干个子节点，并且（除了根节点之外）往往有且只有一个父节点。 在实际游戏当中，对于需要渲染的几何体，比如静态的Mesh或者有Skeleton的Mesh，其本身不包含世界变换信息，而是由其所处的节点决定其位置与朝向。很久以前读Ogre的源码看到它就是这么做的，然而问题是，为什么必须要这么做？我一直心存疑惑，如果不采用Scene Graph的方案，我们给每一个需要渲染的模型赋予世界变换信息，那么这个模型不是一样可以渲染么？为什么非要把变换信息与模型本身分离开？分开有什么好处？而不这样做又有什么缺点呢？ 以下是我对该问题做的小结： 问题1：视锥裁剪 采用了Scene Graph，提供层级结构之后，就可以高效的进行视锥裁剪：（更新父节点的包围盒时需要囊括其所有子节点的包围盒）如果父节点无法被看见，那么其所有子节点也都无法被看见。这样，如果一个节点有繁多（数以百计）的子孙节点，我们可以节约很多计算。 如果没有层级结构——我们采用线性表之类的数据结构，那么需要依此遍历所有物体，并依次根据其包围盒判断可见性。 这两个方案的优劣在场景不够复杂时很难分辨，但是显而易见，随着场景复杂度的增加，场景中的物体树N越大，采用层级结构的优势就越明显。 问题1衍生：碰撞检测支持 碰撞检测是比较耗时的算法，在游戏当中，如果一次碰撞检测需要计算的物体太多，会极大的影响效率，因此，与渲染时做可见性判断类似，碰撞检测也需要在进行计算之前尽可能多的剔除无关物体。 采用层级结构的场景，可以为碰撞检测提供天然的支持，如果父节点不与物体相交，那么自然其所有子节点都不会与之相交。 问题2：资源共享 有时候我们会遇到这种需求：需要在Pos[1], Pos[2], … , Pos[N]处渲染同一个Mesh，Mesh的几何数据是一样的，而变换矩阵不同。（这里不考虑Instancing的方案） 如果没有采用Scene Graph的分离变换信息与几何数据的方案，那么我们会遇到一个尴尬的局面：重复。 下面一个例子摘自SceneGraph的Wiki，生动的描述了这个问题： For instance, a game might define a logical relationship between a knight and a horse so that the knight is considered an extension to the horse. The scene graph would have a [...]]]></description>
			<content:encoded><![CDATA[<p>我们常说3D引擎应当包含若干功能：材质，模型，动画等等，这些功能我们很好理解，模型是我们需要渲染的几何体，材质表现的是几何体如何对光照如何做出回应，动画（特别的，骨骼动画）往往是3D世界中可运动模型的基础。除了这些与渲染直接关联的概念之外，往往还有个与渲染看似无关的概念：场景管理。</p>
<p>游戏渲染当中常用的场景管理往往被称为Scene Graph：它的实现通常采用树状结构，构成它的基本单位是节点，一个节点可以有若干个子节点，并且（除了根节点之外）往往有且只有一个父节点。</p>
<p>在实际游戏当中，对于需要渲染的几何体，比如静态的Mesh或者有Skeleton的Mesh，其本身不包含世界变换信息，而是由其所处的节点决定其位置与朝向。很久以前读Ogre的源码看到它就是这么做的，然而问题是，为什么必须要这么做？我一直心存疑惑，如果不采用Scene Graph的方案，我们给每一个需要渲染的模型赋予世界变换信息，那么这个模型不是一样可以渲染么？为什么非要把变换信息与模型本身分离开？分开有什么好处？而不这样做又有什么缺点呢？</p>
<p><span id="more-237"></span></p>
<p>以下是我对该问题做的小结：</p>
<p><strong>问题1</strong><strong>：视锥裁剪</strong></p>
<p>采用了Scene Graph，提供层级结构之后，就可以高效的进行视锥裁剪：（更新父节点的包围盒时需要囊括其所有子节点的包围盒）如果父节点无法被看见，那么其所有子节点也都无法被看见。这样，如果一个节点有繁多（数以百计）的子孙节点，我们可以节约很多计算。</p>
<p>如果没有层级结构——我们采用线性表之类的数据结构，那么需要依此遍历所有物体，并依次根据其包围盒判断可见性。</p>
<p>这两个方案的优劣在场景不够复杂时很难分辨，但是显而易见，随着场景复杂度的增加，场景中的物体树N越大，采用层级结构的优势就越明显。</p>
<p><strong>问题1</strong><strong>衍生：碰撞检测支持</strong></p>
<p>碰撞检测是比较耗时的算法，在游戏当中，如果一次碰撞检测需要计算的物体太多，会极大的影响效率，因此，与渲染时做可见性判断类似，碰撞检测也需要在进行计算之前尽可能多的剔除无关物体。</p>
<p>采用层级结构的场景，可以为碰撞检测提供天然的支持，如果父节点不与物体相交，那么自然其所有子节点都不会与之相交。</p>
<p><strong>问题2</strong><strong>：资源共享</strong></p>
<p>有时候我们会遇到这种需求：需要在Pos[1], Pos[2], … , Pos[N]处渲染同一个Mesh，Mesh的几何数据是一样的，而变换矩阵不同。（这里不考虑Instancing的方案）</p>
<p>如果没有采用Scene Graph的分离变换信息与几何数据的方案，那么我们会遇到一个尴尬的局面：重复。</p>
<p>下面一个例子摘自<a href="http://en.wikipedia.org/wiki/Scene_graph">SceneGraph的Wiki</a>，生动的描述了这个问题：</p>
<blockquote><p>For instance, a game might define a logical relationship between a knight and a horse so that the knight is considered an extension to the horse. The scene graph would have a &#8216;horse&#8217; node with a &#8216;knight&#8217; node attached to it.</p>
<p>As well as describing the logical relationship, the scene graph may also describe the spatial relationship of the various entities: the knight moves through 3D space as the horse moves.</p>
<p>In these large applications, memory requirements are major considerations when designing a scene graph. For this reason many large scene graph systems use instancing to reduce memory costs and increase speed. <strong>In our example above, each knight is a separate scene node, but the graphical representation of the knight (made up of a 3D mesh, textures, materials and shaders) is instanced. This means that only a single copy of the data is kept, which is then referenced by any &#8216;knight&#8217; nodes in the scene graph. This allows a reduced memory budget and increased speed, since when a new knight node is created, the appearance data does not need to be duplicated.</strong></p></blockquote>
<p>可能有人会说：为什么不能在一个循环里依次设置世界矩阵，并绘制Mesh？这样就不会有重复。</p>
<p>是的，如果我们可以只针对这一个Mesh渲染的话，自然没有问题——但情况往往是，我们会预先注册很多个Mesh，最后一次性批量渲染（以尽可能最小化渲染状态切换的次数）。这样就要求我们注册Mesh的时候，附带着把Mesh的Transform信息也一起带着——如果不采用SceneGraph的方案，头疼的事情来了，这个Transform信息应该存放在哪？如果把它放在Mesh里：比如Mesh有一个属性说明自己在世界空间中的位置朝向 ，那么这个Mesh就被绑定到这个位置上了！绘制N个不同位置的Mesh就需要创建N个不同的Mesh实例！这在3D渲染中是无法想象的资源浪费。最后，我们还是不得不想办法把Mesh的Transform信息与Geometry数据分开。</p>
<p>SceneGraph是如何解决问题的？很简单，由于不同的Node可以挂接同一个Mesh，这样自然就实现的几何数据，乃至纹理，材质的共享。注册渲染的时候把Node注册到渲染器里就万事大吉了。</p>
<p>上述两个问题我认为是在3D游戏中采用SceneGraph的最重要的理由。</p>
<p>另，以下摘译自《<a href="http://book.douban.com/subject/3554163/">3D Game Engine Design</a>》一书：</p>
<blockquote><p>采用场景图组织游戏内容，对于游戏来说非常重要，以下是原因：</p>
<p>1. 需要管理的数据通常很大，并且是有艺术家通过小片小片的形式建造出来的。关卡编辑者可以将一整个关卡的内容，通过树结构聚合起来，则自然的由此结构造出了整体性的结构。譬如，场景中的一盏灯可以只照亮场景图中的某个子树。关卡编辑者的责任就是将该灯赋予场景图中的某个节点，该灯的效果就由场景管理系统来维护。</p>
<p>2. 层次组织结构提供了一个局部性的模式：通常游戏中存在当前交互的对象，都处在相同的空间范围内。场景图可以使得游戏程序快速的排除游戏的其他区域，从而加速后续的处理。尽管将尽可能少的数据传送至显卡是让游戏运行效率提高的主要目标，但是聚焦处理小块的数据，也对碰撞检测尤为重要。如果潜在可碰撞物体特别多的话，那么碰撞检测模块也会变得非常慢。场景图可以将一组潜在需碰撞的物体组织起来，这些物体仅在当前游戏交互区域内被考虑进来。</p>
<p>3. 很多物体天然就被用层次结构搭建起来：譬如绝大多数类人体的骨架系统。手部的位置和朝向，自然的决定于腕部，肘部以及肩部的位置和朝向。</p>
<p>4. 有持久化需要的游戏，有的时候玩家需要在某一时刻将游戏状态储存起来，并在将来从此状态继续。层次化组织结构可以使得存储世界的状态非常简单：要求根节点存储自己，然后后续的就是递归的存储每一个节点即可。</p></blockquote>
<p><strong>关于Scene Graph </strong><strong>的实现：</strong></p>
<p>SceneGraph的好处众多，但重要的问题是，维护这样一个数据结构需要的代价有多大？</p>
<p>最简单的Scene Graph就直接采用线性表，渲染，碰撞检测，都采取线性遍历即可，维护也极其简单，一个Node的移动旋转不会对其他Node产生任何影响（因为没有父子关系），因此也无需维护。对于小规模的场景，这种方案是可以接受的。但是当场景规模增大时，在可见性判断，碰撞检测等问题上，线性表的实现就表现出很大的局限性了。</p>
<p>因此比较常见的SceneGraph实现是一棵树，由Node构成，每个Node都可以挂接可渲染对象Object，并且可以有多个子Node，有且只有一个Parent Node。Node上挂接的可渲染对象决定了该Node的包围体。</p>
<p>当有Node的位置移动/朝向改变时（位于其局部坐标系内）一方面，从该Node出发，其父节点直至根节点的包围体都需要更新；另一方面，其所有的子孙节点的世界变换也需要更新以适应该节点的新位置朝向信息。</p>
<p>可能有不止一处改变在SceneGraph中的不同Node上同时发生，SceneGraph管理机制应当保证仅维护必要的节点：举例而言，如果A，B两个节点都有局部坐标变换发生，并且如果B是A的子孙节点，那么更新A的子树会自动更新B的子树，因此如果我们先更新B子树，再更新A子树，则会导致多余的计算，造成不必要的效率损失。</p>
<p>参考资料：</p>
<p>1.《3D Game Engine Desion》 Eberly, D.H <a href="http://book.douban.com/subject/3554163/">http://book.douban.com/subject/3554163/</a></p>
<p>2. Wiki SceneGraph <a href="http://en.wikipedia.org/wiki/Scene_graph">http://en.wikipedia.org/wiki/Scene_graph</a></p>
<p>3. <a href="http://www.gamerendering.com/category/scene-management/">http://www.gamerendering.com/category/scene-management/</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.windameister.org/blog/2010/11/14/value-of-scene-management-in-3d-game/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>D3D基础 – 光照，材质与着色#1</title>
		<link>http://www.windameister.org/blog/2010/09/18/d3dbasic-lighting-material-shading/</link>
		<comments>http://www.windameister.org/blog/2010/09/18/d3dbasic-lighting-material-shading/#comments</comments>
		<pubDate>Sat, 18 Sep 2010 04:22:25 +0000</pubDate>
		<dc:creator>windam</dc:creator>
				<category><![CDATA[3D渲染]]></category>
		<category><![CDATA[shader]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[d3d]]></category>
		<category><![CDATA[lighting]]></category>
		<category><![CDATA[pileline]]></category>
		<category><![CDATA[shading]]></category>

		<guid isPermaLink="false">http://www.windameister.org/blog/2010/09/18/d3d%e5%9f%ba%e7%a1%80-%e5%85%89%e7%85%a7%ef%bc%8c%e6%9d%90%e8%b4%a8%e4%b8%8e%e7%9d%80%e8%89%b2/</guid>
		<description><![CDATA[初接触d3d时，相信许多初学者和我一样，虽然对3D实时渲染的原理有所了解，但是却对整体的管线结构有所困惑，包括可能许多做3d游戏的程序员，由于常常采用封装好的引擎做上层逻辑开发，因此对底层的架构也未必了解得十分透彻。 长久以来，对3d图形学的底层技术，我一直也是只了解个只鳞半爪而已，最近一段时间，在工作中不断的接触到这方面的知识，才有了比较系统的思考与总结，本文记叙了我学习dx管线的一些思考，由于水平所限以及尚未对最新的DX11架构有所关注，所以本文不涵盖DX11的最新架构，只涉及DX9及以前的知识结构，虽然如此，但是相信如果系统的掌握了这块知识，对了解后续的新技术也是有所帮助的。 本文主要是针对对3D渲染知识有一定了解，然而对D3D的结构却不是很清楚的同学，把DX8/9的管线结构做了一番说明，将固定管线的光照、纹理混合与可编程管线加以对比，期望对这一块知识有一个比较完整的总结。 &#160; 目录 1. 硬件加速的3D渲染 2. 材质与光照 3. 变换，光照与顶点着色器 4. 纹理与像素着色器 5. Alpha测试，深度测试与Alpha混合 6. 更多话题 1. 硬件加速的3D渲染 硬件加速的3D渲染，早些时候只是被称为Hardware Transform &#38; Lighting的加速技术，这些技术是相对于早期时候，图形硬件尚未普及，软件实现的实时3d游戏中常用的软件变换与光照而言的。因此，最早的显卡最主要的目标也就是通过硬件来加速顶点变换，和顶点光照（如果严格来说，纹理采样应该也要算在内），从DX的固定管线接口中可以看出这一点，在固定管线中，通过SetTransform来设置变换矩阵，通过SetMaterial来设置材质属性，SetLight来设置光源参数，最后一个DrawPirmitive把三角形画到表面上去。 这几个步骤分别都是什么意思？接下来我会一一分说。 对于3D图形学有所了解的同学应当知道，绘制3D画面时，我们需要几样东西： 1) 所要绘制的目标：一个Mesh网格（包含了若干顶点，若干索引，实际构成为一个个三角形） 2) 物体的世界变换矩阵：通常我们表示一个3d物体（比如一个Mesh）的时候，都是用Local坐标系进行表示，实际渲染时，再乘上变换矩阵得到其在世界中的位置。这种做法有什么好处？或者是否非如此不可？读者可以想一想，其实原因非常简单。 : ) 3) 摄像机与视口：我们如何去看待三维世界中的物体，摄像机定义了我们观察物体的位置，角度，以及远近变换的程度 上述三样东西是必须的，有了相机和绘制目标，以及绘制目标的世界矩阵，我们就可以进行3D绘制了。但是想要获得更加具有真实感的图像，我们需要给绘制目标加上光照（注意，光照不等同于阴影，尽管当我们要实施阴影的时候，也往往是按照光照时设置的光源来绘制阴影的）此处所说的光照是指顶点光照，根据光源与顶点的相对位置，顶点的法线朝向，以及物体的受光材质决定最终的顶点颜色。一个简单的光照函数类似下面这样： DiffuseColor = Clamp(DotProduct(VertexNormal, VertexToLightDir), 0, 1) * LightColor * MaterialColor + Ambient Clamp函数保证点积的结果在[0.0, 1.0]区间内 上面的这个函数没有考虑光源衰减，灯光范围，不考虑SpotLight的衰减，另外只涉及漫反射光照，高光则需另外计算。但是从原理上已经可以说明（顶点）光照是怎么一回事了。这里强调顶点光照，是相对于像素光照来说的，顶点光照与像素光照后文会详述。 公式中提到的VertexToLightDir, LightColor属于光源的属性，在D3D9中则是由D3DLIGHT9来描述的，通过D3DDevice::SetLight接口进行设置。而MaterialColor属于材质属性，描述结构为D3DMATERIAL9，通过SetMaterial设置。 这样我们得到了绘制3D物体的另两样参数，分别为： 4) 光源：决定灯光的强度，颜色，范围，衰减等 5) [...]]]></description>
			<content:encoded><![CDATA[<p>初接触d3d时，相信许多初学者和我一样，虽然对3D实时渲染的原理有所了解，但是却对整体的管线结构有所困惑，包括可能许多做3d游戏的程序员，由于常常采用封装好的引擎做上层逻辑开发，因此对底层的架构也未必了解得十分透彻。</p>
<p>长久以来，对3d图形学的底层技术，我一直也是只了解个只鳞半爪而已，最近一段时间，在工作中不断的接触到这方面的知识，才有了比较系统的思考与总结，本文记叙了我学习dx管线的一些思考，由于水平所限以及尚未对最新的DX11架构有所关注，所以本文不涵盖DX11的最新架构，只涉及DX9及以前的知识结构，虽然如此，但是相信如果系统的掌握了这块知识，对了解后续的新技术也是有所帮助的。</p>
<p>本文主要是针对对3D渲染知识有一定了解，然而对D3D的结构却不是很清楚的同学，把DX8/9的管线结构做了一番说明，将固定管线的光照、纹理混合与可编程管线加以对比，期望对这一块知识有一个比较完整的总结。</p>
<p>&nbsp;</p>
<h5>目录</h5>
<p>1. 硬件加速的3D渲染</p>
<p>2. 材质与光照</p>
<p>3. 变换，光照与顶点着色器</p>
<p>4. 纹理与像素着色器</p>
<p>5. Alpha测试，深度测试与Alpha混合</p>
<p>6. 更多话题</p>
<p><span id="more-231"></span></p>
<h5>1. 硬件加速的3D渲染</h5>
<p>硬件加速的3D渲染，早些时候只是被称为Hardware Transform &amp; Lighting的加速技术，这些技术是相对于早期时候，图形硬件尚未普及，软件实现的实时3d游戏中常用的软件变换与光照而言的。因此，最早的显卡最主要的目标也就是通过硬件来加速顶点变换，和顶点光照（如果严格来说，纹理采样应该也要算在内），从DX的固定管线接口中可以看出这一点，在固定管线中，通过SetTransform来设置变换矩阵，通过SetMaterial来设置材质属性，SetLight来设置光源参数，最后一个DrawPirmitive把三角形画到表面上去。</p>
<p>这几个步骤分别都是什么意思？接下来我会一一分说。</p>
<p>对于3D图形学有所了解的同学应当知道，绘制3D画面时，我们需要几样东西：</p>
<p>1) 所要绘制的目标：一个Mesh网格（包含了若干顶点，若干索引，实际构成为一个个三角形）</p>
<p>2) 物体的世界变换矩阵：通常我们表示一个3d物体（比如一个Mesh）的时候，都是用Local坐标系进行表示，实际渲染时，再乘上变换矩阵得到其在世界中的位置。这种做法有什么好处？或者是否非如此不可？读者可以想一想，其实原因非常简单。 : )</p>
<p>3) 摄像机与视口：我们如何去看待三维世界中的物体，摄像机定义了我们观察物体的位置，角度，以及远近变换的程度</p>
<p>上述三样东西是必须的，有了相机和绘制目标，以及绘制目标的世界矩阵，我们就可以进行3D绘制了。但是想要获得更加具有真实感的图像，我们需要给绘制目标加上光照（注意，光照不等同于阴影，尽管当我们要实施阴影的时候，也往往是按照光照时设置的光源来绘制阴影的）此处所说的光照是指顶点光照，根据光源与顶点的相对位置，顶点的法线朝向，以及物体的受光材质决定最终的顶点颜色。一个简单的光照函数类似下面这样：</p>
<p>DiffuseColor = Clamp(DotProduct(VertexNormal, VertexToLightDir), 0, 1) * LightColor * MaterialColor + Ambient</p>
<p><em>Clamp函数保证点积的结果在[0.0, 1.0]区间内</em></p>
<p>上面的这个函数没有考虑光源衰减，灯光范围，不考虑SpotLight的衰减，另外只涉及漫反射光照，高光则需另外计算。但是从原理上已经可以说明（顶点）光照是怎么一回事了。这里强调顶点光照，是相对于像素光照来说的，顶点光照与像素光照后文会详述。</p>
<p>公式中提到的VertexToLightDir, LightColor属于光源的属性，在D3D9中则是由D3DLIGHT9来描述的，通过D3DDevice::SetLight接口进行设置。而MaterialColor属于材质属性，描述结构为D3DMATERIAL9，通过SetMaterial设置。</p>
<p>这样我们得到了绘制3D物体的另两样参数，分别为：</p>
<p>4) 光源：决定灯光的强度，颜色，范围，衰减等</p>
<p>5) 材质：决定物体受光的颜色，比如是绿色的物体，或红色的物体等。</p>
<p>有了光源和材质，绘制的3D物体则有了明暗和颜色，这对于提升渲染的真实感是有很大帮助的。</p>
<p>在此做一下小节：我们刚才回顾了绘制3D画面时，所必要做的事情，我们想象一下，如果没有3D硬件，或者不利用D3D，要实现上述功能，得由我们自己去完成哪些事？</p>
<p>首先要做软件的顶点变换，对每一个顶点，使用世界矩阵把它乘到世界坐标系中，用相机参数做必要的可见性剔除，利用相机朝向与面法线朝向做隐藏面消除，用光源与材质参数对世界空间中的顶点位置做顶点光照，将世界空间中的三角形变换到屏幕空间中，利用光照得到的顶点颜色，对三角形做<a href="http://en.wikipedia.org/wiki/Gouraud_shading">Gouraud插值着色</a>，最终得到绘制在屏幕上的图像。（有一本书叫做<a href="http://book.douban.com/subject/1321769/">《3D游戏编程大师技巧》</a>，对上述过程作了非常详尽的描述）</p>
<p>好了，有了图形硬件与D3D之后，上述工作都省掉了，我们通过SetTransform来设置世界矩阵、观察矩阵和投影矩阵，通过SetMaterial/SetLight来设置材质和光源参数，然后通过DrawPrimitive来绘制三角面。图形硬件和D3D接口极大的简化了我们编写3D程序的难度，诸多繁杂的事情都被交给硬件去完成了。</p>
<p>&nbsp;</p>
<h5>2. 材质与光照</h5>
<p>前一节主要是简单介绍了一下D3D硬件都为我们做了哪些事——事实上现如今的图形硬件所完成的功能已经远远不止上述那些了。接下来一节，我准备总结一下材质与光照的细节和原理。</p>
<p>上一节简单介绍了下材质与光照，但是没有深入系统的做总结，本节准备将这个过程做一个比较系统的总结。</p>
<p>关于几种基本光源，其公式，原理，实现，可以通过<a href="http://www.cnblogs.com/miloyip/archive/2010/04/02/1702768.html">MiloYip大牛的这篇文章</a>来了解。该文中的范例是基于光线追踪渲染器实现的，因此光和影的效果直接都有了，甚至在最后一个Demo中运用多光源模拟了软阴影的绘制。然而在光栅化渲染器中，光照与材质只能解决物体的颜色以及明暗问题，不能解决影子问题。要在光栅化渲染器中实现阴影的绘制，则是另外一番话题了。</p>
<p>接下来还是借助D3D的接口来说材质。</p>
<div class="codearea linewrap">
<pre><span style="color: blue;">typedef struct </span><span style="color: #010001;">_D3DMATERIAL9 </span>{
    <span style="color: #010001;">D3DCOLORVALUE   Diffuse</span>;        <span style="color: green;">/* Diffuse color RGBA */
    </span><span style="color: #010001;">D3DCOLORVALUE   Ambient</span>;        <span style="color: green;">/* Ambient color RGB */
    </span><span style="color: #010001;">D3DCOLORVALUE   Specular</span>;       <span style="color: green;">/* Specular 'shininess' */
    </span><span style="color: #010001;">D3DCOLORVALUE   Emissive</span>;       <span style="color: green;">/* Emissive color RGB */
    </span><span style="color: blue;">float           </span><span style="color: #010001;">Power</span>;          <span style="color: green;">/* Sharpness if specular highlight */
</span>} <span style="color: #010001;">D3DMATERIAL9</span>;</pre>
</div>
<p>材质描述了物体是如何对光作出响应的。虽然在可编程管线中，材质、光照都有自定义的实现方法，然而在固定管线中，光照采用的都是D3D所固有的一套机制，D3D中的最终光照结果由下式构成：</p>
<p>Global Illumination = Ambient Light + Diffuse Light + Specular Light + Emissive Light</p>
<p>环境光（Ambient Light）的计算方法为：Ambient Lighting = <a href="http://msdn.microsoft.com/en-us/library/bb172256(VS.85).aspx"><img src="http://latex.codecogs.com/gif.latex?C_{a}%20*%20[G_{a}%20+%20\sum%20\left%20(%20Atten_{i}%20*%20Spot_{i}%20*%20L_{ai}%20\right%20)]" alt="" /></a></p>
<p>其中C<sub>a</sub>为材质的Ambient分量，G<sub>a</sub>为全局环境光颜色（通过ID3DDevice9::SetRenderState(D3DRS_AMBIENT, COLOR)设置全局环境光颜色），求和的部分是所有被激活的光源的环境光进行求和，光源的衰减，聚光灯的Factor（如果是聚光灯）会被考虑在内。</p>
<p>漫反射光（Diffuse Light）的计算方法：Diffuse Lighting = <a href="http://msdn.microsoft.com/en-us/library/bb219656(VS.85).aspx"><img src="http://latex.codecogs.com/gif.latex?\sum%20(C_{d}%20*%20L_{d}%20*%20(N%20\cdot%20L_{dir}))%20*%20Atten%20*%20Spot)" alt="" /></a></p>
<p>解释一下这个方程，其中C<sub>d</sub>是材质的DiffuseColor，L<sub>d</sub>是光的DiffuseColor，N是顶点法线，L<sub>dir</sub>是从顶点到光源的方向向量（N与L<sub>dir</sub>都是单位向量），再乘以光源的衰减参数，及SpotLight的因子。这里的N<sup>.</sup>L<sub>dir</sub>正是计算漫反射光强弱的关键，一个顶点是否能被光源照亮，取决于该点法线与光方向的夹角，如果两者重合，该点受光程度达到最大，如果夹角大于等于90度，则不受光。</p>
<p>高光（Specular Light）的计算方法：Specular Lighting = <a href="http://msdn.microsoft.com/en-us/library/bb147399(VS.85).aspx"><img src="http://latex.codecogs.com/gif.latex?C_{s}%20*%20\sum%20[L_{s}%20*%20(N%20\cdot%20H)^{p}%20*%20Atten%20*%20Spot]" alt="" /></a></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>C<sub>s</sub>是材质的Specular值，p是材质中的Power，L<sub>s</sub>是灯光的Specular值，N是顶点法线，H是一个被称为Halfway vector的单位方向，该向量的含义为顶点到摄像机的向量与顶点到光源的向量的中间向量，计算方式为（H = norm(norm(C<sub>p</sub> &#8211; V<sub>p</sub>) + L<sub>dir</sub>)）</p>
<p>关于Halfway vector的含义，可以参考下图：</p>
<p><a href="http://www.windameister.org/blog/wp-content/uploads/2010/09/halfwayvector.png"><img style="display: inline; border-width: 0px;" title="halfwayvector" src="http://www.windameister.org/blog/wp-content/uploads/2010/09/halfwayvector_thumb.png" border="0" alt="halfwayvector" width="244" height="244" /></a></p>
<p>仔细思考一下我们就能明白，相比将入射光向各个方向均匀反射的漫反射而言（对方向不敏感），高光对视线与反射光线方向是敏感的，也就是说，从眼睛到顶点与反射光方向的夹角越小就会越亮，日常生活中我们往往可以在光滑表面看到高光现象，比如汽车表面的喷漆或者玻璃。</p>
<p>最后一个是自发光，自发光很简单，就直接等于材质参数中的Emissive分量：Emissive Lighting = C<sub>e</sub></p>
<p>上述这些就是D3D固定管线中材质与光照的工作原理。其实所有的公式都可以在DXSDK的文档中获取到，这里不过是简单总结了一下。</p>
<p>前面所描述的这些材质与光照，依赖于正确的输入，比如顶点法线：漫反射和高光的计算都要依赖于顶点法线。我们都知道D3D有自定义的顶点格式，被称为FVF的东西。那么也就是说，如果想要正确的使用D3D顶点光照，我们采用的顶点格式，应当至少包含法线（D3DFVF_NORMAL）。</p>
<p>我们也常见到不包含法线，而是包含漫反射颜色（D3DFVF_DIFFUSE）和高光色彩（D3DFVF_SPECULAR）的顶点格式，采用这种顶点格式往往意味着我们准备自行为顶点填充颜色，而不采用D3D光照。</p>
<p>&nbsp;</p>
<h5>3. 变换，光照与顶点着色器</h5>
<p>要说顶点着色器，则不得不从固定管线与顶点格式说起。D3D中有所谓的Flexible Vertex Format，也就是大名鼎鼎的FVF常量。我们常见的D3DFVF_DIFFUSE，D3DFVF_XYZ，D3DFVF_NORMAL都是定义好的常量，在需要用的时候，我们把这些常量“或”在一起，构成一个顶点格式，传递给D3D使用。D3D固定管线通过该FVF格式对我们传入的VertexBuffer进行解释（每份顶点数据的长度，以及每个成员的偏移）。</p>
<p>起初学习D3D的时候，我对FVF格式有个疑惑，百思而不得其解。我们都知道要写一个普通的具有位置，法线和UV的顶点格式声明应该怎么写：</p>
<div class="codearea linewrap">
<pre><span style="color: blue;">#define </span><span style="color: #010001;">MY_VERTEX_FVF D3DFVF_XYZ </span>| <span style="color: #010001;">D3DFVF_NORMAL </span>| <span style="color: #010001;">D3DFVF_TEX0

</span><span style="color: blue;">struct </span><span style="color: #010001;">VERTEX
</span>{
    <span style="color: blue;">float </span><span style="color: #010001;">x</span>, <span style="color: #010001;">y</span>, <span style="color: #010001;">z</span>;
    <span style="color: blue;">float </span><span style="color: #010001;">nx</span>, <span style="color: #010001;">ny</span>, <span style="color: #010001;">nz</span>;
    <span style="color: blue;">float </span><span style="color: #010001;">u</span>, <span style="color: #010001;">v</span>;
};</pre>
</div>
<p>我觉得很奇怪，D3D怎么知道我把x，y，z放在前面还是把nx，ny，nz放在前面，难道是根据我定义FVF时的先后顺序？明显讲不通啊：因为或运算不可能给出任何先后顺序的信息来的。</p>
<p>直到后来我才明白，原来D3D固定管线的顶点格式也是有固定顺序的，也就是说如果要写一个既带有位置，又带有法线的顶点数据结构，那么x，y，z一定要放在nx，ny，nz之前，或者说，D3D是根据一个固定的偏移去解释这个顶点数据结构的，首先一定是位置，然后才是法线，其他更复杂包含更多成员的数据格式也是与此同理。什么意思呢？</p>
<p>还是以上面的例子来解释，你把顶点数据结构写成下面这样（区别在于，nx，ny，nz与x，y，z调换了顺序）：</p>
<div class="codearea linewrap">
<pre><span style="color: blue;">#define </span><span style="color: #010001;">MY_VERTEX_FVF D3DFVF_XYZ </span>| <span style="color: #010001;">D3DFVF_NORMAL </span>| <span style="color: #010001;">D3DFVF_TEX0

</span><span style="color: blue;">struct </span><span style="color: #010001;">VERTEX
</span>{
    <span style="color: blue;">float </span><span style="color: #010001;">nx</span>, <span style="color: #010001;">ny</span>, <span style="color: #010001;">nz</span>;
    <span style="color: blue;">float </span><span style="color: #010001;">x</span>, <span style="color: #010001;">y</span>, <span style="color: #010001;">z</span>;
    <span style="color: blue;">float </span><span style="color: #010001;">u</span>, <span style="color: #010001;">v</span>;
};</pre>
</div>
<p>D3D会说，不好意思，我不知道你怎么命名的，也不在乎你把它叫nx还是x，反正我都把你你头三个浮点数就当作是位置，接下来三个浮点数当成是法线，只要你给我的FVF是有D3DFVF_XYZ的，那头三个浮点就是位置没跑了！</p>
<p>从上面的例子中我们可以看到，实际上固定管线的结构是很死板的，缺乏给程序员自由发挥的空间，它规定好了一系列的顶点数据的用途，然后你必须按照它指定的方式去做，否则结果就不对。</p>
<p>当然了，这也只是后来的可编程管线替代固定管线的诸多原因之一。如今我们见到的市面上的所有显卡基本上已经没有不支持可编程管线的了，而且DX也已经更新换代到了DX11，新架构与本文中所介绍的基于DX9的结构又有了很多变化。</p>
<p>继续前面所述的话题，D3DFVF格式不仅决定了顶点数据结构如何定义，也说明了顶点要如何进行坐标系变换，比如：</p>
<p>D3DFVF_XYZ说明了顶点需要进行全套的世界、观察、投影变换；</p>
<p>D3DFVF_XYZRHW则说明该顶点坐标已经是屏幕坐标，x，y表示窗口中的坐标，z值表示z-buffer中的值，从近到远为0.0~1.0，采用这种格式的顶点声明不能与D3DFVF_XYZ或者D3DFVF_NORMAL混用，并且不经过顶点处理单元；</p>
<p>D3DFVF_XYZW也是表示已经变换过后的顶点，但是这种顶点格式会经过顶点处理单元，也就是vertexshader的阶段；</p>
<p>D3DFVF_XYZBn (n=1..4) 表示该顶点在变换阶段需要进行混合，啥意思？也就是说在做变换的时候，不是一个世界矩阵对其起作用，而是有n个矩阵，根据Blend的参数共同对该顶点起作用。如下例（摘自DXSDK）：</p>
<div class="codearea linewrap">
<pre><span style="color: blue;">#define </span><span style="color: #010001;">D3DFVF_BLENDVERTEX </span>(<span style="color: #010001;">D3DFVF_XYZB3</span>|<span style="color: #010001;">D3DFVF_NORMAL</span>|<span style="color: #010001;">D3DFVF_TEX1</span>)

<span style="color: blue;">struct </span><span style="color: #010001;">BLENDVERTEX
</span>{
    <span style="color: #010001;">D3DXVECTOR3 v</span>;       <span style="color: green;">// Referenced as v0 in the vertex shader
    </span><span style="color: #010001;">FLOAT       blend1</span>;  <span style="color: green;">// Referenced as v1.x in the vertex shader
    </span><span style="color: #010001;">FLOAT       blend2</span>;  <span style="color: green;">// Referenced as v1.y in the vertex shader
    </span><span style="color: #010001;">FLOAT       blend3</span>;  <span style="color: green;">// Referenced as v1.z in the vertex shader
    // v1.w = 1.0 - (v1.x + v1.y + v1.z)
    </span><span style="color: #010001;">D3DXVECTOR3 n</span>;       <span style="color: green;">// Referenced as v3 in the vertex shader
    </span><span style="color: #010001;">FLOAT       tu</span>, <span style="color: #010001;">tv</span>;  <span style="color: green;">// Referenced as v7 in the vertex shader
</span>};</pre>
</div>
<p>也就是说v在被变换的时候，会被4个矩阵依次乘上，并将其结果使用（blend1,blend2,blend3,1-(blend1 + blend2 + blend3)）四个参数作为系数进行混合。注意一下SetTransform函数，它有两个参数，第一个参数的说明里有这么一段：</p>
<p>&nbsp;</p>
<dt><em>State</em>&nbsp;</p>
</dt>
<dd>[in] Device-state variable that is being modified. This parameter can be any member of the <a href="http://msdn.microsoft.com/en-us/library/bb172619(VS.85).aspx">D3DTRANSFORMSTATETYPE</a> enumerated type, or the <a href="http://msdn.microsoft.com/en-us/library/bb172623(VS.85).aspx">D3DTS_WORLDMATRIX</a> macro.</dd>
<dd>其中的D3DTS_WORLDMATRIX是一个宏，可以像这么用D3DTS_WORLDMATRIX(0)，D3DTS_WORLDMATRIX(1)，这样一来就可以传递多个矩阵到固定管线中了（等到可编程管线就没这么麻烦了，可以直接通过顶点着色器常量传递矩阵）。</p>
<p>最后简单说一下D3DFVF_XYZBn这种顶点格式的用途：这种顶点格式往往被用在骨骼动画的顶点里，因为骨骼动画需要一个顶点受多个骨骼的影响。</p>
<p>定义好了FVF与顶点数据结构之后，我们需要把它设置到设备上去。我们知道，早些年的DX8也好，DX9也好，其Device都有一个SetVertexShader接口，用于设置顶点着色器程序。而在DX8中，这个接口不仅仅是可编程管线的vertex shader通过它来设置，实际上如果采用的是固定管线的话，顶点的FVF格式也是走这个接口设置的。DX8中的Vertex shader创建出来之后是一个32位的DWORD类型的handle，从而得以与FVF常量共用这同一个接口。而在DX9中，SetVertexShader的接受参数变成一个<strong>IDirect3DVertexShader9*</strong>了，而固定管线的FVF被挪走，需要通过接口SetFVF来进行设置了。</p>
<p>另外，由于在DX9中，<strong>IDirect3DVertexShader9</strong>在创建的时候，并没有包含顶点声明信息，因此DX9增加了一个接口<strong>IDirect3DVertexDeclaration9</strong>专门用于为可编程管线的VertexShader提供顶点声明信息。为什么一定要有顶点声明信息呢？写过HLSL的同学都知道，我们在vertex shader里是必须要定义vs input的数据结构的，顶点声明信息必须与我们的VS_INPUT数据结构保持一致。那么这个顶点声明信息在什么时候用呢？仔细思考一下就会明白，vs是一段运行于GPU上的程序，它按照我们编写的HLSL代码接受输入，经过计算后输出，输入的数据是在顶点缓冲中准备好的，在DrawPrimitive之前，通过SetVertexBuffer设置，此后这些输入数据通过驱动程序被提供给显卡，然而底层如何知晓应把多长一截的数据作为一份输入的顶点数据呢？以及应该把顶点数据中的哪一部分送到顶点寄存器里，哪些送到纹理采样单元里去？到这里，就不得不依赖顶点声明提供的信息了。</p>
<p>前面在材质与光照一节，我们简单介绍了一下D3D固定管线光照中，材质是如何与灯光一起起作用，并作用于物体的色泽，明暗，高光的。现在当我们手握Shader这样的利器，就拥有了绕开D3D固定管线光照的自由了（譬如如今已经很普及的基于像素的光照技术，就是把光照从顶点着色阶段推迟至像素着色器中）。我们还可以利用顶点着色器的常量传递变换矩阵，从而拥有自定义顶点变换，或者在顶点变换阶段获取一些我们想要的中间结果的能力。</p>
<h5>4. 纹理与像素着色器</h5>
<p>前面说了关于顶点变换与顶点光照相关的东西。接下来要说的是纹理混合，也就是SetTextureStageState函数干的事情。我们都知道DX支持纹理混合，我们可以把多个纹理按照Sampler的索引(DX9中是Sampler，而在DX8中则是Stage)，通过SetTexture函数设置到设备上。</p>
<p>&nbsp;</p>
<p>所谓Stage是什么一个概念呢？参考下图（来源于DX9SDK）：</p>
<p><a href="http://www.windameister.org/blog/wp-content/uploads/2010/09/dx_texture_stage.png"><img style="display: inline; border-width: 0px;" title="dx_texture_stage" src="http://www.windameister.org/blog/wp-content/uploads/2010/09/dx_texture_stage_thumb.png" border="0" alt="dx_texture_stage" width="400" height="393" /></a></p>
<p>在每一个Stage上设有一张纹理（如果没有设置纹理），在固定管线中，我们通过<strong>SetTextureStageState</strong>设置在当前的Stage中，纹理如何与“当前像素”混合并输出到下一个Stage，“当前像素”来源于上一个Stage的输出，0Stage的当前像素的Color及Alpha是顶点Color/Alpha插值得来的。在纹理阶段比较常见的就是DiffuseTexture，SpecularTexture，分别用于颜色与高光。在每一个Stage中，纹理颜色与上一阶段颜色采用下式进行混合：</p>
</dd>
<p><em>FinalColor = TexelColor × SourceBlendFactor + PixelColor × DestBlendFactor</em></p>
<p>这里需要注意的是，纹理混合与最终的AlphaBlending是两个概念。纹理混合阶段，根据顶点颜色以及纹理决定最终要输出的像素的颜色和透明度，而到了AlphaBlending阶段，则是决定该像素与已经绘制到BackBuffer上的像素如何去混合。此外，更复杂的功能如NormalMapping，通常则需要像素着色器参与。按照我的理解，如果想要实现像素光照，则必须把光照阶段从顶点着色器中推迟到像素着色器中才可行，NormalMap提供了这一阶段每个像素的法线，从而使像素光照得以实现。（D3D9在固定管线中提供了一套<a href="http://msdn.microsoft.com/en-us/library/bb206304(VS.85).aspx">BumpMap</a>，但是老实说，<a href="http://msdn.microsoft.com/en-us/library/bb172379(VS.85).aspx">D3D9文档里对此的说明</a>看得我云里雾里的，并没有搞清楚其原理究竟是什么。）</p>
<p>像素着色器最初的就是为了替代固定管线的纹理混合操作而诞生的。也就是说，如果采用了PixelShader对像素进行着色，那么SetTextureStageState里面的那些颜色，Alpha混合操作就不再起作用了。</p>
<p>我们以一个常见的例子来解释纹理混合的用法，美术可能要求实现一种<a href="http://en.wikipedia.org/wiki/Blend_modes#Multiply">正片叠底</a>的纹理混合效果，对于程序实现而言，实现方案就是找两张贴图，用乘法进行纹理混合。如果采用SetTextureStageState实现：</p>
<p class="codearea linewrap">&nbsp;</p>
<pre><span style="color: #010001;">pD3DDevice</span>-&gt;<span style="color: #010001;">SetTextureStageState</span>(0, <span style="color: #010001;">D3DTSS_COLOROP</span>, <span style="color: #010001;">D3DTOP_SELECTARG1</span>);
<span style="color: #010001;">pD3DDevice</span>-&gt;<span style="color: #010001;">SetTextureStageState</span>(0, <span style="color: #010001;">D3DTSS_COLORARG1</span>, <span style="color: #010001;">D3DTA_TEXTURE</span>);

<span style="color: #010001;">pD3DDevice</span>-&gt;<span style="color: #010001;">SetTextureStageState</span>(1, <span style="color: #010001;">D3DTSS_COLOROP</span>, D<span style="color: #010001;">3DTOP_MODULATE</span>);
<span style="color: #010001;">pD3DDevice</span>-&gt;<span style="color: #010001;">SetTextureStageState</span>(1, <span style="color: #010001;">D3DTSS_COLORARG1</span>, <span style="color: #010001;">D3DTA_TEXTURE</span>);
<span style="color: #010001;">pD3DDevice</span>-&gt;<span style="color: #010001;">SetTextureStageState</span>(1, <span style="color: #010001;">D3DTSS_COLORARG2</span>, <span style="color: #010001;">D3DTA_CURRENT</span>);</pre>
<p>&nbsp;</p>
<p>解释一下这里的两个StageState的设置：阶段0，我们对Color采用SELECTARG1操作，同时把COLORARG1，设置为D3DTA_TEXTURE，也就是说，完全以SetTexture(0, …)设置的纹理颜色作为这一阶段的输出；在阶段1，COLOROP为MODULATE，也就是将COLORARG1与COLORARG2相乘作为输出，公式：<img src="http://latex.codecogs.com/gif.latex?S_{rgba}%20=%20Arg1%20\times%20Arg2" alt="" /></p>
<p>&nbsp;</p>
<p>其中COLORARG1设置为D3DTA_TEXTURE，也就是SetTexture(1, …)中设置的纹理，而COLORARG2为D3DTA_CURRENT，表示的是上一阶段的输出。这样经过这两个Stage之后，我们获得的输出就是两张贴图相乘的结果了。</p>
<p>接下来再用Ps2.0（需要DX9，如果采用DX8则需用ps1.4实现）实现一遍该效果：</p>
<p class="codearea">&nbsp;</p>
<pre><span style="color: #010001;">texture g_Tex0</span>;
<span style="color: #010001;">texture g_Tex1</span>;
<span style="color: #010001;">sampler g_Sampler0 </span>= <span style="color: #010001;">sampler_state </span>{<span style="color: #010001;">Texture </span>= <span style="color: #010001;">g_Tex0</span>; <span style="color: #010001;">MipFilter </span>= <span style="color: #010001;">LINEAR</span>; <span style="color: #010001;">MinFilter </span>= <span style="color: #010001;">LINEAR</span>; <span style="color: #010001;">MagFilter </span>= <span style="color: #010001;">LINEAR</span>;}
<span style="color: #010001;">sampler g_Sampler1 </span>= <span style="color: #010001;">sampler_state </span>{<span style="color: #010001;">Texture </span>= <span style="color: #010001;">g_Tex1</span>; <span style="color: #010001;">MipFilter </span>= <span style="color: #010001;">LINEAR</span>; <span style="color: #010001;">MinFilter </span>= <span style="color: #010001;">LINEAR</span>; <span style="color: #010001;">MagFilter </span>= <span style="color: #010001;">LINEAR</span>;}

<span style="color: blue;">struct </span><span style="color: #010001;">VS_OUTPUT
</span>{
    <span style="color: #010001;">float4 Position   </span>: <span style="color: #010001;">POSITION</span>;   <span style="color: green;">// vertex position
    </span><span style="color: #010001;">float2 TexCoord   </span>: <span style="color: #010001;">TEXCOORD0</span>;  <span style="color: green;">// vertex texture coords
</span>};

<span style="color: #010001;">float4 PS_Main_2_0</span>( <span style="color: #010001;">VS_OUTPUT Input </span>) : <span style="color: #010001;">COLOR0
</span>{
    <span style="color: #010001;">float4 color0 </span>= <span style="color: #010001;">tex2D</span>( <span style="color: #010001;">g_Sampler0</span>, <span style="color: #010001;">Input</span>.<span style="color: #010001;">TexCoord </span>);
    <span style="color: #010001;">float4 color1 </span>= <span style="color: #010001;">tex2D</span>( <span style="color: #010001;">g_Sampler1</span>, <span style="color: #010001;">Input</span>.<span style="color: #010001;">TexCoord </span>);
    <span style="color: blue;">return </span><span style="color: #010001;">float4</span>(<span style="color: #010001;">color0 </span>* <span style="color: #010001;">color1</span>);
}</pre>
<p>&nbsp;</p>
<p>使用ps的话，所需要做的工作非常直观，用预先定义好的<a href="http://msdn.microsoft.com/en-us/library/bb509644(VS.85).aspx">Sampler</a>以及uv坐标对纹理采样，并把颜色相乘输出即可。</p>
<p>这只是一个最简单的例子，实际上ps的用途非常广泛（实现ShaderMap，像素光照，诸多后期处理效果都需要采用ps实现），而更多的内容就不可能涵盖在本文的范围之内了，感兴趣的读者可以读一读GPUGems系列，ShaderX系列等经典书籍。</p>
<h5>5. Alpha测试，深度测试，以及Alpha混合</h5>
<p>走完了像素着色的过程之后，就到了最终的绘制步骤了。这里有几个因素决定我们是否，以及如何往BackBuffer/ZBuffer/StencilBuffer上绘制像素：AlphaTest，StencilTest，ZTest，Alpha混合模式。</p>
<p>关于上述测试的顺序和流程，由于OpenGl与D3D在上述测试的顺序上达成了一致，因此可以参考下图（图摘自<a href="http://www.opengl.org/documentation/specs/version2.0/glspec20.pdf">OpenGL2.0 spec Page 199</a>）：</p>
<p><a href="http://www.windameister.org/blog/wp-content/uploads/2010/09/PreFragmentOperations.png"><img style="display: inline; border-width: 0px;" title="Pre-FragmentOperations" src="http://www.windameister.org/blog/wp-content/uploads/2010/09/PreFragmentOperations_thumb.png" border="0" alt="Pre-FragmentOperations" width="648" height="492" /></a></p>
<p>如果开启了AlphaTest，则首先做AlphaTest，根据像素的Alpha值与一个指定的D3DRS_ALPHAREF值比较的结果，将D3DRS_ALPHAFUNC的判断未能通过的像素直接砍掉。（不会进行后续的z-test以及z-write）</p>
<p>如果开启了StencilTest，则继续做StencilTest，如果不通过StencilTest，则也不会进行下一步ZTest，但是可能会修改StencilBuffer。</p>
<p>如果开启了ZTest，根据当前像素Z值，与DepthBuffer上该位置已有Z值做比较，如果D3DRS_ZFUNC的判断未能通过，则将该像素砍掉，但是根据D3DRS_STENCILZFAIL的状态，有可能会修改StencilBuffer。通过了ZBuffer的像素，如果开启了ZWrite，则该像素的Z值会写入DepthBuffer，否则（ZWrite关闭）该像素不会影响DepthBuffer（也就是说不会遮挡后续绘制的像素，尽管可能该像素离观察者比将来要绘制的像素更近）</p>
<p>最后根据D3DRS_ALPHABLENDENABLE决定是否开启Alpha混合。如果Alpha混合没有开启：则直接把像素的颜色透明度写入BackBuffer，如果Alpha混合开启了，则根据D3DRS_SRCBLEND以及D3DRS_DESTBLEND的选项，决定新写入像素如何与BackBuffer已有像素进行混合。</p>
<p>上述流程是标准流程，然而在较新的几代显卡里，针对StencilTest与ZTest又有了许多新的技术，比如EarlyZ优化等，由于这里牵扯到的显卡型号，各厂商可能都有不一致的地方，我就没有做更进一步的详细研究。下面有几个可以供参考的链接，感兴趣的同学可以自己了解一下：</p>
<p>amddeveloper的一篇文章在这里：<a href="http://developer.amd.com/media/gpu_assets/Depth_in-depth.pdf">http://developer.amd.com/media/gpu_assets/Depth_in-depth.pdf</a></p>
<p>beyond3d上的一个讨论帖看这里：<a href="http://forum.beyond3d.com/showthread.php?t=51025">http://forum.beyond3d.com/showthread.php?t=51025</a></p>
<p>&nbsp;</p>
<h5>6. 更多话题</h5>
<p>本文介绍了D3D流水线的一般过程，对比了固定管线与可编程管线的差别（当然有很多细节未能兼顾到），对D3D渲染流程的知识结构做了一个一般性的总结。</p>
<p>事实上，随着图形技术的发展，如今的3D图形技术已远走出很长一段路了，比如在DX9架构下就已经发展出很多成果的ShadowMap，NormalMap，Realtime Global Illumination等。再比如到DX10新增的Geometry Shader以及DX11架构下新增的Compute Shader，Tessellation等。</p>
<p>有时候学得越多，才越知道自己所知甚少。在撰写本文的过程中，我也把此前有些混淆的概念做了整理，自己也有不少收获。如果读者对本文所涉的话题有更多理解，或者发现了错漏之处，还望不吝告知。 : )</p>
<p>&nbsp;</p>
<p>#1 更正了文中的几处文字、公式错误，修改了针对高光概念的描述。(感谢<a href="http://1111h.blogspot.com/">Rainsing</a>的帮助)</p>
]]></content:encoded>
			<wfw:commentRss>http://www.windameister.org/blog/2010/09/18/d3dbasic-lighting-material-shading/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>溶解效果的原理与实现#1</title>
		<link>http://www.windameister.org/blog/2010/09/11/dissolve-effect-implement-in-dx9/</link>
		<comments>http://www.windameister.org/blog/2010/09/11/dissolve-effect-implement-in-dx9/#comments</comments>
		<pubDate>Sat, 11 Sep 2010 04:21:30 +0000</pubDate>
		<dc:creator>windam</dc:creator>
				<category><![CDATA[shader]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[3d]]></category>
		<category><![CDATA[dissolve]]></category>
		<category><![CDATA[effect]]></category>
		<category><![CDATA[pixel shader]]></category>

		<guid isPermaLink="false">http://www.windameister.org/blog/2010/09/11/%e6%ba%b6%e8%a7%a3%e6%95%88%e6%9e%9c%e7%9a%84%e5%8e%9f%e7%90%86%e4%b8%8e%e5%ae%9e%e7%8e%b0/</guid>
		<description><![CDATA[在游戏中实现溶解效果，往往有两种选择：其一是采用多重纹理；其二是采用模板缓冲。 本文说明采用多重纹理方案时如何实现溶解效果，采用模板缓冲的方案容后再补。 上图是一个利用PixelShader达成的效果，其原理是，当我们把一个面片绘制到渲染表面上时，将特定像素的Alpha值设为0，从而在绘制到表面与背景混合时，达到镂空的效果。镂空的图案由美术指定一张Mask纹理确定。譬如在上图的效果中，我利用了下面这样的两张纹理图（左图是前景贴图，右图作为Mask），美术可以通过指定不同的Mask图，实现不同类型的逐渐消解/生成的效果： 实现此效果需要两张贴图，一张是物体表面的纹理，另一张作为溶解的Mask图。 &#160; &#160; PixelShader(ps_2_0)代码如下： float g_DissolveThreshold;    // 该参数作为阈值，与mask图上的当前像素比较大小以决定像素是否镂空 float4 PS_Main_2_0( VS_OUTPUT Input ) : COLOR0 { float4 DiffuseColor = tex2D( diffuse_sampler, Input.TexCoord ); float4 TexDissolve = tex2D( mask_sampler, Input.TexCoord ); DiffuseColor.rgb = DiffuseColor.rgb * Input.Diffuse.rgb; float originalalpha = Input.Diffuse.a * DiffuseColor.a; float dissolveDelta = TexDissolve.r &#8211; g_DissolveThreshold; if (dissolveDelta &#62;= 0) DiffuseColor.a [...]]]></description>
			<content:encoded><![CDATA[<p>在游戏中实现溶解效果，往往有两种选择：其一是采用多重纹理；其二是采用模板缓冲。</p>
<p>本文说明采用多重纹理方案时如何实现溶解效果，采用模板缓冲的方案容后再补。</p>
<p><a href="http://www.windameister.org/blog/wp-content/uploads/2010/09/DissolveMiddle.jpg"><img style="display: inline; border-width: 0px;" title="Dissolve-Middle" src="http://www.windameister.org/blog/wp-content/uploads/2010/09/DissolveMiddle_thumb.jpg" border="0" alt="Dissolve-Middle" width="244" height="223" /></a></p>
<p>上图是一个利用PixelShader达成的效果，其原理是，当我们把一个面片绘制到渲染表面上时，将特定像素的Alpha值设为0，从而在绘制到表面与背景混合时，达到镂空的效果。镂空的图案由美术指定一张Mask纹理确定。譬如在上图的效果中，我利用了下面这样的两张纹理图（左图是前景贴图，右图作为Mask），美术可以通过指定不同的Mask图，实现不同类型的逐渐消解/生成的效果：</p>
<p><a href="http://www.windameister.org/blog/wp-content/uploads/2010/09/06_11_2_moon.jpg"><img style="display: inline; border-width: 0px;" title="06_11_2_moon" src="http://www.windameister.org/blog/wp-content/uploads/2010/09/06_11_2_moon_thumb.jpg" border="0" alt="06_11_2_moon" width="231" height="244" /></a> <a href="http://www.windameister.org/blog/wp-content/uploads/2010/09/dissolve.jpg"><img style="display: inline; border-width: 0px;" title="dissolve" src="http://www.windameister.org/blog/wp-content/uploads/2010/09/dissolve_thumb.jpg" border="0" alt="dissolve" width="197" height="244" /></a></p>
<p><em>实现此效果需要两张贴图，一张是物体表面的纹理，另一张作为溶解的Mask图。</em></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><span id="more-220"></span></p>
<p>PixelShader(ps_2_0)代码如下：</p>
<p>float g_DissolveThreshold;    // 该参数作为阈值，与mask图上的当前像素比较大小以决定像素是否镂空</p>
<p>float4 PS_Main_2_0( VS_OUTPUT Input ) : COLOR0<br />
{<br />
float4 DiffuseColor = tex2D( diffuse_sampler, Input.TexCoord );<br />
float4 TexDissolve = tex2D( mask_sampler, Input.TexCoord );<br />
DiffuseColor.rgb = DiffuseColor.rgb * Input.Diffuse.rgb;<br />
float originalalpha = Input.Diffuse.a * DiffuseColor.a;<br />
float dissolveDelta = TexDissolve.r &#8211; g_DissolveThreshold;<br />
if (dissolveDelta &gt;= 0)<br />
DiffuseColor.a = originalalpha;<br />
else<br />
DiffuseColor.a = 0;<br />
return DiffuseColor;<br />
};</p>
<p>该函数的原理是这样的：我们在光栅化三角形的时候，通过pixelshader给当前像素着色，首先分别取得两张纹理当前颜色（通过前一阶段VS输出的uv坐标Input.TexCoord，以及Sampler进行采样，<a href="http://msdn.microsoft.com/en-us/library/bb509644(VS.85).aspx">这里的diffuse_sampler, mask_sampler是此前定义的分别针对两张纹理的Sampler</a>他们指定了针对哪个纹理采样，以及如何采样），对Mask纹理，我们只取r通道的值作为判断依据(<strong>TexDissolve.r</strong>)——通常情况下作为Mask纹理的图可以采用8bit单通道图以节省空间，采用r通道是为了简便，实际上对于灰度图而言，无论采用哪个通道都是相同的。如果传入常量g_DissolveThreshold大于TexDissolve.r，则输出像素的Alpha被置为0。</p>
<p>事实上，上述溶解效果只考虑以Alpha作为因子的Alpha混合（AlphaBlending以D3DBLEND_SRCALPHA作为混合的因子的情况），因此输出像素的Alpha被置为0即可。如果需要考虑其他的混合模式，例如D3DBLEND_SRCCOLOR，则需将输出像素整体置为0。这里涉及到Alpha混合相关的知识，我会另外撰文总结。</p>
<p>其中g_DissolveThreshold是一个Pixel Shader常量，程序逻辑中可以控制该常量的变化，而后将值传入ps，以达到不同程度的溶解。改变该变量的行为方式，则可以实现各种不同的溶解/生成效果。</p>
<p>p.s.1使用上述逻辑实现溶解效果时，可以根据g_DissolveThreshold与Mask像素差的大小，增加一些判定从而实现边缘描边，边缘淡出等效果。</p>
<p>p.s.2 如果想针对Mesh实现该效果，其原理与面片的类似，可以利用相似的办法（Mask贴图以及针对模型的PixelShader），实现模型或物体在背景中溶解消失的效果</p>
<p>p.s.3 本文中所用ps如果在dx8.1版本或之前，需要采用ps1.4，则应当利用cmp指令解决alpha值的运算问题：</p>
<p><em>; ps1.4</em></p>
<p><em>; c0 is g_DissolveThreshold</em></p>
<p><em>; c1 is (0, 0, 0, 0)</em></p>
<p><em>texld r0, t0 ; diffuse texture</em></p>
<p><em>texld r1, t1 ; mask texture</em></p>
<p><em>mul r0, r0, v0 ; diffuse texture color * vertex diffuse color</em></p>
<p><em>add r2, r1, –c0 ; sub to get the dissolve delta</em></p>
<p><em>cmp r0.a r2, r0, c1 ; cmp using r2’s value r2.a &gt;= 0? use what is there (r0.a) , otherwise use 0</em></p>
<p>#1 增加了ps1.4版本的ps。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.windameister.org/blog/2010/09/11/dissolve-effect-implement-in-dx9/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>有趣的进化心理学</title>
		<link>http://www.windameister.org/blog/2010/05/30/interesting-evolutionary-psychology/</link>
		<comments>http://www.windameister.org/blog/2010/05/30/interesting-evolutionary-psychology/#comments</comments>
		<pubDate>Sun, 30 May 2010 11:00:27 +0000</pubDate>
		<dc:creator>windam</dc:creator>
				<category><![CDATA[心理与思维]]></category>
		<category><![CDATA[心理学]]></category>
		<category><![CDATA[进化心理学]]></category>

		<guid isPermaLink="false">http://www.windameister.org/blog/2010/05/30/%e6%9c%89%e8%b6%a3%e7%9a%84%e8%bf%9b%e5%8c%96%e5%bf%83%e7%90%86%e5%ad%a6/</guid>
		<description><![CDATA[最近在读《进化心理学》，进化心理学是心理学中非常有意思的一个领域，近些年来发展非常快，成果也非常多。通过进化的观点来为人类及其他物种的许多行为和心理偏好寻找解释，是一个非常有趣的解读方式。 通俗的说，所有的物种包括人类的很大一部分行为和心理机制都是在自然选择的过程中被逐步筛选出来的——筛选的原则是这种行为或心理机制是否曾经为该物种的生存或者繁殖提供了某种好处，从而使得持有该基因的个体更容易获得选择优势，并且在繁殖的过程中将这种行为/心理机制的基因传递给了后代。这样，最终延续下来的个体大都保留了该行为方式/心理机制。 简而言之，进化心理学的核心观点可以简述为：自然选择的压力持续作用，种群中的部分个体有某种可由基因操控的行为或者心理机制使其在自然选择的压力下拥有生存或繁殖上的优势，繁殖行为将该基因及其决定的行为、心理机制和可能存在的副产品传递给后代。 人类对蛇有天生的恐惧感，这源于我们祖先在进化过程中生命不断受到蛇的威胁，获得了对蛇的恐惧感的人更好的适应了有蛇存在的环境，从而有更高的几率活下来。但是由于进化机制只能在非常大的时间尺度上起作用——因此现代社会的汽车出现才刚刚100多年的时间，虽然每年丧生于车轮下的人数远远多于丧生于蛇的攻击的人数，但是在短短的几代人的时间里，人类还未能对汽车还未能建立起相应的心理机制。（事实上，如果人类社会的秩序和法律持续发展，遵守秩序和法规可以带来安全性和生存优势的话，也许未来社会的中人类会进化出遵守秩序和法规的心理机制） 人类对食物的偏好中也有进化的痕迹存在：人类喜欢香料很可能是因为香料能够杀死食物中的微生物或者抑制微生物生长，从而阻止毒素生成。人类喜欢喝酒则很可能是由于灵长类动物在历史上有2400万年的食用水果的历史——水果散发出来的“乙醇香味”是其成熟的极佳线索，灵长类生物在食用水果的同时，也获得了对酒精的偏好——这种偏好未必是一种有利于生存的适应行为，而很可能是灵长类祖先过度沉溺于水果类食物而带来的适应不良（进化的副产品）。孕妇在怀孕的头三个月中，通常会有妊娠反应——对某些特定食物高度敏感，并伴有呕吐反应，心理学家找到了强大的证据证明妊娠反应是一种适应机制，它能够阻止孕妇摄入或吸收不利于胎儿发育的有害毒素。 人类对美的一般标准的判断是近似的，审美标准是在百万年的进化中逐渐形成的，许多美的特征——丰满的嘴唇，光洁的皮肤，亮泽的头发，对称的面孔，恰到好处的肌肉和匀称的体型等外貌特征往往说明了身体健康，没有寄生虫或毒素，高的生育力和高繁殖价值。这些外貌上的线索提供了女性繁殖价值的最有效的标准，远古男性从而得以进化出对拥有这些线索的女性的偏好。相反，如果对这些高生育力和高繁殖价值的品质不加重视，反而偏好皮松肉弛，头发灰白的女性，那么该类男子很可能繁殖不出什么后代，其家族血脉也终将灭亡。 心理学家Judith Langlois和她的同事们关于婴儿对面孔的反应的研究为前述观点提供了证据： 先呈现白人女性或黑人女性面孔的彩色立体图像，让成人评估她们的性魅力。然后给2-3个月以及6-8个月大的婴儿呈现这些性魅力程度不同的面孔。不管是较小的婴儿还是较大的婴儿都对更有魅力的面孔注视得更久，这表明美的标准显然在生命早期就已经出现了。该证据对一般认为性魅力标准是在当前文化模式中逐渐形成的观点提出了挑战。 美的标准存在跨文化性，心理学家要求不同种族的人评价亚洲，西班牙，黑人以及白人女性的照片中有吸引力的面孔时，发现在评判一个人是否好看时存在着惊人的一致性。 最新的技术也为进化心理学提供了新证据，心理学家给异性恋男性被试提供四组吸引力不同的面孔——性魅力事先评判为：迷人的女性、普通女性、迷人的男性和普通男性，当使用核磁共振技术扫描男性的大脑时，发现男性观看迷人的女性时，大脑的伏隔核区域(nucleus accumbens area)显得非常活跃——众所周知，该区域是一个基本的奖赏回路，已被证明是脑部的快乐中枢。而男性观看普通女性和任何男性面孔的时候，该区域都不会被激活。简而言之，男性在注视迷人的女性面孔时会获得大脑中的奖励，这可以解释为什么大多男生都那么爱看美女: ) 人类在择偶和抚育后代的行为也有进化的痕迹。比如，我们知道人类在99%的进化历史中都过着狩猎-采集的生活，所以可以预测女性进化而来的择偶偏好中，应该包含成功狩猎所必需的特定品质，比如运动能力，良好的手眼协调能力，长途狩猎所需的耐力，显示力量的高大身材等等。 而亲代投资理论认为两性中对后代投入更多资源的一方（通常是雌性，但是摩门蟋蟀、巴拿马箭毒蛙和海岸尖嘴鱼中雄性对繁殖的投资则远远高于雌性），在选择配偶时会表现得更加敏锐和谨慎。相反，投资较少的一方则不那么挑剔，但他们会表现出很强的同性竞争倾向，主要是为了争夺更有价值的异性。 该理论预测女性比较看重男性身上与获得资源相关的特定品质，比如社会地位，智力，较大的年龄——女性进化了对资源富足型男性的特定偏好。（从这个意义上，现代社会舆论虽然批评拜金的女性，但事实上这种心理有根深蒂固的繁殖意义——一个拥有更多资源的配偶往往意味着子女可以有更高的存活率和更大的成长空间，从而有益于该心理机制的基因的得以存续）处于支配地位的男性更受女性青睐，在一夫多妻制的社会背景中，一个女人宁可与其他女性共侍一夫（当然是地位高资源多的男性），也不愿意和一个地位低资源少的男性结婚。尽管相比前者女性只能获得配偶的部分资源，而后者可以获得全部资源，但是即便如此，女性还是更愿意选择前者。造成的结果是，拥有较少资源的男性远远无法与拥有较多资源的男性进行竞争，最终往往是少数拥有高社会地位和大量资源的人（皇帝，官员，富商等）拥有数量繁多的配偶，而大量处于社会底层的男性则无法拥有配偶。从这个意义上讲，一夫一妻制的社会为大量原来无法找到配偶的男性提供了更多择偶机会，其对女性的好处或许远没有对男性的好处大。 总而言之，进化心理学提供了这样一种观点：人类的许多共通的行为方式和心理机制都是为了解决生存和繁殖过程中遇到的适应性问题。通过这样一种观点来审视自身的行为和心理机制，其实挺有趣味的。: )]]></description>
			<content:encoded><![CDATA[<p>最近在读《<a href="http://book.douban.com/subject/2143904/">进化心理学</a>》，进化心理学是心理学中非常有意思的一个领域，近些年来发展非常快，成果也非常多。通过进化的观点来为人类及其他物种的许多行为和心理偏好寻找解释，是一个非常有趣的解读方式。</p>
<p>通俗的说，所有的物种包括人类的很大一部分行为和心理机制都是在自然选择的过程中被逐步筛选出来的——筛选的原则是这种行为或心理机制是否曾经为该物种的生存或者繁殖提供了某种好处，从而使得持有该基因的个体更容易获得选择优势，并且在繁殖的过程中将这种行为/心理机制的基因传递给了后代。这样，最终延续下来的个体大都保留了该行为方式/心理机制。</p>
<p>简而言之，进化心理学的核心观点可以简述为：<strong>自然选择的压力持续作用，种群中的部分个体有某种可由基因操控的行为或者心理机制使其在自然选择的压力下拥有生存或繁殖上的优势，繁殖行为将该基因及其决定的行为、心理机制和可能存在的副产品传递给后代</strong>。</p>
<p>人类对蛇有天生的恐惧感，这源于我们祖先在进化过程中生命不断受到蛇的威胁，获得了对蛇的恐惧感的人更好的适应了有蛇存在的环境，从而有更高的几率活下来。但是由于进化机制只能在非常大的时间尺度上起作用——因此现代社会的汽车出现才刚刚100多年的时间，虽然每年丧生于车轮下的人数远远多于丧生于蛇的攻击的人数，但是在短短的几代人的时间里，人类还未能对汽车还未能建立起相应的心理机制。（事实上，如果人类社会的秩序和法律持续发展，遵守秩序和法规可以带来安全性和生存优势的话，也许未来社会的中人类会进化出遵守秩序和法规的心理机制）</p>
<p><span id="more-209"></span></p>
<p>人类对食物的偏好中也有进化的痕迹存在：人类喜欢香料很可能是因为香料能够杀死食物中的微生物或者抑制微生物生长，从而阻止毒素生成。人类喜欢喝酒则很可能是由于灵长类动物在历史上有2400万年的食用水果的历史——水果散发出来的“乙醇香味”是其成熟的极佳线索，灵长类生物在食用水果的同时，也获得了对酒精的偏好——这种偏好未必是一种有利于生存的适应行为，而很可能是灵长类祖先过度沉溺于水果类食物而带来的适应不良（进化的副产品）。孕妇在怀孕的头三个月中，通常会有妊娠反应——对某些特定食物高度敏感，并伴有呕吐反应，心理学家找到了强大的证据证明妊娠反应是一种适应机制，它能够阻止孕妇摄入或吸收不利于胎儿发育的有害毒素。</p>
<p>人类对美的一般标准的判断是近似的，审美标准是在百万年的进化中逐渐形成的，许多美的特征——丰满的嘴唇，光洁的皮肤，亮泽的头发，对称的面孔，恰到好处的肌肉和匀称的体型等外貌特征往往说明了身体健康，没有寄生虫或毒素，高的生育力和高繁殖价值。这些外貌上的线索提供了女性繁殖价值的最有效的标准，远古男性从而得以进化出对拥有这些线索的女性的偏好。相反，如果对这些高生育力和高繁殖价值的品质不加重视，反而偏好皮松肉弛，头发灰白的女性，那么该类男子很可能繁殖不出什么后代，其家族血脉也终将灭亡。</p>
<p>心理学家Judith Langlois和她的同事们关于婴儿对面孔的反应的研究为前述观点提供了证据：</p>
<p>先呈现白人女性或黑人女性面孔的彩色立体图像，让成人评估她们的性魅力。然后给2-3个月以及6-8个月大的婴儿呈现这些性魅力程度不同的面孔。不管是较小的婴儿还是较大的婴儿都对更有魅力的面孔注视得更久，这表明美的标准显然在生命早期就已经出现了。该证据对一般认为性魅力标准是在当前文化模式中逐渐形成的观点提出了挑战。</p>
<p>美的标准存在跨文化性，心理学家要求不同种族的人评价亚洲，西班牙，黑人以及白人女性的照片中有吸引力的面孔时，发现在评判一个人是否好看时存在着惊人的一致性。</p>
<p>最新的技术也为进化心理学提供了新证据，心理学家给异性恋男性被试提供四组吸引力不同的面孔——性魅力事先评判为：迷人的女性、普通女性、迷人的男性和普通男性，当使用核磁共振技术扫描男性的大脑时，发现男性观看迷人的女性时，大脑的伏隔核区域(<a href="http://en.wikipedia.org/wiki/Nucleus_accumbens">nucleus accumbens area</a>)显得非常活跃——众所周知，该区域是一个基本的奖赏回路，已被证明是脑部的快乐中枢。而男性观看普通女性和任何男性面孔的时候，该区域都不会被激活。简而言之，男性在注视迷人的女性面孔时会获得大脑中的奖励，这可以解释为什么大多男生都那么爱看美女: )</p>
<p>人类在择偶和抚育后代的行为也有进化的痕迹。比如，我们知道人类在99%的进化历史中都过着狩猎-采集的生活，所以可以预测女性进化而来的择偶偏好中，应该包含成功狩猎所必需的特定品质，比如运动能力，良好的手眼协调能力，长途狩猎所需的耐力，显示力量的高大身材等等。</p>
<p>而亲代投资理论认为两性中对后代投入更多资源的一方（通常是雌性，但是摩门蟋蟀、巴拿马箭毒蛙和海岸尖嘴鱼中雄性对繁殖的投资则远远高于雌性），在选择配偶时会表现得更加敏锐和谨慎。相反，投资较少的一方则不那么挑剔，但他们会表现出很强的同性竞争倾向，主要是为了争夺更有价值的异性。</p>
<p>该理论预测女性比较看重男性身上与获得资源相关的特定品质，比如社会地位，智力，较大的年龄——女性进化了对资源富足型男性的特定偏好。（从这个意义上，现代社会舆论虽然批评拜金的女性，但事实上这种心理有根深蒂固的繁殖意义——一个拥有更多资源的配偶往往意味着子女可以有更高的存活率和更大的成长空间，从而有益于该心理机制的基因的得以存续）处于支配地位的男性更受女性青睐，在一夫多妻制的社会背景中，一个女人宁可与其他女性共侍一夫（当然是地位高资源多的男性），也不愿意和一个地位低资源少的男性结婚。尽管相比前者女性只能获得配偶的部分资源，而后者可以获得全部资源，但是即便如此，女性还是更愿意选择前者。造成的结果是，拥有较少资源的男性远远无法与拥有较多资源的男性进行竞争，最终往往是少数拥有高社会地位和大量资源的人（皇帝，官员，富商等）拥有数量繁多的配偶，而大量处于社会底层的男性则无法拥有配偶。从这个意义上讲，一夫一妻制的社会为大量原来无法找到配偶的男性提供了更多择偶机会，其对女性的好处或许远没有对男性的好处大。</p>
<p>总而言之，进化心理学提供了这样一种观点：人类的许多共通的行为方式和心理机制都是为了解决生存和繁殖过程中遇到的适应性问题。通过这样一种观点来审视自身的行为和心理机制，其实挺有趣味的。: )</p>
]]></content:encoded>
			<wfw:commentRss>http://www.windameister.org/blog/2010/05/30/interesting-evolutionary-psychology/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

