meta data for this page
  •  

User render configuration

The SpeedTree 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 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 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;
};