SpeedTree
    Show / Hide Table of Contents

    User render configuration

    The SpeedTree Runtime SDK can be configured to have N render passes, each one defined by a name string. In the reference application, we define Forward, Depth Only (shadow casting), and Deferred in MyRenderPassNames.h. These names are passed in an array to each model object in the Runtime SDK via a call to pModel→SetRenderPasses(). This is demonstrated in CMyPopulate::InitBaseTreeGraphics() in MyPopulate.cpp. Right after this code, there is a function call to establish a render config callback for each model (the reference application uses the same callback for all models). The callback is invoked several times for each model, each time with a different configuration to allow the developers to set as many or as few state changes as they need. For example, it’s possible to use only two render configurations for the entire forest: one for 3D tree and grass models, another for the billboard models. Alternatively, it’s possible to establish a different render state/shader for each LOD and each geometry type within each LOD. The former will generate fewer draw calls/state changes and the latter more (but with potentially less general and shorter shaders).

    Listing 1 below shows our reference application’s render configuration callback, located in MyPopulate.cpp. There is some specialization in this callback, but not a lot. There are separate shaders for grass, trees (each uses a different vertex definition), and billboards. There’s also a distinction for the depth-only pass to allow significantly shorter shaders to be used. The Runtime SDK intelligently sorts by these states to minimize state changes in the whole-forest draw loop.

    Note

    In our example, the base names of the shaders are derived from the name of the vertex packer selected upon .stsdk export for the Modeler.

    Listing 1. Reference Application Example Callback

    void MyRenderStateCallback(const SDrawCallDetails* pDrawCallDetails, SClientPipeline* pClientPipeline)
    {
        assert(pDrawCallDetails);
        assert(pClientPipeline);
    
        // in our example, the shader filenames are based on the name of the vertex packer selected on
        // export from the Modeler app
    
        // get application pointer
        assert(g_pThisApp); // doesn't have to non-null in all uses, but it does for our example
        const SMyCmdLineOptions& sCmdLineOptions = g_pThisApp->GetCmdLineOptions( );
        const CMyConfigFile& cConfigFile = g_pThisApp->GetConfig( );
    
        // render pass flags based on the name of the render pass
        const st_bool c_bForwardLitPass = (strcmp(pDrawCallDetails->m_pPassName, c_pMyRenderPassForwardLit) == 0);
        const st_bool c_bDeferredPass = (strcmp(pDrawCallDetails->m_pPassName, c_pMyRenderPassDeferred) == 0);
        const st_bool c_bDepthOnlyPass = (strcmp(pDrawCallDetails->m_pPassName, c_pMyRenderPassDepthOnly) == 0); // shadows
        const st_bool c_bFogOnMesh = pDrawCallDetails->m_bFogOn3dMesh && c_bForwardLitPass;
    
        // render state
        pClientPipeline->m_sRenderState.m_eFaceCulling = pDrawCallDetails->m_bBillboard ? CULL_FACE_BACK : CULL_FACE_NONE;
        pClientPipeline->m_sRenderState.m_bAlphaToCoverage = !c_bDepthOnlyPass && (sCmdLineOptions.m_nNumSamples > 1);
        pClientPipeline->m_sRenderState.m_bMultisampling = !c_bDepthOnlyPass && (sCmdLineOptions.m_nNumSamples > 1);
        pClientPipeline->m_sRenderState.m_nNumMsaaSamples = c_bDepthOnlyPass ? 1 : sCmdLineOptions.m_nNumSamples;
        pClientPipeline->m_sRenderState.m_bPolygonOffset = c_bDepthOnlyPass;
    
        // figure out the shader path base on the render type
        const st_char* c_pRenderType = "/forward/";
        if (c_bDeferredPass)
            c_pRenderType = "/deferred/";
        else if (c_bDepthOnlyPass)
            c_pRenderType = "/depth/";
    
        pClientPipeline->m_strShaderPath = cConfigFile.m_sWorld.m_strShaderPath + c_pRenderType + 
                                           CPipeline::GetCompiledShaderFolder( );
        CFixedString& strVertexShader = pClientPipeline->m_strVertexShaderFilename;
        CFixedString& strPixelShader = pClientPipeline->m_strPixelShaderFilename;
    
        // get all lowercase vertex packer names
        CFixedString strVertexPacker = pDrawCallDetails->m_bBillboard ? 
            pDrawCallDetails->m_pTree->BillboardVertexPackingName( ).Data( ) : 
            pDrawCallDetails->m_pTree->VertexPackingName( ).Data( );
        for (size_t i = 0; i < strVertexPacker.size( ); ++i)
            strVertexPacker[i] = static_cast<char>(tolower(strVertexPacker[i]));
    
        // when shader names are assigned, the "_vs" and "_ps" prefixes are left off since they'll be added
        // by the shader loading system
        strVertexShader = strPixelShader = CFixedString::Format("%s%s%s", strVertexPacker.c_str( ), 
            c_bDepthOnlyPass ? "_depth" : "", c_bFogOnMesh ? "_fogged" : "");
    
        if (c_bDepthOnlyPass)
            pClientPipeline->m_sRenderState.m_aeRenderTargetTypes[0] = RENDER_TARGET_TYPE_DEPTH;
        else
        {
            pClientPipeline->m_sRenderState.m_aeRenderTargetTypes[0] = RENDER_TARGET_TYPE_COLOR;
            if (sCmdLineOptions.m_bDeferred)
                pClientPipeline->m_sRenderState.m_aeRenderTargetTypes[1] = RENDER_TARGET_TYPE_COLOR;
            pClientPipeline->m_sRenderState.m_nNumRenderTargets = sCmdLineOptions.m_bDeferred ? 2 : 1;
        }
    }
    

    The callback is given an SDrawCallDetails object to base its decisions on, which is in Listing 2.

    Listing 2. SDrawCallDetails Declaration

    struct SDrawCallDetails
    {
        st_int32        m_nPassIndex;
        const st_char*  m_pPassName;
        const st_char*  m_pStsdkFilename;
        SMaterial       m_sMaterial;
        st_int32        m_nLodIndex;
        CLodInfo        m_cLod;
        st_bool         m_bBillboard;
        st_bool         m_bGrassModel;
        st_bool         m_bFogOn3dMesh;
        st_int32        m_nDrawCallIndex;
        SDrawCall       m_sDrawCall;
        const CCore*    m_pTree;
    };
    

    The output of the callback function is an SClientPipeline object (Listing 3) which simply contains shader filenames, a shader path, and a render state object.

    Listing 3. SClientPipeline Declaration

    struct SClientPipeline
    {
        CFixedString        m_strVertexShaderFilename;
        CFixedString        m_strPixelShaderFilename;
        CFixedString        m_strShaderPath;
        SRenderState        m_sRenderState;
    };
    
    Copyright © 2023 Unity Technologies
    • Legal
    • Privacy Policy
    • Cookies
    • Do Not Sell or Share My Personal Information
    • Your Privacy Choices (Cookie Settings)
    "Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
    Generated by DocFX.