1
\$\begingroup\$

Generally when rendering objects you would:

  1. Apply world transformation to go from object space to world space
  2. Apply view transformation to go from world space to camera space
  3. Apply perspective projection transform to go from camera space to canonical view volume

In a normal situation, you only have a single camera and screen to work with so you only have one each of view transform matrix and projection transform matrix to render a frame. So, before the rendering begins, you need to put all this info in a constant buffer. So that the vertex shader can transform the vertices per objects into a world matrix.

Considering you need a (probably unique) world transform matrix for each object, how is this process actually done in practical situations?

The easiest and obvious solution I can think of is to put world matrices in a constant buffer but if I am not mistaken from the architecture of video card memory, you are not able to dynamically allocate constant buffers. I can think of this being a big problem because in real situations, the number of objects to be rendered would vary each frame.

A tutorial I came across handles this situation in a rather concerning way. If object A, B, C exists, instead of passing all objects to the vertex shader, it would go through the following process:

  • Add world matrix of object A into a constant buffer then draw the object A
  • Add world matrix of object B into a constant buffer then draw the object B.
  • Add world matrix of object C into a constant buffer then draw the object C.

This works, but this seems really counter-intuitive from a performance perspective as you have to do a lot of synchronization for a simple rendering process. Not to mention drawing operation becomes O(n) for the number of objects. It also doesn't fit too well with the philosophy of shader process where parallel processing is heavily favored.

So I had a random thought for a potential solution: you would first dynamically allocate memory size of (XMMatrix) * (Object Count) on the GPU, and when transforming from vertex from vertex shader, you would select which matrix to use from list of world matrices. This seems very doable in CUDA but not too sure about HLSL . Is this idea even legit to begin with?

I guess you could also somehow send the vertices of the object located in world space directly to the vertex shader.

\$\endgroup\$

2 Answers 2

6
\$\begingroup\$

Your understanding of the general rendering process seems mostly correct, except that you frequently refer to "passing the object to the vertex shader," which isn't what happens. Instead you set an active vertex shader, along with several other active state objects (textures, uniform parameters to your shader, constant buffers, and so on), and then you submit the object geometry to be rendered to the GPU. It does the job of processing those vertices and passes those vertices to the vertex shader.

In terms of answering your question (how to deal with the fact that most objects will generally have unique world transforms), the first approach you described is generally what's done. Each object is gets a corresponding draw call, and prior to that draw call the appropriate world matrix is set. The world matrix can be set as a shader uniform, but D3D10 introduced constant buffers which can also be used (as you've noted) for this purpose.

One of the benefits of constant buffers is that you can construct several, each with unique intended update scopes, so you can create a constant buffer dedicated entirely to containing the values that will changed in each frame.

You're correct that this approach involves a number of draw calls that scales with the number of objects to be rendered, and that one solution to this is to pre-pack a large vertex buffer with all the pre-transformed world coordinates of all the geometry and render it in one go. This kind of batching can be useful for static geometry.

Another approach for dealing with varying world transforms while reducing the number of draw calls is instancing. This involves multiple vertex buffers, one containing the baseline geometry data and another containing the per-instance data for each object to be drawn. There's a D3D9-specific article here, but the 10+ API still retains support for it via calls like DrawInstancedIndexed.

Basically, you were pretty close to the more viable solutions with your guesses and speculation. The biggest place you missed it was talking about directly allocating (matrix * object count) on the GPU, because you can't really directly do that (and XMMatrix is not a GPU type, it's a CPU-side matrix type), but it's effectively what you're doing when you supply per-instance data via a vertex buffer.

\$\endgroup\$
2
\$\begingroup\$

For one-off objects like the world geometry, you shouldn't have issues using a matrix in the constant buffer.

A technique for dealing with lots of small objects is to place the positions and other per-object data into their own buffers (texture samplers were used heavily some years back). This is used frequently with Instancing.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .