http://blog.csdn.net/i_dovelemon/article/details/40481681

作者:i_dovelemon

来源:CSDN

日期:2014 / 10 / 26

主题:Cube mapping, SkyBox

引言

在3D游戏中,特别是在室外游戏场景中,往往需要模拟出天空的效果。如下图所示:

在这张图片中,读者可以发现,场景中存在这天空,而且不管游戏角色如何在场景中进行移动,都无法靠近天空。通过添加这样的效果,使得游戏世界更加的丰富多彩,更加的接近现实的世界。本节,就想大家讲述,如何在DirectX中实现这样的天空效果。

Cube Mapping

Cube Mapping是Enviroment Mapping中的一种。我们知道,传统的纹理映射方式,是用一张2D的图片,映射到2D的表面上去。这样的映射方法在我的博客中关于纹理映射一章中讲述了如何一一的进行了映射。从中我们可以发现,这是一种平面上的一对一的映射方法。而Cube mapping是一种3D空间的映射方法。对于一个3D空间的物体,我们有时候会发现使用2D的纹理来对3D物体的每一个表面进行映射,在物体的整体感觉上会差很多。那么,有没有一种方法,能够将纹理进行整体的考量,然后映射到3D物体上去,这样物体的纹理效果看上去就十分的和谐,整体上十分的舒适。使用Cube mapping就能够做到这一点。

Cube mapping是将一张由6个面组成的纹理,构成一个在逻辑上是Cube立方体的3D纹理图。也就是如下图所示的样子:

这样的一张图,它有6个面组成,分别对应了一个立方体的6个面。一般来说,我们将以如下的顺序来构成这个纹理图:

从图中可以看出,这6个面就构成了一个立方体。在有了这个逻辑上,可以作为立方体来看待的纹理图之后,我们需要的就是如何定义这个纹理上的坐标。很显然,对于3D的纹理图,自然应该使用3D空间坐标来表示。那么,这个3D空间坐标是如何映射到这个3D空间纹理上来的了。

我们来看看如下的一个例子,为了解释的方便,我在2D空间上作图,看下图:

上图中外围的立方体就是我们的3D空间的纹理,而里面的球体,就是我们希望将3D纹理映射到的物体。进行Cube mapping的时候,我们从球体的几何中心发射一条射线到我们希望映射的那个球体的点上去,这个射线同时也会和立方体相交,那么我们就可以将这个与立方体相交的点作为球体上的点的纹理。通过这样的方法,对于任何一个球体上的点,我们都可以在立方体上找到与之相对应的纹理。那么剩下的问题是,纹理坐标从何而来?

从上面的描述中,读者会发现,这条射线应该就是球体上的坐标点的坐标值(如果球心就是模型坐标的中心点的话)V(x,y,z)。那么,如何根据这个纹理坐标来获取我们那个纹理图上的纹理了?要知道,虽然逻辑上它是一个3D纹理图,但是实际在存储的时候,还是使用2D的方法进行存储的。所以,我们需要从向量V中获取与射线相交的那一个点到底在哪一个面上?

对于任何一个向量V(x,y,z),我们选取它的任何一个绝对值最大的分量,那么这个分量就标示了这条射线与哪一个面相交。比如说,对于向量V(-3,1,0)来说,这个向量所发出的射线是与立方体的左面,即-X面相交的。对于向量V(1,4,-1)来说,这个向量所发出的射线是与立方体的上面,即+Y面相交的。这是很显然的事实,读者可以自行在图上画画,了解一下。

在获取到向量与哪一个面相交之后,剩下的问题就是如何从这个面上获取纹理。这个问题就变化成为了一个2D平面上获取纹理的问题了。从纹理映射一节中,我们知道,纹理映射的方法,是纹理的左上角坐标为(0,0),右下角坐标为(1,1),即纹理坐标的u和v坐标范围是(0,1)上。那么,我们如何获取这个坐标值了?

在上面,我们使用绝对值最大的分量来确定了与哪一个面相交,那么剩下的两个分量与纹理坐标是否有联系了?答案是肯定的,读者可以想象一下,在3D空间中,当我们正对着3D纹理的一个面的时候,是不是它的坐标就对应着剩下的两个分量了?也就是说,我们通过一种线性的变换,将剩下的两个值,转化成为[0,1]这个空间来,就能够根据这个像素值来获取上面的纹理值了。

这样的线性变换十分的简单,我们知道,一个绝对值最大的分量,那么我们就可以使用这个分量的绝对值来作为参考,因为其他值的绝对值都会比这个值来的小,即,其他值除以这个值之后的值所在的范围是[-1,1]。我们对这个结果进行平移操作使得范围变成了[0,2],再在这个基础上乘以0.5就变成了[0,1]。由于前面进行变换都是线性的,所以一一对应的这种属性并没有随着变换而发生改变,即原来的值对应的纹理,在进过变换之后,依然还是这个纹理,只是从3D空间的坐标变成了2D空间的纹理坐标,便于我们在纹理图上进行访问。

