Draw Shadows using DisplayPipeline.PostDrawObjects()

Hello,

When an object is added to Document, shadows are displayed in Rendered and Raytraced Display Modes, although when it’s drawn like that:

  DisplayPipeline.PostDrawObjects += PostDrawObjects;
  private void PostDrawObjects(object sender, DrawEventArgs e)
  {
        e.Display.DrawMeshShaded(meshToDraw,displayMaterial);
        e.Display.DrawBrepShaded(brepToDraw,displayMaterial);
   }

There are no shadows, it happens using PostDraw, DrawOverlay and DrawForeground


Is there a way to enable the shadows using these methods (DrawMeshShaded and DrawBrepShaded)?

Thanks.

@jeff, can you assist?

Hey Harper,

The “shadow pass” is always/only done in the middle ground channel… which is where any/all object drawing is supposed to take place. If you’re drawing objects in any other channels, and those objects are supposed to be part of the “primary” frame buffer output, then all bets are off as to whether or not those objects are going to participate and/or produce the expected results.

You cannot just call some draw method at any point, at any time within the pipeline’s channel chain, and then expect it to work or produce the correct results…There are very specific things that happen at very specific times within the pipeline…and they’re done for very specific reasons.

Having said that…if you want your own objects to cast and/or receive shadows, and your objects are not part of Rhino’s object database, then you must draw your object(s) from within the “middleground” channel. That means you must implement the “DrawObject()” conduit channel from within your own conduit (i.e. CSupportChannels::SC_DRAWOBJECT) and draw your object(s) there … Doing it within the PostDrawObjects() channel is just simply not going to work…and the actual name of the channel should be enough to understand why… “Post … Draw … Objects” … That means that channel gets executed AFTER all the objects have been drawn…in which case, if you’re drawing any kind of shaded mesh in that channel, how could it possibly participate in any kind of shadow casting or receiving phase, since everything has already been drawn?

Hope this is helps clear things up… If you are drawing your objects via the CSupportChannels::SC_DRAWOBJECT channel and things aren’t working as expected, then there’s a bug somewhere…and I’ll need a conduit example that I can compile and debug with.

Let me know if you have any questions or issues on this behavior…I’ll be glad to help.

Thanks,
-Jeff

Hi Jeff,

Thanks for the explanation, I understand that shadows cannot be computed on a “Post Draw” phase but as far as I understant neither in PreDrawObjects I’m quite new in Rhino development and I wouldn’t like to create a custom shader for that or intervene Rhino OpenGL pipeline explicitly due it’s a hard work… but if there is no other way I’ll do it., I was guessing if Rhino has any easier way to do it.

I’m writing this plugin in c# but I have no problem to write some parts in c++ and make a wrapper, I’ve found the DisplayConduit class which seems to be the equivalent for CRhinoDisplayConduit but I didn’t find the equivalence for CSupportChannels::SC_DRAWOBJECT or any way to draw there except overriding predraw, postdraw which doesn’t help to cast shadows, is that a c++ exclusive feature? or is it accesible from rhinocomon c# sdk? actually I’m a little bit lost…

Thanks.
Will

Hi Will,

It’s most likely that the DrawObject() channel hasn’t been put into RhinoCommon since it can become a severe overhead if/when not used properly… @dale would probably know more but @steve would definitely know why. I thought there was at least one “object based” channel available to C#… I want to say it’s PreDrawObject (note: the singular usage … i.e. NOT PreDrawObjects)… that is definitely confusing I realize…but one gets called just before an object is drawn (PreDrawObject), while the other gets called before ANY object gets draw (PreDrawObjects).

It’s starting to sound like we’re going to need yet another conduit channel (SC_SHADOWPASS) for this type of thing if DrawObject() will not be exposed… In either case, it doesn’t look like this can be done using C# at the moment…unless what I said above is true.

This small rundown of the pipeline might shed a little more light on conduits.

Thanks,
-Jeff

Hey Will…

I just realized that you’re talking about your own geometry that doesn’t exist anywhere… All of the “object-based” channels require some kind of reference geometry to work, otherwise, none of the object channels will ever be called.

