I’m using youtube tutorials like a good indie dev to get a compute shader to generate grass. A major difference from the tutorial is I already have my list of world vertices easily because I’m generating my terrain at runtime, but the tutorial assumes a predefined mesh which needs its vertices converted to world coordinates. I already have my vertices in world coordinates which should make this simpler.
However running the below compute shader, if I do matrix multiplication like (rotation X scale&position), everything scales and rotates properly but the positions are all totally wrong. If I multiply (scale&position X rotation), then everything is positioned exactly where it should be but the scale is applied after the rotation, so the meshes are all distorted shapes. I’ve tried transposing things with no luck.
I’ve already adjusted the original code a bit. Right now it’s not doing things the way it should, like it still is using the number of triangles when that won’t be relevant for my purposes, but at the core of it I just want it to output the mesh orientated and position correctly.
#pragma kernel CalculateBladePositions
StructuredBuffer<float3> _TerrainPositions;
RWStructuredBuffer<float4x4> _TransformMatrices;
uniform int _TerrainTriangleCount;
uniform float _Scale;
uniform float _MinBladeHeight;
uniform float _MaxBladeHeight;
uniform float _MinOffset;
uniform float _MaxOffset;
uniform float4x4 _TerrainObjectToWorld;
//uniform float3 _LookDirection; // New uniform for the direction the object should look in
#define TWO_PI 6.28318530718f
// Function that takes a 2-element seed and returns a random value
// between the min and max bounds.
float randomRange(float2 seed, float min, float max)
{
float randnum = frac(sin(dot(seed, float2(12.9898, 78.233)))*43758.5453);
return lerp(min, max, randnum);
}
// Function to rotate around the y-axis by a specified angle.
float4x4 rotationMatrix(float3 anglesDeg)
{
float sX, cX, sY, cY, sZ, cZ;
sincos(anglesDeg.x, sX, cX);
sincos(anglesDeg.y, sY, cY);
sincos(anglesDeg.z, sZ, cZ);
float4x4 rotationX = float4x4(1, 0, 0, 0,
0, cX, sX, 0,
0, -sX, cX, 0,
0, 0, 0, 1);
float4x4 rotationY = float4x4(cY, 0, -sY, 0,
0, 1, 0, 0,
sY, 0, cY, 0,
0, 0, 0, 1);
float4x4 rotationZ = float4x4(cY, sY, 0, 0,
-sY, cY, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
return rotationX * rotationY * rotationZ;
}
// This kernel calculates transformation matrices for each grass blade
// to place them in different positions on the terrain mesh.
[numthreads(64, 1, 1)]
void CalculateBladePositions(uint3 id : SV_DispatchThreadID)
{
// Avoid running 'overflow' tasks when the number of tasks
// wasn't divisible by the number of threads.
if (id.x > _TerrainTriangleCount)
{
return;
}
float2 randomSeed1 = float2(id.x, id.y);
float2 randomSeed2 = float2(id.y, id.x);
float scaleY = _Scale * _MaxBladeHeight;
float4x4 grassTransformMatrix = float4x4
(
_Scale, 0, 0, _TerrainPositions[id.x].x,
0, scaleY, 0, _TerrainPositions[id.x].y,
0, 0, _Scale, _TerrainPositions[id.x].z,
0, 0, 0, 1
);
float4x4 randomRotationMatrix = rotationMatrix(float3(randomRange(randomSeed1, 0.0f, TWO_PI), randomRange(randomSeed1, 0.0f, TWO_PI), randomRange(randomSeed1, 0.0f, TWO_PI)));
_TransformMatrices[id.x] = mul(randomRotationMatrix, grassTransformMatrix);
//_TransformMatrices[id.x] = mul(grassTransformMatrix, randomRotationMatrix);
}
Below is from the actual .shader file. The matrices from above are assigned to objectToWorld becase that’s what the original code was doing. What confuses me is why this all seems to include the position twice, and why that works anyway as long as I do the matrix calculation a certain way. I’ve meshed with this a bunch and basically nothing but exactly this code will output anything at all (and it’s funny because I can see the number of triangles in the scene jump, but I don’t actually see them anywhere)
v2f vert(appdata v)
{
v2f o;
float4 positionOS = float4(_Positions[v.vertexID], 1.0f);
float4x4 objectToWorld = _TransformMatrices[v.instanceID];
o.positionWS = mul(objectToWorld, positionOS);
o.positionCS = mul(UNITY_MATRIX_VP, o.positionWS);
o.uv = _UVs[v.vertexID];
return o;
}