Per Primitive Shading

102 views
Skip to first unread message

Jean-Colas Prunier

unread,
Mar 18, 2023, 2:25:14 PM3/18/23
to Dawn Graphics

My other question relates to shading more. Still porting some old code to test Dawn further, I have been playing around with face-varying normals and per-face shading in general (e.g., give each triangle, for instance, a different color). I have been successfully using vertex_index for other things, but I can't seem to see in the WGSL specs anything that would relate to getting the index of a primitive (e.g. face_index or primitive_index as in gl_PrimitveID) and what buffer mechanism I should use to store and access per-primitive data.

So far, I have used the dumb approach of duplicating the vertex data. It works of course, but I was wondering if you had any idea how to do this more efficiently. Is there anything I missed from the WGSL specs that would be designed for that purpose? Is there a plan/discussion for that?

And, as usual, a great thank you for this wonderful piece of work.


Mark Sibly

unread,
Mar 18, 2023, 7:30:16 PM3/18/23
to Jean-Colas Prunier, Dawn Graphics
Hi,

If you're not using index buffers, you can just use vertex_index / 3 for triangle_index, assuming you're drawing triangles of course.

This won't work if you are using index buffers though, as vertex_index is the vertex buffer index,  but you could potentially use a 'flat varying' along with an int vertex attribute.

This is done in webgpu using the 'interpolate flat' attribute, and it works by using the 3rd (or maybe 1st, don't think it's docced) vertex attribute of each triangle as a 'constant' attribute, ie: shader doesn't interpolate between vertices, just uses the 3rd attribute for all vertices.

The only potentially tricky thing is getting the attribute you want to be 'flat' into the last triangle vertex, which can involve rotating order of triangle vertices if you want to keep triangle count low, and sometimes even adding new triangles, but IME very few new triangles need to be added if you do it right and I suspect in the case of a 'correct manifold' mesh none are needed.

I've used this before in gles for flat shading effects and it works well, and I've checked it works in dawn and it appears to although I've never done anything with it in dawn to date. I was considering attaching a 'materialId' to each triangle this way, but didn't get around to trying it. It's still not easy to create a material array in modern shader languages, unless all your textures are the same size so you can use a texture array, which is probably OK for pros but not for hacks like me who scrape all their textures off the internet, although I guess you could preprocess/resize all textures to be the same size? What would be super nice of course would be 'bind group arrays'!

Bye!
Mark


--
You received this message because you are subscribed to the Google Groups "Dawn Graphics" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dawn-graphic...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/dawn-graphics/8476e4e5-8bbb-45fa-9986-cdcc2eb6191an%40googlegroups.com.

Gregg Tavares

unread,
Mar 19, 2023, 4:48:34 PM3/19/23
to Mark Sibly, Jean-Colas Prunier, Dawn Graphics
> This is done in webgpu using the 'interpolate flat' attribute, and it works by using the 3rd (or maybe 1st, don't think it's docced)


Mark Sibly

unread,
Mar 19, 2023, 5:44:25 PM3/19/23
to Gregg Tavares, Jean-Colas Prunier, Dawn Graphics
Thanks for that, I couldn't find it last time I looked for it.

But it raises a sort of IMO interesting question - how is this achieved in the GLES backend? I remember GLES being the 'odd one out' in that it was the only API that made the 3rd vertex the provoking vertex, which pissed a lot of game-devs off at the time! How does webgpu deal with this?

Corentin Wallez

unread,
Mar 20, 2023, 8:04:34 AM3/20/23
to Mark Sibly, Gregg Tavares, Jean-Colas Prunier, Dawn Graphics
At the moment the provoking vertex in the OpenGL backend is incorrect. We'll see how to implement it efficiently in the future and it might be a place where a compatibility mode could allow changing the provoking vertex to the one that the backend actually uses.

--
You received this message because you are subscribed to the Google Groups "Dawn Graphics" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dawn-graphic...@googlegroups.com.

Jean-Colas Prunier

unread,
Mar 23, 2023, 3:05:56 PM3/23/23
to Dawn Graphics
Thank you so much for your contributions, everyone. Will give it a go whenever possible and provide you with feedback (a bit swamped right now).

Jean-Colas Prunier