I’m looking into why this hasn’t been setup to work in cases like yours…and scratching my head as to why we haven’t caught or heard of this problem up until now.

Stay tuned…
-Jeff

Hi Will,

This is clearly a bug and oversight on our part… Grasshopper objects work, so I guess we figured everything was working since it’s a plugin as well… But it turns out it works for very different reasons, which I will not go into here.

For now, this has been logged as a bug and you can track it here:

https://mcneel.myjetbrains.com/youtrack/issue/RH-54147

Thanks,
-Jeff

Hi Jeff,

Thank you to toke a look deeply, I appreciate it a lot, I’ll wait for a fix.
Thanks again.

William.

Hi @jeff is there any update to this issue? The jetbrains link seems to be invalid.

Hi - that link should now work for you. The issue is currently on the 7.x list.
-wim

This issue is still in 7.15.

Hi -

If you check the link to the issue tracker that was posted earlier, you’ll see that this is now on the 8.x list.
image

Also, since this thread is mentioned in the bug item, a notification will be posted here if/when that issue gets solved.
-wim

Hi all - any updates on this thread? seems like RH8x still has this issue… keen to know if there is any workaround (I.e. how GH is achieving it).
Best,
C.

@jeff, I’m bumping this thread with an attempt to solve this from the c++ pipeline but without success. The issue tracker explicitly mentions RhinoCommon, and I was hoping that using the SC_DRAWOBJECT channel will allow for non-referenced geometry to participate in the shadow pass.

Is this possible at all given the current setup?

https://mcneel.myjetbrains.com/youtrack/issue/RH-54147

class CColoredMeshConduit : public CRhinoDisplayConduit
{
public:
    CColoredMeshConduit();
    bool ExecConduit(CRhinoDisplayPipeline& dp, UINT nChannel, bool& bTerminate) override;
};

CColoredMeshConduit::CColoredMeshConduit()
    : CRhinoDisplayConduit(CSupportChannels::SC_CALCBOUNDINGBOX | CSupportChannels::SC_DRAWOBJECT)
{
}

static CColoredMeshConduit* g_pColoredMeshConduit = nullptr;

bool CColoredMeshConduit::ExecConduit(CRhinoDisplayPipeline& dp, UINT nChannel, bool& bTerminate)
{
    UNREFERENCED_PARAMETER(bTerminate);

	ON_Mesh mesh;
	mesh.SetVertex(0, ON_3fPoint(0.0, 0.0, 0.0));
	mesh.SetVertex(1, ON_3fPoint(100.0, 0.0, 0.0));
	mesh.SetVertex(2, ON_3fPoint(50.0, 90.0, 0.0));
	mesh.SetTriangle(0, 0, 1, 2);
	mesh.ComputeVertexNormals();

	switch (nChannel)
	{
	case CSupportChannels::SC_CALCBOUNDINGBOX:
        m_pChannelAttrs->m_BoundingBox.Union(mesh.BoundingBox());
        break;
	case CSupportChannels::SC_DRAWOBJECT:
		CDisplayPipelineMaterial mat;
		dp.SetupDisplayMaterial(mat, RGB(255, 0, 255));
		dp.DrawShadedMesh(mesh, &mat);
		break;
	}
	return true;
}

PINVOKE
void DrawColoredMesh(unsigned int docSN)
{
    if (!g_pColoredMeshConduit)
        g_pColoredMeshConduit = new CColoredMeshConduit();

    if (!g_pColoredMeshConduit->IsEnabled())
    {
        g_pColoredMeshConduit->Enable(docSN);
        RhinoApp().ActiveDoc()->Redraw();
    }
}

PINVOKE
void RemoveColoredMesh()
{
    if (g_pColoredMeshConduit)
    {
        g_pColoredMeshConduit->Disable();
        delete g_pColoredMeshConduit;
        g_pColoredMeshConduit = nullptr;
        RhinoApp().ActiveDoc()->Redraw();
    }
}


