Contents

Correcting normals after vertex displacement with Shader Graph

Approximate new vertex normals for materials that displace a mesh’s vertices.

Overview

Vertex displacement shaders can create a myriad of dynamic effects, such as water, terrain, and undulating surfaces. However, displacing vertex positions without correcting the vertex normals can result in incorrect lighting, as shown in the examples below:

Assuming the vertices of your mesh have correct normals predisplacement, and the displacement is a continuous function of the vertex positions in model space, you can correct the normals of your vertex displacement shader by sampling the displacement of neighboring points along the surface of the mesh and approximating a new normal direction with the cross product.

For each vertex in the mesh, find two neighboring points on the surface of the mesh that are in orthogonal directions to one another. Approximate the new normal direction by displacing these points with the same function that displaces the original vertex, and then take the cross product between the new vectors pointing toward them. See Loading entities with ShaderGraph materials to download a sample project containing this shader along with many others.

Create a displacement node graph

Start by creating a node graph that takes a position input in object or model space and outputs a model position offset vector. See Creating a vertex displacement material with Shader Graph for one such example, replicated below:

[Image]

In this example, the Displacement node graph is as follows:

[Image]

Set up the offset and normal node graph

Create the outline for a node graph that calculates the model position offset and the new normal direction in tandem:

  1. Select the Displacement node graph and all of its inputs.

  2. Control-click on one of the selected nodes and choose Compose Node Graph from the contextual menu.

  3. Name the new NodeGraph node “OffsetAndNormal”.

  4. Add a new output of type Vector3 (Float) to the OffsetAndNormal node graph and name it “Normal”.

  5. Connect the Normal output of the OffsetAndNormal node graph to the Normal input of the Geometry Modifier node.

  6. Open the OffsetAndNormal node graph and optionally rename its inputs and outputs.

The main body of the shader is shown here:

[Image]

See the interior of the OffsetAndNormal node graph below:

[Image]

Set up the neighbors node graph

Prepare a new node graph to calculate two orthogonal neighboring positions at a given distance away from a given vertex position:

  1. Inside the OffsetAndNormal node graph, add a new NodeGraph node and name it “Neighbors”.

  2. Add a new input of type Float to the Neighbors node and name it “Distance”.

  3. Add a new input of type Vector3 (Float) to the Neighbors node and name it “Position”.

  4. Add a new output of type Vector3 (Float) to the Neighbors node graph and name it “Neighbor1Position”.

  5. Add a new output of type Vector3 (Float) to the Neighbors node graph and name it “Neighbor2Position”.

  6. Connect the Position node to the Position input of the Neighbors node.

  7. Set the Distance input value of the Neighbors node to a small value, such as 0.01.

The interior of the OffsetAndNormal node graph is as follows:

[Image]

Find neighboring positions along the surface

Calculate two orthogonal neighbor positions inside the Neighbors node graph:

  1. Add a Normal node and set its Space to object.

  2. Calculate a vector orthogonal to the normal (tangent to the surface) by taking the cross product of the Normal node with an arbitrary unit vector, such as (1, 0, 0), using a Cross Product node. Name it “Tangent”.

  3. Compute the cross product of the Tangent Cross Product node with the Normal node and name it “Bitangent”. This produces a second vector orthogonal to the normal, but also tangent to the surface of the mesh.

  4. Normalize the output of both the Tangent and the Bitangent Cross Product nodes with Normalize nodes.

  5. Multiply the output of both the Normalize nodes by the Distance input with Multiply nodes.

  6. Compute the two neighbor positions by adding the Position input to the result of both the Multiply nodes with Add nodes.

  7. Connect the output of the Add nodes to the Neighbor1Position and Neighbor2Position outputs.

The interior of the Neighbors node graph is as follows:

[Image]

While this node graph outputs orthogonal neighboring positions under most circumstances, it fails when the normal vector is similar or equal to the arbitrary unit vector you take the cross product with to get the tangent. One approach to remedying this issue is to compute the similarity between the normal and the arbitrary unit vector with a dot product, and then linearly interpolate toward a different arbitrary unit vector by the similarity value before computing the first tangent, as shown here:

[Image]

Sample the displacement at the neighboring positions

Calculate the model position offset at both of the neighboring positions by making instances of your Displacement node graph:

  1. Create two instances of your Displacement node graph (in the Inspector, Control-click > Create Instance), naming one “DisplacementNeighbor1” and the other “DisplacementNeighbor2”.

  2. Connect the Neighbor1Position output of the Neighbors node graph to the Position input of the DisplacementNeighbor1 node graph instance.

  3. Connect the Neighbor2Position output of the Neighbors node graph to the Position input of the DisplacementNeighbor2 node graph instance.

Perform these steps inside the OffsetAndNormal node graph, connecting the Amplitude, Rate, and Scale input nodes to the corresponding inputs of each Displacement node, as shown in the image below:

[Image]

Compute the new normal direction

Calculate the new normal direction by taking the cross product between the directions from the displaced vertex position to the displaced neighbor positions:

  1. For each of the three displacement nodes, get the displaced position by adding the same value connected to the Position input of the node to the Model Position Offset output of the node with an Add node.

  2. Calculate the vectors pointing from the displaced vertex position to the displaced neighbor positions by subtracting the original displaced vertex position from the displaced neighbor positions with Subtract nodes.

  3. Take the cross product between the vectors with a Cross Product node to get the new normal direction. Name it “Normal”.

  4. Normalize the normal direction with a Normalize node.

  5. Connect the normalized normal direction to the Normal output.

The resulting node graph is as follows:

[Image]

The following videos show the result of correcting the normals for a shader displacing the vertices of a plane mesh:

See Also

Shader Graph