You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;

失败代码的问题分析

你尝试的旋转代码存在两个核心问题:

  1. 错误构建了旋转矩阵:将右、上、相机方向作为矩阵行,不符合3D旋转矩阵的结构逻辑,导致顶点变换混乱。
  2. 旋转方式错误:直接修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 20:40:30