[osg-users] Computing Normals for Drawables

612 views
Skip to first unread message

James Buckthorpe

unread,
Nov 6, 2009, 11:18:15 AM11/6/09
to osg-...@lists.openscenegraph.org
Hi,

I have a scene that contains a very large number of osg::Drawables.

I am currently using osg::SmoothingVisitor to compute the normals for each geometry. This creates a normal per vertex. If I turn normals off I see a sigificant reduction in the amount of memory in use, hence I am currently looking at implementing a visitor to compute 1 normal per triangle which would still give the appearance I need and reduce the memory load.

However, I am not sure of the correct way to binding normals to the geometry:

In osg::SmoothingVisitor this is done using:

geom.setNormalArray( normals );
geom.setNormalIndices( geom.getVertexIndices() );
geom.setNormalBinding(osg::Geometry::BIND_PER_VERTEX);

My geometries are mostly defined using quads and triangle strips. If I use osg::Geometry::BIND_PER_PRIMITVE I am guessing that this will try to bind to a quad or triangle strip not a triangle as I would like. Is this correct?
Using a single normal for a triangle strip is not going to give me a suitable view of the geometry.

Is anyone able to point me in the right direction?

Many Thanks for your help.
James

------------------
Read this topic online here:
http://forum.openscenegraph.org/viewtopic.php?p=19269#19269

_______________________________________________
osg-users mailing list
osg-...@lists.openscenegraph.org
http://lists.openscenegraph.org/listinfo.cgi/osg-users-openscenegraph.org

Simon Hammett

unread,
Nov 6, 2009, 11:41:47 AM11/6/09
to osg-...@lists.openscenegraph.org
2009/11/6 James Buckthorpe <James.Bu...@gmail.com>:

> Hi,
>
> I have a scene that contains a very large number of osg::Drawables.
>
> I am currently using osg::SmoothingVisitor to compute the normals for each geometry. This creates a normal per vertex. If I turn normals off I see a sigificant reduction in the amount of memory in use, hence I am currently looking at implementing a visitor to compute 1 normal per triangle which would still give the appearance I need and reduce the memory load.
>
> However, I am not sure of the correct way to binding normals to the geometry:
>
> In osg::SmoothingVisitor this is done using:
>
> geom.setNormalArray( normals );
> geom.setNormalIndices( geom.getVertexIndices() );
> geom.setNormalBinding(osg::Geometry::BIND_PER_VERTEX);
>
> My geometries are mostly defined using quads and triangle strips. If I use osg::Geometry::BIND_PER_PRIMITVE I am guessing that this will try to bind to a quad or triangle strip not a triangle as I would like. Is this correct?

Yes.

> Using a single normal for a triangle strip is not going to give me a suitable view of the geometry.
>

You've already worked it all out, you don't need anything else.

Though beware that using PER_PRIMITVE drops you onto OSG's slow render pass,
so you may take a big performance hit.

--
http://www.ssTk.co.uk

Jean-Sébastien Guay

unread,
Nov 6, 2009, 11:55:41 AM11/6/09
to osg-...@lists.openscenegraph.org
Hi James,

> If I turn normals off I see a sigificant reduction in the amount of memory in use, hence I am currently looking at implementing a visitor to compute 1 normal per triangle which would still give the appearance I need and reduce the memory load.

As Simon mentioned, using per primitive bindings drop you down to OpenGL
slow paths, so if you have a large number of drawables you will surely
see a performance hit. Additionally, one normal per primitive will mean
that you will have a faceted look which may not be what you want.

You may find that in your case, you just have to bite the bullet and
accept the higher memory usage.

You could of course reduce the number of vertices your geometry has.
Otherwise, a possible alternative would be using PagedLOD with objects
split up into various LODs so that when objects are further away, they
have less geometry, and when they're closer up OSG automatically loads
up the more refined version (and would expire and unload the lower res
version eventually). But then the scene graph structure becomes a bit
more complicated.

Hope this helps,

