我正在使用计算着色器开发草实例器。每个草叶应正常旋转至地面。
问题:我无法使用像
Quaternion.FromToRotation
这样的Unity函数,它们必须在计算着色器上实现,我不知道我在哪里搞砸了。
目前,所有实例均侧向旋转。
在计算着色器上执行此操作之前,我使用光线投射并且使用了此函数:
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 模型方向不正确。
我不知道这里到底出了什么问题。任何提示将不胜感激。
遵循您的代码有点困难,我并不是想验证您的欧拉角到四元数的转换。然而这一行:
float4 finalRotation = normalize(rotationToNormal * yRotation);
没有多大意义。在 HLSL 中,当您将两个
float4
彼此相乘时,您正在执行分量乘法(Hadamard 乘积)。这不是两个四元数相乘的方式。
快速谷歌搜索给了我这个 HLSL 的四元数库。我无法验证这是否 100% 正确实现,但是
qmul
乍一看是正确的。
四元数不仅仅是 4d 向量。它有 3 个虚数单位,乘法并不是那么简单。 HLSL 没有内置的四元数乘法,因为没有内置的 hamilton 产品。