Cube Mapping和EnvironmentMapping

来源:互联网 发布:java protected关键字 编辑:程序博客网 时间:2024/04/28 16:32

一、下面来自:http://www.tuicool.com/articles/RJbmqq

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的代码。

<span style="font-family:Microsoft YaHei;">//Load the textureHR(D3DXCreateCubeTextureFromFile(m_pDevice, L"grassenvmap1024.dds", &m_pCubeTex));</span>
               调用D3DXCreateCubeTextureFromFile来读取3D纹理图。这个纹理图可以通过一些软件制作得到。这里不介绍如何制作这些图片。
<span style="font-family:Microsoft YaHei;">void CubeDemo::drawCube(){  UINT pass = 0 ;   m_pEffect->Begin(&pass, 0);  for(UINT i = 0 ; i< pass ; i ++ )  {    m_pEffect->BeginPass(i);    D3DXMATRIX m ;    D3DXMatrixIdentity(&m);    D3DXMatrixTranslation(&m, m_Camera.pos().x, m_Camera.pos().y,m_Camera.pos().z );    m_pEffect->SetMatrix(m_gWVP, &(m * m_Camera.viewproj()));    m_pEffect->SetTexture(m_gTex, m_pCubeTex);    m_SphereMesh->DrawSubset(0);    m_SphereMesh->DrawSubset(0);    m_pEffect->EndPass();  }  m_pEffect->End();}</span>
                这个函数用来将天空盒子进行平移,使得相机总是至于天空盒子的中心处。
<span style="font-family:Microsoft YaHei;">//---------------------------------------------------------------------// declaration: Copyright (c), by XJ , 2014 . All right reserved.// brief: This shader file will define the skybox using the cube mapping// file: SkyBox.fx// author: XJ// date: 2014 / 10 / 26// version: 1.0//------------------------------------------------------------------------/*** Define the variant*/uniform extern float4x4 gWVP ;// the world-view-projection matrixuniform extern texture gTex   ;// the cube mapping texturesampler EnvMaps = sampler_state{  Texture = <gTex> ;  MinFilter = LINEAR ;  MagFilter = LINEAR ;  MipFilter = LINEAR ;  AddressU = WRAP ;  AddressV = WRAP ;};void SkyVS(float3 posL: POSITION0,     out float4 posH: POSITION0,     out float3 oEnvTex: TEXCOORD0){  //set the z = w, to make the z/w =1, which means the vertex is always in the far plane  posH = mul((float4(posL, 1.0f)), gWVP).xyww;  //save the vertex position as the texture coordinate  oEnvTex = posL ;}float4 SkyPS(float3 oEnvTex: TEXCOORD0):COLOR0{  return texCUBE(EnvMaps, oEnvTex);}technique SkyBox{  pass p0  {    vertexShader = compile vs_2_0 SkyVS();    pixelShader = compile ps_2_0 SkyPS();    CullMode = None ;    ZFunc= Always ;    StencilEnable = true ;    StencilFunc = Always ;    StencilPass = Replace ;    StencilRef = 0 ;  }}</span>
                这个是天空盒子的Shader文件。可以发现,我们在VS中,将齐次化之后的坐标值的z值设置为w,这样在进行最后的齐次化操作的时候,z/w = 1。而我们知道,在DirectX中,进行3次基本变换之后的空间是一个X[-1,1] - Y[-1,1] - Z[0,1]的空间。也就是说,如果z值为1的时候,就是距离最远的地方,在大的话,就会被硬件上的流水操作裁剪掉了。所以,通过这样的方法,我们就能够保证,天空盒子用于处在最远处。

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

下面是最终的截图:


二、下面来自:http://www.cppblog.com/Herbert/archive/2009/02/11/73489.html

                 环境映射

  就我所知常用的环境贴图有两种,一种是球状的环境贴图;一种是立方体的环境贴图。如果是制作水体的反射或折射效果也可以只做平面的环境贴图。
  DirectX中提供了一个很方便的立方体环境贴图。立方体的六个面定义如下:

