Quick answer: Connect your displaced position into the Vertex output’s Position pin, not Position Predisplacement. Inflate mesh bounds (mesh.bounds = new Bounds(...)) so the displaced silhouette is not frustum-culled when the original bounds exit view.
Here is how to fix Unity Shader Graph vertex shaders where the math is correct but the mesh visually does not deform. You add a Sample Texture, multiply by the normal, and feed it back somewhere on the master node, but the geometry sits there unmoved. Two pins look similar at the top of the master node: only one of them actually displaces.
The Symptom
Shader Graph vertex math runs (you can see it in graph preview), but the rendered mesh does not deform. Or it deforms in the asset preview but not on the GameObject in scene.
What Causes This
Connected to Position Predisplacement. This pin is the input value, useful for reading the original position. Modifying it does nothing because the vertex stage uses the lower Position pin output.
Frustum culling. Even with displacement working, the renderer culls based on the original mesh bounds. If the original bounds are off-screen but the displaced geometry would be visible, the mesh disappears entirely.
Inactive vertex stage. Some Shader Graph templates lock the vertex stage. Confirm the master node has a Vertex output expanded.
SRP mismatch. A URP-targeted graph used on an HDRP material (or vice versa) compiles a fallback that may strip vertex modification.
The Fix
Step 1: Connect to Position, not Position Predisplacement. In the master output (URP Lit / HDRP Lit), expand Vertex and find Position. Drag your displaced Vector3 into that pin. The Predisplacement pin is for reading the input.
Step 2: Inflate mesh bounds for displaced meshes.
using UnityEngine;
[RequireComponent(typeof(MeshFilter))]
public class InflateBounds : MonoBehaviour
{
[SerializeField] private float displacementMargin = 2f;
void Start()
{
var mf = GetComponent<MeshFilter>();
var mesh = mf.mesh;
var b = mesh.bounds;
b.Expand(displacementMargin * 2f);
mesh.bounds = b;
}
}
Inflated bounds keep the renderer in the frustum even when displaced.
Step 3: Verify SRP target. Open the Shader Graph asset. Top-left, the Master Stack header shows the target (URP/HDRP). Match this to your project’s SRP. Otherwise you are compiling for the wrong pipeline.
Step 4: Test with a known-good displacement.
// Simple test: oscillate Y
Position.y += sin(_Time.y) * 0.5;
If this oscillates the mesh, the pipeline works. Build up to your real displacement from there.
Step 5: Use Custom Function for advanced math. If your displacement needs noise or matrix transforms not in graph nodes, drop a Custom Function node and write HLSL inline. Make sure to declare it for the vertex stage if needed:
// Custom Function: VertexDisplace.hlsl
void VertexDisplace_float(float3 position, float time, out float3 result)
{
result = position + float3(sin(position.x + time), 0, 0);
}
Shadow Cast Considerations
By default, the shadow caster pass uses the same vertex displacement only if Shader Graph’s Override Vertex Position For Shadow is configured correctly. Without it, your displaced mesh casts shadows from the original silhouette — producing visible shadow-mesh mismatch. See related issues for fixing this.
“Position pin moves the mesh. Predisplacement pin reads the input. Inflate bounds so culling does not undo your art.”
Related Issues
For Shader Graph time issues, see Shader Graph Time Build. For shadow displacement mismatch, see Vertex Displacement Shadow.
Position pin (lower). Inflate bounds. Match SRP target. The mesh moves.