Hi @mrhe

This actually should have worked…and does in a C++ only command, so I’ll need to dig into why it doesn’t work when doing it the way you’ve shown above.

With that said, I’m assuming your example was just a quick way to demonstrate your point, because it is certainly not a very good way to do what you’re trying/wanting to do…

The DRAWOBJECT channel gets called for every single visible object in the scene, which means your triangle would be drawn for every object drawn (it also means it will never get drawn if there are no visible objects or no objects at all)… So unless you plan on using proxy objects (objects that you replace with your own object/mesh), I don’t recommend using the DRAWOBJECT channel. Again, I’ll assume this was for example purposes…but as I said, this should have worked, and does work in a C++ only implementation.

The PostDrawObjects mechanism should be working as well…I thought I had this working a while ago, but I guess it either broke at some point, or I missed something.

This is snippet of the command you provided…I changed/added the POSTDRAWOBEJECTS channel to it…

  ON_Mesh mesh;
  mesh.SetVertex(0, ON_3fPoint(0.0, 0.0, 0.0));
  mesh.SetVertex(1, ON_3fPoint(100.0, 0.0, 0.0));
  mesh.SetVertex(2, ON_3fPoint(50.0, 90.0, 0.0));
  mesh.SetTriangle(0, 0, 1, 2);
  mesh.ComputeVertexNormals();

  switch (nChannel)
  {
  case CSupportChannels::SC_CALCBOUNDINGBOX:
    m_pChannelAttrs->m_BoundingBox.Union(mesh.BoundingBox());
    break;
  case CSupportChannels::SC_POSTDRAWOBJECTS:
    {
      CDisplayPipelineMaterial mat;
      dp.SetupDisplayMaterial(mat, RGB(255, 0, 255));
      dp.DrawShadedMesh(mesh, &mat);
    }
    break;
  }

And here is a video showing it in action…

So it does seem to work (for me)… I just need to figure out why it’s not working for you. Something might be getting lost in translation between Common and Core API.

I’ll let you know what I find.

Thanks,
-Jeff

Thanks for looking into this issue @jeff.

Yes, I only provided a minimal example for the sake of reproducibility on your end and am aware of how inefficient this code snippet is.

Just to be sure, I checked the SC_POSTDRAWOBJECTS channel but it still doesn’t show any shadows. This is tested in R7 & R8. For the sake of completeness, this is how I call these methods:

    internal class NativeMethods
    {
        [DllImport("RhinoWrapper.dll")]
        public static extern void DrawColoredMesh(uint docSN);

        [DllImport("RhinoWrapper.dll")]
        public static extern void RemoveColoredMesh();
    }

and on the native part, I have this definition:

#define PINVOKE extern "C" __declspec(dllexport)

In the meantime, I’ve experimented with custom mesh providers and got it working. It’s a bit of a mess for a few reasons, though:

  1. R8 deprecated CustomRenderMeshProvider2 and only supports RenderMeshProvider which in turn is not fully supported in R7, so I need to maintain 2 separate classes for two Rhino versions with all the conditional compilation etc.
  2. Neither class works in wireframe, so I still need to run the mesh via a Conduit to support this display mode on top of the mesh providers.
  3. RhinoCommon doesn’t expose the DrawShadedMeshes (note the plural) which in my case results in potentially thousands of draw calls, which could be avoided on the native side:
void DrawShadedMeshes(const ON_Mesh* const* meshes, int count, const CDisplayPipelineMaterial* material, CRhinoCacheHandle* const * caches);

So yeah, I’d really appreciate it, if there was a way of using the native display pipeline from a managed plugin and still participate in the shadow pass.

I think I can see where things might be going wrong… and unfortunately, it’s now a bigger problem.

I won’t go into the details here, but basically when Rhino renders its shadows, it does so using a more optimized mesh drawing mechanism (there are all kinds of things that can be skipped when only trying to get depth information and produce a shadow map). These optimizations I think are preventing certain things from happening at certain times…again, it’s unfortunate.