unread,
Apr 2, 2023, 10:03:36 AM4/2/23
to Dawn Graphics
Sorry Mark, I only got a chance to get back to this topic now. I will be testing the 'provoking' vertex technique (thanks for pointing it out indeed - I assumed something like this existed but didn't find it so thx). I have been using the vertex_index with triangle indeed for the a shape/text rendering pipeline in Dawn and it worked as expected (well) - and you need to branch in the shader indeed).

I will be testing this and post some results. Once I get it working I will send some more feedback.

On Sunday, March 19, 2023 at 12:30:16 AM UTC+1 mark...@gmail.com wrote:
Message has been deleted
Message has been deleted

Jean-Colas Prunier

unread,
Apr 6, 2023, 4:19:28 AM4/6/23
to Dawn Graphics
So I looked into the provoking vertex, and it works well. Nonetheless, if one is interested in mesh optimization, this wouldn't solve the problem. It would just make flat shading easier. Is there any reason why the WebGPU specs don't have the concept of primitiveId similar to other APIs as it would make it easier than to do a lookup into a normal buffer for flat shading for instance?
I can still use vertex_index as suggested by Mark to calculate an primitive id assuming I have at least declared the mesh as a series of individual triangles. So I'd still have vertex duplication but I wouldn't need to define the normals that way then and could just used the calculated prim id for looking into a buffer. So that's a compromise but it seems like primitiveId like vertex_index is something also important.

Mark Sibly

unread,
Apr 6, 2023, 5:38:51 PM4/6/23
to Jean-Colas Prunier, Dawn Graphics
Hi Jean-Colas ,

I'm not really sure what you're trying to do, but since you can store whatever 'per primitive' attributes you want in the provoking vertex (eg: a normal if you want to do flat shading, like I've done) you should be able to simulate 'primitve_id' using a simple int attribute that just increments every face if that's your goal. That said, I've never tried it...

But this does mean an extra int per vertex and more 'vertex sorting shenanigans' so I agree it's weird webgpu doesn't have primitive_index if all the underlying APIs have it, and they all appear to AFAICT, although in GLES it's only supported in 3.2 so perhaps that's the deal breaker? And I suspect GLES is quite an important target for the mobile side of things.

Does anyone know if there's some kind of minimum version required by the GL backends, or does it all just come down to the usual 'extensions bingo'?

Bye,
Mark

Corentin Wallez

unread,
Apr 7, 2023, 6:24:43 AM4/7/23
to Mark Sibly, Jean-Colas Prunier, Dawn Graphics
stiHey all,

I haven't followed the thread in details, but @builtin(primitive_id) might be possible to support, but not part of the core feature set of WebGPU. See https://github.com/gpuweb/gpuweb/issues/1786 Note also that just using this builtin can result in a large performance degradation as on some hardware it can force the introduction of a geometry shader stage (see one of the comments on that issue).

Cheers,

Corentin

--
You received this message because you are subscribed to the Google Groups "Dawn Graphics" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dawn-graphic...@googlegroups.com.

j...@jean-colas.com

unread,
Apr 7, 2023, 9:31:13 AM4/7/23
to Mark Sibly, Dawn Graphics
Thanks Corentin for the link.

I agree with you Mark, there are workarounds for sure, but while there are more complicated things in coding), this would introduce an extra step that would not be necessary, if you had something like the primitiveId. Furthermore it seems like a natural extension to the vertex_index and would make that lookup in a buffer as suggested before really easy for many things (flat shading to start with in my case which is very common for geometry representing objects).

I will look at the link pointed by Corentin. I hope that performance hit can go away in the future. I will see which hardware is concerned by this / which framework.

Thanks again and sorry to have spammed you on that thread a bit).

--
You received this message because you are subscribed to the Google Groups "Dawn Graphics" group.
To unsubscribe from this group and stop receiving emails from it, send an email to dawn-graphic...@googlegroups.com.

Mark Sibly

unread,
Apr 7, 2023, 8:48:00 PM4/7/23
to j...@jean-colas.com, Dawn Graphics
Hi Jean-Colas,

I have very much enjoyed this discussion (and all webgpu discussions) as it's really the only time I ever get to discuss this stuff at all, so please don't worry about spamming! I have learned a lot in the process, and I think I appreciate the provoking vertex system much more after this discussion.

I do agree using provoking vertex to simulate primitive_id is a hacky work around, but I also now think provoking vertex is actually more useful than primitive_id, as it allows you to store the array data you'd be indexing with primitive_id directly into your vertex data (assuming this is what you're doing with primitive_id - but what else can you do with it?)  which eliminates a level of indirection. And while provoking vertex does involve an extra setup step, so does primitive_id as the wgpu::buffer containing the data to be looked up needs to be created and maintained etc.

Anyway, it's a lazy Easter Saturday morning so I've had a crack at writing a (completely untested!) provoking vertex demo just in case anyone's worried it's a hugely complex algorithm. I remember thinking it was gonna be a major PITA when I first attempted it a few years back, but it's really not. And remember you can just write the per-primitive attributes directly into your vertices this way and don't have to worry about an external normal array buffer or anything, so using it to generate primitiveId is actually a waste of time IMO, but probably makes for a good enough demo:

// Our simple model format!

struct Vertex {
    Vec3f position;
    Vec3f normal;
    Vec2f texcoord;
    uint32_t primitiveId;
};

struct Triangle {
    uint32_t vertices[3];
};

void assignPrimitiveIds(std::vector<Vertex>& vertices, std::vector<Triangle>& triangles) {

    std::vector<bool> provoking(vertices.size());

    uint32_t primitiveId = 0;

    for (auto& tri : triangles) {

        // Find first non-provoking triangle vertex
        int i = 0;
        while (i < 3 && provoking[tri.vertices[i]]) ++i;

        if (i == 3) {
            // Couldn't find a non-provoking vertex, need to create a new one...
            vertices.push_back(vertices[tri.vertices[0]]);
            tri.vertices[0] = provoking.size();
            provoking.push_back(false);
        } else if (i ) {
            // Found a non-provoking vertex but it's not first triangle vertex, need to fix this...
            auto tmp = tri;
            for (int j = 0; j < 3; ++j) tri.vertices[j] = tmp.vertices[(j + i) % 3];
        } else {
            // First triangle vertex is already non-provoking, nothing to do!
        }

        // OK, provoking vertex is ready to use!
        auto& provokingVertex = vertices[tri.vertices[0]];

        // Kind of pointless IMO, may as well assign what you're planning to 'look-up'
        // with primitiveId here instead, eg: normal, tri.materialId etc...
        provokingVertex.primitiveId = primitiveId++;

        // This vertex is now provoking...
        provoking[tri.vertices[0]] = true;
    }
}

Bye!
Mark

Reply all
Reply to author
Forward
0 new messages