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

在HLSL计算着色器中实现Quaternion.FromToRotation修复草实例旋转异常

草实例化系统旋转侧翻问题排查提示

在计算着色器开发草实例化系统时,需要让每片草叶根据地面法线旋转,但无法使用Unity内置的Quaternion.FromToRotation等函数,自行实现后所有草实例出现侧翻错误。

原CPU射线检测实现代码

此前通过射线检测实现时使用的旋转计算函数:

private Quaternion GetRotationFromNormal(Vector3 normal)
{
    Vector3 eulerIdentiy = Quaternion.ToEulerAngles(Quaternion.identity);
    eulerIdentiy.x += 90; //can be removed or changed, depends on your mesh orientation

    if (_randomYAxisRotation) eulerIdentiy.y += UnityEngine.Random.Range(-_maxYRotation, _maxYRotation);

    return Quaternion.FromToRotation(Vector3.up, normal) * Quaternion.Euler(eulerIdentiy);
}

地形法线计算代码

已验证草叶位置计算正常,当前地形法线计算代码:

float3 CalculateTerrainNormal(float2 uv)
{
    float texelSize = 1.0 / (float) terrainHeightmapResolution;

    float heightL = GetTerrainHeight(uv + float2(-texelSize, 0.0));
    float heightR = GetTerrainHeight(uv + float2(texelSize, 0.0));
    float heightD = GetTerrainHeight(uv + float2(0.0, -texelSize));
    float heightU = GetTerrainHeight(uv + float2(0.0, texelSize));

    float3 tangentX = float3(2.0 * texelSize, heightR - heightL, 0.0);
    float3 tangentZ = float3(0.0, heightU - heightD, 2.0 * texelSize);

    return normalize(cross(tangentZ, tangentX));
}

当前计算着色器旋转实现代码

自行实现的四元数相关函数及草叶旋转计算:

float4 EulerToQuaternion(float pitch, float yaw, float roll) {
    float pitchRad = radians(pitch);
    float yawRad = radians(yaw);
    float rollRad = radians(roll);

    float cy = cos(yawRad * 0.5);
    float sy = sin(yawRad * 0.5);
    float cp = cos(pitchRad * 0.5);
    float sp = sin(pitchRad * 0.5);
    float cr = cos(rollRad * 0.5);
    float sr = sin(rollRad * 0.5);

    float4 q;
    q.x = sr * cp * cy - cr * sp * sy;
    q.y = cr * sp * cy + sr * cp * sy;
    q.z = cr * cp * sy - sr * sp * cy;
    q.w = cr * cp * cy + sr * sp * sy;

    return q;
}

float4 AxisAngleToQuaternion(float3 axis, float angle)
{
    float halfAngle = angle * 0.5;
    float s = sin(halfAngle);
    
    float4 q;
    q.xyz = axis * s;
    q.w = cos(halfAngle);
    
    return q;
}

float4 FromToQuaternion(float3 from, float3 to)
{
    float3 axis = normalize(cross(from, to));
    float dotProduct = dot(from, to);
    float angle = acos(dotProduct);      
    
    return AxisAngleToQuaternion(axis, angle);
}

//...
// For each grass blade: 

float3 normal = CalculateTerrainNormal(uv);

float4 rotationToNormal = FromToQuaternion(float3(0.0, 1.0, 0.0), normal);
        
//For random y rotation
float angle = Random11(instanceSeed) * 360.0;
//Somwhow messed up here to since x has to be used as y axis
float4 yRotation = EulerToQuaternion(angle, 0, 90.);
float4 finalRotation = normalize(rotationToNormal * yRotation); 

float4x4 instanceTransform = CreateTRSMatrix(instancePos, finalRotation, scale);
// ...

注:设置float4 yRotation = EulerToQuaternion(angle, 0, 90.);中的90是因为3D模型初始朝向问题所需的偏移。


排查关键点

  • 四元数乘法顺序错误:Unity中Quaternion乘法是右乘逻辑(a * b表示先应用b的旋转,再应用a的旋转),你当前代码中rotationToNormal * yRotation的顺序和原CPU代码的FromToRotation(...) * Quaternion.Euler(...)逻辑相反,应改为yRotation * rotationToNormal。
  • 欧拉角参数顺序完全颠倒:原CPU代码是给欧拉角X轴加90度偏移,Y轴加随机旋转;但你当前调用EulerToQuaternion(angle, 0, 90.)时,把随机Y轴旋转的角度放到了X轴(pitch)参数,把90度偏移放到了Z轴(roll)参数,正确调用应为EulerToQuaternion(90., angle, 0.)。
  • FromToQuaternion边界情况未处理:当from和to向量几乎平行时,cross(from, to)会得到零向量,normalize后会出现非法值。需补充判断:
    float4 FromToQuaternion(float3 from, float3 to)
    {
        float dotProduct = dot(from, to);
        // 向量几乎同向,返回单位四元数
        if (dotProduct > 0.999999) return float4(0,0,0,1);
        // 向量几乎反向,返回绕X轴旋转180度的四元数
        if (dotProduct < -0.999999) return float4(1,0,0,0);
        
        float3 axis = normalize(cross(from, to));
        float angle = acos(dotProduct);      
        return AxisAngleToQuaternion(axis, angle);
    }
    
  • 四元数乘法实现验证:检查CreateTRSMatrix中或自定义的四元数乘法逻辑是否符合标准四元数乘法规则,错误的乘法实现会直接导致旋转混乱。
  • 法线方向验证:可以临时将法线值输出到颜色缓冲区(比如把normal.xzy映射到RGB范围),确认法线方向是否符合地形起伏,避免法线反转导致旋转错误。

内容的提问来源于stack exchange,提问作者Buttermilch

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 06:23:14