Drawing Sphere with Metal

Is there a way to draw a sphere in metal by calculating points and then connecting them using triangles?

Hi @hcrogman, and welcome to these forums!

The short answer is usually “yes” :grimacing:

It really depends what you want to do. You can use

let sphereMesh = MDLMesh(
  sphereWithExtent: [0.4, 0.4, 0.4], 
  segments: [10, 10], 
  inwardNormals: false, 
  geometryType: .triangles, 
  allocator: allocator)

to create a sphere instead of having to calculate vertices.

You can load a sphere obj that you created in Blender.

You can create an MTLBuffer of calculated vertices, just as you can do with one triangle as in this tutorial: https://www.raywenderlich.com/7475-metal-tutorial-getting-started.

This looks like a place to start with a generating sphere algorithm, if that’s what you really want to do: c++ - OpenGL calculate UV sphere vertices - Game Development Stack Exchange.

1 Like

Thank you.
I understand that but the reason I am confused is because I want my sphere to become deformed but only way know how do that is calculate the vertices… this what I did in opengl. So how do I do this in metal? This would mean my radius is not fixed.

I can’t visualise what you mean.

I would guess that you would do it in Metal much the same way as you did in OpenGL. Did you create a vertex buffer and send it to the GPU? You can do the same in Metal.

What did your OpenGL shaders do?

I have calculate the points for the sphere but I am not clear create the vertex buffer from here.

Sorry @hcrogman - I missed this question.

To create a vertex buffer, you use:

let buffer = Renderer.device.makeBuffer(
                        bytes: InsertYourData,
                        length: MemoryLayout<YourDataLayout>.stride)

This project has a file Primitive.swift which contains an example of creating a cube mesh compatible with indexed drawing.

MeshGeneration.zip (179.4 KB)

If you don’t calculate vertex indices, you can do non-indexed drawing with:

renderEncoder.drawPrimitives(type:vertexStart:vertexCount:)
1 Like

Primitive.swift
WOW!!! I’ve always wondered how to create indexed geometry…

1 Like

I am still have problem: I made this modification in MeshGeneration… and I get a black output:
static func buildSphere(size: float3) → Mesh {
var submeshes: [Submesh] = []
let vertexArray: [Float] = matrixobj.computeMypoints(arg1: 5, arg2: 100)

    let vertexBuffer = Renderer.device.makeBuffer(bytes: vertexArray,
                                                  length: MemoryLayout<Vertex>.stride * vertexArray.count,
                                                  options: [])!
     let mesh = Mesh(vertexBuffer: vertexBuffer, submeshes: submeshes)
    return mesh
}

I set var submeshes: [Submesh] = []… because I am thinking to indexing would be quite difficult.
computeMypoints just computes points for a sphere in return a matix of points… Why can ge these point to plot.

Thank you

You don’t need submeshes to draw, if you’re not using indexing, but the Mesh code doesn’t draw unless there is at least one Submesh, so you’d have to extract the draw from that loop.

How about reviewing the early code in Chapter 4 where you set up individual vertices, and then draw a triangle?

You should just be able to put vertices in a buffer in the same way and render them.

With your code, instead of having sphere vertices, just make up a quad and render that. Then you can use Geometry in the GPU debugger to debug what’s happening to each of your vertices.

This is a cut-down version of the playground in Chapter 4 that renders one triangle.

3DTransforms.playground.zip (20.0 KB)

I have tried to create sphere but was unsuccessful. I calculate all my points. Then create an index for them but it never draws sphere. Do you know someone that I can pay $100 to help me … solve this problem…

This is a simple app that creates vertices for a sphere, using the code from this stackoverflow answer:

The vertices are not indexed.

To prevent any confusion, it doesn’t have any model / view / projection matrices in it. The sphere’s vertices go from -1 to 1. So to make it render as a sphere, you have to make your window square, otherwise the sphere is stretched.

init(device:latitude:longitude:radius) in Sphere.swift is where the sphere vertex buffer is created.

