I’m trying to achieve the exact same result with Unity’s built-in ‘Mesh.RecalculateNormals()’ method.
I can get the same result in a C# script. (script attached below)
I can not get the same result even though I’m using the same algorithm. (compute script attached below)
Can someone point out the thing I’m missing or doing wrong? (picture of a sample result with compute shader attached below)
Some notes:
- I get different results every time I dispatch Compute Shader.
- I have used Unity’s default sphere and a simple sphere created in blender. Same results.
- My ambient light color is black, that is why bottom half of the sphere is complete black. It does not affect the results.
Here’s how I calculate normals in C# - CPU:
private void CalculateNormalsCPU()
{
var sphereMesh = MeshFilter.mesh;
var vertices = sphereMesh.vertices;
var triangles = sphereMesh.triangles;
var triangleCount = triangles.Length / 3;
var normals = new Vector3[vertices.Length];
for (var i = 0; i < triangleCount; i++)
{
var triangleIndex = i * 3;
var vertex1 = vertices[triangles[triangleIndex]];
var vertex2 = vertices[triangles[triangleIndex + 1]];
var vertex3 = vertices[triangles[triangleIndex + 2]];
var side1 = vertex2 - vertex1;
var side2 = vertex3 - vertex1;
var triangleNormal = Vector3.Normalize(Vector3.Cross(side1, side2));
normals[triangles[triangleIndex]] += triangleNormal;
normals[triangles[triangleIndex + 1]] += triangleNormal;
normals[triangles[triangleIndex + 2]] += triangleNormal;
}
for (int i = 0; i < vertices.Length; i++)
{
normals[i] = normals[i].normalized;
}
sphereMesh.normals = normals;
}
Here is how I prepare and dispatch my Compute Shader:
private void CalculateNormalsComputeShader()
{
var sphereMesh = MeshFilter.mesh;
var vertexCount = sphereMesh.vertexCount;
var triangleCount = sphereMesh.triangles.Length / 3;
sphereMesh.normals = new Vector3[vertexCount];
var trianglesBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, sphereMesh.triangles.Length, sizeof(int));
trianglesBuffer.SetData(sphereMesh.triangles);
sphereMesh.vertexBufferTarget |= GraphicsBuffer.Target.Raw;
var vertexBuffer = sphereMesh.GetVertexBuffer(0);
DowngradeComputeShader.SetInt("VertexCount", vertexCount);
DowngradeComputeShader.SetInt("TriangleCount", triangleCount);
DowngradeComputeShader.SetInt("Stride", sphereMesh.GetVertexBufferStride(0));
DowngradeComputeShader.SetBuffer(0, "Triangles", trianglesBuffer);
DowngradeComputeShader.SetBuffer(0, "VertexBuffer", vertexBuffer);
DowngradeComputeShader.Dispatch(0, Mathf.CeilToInt(triangleCount / 64f), 1, 1);
DowngradeComputeShader.SetBuffer(1, "VertexBuffer", vertexBuffer);
DowngradeComputeShader.Dispatch(1, Mathf.CeilToInt(vertexCount / 64f), 1, 1);
vertexBuffer.Dispose();
trianglesBuffer.Dispose();
}
Here is the Compute Shader:
#pragma kernel CalculateNormals
#pragma kernel NormalizeNormals
#define PI 3.14159265359
#define TAU 6.28318530718
uint VertexCount;
uint TriangleCount;
uint Stride;
RWByteAddressBuffer VertexBuffer;
StructuredBuffer<uint> Triangles;
[numthreads(64,1,1)]
void CalculateNormals (uint3 id: SV_DispatchThreadID)
{
if (id.x >= TriangleCount) return;
uint triangleIndex = id.x * 3;
uint indexVertex1 = uint(Triangles[triangleIndex]);
uint indexVertex2 = uint(Triangles[triangleIndex + 1]);
uint indexVertex3 = uint(Triangles[triangleIndex + 2]);
float3 vertex1 = asfloat(VertexBuffer.Load3(indexVertex1 * Stride));
float3 vertex2 = asfloat(VertexBuffer.Load3(indexVertex2 * Stride));
float3 vertex3 = asfloat(VertexBuffer.Load3(indexVertex3 * Stride));
float3 side1 = vertex2 - vertex1;
float3 side2 = vertex3 - vertex1;
float3 triangleNormal = normalize(cross(side1, side2));
float3 normalVertex1 = asfloat(VertexBuffer.Load3(indexVertex1 * Stride + 12));
VertexBuffer.Store3(indexVertex1 * Stride + 12, asuint(normalVertex1 + triangleNormal));
float3 normalVertex2 = asfloat(VertexBuffer.Load3(indexVertex2 * Stride + 12));
VertexBuffer.Store3(indexVertex2 * Stride + 12, asuint(normalVertex2 + triangleNormal));
float3 normalVertex3 = asfloat(VertexBuffer.Load3(indexVertex3 * Stride + 12));
VertexBuffer.Store3(indexVertex3 * Stride + 12, asuint(normalVertex3 + triangleNormal));
}
[numthreads(64, 1, 1)]
void NormalizeNormals (uint3 id: SV_DispatchThreadID)
{
if (id.x >= VertexCount) return;
uint vid = id.x * Stride;
float3 normal = asfloat(VertexBuffer.Load3(vid + 12));
VertexBuffer.Store3(vid + 12, asuint(normalize(normal)));
}