typedef enum _D3DCUBEMAP_FACES
{
    D3DCUBEMAP_FACE_POSITIVE_X     = 0,
    D3DCUBEMAP_FACE_NEGATIVE_X     = 1,
    D3DCUBEMAP_FACE_POSITIVE_Y     = 2,
    D3DCUBEMAP_FACE_NEGATIVE_Y     = 3,
    D3DCUBEMAP_FACE_POSITIVE_Z     = 4,
    D3DCUBEMAP_FACE_NEGATIVE_Z     = 5,

    D3DCUBEMAP_FACE_FORCE_DWORD    = 0x7fffffff
} D3DCUBEMAP_FACES;


如下图所示:


   实现原理也很简单:
   1、把立方体环境贴图放到反射物体所知位置,并把该反射物体以外的东西映射到立方体环境贴图上。
   2、根据摄像机到反射物体表面的点的向量a和该点的法向量n,求出a的反射向量b。
   3、以反射向量b作为立方体环境贴图的寻址,把它所对应的颜色赋给反射物体上的点。

   实现过程需要用到一个立方体环境贴图和一个深度表面,步骤如下:

   1、定义
    LPDIRECT3DCUBETEXTURE9 m_pReflectCubeTex;  //反射的立方贴图 
    LPDIRECT3DSURFACE9           m_pSurfDepthCube; // DepthStencilSurface

   2、初始化
    HRESULT hr;

 //创建 DepthStencilSurface
    DXUTDeviceSettings d3dSettings = DXUTGetDeviceSettings();
 hr = pDev->CreateDepthStencilSurface( ENVMAPSIZE,
         ENVMAPSIZE,
         d3dSettings.pp.AutoDepthStencilFormat,
         D3DMULTISAMPLE_NONE,
         0,
         TRUE,
         & m_pSurfDepthCube,
         NULL );
 if( FAILED( hr))
  return hr;

 //创建立方体贴图
    hr = pDev->CreateCubeTexture( ENVMAPSIZE,
                                        1,
                                        D3DUSAGE_RENDERTARGET,
                                        D3DFMT_A16B16G16R16F,
                                        D3DPOOL_DEFAULT,
                                        & m_pReflectCubeTex,
                                        NULL );
 if( FAILED( hr) )
  return hr;



3、把被反射物体渲染到立方体环境贴图中

//------------------------------------------------------------
//desc: 渲染反射场景
//param: pDev 设备
//return: 是否创建成功
//------------------------------------------------------------
HRESULT GRenderMaterialProxy::RenderReflectScene(LPDIRECT3DDEVICE9 pDev)
{
 HRESULT hr;

 if( pDev == NULL)
  return E_FAIL;

 LPDIRECT3DSURFACE9 pSufRTBackup; //RenderTarget 备份
 LPDIRECT3DSURFACE9 pSufDSBackup; //DepthStencilSurface 备份

 pDev->GetRenderTarget( 0, & pSufRTBackup);

 if( SUCCEEDED( pDev->GetDepthStencilSurface( & pSufDSBackup) ) )
 {
  pDev->SetDepthStencilSurface( m_pSurfDepthCube);
 }

 //以反射物体为中心,设置视口投影矩阵
 LPDIRECT3DSURFACE9 pSurf;
 D3DXMATRIXA16 mView, mProj;
 D3DXMATRIXA16 mViewDir( * GCameraManager::GetInstance()->GetCamera()->GetViewMatrix() );
 mViewDir._41 = mViewDir._42 = mViewDir._43 = 0.0f;

    D3DXMatrixPerspectiveFovLH( &mProj, D3DX_PI * 0.5f, 1.0f, 0.01f, 10000.0f );

 std::vector< IRenderObject *>::iterator itRenderObj;

 //渲染Cube 6个表面
 for( int iFace = 0; iFace < 6; ++iFace)
 {
  //获取立方体环境贴图表面,并设为设备的渲染目标
  //注意:GetCubeMapSurface的第二个参数0表示获取立方体环境贴图的表面
  //如果是 n则表示获取立方体环境贴图的第n层表面
  V( m_pReflectCubeTex->GetCubeMapSurface( ( D3DCUBEMAP_FACES) iFace, 0, & pSurf)); 
  V( pDev->SetRenderTarget( 0, pSurf));


  mView = DXUTGetCubeMapViewMatrix( iFace);
  D3DXMatrixMultiply( & mView, & mViewDir, & mView);

  V( pDev->Clear( 0L, NULL, D3DCLEAR_ZBUFFER, 0x000000ff, 1.0f, 0L ));
  
  //渲染被反射物体
  for( itRenderObj = m_vpReflectObjects.begin(); itRenderObj != m_vpReflectObjects.end(); itRenderObj++)
   (*itRenderObj)->Render( pDev, & mView, & mProj);

  SAFE_RELEASE( pSurf);

 }

 if( pSufDSBackup)
 {
  pDev->SetDepthStencilSurface( pSufDSBackup); //恢复深度表面
  SAFE_RELEASE( pSufDSBackup);
 }

 pDev->SetRenderTarget( 0, pSufRTBackup); //恢复渲染目标
 SAFE_RELEASE( pSufRTBackup);

 return S_OK;
}
   