J-S
--
______________________________________________________
Jean-Sebastien Guay jean-seba...@cm-labs.com
http://www.cm-labs.com/
http://whitestar02.webhop.org/

James Buckthorpe

unread,
Nov 6, 2009, 12:27:02 PM11/6/09
to osg-...@lists.openscenegraph.org
Hi there,

Thank you for your responses, I really appreciate the input.

Can I tell you little more about what I'm working on as that may clarify the problem. The application is a CAD app and it must be able to load in literally millions of objects. The objects are low detail meshes and for a worst case scenario, the scene has around 20million vertices. I'm not really concerned about detail, lighting looking too correct or even rendering performance, I'd gladly sacrifice some rendering performance to load the entire model in!

What the system must be able to do is load the entire model (we have some guideline models) into memory, under a 32bit OS limit. A legacy app being replaced uses OpenGL slow paths (Begin/End) and computes the normals manually. This has 1 normal per triangle (or less for some flat surfaces) whereas OSG is using 1 normal per vertex. Sure it looks a LOT better and its also higher performance in OSG, but it's also using approximately double the memory to render the same model.

I have heard about PagedLOD but I am not sure how much of a performance hit this will give us when the objects are loaded/unloaded, as there are literally millions of objects in a small viewable area. I just noticed that disabling the smoothing visitor for objects (ie: having no normals) saved several hundred megabytes, and found out by trial/error that in OSG each vertex Vec3f has a normal (Another Vec3f) and an index associated with it.

So would you happen to know if there is anything I can do (bar PagedLOD) to reduce the memory consumption in this application? I was hoping to target the normal generation and output one normal per triangle as opposed to one per vertex, as this would shave a few hundred megabytes from the app when under load.

Thank you,
James

------------------
Read this topic online here:

http://forum.openscenegraph.org/viewtopic.php?p=19275#19275

Jean-Sébastien Guay

unread,
Nov 6, 2009, 2:00:31 PM11/6/09
to osg-...@lists.openscenegraph.org
Hello James,

> So would you happen to know if there is anything I can do (bar PagedLOD) to reduce the memory consumption in this application? I was hoping to target the normal generation and output one normal per triangle as opposed to one per vertex, as this would shave a few hundred megabytes from the app when under load.

Well, I don't have anything to add. Your requirements are interesting,
and you can certainly try generating normals per primitive to see if
you'll get fast enough results make the savings in memory worth it. If
you have 20 million vertices in view at all times I can't see how
rendering on the slow path would yield a usable frame rate, but it might
be good enough for your needs, I wouldn't know. Try it and see.

If you're not concerned with lighting quality, why do you even need
normals? You could just disable lighting on your objects and do away
with normals entirely...