上面就是进行Cube mapping的过程。这个过程在DirectX中,已经支持了。我们可以不需要自己来实现。我们的工作只是需要创建一个这样的3D纹理图。然后将这个图附加给我们想要映射的3D物体即可。在上面的讨论中,我们已经明白了进行这种映射时的纹理坐标就是3D物体本身的模型坐标。

Sky Box

好了,在讲解了Cube mapping之后,现在就来讲解下,如何实现天空。实现天空的方法,有很多。一般来讲,我们可以创建一个天空几何体,这个几何体可以是球体,也可以是立方体。这里我们使用球体来代表天空盒子。创建好了天空盒子之后,在绘制的时候,我们需要注意一点,也就是上面我们说的,不管相机如何的移动,总是没有办法接近SkyBox的边缘。而实现这样的效果,要么将天空盒子做的无限大,很显然在计算机中没有无限大的概念,那么就只有将天空盒子随着相机一起进行移动。也就是说,使的相机总是治愈天空盒子的中心处,并且将天空盒子的z值设置成为最远的z值。这样就能够保证,天空总是处于最外围的包围状态。下面是实现这个Skybox的代码。

  1. <span style="font-family:Microsoft YaHei;">//Load the texture
  2. HR(D3DXCreateCubeTextureFromFile(m_pDevice, L"grassenvmap1024.dds", &m_pCubeTex));</span>

调用D3DXCreateCubeTextureFromFile来读取3D纹理图。这个纹理图可以通过一些软件制作得到。这里不介绍如何制作这些图片。

  1. <span style="font-family:Microsoft YaHei;">void CubeDemo::drawCube()
  2. {
  3. UINT pass = 0 ;
  4. m_pEffect->Begin(&pass, 0);
  5. for(UINT i = 0 ; i< pass ; i ++ )
  6. {
  7. m_pEffect->BeginPass(i);
  8. D3DXMATRIX m ;
  9. D3DXMatrixIdentity(&m);
  10. D3DXMatrixTranslation(&m, m_Camera.pos().x, m_Camera.pos().y,m_Camera.pos().z );
  11. m_pEffect->SetMatrix(m_gWVP, &(m * m_Camera.viewproj()));
  12. m_pEffect->SetTexture(m_gTex, m_pCubeTex);
  13. m_SphereMesh->DrawSubset(0);
  14. m_SphereMesh->DrawSubset(0);
  15. m_pEffect->EndPass();
  16. }
  17. m_pEffect->End();
  18. }</span>

这个函数用来将天空盒子进行平移,使得相机总是至于天空盒子的中心处。

  1. <span style="font-family:Microsoft YaHei;">//---------------------------------------------------------------------
  2. // declaration  : Copyright (c), by XJ , 2014 . All right reserved.
  3. // brief    : This shader file will define the skybox using the cube mapping
  4. // file     : SkyBox.fx
  5. // author   : XJ
  6. // date     : 2014 / 10 / 26
  7. // version  : 1.0
  8. //------------------------------------------------------------------------
  9. /**
  10. * Define the variant
  11. */
  12. uniform extern float4x4 gWVP ;              // the world-view-projection matrix
  13. uniform extern texture gTex   ;             // the cube mapping texture
  14. sampler EnvMaps = sampler_state
  15. {
  16. Texture = <gTex> ;
  17. MinFilter = LINEAR ;
  18. MagFilter = LINEAR ;
  19. MipFilter = LINEAR ;
  20. AddressU = WRAP ;
  21. AddressV = WRAP ;
  22. };
  23. void SkyVS(float3 posL: POSITION0,
  24. out float4 posH: POSITION0,
  25. out float3 oEnvTex: TEXCOORD0)
  26. {
  27. //set the z = w, to make the z/w =1, which means the vertex is always in the far plane
  28. posH = mul((float4(posL, 1.0f)), gWVP).xyww;
  29. //save the vertex position as the texture coordinate
  30. oEnvTex = posL ;
  31. }
  32. float4 SkyPS(float3 oEnvTex: TEXCOORD0):COLOR0
  33. {
  34. return texCUBE(EnvMaps, oEnvTex);
  35. }
  36. technique SkyBox
  37. {
  38. pass p0
  39. {
  40. vertexShader = compile vs_2_0 SkyVS();
  41. pixelShader = compile ps_2_0 SkyPS();
  42. CullMode = None ;
  43. ZFunc   = Always ;
  44. StencilEnable = true ;
  45. StencilFunc = Always ;
  46. StencilPass = Replace ;
  47. StencilRef = 0 ;
  48. }
  49. }</span>

这个是天空盒子的Shader文件。可以发现,我们在VS中,将齐次化之后的坐标值的z值设置为w,这样在进行最后的齐次化操作的时候,z/w = 1。而我们知道,在DirectX中,进行3次基本变换之后的空间是一个X[-1,1] - Y[-1,1] - Z[0,1]的空间。也就是说,如果z值为1的时候,就是距离最远的地方,在大的话,就会被硬件上的流水操作裁剪掉了。所以,通过这样的方法,我们就能够保证,天空盒子用于处在最远处。

同时还需要注意的是,我们这时候使用的映射方法不是以前的tex2D映射方式了,而是texCUBE映射方法。

