使用计算着色器在 hlsl 中重新创建 Quaternion.FromToRotation

问题描述 投票:0回答:1

我正在使用计算着色器开发草实例器。每个草叶应正常旋转至地面。

问题:我无法使用像

Quaternion.FromToRotation
这样的Unity函数,它们必须在计算着色器上实现,我不知道我在哪里搞砸了。 目前,所有实例均侧向旋转。 wrong rotation of grass blades

在计算着色器上执行此操作之前,我使用光线投射并且使用了此函数:

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);
    }

每个草叶都有一个位置,我用它来计算用于采样高度图的

terrain UVs
,并且我已经验证了此步骤是否有效。

法线的计算看起来像这样(

GetTerrainHeight
只是采样纹理的包装):

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-game-engine shader hlsl
1个回答
0
投票

遵循您的代码有点困难,我并不是想验证您的欧拉角到四元数的转换。然而这一行:

float4 finalRotation = normalize(rotationToNormal * yRotation);

没有多大意义。在 HLSL 中,当您将两个

float4
彼此相乘时,您正在执行分量乘法(Hadamard 乘积)。这不是两个四元数相乘的方式。

快速谷歌搜索给了我这个 HLSL 的四元数库。我无法验证这是否 100% 正确实现,但是

qmul
乍一看是正确的。

四元数不仅仅是 4d 向量。它有 3 个虚数单位,乘法并不是那么简单。 HLSL 没有内置的四元数乘法,因为没有内置的 hamilton 产品

© www.soinside.com 2019 - 2024. All rights reserved.