DirectX实现球面纹理映射
介绍
球面纹理映射就是将一个平面纹理映射到球面上。见下图。
实现球面纹理映射有两种方法,一种是使用顶点的法向量来生成纹理坐标,另一个是使用顶点的位置向量来生成纹理坐标。
使用顶点的法向量生成纹理坐标
分析
问题的本质是根据球面上每个点的法向量坐标生成对应的纹理坐标,请看下图,下图中外部的方框表示二维纹理坐标,其范围是(u,v)min = (0,0), (u,v)max = (1,1),中间的圆形表示球面法向量坐标,其x,y分量的范围是(x,y)min = (-1,-1), (x,y)max = (1,1)。
所以问题的本质变成了两组坐标的映射,也即将区间(x,y)min - (x,y)max映射到区间(u,v)min - (u,v)max。这里我们使用反正弦函数y = acrsin(x)来实现。先看一下它的函数图象。
由这个图象知,它的定义域x = (-1,1),值域是y = (-pi / 2, pi / 2)。我们稍作变型,得到下面两个公式。正好完成了由(-1, 1)到(0, 1)的映射。这里tu表示纹理的x坐标,tv表示纹理的y坐标,Nx表示顶点法向量的x轴分量,Ny表示顶点法向量的y轴分量。
tu = arcsin(Nx) / PI + 0.5
tv = arcsin(Ny) / PI + 0.5
代码
具体实现分以下几个步骤
-
用D3DXCreateSphere生成球体
-
克隆一份该球体的Mesh
-
对新的Mesh添加纹理坐标
-
绘制新的Mesh
核心代码如下
struct VERTEX
{
D3DXVECTOR3 pos ; // Vertex position
D3DXVECTOR3 norm ; // Vertex normal
float tu ; // Texture coordinate u
float tv ; // Texture coordinate v
} ;
#define FVF_VERTEX D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX1
LPD3DXMESH GenerateSphereMesh(LPDIRECT3DDEVICE9 pDev,
float fRadius, UINT slices, UINT stacks)
{
// Create D3D sphere
LPD3DXMESH mesh ;
if(FAILED(D3DXCreateSphere(pDev, fRadius, slices, stacks, &mesh, NULL)))
{
return NULL ;
}
// Get a copy of the sphere mesh
LPD3DXMESH texMesh ;
if (FAILED(mesh->CloneMeshFVF(D3DXMESH_SYSTEMMEM, FVF_VERTEX, pDev, &texMesh)))
{
return NULL ;
}
// Release original mesh
mesh->Release() ;
// add texture coordinates
VERTEX* pVerts ;
if (SUCCEEDED(texMesh->LockVertexBuffer(0, (void**)&pVerts)))
{
// Get vertex count
int numVerts = texMesh->GetNumVertices() ;
for (int i =0; i < numVerts; ++i)
{
// Calculates texture coordinates
pVerts->tu = asinf(pVerts->norm.x) / D3DX_PI +0.5f ;
pVerts->tv = asinf(pVerts->norm.y) / D3DX_PI +0.5f ;
++pVerts ;
}
// Unlock the vertex buffer
texMesh->UnlockVertexBuffer() ;
}
return texMesh ;
}
在render函数中调用如下代码
if( SUCCEEDED( g_pd3dDevice->BeginScene() ) )
{
// Set texture
g_pd3dDevice->SetTexture(0, g_pTexture) ;
g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE );
g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );
g_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_MODULATE );
// Draw mesh
g_pMesh->DrawSubset(0) ;
g_pd3dDevice->EndScene();
}
另一种更快的方法是使用下面的公式生成纹理坐标,这里省去了三角函数的计算,用除2代替除pi,速度上快了一些。但是这个方法生成的坐标并不是线性的。
tu = Nx/2 + 0.5
tv = Ny/2 + 0.5
使用顶点的位置向量生成纹理坐标
分析
上面的方法在某些场合下并不适用,比如当模型是立方体的时候,使用顶点法向量就不合适了,因为立方体使用的是面法向量,也就是位于同一个面的顶点的法向量是相同的,这时候应该使用顶点的位置来计算纹理坐标。可以用顶点的位置坐标与模型的中心坐标做差,这样得到一个向量,相当于由中心到一个假想球体的投影,这里并不是真正意义的球面映射,只不过在映射过程中有一个假想球而已。如下图所示。
代码
LPD3DXMESH GenerateBoxMesh(LPDIRECT3DDEVICE9 pDev)
{
// Create D3D sphere
LPD3DXMESH mesh ;
if(FAILED(D3DXCreateBox(pDev, 1.0f, 1.0f, 1.0f, &mesh, NULL)))
{
return NULL ;
}
// Get a copy of the sphere mesh
LPD3DXMESH texMesh ;
if (FAILED(mesh->CloneMeshFVF(D3DXMESH_SYSTEMMEM, FVF_VERTEX, pDev, &texMesh)))
{
return NULL ;
}
// Release original mesh
mesh->Release() ;
// add texture coordinates
VERTEX* pVerts ;
if (SUCCEEDED(texMesh->LockVertexBuffer(0, (void**)&pVerts)))
{
// Get vertex count
int numVerts = texMesh->GetNumVertices() ;
for (int i =0; i < numVerts; ++i)
{
D3DXVECTOR3 v = pVerts->pos ;
D3DXVec3Normalize(&v, &v) ;
// Calculates texture coordinates
pVerts->tu = asinf(v.x) / D3DX_PI +0.5f ;
pVerts->tv = asinf(v.y) / D3DX_PI +0.5f ;
++pVerts ;
}
// Unlock the vertex buffer
texMesh->UnlockVertexBuffer() ;
}
return texMesh ;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· .NET周刊【3月第1期 2025-03-02】
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· [AI/GPT/综述] AI Agent的设计模式综述