I'm working on a grass instancer using compute shaders. Each grass blade shall be rotated to the ground normal.
The problem: I can't use Unity functions like Quaternion.FromToRotation
and they musst be implemented on the compute shader and I don't know where I messed up.
Currently all instances are rotated on their side.
Before doing this on a compute shader I was using raycasts and I've used this function:
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);
}
Each grass blade has a position that I use to calculate the terrain UVs
for sampling the heightmap and I have already verified that this step works.
The calculation of the normal looks like this (GetTerrainHeight
is just a wrapper for sampling the texture):
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));
}
Each grass blade rotation is calculated like this:
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);
// ...
Note: when setting float4 yRotation = EulerToQuaternion(angle, 0, 90.);
, the 90 is needed as an offset since my 3D model is not oriented correctly.
I can't figure out what exaclty is wrong here. Any hints would be apprecieated.
It's a bit difficult to follow your code and I'm not trying to verify your euler angles to quaternion conversion. However this line:
float4 finalRotation = normalize(rotationToNormal * yRotation);
doesn't make much sense. In HLSL when you multiply two float4
with each other, you're doing a component wise multiplication (Hadamard product). This is not how you multiply two quaternions with each other.
A quick google search gave me this quaternion library for HLSL. I can't verify if this is implemented 100% correct, but qmul
looks right at first glance.
Quaternions are not just 4d vectors. It has 3 imaginary units and the multiplication isn't that straight forward. HLSL doesn't have a built-in way to multiply quaternions as there is no built-in hamilton product in place.