The simplest code to draw a triangle?

Hi,

I totally new to 3D programming. Thanks to the manual I can load a texture, a font, a model, an actor and make it moves and even use directgui, but when it comes to vertex, geom, shaders I’m lost. I feel totally dumb :frowning_face: The snippets I found are always to complicated to me but the manual only gives atomic example for each directive : I don’t know how to assemble the directives to draw a simple triangle.

Can you please guy show me how to draw a triangle ?:scream:

Thanks.

The easiest way to draw a triangle is to model it in something like Blender and then load the model in Panda. Here is a section from the Hello World tutorial in the manual on loading models.

If you need to generate the triangles on the fly (e.g., procedural geometry), then things get quite a bit more involved. I do not know of a good example of this, but maybe this bit of code from panda3d-gltf can help you out.

Yes, my goal is to make procedural geometry. Your example is too complicated for me at this time, I lack the basic.

Thanks.

Looks like one of the sample programs does procedural geometry; maybe that’s a bit easier to look through?

To create a triangle procedurally, you start off by choosing a vertex format; this format determines what kind of attributes your triangle vertices can have, such as:

  • a position (this is always needed);
  • a lighting normal for correct shading of the triangle;
  • texture coordinates;
  • a color (not often needed, especially if you’re going to use textures, materials and/or a “scenegraph color”);
  • tangent-space vectors (needed if you want to apply a normal map to your geometry);
  • some custom attributes, often for use with shaders.

For the following small tutorial, let’s use a simple, predefined format which allows you to define position and normal for each vertex:

vertex_format = GeomVertexFormat.get_v3n3()

Now you need to build up a table of vertex data, filled with rows containing the values for each of those aforementioned attributes: the GeomVertexData.

vertex_data = GeomVertexData("triangle_data", vertex_format, Geom.UH_static)

To add the rows, you can use a GeomVertexWriter for each attribute of the three triangle vertices:

pos_writer = GeomVertexWriter("vertex")
normal_writer = GeomVertexWriter("normal")
normal = Vec3(0., -1., 0.)

pos_writer.add_data3(-1., 0., -1.)
pos_writer.add_data3(1., 0., -1.)
pos_writer.add_data3(0., 0., 1.)

# since the normal is the same for each vertex, just add it three consecutive times
for _ in range(3):
    normal_writer.add_data3(normal)

The normal in this case is very straightforward: it points in the direction of the negative Y-axis. With more complicated shapes, you’ll need to compute it, using two vectors pointing from one vertex to another.

Now that you have the data, you need to build up geometry structures to make use of it.
First there’s the GeomPrimitive, specifically GeomTriangles:

prim = GeomTriangles(Geom.UH_static)

Now comes an important step: you need to add the vertices (actually the indices of the vertex data rows) in counter-clockwise winding order to the primitive, as seen when looking straight at the side of the triangle that you want to be rendered (the other side will be “culled”, i.e. not rendered). In this example, that index order would be (0, 1, 2), i.e. the order the rows were added to the vertex data table. That makes it possible to do:

prim.add_next_vertices(3)

It’s not always that easy, though, and then you’ll need to be more explicit, like so:

prim.add_vertices(0, 1, 2)

Phew :slight_smile: ! Finally you need to put that primitive inside a Geom, add that Geom to a GeomNode and wrap that GeomNode into a NodePath that can ultimately be attached to the scenegraph:

geom = Geom(vertex_data)
geom.add_primitive(prim)
node = GeomNode("my_triangle")
node.add_geom(geom)
triangle = NodePath(node)
triangle.reparent_to(some_other_nodepath)

That’s it :slight_smile: !

If you want more info or some better understanding of that winding order, please check out my proposed new geometry samples (start with basic.py), which are an attempt to improve upon the outdated sample that @Moguri just linked to and which is actually not a very good sample, for several reasons.

1 Like

Yes, the cube was a good start. I reduced the cube code at max, and the result was similar to Epihaius answer but without explanations :smiley:

Thanks you all,

Best regards.

New dumb question… Why do you add 3 normals (one for each vertex) ? Why not a single normal for the whole triangle’s face ?

In Panda, you can’t define face normals, only vertex normals. This makes sense, because it are the vertex normals that determine the lighting of the geometry, not the face normals.

Ok, then I have 2 new questions… hope I’m not too off topic.

1/ Why in the procedural_cube example each vertex of a single square have a different normal ? Shouldn’t the 4 vertices of a square have the same normal?

2/ I spend my last 2 hours to understand why there is a need of a normal for each vertex, and I found that it’s needed to make smooth shading (https://www.scratchapixel.com/lessons/3d-basic-rendering/introduction-to-shading/shading-normals)
Does it mean that I cant make a flat shading per face with a GeomTrifans ?

Yes, they should have the same normal. That’s one of those reasons why I find this to be a bad example of geometry creation. (Admittedly, it might have been the author’s intent to make the object look smooth, but then it’s not really a cube.) Number two is that some of the quads are facing the wrong way (leave out the call to setTwoSided and you’ll see); remember the importance of winding order? Number three is that multiple Geoms are created, while it could easily have been done with just a single GeomTriangles in just one Geom.
Another thing that is wrong with this sample is calling addData4f and addData2f; if Panda is compiled to use double precision, these calls will make your application crash. Call addData4 and addData2 instead and it won’t matter how Panda is compiled.
And the example code is also outdated in the sense that the custom normalized helper function is no longer required, as Vec3 now has this method built-in.

Sure you can achieve flat shading, by using the same normal for each vertex of the same face. Do keep in mind though, that vertices cannot be shared by adjacent faces in this case. You’ll need duplicate vertices, since each vertex can only have a single normal, otherwise unwanted smoothing will occur. For example, a cube will need 24 vertices (4 for each of the 6 sides), while 8 would suffice to create the shape of the cube.