关于作者

姓名:

性别:男

出生日期:1962-03-31

地区:北京

联系电话:

QQ:--

婚否:保密
用户名:xmchang
笔名:方方
地区: 北京
行业:其他

日历  

快速登录

+ 用户名:
+ 密 码:

在线留言



访问统计:
文章个数:16
评论个数:3
留言条数:2




Powered by BlogDriver 2.1

xmchang的博客

 

欢迎访问xmchang的博客

文章

Android 文字绘制到Bitmap上
Android 文字绘制到Bitmap上 OpenGL ES中似乎不能输文本.将文本写到Bitmap上,再作为贴图,则可实现文字输出. 文字绘制到Bitmap上的方法为: String mstrTitle = "文字渲染到Bitmap!"; Bitmap bmp = Bitmap.createBitmap(256,256, Bitmap.Config.ARGB_8888); //图象大小要根据文字大小算下,以和文本长度对应 Canvas canvasTemp = new Canvas(bmp); canvasTemp.drawColor(Color.WHITE); Paint p = new Paint(); String familyName ="宋体"; Typeface font = Typeface.create(familyName,Typeface.BOLD); p.setColor(Color.RED); p.setTypeface(font); p.setTextSize(22); canvasTemp.drawText(mstrTitle,0,100,p); 本例图是黑底红字我们可以用Bitmap.getPixel,和setPixel或getPixels,setPixels,取得点的颜色,根据是否有点,将Alpha通道清空,获得透明的字. 模拟器上显示中文无问题,不知道手机对中文的支持如何.

- 作者: 方方 2009年09月18日, 星期五 10:15  回复(0) |  引用(0) 加入博采

Andriod 使用SurfaceView的OpenGL ES基本框架(sdk1.5)
摘要:Android的OpenGL ES,一般使用类android.opengl.GLSurfaceView; 网上看到一个程序直接使用SurfaceView来使用OpenGL的,尝试下,看是否可以和canvas并用,这样就可以输出文字等. 测试结果是不能并用.先留着该框架,以后测试是否速度和效果上有差别. 下面使用5个class构造一个基本的框架 查看全文

- 作者: 方方 2009年09月18日, 星期五 10:09  回复(0) |  引用(0) 加入博采

Andriod Java OpenGL ES基本框架(sdk1.5)
摘要:Android的OpenGL ES,一般使用类android.opengl.GLSurfaceView; 下面使用5个class构造一个基本的框架 Main为入口Activity MyOpenGLView为窗口,可取得消息做处理。 MyOpenGLRender为渲染器,这里可获得gl句柄,实现gl初始化,窗口大小改变,帧绘制。 MyDraw为OpenGL ES的实际操作,当然,可以合并到上面渲染器中,为了明晰,独立出来。 其中包含函数init(gl),change(gl,w,h),drawframe(gl);这样修改起来是不是更便捷? MyCube是一个彩色盒子的类,定义了顶点,色彩,面索引,draw(gl)即可绘制一个旋转的盒子。 查看全文

- 作者: 方方 2009年09月18日, 星期五 10:04  回复(0) |  引用(0) 加入博采

十一节期间,对G2引擎做修改

    G2引擎是1年前开始写的,前面的一些Demo图象都是使用了G2.
    之所以叫G2,因为以前花了很长很长时间学习golgotha引擎(令人难忘的引擎,后来我基于它制作了游戏<啊平型关>,不幸和golgotha引擎的命运一样被打入冷宫),一些思想都是从它继承的,然而golgotha太复杂了,用起来难度很大.但要自己写个"引擎"的确是个浩大的工程,一直不敢去尝试,终于有一天心情不错,开始写了几段,就一直写下去了.写得烦躁了,就扔下休息.心情好的时候继续.半年前初步成型了.叫G2意思是golgotha的第2版,又比golgotha精练和模块化分明.新的内容是对HLSL的封装,因为golgotha时代还没有这个东西.
    构架比较理想,制作个演示变得很容易了.那些时髦的内容比较方便地制作个独立的object,加入系统中,因此,把bsp,terrain,mdl等,全加进去了.
    但是,所有新的内容都需要用C来写,还是比较费劲,且没有使用COM接口,只使用了DLL接口,模块修改起来相互干扰比较厉害,也懒的学COM了.
    十一期间,7天长假,用来做个结构性改变的大工程.(好羡慕人人都去旅游,想不明白中国怎么一下变成都是富人了)
    结构性变化的用意是将Lua引入G2引擎,达到一个类似c++builder的境界,原来的object变为控件,可以通过一个属性表来设置属性,可加入事件函数,为Lua的程序.这样,制作内容的过程就变的比较简单了,不需要再去C编译.
    于是,把G2核心进一步简化,把原来包含其中的object管理器,渲染器都独立出来作为driver,只是driver管理其留在中间,其实也可以扔出来,但把Driver管理器放到driver组里面去不合适,父亲和儿子不能一组.原来都使用Class成员指针记录各个Driver,如display_drv,model_drv,使用起来很方便,但有个问题,就是g2.dll一加内容,class结构变了,其他的driver及object需要重新编译才行.改为一个指针数组,将各driver变为序号,DRV[DISPLAY_DRV],加入一个get_drv(int n)取得driver地址,再加个宏定义,#define g2_display_drv g2->get_drv(DISPLAY_DRV),这样,原来的g2->display_drv,简单修改为g2_display_drv即可.好处是加driver时,class不变,这样g2的小变化,其他driver及object不必要重新编译.
    引擎性能优越了一些,经过几天奋斗,终于让它又运转起来了.编写了一个测试的Lua驱动.
    但对lua的引入仍未能实现,需要将object作个可控制的参数表,这样,原来的int,bool之类的C方式显然不行,必须有参数的名字,可以外部取得参数表,可定义数值,参数可能是多项选择性,如bool可能有true及false选择器,其他可能有多项选择.lua事件脚本也能读写参数表,这样,可控制这个object.
    属性编辑器以前看过的nvidia的cgfx编辑器的,不幸硬盘找不到了,是包含在cgtool1.1里的,新版本把它扔了.从nvidia网站拉了几次也拉不下来,太大了,用讯雷下载nvidia的东西总是出现文件错误.
    如何操控仍是个谜团,对图象的动画可以做个多个序列的图片,定义序列为"站立","行走","跑",直接播放指定序列及可.
    对object要复杂了,比如,一辆Tank,需要不同方向开动,对地形反映,转动炮塔,开火,被击中,变为残骸.用C写比较固定的内容,比如炮塔的转动,履带的转动等.Tank的各个model可以参数变更,这样,更换model就成了不同Tank,没有炮塔就是装甲运兵车了.运行的速度可以定义,以便重型Tank和轻型Tank的不同.那Lua脚本做什么用?比如,我们在场景中一下扔10辆坦克,都定义为不活动的不可见的,只有一个Think过程在运转,这样,场景中似乎没有Tank这个东西,当条件具备时,比如到一定时间,Tank才按序出现,并各按其路线前进,出现障碍,选择绕行路线,发现敌人,开火,直到壮烈牺牲了,变为一个没有思想的障碍物.C写硬性过程,Lua写软性的东西.
   接下去,再继续确定object参数表,用Lua控制它.结果如何,Lua接口如何自动生成,仍需要研究.以前有个CBP可自动封装Lua,但竟然不支持dll,麻烦.
   目标只有一个,当做完后,自己感觉做的很棒.肯定不想OpenSource,但拿它当个下蛋的鸡也好难.路需要向前走,走向哪里,有首歌说得好"未来会怎样怎样?我无法猜想......"
   上文如同天书,只有自己明白.以前有个笑话,说女人说话不加思考,女人回答,我不说出来如何思考? 同理,有时候自己有事不明白,写着写着就明白了许多.

   20061009

- 作者: xmchang 2006年10月9日, 星期一 22:51  回复(0) |  引用(0) 加入博采

旧文拾遗:Nebula2原理初探(续3)

    半年不做研究和写文章了,总是为衣食奔波,硬盘上发现这篇1年多前写的小文章,前面几篇发在别人的论坛上,找不到了,只剩这篇尾巴,读起来莫名其妙,但也有点周星星的无喱头风格,贴出来,作为一个过度......


Nebula2原理初探(续3):超越

在前面几篇,我们勾画出Nebula2的确美好画面.但是,现实是复杂的,那些所说的美好的东西,哪些是真的,哪些不是真的?往往不是凭感觉。我们能够做什么?仍然是个问题。

1.层次模型的骗局
层次结构能加快搜索速度,似乎大家一听,都感觉有道理。比如,有1000个对象,顺序搜索的话,最多要1000次。等分成四组,那只需要250次,加组的判别,只需要多加四次。但是,仔细一想,随便哪个程序员,也不会傻到一个个去比较。可以采取二分法查找,那1000个对象,最多只要10次就找到了。如果等分成4组,需要8次,加上组的判别需要2次,哪仍然是10次,一点没快。如果分成三个层次,只会次数更多。
而为了这个并不见提高效率的层次,我们要付出很大的代价,要编写记录许多的层次……这样的话,我们有什么必要采用这样的层次模型?

2.更好而简单的办法:
我们可以采取一个简单的数组存储所有的对象名字,在Game开始事,将所有脚本先行载入。这时,我们查找一脚本里所有对象,把它转换为数组的序号,成为一个“编译”了的脚本,执行时,直接到数组里去取,不就不需要任何查询了吗?
这个“编译”过程,所花费的时间,相比Game载入几十兆的图象所花的时间,简直是不值的一提。这是一个解决方案。应该还有其他的方案。

3.N2E的新解决方案
   我们在脚本载入前,必须加入一个预“编译”器,将其调用的对象提前变更为数组的序号。
   当然,脚本现在已有不少现成的编译器,我们大可以研究一下,改进成我们需要的编译器。
   我们最终的Game,应该不会是解释方式执行的,所以,我们必须在解释和编译之间,寻找一个结合点。
   这样,我们可以构造一个全新的构架。

4.N2E改名
既然我们不认为Nebula2的骨架确有优越性,那我们就大可不必采取Nebula2的骨架。而采用我们自己的骨架。而我们原先命名的N2E(Nebula2Edit)系统,自然就就没有必要在用这个名字了。我们大可以采取自己的骨架,自己的名字……我们可以取个具有时代特色的名字,比如SDH1:意思就是“苏丹红一号”…….当然,这样毒性有点大,要起个可爱的名字,还需要再考虑。
当然,对Nebula2的牛肉,我们也没有必要一古脑丢掉,闲来无时嚼嚼看,也许有些营养。看见旁人推崇Nebuka2的速度,我不感苟同,我运行时,感觉很Slow,远远达不实用的程度。

5.C#是一条路吗?
C#是个新的解决方案,可以结合多种语言。它所兼容的Java,就是解释性语言。如果用它来打造一个编辑器,具有可行性。不过,因为它太庞大了太新了,还没有看到什么具体的应用,对它还是一知半解的,也许行,也许不行…….这需要懂的人来解答。

6.总结
   混沌……上面所述的,使我们在还没有踏上征程时,已经对前途充满迷茫。
只是,我觉得我们仍然该做点什么。我们的程序员,在确知自己不如美国人、俄国人、印度人、巴西人及韩国人之后,是否要向德国人低头,仍然还需要考虑一下。
   我们应该可以做点什么,这样,至少我们的将来,软件水品可以不至于落在其他的国家后面,例如尼加拉瓜,爪哇的后面……
   呵呵,说这话,不要杀我呀。我们目前至少还有值得骄傲的金山软件,盛大软件替国人扬威呢。

(Nebula2原理的胡乱初探,到此告一段落)

笨笨  2005.4.17

 

- 作者: xmchang 2006年10月9日, 星期一 21:00  回复(0) |  引用(0) 加入博采

已锁定
此日志的浏览权限已被作者锁定,请同作者联系,发送短消息,如果你的身份符合作者的要求,点击此处可以进行浏览

- 作者: xmchang 2006年03月7日, 星期二 03:38  回复(0) |  引用(0) 加入博采

ATI的Octree的terrain Demo简要分析

ATI总是被Nvidia的光辉遮挡,其实他们常有一些好东西,如Rendermonkey等.
这个以前偶然在他们网站上看到的terrain demo,就是个不错的东西.这个Demo在Nvidia卡上也能很好地运行.