Sphere.zip (72.3 KB)

Screen Shot 2021-07-11 at 11.52.18 am

This project needs Xcode 13 and uses SwiftUI rather than storyboards.

Desktop/Sphere /Sphere.xcodeproj’ cannot be opened because it is in a future Xcode project file format. Adjust the project format using a compatible version of Xcode to allow it to be opened by this version of Xcode.

I am getting this error and I have no how to make it work. The project is not opening… I am attempting download xcode 10 since I am running the newest xcode. What advise you have or what version of xcode this was created in?

Thanks

I created it in Xcode 13 beta. If you don’t have that, I will do it in Xcode 12

1 Like

This is an Xcode 12 version that uses Storyboards:

Sphere.zip (59.7 KB)

1 Like

Hello Caroline
Thank you for demonstrating how to do this.

However, I am having trouble with shader to incorporate into chapter 4 … rotations.
This is th changes I have made
struct VertexIn {

packed_float3 position;

} ;

struct VertexOut {

float4 position [[position]];

float pointSize [[point_size]];

};

vertex VertexOut vertex_sphere(uint vertexID [[vertex_id]],const device VertexIn* vertex_array [[buffer(0)]], constant Uniforms &uniforms [[buffer(1)]]) {

/*

VertexOut out = {

float4(vertexIn[vertexID], 1),

5.0

};

*/

VertexOut out;

VertexIn VertexIn = vertex_array[vertexID];

out.position = uniforms.projectionMatrix * uniforms.viewMatrix

  • uniforms.modelMatrix * float4(VertexIn.position,1);

out.pointSize = 5.0;

return out;

}

fragment float4 fragment_sphere() {

return float4(0.3, 0.3, 0.3, 1);

}

This is where problem:
renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: sphere.vertexCount).

-[MTLDebugRenderCommandEncoder validateCommonDrawErrors:]:5252: failed assertion `Draw Errors Validation

I am lost on why this does not wo

It’s hard to tell from a small code snippet as I don’t know what you’ve set up in the render command encoder. Are you able to upload a zip?

Sphere.zip (121.6 KB)
Hello Caroline
Please see the zip file attached.
Thanks
Horace

The macOS Target needs a Bundle Identifier eg:

Screen Shot 2021-07-18 at 10.01.36 am

Without it the, app doesn’t start.

With it, you get a render, but then you need to change packed_float3 to float3 in VertexIn in Shaders.metal.

Sphere-new.zip (132.2 KB)
Thanks again Caroline.

As following with the Metal tutorial book … I was trying integrate the Sphere into chapter 4 code… your last comunication was quite helpful.

However, I though I have a good grip on the shader but it turns out this is not the case. the changes I have made produces the sphere as seen below… the horizontal circles are gone. The changes I made is to integrate the sphere into chapter 5 tutorial

image

If comment out the the following line also:
renderEncoder.setTriangleFillMode(.lines)

It disappear. This is strange… I am guessing it had something to do with attribute. i.e.

struct VertexIn {
//float3 position;
float4 position[[attribute(0)]];
float3 normal [[attribute(1)]];
} ;

I am lost here again.

From the vertex descriptor, your VertexIn is expecting a buffer of

[position, normal, position, normal, position, ...]

Your buffer is actually

[position, position, position, position, position, ...]

So the shader considers that every other position is a normal, which is why not all vertices are rendered.

You can check this by adding a float3 for the normals when you add the positions in Sphere.swift.

vertices.append(p0)
vertices.append([0, 1, 0])
vertices.append(p2)
vertices.append([0, 1, 0])
vertices.append(p1)
vertices.append([0, 1, 0])

vertices.append(p3)
vertices.append([0, 1, 0])
vertices.append(p1)
vertices.append([0, 1, 0])
vertices.append(p2)
vertices.append([0, 1, 0])

Your sphere will now render all its triangles.

These aren’t the correct normals. As you’re not using objs, you’d have to calculate them. This is not something I’ve done before.