Hydra Procedural Houdini Plugin


Following the topic of my old post about building a hydra procedural plugin for usd, I decided to try and get it working inside of Houdini. This will be a much shorter post about how one can port the old project to also work as a Houdini plugin.

Prerequisites

In order to build any plugin for Houdini, you need the Houdini development toolkit (HDK) which is installed with Houdini by default. In this case we need it in order to link against the usd libraries shipped with Houdini rather than the “vanilla” installs we used before. We’ll also need the Houdini boost and python libraries. It’s also worth mentioning that this will only work with Houdini 20 or later, since earlier versions of Houdini come with old usd versions.

Updating the CMake files

In order to link against Houdini, we need to link against its libraries rather than our locally installed usd libraries. The same goes for python and boost. Since I want to still be able to build the old “vanilla”-usd plugin, I decided to create a cmake function that will generate the targets, and simply call it twice, once for the “vanilla” plugin and once for the Houdini plugin.

The create_target function:

CMake
function(create_target TARGET)
    set(PLUG_INFO_LIBRARY_PATH "../../${TARGET}.dylib")
    set(PLUG_INFO_RESOURCE_PATH "resources")
    set(PLUG_INFO_ROOT "..")

    set(oneValueArgs
        PYPACKAGE_NAME
    )
    set(multiValueArgs
        HEADERS
        SOURCES
        PY_SOURCES
        INCLUDE_DIRS
        LIBS
    )
    set(options
        BUILD_HOUDINI
    )
    ....

This function will essentially do what we did in the old project, just that it listens to the arguments passed to this function instead of hard coded paths. It will also make some adjustments depending on BUILD_HOUDINI being true or false. Check the full function definition on GitHub.

I then updated the usd/CMakeLists.txt file to link against Houdini 20:

CMake
....
if (${BUILD_HOUDINI_PLUGIN})
    # HOUDINI LIBRARY
    
    set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
    set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
    set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE)
    
    set(HPLUGIN_NAME ${USDPLUGIN_NAME}Houdini)
    
    list( APPEND CMAKE_PREFIX_PATH "${HOUDINI_INSTALL_ROOT}/toolkit/cmake" )
    
    find_package(Houdini REQUIRED)

    set(PYTHON_LIB python3.10)
    set(PYTHON_LIB_NUMBER python310)
    set(H_INCLUDE_DIR "${HOUDINI_INSTALL_ROOT}/toolkit/include")

    if (${APPLE})
        set(H_LIB_DIR "${HOUDINI_INSTALL_ROOT}/../Libraries")
        set(PYTHON_LIB_DIR "${HOUDINI_INSTALL_ROOT}/Frameworks/Python.framework/Versions/3.10")
        set(PYTHON_FRAMEWORK "${HOUDINI_INSTALL_ROOT}/Frameworks/Python.framework")
    else()
        set(H_LIB_DIR "${HOUDINI_INSTALL_ROOT}/dsolib")
        set(PYTHON_LIB_DIR "${HOUDINI_INSTALL_ROOT}/python/lib")
        set(PYTHON_FRAMEWORK "")
    endif()

    set(PYTHON_INCLUDE_PATH "${H_INCLUDE_DIR}/${PYTHON_LIB}")
    set(PXR_INCLUDE_DIR ${H_INCLUDE_DIR})

    set(BOOST_NAMESPACE hboost)
    set(BOOST_PYTHON_LIB ${BOOST_NAMESPACE}_${PYTHON_LIB_NUMBER})
    set(BOOST_INCLUDE_DIR ${H_INCLUDE_DIR}/${BOOST_NAMESPACE})

    link_directories(${HPLUGIN_NAME} PUBLIC ${H_LIB_DIR})
    link_directories(${HPLUGIN_NAME} PUBLIC ${PYTHON_LIB_DIR}/lib)

    create_target(
        ${HPLUGIN_NAME}
        BUILD_HOUDINI
        PYPACKAGE_NAME
            ${PYPACKAGE_NAME}
        HEADERS
            ${headers}
        SOURCES
            ${sources}
        PY_SOURCES
            ${py_sources}
        INCLUDE_DIRS
            ${PYTHON_INCLUDE_PATH}
            ${PXR_INCLUDE_DIR}
            ".."
        LIBS
            Houdini
            ${PYTHON_FRAMEWORK}
            ${OCLMODULE_NAME}
        )

    endif()

I should mention that I haven’t tested building this on any other platform than my Mac, so other users should be weary of some of those paths in there.

Since I now changed these install paths from being hard coded, we’ll need to build the project with some arguments. I use VSCode and therefore just set them in the settings.json file:

CMake
    "cmake.configureArgs": [
        "-DUSD_INSTALL_ROOT=/opt/USD",
        "-DHOUDINI_INSTALL_ROOT=/Applications/Houdini/current/Frameworks/Houdini.framework/Resources",
        "-DCMAKE_INSTALL_PREFIX=/opt/USD_resources"
    ]

Small Changes in Code

Since we’re now building two separate plugins, I figured it makes sense to name the Houdini plugin binary something else. I went with hairProcHoudini. Because of this, we need to update some cpp files and the plugInfo.json file at compile time. For this, we’re passing a compile definition when building the Houdini plugin, e.g BUILD_HOUDINI_PLUGIN. I then updated all the files in the project which was affected by the change.

usd/wrapHairProcedural.cpp
C++
#ifdef BUILD_HOUDINI_PLUGIN
    #include <hboost/python.hpp>
    using namespace hboost::python;
#else
    #include <boost/python.hpp>
    using namespace boost::python;
#endif

The same goes for wrapTokens.cpp

usd/moduleDeps.cpp
C++
#ifndef BUILD_HOUDINI_PLUGIN
    TfScriptModuleLoader::GetInstance().RegisterLibrary(TfToken("hairProc"), TfToken("vik.HairProc"), reqs);
#else
    TfScriptModuleLoader::GetInstance().RegisterLibrary(TfToken("hairProcHoudini"), TfToken("vik.HairProcHoudini"), reqs);
#endif
usd/plugInfoHoudini.json

I also made a copy of the old plugInfo.json file and named it plugInfoHoudini.json, in order to install it twice and keep the correct file configurations between the two copies (if not using two files, both copies of the installed plugInfo.json files will point to the same binary because configure_file is called twice on the same source).

Build and Test

This should be enough to build the project and run it in Houdini 20. Just make sure to set these environment variables as before, in order to Houdini to find our plugin:

Bash
export PYTHONPATH=${PYTHONPATH}:/opt/USD_resources/lib/python;
export PXR_PLUGINPATH_NAME=/opt/USD_resources/lib/usd/hairProcHoudini/resources;
export USDIMAGINGGL_ENGINE_ENABLE_SCENE_INDEX=1;

Worth mentioning that I was stuck for a long time trying to figure our why my plugin was misbehaving, until I was told that SideFX doesn’t parse the USDIMAGINGGL_ENGINE_ENABLE_SCENE_INDEX environment variable as I expected, since I was setting it to true, rather than 1. So don’t be like me, set it to 1 and save yourself the headache.

Starting up Houdini now and loading our testenv/hairProc.usda file in solaris should result in a functional plugin!

If building both plugins, the install directory should look like:

opt/
|__USD_resources/
    |__lib/
        |––hairProc(.dylib, .so, .dll)
        |––hairProcHoudini(.dylib, .so, .dll)
        |__python/
            |__vik/
                |––__init__.py
                |__hairProc/
                    |––_hairProc.so
                |__hairProcHoudini/
                    |––_hairProcHoudini.so
        |__usd/
            |__hairProc/
                |__resources/
                    |__hairProc/
                        |––schema.usda
                    |––plugInfo.json
                    |––generatedSchema.usda
            |__hairProcHoudini/
                |__resources/
                    |__hairProc/
                        |––schema.usda
                    |––plugInfo.json
                    |––generatedSchema.usda
    |__include/
        |__usd/
            |––headers..

Notes

Currently, at Houdini 20.0.506, Hydra 2.0 isn’t fully integrated and there are some unexpected behaviors. For example:

  • I haven’t gotten motion blur to work when USDIMAGINGGL_ENGINE_ENABLE_SCENE_INDEX is enabled
  • Stage scene index is not always initialized, need to restart the renderer to kickstart it
  • Hair is not deformed when rendering to Mplay (Karma)
  • Selection highlighting is not visible when USDIMAGINGGL_ENGINE_ENABLE_SCENE_INDEX is enabled

There might be some other bugs out there that I haven’t seen, but for now, this is it! Not a very complicated setup and will definitely be useful in the future once Hydra2.0 is fully integrated in to the render engines and Houdini.

, , ,