初看以为是个和其他演示类似的东西,但地形起伏有点看起来有点异样,后面居然钻入长长的地洞,的确让人吃惊.
简单看了一下,原理也不复杂,就是不使用LOD技术,而改用Octree,从四叉树改为八叉树.这样,地形可以任意变化.

但是,这样又出现新问题了,对于使用LOD的,可以简单地使用Perlin等生成多变的高度图来得到Terrain地形,而OCTree的地形如何生成.就象这个Demo的内容是如何制作出来的?ATI没有给出解释.也许是费了牛劲抠出来的.

另外,LOD方式可以比较容易地根据距离做简化,以提高渲染速度,对Octree的内容如何简化?仍然是一件困难的事情.
因此,这个Demo看过后,觉得没有什么实用价值,丢在一边没有理会.

最近,制作多层贴图terrain后,感觉采用自动贴图的方式可以基本解决这个难题,方法是:
先由高度图生成一般的Mesh,再将Mesh在3dmax中任意修改,可扔上去一些石头,峭壁,天桥等,当然,也可以是地洞等.
要把各种材料的过度手工处理好很难,我们采取自动贴图的方式,按面的高度,坡度,阴阳等参数控制多层贴图,这样,
各元件浑然一体,再将Mesh作Octree切割就可以了.(对于地洞类的,我觉得还是采用入口方式单独渲染比较好).

程序文件较多,但各单元都很简单,简要分析如下:

基本文件如下:
DXErrors.cpp  //出错处理

FlyDemo.cpp  //飞行演示,里面建立terrain,skydome,path

Helper.cpp  //不是帮助提示,里面只有一个函数ComputeViewMatrix,计算视口矩阵,(放在这里有点怪怪的)

Material.cpp  //材料
MManager.cpp  //材料管理器

Octree.cpp  //Octree功能单元,对树上各渲染队列的可视化检测,后面注释.

Path.cpp  //自动演示路径功能,打开path.dat,文本记录的路径,按路径移动摄影机

RenderQueue.cpp //队列渲染:Octree记录的不是片,而是一个个片的三角形队列,Octree检索后保存可视队列到这里,然后渲染

Skydome.cpp  //天空球,与skybox类似,不管它

SplashScreen.cpp //Very简单,封面图象

Terrain.cpp  //Terrain单元,很简单,载入切割好的Octree队列,渲染时调用Octree检索可视队列,调用队列渲染绘制,fog的设置

TerrainApp.cpp //主程序,窗口建立,d3d初始化,建立FlyDemo及SplashScreen 

Texture.cpp  //贴图
TManager.cpp  //贴图管理

TriangleList.cpp //三角形列表,由RenderQueue调用,实现最终渲染

VertexStore.cpp //顶点堆,供三角形列表使用 

Viewer.cpp  //摄影机,内容不多,要点是里面的ComputeClipVolume函数,计算出投影锥的6个平面,供Octree检索使用


核心程序是Octree.cpp,功能也很简单,

//-----------------------------------------------------------------------------
// File: Octree.cpp
//
//  Terrain Demo.
//
// Copyright (c) 2000 ATI Technologies Inc. All rights reserved.
//-----------------------------------------------------------------------------

#define STRICT
#define D3D_OVERLOADS

#include
#include
#include "Helper.h"
#include "Octree.h"
#include "DXErrors.h"

COctreeNode::COctreeNode(FILE *fp) //建立
{
 ReadTree(fp);   //从切割好的文件读取渲染队列内容
}

COctreeNode::~COctreeNode()
{
 if (m_pKids[0] != NULL)
 {
  for(DWORD i = 0; i < 8; i++)
   delete m_pKids[i];
 }
 else
 {
  //this node is a leaf so just delete its poly lists
  delete []m_pTriangleLists;
 }
}

VOID COctreeNode::ReadTree(FILE *fp)  //读取树文件
{
 CHAR type[5];

 fread(type, sizeof(CHAR), 4, fp);  //4字节头标志
 type[4]='\0';

 fread(&m_vCenter, sizeof(D3DXVECTOR3), 1, fp); //三个float中心位置
 fread(&m_fWidth, sizeof(FLOAT), 1, fp);  //1个float宽
 fread(&m_fHeight, sizeof(FLOAT), 1, fp); //1个float高
 fread(&m_fDepth, sizeof(FLOAT), 1, fp);  //1个float深
 fread(&m_dwListCount, sizeof(DWORD), 1, fp); //队列数

 DWORD i;
 FLOAT wd2 = m_fWidth / 2.0f;
 FLOAT hd2 = m_fHeight / 2.0f;
 FLOAT dd2 = m_fDepth / 2.0f;

 for(i = 0; i < 8; i++)   //8叉存储,记录每格的中心坐标
 {
  //compute the location of the current corner
  m_vCorners[i].x = m_vCenter.x + ((i & 0x1) ? wd2 : -wd2);
  m_vCorners[i].y = m_vCenter.y + ((i & 0x2) ? hd2 : -hd2);
  m_vCorners[i].z = m_vCenter.z + ((i & 0x4) ? dd2 : -dd2);
 }

 if (!strcmp(type, "LEAF"))  //如果是叶子,读取并存入面片队列
 {
  //node is a leaf in the tree read its polygon
  //lists and set its children to NULL
  m_pTriangleLists = new CTriangleList[m_dwListCount];

  for(i = 0; i < m_dwListCount; i++)
   m_pTriangleLists[i].Load(fp);  //读取面片队列

  for(i = 0; i < 8; i++)
  {
   m_pKids[i] = NULL;
  }
 }
 else if (!strcmp(type, "NODE"))  //如是树叉,建立八个分支
 {
  //the node is non-leaf, so read its children
  for(i = 0; i < 8; i++)
  {
   m_pKids[i] = new COctreeNode(fp);  //递归,直到全部读入,Octree读入完成
  }
 }
}

//Terrain采用Octree,估计应该把内容分成小块区域,使渲染及查询的比例平衡
//每个叶子上保存的是三角队列,相当于一个model.
//不知道在切割时对跨界的队列如何处理,是强行切开或是两边都存.两边都存的话,要设定个检索标志,避免重复渲染.


//下面是Octree可视检测,基本的Octree检索方法.好象算法还比较简单,没有太多的乘法.

VOID COctreeNode::Cull(CRenderQueue *pRQ, CLIPVOLUME& cv, D3DXVECTOR3& pos)
{
 DWORD zones[8] = {0, 0, 0, 0, 0, 0, 0, 0};
 FLOAT x, y, z;
 DWORD i;

 //对八叉的某格子,按包围盒8个点作是否在视锥内的检测
 for(i = 0; i < 8; i++)
 {
  x = m_vCorners[i].x;
  y = m_vCorners[i].y;
  z = m_vCorners[i].z;

  //由点到面的距离,可知道是否在视锥的某个平面的外面
  if ((cv.pNear.a * x + cv.pNear.b * y + cv.pNear.c * z + cv.pNear.d) > -0.01f)  //近平面
   zones[i] |= 0x01; //某个在里,做个标记
  else if ((cv.pFar.a * x + cv.pFar.b * y + cv.pFar.c * z + cv.pFar.d) > -0.01f)  //远平面
   zones[i] |= 0x02;

  if ((cv.pLeft.a * x + cv.pLeft.b * y + cv.pLeft.c * z + cv.pLeft.d) > -001f)
   zones[i] |= 0x04;
  else if ((cv.pRight.a * x + cv.pRight.b * y + cv.pRight.c * z + cv.pRight.d) > -0.01f)
   zones[i] |= 0x08;

  if ((cv.pTop.a * x + cv.pTop.b * y + cv.pTop.c * z + cv.pTop.d) > -0.01f)
   zones[i] |= 0x10;
  else if ((cv.pBottom.a * x + cv.pBottom.b * y + cv.pBottom.c * z + cv.pBottom.d) > -0.01f)
   zones[i] |= 0x20;
 }
 
 //if all of the corners are outside of the boundaries
 // this node is excluded, so stop traversing
 if (zones[0] & zones[1] & zones[2] & zones[3] & zones[4] & zones[5] & zones[6] & zones[7]) //如果8点均在外,剔除
  return;  //疑问,如果一个大包围盒把视锥完整包围了,不是也被剔除了吗? 看过别的算法是,如果8个点都在某个面的外侧才剔除,

 // if this is a leaf add the triangle lists to the render queue
 if (m_pKids[0] == NULL)
 {
  for(i = 0; i < m_dwListCount; i++)
   pRQ->AddToQueue(&m_pTriangleLists[i]); //如果是叶子,将叶子上内容保存到渲染队列
 }
 else
 {
  //this is not a leaf traverse deeper
  for(i = 0; i < 8; i++)
   m_pKids[i]->Cull(pRQ, cv, pos);  //如果是树叉,按八叉递归查询更细的盒子
 }
}

//Octree的切割并不复杂,只要安八叉切割方块,比较三角条的包围盒是否在方块内就可以了.

//对庞大的场景完全采用Octree的方式,是否能达到理想的效率仍表示怀疑.
//虽然ATI的Demo看起来速度理想,我觉得还是需要对不同单元做入口处理比较可靠

//其他单元不复杂,不再注解.有兴趣的可自己看程序,如下地址下载.
http://www.ati.com/developer/sdk/RadeonSDK/Html/Samples/Direct3D/RadeonTerrainDemo.html

- 作者: xmchang 2006年02月18日, 星期六 21:58  回复(0) |  引用(0) 加入博采

反射计算(dx9 StencilMirror)

反射只是翻转摄影机,将场景渲染到Texture上,或使用STENCIL蒙板直接绘制。
具体计算查看了D3D的 Sample,如下:


   D3DXMATRIXA16 matViewSaved;
    m_pd3dDevice->GetTransform( D3DTS_VIEW, &matViewSaved ); //取得视口

//定义一个翻转的参考平面,可根据需要自己定义位置及法线
    D3DXVECTOR3 vPoint(0,0,0);
    D3DXVECTOR3 vNormal(0,1,0)
;    
    D3DXMATRIXA16 matView, matReflect;
    D3DXPLANE plane;
    D3DXPlaneFromPointNormal( &plane, &vPoint, &vNormal ); //生成这个平面

    D3DXMatrixReflect( &matReflect, &plane );   //取得该平面的反射矩阵

    D3DXMatrixMultiply( &matView, &matReflect, &matViewSaved ); //使用反射矩阵翻转视口,摄影机被翻转到反射位置

    m_pd3dDevice->SetTransform( D3DTS_VIEW, &matView );  //设置视口,这样摄影机渲染出的内容就是翻转的了

//设置剪切平面,使反射面上的内容被渲染,面下的被丢弃
    m_pd3dDevice->SetClipPlane( 0, plane );
    m_pd3dDevice->SetRenderState( D3DRS_CLIPPLANEENABLE, 0x01 );

//(不知在Shader里面,这个功能是否还起作用)
//如果渲染到材料上,需要使用投影矩阵把材料贴到实际反射的物体上。也可把物体点位先转换为屏幕坐标再贴图,原理是一样的

- 作者: xmchang 2006年02月18日, 星期六 09:07  回复(0) |  引用(0) 加入博采

Bump 简要注释

   Bump原理本来很简单:根据细节图,调整每点的光照,使平面具有凹凸效果.
   但查阅了一下,门派很多,算法各有差别,比如,有直接normalize的,因ps1.1不支持,有用Cube图查表法解决normalize,
   有用3D图实现SelfShadow的,等,搞得复杂无比,似乎需要研究十天八天的才能搞明白.

   下面这个程序是从http://www-user.tu-chemnitz.de/~vix/homepage/找到的一个小Demo,比较单纯.

   1.切线的生成
 绘制Bump除了需要法线,还需要知道切线,幸好Direct3D为我们准备了切线计算函数,一切就变得很简单了:

 if FAILED( D3DXLoadMeshFromX( sFilename, D3DXMESH_SYSTEMMEM, dev, NULL, &pD3DXBuf, NULL, &dwNumMaterials, &pMsh ) )
  return E_FAIL;


 if (Tangent)
 {
  D3DVERTEXELEMENT9 decl[] =
  {
   { 0, 0,  D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },  //准备一个描述标
   { 0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0 }, 
   { 0, 24, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 },
   { 0, 32, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TANGENT, 0 },
   D3DDECL_END()
  };

  //reform the mesh to one which will have space for the tangent vectors
  if (FAILED(pMsh->CloneMesh(D3DXMESH_MANAGED, decl, dev, &pMsh2)))
   return E_FAIL;

  SAFE_RELEASE(pMsh); pMsh = pMsh2;

  if FAILED(D3DXComputeNormals(pMsh2,NULL))
   return E_FAIL;
 
  if FAILED(D3DXomputeTangent(pMsh2,0,0,0,TRUE,NULL))
   return E_FAIL;
 }

 //这样,Mesh的法线和切线就制作好了,可以投入FX去渲染Bump了

