Monogame中3D粒子Billboard始终面向相机且支持X轴旋转的实现问题
实现面向相机且绕X轴旋转的粒子Billboard
在MonoGame的HLSL着色器中,你已经实现了基础的相机朝向Billboard效果,现在需要在保持该效果的同时让粒子绕自身X轴旋转,以下是正确的实现方案及问题分析:
基础无旋转代码回顾
原代码通过计算粒子到相机的方向,结合世界上向量得到Billboard的右向量,再用纹理坐标偏移生成顶点位置,确保粒子始终面向相机:
output.PositionWS = mul(float4(input.inPositionOS, 1), World).xyz; float3 ParticleToCamera = output.PositionWS - CameraPosWS; float3 ParticleUp = float3(0, 0, 1); float3 ParticleRight = normalize(cross(ParticleToCamera, ParticleUp)); finalPosition.xyz += ((input.inTexCoords.x - 0.5f) * size.x) * ParticleRight; finalPosition.xyz += ((0.5f - input.inTexCoords.y) * size.y) * ParticleUp;
失败代码的问题分析
你尝试的旋转代码存在两个核心问题:
- 错误构建了旋转矩阵:将右、上、相机方向作为矩阵行,不符合3D旋转矩阵的结构逻辑,导致顶点变换混乱。
- 旋转方式错误:直接修改
rotatedVertex.x不是绕X轴旋转的正确做法——绕X轴旋转时,X分量保持不变,应修改Y和Z分量。
正确实现方案
方案一:旋转Billboard的上向量
先计算基础的Billboard坐标系,再对其中的上向量应用绕X轴的旋转,最后用旋转后的向量计算顶点偏移:
output.PositionWS = mul(float4(input.inPositionOS, 1), World).xyz; // 计算粒子到相机的归一化方向 float3 ParticleToCamera = normalize(output.PositionWS - CameraPosWS); // 计算Billboard的右向量(世界上向量叉乘相机方向,确保方向正确) float3 ParticleRight = normalize(cross(float3(0, 0, 1), ParticleToCamera)); // 计算原始的Billboard上向量(相机方向叉乘右向量) float3 ParticleUp = cross(ParticleToCamera, ParticleRight); // 构建绕X轴(Billboard右向量)的旋转矩阵 float cosRot = cos(rotation); float sinRot = sin(rotation); float3x3 rotXMatrix = float3x3( 1, 0, 0, 0, cosRot, -sinRot, 0, sinRot, cosRot ); // 将原始上向量绕X轴旋转 float3 RotatedParticleUp = mul(ParticleUp, rotXMatrix); // 计算最终顶点位置 finalPosition.xyz = output.PositionWS; finalPosition.xyz += ((input.inTexCoords.x - 0.5f) * size.x) * ParticleRight; finalPosition.xyz += ((0.5f - input.inTexCoords.y) * size.y) * RotatedParticleUp;
方案二:先旋转局部顶点再转换到世界空间
先在Billboard局部空间生成顶点偏移,绕X轴旋转后再转换到世界空间:
output.PositionWS = mul(float4(input.inPositionOS, 1), World).xyz; float3 ParticleToCamera = normalize(output.PositionWS - CameraPosWS); float3 ParticleRight = normalize(cross(float3(0, 0, 1), ParticleToCamera)); float3 ParticleUp = cross(ParticleToCamera, ParticleRight); // 将纹理坐标转换为-0.5到0.5的局部偏移范围 float2 localUVOffset = input.inTexCoords - 0.5f; // 局部空间的顶点偏移 float3 localVertex = float3(localUVOffset.x * size.x, localUVOffset.y * size.y, 0); // 绕X轴旋转局部顶点 float cosRot = cos(rotation); float sinRot = sin(rotation); float3 rotatedLocalVertex = float3( localVertex.x, // X分量保持不变 localVertex.y * cosRot - localVertex.z * sinRot, localVertex.y * sinRot + localVertex.z * cosRot ); // 转换到世界空间并得到最终位置 finalPosition.xyz = output.PositionWS + rotatedLocalVertex.x * ParticleRight + rotatedLocalVertex.y * ParticleUp;
关键要点
- 确保Billboard的右、上向量计算方向正确,叉乘顺序会影响向量方向,需根据你的坐标系调整。
- 绕X轴旋转时,Billboard的右向量作为旋转轴保持不变,仅需旋转上向量或局部空间的Y分量。
- 旋转角度需保持弧度制,与你传入的
rotation参数类型一致。
内容的提问来源于stack exchange,提问作者echo
相关产品推荐
相关产品推荐

