基于DirectX 9顶点着色器实现2D精灵始终朝向相机
关于精灵朝向相机的矩阵计算优化与顶点着色器实现
首先看你当前代码里的几个问题:
cameraPosition被错误定义为D3DXMATRIX,实际应该是D3DXVECTOR3,类型不匹配会导致逻辑错误。worldViewProj的计算存在笔误,正确公式应为world * view * proj,你写的matrix.rotation * matrix.view * matrix.proj里的matrix变量未定义。- 通过
D3DXMatrixLookAtRH再求逆的方式计算旋转矩阵,存在额外的矩阵求逆开销,其实可以通过向量运算直接构建朝向矩阵。
修正后的CPU端计算代码
如果继续在CPU端处理,优化后的代码如下:
static D3DXVECTOR3 up(0, 0, 1); D3DXMATRIX world, view, proj; g_pd3dDevice->GetTransform(D3DTS_VIEW, &view); g_pd3dDevice->GetTransform(D3DTS_PROJECTION, &proj); // 从视图矩阵提取相机位置 D3DXMATRIX invView; D3DXMatrixInverse(&invView, NULL, &view); D3DXVECTOR3 cameraPos(invView._41, invView._42, invView._43); // 计算精灵到相机的归一化方向向量 D3DXVECTOR3 lookDir = cameraPos - spritePosition; D3DXVec3Normalize(&lookDir, &lookDir); // 构建朝向矩阵:先计算右向量,再通过叉乘修正上方向(避免原始上方向与视线平行) D3DXVECTOR3 right; D3DXVec3Cross(&right, &up, &lookDir); D3DXVec3Normalize(&right, &right); D3DXVECTOR3 newUp; D3DXVec3Cross(&newUp, &lookDir, &right); // 构建行优先的旋转矩阵 D3DXMATRIX rotation; rotation._11 = right.x; rotation._12 = right.y; rotation._13 = right.z; rotation._14 = 0; rotation._21 = newUp.x; rotation._22 = newUp.y; rotation._23 = newUp.z; rotation._24 = 0; rotation._31 = lookDir.x; rotation._32 = lookDir.y; rotation._33 = lookDir.z; rotation._34 = 0; rotation._41 = 0; rotation._42 = 0; rotation._43 = 0; rotation._44 = 1; // 平移矩阵 D3DXMATRIX translation; D3DXMatrixTranslation(&translation, spritePosition.x, spritePosition.y, spritePosition.z); // 世界矩阵 = 旋转 * 平移(DirectX矩阵乘法顺序:变换从右到左) world = rotation * translation; // 计算世界-视图-投影矩阵 D3DXMATRIX worldViewProj = world * view * proj; g_pEffect->SetMatrix("WorldViewProj", &worldViewProj);
顶点着色器端实现方案
将逻辑移到GPU(顶点着色器)可以减少CPU到GPU的数据传输开销,尤其适合大量精灵的场景。
1. C++端传递必要参数
不需要计算完整世界矩阵,仅传递基础参数到着色器:
D3DXMATRIX view, proj; g_pd3dDevice->GetTransform(D3DTS_VIEW, &view); g_pd3dDevice->GetTransform(D3DTS_PROJECTION, &proj); D3DXMATRIX invView; D3DXMatrixInverse(&invView, NULL, &view); D3DXVECTOR3 cameraPos(invView._41, invView._42, invView._43); g_pEffect->SetVector("SpritePos", (D3DXVECTOR4*)&spritePosition); g_pEffect->SetVector("CameraPos", (D3DXVECTOR4*)&cameraPos); g_pEffect->SetVector("UpDir", (D3DXVECTOR4*)&up); g_pEffect->SetMatrix("View", &view); g_pEffect->SetMatrix("Proj", &proj);
2. HLSL顶点着色器实现
在着色器中完成朝向矩阵计算与顶点变换:
struct VS_INPUT { float3 pos : POSITION; // 精灵本地顶点坐标(比如中心为(0,0,0),四角为(±0.5,±0.5,0)) float2 uv : TEXCOORD0; }; struct PS_INPUT { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; }; cbuffer ConstantBuffer : register(b0) { float4 SpritePos; float4 CameraPos; float4 UpDir; float4x4 View; float4x4 Proj; }; PS_INPUT VS(VS_INPUT input) { PS_INPUT output; // 计算精灵到相机的归一化方向 float3 lookDir = normalize(CameraPos.xyz - SpritePos.xyz); // 计算右向量 float3 right = normalize(cross(UpDir.xyz, lookDir)); // 修正上方向,避免与视线平行 float3 newUp = cross(lookDir, right); // 构建世界矩阵:旋转 + 平移 float4x4 world = float4x4( right.x, newUp.x, lookDir.x, 0, right.y, newUp.y, lookDir.y, 0, right.z, newUp.z, lookDir.z, 0, SpritePos.x, SpritePos.y, SpritePos.z, 1 ); // 顶点坐标变换:本地 -> 世界 -> 视图 -> 裁剪 float4 worldPos = mul(float4(input.pos, 1.0f), world); float4 viewPos = mul(worldPos, View); output.pos = mul(viewPos, Proj); output.uv = input.uv; return output; }
方案对比
- CPU端计算:适合精灵数量少的场景,代码直观易调试,但大量精灵会占用较多CPU资源。
- 顶点着色器计算:将矩阵计算转移到GPU,CPU仅传递基础参数,适合大规模精灵场景,效率更高。
内容的提问来源于stack exchange,提问作者thedemons
相关产品推荐
相关产品推荐