Here's another trick you could do, if you want lighting on, but this
would require using shaders. Remove your normal arrays, encode the
normals as theta+phi angles (2 floats instead of 3, and can even be
quantized into 2 half-floats which would physically take up only one
32-bit float per vertex) in a texture or vertex attributes, and then
calculate the vertex normal from this in the vertex shader. The
calculation in the shader is trivial, so the question is whether
generating the normals normally (SmoothingVisitor) and then encoding
them when a model is loaded would be too time consuming (note you could
do this as a pre-process before loading the model, and then save the
objects that way and as long as the user doesn't change them, you
wouldn't have to re-do this step each time).

Hope this helps,

J-S
--
______________________________________________________
Jean-Sebastien Guay jean-seba...@cm-labs.com
http://www.cm-labs.com/
http://whitestar02.webhop.org/

Simon Hammett

unread,
Nov 6, 2009, 2:05:24 PM11/6/09
to osg-...@lists.openscenegraph.org
2009/11/6 James Buckthorpe <James.Bu...@gmail.com>:

> Hi there,
>
> Thank you for your responses, I really appreciate the input.
>
> Can I tell you little more about what I'm working on as that may clarify the problem. The application is a CAD app and it must be able to load in literally millions of objects. The objects are low detail meshes and for a worst case scenario, the scene has around 20million vertices. I'm not really concerned about detail, lighting looking too correct or even rendering performance, I'd gladly sacrifice some rendering performance to load the entire model in!
>
> What the system must be able to do is load the entire model (we have some guideline models) into memory, under a 32bit OS limit. A legacy app being replaced uses OpenGL slow paths (Begin/End) and computes the normals manually. This has 1 normal per triangle (or less for some flat surfaces) whereas OSG is using 1 normal per vertex. Sure it looks a LOT better and its also higher performance in OSG, but it's also using approximately double the memory to render the same model.
>
> I have heard about PagedLOD but I am not sure how much of a performance hit this will give us when the objects are loaded/unloaded, as there are literally millions of objects in a small viewable area. I just noticed that disabling the smoothing visitor for objects (ie: having no normals) saved several hundred megabytes, and found out by trial/error that in OSG each vertex Vec3f has a normal (Another Vec3f) and an index associated with it.
>
> So would you happen to know if there is anything I can do (bar PagedLOD) to reduce the memory consumption in this application? I was hoping to target the normal generation and output one normal per triangle as opposed to one per vertex, as this would shave a few hundred megabytes from the app when under load.
>
> Thank you,
> James

Well one idea I've had kicking around in my head for several years,
may be possible with a custom vertex shader.

Take a sphere, generate a mesh for it, take the surface normals and store them.
(well actually you don't need to generate the mesh, working out a
normal for a point on the sphere is trivial)

Then for every triangle/quad, simply chose one of the stored normals
which is 'closest' to your calculated normal
and 'associate' the index for that normal with the primitive. After
all if you are flat shading stuff,
the minor difference between the proper normal and the 'closest'
normal will only have a minor effect on the
resultant shading.

This scheme requires you be able to specify a 'normal index' array, I
don't think you can do this with the fixed pipeline,
which is why you'd need a customer vertex shader.

I'm not familiar enough with shaders though to say it's actually
possible to do that., but it looks doable.
This would save huge amounts of ram and you'd get away with only using
a byte for the normal index.

--
http://www.ssTk.co.uk

Jean-Sébastien Guay

unread,
Nov 6, 2009, 2:31:42 PM11/6/09
to OpenSceneGraph Users
Hi Simon,

> I'm not familiar enough with shaders though to say it's actually
> possible to do that., but it looks doable.

Totally, it's good idea. It's actually reminiscent of spherical
harmonics, but in reverse (instead of sampling illumination in all
directions on a sphere, you're choosing the direction that best
represents the normal).

The only thing is that the smallest unit for per-vertex attributes is a
32-bit float, so using a byte doesn't necessarily save anything over a
normal stored as theta+phi angles quantized to two half-floats, and that
way you don't need the lookup table of normals to index into.

But it's a good idea, I agree, as long as you can accept a slight loss
in lighting quality because of approximated normals (will probably show
up as banding on smooth surfaces).

J-S
--
______________________________________________________
Jean-Sebastien Guay jean-seba...@cm-labs.com
http://www.cm-labs.com/
http://whitestar02.webhop.org/

Simon Hammett

unread,
Nov 6, 2009, 4:34:08 PM11/6/09
to OpenSceneGraph Users
2009/11/6 Jean-Sébastien Guay <jean-seba...@cm-labs.com>:

> Hi Simon,
>
>> I'm not familiar enough with shaders though to say it's actually
>> possible to do that., but it looks doable.
>
> Totally, it's good idea. It's actually reminiscent of spherical harmonics,
> but in reverse (instead of sampling illumination in all directions on a
> sphere, you're choosing the direction that best represents the normal).
>
> The only thing is that the smallest unit for per-vertex attributes is a
> 32-bit float, so using a byte doesn't necessarily save anything over a
> normal stored as theta+phi angles quantized to two half-floats, and that way
> you don't need the lookup table of normals to index into.

Ah I didn't know that, ty. Seems like an odd limitation though.

How does the angles thing work? Not sure I follow you.

If you did the normal lookup, wouldn't you save a whole load
of shader processing time, or is memory bandwidth a bigger limitation with
shaders?

--
http://www.ssTk.co.uk

Jean-Sébastien Guay

unread,
Nov 6, 2009, 4:51:59 PM11/6/09
to OpenSceneGraph Users
Hi Simon,

> Ah I didn't know that, ty. Seems like an odd limitation though.

Well, the video cards are implemented to take in only certain types of
data. Vertices, normals and colors are in general aggregates of 32-bit
floats (which is why no vertex type is ever a vector of 3 doubles).

> How does the angles thing work? Not sure I follow you.

Your intuition is right that normals around a vertex will always be in a
sphere. The thing is that you can encode that as spherical coordinates
(http://en.wikipedia.org/wiki/Spherical_coordinates), where r = 1 (since
a normal is always unit-length). So the direction could be encoded as
two angles, theta and phi, inclination and azimuth (see the Wikipedia
article).

Now, you already save one float that way over storing a 3-component
vector. If you want to save more, since the angles are small numbers,
you can store both of them into the lower and upper parts of a single
32-bit float (two 16 bit numbers) and re-extract them from the float in
the shader.

Another way is you could store only 2 of the 3 components of the normal
- since a normal is unit-length you can calculate the third component
easily. Those numbers will also be small (less than or equal to 1) so
you could do the same transformation into two 16-bit values and store
them into a single float. The spherical coordinates just came to mind
first as I've used it before, but the result would be the same.

> If you did the normal lookup, wouldn't you save a whole load
> of shader processing time, or is memory bandwidth a bigger limitation with
> shaders?

You'd save a little bit of processing time per vertex, but compared to a
texture lookup to get the right normal from the lookup table, I don't
know what would win. Seriously, what I've described above is child's
play for a GPU, and very easily parallelizable, which is what GPUs do best.

It's not really a matter of bandwidth since your lookup table would be
pretty small too. I expect you'd get similar performance in both cases,
for me it's just inconvenient to drag around an extra table when I don't
have to - the angles / quantized vector method above would all fit
nicely in the object itself without needing any extra data.

J-S
--
______________________________________________________
Jean-Sebastien Guay jean-seba...@cm-labs.com
http://www.cm-labs.com/
http://whitestar02.webhop.org/

yann le paih

unread,
Nov 7, 2009, 2:46:47 AM11/7/09
to OpenSceneGraph Users

Andrew Burnett-Thompson

unread,
Nov 9, 2009, 5:21:50 AM11/9/09
to OpenSceneGraph Users
Hi guys,

Interesting conversation on solving this problem. I'm working with James on this and he's away this week, so I thought I'd reply to keep the conversation going, and also to thank you for your input.

Just to throw another requirement into the mix, I don't think we can use vertex shaders, as we cannot guarantee what hardware the software will be run on. Basically integrated graphics is the lowest possible hardware, so what's that, DirectX 8? 9? Vertex shaders run on DX8 but you won't get many instructions.

Can this not be done on the CPU side only? Interestingly we did float the idea of computing a shared normal array (a sphere coated with normals) for all objects in the scene, then provide indices between Geometry vertices and the shared normal array, however we really didn't know if this was possible in OSG, or if it would work.

A quick look at osg::Geometry source code there is a function setNormalIndices, so perhaps if we were to create a NodeVisitor that did the following this could work?

1. On creation of the nodeVisitor, create an osg::Vec3Array for shared normals. Populate with normals in all directions.
2. As the NodeVisitor walks the tree, for each Geometry object use the code from SmoothingVisitor to calculate what the normal should be
3. Rather than store that normal in an array and pass to the Geometry, instead find the closest normal to it in the shared normal array,
4. Store the index to the shared normal, and finally set the indices on the Geometry using setNormalIndices

This would be done once on load or creation of an object. It wouldn't matter to take the performance hit once per load, nor would it matter if this slowed down rendering. We already employ aggressive use of culling to achieve decent rendering framerate when the object count is high. The hard limit is memory.

Thanks a lot for your input,

Regards,
Andrew

Robert Osfield

unread,
Nov 9, 2009, 5:47:53 AM11/9/09
to OpenSceneGraph Users
Hi Andrew,

Vertex indices are just available for backwards compatibility, I would
not recommend using them at all as they force the OSG down on to slow
paths where it assemble the actual data to send to graphics card on
each frame, as graphics cards do not support per primitive normals, or
decoupled vertex indicies.

The most efficient way to use OpenGL/graphcis hardware is with
straight vertex arrays and osg::DrawElements to manage indices.

Robert

Simon Hammett

unread,
Nov 9, 2009, 8:41:59 AM11/9/09
to OpenSceneGraph Users
2009/11/9 Andrew Burnett-Thompson <aburnett...@googlemail.com>:

The only way to do it with the fixed pipeline I can think of, is basically
just sort all your geometry into group's by the approximate normal, then
you don't have to store the normal index at all so you'd save more memory.

Doing it that way you're using BIND_OVERALL and vertex buffers so
you're still using
osgs fast path. Makes updating geometry a pain, but that doesn't sound like a
problem from your description.

Also with grouping the geometry this way you aren't restricted to a
using a power of
two number of normals, you can just try different numbers of normals
till you get
reasonable looking results.

--
http://www.ssTk.co.uk

Jean-Sébastien Guay

unread,
Nov 9, 2009, 9:14:26 AM11/9/09
to OpenSceneGraph Users
Hi Andrew,

> Just to throw another requirement into the mix, I don't think we can use
> vertex shaders, as we cannot guarantee what hardware the software will
> be run on. Basically integrated graphics is the lowest possible
> hardware, so what's that, DirectX 8? 9? Vertex shaders run on DX8 but
> you won't get many instructions.

Well you're kind of juggling conflicting requirements. You want the app
to run on low-end graphics, but you want to reduce its CPU-side memory
usage to a minimum by not storing normals. Generally you have a
trade-off to make by using more memory and getting good performance, and
getting lower performance but using less memory. That's just the way it
goes, but you'll have to try to know for sure. No one has a situation
exactly like yours, we can only offer guidance.

We've given you a few different alternatives, now you have to weigh the
pros and cons yourselves. Perhaps try a few of the alternatives in a
little test program before making changes in your software itself, and
test the alternatives in realistic settings (with lots of objects for
example) looking at the stats each time.

If you really want to use normal indices, try it out, but as Robert said
(and I and others have said before in this thread) this will drop you
down to slow paths. If that's good enough for you, then fine, but you
won't know unless you try it.

Hope this helps,

Andrew Burnett-Thompson

unread,
Nov 9, 2009, 12:01:53 PM11/9/09
to OpenSceneGraph Users
Hi there,

> Jean
> Well you're kind of juggling conflicting requirements. You want the app
to run on low-end graphics, but you want to reduce its CPU-side memory
usage to a minimum by not storing normals.

Well it's just a typical project really, everyone wants the moon on a stick and they don't understand the concept of engineering compromise! I think the overriding limit is memory usage. If the app cannot load the models it needs to, then its no good. Rendering performance is quite good (we aggressively cull and get 35 frames per second on an nVidia 9600. The most amount of time spent is cull) and so long as the frame rate remains above 5 FPS (ie: just about interactive) its ok ...

> Simon Hammett

> The only way to do it with the fixed pipeline I can think of, is basically
just sort all your geometry into group's by the approximate normal, then
you don't have to store the normal index at all so you'd save more memory.

Simon I'm not sure I 100% follow what you're saying here. Could you clarify what you meant?

Ok thanks all for your excellent suggestions. I think I will take a look at implementing an "Index to shared Normal Array" using setNormalIndices, even if this drops us down to slow paths, as I would be interested to see just how bad an effect it has on performance. If it's several orders of magnitude, well I will chalk that up to experience and move on.

The other idea I like is to use the same method (Indices to normals) in a vertex shader. Not quite sure how I will do this but I believe the hardware we are operating on will be Shader Model 1.0 or 2.0 as a minimum.

Thanks a lot,
Andrew
Reply all
Reply to author
Forward
0 new messages