2.Offset Bump的Fx
 该部分也很简单,只需要VS1.1及PS1.1支持,PS里完全没用到normalize

loat4x4 mWorldViewProj;
float4x4 mWorld;
float3   vEye_Pos;
float3  vLight_Pos;
float3  vFactors = { 0.02, -0.01, 0.5 };  //位移控制参数,

texture tBump < string name = "Data\\rockwall_normal.tga"; >; //细节法线图
texture tHeight < string name = "Data\\rockwall_height.tga"; >; //细节高度图(由高度图可以生成上面的法线图,为了速度快,我们预先分别生成好两个图)
texture tBase < string name = "Data\\rockwall.tga"; >; //表面贴图


sampler sBase = sampler_state
{
    Texture   = <tBase>;
    MipFilter = LINEAR;
    MinFilter = LINEAR;
    MagFilter = LINEAR;
};

sampler sBump = sampler_state
{
    Texture   = <tBump>;
    MipFilter = LINEAR;
    MinFilter = LINEAR;
    MagFilter = LINEAR;
};

sampler sHeight = sampler_state
{
    Texture   = <tHeight>;
    MipFilter = LINEAR;
    MinFilter = LINEAR;
    MagFilter = LINEAR;
};


struct OFFSET_VS_OUT
{
    float4 Pos:     POSITION;
 float3 Light:   COLOR0;
 float3 Normal:  COLOR1;
 float2 Height:  TEXCOORD0;
 float3 Base1:   TEXCOORD1;
 float3 Base2:   TEXCOORD2;
 float2 Bump:    TEXCOORD3;
};

//顶点程序
OFFSET_VS_OUT Offset_VS(float4 inPos:   POSITION,     //点坐标
      float3 inNormal: NORMAL,  //法线
       float2 inTex:   TEXCOORD0, //贴图坐标
       float3 inTangent: TANGENT)  //输入参数加上切钱
{
    OFFSET_VS_OUT Out;


    Out.Pos = mul(inPos, mWorldViewProj);  //点转换到屏幕空间

 Out.Height  = inTex;
 Out.Bump  = inTex;


 float3x3 TextureSpace;   //制作该点的切线空间转换矩阵,3x3的,不需要平移等参数,不必4x4的

 TextureSpace[0] = mul(inTangent, mWorld);
 TextureSpace[1] = mul(cross(inTangent, inNormal), mWorld); //除法线,切线外的第三轴,人称Binormal,其实就是法线切线的cross,不需要另准备的
 TextureSpace[2] = mul(inNormal, mWorld);

 //视线转换到切线空间
 float3 Pos = mul(inPos, mWorld);

 float3 eyePos = mul(vEye_Pos, mWorld);

 float3 eyeDir = normalize(eyePos - Pos); //视点-点坐标=视线

 float3 temp = mul(TextureSpace, eyeDir); //视线转换到切线空间

 //这里生成了两个base坐标,传递给ps,计算视线的x偏移及y偏移
 Out.Base1.x = temp.x * vFactors.x;
 Out.Base1.y = temp.x * vFactors.y;
 Out.Base1.z = inTex.x;

 Out.Base2.x = temp.y * vFactors.x;
 Out.Base2.y = temp.y * vFactors.y;
 Out.Base2.z = inTex.y;


 //灯光转换到切线空间
 float3 Light =  mul(vLight_Pos, mWorld);

 float3 LightDir = normalize(Light - Pos);

 Out.Light.xyz = mul(TextureSpace, LightDir) * 0.5 + 0.5; //将正负1规范到0-1

 //法线转换到切线空间
 float3 Normal = mul(inNormal, mWorld);

 Out.Normal = mul(TextureSpace, Normal) * 0.5 + 0.5;  //将正负1规范到0-1

    return Out;
}

//片段程序
float4 Offset_PS(OFFSET_VS_OUT IN): COLOR
{
 float3 height = tex2D(sHeight, IN.Height); //查出高度

//根据高度计算出偏移后的坐标,就是所谓的Parallax
 float2 temp;  
 
 temp.x = dot(height,IN.Base1); //视线的x偏移
 temp.y = dot(height,IN.Base2);  //视线的y偏移

 float4 base = tex2D(sBase, temp);  //取出新位置的贴图

 float3 bumpNormal = 2 * (tex2D(sBump,IN.Bump) - 0.5); //取出老位置的Bump法线

 float4 diff = saturate(dot(IN.Light * 2 - 1,bumpNormal)); //根据入射光与Bump法线夹角,计算出扰动后的亮度

 float shadow = saturate(4 * dot(IN.Normal.xyz * 2 - 1,IN.Light.xyz * 2 - 1)); //由法线和入射光夹角计算出亮度

 return base * diff * shadow;  //颜色,亮度和扰动后的亮度合成,输出
}

technique OffsetBumpMapping
{
 pass Diff
 {
  VertexShader = compile vs_1_1 Offset_VS();
  PixelShader = compile ps_1_1 Offset_PS();
 }
}

3.SelfShadow的Horizon_Mapping

上例中没有用到SelfShadow,水平Map的原理和以前文章的Terrain的阴影原理是一样的,预先根据高度图计算出阴影图,渲染时合成上去.
当灯光位置变化时,重新计算阴影图;当然,也可以计算出不同角度的多张阴影图,使用3D图传进来,通过自动插值得到所需要角度的阴影图
这是来自humus.ca的SelfShadow程序中的一小段

#define round(x) (int) (x + 0.5f)

inline unsigned char Pack1(const float x){
    return (unsigned char) (255.0f * fabsf(x));
}

inline unsigned char Pack2(const float x){
    return (unsigned char) (255.0f * x);
}


void MainApp::createHorizonMap(Image3D *hMap, Image *image, int wt, int ht, int n, float height){
 int w = image->getWidth();
 int h = image->getHeight();
 unsigned char *src = image->getImage();

 unsigned char *dest = new unsigned char[wt * ht * n];

 hMap->loadFromMemory(dest, wt, ht, n, FORMAT_I8, true, false);

 for (int z = 0; z < n; z++){    //按角度分别计算出多幅水平阴影图
  float xv = cosf(z * 2 * PI / n);  //根据角度计算查询方向
  float yv = sinf(z * 2 * PI / n);

  float invMax = 1.0f / max(fabsf(xv), fabsf(yv));
  xv *= invMax;
  yv *= invMax;

  float len = sqrtf(xv * xv + yv * yv);

  for (int y = 0; y < ht; y++){   .//遍历图上所有点
   for (int x = 0; x < wt; x++){
    float maxAng = 0;

    float xp = float(x * w) / wt;
    float yp = float(y * h) / ht;

    float bh = float(src[int(yp) * w + int(xp)]);  //取得高度

    float dist = 0;
    //do {
    for (unsigned int i = 0; i < 256; i++){   //按光线方向查询是否有更高的点,有就是阴影,没有不是阴影
     xp += xv;     //为什么查询256点?
     yp += yv;
     dist += len;

     int xt = round(xp);   //图片越界循环
     int yt = round(yp);

     while (xt < 0) xt += w;
     while (yt < 0) yt += h;

     xt %= w;
     yt %= h;

     //if (xt < 0 || xt >= w || yt < 0 || yt >= h) break;

     float ang = (float(src[yt * w + xt]) - bh) / dist;  //计算投影高度
     if (ang > maxAng) maxAng = ang;
    }
    //} while (true);

    *dest++ = Pack2(sinf(atanf(height * maxAng)));
 nbsp; }
  }
 }
}

在ps中查询也很简单:
 float horizon = tex3D(HorizonMap, float3(newTexCoord, e)); //从3d图查询阴影
其中的e为灯光的方向

- 作者: xmchang 2006年02月17日, 星期五 12:07  回复(0) |  引用(0) 加入博采

软阴影Soft Shadows程序注释

//软阴影Soft Shadows程序注释

//这是一个效果不错的模糊边缘阴影的ShadowMap的Demo,网上能找到中文说明。
//原始程序可在下列地址下载:
http://www.gamedev.net/reference/articles/article2193.asp

/*-----------------------------------------------------------------------------
 Name : Soft Shadows.cpp
 Desc : Main source file.
 Author : Anirudh S Shastry. Copyright (c) 2004.
 Date : 22nd June, 2004.
-----------------------------------------------------------------------------*/

//其原理是,先按灯光位置渲染出ShadowMap,这和其他的一样。
//再从摄影机位置,渲染场景阴影图(只要黑白的阴影图)。阴影图选择屏幕的1/4大小,阴影图比较小,速度比较快。//对阴影图进行模糊处理,这是2D处理,横向一次,纵向一次

//再次绘制场景,把模糊完的阴影图贴上去(当然不是直接贴,而是边绘制变查找屏幕坐标位置的模糊后的阴影值,
//这样,场景就有过度平滑的阴影,避免了ShadowMap锯齿的问题

//这个方法有两个问题,
//一是场景使用了三次渲染(一次ShadowMap,一次阴影,最后一次完整渲染,另外,模糊材料渲染二次),对速度有影响,
//但比较别的方法的64点的查询融合要快。

//另一个问题是模糊算法导致阴影移位,这样,场景渲染可能会把阴影投到错误的位置:应该采取一种不移位的模糊方式,即在阴影范围
//内进行过渡处理,这应该可以做到

//如果做到这两点,应该可以做出效果和速度都比较理想的阴影。

//本注释对程序作了几处优化.
//1.绘制阴影图使用白色背景,这样可以避免把未绘制的空部分当成阴影.
//2.阴影图幅面比场景幅面增加部分,避免模糊处理边缘取不到样.在使用阴影图时校正对准正确区域.

#define STRICT
#define WIN32_LEAN_AND_MEAN

#include <stdio.h>
#include <d3d9.h>
#include <d3dx9.h>
#include <mmsystem.h>
#include "resource.h"

#define SHADOW_MAP_SIZE 512

#define SCREEN_WIDTH 1024
#define SCREEN_HEIGHT 768

#define SAFE_RELEASE( x ) { if( x ) { x->Release(); x = NULL; } }

//
// Gaussian functions
//
//模糊处理的参数计算
float GetGaussianDistribution( float x, float y, float rho ) {
    float g = 1.0f / sqrt( 2.0f * 3.141592654f * rho * rho );
    return g * exp( -(x * x + y * y) / (2 * rho * rho) );
}

void GetGaussianOffsets( bool bHorizontal, D3DXVECTOR2 vViewportTexelSize,
       D3DXVECTOR2* vSampleOffsets, float* fSampleWeights ) {
    // Get the center texel offset and weight
    fSampleWeights[0] = 1.0f * GetGaussianDistribution( 0, 0, 2.0f );
    vSampleOffsets[0] = D3DXVECTOR2( 0.0f, 0.0f );
   
 // Get the offsets and weights for the remaining taps
 if( bHorizontal ) {
  for( int i = 1; i < 15; i += 2 ) {
   vSampleOffsets[i + 0] = D3DXVECTOR2(  i * vViewportTexelSize.x, 0.0f );
   vSampleOffsets[i + 1] = D3DXVECTOR2( -i * vViewportTexelSize.x, 0.0f );

   fSampleWeights[i + 0] = 2.0f * GetGaussianDistribution( float(i + 0), 0.0f, 3.0f );
   fSampleWeights[i + 1] = 2.0f * GetGaussianDistribution( float(i + 1), 0.0f, 3.0f );
  }
 }

 else {
  for( int i = 1; i < 15; i += 2 ) {
   vSampleOffsets[i + 0] = D3DXVECTOR2( 0.0f,  i * vViewportTexelSize.y );
   vSampleOffsets[i + 1] = D3DXVECTOR2( 0.0f, -i * vViewportTexelSize.y );
   
   fSampleWeights[i + 0] = 2.0f * GetGaussianDistribution( 0.0f, float(i + 0), 3.0f );
   fSampleWeights[i + 1] = 2.0f * GetGaussianDistribution( 0.0f, float(i + 1), 3.0f );
  }
 }
}

//-----------------------------------------------------------------------------
//  一些变量设置   Global variables
//-----------------------------------------------------------------------------
HWND    g_hWnd    = NULL;
LPDIRECT3D9   g_pD3D    = NULL;
LPDIRECT3DDEVICE9 g_pd3dDevice  = NULL;

LPD3DXFONT   g_pFont    = NULL;

LPD3DXMESH   g_pScene   = NULL;
LPD3DXEFFECT  g_pEffect   = NULL;

LPDIRECT3DVERTEXBUFFER9 g_pQuadVB  = NULL;

LPDIRECT3DTEXTURE9 g_pColorMap_Floor = NULL;
LPDIRECT3DTEXTURE9 g_pColorMap_Statue = NULL;
LPDIRECT3DTEXTURE9 g_pSpotMap   = NULL;

LPDIRECT3DTEXTURE9 g_pShadowMap  = NULL;
LPDIRECT3DSURFACE9 g_pShadowSurf  = NULL;
LPDIRECT3DSURFACE9 g_pShadowDepth  = NULL;

LPDIRECT3DTEXTURE9 g_pScreenMap  = NULL;
LPDIRECT3DSURFACE9 g_pScreenSurf  = NULL;

LPDIRECT3DTEXTURE9 g_pBlurMap[2]  = {NULL};
LPDIRECT3DSURFACE9 g_pBlurSurf[2]  = {NULL};

LPDIRECT3DSURFACE9 g_pNewDepthRT  = NULL;
LPDIRECT3DSURFACE9 g_pOldColorRT  = NULL;
LPDIRECT3DSURFACE9 g_pOldDepthRT  = NULL;

D3DXVECTOR2   g_vSampleOffsets[15];
FLOAT    g_fSampleWeights[15];

LPDIRECT3DSURFACE9 g_pScreenshot  = NULL;
UINT    g_uNumScreenshots = 0;

UINT    g_uFrameCount  = 0;
FLOAT    g_fStartTime  = 0.0f;
FLOAT    g_fFramerate  = 0.0f;
FLOAT    g_fStopTime   = 0.0f;
BOOL    g_bDisplayStats  = TRUE;

D3DXVECTOR3   g_vEyePos   = D3DXVECTOR3( -20.0f, 20.0f, -20.0f );
D3DXVECTOR3   g_vEyeAim   = D3DXVECTOR3(  20.0f,  0.0f,  20.0f );
D3DXVECTOR3   g_vUp    = D3DXVECTOR3(   0.0f,nbsp; 1.0f,   0.0f );

BOOL    g_bWindowed   = FALSE;
BOOL    g_bWireframe  = FALSE;
BOOL    g_bSoftShadows  = TRUE;
BOOL    g_bFiltered   = TRUE;

// Vertex element
D3DVERTEXELEMENT9 dwElement[] =
{
 { 0,   0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },
 
 { 0,  12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,   0 },
 
 { 0,  24, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 },
 
 D3DDECL_END()
};

struct QuadVertex  
{
 D3DXVECTOR4 p;
 D3DXVECTOR2 t;
};

#define FVF_QUADVERTEX (D3DFVF_XYZRHW | D3DFVF_TEX1)

//-----------------------------------------------------------------------------
// 主程序入口 Functions
//-----------------------------------------------------------------------------
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine, int nCmdShow);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
HRESULT Initialize();
HRESULT ShutDown();
HRESULT Render();
HRESULT FrameMove();
HRESULT ScreenGrab();

//-----------------------------------------------------------------------------
// Name: WinMain()
// Desc: The application's entry point
//-----------------------------------------------------------------------------
int WINAPI WinMain( HINSTANCE hInstance,
                    HINSTANCE hPrevInstance,
                    LPSTR     lpCmdLine,
                    int       nCmdShow )
{
 WNDCLASSEX winClass;
 MSG        uMsg;

    memset(&uMsg,0,sizeof(uMsg));

 winClass.lpszClassName = "MY_WINDOWS_CLASS";
 winClass.cbSize        = sizeof(WNDCLASSEX);
 winClass.style         = CS_HREDRAW | CS_VREDRAW;
 winClass.lpfnWndProc   = WindowProc;
 winClass.hInstance     = hInstance;
 winClass.hIcon        = LoadIcon(hInstance, (LPCTSTR)IDI_ICON1);
    winClass.hIconSm    = LoadIcon(hInstance, (LPCTSTR)IDI_ICON1);
 winClass.hCursor       = LoadCursor(NULL, IDC_ARROW);
 winClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
 winClass.lpszMenuName  = (LPCSTR)IDR_MENU1;
 winClass.cbClsExtra    = 0;
 winClass.cbWndExtra    = 0;

 if( RegisterClassEx( &winClass) == 0 )
  return E_FAIL;

 if( g_bWindowed )
  g_hWnd = CreateWindowEx( NULL, "MY_WINDOWS_CLASS", "Soft Shadows", WS_OVERLAPPEDWINDOW | WS_VISIBLE,
         0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, NULL, NULL, hInstance, NULL );
 else
  g_hWnd = CreateWindowEx( NULL, "MY_WINDOWS_CLASS", "Soft Shadows", WS_POPUP | WS_SYSMENU | WS_VISIBLE,
         0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, NULL, NULL, hInstance, NULL );

 if( g_hWnd == NULL )
  return E_FAIL;

    ShowWindow( g_hWnd, nCmdShow );
    UpdateWindow( g_hWnd );

 if( FAILED( Initialize() ) )
 {
  MessageBox( g_hWnd, "Unable to initialize Direct3D!", ERROR, MB_OK | MB_ICONERROR );
  return E_FAIL;
 }

 // Get the starting time
 g_fStartTime = timeGetTime() * 0.001f;

 while( uMsg.message != WM_QUIT )
 {
  if( PeekMessage( &uMsg, NULL, 0, 0, PM_REMOVE ) )
  {
   TranslateMessage( &uMsg );
   DispatchMessage( &uMsg );
  }
        else
  {
      FrameMove();
   Render();
   g_uFrameCount++;
   g_fFramerate = (FLOAT)g_uFrameCount / (timeGetTime() * 0.001f - g_fStartTime);
  }
 }
 
 // Get the stopping time
 g_fStopTime = timeGetTime() * 0.001f;

 if( FAILED( ShutDown() ) )
 {
  MessageBox( g_hWnd, "Unable to shutdown Direct3D!", ERROR, MB_OK | MB_ICONERROR );
  return E_FAIL;
 }  

    UnregisterClass( "MY_WINDOWS_CLASS", winClass.hInstance );

 return uMsg.wParam;
}

//-----------------------------------------------------------------------------
// Name: WindowProc()
// Desc: The window's message handler
//-----------------------------------------------------------------------------
LRESULT CALLBACK WindowProc( HWND   hWnd,
        UINT   msg,
        WPARAM wParam,
        LPARAM lParam )
{
    switch( msg )
 {
        case WM_KEYDOWN:
  {
   switch( wParam )
   {
    case VK_ESCAPE:
     PostQuitMessage(0);
     break;

    case VK_UP:
    {
     D3DXVECTOR3 vView;
     D3DXVec3Normalize( &vView, &(g_vEyeAim - g_vEyePos) );
     g_vEyePos.x += vView.x;
     g_vEyePos.z += vView.z;
     g_vEyeAim.x += vView.x;
     g_vEyeAim.z += vView.z;
     break;
    }
    
    case VK_DOWN:
    { 
     D3DXVECTOR3 vView;
     D3DXVec3Normalize( &vView, &(g_vEyeAim - g_vEyePos) );
     g_vEyePos.x -= vView.x;
     g_vEyePos.z -= vView.z;
     g_vEyeAim.x -= vView.x;
     g_vEyeAim.z -= vView.z;
     break;
    }

    case VK_LEFT:
    { 
     D3DXVECTOR3 vView, vStrafe;
     D3DXVec3Normalize( &vView, &(g_vEyeAim - g_vEyePos) );
     D3DXVec3Cross( &vStrafe, &vView, &g_vUp );
     g_vEyePos.x += vStrafe.x;
     g_vEyePos.z += vStrafe.z;
     g_vEyeAim.x += vStrafe.x;
     g_vEyeAim.z += vStrafe.z;
     break;
    }
    
    case VK_RIGHT:
    { 
     D3DXVECTOR3 vView, vStrafe;
     D3DXVec3Normalize( &vView, &(g_vEyeAim - g_vEyePos) );
     D3DXVec3Cross( &vStrafe, &vView, &g_vUp );
     g_vEyePos.x -= vStrafe.x;
     g_vEyePos.z -= vStrafe.z;
     g_vEyeAim.x -= vStrafe.x;
     g_vEyeAim.z -= vStrafe.z;
     break;
    }

    case 'Z':
    {
     g_vEyePos.y += 1.0f;
     break;
    }
    
    case 'X':
    {
     g_vEyePos.y -= 1.0f;
     break;
    }

    case 'W':
    {
     g_bWireframe = !g_bWireframe;
     break;
    }

    case 'S':
    {
     g_bSoftShadows = !g_bSoftShadows;
     break;
    }
    
    case 'F':
    {
     g_bFiltered = !g_bFiltered;
     break;
    }

    case 'G':
    {
     ScreenGrab();
     break;
    }

    case VK_F1:
    {
     g_bDisplayStats = !g_bDisplayStats;
    }

   }
  }
        break;

  case WM_CLOSE:
  {
   MessageBox( g_hWnd, "By Anirudh S Shastry. Copyright (c) 2004.", "About", MB_OK );
   PostQuitMessage(0);
  }
  
        case WM_DESTROY:
  {
            PostQuitMessage(0);
  }
        break;
       
  case WM_COMMAND:
  {
   switch( LOWORD(wParam) )
            {
    case ID_FILE_EXIT:
    {
                    SendMessage( g_hWnd, WM_CLOSE, 0, 0 );
                    return 0;
    }
     
    case ID_HELP_ABOUT:
    {
     MessageBox( g_hWnd, "By Anirudh S Shastry. Copyright (c) 2004.", "About", MB_OK );
                    return 0;
    }

    case ID_FILE_TOGGLESTATS:
    {
     g_bDisplayStats = !g_bDisplayStats;
     return 0;
    }
     
    default:
    {
     break;
    }
   }
   break;
  }

  default:
  {
   return DefWindowProc( hWnd, msg, wParam, lParam );
  }
  break;
 }

 return 0;
}

