﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>IT博客-魔のkyo的工作室-随笔分类-Graphics</title><link>http://www.cnitblog.com/luckydmz/category/8857.html</link><description /><language>zh-cn</language><lastBuildDate>Thu, 02 Feb 2012 22:02:00 GMT</lastBuildDate><pubDate>Thu, 02 Feb 2012 22:02:00 GMT</pubDate><ttl>60</ttl><item><title>平行分割阴影图(PSSMs)的研究</title><link>http://www.cnitblog.com/luckydmz/archive/2012/01/02/76993.html</link><dc:creator>魔のkyo</dc:creator><author>魔のkyo</author><pubDate>Mon, 02 Jan 2012 13:34:00 GMT</pubDate><guid>http://www.cnitblog.com/luckydmz/archive/2012/01/02/76993.html</guid><wfw:comment>http://www.cnitblog.com/luckydmz/comments/76993.html</wfw:comment><comments>http://www.cnitblog.com/luckydmz/archive/2012/01/02/76993.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/luckydmz/comments/commentRss/76993.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/luckydmz/services/trackbacks/76993.html</trackback:ping><description><![CDATA[<p>一．简介  <p>目前的阴影算法大致可以分为以下三类：基于ray tracing，基于shadow volume，基于shadow map。shadow map因其易于实现，算法复杂度与场景复杂度无关等优点被广泛应用，但是shadow map有alias（走样、锯齿）的问题。为解决这个问题，产生了诸多对标准shadow map的改进办法。  <p>Shadow map的alias问题可以分为两类：Perspective alias和Project alias。Project alias是因为当光照方向与物体表面夹角比较小时，使得多个pixel对应Shadow map中一个texel，产生alias问题，此类alias目前是个困难问题，针对这类alias的研究有Resolution-Matched SMs (Lefohn et al. 2007), Irregular Z-Buffer (Johnson et al.2005)，但都需要承受很大性能损失。Perspective alias产生的原因是透视投影会产生近大远小的效果，这使得近处物体的多个pixel可能对应着Shadow map中一个texel, 产生alias问题。相比其他流行的shadow map技术，如perspective shadow maps (PSMs) (Stamminger and Drettakis 2002), light-space perspective shadow maps (LiSPSMs) (Wimmer et al. 2004) and trapezoidal shadow maps (TSMs) (Martin and Tan 2004)，本文介绍的Parallel-Split Shadow Maps (PSSMs) (Zhang et al. 2007 and Zhang et al. 2006) 思想相对直观，实现也比较简单，并且不需要根据不同类型的关于和特殊的位置对映射进行任何特化处理，而且可以和其他的shadow map算法结合使用。  <p>PSSMs的基本思路就是把摄像机的视域体按照Z的范围平行分割成多个部份，每个部分称为一个子视域体，分别为对每个子视域体生成Shaodw Map，在渲染场景时根据Z值选择最适合的Shaodw Map进行采样。  <p>二．算法  <p><a href="http://www.cnitblog.com/images/cnitblog_com/luckydmz/clip_image002_2955D3CC.gif"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="clip_image002" border="0" alt="clip_image002" src="http://www.cnitblog.com/images/cnitblog_com/luckydmz/clip_image002_thumb_4904AD94.gif" width="553" height="340"></a>  <p>PSSMs算法的主要步骤如下：  <p>1) 将摄像机视域体按深度范围划分成多个子视域体{V1,V2,...,Vm}。  <p>2) 根据各相机子视域体分别计算各光源子视域体{W1,W2,...,Wm}。  <p>3) 生成阴影图，将Wi包含的阴影投射对象渲染到阴影图Ti中。  <p>4) 综合场景阴影效果，Vi中的像素采样Ti进行阴影判断。  <p>Step1. 分割摄像机视域体  <p><a href="http://www.cnitblog.com/images/cnitblog_com/luckydmz/clip_image004_4FB7B717.gif"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="clip_image004" border="0" alt="clip_image004" src="http://www.cnitblog.com/images/cnitblog_com/luckydmz/clip_image004_thumb_6F6690DF.gif" width="500" height="308"></a>  <p>如何分割视域体对PSSMs算法生成的阴影质量有着重要影响，若将视域体划分为m层，则摄像机空间下场景的划分平面深度值Ci可以通过  <p><i><a href="http://www.cnitblog.com/images/cnitblog_com/luckydmz/clip_image006_6AF01018.gif"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="clip_image006" border="0" alt="clip_image006" src="http://www.cnitblog.com/images/cnitblog_com/luckydmz/clip_image006_thumb_11BE2659.gif" width="313" height="133"></a></i><b></b>  <p>求得，其中f,n分别表示V的远近裁剪面，<a href="http://www.cnitblog.com/images/cnitblog_com/luckydmz/clip_image008_66798F51.gif"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="clip_image008" border="0" alt="clip_image008" src="http://www.cnitblog.com/images/cnitblog_com/luckydmz/clip_image008_thumb_13FAAF15.gif" width="36" height="27"></a>和<a href="http://www.cnitblog.com/images/cnitblog_com/luckydmz/clip_image010_417BCED8.gif"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="clip_image010" border="0" alt="clip_image010" src="http://www.cnitblog.com/images/cnitblog_com/luckydmz/clip_image010_thumb_1CEA4154.gif" width="36" height="25"></a>代表2种划分策略，参数<a href="http://www.cnitblog.com/images/cnitblog_com/luckydmz/clip_image012_1C7E0E5F.gif"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="clip_image012" border="0" alt="clip_image012" src="http://www.cnitblog.com/images/cnitblog_com/luckydmz/clip_image012_thumb_49FF2E22.gif" width="16" height="20"></a>用以调节他们的比例，分层策略的详细解释见参考文献[1]。  <p>Step2. 计算光源视域体及其变换矩阵  <p><a href="http://www.cnitblog.com/images/cnitblog_com/luckydmz/clip_image014_77804DE5.gif"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="clip_image014" border="0" alt="clip_image014" src="http://www.cnitblog.com/images/cnitblog_com/luckydmz/clip_image014_thumb_451C7A66.gif" width="400" height="324"></a>  <p>在计算子视域体Wi的view-projection矩阵之前，我们必须找到分割视域体Vi在光空间下的轴对齐包围盒。然后，我们引入cropMatrix的概念，cropMatrix是一个在投影空间内对规则观察体进行缩放和平移的矩阵，最终我们在以光源为视点进行阴影图Ti的渲染时使用的view-projection变换矩阵是  <p>lightViewMatrix * lightProjMatrix * cropMatrix  <p>当lightProjMatrix为正交投影时(对于平行光)，cropMatrix的作用相当于在世界空间内平移和缩放以光源为视点的视域体；  <p>当lightProjMatrix为透视投影时(对于点光源)，cropMatrix的作用相当于在世界空间内绕光源旋转和缩放以光源为视点的视域体。  <p>这正好符合平行光和点光源的特性，所以cropMatrix在平行光和点光源下都是正确的。  <p>我们使用如下函数来计算cropMatrix，  <p>Matrix4f CalcCropMatrix( Frustum splitFrustum, Matrix4f matViewProj);  <p>其中，  <p>参数 splitFrustum 是某个子视域体Vi。  <p>参数 matViewProj 是一个未经修剪的view-projection矩阵(即视矩阵和投影矩阵的复合)  <p>函数返回一个cropMatrix使得 matViewProj * cropMatrix所对应的规则观察体恰可以包住splitFrustum的轴对齐包围盒（AABB）。  <p>函数实现如下:  <table border="1" cellspacing="0" cellpadding="0"> <tbody> <tr> <td valign="top" width="568"><pre class="csharpcode">Matrix4f CalcCropMatrix( Frustum splitFrustum, Matrix4f matViewProj)
{
    <span class="rem">// 计算视域体splitFrustum在matViewProj空间下的轴对齐包围盒</span>
    BoundingBox cropBB = CalcAABB(splitFrustum, matViewProj); 

    <span class="rem">// 使用默认的近裁剪面</span>
    cropBB.min.z = 0.0f; 

    <span class="rem">// 计算缩放和偏移值</span>
    <span class="kwrd">float</span> scaleX, scaleY, scaleZ;
    <span class="kwrd">float</span> offsetX, offsetY, offsetZ;
    scaleX = 2.0f / (cropBB.max.x - cropBB.min.x);
    scaleY = 2.0f / (cropBB.max.y - cropBB.min.y);
    offsetX = -0.5f * (cropBB.max.x + cropBB.min.x) * scaleX;
    offsetY = -0.5f * (cropBB.max.y + cropBB.min.y) * scaleY;
    scaleZ = 1.0f / (cropBB.max.z - cropBB.min.z);
    offsetZ = -cropBB.min.z * scaleZ; 

    <span class="kwrd">return</span> Matrix4f(scaleX,  0.0f,     0.0f,  0.0f,
        0.0f,   scaleY,     0.0f,  0.0f,
        0.0f,     0.0f,   scaleZ,  0.0f,
        offsetX,  offsetY,  offsetZ,  1.0f);
}
</pre>
<style type="text/css">.csharpcode, .csharpcode pre
{
	font-size: small;
	color: black;
	font-family: consolas, "Courier New", courier, monospace;
	background-color: #ffffff;
	/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt 
{
	background-color: #f4f4f4;
	width: 100%;
	margin: 0em;
}
.csharpcode .lnum { color: #606060; }
</style>
</td></tr></tbody></table>
<p>这里有几点需要说明的，cropBB.min.z = 0.0f表示使用默认的近裁剪面，即生成的cropMatrix不对参数matViewProj所定义的近裁剪面进行调整，如果使用由包围盒计算出的近裁剪面会导致从由matViewProj所定义的视点到包围盒之间的阴影投射体不会被渲染到阴影图上，那么其所产生的阴影就不会出现在包围盒内的物体上，如图2-3所示，Caster的阴影应该出现在Vi内的物体上，但Caster不在Vi所对应的AABB内。 
<p>还有一点需要注意的是应该保证splitFrustum 不会出现在matViewProj所对应的投影空间的Z轴的副半轴，否则生成cropMatrix将不正确。 
<p>Step3. 生成分段阴影图 
<p>在阴影图生成方面，在DX10上是可以优化到渲染一次的。而在DX9上如果将视域体分成N段的话需要渲染N次来生成N张不同子视域体的阴影图。如下图2-4： 
<p><a href="http://www.cnitblog.com/images/cnitblog_com/luckydmz/clip_image016_12B8A6E7.gif"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="clip_image016" border="0" alt="clip_image016" src="http://www.cnitblog.com/images/cnitblog_com/luckydmz/clip_image016_thumb_0722E9A8.gif" width="553" height="552"></a> 
<p>在DX9上使用GPU加速的渲染流程大致如下： 
<table border="1" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td valign="top" width="568"><pre class="csharpcode">Matrix4f lightViewProjMatrix[numSplits];
<span class="rem">//生成光源深度图</span>
<span class="kwrd">for</span>(<span class="kwrd">int</span> i=0;i&lt;numSplits;i++)
{ 
    <span class="rem">//设置阴影图为RenderTarget</span>
    GetRenderDevice()-&gt;BeginRenderToTargets(m_pTexShadowMap[i]));
    GetRenderDevice()-&gt;Clear(); 

    <span class="rem">//计算子视域体</span>
    splitFrustum = camera-&gt;CalculateFrustum(splitPos[i], splitPos[i+1]);
    <span class="rem">//计算cropMatrix</span>
    cropMatrix = CalcCropMatrix(splitFrustum, lightViewMatrix * lightProjMatrix); 

    lightViewProjMatrix[i] = lightViewMatrix * lightProjMatrix * cropMatrix;
    <span class="rem">//以lightViewMatrix * lightProjMatrix * cropMatrix为ViewProj变换渲染阴影投射体</span>
    RenderCasters(casters, lightViewProjMatrix[i]); 

    GetRenderDevice()-&gt;EndRenderToTargets();
}
</pre>
<style type="text/css">.csharpcode, .csharpcode pre
{
	font-size: small;
	color: black;
	font-family: consolas, "Courier New", courier, monospace;
	background-color: #ffffff;
	/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt 
{
	background-color: #f4f4f4;
	width: 100%;
	margin: 0em;
}
.csharpcode .lnum { color: #606060; }
</style>
</td></tr></tbody></table>
<p>Step4. 合成场景阴影效果 
<table border="1" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td valign="top" width="568"><pre class="csharpcode"><span class="kwrd">for</span>(<span class="kwrd">int</span> i=0;i&lt;numSplits;i++)
{
    <span class="rem">//设置各个子视域体的光源ViewProj矩阵</span>
    GetRenderDevice()-&gt;ShaderSetConstant(ShaderStage::VERTEX_SHADER_STAGE, <br>        LIGHT_VPMATRIX_REGISTER+i*4, &amp;lightViewProjMatrix[i]);<br>
    <span class="rem">//设置各个子视域体生成的阴影图</span>
    GetRenderDevice()-&gt;ShaderSetConstant(ShaderStage::PIXEL_SHADER_STAGE, <br>        SHADOWMAP_REGISTER+i, m_pTexShadowMap[i]);
}
<span class="rem">//以摄像机的ViewProj变换渲染阴影接收体</span>
RenderReivers(receivers, camera-&gt;viewMatrix * camera-&gt;projMatrix); 
</pre>
<style type="text/css">.csharpcode, .csharpcode pre
{
	font-size: small;
	color: black;
	font-family: consolas, "Courier New", courier, monospace;
	background-color: #ffffff;
	/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt 
{
	background-color: #f4f4f4;
	width: 100%;
	margin: 0em;
}
.csharpcode .lnum { color: #606060; }
</style>
</td></tr></tbody></table>
<p>Vertex and Pixel Shaders for Synthesizing Shadows in DirectX 9 
<table border="1" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td valign="top" width="568"><pre class="csharpcode"><span class="kwrd">struct</span> VS_IN
{
    float3 ObjPos    : POSITION;
    float2 ObjUV     : TEXCOORD2;
}; 

<span class="kwrd">struct</span> VS_OUT
{
    float4 ProjPos   : POSITION;
    float2 ObjUV     : TEXCOORD2;
    <span class="kwrd">float</span>  viewZ     : TEXCOORD3;
    float4 texCoordProj[3] : TEXCOORD4;
}; 

float4x4 matWorld : register(c0);
float4x4 matView : register(c4);
float4x4 matProj : register(c8);
float4x4 matLightVP[3] : register(c12); 

VS_OUT main( VS_IN In )
{
    float4 wPosition = mul(float4(In.ObjPos.xyz, 1.0f), matWorld);
    float4 vPosition = mul(wPosition, matView);
    VS_OUT Out;
    Out.ProjPos = mul( vPosition, matProj );
    Out.ObjUV = In.ObjUV; 

    Out.viewZ = vPosition.z;
    float4x4 matLightViewport = float4x4(
        0.5f,  0.0f,  0.0f,  0.0f,
        0.0f, -0.5f,  0.0f,  0.0f,
        0.0f,  0.0f,  1.0f,  0.0f,
        0.5f + 0.5f / 512.0f,  0.5f + 0.5f / 512.0f,  0,  1.0f
        );
    <span class="kwrd">for</span>(<span class="kwrd">int</span> i=0;i&lt;3;i++)
    {
        Out.texCoordProj[i] = mul( wPosition, matLightVP[i] );
        Out.texCoordProj[i] = mul( Out.texCoordProj[i], matLightViewport);
    } 

    <span class="kwrd">return</span> Out;
}
</pre>
<style type="text/css">.csharpcode, .csharpcode pre
{
	font-size: small;
	color: black;
	font-family: consolas, "Courier New", courier, monospace;
	background-color: #ffffff;
	/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt 
{
	background-color: #f4f4f4;
	width: 100%;
	margin: 0em;
}
.csharpcode .lnum { color: #606060; }
</style>
</td></tr></tbody></table>
<table border="1" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td valign="top" width="568"><pre class="csharpcode"><span class="kwrd">struct</span> PS_IN
{
    float2 ObjUV     : TEXCOORD2;
    <span class="kwrd">float</span>  viewZ     : TEXCOORD3;
    float4 texCoordProj[3] : TEXCOORD4;
}; 

sampler2D g_Tex : register(s0);
sampler2D g_tShadowMap[3] : register(s1); 

float4 main( PS_IN In ) : COLOR
{
    <span class="kwrd">float</span> lightIntensity = 1.0; 

    <span class="kwrd">for</span>(<span class="kwrd">int</span> i=0;i&lt;3;i++)
    {
        <span class="kwrd">if</span>(In.viewZ &lt; splitPlane[i])
        {
            lightIntensity = tex2Dproj(g_tShadowMap[i], In.texCoordProj[i]).x;
        }
    } 

    float4 COLOR_IN_SHADOW, COLOR_IN_LIGHT;
    COLOR_IN_SHADOW.rgba = float4(0.2,0.2,0.2,1.0);
    COLOR_IN_LIGHT.rgba = float4(1.0,1.0,1.0,1.0); 

    float4 tex = tex2D(g_Tex, In.ObjUV);
    tex *= lerp(COLOR_IN_SHADOW, COLOR_IN_LIGHT, lightIntensity);
    <span class="kwrd">return</span> tex;
}
</pre>
<style type="text/css">.csharpcode, .csharpcode pre
{
	font-size: small;
	color: black;
	font-family: consolas, "Courier New", courier, monospace;
	background-color: #ffffff;
	/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt 
{
	background-color: #f4f4f4;
	width: 100%;
	margin: 0em;
}
.csharpcode .lnum { color: #606060; }
</style>
</td></tr></tbody></table>
<p>&nbsp; <p>三．可能的优化 
<p>·结合使用Variance Shadow Maps和Gaussian Blur可进一步软化阴影边缘 
<p>·正反Culling渲染2次取平均深度可以帮助解决Shadow Acne &amp; Peter Panning 
<p>·将阴影图纹理打包，使用TextureArray或者拼到一张2D Texture上，可以减少PS的分支<br>参考文献： 
<p>[1] Zhang F, Sun H Q, Xu L L, et al. Hardware-accelerated parallel-split shadow maps[J]. International Journal of Image and Graphics, 2008.8(2): 223-241 
<p>[2] Zhang F, Sun H Q, Nyman O. Parallel-Split Shadow Maps on Programmable GPUs. GPU Gems 3 - Chapter 10, 2007: 203-238 
<img src ="http://www.cnitblog.com/luckydmz/aggbug/76993.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/luckydmz/" target="_blank">魔のkyo</a> 2012-01-02 21:34 <a href="http://www.cnitblog.com/luckydmz/archive/2012/01/02/76993.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>估计纹理像素的缩放倍数</title><link>http://www.cnitblog.com/luckydmz/archive/2011/08/25/75191.html</link><dc:creator>魔のkyo</dc:creator><author>魔のkyo</author><pubDate>Thu, 25 Aug 2011 13:30:00 GMT</pubDate><guid>http://www.cnitblog.com/luckydmz/archive/2011/08/25/75191.html</guid><wfw:comment>http://www.cnitblog.com/luckydmz/comments/75191.html</wfw:comment><comments>http://www.cnitblog.com/luckydmz/archive/2011/08/25/75191.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/luckydmz/comments/commentRss/75191.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/luckydmz/services/trackbacks/75191.html</trackback:ping><description><![CDATA[<div style="background-color: #eeeeee; font-size: 13px; border: 1px solid #cccccc; padding: 4px 5px 4px 4px; width: 98%;"><!--<br />
<br />
Code highlighting produced by Actipro CodeHighlighter (freeware)<br />
http://www.CodeHighlighter.com/<br />
<br />
--><span style="color: #000000; ">&nbsp;&nbsp;&nbsp;&nbsp;float2&nbsp;uv&nbsp;</span><span style="color: #000000; ">=</span><span style="color: #000000; ">&nbsp;texCoordProj.xy</span><span style="color: #000000; ">/</span><span style="color: #000000; ">texCoordProj.w;<br />
&nbsp;&nbsp;&nbsp;&nbsp;float2&nbsp;uvScaled&nbsp;</span><span style="color: #000000; ">=</span><span style="color: #000000; ">&nbsp;uv</span><span style="color: #000000; ">*</span><span style="color: #000000; ">wh;<br />
&nbsp;&nbsp;&nbsp;&nbsp;float2&nbsp;ef</span><span style="color: #000000; ">=</span><span style="color: #000000; ">ddx(uvScaled);<br />
&nbsp;&nbsp;&nbsp;&nbsp;float2&nbsp;gh</span><span style="color: #000000; ">=</span><span style="color: #000000; ">ddy(uvScaled);<br />
&nbsp;&nbsp;&nbsp;&nbsp;half&nbsp;sInv&nbsp;</span><span style="color: #000000; ">=</span><span style="color: #000000; ">&nbsp;length(float4(ef,gh));<br />
&nbsp;&nbsp;&nbsp;&nbsp;half&nbsp;s </span><span style="color: #000000; ">=</span><span style="color: #000000; ">&nbsp;</span><span style="color: #000000; ">1.4</span><span style="color: #000000; ">/</span><span style="color: #000000; ">sInv;&nbsp;</span><span style="color: #008000; ">//</span><span style="color: #008000; ">估计纹理像素的缩放倍数<br />
</span></div>详见《游戏编程精粹6》, P395, 路标渲染清晰化<img src ="http://www.cnitblog.com/luckydmz/aggbug/75191.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/luckydmz/" target="_blank">魔のkyo</a> 2011-08-25 21:30 <a href="http://www.cnitblog.com/luckydmz/archive/2011/08/25/75191.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>根据模型的顶点位置坐标和纹理坐标计算顶点的法线、切线和副法线</title><link>http://www.cnitblog.com/luckydmz/archive/2011/04/21/73393.html</link><dc:creator>魔のkyo</dc:creator><author>魔のkyo</author><pubDate>Thu, 21 Apr 2011 13:25:00 GMT</pubDate><guid>http://www.cnitblog.com/luckydmz/archive/2011/04/21/73393.html</guid><wfw:comment>http://www.cnitblog.com/luckydmz/comments/73393.html</wfw:comment><comments>http://www.cnitblog.com/luckydmz/archive/2011/04/21/73393.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/luckydmz/comments/commentRss/73393.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/luckydmz/services/trackbacks/73393.html</trackback:ping><description><![CDATA[如何根据模型的顶点位置坐标和纹理坐标计算顶点的法线、切线和副法线？<br />我们把顶点数据记作P(x,y,z,u,v)，(x,y,z)是位置坐标，(u,v)纹理坐标<br />三角形的3个顶点就可以表示成<br />P0(x0,y0,z0,u0,v0)<br />P1(x1,y1,z1,u1,v2)<br />P2(x2,y2,z2,u2,v1)<br />因为u,v的变化对x的影响是线性的，则有<br />x = C1 u + C2 v + C3<br />不妨整理一下，写成<br />A0 x + B0 u + C0 v + D0 = 0 (1)<br />同理u,v的变化对y,z的影响是线性的，有<br />A1 y + B1 u + C1 v + D1 = 0 (2)<br />A2 z + B2 u + C2 v + D2 = 0 (3)<br /><br />可以看到 x,u,v 是成平面的，而A0,B0,C0就是平面的法线，可以通过三角形的3个顶点求得<br />(A0,B0,C0) = ((x0,u0,v0)-(x1,u1,v1))&#215;((x0,u0,v0)-(x2,u2,v2))<br />D0 = -(A0,B0,C0)&#183;(x0,s0,t0)<br /><br />同理也可以求得(A1,B1,C1,D1),(A2,B2,C2,D2)<br /><br />通过(1),(2),(3)式联立可以求得<br />d(x,y,z)/du = (-B0/A0,-B1/A1,-B2/A2)<br />d(x,y,z)/dv = (-C0/A0,-C1/A1,-C2/A2)<br /><br />我们就可以取d(x,y,z)/du为切线T，d(x,y,z)/dv为副法线B，法线N = T&#215;B<br /><br />参考：The.Cg.Tutorial.The.Definitive.Guide.to.Programmable.Real-Time.Graphics
8.4.1 Examining a Single Triangle
<br /><a href="http://http.developer.nvidia.com/CgTutorial/cg_tutorial_chapter08.html">http://http.developer.nvidia.com/CgTutorial/cg_tutorial_chapter08.html</a><br /><img src ="http://www.cnitblog.com/luckydmz/aggbug/73393.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/luckydmz/" target="_blank">魔のkyo</a> 2011-04-21 21:25 <a href="http://www.cnitblog.com/luckydmz/archive/2011/04/21/73393.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>为什么texture wrapping不工作？</title><link>http://www.cnitblog.com/luckydmz/archive/2011/04/18/73374.html</link><dc:creator>魔のkyo</dc:creator><author>魔のkyo</author><pubDate>Mon, 18 Apr 2011 15:29:00 GMT</pubDate><guid>http://www.cnitblog.com/luckydmz/archive/2011/04/18/73374.html</guid><wfw:comment>http://www.cnitblog.com/luckydmz/comments/73374.html</wfw:comment><comments>http://www.cnitblog.com/luckydmz/archive/2011/04/18/73374.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.cnitblog.com/luckydmz/comments/commentRss/73374.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/luckydmz/services/trackbacks/73374.html</trackback:ping><description><![CDATA[D3D里面有两个容易混淆的概念，环绕纹理寻址模式(wrap texture address mode)和纹理环绕(texture wrapping)。<br><br>环绕纹理寻址模式，是一种纹理坐标寻址模式，指定了纹理坐标超出[0,1]的部分的处理方法，环绕纹理寻址模式就是简单的重复，如果用代码来描述它相当于对uv做了<br>u = fmod(u,1.0);<br>v = fmod(v,1.0);<br><br>纹理环绕，这个描述起来更麻烦些，它其实是改变了每个顶点的纹理坐标在光栅化时的插值方法。举个例子，如果有一个三角形的两个顶点u值分别等于0.9和0.1，在不启用纹理环绕时，他们之间的插值将在0,9~0.1上进行，而有的时候我们希望它在0.9~1.0连上0.0~0.1上进行，这时就可以通过指定WRAP_U实现。<br><br>环绕纹理寻址模式是默认的寻址模式，而纹理环绕默认是不启用的。<br><br>今天我做了个例子，在例子中球面的纹理需要启用u方向上的纹理环绕，然而我发现它并不工作。<br><img alt="" src="http://www.cnitblog.com/images/cnitblog_com/luckydmz/wrappingfailed.PNG"><br><br>球面的纹理坐标是在VertexShader中通过球的顶点位置坐标计算出来的，计算方法如下<br>&nbsp;&nbsp;&nbsp; const float HALF_PI = 3.1415926/2;<br>&nbsp;&nbsp;&nbsp; Out.UV.x = atan2(In.Position.z,In.Position.x) /HALF_PI;<br>&nbsp;&nbsp;&nbsp; Out.UV.y = In.Position.y;<br>事实上这是一个单位球，这个的计算使得 u&#8712;[0,4), v&#8712;[-1,1]，然后利用了wrap寻址模式进行寻址。然而，在边界处左右的顶点的u值分别是3.x和0.x，我期望在启用纹理环绕的情况下可以在3.x~4.0连上0.0~0.x上插值，但事实情况似乎是在3.x~0.x上直接做了插值才导致了上面图中的问题。<br>解决方法是将Shader改为<br>&nbsp;&nbsp;&nbsp; const float HALF_PI = 3.1415926/2;<br>&nbsp;&nbsp;&nbsp; Out.UV.x = atan2(In.Position.z,In.Position.x) /HALF_PI;<br>&nbsp;&nbsp;&nbsp; Out.UV.x = fmod(Out.UV.x, 1); //手动实现了wrap寻址模式<br>&nbsp;&nbsp;&nbsp; Out.UV.y = In.Position.y;
<br>让u在区间[0,1]上，这时纹理环绕就正常工作了。<br>我用颜色输出了uv，可以看到明显的1.0和0.0的边界，说明插值是按照我需要的wrapping方法，从贴图的结果也可以看到是正常的。<br><img alt="" src="http://www.cnitblog.com/images/cnitblog_com/luckydmz/wrappingsuccess.PNG"><br><br>结论：纹理环绕要求纹理坐标在[0,1]范围才能正常工作。也可以认为wrap寻址和texture wrapping不能同时工作。<br><br>   <img src ="http://www.cnitblog.com/luckydmz/aggbug/73374.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/luckydmz/" target="_blank">魔のkyo</a> 2011-04-18 23:29 <a href="http://www.cnitblog.com/luckydmz/archive/2011/04/18/73374.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>纹理采样过滤方式</title><link>http://www.cnitblog.com/luckydmz/archive/2011/04/04/73261.html</link><dc:creator>魔のkyo</dc:creator><author>魔のkyo</author><pubDate>Mon, 04 Apr 2011 14:54:00 GMT</pubDate><guid>http://www.cnitblog.com/luckydmz/archive/2011/04/04/73261.html</guid><wfw:comment>http://www.cnitblog.com/luckydmz/comments/73261.html</wfw:comment><comments>http://www.cnitblog.com/luckydmz/archive/2011/04/04/73261.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/luckydmz/comments/commentRss/73261.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/luckydmz/services/trackbacks/73261.html</trackback:ping><description><![CDATA[在DirectX设置纹理采样过滤方式是通过
<title>IDirect3DDevice9::SetSamplerState</title>
<style>
p.clsInfo {margin-bottom:0px;margin-top:2px}
span.clsWarning {display:none}
span.clsPreliminary {color:#C1C0C0}
span.clsLink{color:blue;cursor:pointer;}
</style><xml></xml>
<h1>IDirect3DDevice9::SetSamplerState</h1>
<p>Sets the sampler state value.</p>
<pre class="syntax"><strong>HRESULT </strong><strong>SetSamplerState(</strong>
<strong>DWORD</strong> <em>Sampler</em><strong>,</strong>
<strong>D3DSAMPLERSTATETYPE</strong> <em>Type</em><strong>,</strong>
<strong>DWORD</strong> <em>Value</em>
<strong>)</strong>;</pre>
通过第二个参数Type可以分别传递<br>D3DSAMP_MAGFILTER = 5, 放大时的过滤方式<br>D3DSAMP_MINFILTER = 6, 缩小时的过滤方式<br>D3DSAMP_MIPFILTER = 7,
MipMap的过滤方式<br><br>第三个参数Value可以传递<br>    D3DTEXF_NONE = 0,&nbsp;
//只用于MipMap的过滤方式，表示不启用MipMap<br>D3DTEXF_POINT = 1,
//对于放大或缩小过滤时表示最近点过滤，对于MipMap过滤表示选择最近层<br>D3DTEXF_LINEAR = 2,
//对于放大或缩小过滤时表示线性过滤，对于MipMap过滤表示对最近的上下两层过滤后的值做线性插值<br>    D3DTEXF_ANISOTROPIC = 3,
//各向异性过滤，只用于缩小<br>    D3DTEXF_PYRAMIDALQUAD = 6,
//没用过<br>D3DTEXF_GAUSSIANQUAD = 7,
//没用过<br>    D3DTEXF_CONVOLUTIONMONO = 8,
//仅用于单色纹理，没用过<br><br>最基本的过滤方式就是最近点过滤直接把插值出来的uv通过公式计算到图素坐标，可以参考<a href="http://www.cnitblog.com/luckydmz/archive/2010/10/08/69779.html">http://www.cnitblog.com/luckydmz/archive/2010/10/08/69779.html
</a><br>因为算出来的可能是小数，就四舍五入，这个"四舍五入"就是最近点过滤。<br><br><img alt="" src="http://www.cnitblog.com//images/cnitblog_com/luckydmz/POINT.PNG"><br>&nbsp;&nbsp;&nbsp; 图：最近点过滤<br>
如果用u对应小数部分做权重对左右线性插值，再用v对应的小数部分进行上下线性插值，这个就叫线性过滤。<br>线性过滤可以较好的解决纹理被放大时像&#8220;马赛克&#8221;一样的问题，但对纹理被缩小时的走样就没什么用了（至少我感觉是这样的）。<br>为了解决缩小时的走样就出现了MipMap，就是把纹理做预处理，平均一下计算出对应的1/4大的一张(横竖各1/2)作为MipMap的一层，再平均一下算出1/16大的一层，...，然后采样时就到预计算好的层上去采，这样又快效果又好。对放大缩小和MipMap都使用线性过滤的方式一般被称作3线性过滤。<br><br><img alt="" src="http://www.cnitblog.com//images/cnitblog_com/luckydmz/3LINEAR.PNG"><br>&nbsp;&nbsp;&nbsp;
图：3线性过滤<br><br>
MipMap的主要问题是，当纹理的纵横缩放比差很大的时候会很模糊，当然其实也可能控制采样的层使它不模糊，但是又会出现和不使用MipMap时一样的走样问题，这很容易理解，因为MipMap预处理的时候是横纵一起缩放的，要大都大，要小都小，后来HP发明了一种rimipmap的东西，可以分别对横纵控制缩放比，不过最终没有被硬件广泛支持，可能是因为开销比较大（相当于需要预处理生成logW*logH层而且要多消耗原纹理空间的3倍，mipmap只要多消耗1/3倍）<br>
<br><img alt="" src="http://www.cnitblog.com//images/cnitblog_com/luckydmz/ANISOTROPIC.PNG"><br>&nbsp;&nbsp;&nbsp; 图：
各项异性过滤<br>所以后来又提出了一个基于MipMap的技术，就是各向异性，各项异性简单的可以这样理解：一个屏幕像素被反映射到纹理上的区域是一块近似的平行四边形（应该不是标准的平行四边形），然后用短边决定MipMap的层，再在长边的方向上多采样几次做混合，用短边决定MipMap层就使得层数不会太深，然后再用长边多次采样混合，这样就缓解了上面提到的模糊问题。<br><br>附：设置采样器状态的代码<br>&nbsp;&nbsp;&nbsp; { //最近点过滤且不使用MipMap<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_pRenderDevice-&gt;SetSamplerState(0, SAMP_MAGFILTER, TEXF_POINT);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_pRenderDevice-&gt;SetSamplerState(0, SAMP_MINFILTER, TEXF_POINT);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_pRenderDevice-&gt;SetSamplerState(0, SAMP_MIPFILTER, TEXF_NONE);<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; { //3线性过滤<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_pRenderDevice-&gt;SetSamplerState(0, SAMP_MAGFILTER, TEXF_LINEAR);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_pRenderDevice-&gt;SetSamplerState(0, SAMP_MINFILTER, TEXF_LINEAR);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_pRenderDevice-&gt;SetSamplerState(0, SAMP_MIPFILTER, TEXF_LINEAR);<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; { //各向异性过滤<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_pRenderDevice-&gt;SetSamplerState(0, SAMP_MAGFILTER, TEXF_LINEAR);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_pRenderDevice-&gt;SetSamplerState(0, SAMP_MINFILTER, TEXF_ANISOTROPIC);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_pRenderDevice-&gt;SetSamplerState(0, SAMP_MIPFILTER, TEXF_LINEAR);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_pRenderDevice-&gt;SetSamplerState(0, SAMP_MAXANISOTROPY, 4); //设置沿着长抽最多采样几次，默认是1次就和普通3线性采样没区别了<br>&nbsp;&nbsp;&nbsp; }
<br><br>    <img src ="http://www.cnitblog.com/luckydmz/aggbug/73261.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/luckydmz/" target="_blank">魔のkyo</a> 2011-04-04 22:54 <a href="http://www.cnitblog.com/luckydmz/archive/2011/04/04/73261.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Direct3D中投影空间内的点坐标与屏幕上点的对应关系</title><link>http://www.cnitblog.com/luckydmz/archive/2010/10/08/69779.html</link><dc:creator>魔のkyo</dc:creator><author>魔のkyo</author><pubDate>Fri, 08 Oct 2010 09:45:00 GMT</pubDate><guid>http://www.cnitblog.com/luckydmz/archive/2010/10/08/69779.html</guid><wfw:comment>http://www.cnitblog.com/luckydmz/comments/69779.html</wfw:comment><comments>http://www.cnitblog.com/luckydmz/archive/2010/10/08/69779.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.cnitblog.com/luckydmz/comments/commentRss/69779.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/luckydmz/services/trackbacks/69779.html</trackback:ping><description><![CDATA[Direct3D中投影空间内的点坐标与屏幕上（或视口内）点的对应关系，<br>设屏幕大小为w&#215;h，屏幕左上角像素的中心被定义为(0,0)，整个屏幕是从(-0.5,-0.5)-(w-0.5,h-0.5)，<br>像素<br>将投影空间内的x轴上区间(-1.0-1/w, 1.0-1/w]均匀分成w份，每份长度2/w，<br>将投影空间内的y轴上区间(-1.0-1/h, 1.0-1/h]均匀分成h份，每份长度2/h。<br>例如最左侧的像素点的对应的横坐标区间为(-1.0-1/w,-1.0+1/w]，依次类推。<br>对于更一般的情况有<br>光栅化公式 投影空间坐标(x,y) -&gt; 屏幕像素坐标(Sx,Sy)<br>Sx = x * (w/2) + (w/2)&nbsp;&nbsp; [1]<br>Sy = y * (-h/2) + (h/2)&nbsp; [2]<br><br>以上是光栅化时的规律，在纹理采样时，Direct3D使用如下公式<br>采样公式 纹理坐标(u,v) -&gt; 图素坐标(Tx,Ty)<br>Tx = u*w - 0.5&nbsp; [3]<br>Ty = v*h - 0.5&nbsp; [4]<br><br>如果需要将一张图的左上角（最左上角图素的左上角）对应到屏幕的左上角<br>有些人可能会将左上角Vertex的投影空间坐标设置为(-1.0,1.0)，其实这是不对的，我们可以用光栅化公式推导一下，<br>当(x,y) = (-1.0,1.0)时，<br>Sx = x * (w/2) + (w/2) = 0<br>Sy = y * (-h/2) + (h/2) = 0<br>而(Sx,Sy)=(0,0)并不是屏幕的左上角，而是屏幕左上角像素的中心，屏幕的左上角坐标应该是(-0.5,-0.5)<br>解<br>x * (w/2) + (w/2) = -0.5<br>y * (-h/2) + (h/2) = -0.5<br>可得，<br>x = -1 -1/w<br>y = 1 + 1/h<br>这才是正确的Vertex的投影空间坐标<br><br>结合光栅化公式和采样公式我们还可以推导一下ShadowMap中采样深度图的纹理坐标，首先明确问题：<br>即，已知深度图上一点是由投影空间坐标(x,y)光栅化成的，现在要采样该点，计算(u,v)，用(x,y)表示。<br><br>解：只需将建立方程让纹理坐标对应的图素坐标=屏幕像素坐标(即深度图上的图素坐标)<br>Tx = Sx<br>Ty = Sy<br>将[1],[2],[3],[4]式代入可解得，<br>u = x * 0.5 + (0.5 + 0.5/w)<br>v = y * -0.5 + (0.5 + 0.5/h)<br><br>参考：http://www.gesoftfactory.com/developer/Textures.htm   <img src ="http://www.cnitblog.com/luckydmz/aggbug/69779.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/luckydmz/" target="_blank">魔のkyo</a> 2010-10-08 17:45 <a href="http://www.cnitblog.com/luckydmz/archive/2010/10/08/69779.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>欧拉角与万向节死锁</title><link>http://www.cnitblog.com/luckydmz/archive/2010/09/07/68674.html</link><dc:creator>魔のkyo</dc:creator><author>魔のkyo</author><pubDate>Mon, 06 Sep 2010 17:51:00 GMT</pubDate><guid>http://www.cnitblog.com/luckydmz/archive/2010/09/07/68674.html</guid><wfw:comment>http://www.cnitblog.com/luckydmz/comments/68674.html</wfw:comment><comments>http://www.cnitblog.com/luckydmz/archive/2010/09/07/68674.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cnitblog.com/luckydmz/comments/commentRss/68674.html</wfw:commentRss><trackback:ping>http://www.cnitblog.com/luckydmz/services/trackbacks/68674.html</trackback:ping><description><![CDATA[首先来看一下什么是欧拉角（Euler angles）？<br />构件在三维空间中的有限转动，可依次用三个相对转角表示，即进动角、章动角和自旋角，这三个转角统称为欧拉角。&#8212;&#8212;引自百度百科<br />莱昂哈德&#183;欧拉用欧拉角来描述刚体在三维欧几里得空间的取向。对于任何一个参考系，一个刚体的取向，是依照顺序，从这参考系，做三个欧拉角的旋转而设定的。所以，刚体的取向可以用三个基本旋转矩阵来决定。换句话说，任何关于刚体旋转的旋转矩阵是由三个基本旋转矩阵复合而成的。&#8212;&#8212;引自wikipedia<br /><br />好了，引完了，我来说一下我的理解吧，欧拉角是对旋转的一种刻画方式，就像其他刻画方式一样如旋转矩阵，四元数。欧拉角对应的旋转矩阵可以看作是三个绕轴旋转的旋转矩阵的复合。<br />问题来了，三个绕轴旋转的旋转矩阵绕的是什么坐标系下的轴？<br />对于坐标系E下的欧拉角(&#945;，&#946;，r)和以下哪个旋转矩阵是等价的<br />1.绕坐标系E下的x轴旋转&#945;，绕坐标系E下的y轴旋转&#946;，绕坐标系E下的z轴旋转r，三个矩阵的复合<br />2.绕坐标系E下的x轴旋转&#945;，绕 坐标系E在绕x轴旋转&#945;后的新系E'下的y轴旋转&#946;，绕 坐标系E'在绕y轴旋转&#946;后的新系E''下的z轴旋转r，三个矩阵的复合<br />通俗的讲，我们在旋转时，要不要把坐标系一起转动？<br />事实上两种理解都可以，当然，两种转法并不等价，下面我来解释这个问题，<br />当我们讲到坐标系E下的欧拉角(&#945;，&#946;，r)时，这句话是有歧义的，我们必须定义旋转顺序，因为旋转顺序会影响旋转结果。<br />如果假设旋转顺序是先绕x轴再y轴再z轴，x-y-z，那么这个欧拉角对应的旋转矩阵是指上述的2所表示的旋转矩阵。<br />如果假设旋转顺序是先绕z轴再y轴再x轴，z-y-x，那么这个欧拉角对应的旋转矩阵是指上述的1所表示的旋转矩阵，等等，你肯定会问，这难道不是把2中的先后顺序换一下就行了吗，"绕坐标系E下的z轴旋转r，绕 坐标系E在绕z轴旋转r后的新系E'下的y轴旋转&#946;，绕 坐标系E'在绕y轴旋转&#946;后的新系E''下的x轴旋转&#945;，三个矩阵的复合"难道不是这样吗？是的，当然也是这样。<br /><br />下面我来证明两种复合方式是相等的，<br />为了方便证明我先定义一些记号，<br />记：<br />绕坐标系E下的x轴旋转&#945;的旋转矩阵为Rx,<br />绕坐标系E下的y轴旋转&#946;的旋转矩阵为Ry,<br />绕坐标系E下的z轴旋转r的旋转矩阵为Rz,<br /><br />绕坐标系E下的z轴旋转r的旋转矩阵为Rr（Rr=Rz），<br />绕 坐标系E在绕z轴旋转r后的新系E'下的y轴旋转&#946;的旋转矩阵为Rb，<br />绕 坐标系E'在绕y轴旋转&#946;后的新系E''下的x轴旋转&#945;的旋转矩阵为Ra，<br /><br />另外，将矩阵R的逆记作R~<br /><br />求证：Rx*Ry*Rz = Rr*Rb*Ra<br /><br />证明：<br />Rr = Rz&nbsp; 定义就是一样的，显然相等<br /><br />Rb = Rr~*Ry*Rr 要得到绕 坐标系E在绕z轴旋转r后的新系E'下的y轴旋转&#946;的旋转矩阵为Rb，可以先应用Rr~这时可以视作在E下，然后使用E下的旋转Ry绕旧的y轴旋转，在应用Rr转回到E'<br /><br />Ra = (Rr*Rb)~*Rx*(Rr*Rb) 理由同上<br /><br />所以 右边=Rr*Rb * Ra<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; =Rr*Rb * (Rr*Rb)~*Rx*(Rr*Rb)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; =(Rr*Rb)* (Rr*Rb)~*Rx*(Rr*Rb)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; =Rx*(Rr*Rb)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; =Rx*(Rr*Rr~*Ry*Rr)<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; =Rx*Ry*Rz =左边 <br />#证毕<br /><br />这与DirectX在文档中对D3DXMatrixRotationYawPitchRoll的描述是一致的<br /><img alt="" src="../../images/cnitblog_com/luckydmz/dx.PNG" width="238" border="0" height="207" /><br />D3DXMATRIX * D3DXMatrixRotationYawPitchRoll(<br />&nbsp; D3DXMATRIX *pOut,<br />&nbsp; FLOAT Yaw,&nbsp;&nbsp;&nbsp;&nbsp; //绕y轴的转动角<br />&nbsp; FLOAT Pitch,&nbsp;&nbsp; //绕x轴的转动角<br />&nbsp; FLOAT Roll&nbsp;&nbsp;&nbsp;&nbsp; //绕z轴的转动角<br />);<br /><div>The order of transformations is roll first, then pitch,  then yaw. Relative to the object's local coordinate axis, this is  equivalent to rotation around the z-axis, followed by rotation around  the x-axis, followed by rotation around the y-axis.<br /> </div><br /><br />关于欧拉角讲到这里就差不多了，下面来探讨一个和欧拉角有关的概念万向节死锁。<br /> <br /> 讲到欧拉角一般都会提到万向节死锁，什么是万向节死锁（Gimbal Lock）呢？<br /> 万向节死锁有时又被简称为万向节锁或者万向锁，是指当三个万向节其中两个的轴发生重合时，会失去一个自由度的情形。<br /> 下面的视频很好的说明了这一点。<br /><embed src="http://player.youku.com/player.php/sid/XNzkyOTIyMTI=/v.swf" quality="high" width="480" height="400" align="center" allowscriptaccess="sameDomain" type="application/x-shockwave-flash"></embed><br />正因万向节死锁的存在，使用欧拉角是无法实现球面平滑插值的，<br /> <img src="http://www.cnitblog.com/images/cnitblog_com/luckydmz/GimbalLock.PNG" alt="" width="430" border="0" height="345" /><br /> 如上图，此时如果下一帧要让箭头指向右侧后方，那么绿色和蓝色对应的旋转角必定要发生突变，因为目前如果想朝着垂直红色圈的方向旋转箭头就像被卡住一样，我想这就是叫它死锁的原因吧。<br /> 总之万向节死锁会导致位置上连续变化 在数值表示上确是非连续的。给定的两个关键帧之间无法平滑过渡。顺便提一下解决方法，可以使用四元数球面线性插值(Slerp) <img src ="http://www.cnitblog.com/luckydmz/aggbug/68674.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cnitblog.com/luckydmz/" target="_blank">魔のkyo</a> 2010-09-07 01:51 <a href="http://www.cnitblog.com/luckydmz/archive/2010/09/07/68674.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>