I’m not sure why the CRM provider stuff isn’t working…that’s pretty much what GH uses, and as you pointed out, all of its meshes cast and catch shadows. However, it uses its own Document Custom Render Mesh (DCRM) object, and the pipeline ensures it draws that in the proper places at the proper times.

If the renderer/raytracer is correctly rendering your CRMs as expected, then the display should too… if it’s not, then there’s obviously a bug in that area… a small example would go a long way to help me track this part down.

For display purposes only, I could add yet another conduit channel… (i.e. SC_CUSTOMRENDEROBJECTS), that will be executed at the proper points within the pipeline for shadow depth, shadow map, and render/material operations. It is here where you would draw all of your own custom objects/meshes, and they will be included in all shadow and material calculations, much like how the DCRMs are handled. This would also allow you to draw all of your “potentially thousands” of objects all at once. This doesn’t solve the issue you mention in #2, so you’ll still need to maintain two different approaches for your CRMs, but if we can determine if there is a bug in the display with regards to how you’re using CRMs, then we may be able to solve this in one area and eliminate the need for a “display only” solution.

If you have an example that demonstrates the use of the CRM provider code, where things “render” properly but do not display properly, that would be very much appreciated.

I apologize this isn’t working still… I thought it was, but now I think I see why it’s not.

-J

1 Like

Thanks @jeff,

to be clear - the CRM works ok in Rendered and Shaded. It just doesn’t show up in Wireframe. I guess this is not a bug, but a feature - there are no shaded objects to display, hence the pipeline skips all checks to registered CRMs.

In Wireframe, WillBuildCustomMeshes is not called at all.

I believe to have read somewhere on the forum that GH uses a dual approach - CRMs in Rendered and Conduits in Wireframe/Shaded. Can’t find the reference now, though.

#if RHINO7
    public class CustomMeshProvider : CustomRenderMeshProvider2
    {
        private static Mesh _customBox;
        private static BoundingBox _boundingBox;
        private RenderMaterial _material;
        public override string Name => "CRM_Test";
        public CustomMeshProvider()
        {
            InitializeCustomGeometry();
        }

        private void InitializeCustomGeometry()
        {
            var box = new Box(new Plane(Point3d.Origin, Vector3d.ZAxis), new Interval(-5, 5), new Interval(-5, 5), new Interval(-5, 5));
            _customBox = Mesh.CreateFromBox(box, 1, 1, 1);
            _boundingBox = _customBox.GetBoundingBox(false);
            var material = new Material
            {
                DiffuseColor = System.Drawing.Color.Gray
            };
            _material = RenderMaterial.CreateBasicMaterial(material, RhinoDoc.ActiveDoc);
        }

        public static void ForceRedraw()
        {
            Random rnd = new Random();
            var x = rnd.Next(3, 7);
            var y = rnd.Next(3, 7);
            var z = rnd.Next(3, 7);
            var box = new Box(new Plane(Point3d.Origin, Vector3d.ZAxis), new Interval(-x, x), new Interval(-y, y), new Interval(-z, z));
            _customBox = Mesh.CreateFromBox(box, 1, 1, 1);
            _boundingBox = _customBox.GetBoundingBox(false);

            CustomRenderMeshProvider2.DocumentBasedMeshesChanged(RhinoDoc.ActiveDoc);
        }

        public override bool BuildCustomMeshes(ViewportInfo vp, RhinoDoc doc, RenderPrimitiveList primitives, Guid requestingPlugIn, DisplayPipelineAttributes attrs)
        {
            bool addedContent = false;
            if (primitives.RhinoObject == null)
            {
                primitives.Add(_customBox, _material);
                BoundingBox(vp, null, doc, requestingPlugIn, attrs);
                addedContent = true;
            }

            return addedContent;
        }

        public override bool WillBuildCustomMeshes(ViewportInfo vp, RhinoObject obj, RhinoDoc doc, Guid requestingPlugIn, DisplayPipelineAttributes attrs)
        {
            if (obj == null)// && attrs != null && attrs.EnglishName == "Rendered")
                return true;

            return false;
        }


        public override BoundingBox BoundingBox(ViewportInfo vp, RhinoObject obj, RhinoDoc doc, Guid requestingPlugIn, DisplayPipelineAttributes attrs)
        {
            var bbox = Rhino.Geometry.BoundingBox.Empty;
            bbox.Union(_boundingBox);

            return bbox;
        }
    }