下面是最终的截图:

好了,今天的笔记到此结束!!!

DirectX 读书笔记(14) Cube mapping之SkyBox[转]的更多相关文章

  1. 进入保护模式(二)——《x86汇编语言:从实模式到保护模式》读书笔记14

    首先来段题外话:之前我发现我贴出的代码都没有行号,给讲解带来不便.所以从现在起,我要给代码加上行号.我写博客用的这个插入代码的插件,确实不支持自动插入行号.我真的没有找到什么好方法,无奈之下,只能按照 ...

  2. 机器学习实战 - 读书笔记(14) - 利用SVD简化数据

    前言 最近在看Peter Harrington写的"机器学习实战",这是我的学习心得,这次是第14章 - 利用SVD简化数据. 这里介绍,机器学习中的降维技术,可简化样品数据. 基 ...

  3. OCP读书笔记(14) - 管理数据库性能

    搜集统计信息 1. dbms_stats gather_schema_stats 1)option:有四个选项: a.gather empty:只分析目前还没有搜集过统计信息的表 SQL> co ...

  4. 『TCP/IP详解——卷一:协议』读书笔记——14

    2013-08-25 11:32:06 第5章 RARP:逆地址解析协议 5.1 引言 具有本地磁盘的系统引导时,一般是从磁盘上的配置文件中读取IP地址.但是无盘机,如X终端或无盘工作站,则需要采用其 ...

  5. OCA读书笔记(14) - 备份和恢复基本概念

    备份恢复概念 如何判断数据库的一致性 在mount状态下,oracle如何判断数据库的一致性 scn:system change number,它是数据库时钟 如何查询当前系统的scn: select ...

  6. RH033读书笔记(14)-Lab 15 Switching Users and Setting a Umask

    Lab 15 Switching Users and Setting a Umask Goal: Become familiar with the use of several essential c ...

  7. 《http权威指南》读书笔记14

    概述 最近对http很感兴趣,于是开始看<http权威指南>.别人都说这本书有点老了,而且内容太多.我个人觉得这本书写的太好了,非常长知识,让你知道关于http的很多概念,不仅告诉你怎么做 ...

  8. 【黑客免杀攻防】读书笔记14 - 面向对象逆向-虚函数、MFC逆向

    虚函数存在是为了克服类型域解决方案的缺陷,以使程序员可以在基类里声明一些能够在各个派生类里重新定义的函数. 1 识别简单的虚函数 代码示例: #include "stdafx.h" ...

  9. MapReduce与批处理------《Designing Data-Intensive Applications》读书笔记14

    之前的文章大量的内容在和大家探讨分布式存储,接下来的章节进入了分布式计算领域.坦白说,个人之前专业的重心侧重于存储,对许多计算的内容理解可能不是和确切,如果文章中的理解有所不妥,愿虚心赐教.本篇将和大 ...

随机推荐

  1. python 课堂笔记-for语句

    for i in range(10): print("----------",i) for j in range(10): print("world",j) i ...

  2. Linux基本命令 vim命令(二)

    Linux Vim显示行号 在命令模式下输入" : " 进入编辑模式后执行 set nu 命令 即可显示每一行的行号,如果想要取消行号,则再次输入":set nonu&q ...

  3. 验证——正则<37>

    1,郵箱合法性驗證 /* * 郵箱合法性驗證 * @method matchTel * @papram{string} str,電子郵箱 * @return{boolean} * */ functio ...

  4. 【Flask】Sqlalchemy limit, offset slice操作

    ### limit.offset和切片操作:1. limit:可以限制每次查询的时候只查询几条数据.2. offset:可以限制查找数据的时候过滤掉前面多少条.3. 切片:可以对Query对象使用切片 ...

  5. 读完这篇文章,就基本搞定了Redis数据库

    简单来说Redis就是一个数据库,不过与传统的数据库不同的是Redis的数据是存在内存中的,所以存写速度非常快,因此Redis被广泛应用于缓存方向. 另外,Redis也经常用来做分布式锁.Redis提 ...

  6. Cgroups控制cpu,内存,io示例【转】

    本文转载自:https://www.cnblogs.com/yanghuahui/p/3751826.html 百度私有PaaS云就是使用轻量的cgoups做的应用之间的隔离,以下是关于百度架构师许立 ...

  7. ASPX一句话爆破工具

    #include "stdafx.h" #include <stdio.h> #include <Windows.h> #include <stdli ...

  8. wab框架

    http协议 一.http简介        1.HTTP是一个基于TCP/IP通信协议来传递数据(HTML 文件, 图片文件, 查询结果等).       2.HTTP是一个属于应用层的面向对象的协 ...

  9. ceilometer alarm 创建过程中的DB操作及优化

    创建一个ceilometer alarm需要4次DB操作: api/controllers/v2/alarms.py 1. is_over_quota 每一次都需要查询该user/project的所有 ...

  10. Js的cookie

    我们现在清除cookie缓存. 然后发起的请求观察: ============== ============= 已经有cookie的情况下发起请求: 我们在看看第一次没有缓存的情况下,返回的数据: