Implémentation de fonctionnalités de rendu via ScriptableRendererFeature
Contrairement au pipeline intégré (Built-in) qui reposait sur l'attachement de CommandBuffer directement aux caméras pour étendre les capacités de rendu, l'URP introduit une approche plus modulaire via les RendererFeature. Ces fonctionnalités ne sont plus liées à une caméra spécifique, mais sont plutôt intégrées aux paramètres du moteur de rendu lui-même, comme le ForwardRenderer.
Pour générer un modèle de base, il suffit de se rendre dans le panneau Projet, de faire un clic droit et de naviguer vers Create > Rendering > Universal Render Pipeline > Renderer Feature. Le script généré contient une classse imbriquée héritant de ScriptableRenderPass, laquelle est injectée dans la file d'attente des passes via la méthode AddRenderPasses.
Cette architecture permet de définir précisément le moment d'exécution de chaque passe. Par exemple :
customRenderPass.injectionPoint = RenderPassEvent.BeforeRenderingPostProcessing;
Cette granularité est particulièrement avantageuse lorsqu'un effet complexe nécessite l'intervention de plusieurs passes à des étapes distinctes du pipeline.
Une autre différence majeure réside dans l'exécution des CommandBuffer. Alors qu'ils étaient limités à des étapes prédéfinies dans l'ancien pipeline, ils sont désormais exécutés de manière immédiate dans l'URP. L'exécution se fait directement via le contexte de rendu :
public override void Execute(ScriptableRenderContext executionContext, ref RenderingData frameData)
{
CommandBuffer graphicsCmd = CommandBufferPool.Get("CustomGraphicsCmd");
// Opérations graphiques personnalisées
executionContext.ExecuteCommandBuffer(graphicsCmd);
CommandBufferPool.Release(graphicsCmd);
}
De plus, l'intégration d'outils de profilage est grandement simplifiée. En encapsulant les opérations dans un ProfilingScope, il devient possible de visualiser précisément les blocs d'exécution dans le Frame Debugger :
public override void Execute(ScriptableRenderContext executionContext, ref RenderingData frameData)
{
ProfilingSampler frameProfiler = new ProfilingSampler("CustomProfileStep");
CommandBuffer graphicsCmd = CommandBufferPool.Get("ProfilerCmd");
using (new ProfilingScope(graphicsCmd, frameProfiler))
{
MeshRenderer targetMesh = Resources.Load<MeshRenderer>("SampleModel");
graphicsCmd.DrawRenderer(targetMesh, targetMesh.sharedMaterial);
executionContext.ExecuteCommandBuffer(graphicsCmd);
CommandBufferPool.Release(graphicsCmd);
}
}
En activant le niveau de débogage (Debug Level) dans les configurations du pipeline, des informations supplémentaires sont exposées. Par exemple, dans la méthode RenderSingleCamera de l'URP, une vérification conditionnelle permet d'afficher les noms des caméras dans le Frame Debugger :
static void RenderSingleCamera(...)
{
// ...
if (pipelineAsset.debugLevel >= PipelineDebugLevel.Profiling)
{
// Logique de profilage pour l'affichage des noms de caméra
}
}
Optimisation du rendu en masse avec DrawRenderers
Dans le pipeline intégré, le dessin d'objets spécifiques reposait souvent sur des appels répétés à CommandBuffer.DrawRenderer, une méthode qui devient rapidement inefficace lorsque le nombre d'objets augmente. L'URP résout ce problème en exposant l'interface DrawRenderers du ScriptableRenderContext, conçue pour le traitement par lots.
Cette méthode s'appuie sur les résultats d'écrêtage (CullingResults) de la caméra actuelle et applique un filtrage supplémentaire via FilteringSettings. Ce dernier permet notamment d'utiliser le renderingLayerMask, un masque configuré au niveau des composants MeshRenderer ou SkinnedMeshRenderer, qui fonctionne de manière totalement indépendante des calques (Layers) traditionnels.
En combinant ces paramètres avec DrawingSettings et RenderStateBlock, il est possible de contrôler finement l'état du rendu, comme l'écriture dans le Stencil Buffer ou le Depth Buffer :
renderContext.DrawRenderers(cullingData.results, ref drawConfig, ref filterConfig, ref stateConfig);
Si les objets cibles n'étaient pas présents lors de l'écrêtage initial, il est tout à fait possible de déclencher un nouvel écrêtage à la volée pour obtenir des résultats actualisés :
renderContext.Cull(ref cullingParameters);
Cette flexibilité ouvre de nouvelles perspectives pour le rendu de l'interface utilisateur (uGUI). Il devient evnisageable de segmenter le rendu de l'UI en plusieurs passes distinctes, contrôlant ainsi l'écriture dans des Render Targets spécifiques. Il est également possible d'isoler complètement les étapes de rendu de l'UI via une Feature personnalisée et DrawRenderers, bien qu'il faille noter que le Stencil Buffer sera perdu dans ce processus.
Il est aussi possible de rediriger la sortie vers un Render Target temporaire avant d'exécuter le rendu en masse :
public override void Execute(ScriptableRenderContext executionContext, ref RenderingData frameData)
{
CommandBuffer graphicsCmd = CommandBufferPool.Get("TempRtCmd");
int intermediateBufferId = Shader.PropertyToID("_IntermediateTarget");
graphicsCmd.GetTemporaryRT(intermediateBufferId, renderTextureDescriptor);
graphicsCmd.SetRenderTarget(intermediateBufferId);
executionContext.ExecuteCommandBuffer(graphicsCmd);
executionContext.DrawRenderers(/* paramètres de rendu */);
CommandBufferPool.Release(graphicsCmd);
}
Il est important de noter que la méthode consistant à utiliser des valeurs négatives pour la largeur et la hauteur (comme -1 ou -2) afin d'obtenir des fractions de la taille de l'écran dans les CommandBuffer de l'ancien pipeline n'est plus supportée dans l'URP. Le Render Texture doit être correctement configuré et lié lors de la phase de Configure.
Pour des implémentatinos avancées, l'étude des scripts RenderObjects.cs et DrawObjectsPass.cs fournis dans le package de l'URP est fortement recommandée.
Optimisations et contraintes par rapport au pipeline intégré
L'URP impose une limite stricte de 8 lumières par objet individuel, le calcul de l'éclairage étant consolidé au sein d'une passe unique. Bien que cette restriction puisse sembler contraignante, elle permet de maîtriser efficacement le nombre de Batches (lots de rendu) à travers la scène, offrant un gain de performance notable.
Concernant la gestion des ombres, les versions actuelles (comme la v7.3.1) se limitent aux ombres générées par la lumière directionnelle principale et les projecteurs (spotlights).
Enfin, l'intégration de technologies telles que le SRP Batcher contribue significativement à la réduction du CPU overhead, bien que son optimisation nécessite une compréhension approfondie de la compatibilité des shaders et de la gestion des propriétés de rendu au sein du pipeline.