//-----------------------------------------------------------------------------
// Name: Initialize()
// Desc: Initialize Direct3D
//-----------------------------------------------------------------------------
HRESULT Initialize()
{
    // Create the Direct3D object
 g_pD3D = Direct3DCreate9( D3D_SDK_VERSION );

 if( g_pD3D == NULL )
 {
  MessageBox( g_hWnd, "Unable to create the Direct3D object!", ERROR, MB_OK | MB_ICONERROR );
  return E_FAIL;
 }

    // Display mode
 D3DDISPLAYMODE d3ddm;

    // Get the suitable display mode
 UINT uNumModes = g_pD3D->GetAdapterModeCount( D3DADAPTER_DEFAULT, D3DFMT_X8R8G8B8 );
   
 // Select the best mode (offering the highest frequency at the specified resolution)
 D3DDISPLAYMODE* pModes = new D3DDISPLAYMODE[uNumModes];
 for( UINT uMode = 0; uMode < uNumModes; uMode++ )
 {
  // Enumerate the adapter modes
  if( FAILED( g_pD3D->EnumAdapterModes( D3DADAPTER_DEFAULT, D3DFMT_X8R8G8B8, uMode, &pModes[uMode] ) ) )
  {
   MessageBox( g_hWnd, "Unable to enumerate adapter display mode!", ERROR, MB_OK | MB_ICONERROR );
   return E_FAIL;
  } 
 }
 
 for( uMode = 0; uMode < uNumModes; uMode++ )
 {
  if( pModes[uMode].Width == SCREEN_WIDTH && pModes[uMode].Height == SCREEN_HEIGHT )
  {
   if( pModes[uMode + 1].Width == SCREEN_WIDTH && pModes[uMode + 1].Height == SCREEN_HEIGHT )
   {
    if( pModes[uMode].RefreshRate > pModes[uMode + 1].RefreshRate )
    {
     d3ddm = pModes[uMode];
    }
    else
    {
     d3ddm = pModes[uMode + 1];
    }
   }
   else
   {
    d3ddm = pModes[uMode];
   }
  }

  else if( pModes[uMode].Width > SCREEN_WIDTH || pModes[uMode].Height > SCREEN_HEIGHT ) {
   break;
  }
 }

 // Delete the list
 delete [] pModes;

 if( FAILED( g_pD3D->CheckDeviceFormat( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
             d3ddm.Format, D3DUSAGE_DEPTHSTENCIL,
             D3DRTYPE_SURFACE, D3DFMT_D16 ) ) )
 {
  MessageBox( g_hWnd, "Unable to find suitable display mode!", ERROR, MB_OK | MB_ICONERROR );
  return E_FAIL;
 }

 // If we are using windowed mode
 if( g_bWindowed )
  g_pD3D->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &d3ddm );

 if( FAILED( g_pD3D->CheckDeviceFormat( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
             d3ddm.Format, D3DUSAGE_DEPTHSTENCIL,
             D3DRTYPE_SURFACE, D3DFMT_D16 ) ) )
 {
  MessageBox( g_hWnd, "Unable to get adapter display mode!", ERROR, MB_OK | MB_ICONERROR );
  retrn E_FAIL;
 }

 // Get the hardware caps
 D3DCAPS9 d3dCaps;

 if( FAILED( g_pD3D->GetDeviceCaps( D3DADAPTER_DEFAULT,
                                 D3DDEVTYPE_HAL, &d3dCaps ) ) )
 {
  MessageBox( g_hWnd, "Unable to get hardware caps!", ERROR, MB_OK | MB_ICONERROR );
  return E_FAIL;
 }

 //需要shader2.0 支持
 // Check for vertex/pixel shaders 2.0 support
 if( d3dCaps.VertexShaderVersion < D3DVS_VERSION( 2, 0 ) || d3dCaps.PixelShaderVersion < D3DPS_VERSION( 2, 0 ) )
 {
  MessageBox( g_hWnd, "Vertex/Pixel shaders 2.0 not supported!", ERROR, MB_OK | MB_ICONERROR );
  return E_FAIL;
 }

 //使用32位图象格式,以提高深度测试精度
 // Check for R32F surface format support
 if( FAILED( g_pD3D->CheckDepthStencilMatch( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, d3ddm.Format,
            D3DFMT_R32F, D3DFMT_D16 ) ) )
 {
  MessageBox( g_hWnd, "R32F format not supported!", ERROR, MB_OK | MB_ICONERROR );
  return E_FAIL;
 }

 DWORD dwBehaviorFlags = 0;

 if( d3dCaps.VertexProcessingCaps != 0 )
  dwBehaviorFlags = D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_PUREDEVICE;
 else
  dwBehaviorFlags = D3DCREATE_SOFTWARE_VERTEXPROCESSING;

 D3DPRESENT_PARAMETERS d3dpp;
 memset(&d3dpp, 0, sizeof(d3dpp));

 d3dpp.BackBufferWidth   = d3ddm.Width;
 d3dpp.BackBufferHeight   = d3ddm.Height;
 d3dpp.BackBufferFormat   = d3ddm.Format;
 d3dpp.BackBufferCount   = 1;
 
 d3dpp.MultiSampleType   = D3DMULTISAMPLE_NONE;
 d3dpp.SwapEffect    = D3DSWAPEFFECT_DISCARD;

 d3dpp.hDeviceWindow    = g_hWnd;
 d3dpp.Windowed     = g_bWindowed;

 d3dpp.EnableAutoDepthStencil = TRUE;
 d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
 d3dpp.Flags      = D3DPRESENTFLAG_DISCARD_DEPTHSTENCIL;

 if( g_bWindowed )
 {
  d3dpp.FullScreen_RefreshRateInHz = 0;
  d3dpp.PresentationInterval   = D3DPRESENT_INTERVAL_IMMEDIATE;
 }

 else
 {
  d3dpp.FullScreen_RefreshRateInHz = d3ddm.RefreshRate;
  d3dpp.PresentationInterval  = D3DPRESENT_INTERVAL_ONE;
 }

    if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, g_hWnd,
                                      dwBehaviorFlags, &d3dpp, &g_pd3dDevice ) ) )
 {
  MessageBox( g_hWnd, "Unable to create the Direct3D device!", ERROR, MB_OK | MB_ICONERROR );
  return E_FAIL;
 }

 // Create the font object
    if( FAILED( D3DXCreateFont( g_pd3dDevice, 16, 0, FW_BOLD, 1, false, DEFAULT_CHARSET,
        OUT_TT_ONLY_PRECIS, 0, 0, "Veranda", &g_pFont ) ) )
 {
  MessageBox( g_hWnd, "Unable to create the font object!", ERROR, MB_OK | MB_ICONERROR );
  return E_FAIL;
 }

 // Load the scene
 //载入女神雕象模型
 LPD3DXMESH pTempScene = NULL;
 if( FAILED( D3DXLoadMeshFromX( "Media/Soft Shadows.x", D3DXMESH_32BIT, g_pd3dDevice,
         NULL, NULL, NULL, NULL, &pTempScene ) ) )
 {
  MessageBox( g_hWnd, "Unable to load Soft Shadows.x", "Error", MB_OK | MB_ICONERROR );
  return E_FAIL;
 }
 //复制为32位格式
 pTempScene->CloneMesh( D3DXMESH_32BIT, dwElement, g_pd3dDevice, &g_pScene );

 SAFE_RELEASE( pTempScene );

 // Compute the normals and tangents for the scene
 D3DXComputeNormals( g_pScene, NULL );  //计算模型法线

 // Create the effect
 //效果文件为 Media/Soft Shadows.fx
 LPD3DXBUFFER pBufferErrors = NULL;
 if( FAILED( D3DXCreateEffectFromFile( g_pd3dDevice, "Media/Soft Shadows.fx", NULL, NULL, 0,
            NULL, &g_pEffect, &pBufferErrors ) ) )
 {
  LPVOID pErrors = pBufferErrors->GetBufferPointer();
  MessageBox(NULL, (const char*)pErrors, ERROR, MB_OK | MB_ICONERROR );
  return E_FAIL;
 }

 SAFE_RELEASE( pBufferErrors );
 
 // Create the quad vertex buffer
 //建立一个方片,做模糊绘制用
 if( FAILED( g_pd3dDevice->CreateVertexBuffer( 4 * sizeof(QuadVertex), D3DUSAGE_WRITEONLY, FVF_QUADVERTEX,
              D3DPOOL_DEFAULT, &g_pQuadVB, NULL ) ) )
 {
  MessageBox( g_hWnd, "Unable to create vertex buffer!", ERROR, MB_OK | MB_ICONERROR );
  return E_FAIL;
 }

 // Copy the vertices
 QuadVertex* pVertices;

 //方片为屏幕大小
 g_pQuadVB->Lock( 0, 0, (void**)&pVertices, 0 );
 pVertices[0].p = D3DXVECTOR4( 0.0f, 0.0f, 0.0f, 1.0f );
 pVertices[1].p = D3DXVECTOR4( 0.0f, SCREEN_HEIGHT / 2, 0.0f, 1.0f );
 pVertices[2].p = D3DXVECTOR4( SCREEN_WIDTH / 2, 0.0f, 0.0f, 1.0f );
 pVertices[3].p = D3DXVECTOR4( SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, 0.0f, 1.0f );
 
 pVertices[0].t = D3DXVECTOR2( 0.0f, 0.0f );
 pVertices[1].t = D3DXVECTOR2( 0.0f, 1.0f );
 pVertices[2].t = D3DXVECTOR2( 1.0f, 0.0f );
 pVertices[3].t = D3DXVECTOR2( 1.0f, 1.0f );
 g_pQuadVB->Unlock();

 // Load the color and spot maps
 //地板材料
 if( FAILED( D3DXCreateTextureFromFile( g_pd3dDevice, "Media/Color_Floor.dds", &g_pColorMap_Floor ) ) )
 {
  MessageBox( g_hWnd, "Unable to load Color_Floor.dds", ERROR, MB_OK | MB_ICONERROR );
  return E_FAIL;
 }
 //模型材料 
 if( FAILED( D3DXCreateTextureFromFile( g_pd3dDevice, "Media/Color_Statue.dds", &g_pColorMap_Statue ) ) )
 {
  MessageBox( g_hWnd, "Unable to load Color_Statue.dds", ERROR, MB_OK | MB_ICONERROR );
  return E_FAIL;
 }
 //灯光光罩图象 
 if( FAILED( D3DXCreateTextureFromFile( g_pd3dDevice, "Media/SpotMap.dds", &g_pSpotMap ) ) )
 {
  MessageBox( g_hWnd, "Unable to load SpotMap.dds", ERROR, MB_OK | MB_ICONERROR );
  return E_FAIL;
 }

 // Create the shadow map,这里建立的阴影图像材料,为32位格式的,以提高精度
 if( FAILED( g_pd3dDevice->CreateTexture( SHADOW_MAP_SIZE, SHADOW_MAP_SIZE, 1, D3DUSAGE_RENDERTARGET,
//            D3DFMT_D32F_LOCKABLE,
            D3DFMT_R32F,
            D3DPOOL_DEFAULT, &g_pShadowMap, NULL ) ) )
 {
  MessageBox g_hWnd, "Unable to create shadow map!", ERROR, MB_OK | MB_ICONERROR );
  return E_FAIL;
 }

 // Grab the texture's surface
 g_pShadowMap->GetSurfaceLevel( 0, &g_pShadowSurf );

 // Create the screen-sized buffer map
 //建立一个屏幕大小的材料,供渲染阴影图使用
 if( FAILED( g_pd3dDevice->CreateTexture( SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, 1, D3DUSAGE_RENDERTARGET,
            D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &g_pScreenMap, NULL ) ) )
 {
  MessageBox( g_hWnd, "Unable to create screen map!", ERROR, MB_OK | MB_ICONERROR );
  return E_FAIL;
 }

 // Grab the texture's surface
 g_pScreenMap->GetSurfaceLevel( 0, & g_pScreenSurf );

 //建立两个屏幕大小的材料,作水平垂直两次模糊阴影处理
 //其实并不需要屏幕大小,512x512足够,Linear拉伸会自动平滑处理,
 // Create the blur maps
 for( int i = 0; i < 2; i++ )
 {
  if( FAILED( g_pd3dDevice->CreateTexture( SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, 1, D3DUSAGE_RENDERTARGET,
             D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &g_pBlurMap[i], NULL ) ) )
  {
   MessageBox( g_hWnd, "Unable to create blur map!", ERROR, MB_OK | MB_ICONERROR );
   return E_FAIL;
  }

  // Grab the texture's surface
  g_pBlurMap[i]->GetSurfaceLevel( 0, & g_pBlurSurf[i] );
 }
 
 //建立渲染阴影的深度缓存
 // Create the shadow depth surface
 if( FAILED( g_pd3dDevice->CreateDepthStencilSurface( SHADOW_MAP_SIZE, SHADOW_MAP_SIZE, D3DFMT_D16,
               D3DMULTISAMPLE_NONE, 0, TRUE, &g_pShadowDepth, NULL ) ) )
 {
  MessageBox( g_hWnd, "Unable to create shadow depth surface!", ERROR, MB_OK | MB_ICONERROR );
  return E_FAIL;
 }

 //建立屏幕大小的的深度缓存,供模糊处理用
 // Create the general depth surface
 if( FAILED( g_pd3dDevice->CreateDepthStencilSurface( SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, D3DFMT_D16,
               D3DMULTISAMPLE_NONE, 0, TRUE, &g_pNewDepthRT, NULL ) ) )
 {
  MessageBox( g_hWnd, "Unable to create general depth surface!", ERROR, MB_OK | MB_ICONERROR );
  return E_FAIL;
 }

 return S_OK;
}

