# Contours Unity 6 URP : Render Graph et correction des artefacts aux coins
Dans Unity 6 avec URP 17+, les post-effets nécessitent une refonte complète pour le Render Graph. La méthode Execute est dépréciée, fullscreenMesh est remplacée par Blitter. Les effets de contour traditionnels via ScriptableRendererFeature se cassent à cause des changements d'architecture de rendu.
L'approche standard en deux passes : créer un masque de silhouette des objets en blanc, puis détection de contours plein écran pour dessiner le contour. Nous corrigeons les problèmes clés :
- Les trous aux coins nets des modèles.
- L'épaisseur du contour qui varie avec la résolution d'écran.
Shader de masque de silhouette
La première passe génère une texture avec un remplissage blanc pour les objets cibles. Pas d'éclairage, d'ombres ou de textures — juste de la géométrie.
Créez Silhouette.shader :
Shader "Hidden/OutlineSilhouette"
{
SubShader
{
Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" }
Pass
{
ZWrite On
ZTest LEqual
Cull Back
HLSLPROGRAM
#pragma vertex Vert
#pragma fragment Frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
struct Attributes {
float4 positionOS : POSITION;
};
struct Varyings {
float4 positionCS : SV_POSITION;
};
Varyings Vert(Attributes input) {
Varyings output;
output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
return output;
}
half4 Frag(Varyings input) : SV_Target {
return half4(1, 1, 1, 1);
}
ENDHLSL
}
}
}
Shader de contour : Dilatation et indépendance de résolution
La seconde passe analyse le masque. Vérifier seulement 4 voisins cause des ruptures aux coins. Nous utilisons la dilatation : échantillonner 8 voisins (4 cardinaux + 4 diagonaux), étendre le masque et soustraire l'original.
L'épaisseur est liée à une résolution de référence (1080p). Elle s'adapte via scaleFactor = _ScreenParams.y / 1080.
HardOutline.shader :
Shader "Custom/HardOutline"
{
Properties
{
_OutlineColor ("Outline Color", Color) = (1, 0, 0, 1)
_Thickness ("Outline Thickness", Range(1, 10)) = 1.0
}
SubShader
{
Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" }
Pass
{
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off ZTest Always Cull Off
HLSLPROGRAM
#pragma vertex Vert
#pragma fragment Frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl"
TEXTURE2D_X(_OutlineRenderTexture);
SAMPLER(sampler_OutlineRenderTexture);
float4 _OutlineColor;
float _Thickness;
half4 Frag(Varyings input) : SV_Target {
float2 uv = input.texcoord;
half centerAlpha = SAMPLE_TEXTURE2D_X(_OutlineRenderTexture, sampler_OutlineRenderTexture, uv).a;
float2 texelSize = float2(1.0 / _ScreenParams.x, 1.0 / _ScreenParams.y);
float referenceHeight = 1080.0;
float scaleFactor = _ScreenParams.y / referenceHeight;
float scaledThickness = _Thickness * scaleFactor;
float2 offset = texelSize * scaledThickness;
half up = SAMPLE_TEXTURE2D_X(_OutlineRenderTexture, sampler_OutlineRenderTexture, uv + float2(0, offset.y)).a;
half down = SAMPLE_TEXTURE2D_X(_OutlineRenderTexture, sampler_OutlineRenderTexture, uv + float2(0, -offset.y)).a;
half left = SAMPLE_TEXTURE2D_X(_OutlineRenderTexture, sampler_OutlineRenderTexture, uv + float2(-offset.x, 0)).a;
half right = SAMPLE_TEXTURE2D_X(_OutlineRenderTexture, sampler_OutlineRenderTexture, uv + float2(offset.x, 0)).a;
half topLeft = SAMPLE_TEXTURE2D_X(_OutlineRenderTexture, sampler_OutlineRenderTexture, uv + float2(-offset.x, offset.y)).a;
half topRight = SAMPLE_TEXTURE2D_X(_OutlineRenderTexture, sampler_OutlineRenderTexture, uv + float2(offset.x, offset.y)).a;
half bottomLeft = SAMPLE_TEXTURE2D_X(_OutlineRenderTexture, sampler_OutlineRenderTexture, uv + float2(-offset.x, -offset.y)).a;
half bottomRight = SAMPLE_TEXTURE2D_X(_OutlineRenderTexture, sampler_OutlineRenderTexture, uv + float2(offset.x, -offset.y)).a;
half maxNeighbors = max(max(up, down), max(left, right));
half maxDiagonals = max(max(topLeft, topRight), max(bottomLeft, bottomRight));
half dilated = max(centerAlpha, max(maxNeighbors, maxDiagonals));
half edge = saturate(dilated - centerAlpha);
return half4(_OutlineColor.rgb, edge * _OutlineColor.a);
}
ENDHLSL
}
}
}
Intégration Render Graph : Implémentation C#
OutlineFeature utilise RecordRenderGraph. TextureDesc remplace GetTemporaryRT, Blitter.BlitTexture gère le rendu plein écran.
Changements clés :
CreateTexture(texDesc)pour_OutlineRenderTexture.AddRasterRenderPass<PassData>avecSetRenderAttachment.CreateRendererListpour filtrageLayerMask.SetGlobalTextureAfterPasspour passer au shader.
Script complet OutlineFeature.cs :
using System;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using UnityEngine.Rendering.RenderGraphModule;
using UnityEngine.Experimental.Rendering;
public class OutlineFeature : ScriptableRendererFeature
{
[Serializable]
public class OutlineSettings
{
public LayerMask LayerMask;
public Material SilhouetteMaterial;
public Material OutlineMaterial;
}
public OutlineSettings settings = new OutlineSettings();
class RenderSilhouettePass : ScriptableRenderPass
{
private Material silhouetteMaterial;
private FilteringSettings filteringSettings;
private static readonly int SilhouetteTextureID = Shader.PropertyToID("_OutlineRenderTexture");
public RenderSilhouettePass(LayerMask layerMask, Material mat)
{
silhouetteMaterial = mat;
filteringSettings = new FilteringSettings(RenderQueueRange.opaque, layerMask);
}
private class PassData
{
public RendererListHandle rendererList;
}
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
{
if (silhouetteMaterial == null) return;
UniversalCameraData cameraData = frameData.Get<UniversalCameraData>();
UniversalRenderingData renderingData = frameData.Get<UniversalRenderingData>();
UniversalLightData lightData = frameData.Get<UniversalLightData>();
TextureDesc texDesc = new TextureDesc(cameraData.cameraTargetDescriptor.width, cameraData.cameraTargetDescriptor.height)
{
colorFormat = GraphicsFormat.R8G8B8A8_UNorm,
clearBuffer = true,
clearColor = Color.clear,
name = "_OutlineRenderTexture"
};
TextureHandle silhouetteTexture = renderGraph.CreateTexture(texDesc);
using (var builder = renderGraph.AddRasterRenderPass<PassData>("Render Silhouette", out var passData))
{
var sortingCriteria = cameraData.defaultOpaqueSortFlags;
var drawingSettings = CreateDrawingSettings(new ShaderTagId("UniversalForward"), renderingData, cameraData, lightData, sortingCriteria);
drawingSettings.overrideMaterial = silhouetteMaterial;
RendererListParams listParams = new RendererListParams(renderingData.cullResults, drawingSettings, filteringSettings);
passData.rendererList = renderGraph.CreateRendererList(listParams);
builder.UseRendererList(passData.rendererList);
builder.SetRenderAttachment(silhouetteTexture, 0, AccessFlags.Write);
builder.AllowPassCulling(false);
builder.SetGlobalTextureAfterPass(silhouetteTexture, SilhouetteTextureID);
builder.SetRenderFunc((PassData data, RasterGraphContext context) =>
{
context.cmd.DrawRendererList(data.rendererList);
});
}
}
}
class DrawOutlinePass : ScriptableRenderPass
{
private Material outlineMaterial;
public DrawOutlinePass(Material mat)
{
outlineMaterial = mat;
requiresIntermediateTexture = true;
}
private class PassData
{
public Material material;
}
public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
{
if (outlineMaterial == null) return;
UniversalResourceData resourceData = frameData.Get<UniversalResourceData>();
TextureHandle activeColorTexture = resourceData.activeColorTexture;
using (var builder = renderGraph.AddRasterRenderPass<PassData>("Draw Outline", out var passData))
{
passData.material = outlineMaterial;
builder.SetRenderAttachment(activeColorTexture, 0);
builder.AllowPassCulling(false);
builder.SetRenderFunc((PassData data, RasterGraphContext context) =>
{
Blitter.BlitTexture(context.cmd, new Vector2(1, 1), data.material, 0);
});
}
}
}
private RenderSilhouettePass silhouettePass;
private DrawOutlinePass outlinePass;
public override void Create()
{
silhouettePass = new RenderSilhouettePass(settings.LayerMask, settings.SilhouetteMaterial)
{
renderPassEvent = RenderPassEvent.AfterRenderingOpaques
};
outlinePass = new DrawOutlinePass(settings.OutlineMaterial)
{
renderPassEvent = RenderPassEvent.AfterRenderingSkybox
};
}
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
if (settings.SilhouetteMaterial != null && settings.OutlineMaterial != null)
{
renderer.EnqueuePass(silhouettePass);
renderer.EnqueuePass(outlinePass);
}
}
}
Configuration dans l'éditeur
- Ajoutez
OutlineFeatureà vos données de rendu URP. - Assignez
SilhouetteMaterialetOutlineMaterial. - Définissez
LayerMaskpour les objets à contourner. - Ajustez
_Thicknesset_OutlineColordans l'inspecteur du matériauHardOutline.
Points clés
- Dilatation corrige les trous aux coins : 8 voisins au lieu de 4 assurent des bords lisses aux coins nets.
- Indépendance de résolution : Épaisseur de contour constante via mise à l'échelle sur référence 1080p.
- Optimisation Render Graph : Gestion automatique de la mémoire avec
TextureDescau lieu des RT temporaires. - Performances : Deux passes, sans flou — idéal pour mobile et 4K.
- Compatibilité : URP 17+, Unity 6 uniquement.
— Editorial Team
Aucun commentaire pour le moment.