4、根据立方体环境贴图确定反射物体的颜色(用HLSL实现)

 //渲染环境贴图
 if( m_bEnableReflect)
  RenderReflectScene( pDev);

 D3DXMATRIXA16 mWorld, mView, mProj, mWorldView;
 CBaseCamera * pCamera = GCameraManager::GetInstance()->GetCamera();
 D3DXVECTOR4 vEyePos = D3DXVECTOR4(* GCameraManager::GetInstance()->GetCamera()->GetEyePt(), 0.0f);
 D3DXMATRIXA16 mEyePos;
 ::D3DXMatrixTranslation( & mEyePos, vEyePos.x, vEyePos.y, vEyePos.z);
 ::D3DXMatrixTranslation( & mWorld, 0.0f, 0.0f, 0.0f);

 mView = * pCamera->GetViewMatrix();
 mProj = * pCamera->GetProjMatrix();

 GVector4 * pLightDirList = G3DSceneManager::GetInstance()->GetLightDirList();
 UINT iLightCount = G3DSceneManager::GetInstance()->GetLightCount();

 //设置变化矩阵
 m_pEffect->SetMatrix("g_mWorld", &mWorld); 
 m_pEffect->SetMatrix("g_mProj", &mProj);
 m_pEffect->SetMatrix("g_mView", &mView);

 m_pEffect->SetTexture("g_texReflect", m_pReflectCubeTex); //设置立方体环境贴图
 m_pEffect->SetFloat("g_fReflectivity", m_fReflectivity); //设置反射率




( fx文件代码如下)

extern matrix  g_mWorld;
extern matrix  g_mView;
extern matrix  g_mProj;

texture    g_texReflect;

float    g_fReflectivity = 0.9f;

samplerCUBE g_samReflect =
sampler_state
{
 Texture = <g_texReflect>;
 MinFilter = Linear;
 MagFilter = Linear;
 MipFilter = Linear;
};


void VS_Reflect( float4 iPos : POSITION,
 float3 iNor : NORMAL,
 out float4 oPos : POSITION,
 out float3 oEnvTex : TEXCOORD0 )
{
 matrix mWorldView = mul( g_mWorld, g_mView);
 oPos = mul( iPos, mWorldView);
 
 float3 vN = mul( iNor, mWorldView);
 vN = normalize( vN);
 
 float3 vEyeR = -normalize( oPos);
 
 float3 vRef;
 vRef = 2 * dot( vEyeR, vN) * vN - vEyeR;
 //vRef = reflect( vEyeR, vN);
 
 oEnvTex = vRef;
 
 oPos = mul( oPos, g_mProj);
}


float4 PS_Reflect( float3 iTex : TEXCOORD0 ) : COLOR
{
 float4 fColor = texCUBE( g_samReflect, iTex);
 fColor.x = fColor.x * g_fReflectivity;
 fColor.y = fColor.y * g_fReflectivity;
 fColor.z = fColor.z * g_fReflectivity;
 return fColor;  
}

technique Tec_RenderMaterial
{
 pass p0
 {
  vertexShader = compile vs_2_0 VS_Reflect();
  pixelShader = compile ps_2_0 PS_Reflect(); 
 }
}


实现效果如下:

0 0