//-----------------------------------------------------------------------------
// Name: ShutDown()
// Desc: ShutDown Direct3D
//-----------------------------------------------------------------------------

//结束处理
HRESULT ShutDown()
{
 // Clean up
 SAFE_RELEASE( g_pFont );

 SAFE_RELEASE( g_pScene );
 SAFE_RELEASE( g_pEffect );

 SAFE_RELEASE( g_pColorMap_Floor );
 SAFE_RELEASE( g_pColorMap_Statue );
 SAFE_RELEASE( g_pSpotMap );
 
 SAFE_RELEASE( g_pShadowMap );
 SAFE_RELEASE( g_pShadowSurf );
 SAFE_RELEASE( g_pShadowDepth );

 SAFE_RELEASE( g_pScreenMap );
 SAFE_RELEASE( g_pScreenSurf );

 SAFE_RELEASE( g_pBlurMap[0] );
 SAFE_RELEASE( g_pBlurSurf[0] );
 SAFE_RELEASE( g_pBlurMap[1] );
 SAFE_RELEASE( g_pBlurSurf[1] );

 SAFE_RELEASE( g_pNewDepthRT );
 SAFE_RELEASE( g_pOldColorRT );
 SAFE_RELEASE( g_pOldDepthRT );

 SAFE_RELEASE( g_pd3dDevice );
 SAFE_RELEASE( g_pD3D );
 
 return S_OK;
}

//-----------------------------------------------------------------------------
// Name: FrameMove()
// Desc: Setup effect variables and update stuff
//-----------------------------------------------------------------------------

//场景移动处理,没有什么要说的

HRESULT FrameMove()
{
 //
 // Camera space matrices
 //
 
 // Computee the world matrix
 D3DXMATRIX matWorld;
 D3DXMatrixIdentity( &matWorld );

 // Compute the view matrix
 D3DXMATRIX matView;
 D3DXMatrixLookAtLH( &matView, &g_vEyePos, &g_vEyeAim, &g_vUp );

 // Compute the projection matrix
 D3DXMATRIX matProj;
 D3DXMatrixPerspectiveFovLH( &matProj, D3DXToRadian(60.0f), (FLOAT)SCREEN_WIDTH / (FLOAT)SCREEN_HEIGHT, 1.0f, 1024.0f );

 // Compute the world-view-projection matrix
 D3DXMATRIX matWorldViewProj = matWorld * matView * matProj;

 // World inverse matrix
 D3DXMATRIX matWorldIT;
 D3DXMatrixInverse( &matWorldIT, NULL, &matWorld );
 D3DXMatrixTranspose( &matWorldIT, &matWorldIT );

 //
 // Light space matrices
 //
 
 // View matrix
 D3DXVECTOR3 vLightPos = D3DXVECTOR3(  40.0f, 40.0f, -40.0f );
 D3DXVECTOR3 vLightAim = D3DXVECTOR3(   0.0f,  0.0f,   0.0f );

 D3DXMatrixLookAtLH( &matView, &vLightPos, &vLightAim, &g_vUp );

 // Compute the projection matrix
 D3DXMatrixOrthoLH( &matProj, 45.0f, 45.0f, 1.0f, 1024.0f );

 // Compute the light-view-projection matrix
 D3DXMATRIX matLightViewProj = matWorld * matView * matProj;
 
 // Compute the texture matrix
 float fTexOffs = 0.5 + (0.5 / (float)SHADOW_MAP_SIZE);
 D3DXMATRIX matTexAdj( 0.5f,  0.0f, 0.0f, 0.0f,
        0.0f,    -0.5f, 0.0f, 0.0f,
        0.0f,  0.0f, 1.0f, 0.0f,
        fTexOffs, fTexOffs,  0.0f, 1.0f );

 D3DXMATRIX matTexture = matLightViewProj * matTexAdj;

 // Setup the effect variables
 g_pEffect->SetMatrix( "g_matWorldViewProj", &matWorldViewProj );
 g_pEffect->SetMatrix( "g_matLightViewProj", &matLightViewProj );
 g_pEffect->SetMatrix( "g_matWorld", &matWorld );
 g_pEffect->SetMatrix( "g_matWorldIT", &matWorldIT );
 g_pEffect->SetMatrix( "g_matTexture", &matTexture );

 g_pEffect->SetVector( "g_vLightPos", (D3DXVECTOR4*)&vLightPos );
 g_pEffect->SetVector( "g_vEyePos", (D3DXVECTOR4*)&g_vEyePos );
 g_pEffect->SetVector( "g_vLightColor", &D3DXVECTOR4( 1.0f, 1.0f, 1.0f, 0.5f ) );

 g_pEffect->SetBool("g_bFiltered", g_bFiltered);
 
 return S_OK;
}

//-----------------------------------------------------------------------------
// Name: Render()
// Desc: Render the frame
//-----------------------------------------------------------------------------

//渲染场景
HRESULT Render()
{
    // Clear the viewport
 g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x00000000, 1.0f, 0 );

 // Begin rendering the scene
    if( SUCCEEDED( g_pd3dDevice->BeginScene() ) )
 {
  // Grab the old render target and depth buffer
  g_pd3dDevice->GetRenderTarget( 0, &g_pOldColorRT );
  g_pd3dDevice->GetDepthStencilSurface( &g_pOldDepthRT );
   
  //首先渲染ShadowMap深度图,注意,该FX把深度图计算到32位的前景里,不是深度缓存

  // Render the scenedepth to the shadow map
  g_pd3dDevice->SetRenderTarget( 0, g_pShadowSurf );
  g_pd3dDevice->SetDepthStencilSurface( g_pShadowDepth );
  
  // Clear the viewport
  g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xFFFFFFFF, 1.0f, 0 );

  // Set the technique
  g_pEffect->SetTechnique( "techShadow" );

  // Render the effect
  UINT uPasses = 0;
  g_pEffect->Begin( &uPasses, 0 );

  for( UINT uPass = 0; uPass < uPasses; uPass++ )
  {
   // Set the current pass
   g_pEffect->BeginPass( uPass );

   // Draw the floor
   g_pScene->DrawSubset(0);
   
   // Draw the statue
   g_pScene->DrawSubset(1);
   g_pScene->DrawSubset(2);

   // End the current pass
   g_pEffect->EndPass();
  }

  // End the effect
  g_pEffect->End();
  

  //阴影图渲染完成

  //第二步,渲染场景,产生阴影图
  if( g_bSoftShadows )
  {
   // Render the shadowed scene into the screen map
   g_pd3dDevice->SetRenderTarget( 0, g_pScreenSurf );
   g_pd3dDevice->SetDepthStencilSurface( g_pNewDepthRT );
   
   // Clear the viewport
   //这里清除背景原使用0,应该使用白色,避免把空出的部分当成阴影!
   g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xffffffff, 1.0f, 0 );

   // Set the technique
   g_pEffect->SetTechnique( "techUnlit" );  //这个Tech只渲染阴影
   
   // Set the textures
   g_pEffect->SetTexture( "tShadowMap", g_pShadowMap ); //将阴影map传进去

   // Render the effect
   uPasses = 0;
   g_pEffect->Begin( &uPasses, 0 );

   for( uPass = 0; uPass < uPasses; uPass++ )
   {
    // Set the current pass
    g_pEffect->BeginPass( uPass );

    // Draw the floor
    g_pScene->DrawSubset(0);
    
    // Draw the statue
    g_pScene->DrawSubset(1);
    g_pScene->DrawSubset(2);

    // End the current pass
    g_pEffect->EndPass();
   }

   // End the effect
   g_pEffect->End();


   //阴影图渲染完成

   //第三步,对阴影图进行模糊处理,绘制到另一个材料上,包括水平及垂直两次模糊

   //水平模糊处理
   // Blur the screen map horizontally
   g_pd3dDevice->SetRenderTarget( 0, g_pBlurSurf[0] );  //模糊到g_pBlurSurf[0]上
   g_pd3dDevice->SetDepthStencilSurface( g_pNewDepthRT ); //深度缓存重复利用
   
   // Clear the viewport
   g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x00000000, 1.0f, 0 );

   // Set the technique
   g_pEffect->SetTechnique( "techBlurH" );  //这是模糊的tech
   
   // Compute the Gaussian offsets 计算模糊的距离
   GetGaussianOffsets(TRUE, D3DXVECTOR2(1.0f / (FLOAT)SHADOW_MAP_SIZE, 1.0f / (FLOAT)SHADOW_MAP_SIZE),
          g_vSampleOffsets, g_fSampleWeights);
   g_pEffect->SetValue("g_vSampleOffsets", g_vSampleOffsets, 15 * sizeof(D3DXVECTOR2));
   g_pEffect->SetValue("g_fSampleWeights", g_fSampleWeights, 15 * sizeof(FLOAT));

   // Set the textures
   g_pEffect->SetTexture( "tScreenMap", g_pScreenMap ); //设置刚才的阴影图作为原图

   // Render the effect
   uPasses = 0;
   g_pEffect->Begin( &uPasses, 0 );

   for( uPass = 0; uPass < uPasses; uPass++ )
   {
    // Set the current pass
    g_pEffect->BeginPass( uPass );

    // Draw the quad
    g_pd3dDevice->SetStreamSource( 0, g_pQuadVB, 0, sizeof(QuadVertex) );
    g_pd3dDevice->SetFVF( FVF_QUADVERTEX );
    g_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP, 0, 2 );  //绘制矩形,FX负责模糊
    
    // End the current pass
    g_pEffect->EndPass();
   }

   // End the effect
   g_pEffect->End();

   //下面做纵向模糊
   // Blur the screen map vertically
   g_pd3dDevice->SetRenderTarget( 0, g_pBlurSurf[1] ); //模糊到材料2
   g_pd3dDevice->SetDepthStencilSurface( g_pNewDepthRT );
   
   // Clear the viewport
   g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x00000000, 1.0f, 0 );

   // Set the technique
   g_pEffect->SetTechnique( "techBlurV" ); //使用刚才水平模糊的结果,继续模糊
   
   // Compute the Gaussian offsets
   GetGaussianOffsets(FALSE, D3DXVECTOR2(1.0f / (FLOAT)SHADOW_MAP_SIZE, 1.0f / (FLOAT)SHADOW_MAP_SIZE),
          g_vSampleOffsets, g_fSampleWeights);
   g_pEffect->SetValue("g_vSampleOffsets", g_vSampleOffsets, 15 * sizeof(D3DXVECTOR2));
   g_pEffect->SetValue("g_fSampleWeights", g_fSampleWeights, 15 * sizeof(FLOAT));

   // Set the textures
   g_pEffect->SetTexture( "tBlurHMap", g_pBlurMap[0] );

   // Render the effect
   uPasses = 0;
   g_pEffect->Begin( &uPasses, 0 );

   for( uPass = 0; uPass < uPasses; uPass++ )
   {
    // Set the current pass
    g_pEffect->BeginPass( uPass );

    // Draw the quad
    g_pd3dDevice->SetStreamSource( 0, g_pQuadVB, 0, sizeof(QuadVertex) );
    g_pd3dDevice->SetFVF( FVF_QUADVERTEX );
    g_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP, 0, 2 ); //也是绘制一个方形

    // End the current pass
    g_pEffect->EndPass();
   }

   // End the effect
   g_pEffect->End();

   //阴影图绘制完成

   //最后,第四步,绘制场景,PS根据绘点的位置,查询阴影图同位置的颜色,将模糊完的阴影复合进来

   // Restore the render target and depth buffer
   g_pd3dDevice->SetRenderTarget( 0, g_pOldColorRT );
   g_pd3dDevice->SetDepthStencilSurface( g_pOldDepthRT );
   SAFE_RELEASE( g_pOldColorRT );
nbsp;  SAFE_RELEASE( g_pOldDepthRT );

   // Finally, render the shadowed scene
   g_pEffect->SetTechnique( "techScene" );
   
   // Set the textures
   g_pEffect->SetTexture( "tBlurVMap", g_pBlurMap[1] ); //使用两次模糊的最后结果
   g_pEffect->SetTexture( "tSpotMap", g_pSpotMap );  //加灯光光罩

   g_pEffect->SetTexture( "tScreenMap", g_pScreenMap ); //设置刚才的阴影图作为原图

   // Render the effect
   uPasses = 0;
   g_pEffect->Begin( &uPasses, 0 );

   // Do we need to render the scene in wireframe mode
   if( g_bWireframe )
    g_pd3dDevice->SetRenderState( D3DRS_FILLMODE, D3DFILL_WIREFRAME );

   for( uPass = 0; uPass < uPasses; uPass++ )
   {
    // Set the current pass
    g_pEffect->BeginPass( uPass );

    // Draw the floor
    g_pEffect->SetTexture( "tColorMap", g_pColorMap_Floor );
    g_pEffect->CommitChanges();
    g_pScene->DrawSubset(0);
    
    // Draw the statue
    g_pEffect->SetTexture( "tColorMap", g_pColorMap_Statue );
    g_pEffect->CommitChanges();
    g_pScene->DrawSubset(1);
    g_pScene->DrawSubset(2);

    // End the current pass
    g_pEffect->EndPass();
   }

   // End the effect
   g_pEffect->End();
   
   // Restore the render state
   g_pd3dDevice->SetRenderState( D3DRS_FILLMODE, D3DFILL_SOLID );

   //渲染完成,OK了
  }

  else
  {
   //不使用阴影的渲染,不去管它
   // Restore the render target and depth buffer
   g_pd3dDevice->SetRenderTarget( 0, g_pOldColorRT );
   g_pd3dDevice->SetDepthStencilSurface( g_pOldDepthRT );
   SAFE_RELEASE( g_pOldColorRT );
   SAFE_RELEASE( g_pOldDepthRT );

   // Otherwise, render the scene with hard shadows
   g_pEffect->SetTechnique( "techSceneHard" );
   
   // Set the textures
   g_pEffect->SetTexture( "tShadowMap", g_pShadowMap );
   g_pEffect->SetTexture( "tSpotMap", g_pSpotMap );

   // Render the effect
   uPasses = 0;
   g_pEffect->Begin( &uPasses, 0 );

   // Do we need to render the scene in wireframe mode
   if( g_bWireframe )
    g_pd3dDevice->SetRenderState( D3DRS_FILLMODE, D3DFILL_WIREFRAME );

   for( uPass = 0; uPass < uPasses; uPass++ )
   {
    // Set the current pass
    g_pEffect->BeginPass( uPass );

    // Draw the floor
    g_pEffect->SetTexture( "tColorMap", g_pColorMap_Floor );
    g_pEffect->CommitChanges();
    g_pScene->DrawSubset(0);
    
    // Draw the statue
    g_pEffect->SetTexture( "tColorMap", g_pColorMap_Statue );
    g_pEffect->CommitChanges();
    g_pScene->DrawSubset(1);
    g_pScene->DrawSubset(2);

    // End the current pass
    g_pEffect->EndPass();
   }

   // End the effect
   g_pEffect->End();
   
   // Restore the render state
   g_pd3dDevice->SetRenderState( D3DRS_FILLMODE, D3DFILL_SOLID );
  }

  //绘制面板文字,不管它
  // Display the frame stats
  if( g_bDisplayStats )
  {
   TCHAR szStats[255];
   sprintf( szStats, "Framerate : %2.1f", g_fFramerate );
   
   RECT rc = { 5, 5, 0, 0 };
   g_pFont->DrawText( NULL, szStats, -1, &rc, DT_CALCRECT, 0xFFFFAA00 );
    g_pFont->DrawText( NULL, szStats, -1, &rc, DT_NOCLIP,   0xFFFFAA00 );

   sprintf( szStats, "Use arrow and Z, X keys to move around the scene" );
   SetRect( &rc, 5, SCREEN_HEIGHT - 40, 0, 0 );
   g_pFont->DrawText( NULL, szStats, -1, &rc, DT_CALCRECT, 0xFFFFAA00 );
    g_pFont->DrawText( NULL, szStats, -1, &rc, DT_NOCLIP,   0xFFFFAA00 );
   
   sprintf( szStats, "Press W to toggle wireframe display" );
   SetRect( &rc, 5, SCREEN_HEIGHT - 20, 0, 0 );
   g_pFont->DrawText( NULL, szStats, -1, &rc, DT_CALCRECT, 0xFFFFAA00 );
    g_pFont->DrawText( NULL, szStats, -1, &rc, DT_NOCLIP,   0xFFFFAA00 );
   
   sprintf( szStats, "Press S to toggle soft shadows" );
   SetRect( &rc, 350, SCREEN_HEIGHT - 40, 0, 0 );
   g_pFont->DrawText( NULL, szStats, -1, &rc, DT_CALCRECT, 0xFFFFAA00 );
    g_pFont->DrawText( NULL, szStats, -1, &rc, DT_NOCLIP,   0xFFFFAA00 );
    
   sprintf( szStats, "Press G to pump out a screenshot" );
   SetRect( &rc, 350, SCREEN_HEIGHT - 20, 0, 0 );
   g_pFont->DrawText( NULL, szStats, -1, &rc, DT_CALCRECT, 0xFFFFAA00 );
    g_pFont->DrawText( NULL, szStats, -1, &rc, DT_NOCLIP,   0xFFFFAA00 );
  }

  // End rendering the scene and present it
  g_pd3dDevice->EndScene();
  g_pd3dDevice->Present( NULL, NULL, NULL, NULL );
 }

 return S_OK;
}

//-----------------------------------------------------------------------------
// Name: ScreenGrab()
// Desc: Saves the backbuffer to a file
//-----------------------------------------------------------------------------
//屏幕抓图小程序,没什么用

HRESULT ScreenGrab()
{
 //
 // Grab the back buffer and save it to a file
 //
 
 D3DDISPLAYMODE d3ddm;
 g_pd3dDevice->GetDisplayMode( 0, &d3ddm );

 g_pd3dDevice->CreateOffscreenPlainSurface( d3ddm.Width, d3ddm.Height, D3DFMT_A8R8G8B8,
              D3DPOOL_DEFAULT, &g_pScreenshot, NULL );

 g_pd3dDevice->GetBackBuffer( 0, 0, D3DBACKBUFFER_TYPE_MONO, &g_pScreenshot );

 TCHAR szScreenshot[32];
 sprintf( szScreenshot, "ScreenShot%d.bmp", g_uNumScreenshots++ );
 D3DXSaveSurfaceToFile( szScreenshot, D3DXIFF_BMP, g_pScreenshot, NULL, NULL );

 SAFE_RELEASE( g_pScreenshot );

 return S_OK;
}

//OK 程序完了下面是fx程序

/*-----------------------------------------------------------------------------
 Name : Soft Shadows.fx
 Desc : Soft shadows effect file.
 Author : Anirudh S Shastry. Copyright (c) 2004.
 Date : 22nd June, 2004.
-----------------------------------------------------------------------------*/

#define FRAMESCALE 1.1f  //加一个阴影图缩放参数

//--------------------------------------------
// Global variables
//--------------------------------------------
matrix g_matWorldViewProj : WorldViewProjection;
matrix g_matLightViewProj : LightViewProjection;
matrix g_matWorld   : World;
matrix g_matWorldIT  : WorldInverseTranspose;
matrix g_matTexture  : Texture;

vector g_vLightPos   : LightPosition;
vector g_vEyePos   : EyePosition;
vector g_vLightColor  : LightColor;

float2  g_vSampleOffsets[15];
float   g_fSampleWeights[15];

texture tShadowMap;
texture tScreenMap;
texture tBlurHMap;
texture tBlurVMap;
texture tColorMap;
texture tSpotMap;

sampler ShadowSampler = sampler_state
{
 texture = (tShadowMap);

 MipFilter = Linear;
 MinFilter = Linear;
 MagFilter = Linear;
 
 AddressU = Clamp;
 AddressV = Clamp;
};

sampler ScreenSampler = sampler_state
{
 texture = (tScreenMap);

 MipFilter = Linear;
 MinFilter = Linear;
 MagFilter = Linear;
 
 AddressU = Clamp;
 AddressV = Clamp;
};

sampler BlurHSampler = sampler_state
{
 texture = (tBlurHMap);

 MipFilter = Linear;
 MinFilter = Linear;
 MagFilter = Linear;
 
 AddressU = Clamp;
 AddressV = Clamp;
};

sampler BlurVSampler = sampler_state
{
 texture = (tBlurVMap);

 MipFilter = Linear;
 MinFilter = Linear;
 MagFilter = Linear;
 
 AddressU = Clamp;
 AddressV = Clamp;
};

sampler ColorSampler = sampler_state
{
 texture = (tColorMap);

 MipFilter = Linear;
 MinFilter = Linear;
 MagFilter = Linear;
 
 AddressU = Wrap;
 AddressV = Wrap;
};

sampler SpotSampler = sampler_state
{
 texture = (tSpotMap);

 MipFilter = Linear;
 MinFilter = Linear;
 MagFilter = Linear;
 
 AddressU = Clamp;
 AddressV = Clamp;
};

//--------------------------------------------
// Vertex shaders
//--------------------------------------------
struct VSOUTPUT_SHADOW
{
 float4 vPosition : POSITION;
 float  fDepth  : TEXCOORD0;
};

// Shadow generation vertex shader
//发生阴影图的VS
VSOUTPUT_SHADOW VS_Shadow( float4 inPosition : POSITION )
{
 // Output struct
 VSOUTPUT_SHADOW OUT = (VSOUTPUT_SHADOW)0;

 // Output the transformed position
 OUT.vPosition = mul( inPosition, g_matLightViewProj );

 // Output the scene depth
 OUT.fDepth = OUT.vPosition.z;

 return OUT;
}

// Shadow mapping vertex shader
struct VSOUTPUT_UNLIT
{
 float4 vPosition : POSITION;
 float4 vTexCoord : TEXCOORD0;
};

//绘制场景,只产生阴影图的VS

VSOUTPUT_UNLIT VS_Unlit( float4 inPosition : POSITION )
{
 // Output struct
 VSOUTPUT_UNLIT OUT = (VSOUTPUT_UNLIT)0;

 // Output the transformed position
 OUT.vPosition = mul( inPosition, g_matWorldViewProj );

 OUT.vPosition.w *=FRAMESCALE;  //将图像略缩小,多渲染一点边缘,以防止边缘部分模糊找不到数据
   
 // Output the projective texture coordinates
 OUT.vTexCoord = mul( inPosition, g_matTexture );

 return OUT;
}

struct VSOUTPUT_BLUR
{
 float4 vPosition : POSITION;
 float2 vTexCoord : TEXCOORD0;
};

//模糊处理的VS,横向,纵向都用这一个
// Gaussian filter vertex shader
VSOUTPUT_BLUR VS_Blur( float4 inPosition : POSITION, float2 inTexCoord : TEXCOORD0 )
{
 // Output struct
 VSOUTPUT_BLUR OUT = (VSOUTPUT_BLUR)0;

 // Output the position
 OUT.vPosition = inPosition;

 // Output the texture coordinates
 OUT.vTexCoord = inTexCoord;

 return OUT;
}
  
struct VSOUTPUT_SCENE
{
 float4 vPosition  : POSITION;
 float2 vTexCoord  : TEXCOORD0;
 float4 vProjCoord  : TEXCOORD1;
 float4 vScreenCoord  : TEXCOORD2;
 float3 vNormal   : TEXCOORD3;
 float3 vLightVec  : TEXCOORD4;
 float3 vEyeVec   : TEXCOORD5;
};