#endif

Ok, I guess by “working” you meant that shadows appear correctly in Rendered mode?. I was operating under the assumption that shadows just aren’t working in the display no matter what you’ve tried.

But… Did your previous implementation work when poking the Render button, or entering into the Raytraced display mode? If either of those worked correctly, but the display did not, then that’s a bug.

I don’t think that’s right… GH actually doesn’t draw anything…the pipeline does. GH populates the Document Custom Render Mesh object with its meshes, and the pipeline decides if/when to drawn them and how.

I can certainly get WillBuildCustomMeshes to be called regardless of display mode, if you think that’ll solve everything…but somehow I think I’m still missing something here.

That being said, there are others in this thread that want this to work, and I’m not sure they’re going to want to venture down the CRM path like you have, and would just like a display solution… Unfortunately, it does not look like there is any way to “automate” the shadow casting of unknown document objects, at least not in a Service Release. Steve has some ideas on auto-detecting these types of objects, and we’ll be looking into those in V9… But for now, I’d like to see if there is a way to get something working in V8… I thought the changes I made to V8 at least provided an interim solution for PostDrawObjects …apparently not.

Adding a new conduit channel is trivial, and obviously can’t break existing plugins, so if devs are willing to change SC_POSTDRAWOBJECTS to something like SC_CUSTOMRENDEROBJECTS, and have things work, then I’m happy to type that up… But if the expectations are that “Render” and “Raytrace” also work, then I’m afraid that can’t happen, and CRMs are really the only solution.

I’m glad to hear that the CRM approach is working in the display… So, if getting WillBuildCustomMeshes to always be called solves everything for you, then maybe that’s the way to go (for you)… Am I understanding things correctly now?

Thanks,
-J

Yes, with CRMs shadows properly show up in the Rendered mode. Shaded with default settings doesn’t cast any shadows, but I can still see the meshes just fine. In Wireframe, WillBuildCustomMeshes doesn’t fire, hence no meshes are drawn.

The native code I posted earlier (SC_DRAWOBJECT | SC_POSTDRAWOBJECTS) drew the mesh, but no shadows. I ditched this approach as it didn’t solve the original issue.

My current setup covering all three scenarios in a RhinoCommon plugin looks as follows:
Use the PostDrawObjects channel in a DisplayConduit for Wireframe and Shaded display modes.

When switching to Rendered, disable the DisplayConduit and leverage the CRM provider posted above. In R8, I am using a different class derived from a RenderMeshProvider decorated to only use document objects:

    [CustomRenderMeshProvider(true)]
    internal class CustomMeshProvider : RenderMeshProvider

R8 is neater because it allows me to register/unregister the provider only when needed, whereas R7 registers the CRM provider on plugin load and it checks all the time whether there is any work to be done. I haven’t stress tested it yet, but even with a simplest scene there are a lot of calls to WillBuildCustomMeshes.

RenderMeshProvider doesn’t expose WillBuildCustomMeshes so if you decide to change the behavior of CustomRenderMeshProvider2 you’d need to make sure to adjust its equivalent in RenderMeshProvider.

From my point of view, the ideal solution would be to have another conduit channel covering all display modes (Wireframe/Shaded/Rendered).

If this is not possible, then yes, enabling CRM providers in Wireframe would allow me to avoid using the additional conduit just for this display mode.

Going back to the ‘thousands of meshes’ mentioned earlier. Are CRMs equally performant as Conduits? If not, then I’d probably still keep the conduit running for Wireframe and Shaded and only enable the slower CRM for the Rendered mode.

While at it, it’d be great if you could also expose the DrawShadedMeshes function to RhinoCommon so that it’s more easily available for managed plugins.