// Scene pixel shader
//最后渲染场景,贴阴影图
VSOUTPUT_SCENE VS_Scene( float4 inPosition : POSITION, float3 inNormal : NORMAL, float2 inTexCoord : TEXCOORD0 )
{
 VSOUTPUT_SCENE OUT = (VSOUTPUT_SCENE)0;

// inPosition.w=1.5;  //w参数是一个放大倍数
 // Output the transformed position

 OUT.vPosition = mul( inPosition, g_matWorldViewProj );  //变成屏幕空间坐标

 // Output the texture coordinates
 OUT.vTexCoord = inTexCoord;

 // Output the projective texture coordinates
 OUT.vProjCoord = mul( inPosition, g_matTexture );

//屏幕坐标计算

//  Output the screen-space texture coordinates,       //贴图坐标,这里的屏幕坐标为-1,1;-1,1
// OUT.vScreenCoord.x = ( OUT.vPosition.x * 0.5 + OUT.vPosition.w * 0.5 );  //转换为0-1
// OUT.vScreenCoord.y = ( OUT.vPosition.w * 0.5 - OUT.vPosition.y * 0.5 );  //
// OUT.vScreenCoord.z = OUT.vPosition.w;
// OUT.vScreenCoord.w = OUT.vPosition.w;   //应该是线性放大

 //w是缩放比例,这样,转换为屏幕贴图坐标
 //缩小查找区域,与阴影图对上
 OUT.vScreenCoord.x = ( (OUT.vPosition.x/FRAMESCALE)/OUT.vPosition.w + 1.f)/2.f;  //转换为0-1
 OUT.vScreenCoord.y = ( -(OUT.vPosition.y/FRAMESCALE)/OUT.vPosition.w + 1.f)/2.f;  //

 // Get the world space vertex position
 float4 vWorldPos = mul( inPosition, g_matWorld );

 // Output the world space normal
 OUT.vNormal = mul( inNormal, g_matWorldIT );

 // Move the light vector into tangent space
 OUT.vLightVec = g_vLightPos.xyz - vWorldPos.xyz;

 // Move the eye vector into tangent space
 OUT.vEyeVec = g_vEyePos.xyz - vWorldPos.xyz;
 
 return OUT;
}

//--------------------------------------------
// Pixel shaders
//--------------------------------------------
//发生阴影图的PS,这里把深度保存在RGB中了,不象别的直接存在Z-Buffer中
float4  PS_Shadow( VSOUTPUT_SHADOW IN ) : COLOR0
{
 // Output the scene depth
 return float4( IN.fDepth, IN.fDepth, IN.fDepth, 1.0f );
}
 
// Shadow mapping pixel shader
//绘制场景,只产生阴影图的PS
float4  PS_Unlit( VSOUTPUT_UNLIT IN ) : COLOR0
{
 // Generate the 9 texture co-ordinates for a 3x3 PCF kernel
 float4 vTexCoords[9];

 // Texel size
 float fTexelSize = 1.0f / 512.0f;

 // Generate the tecture co-ordinates for the specified depth-map size
 // 4 3 5
 // 1 0 2
 // 7 6 8
 vTexCoords[0] = IN.vTexCoord;

/* vTexCoords[1] = IN.vTexCoord + float4( -fTexelSize, 0.0f, 0.0f, 0.f );
 vTexCoords[2] = IN.vTexCoord + float4(  fTexelSize, 0.0f, 0.0f, 0.0f );
 vTexCoords[3] = IN.vTexCoord + float4( 0.0f, -fTexelSize, 0.0f, 0.0f );
 vTexCoords[6] = IN.vTexCoord + float4( 0.0f,  fTexelSize, 0.0f, 0.0f );

 vTexCoords[4] = IN.vTexCoord + float4( -fTexelSize, -fTexelSize, 0.0f, 0.0f );
 vTexCoords[5] = IN.vTexCoord + float4(  fTexelSize, -fTexelSize, 0.0f, 0.0f );
 vTexCoords[7] = IN.vTexCoord + float4( -fTexelSize,  fTexelSize, 0.0f, 0.0f );
 vTexCoords[8] = IN.vTexCoord + float4(  fTexelSize,  fTexelSize, 0.0f, 0.0f );
*/
 // Sample each of them checking whether the pixel under test is shadowed or not
 float fShadowTerm = 0.0f;

 for( int i = 0; i < 1; i++ ) //原程序使用了9点平均出深度值,考虑后面有模糊处理,这里没必要平均了,改为1点以提高速度
 {
  float A = tex2Dproj( ShadowSampler, vTexCoords[i] ).r;
  float B = (IN.vTexCoord.z - 0.001f);
  
  // Texel is shadowed
  fShadowTerm += A < B ? 0.1f : 1.0f;
 }

 // Get the average
// fShadowTerm /= 9.0f;
 
 // Uncomment this to disable 3x3 PCF
 // float fShadowTerm = tex2Dproj( ShadowSampler, IN.vTexCoord ) < (IN.vTexCoord.z - 0.001f) ? 0.1f : 1.0f;
 
 return fShadowTerm;
}

// Horizontal blur pixel shader
//水平模糊处理
float4 PS_BlurH( VSOUTPUT_BLUR IN ) : COLOR0
{
 // Accumulated color
 float4 vAccum = float4( 0.0f, 0.0f, 0.0f, 0.0f );

 // Sample the taps (g_vSampleOffsets holds the texel offsets and g_fSampleWeights holds the texel weights)
 for( int i = 0; i < 8; i++ )  //原文为15个点,缩小一点提高速度
 {
  vAccum += tex2D( ScreenSampler, IN.vTexCoord + g_vSampleOffsets[i] ) * g_fSampleWeights[i];
 }

 return vAccum;
}

// Vertical blur pixel shader
//纵向模糊处理,这样分两次模糊了64个点,实际取样为8+8次,比8*8次大为减少,这是本程序的要点
float4 PS_BlurV( VSOUTPUT_BLUR IN ) : COLOR0
{
 // Accumulated color
 float4 vAccum = float4( 0.0f, 0.0f, 0.0f, 0.0f );

 // Sample the taps (g_vSampleOffsets holds the texel offsets and g_fSampleWeights holds the texel weights)
 for( int i = 0; i < 8; i++ )
 {
  vAccum += tex2D( BlurHSampler, IN.vTexCoord + g_vSampleOffsets[i] ) * g_fSampleWeights[i];
 }

 return vAccum;
}

//绘制场景贴阴影

float4 PS_Scene( VSOUTPUT_SCENE IN ) : COLOR0
{
 // Normalize the normal, light and eye vectors
 IN.vNormal  = normalize( IN.vNormal );
 IN.vLightVec = normalize( IN.vLightVec );
 IN.vEyeVec   = normalize( IN.vEyeVec );

 // Sample the color and normal maps
 float4 vColor  = tex2D( ColorSampler, IN.vTexCoord );

 // Compute the diffuse and specular lighting terms
 float diffuse  = max( dot( IN.vNormal, IN.vLightVec ), 0 );
 float specular = pow( max( dot( 2 * dot( IN.vNormal, IN.vLightVec ) * IN.vNormal - IN.vLightVec, IN.vEyeVec ), 0 ), 8 );

 if( diffuse == 0 ) specular = 0;

 // Grab the shadow term
 //取出阴影图像素
// float fShadowTerm = tex2Dproj( BlurVSampler, IN.vScreenCoord );
 float fShadowTerm = tex2D( BlurVSampler, IN.vScreenCoord.xy );  //原程序使用tex2Dproj,改为tex2D好理解,当然,前面坐标计算也改了
// float fShadowTerm = tex2Dproj( ScreenSampler, IN.vScreenCoord );

 // Grab the spot term
 float fSpotTerm = tex2Dproj( SpotSampler, IN.vProjCoord );

 // Compute the final color
 return (diffuse * vColor * g_vLightColor * fShadowTerm * fSpotTerm) +  //合成
     (specular * vColor * g_vLightColor.a * fShadowTerm * fSpotTerm);

// return float4(fShadowTerm,fShadowTerm ,fShadowTerm ,1);

/* return (diffuse * vColor * g_vLightColor * fSpotTerm) +
     (specular * vColor * g_vLightColor.a * fSpotTerm);*/
}

//不加模糊直接产生硬阴影,没用
float4 PS_SceneHard( VSOUTPUT_SCENE IN ) : COLOR0
{
 // Normalize the normal, light and eye vectors
 IN.vNormal  = normalize( IN.vNormal );
 IN.vLightVec = normalize( IN.vLightVec );
 IN.vEyeVec   = normalize( IN.vEyeVec );

 // Sample the color and normal maps
 float4 vColor  = tex2D( ColorSampler, IN.vTexCoord );

 // Compute the ambient, diffuse and specular lighting terms
 float diffuse  = max( dot( IN.vNormal, IN.vLightVec ), 0 );
 float specular = pow( max( dot( 2 * dot( IN.vNormal, IN.vLightVec ) * IN.vNormal - IN.vLightVec, IN.vEyeVec ), 0 ), 8 );

 if( diffuse == 0 ) specular = 0;

 // Grab the spot term
 float fSpotTerm = tex2Dproj( SpotSampler, IN.vProjCoord );

 // Grab the shadow term
 float fShadowTerm = 0.0f;
 fShadowTerm = tex2Dproj( ShadowSampler, IN.vProjCoord ) < (IN.vProjCoord.z - 0.001f) ? 0.1f : 1.0f;
 fShadowTerm *= fSpotTerm;
  
/* // Generate the 9 texture co-ordinates for a 3x3 PCF kernel
 float4 vTexCoords[9];

 // Texel size
 float fTexelSize = 1.0f / 512.0f;

 // Generate the tecture co-ordinates for the specified depth-map size
 // 4 3 5
 // 1 0 2
 // 7 6 8
 vTexCoords[0] = IN.vProjCoord;

 vTexCoords[1] = IN.vProjCoord + float4( -fTexelSize, 0.0f, 0.0f, 0.0f );
 vTexCoords[2] = IN.vProjCoord + float4(  fTexelSize, 0.0f, 0.0f, 0.0f );
 vTexCoords[3] = IN.vProjCoord + float4( 0.0f, -fTexelSize, 0.0f, 0.0f );
 vTexCoords[6] = IN.vProjCoord + float4( 0.0f,  fTexelSize, 0.0f, 0.0f );

 vTexCoords[4] = IN.vProjCoord + float4( -fTexelSize, -fTexelSize, 0.0f, 0.0f );
 vTexCoords[5] = IN.vProjCoord + float4(  fTexelSize, -fTexelSize, 0.0f, 0.0f );
 vTexCoords[7] = IN.vProjCoord + float4( -fTexelSize,  fTexelSize, 0.0f, 0.0f );
 vTexCoords[8] = IN.vProjCoord + float4(  fTexelSize,  fTexelSize, 0.0f, 0.0f );

 // Sample each of them checking whether the pixel under test is shadowed or not
 for( int i = 0; i < 9; i++ )
 {
  float A = tex2Dproj( ShadowSampler, vTexCoords[i] ).r;
  float B = IN.vProjCoord.z - 0.001f;
  
  // Texel is shadowed
  fShadowTerm += A < B ? 0.1f : 1.0f;
 }
 
 // Get the average
 fShadowTerm = fShadowTerm * fSpotTerm / 9.0f;
*/ 
 // Compute the final color
 return (diffuse * vColor * g_vLightColor * fShadowTerm) +
     (specular * vColor * g_vLightColor.a * fShadowTerm);
}

//--------------------------------------------
// Techniques
//--------------------------------------------
technique techShadow
{
 pass p0
 {
  Lighting = False;
  CullMode = CCW;
  
  VertexShader = compile vs_2_0 VS_Shadow();
  PixelShader  = compile ps_2_0 PS_Shadow();
 }
}

technique techUnlit
{
 pass p0
 {
  Lighting = False;
  CullMode = CCW;
  
  VertexShader = compile vs_2_0 VS_Unlit();
  PixelShader  = compile ps_2_0 PS_Unlit();
 }
}

technique techBlurH
{
 pass p0
 {
  Lighting = False;
  CullMode = None;
  
  VertexShader = compil vs_2_0 VS_Blur();
  PixelShader  = compile ps_2_0 PS_BlurH();
 }
}

technique techBlurV
{
 pass p0
 {
  Lighting = False;
  CullMode = None;
  
  VertexShader = compile vs_2_0 VS_Blur();
  PixelShader  = compile ps_2_0 PS_BlurV();
 }
}

technique techScene
{
 pass p0
 {
  Lighting = False;
  CullMode = CCW;
  
  VertexShader = compile vs_2_0 VS_Scene();
  PixelShader  = compile ps_2_0 PS_Scene();
 }
}

technique techSceneHard
{
 pass p0
 {
  Lighting = False;
  CullMode = CCW;
  
  VertexShader = compile vs_2_0 VS_Scene();
  PixelShader  = compile ps_2_0 PS_SceneHard();
 }
}

//Fx完了

//笨笨 2006.02.12

- 作者: xmchang 2006年02月13日, 星期一 00:26  回复(1) |  引用(0) 加入博采