6
$\begingroup$

Im trying to create a volumetric material that uses a 3d texture. I need this volumetric to be animated along a curve. Right now, I have the material using generated coordinates. But the 3d texture does not bend with the object. Is there a way I can have a 3d coordinate space that is calculated before the bend of the object? Or perhaps a way to bend my coordinates to match the geometry?

Repro Steps:

-Create a cylinder with multiple divisions down its length.

-Rotate the cylinder 90* on the Y axis

-Scale the cylinder to 1,1,4

-Create a curve and add a curve modifier to the cylinder using the created curve

-In cycles create a musgrave texture with Generated texture coordinates as the vector input

-Create a colour ramp to clamp the texture values

-Create an emmisive shader and plug the colour ramp into the color of the shader

-plug the shader into the volumetric output

-notice how the texture does not curve with the geometry

$\endgroup$
  • $\begingroup$ Have you tried using the object coordinates? $\endgroup$ – cegaton Feb 26 '18 at 14:47
  • $\begingroup$ yes, it appears that object coordinates, like the generated coordinates are defined by the bounding box of the object after all modifiers have been applied. thanks for the suggestion, unfortunate it doesn't work. $\endgroup$ – Eavin Feb 26 '18 at 15:00
  • 1
    $\begingroup$ Please use the edit link at the bottom of your question (i.stack.imgur.com/lXFuK.png) and add more information. Add images that might help us understand your scene. Consider sharing your .blend file so that others can inspect it. You can upload it at blend-exchange.giantcowfilms.com and then paste the resulting link as part of your question. $\endgroup$ – cegaton Feb 26 '18 at 15:02
  • $\begingroup$ I cant share my file as its for a project under nda. I can however give detailed repro steps. $\endgroup$ – Eavin Feb 26 '18 at 15:21
  • 1
    $\begingroup$ Related blender.stackexchange.com/questions/51349/… $\endgroup$ – kheetor Feb 26 '18 at 23:26
6
$\begingroup$

It can be done!

Approaching it on a Surface

Before we try to do this with a 3-dimensional texture, let's attempt it in 2-dimensions. This is actually fairly straightforward. We can create a proxy mesh by duplicating our object (Shift + D), removing the modifiers from our duplicated object, then setting the Texture Mesh setting for our original object to the mesh of the second object:

Mesh properties panel

Result:

render result

Hey, that works perfectly! Unfortunately, it doesn't work for 3D textures. When we attempt this with a volumetric texture it does absolutely nothing.

Approaching it in a Volume

The best way to do this in a volume (believe me, I've tested others) is to create a proxy mesh, then take the coordinates from the proxy mesh.

What should our proxy mesh look like?

Unfortunately, Blender does not provide an easy way to transfer volumetric data on and off of meshes. Our saving grace is the Point Density node in Cycles. The Point Density node will create a volumetric texture based off of the vertices of any mesh, and interpolate between them. It follows, then, that our coordinate proxy mesh should be a 3D grid of points.

Creating the proxy mesh

We'll start by adding a plane, then in Edit Mode, moving and scaling it so it has the same size and location of the bottom of the cylinder's bounding box (in the image, I enabled the bounding box display):

plane in edit mode

Next, we need to have more points in our coordinate grid. Use the Loop Cut tool (Ctrl + R) to add lots of loop cuts in both directions, trying to make the faces roughly square (I did $63$ and $15$, to get a $64\times16$ grid:

subdivided plane

Next, in Object Mode add an Array modifier. Use Constant Offset in the $Z$ direction to create a "stack" of planes (I used an offset of $0.125$). Add enough planes to fill the bounding box of the cylinder:

stack of planes

Apply the Array modifier and add a Curve modifier. Set up the Curve modifier the same way that it is set up on the cylinder. Check to make sure everything deforms properly (proxy mesh set to Wireframe display):

checking deformation

Now the hard part:

Creating a coordinate system

The Point Density node can only take color from Vertex Colors, Weights, and Normals. By far the most customizable of these options is Vertex Colors. We need to find a way to put Texture Coordinates into Vertex Colors.

Let's use Animation Nodes! After downloading and installing the add-on, go into the Node Editor and add a new NodeTree.

Before we start adding nodes, we need to figure out how to emulate Generated texture coordinates. According to the Blender Manual:

Generated
Automatically-generated texture coordinates from the vertex positions of the mesh without deformation, keeping them sticking to the surface under animation. Range from 0.0 to 1. 0 over the bounding box of the undeformed mesh.

This means we need to normalize the vectors representing the positions of our vertices and put them in the positive-positive-positive octant. Here is the formula we will use:

$$\mathbf p'=\frac{\mathbf p - \mathbf c}{\mathbf d} + \mathbf{0.5}$$

where $\mathbf p$ is a point in 3D space, $\mathbf c$ is the center of the bounding box, and $\mathbf d$ is the vector of the longest diagonal of the bounding box (i.e. the dimensions).

Let's get our inputs first. Add the following nodes (press Shift + A to bring up the list of nodes or Ctrl + A to search):

input nodes

The object in the Object Input node is our coordinate proxy object. The text in the Object Attribute node is dimensions. Note that "Use Modifiers" is unchecked. This ensures that the coordinates we get don't include the Curve modifier. Next, we'll use Vector Math nodes to implement the above formula:

vector math nodes

The Convert node will be added automatically. We have one more issue with the texture coordinates: texture coordinates are encoded in a linear color space, while Vertex Colors are encoded in sRGB. From the linked Wikipedia page, we get the formula for encoding colors in sRGB:

$$C_\text{srgb}=\begin{cases} 12.92C_\text{linear}, & C_\text{linear} \leq 0.0031308\\ 1.055C_\text{linear}^{1/2.4}-0.055, & C_\text{linear} > 0.0031308 \end{cases}$$

To utilize this, create a loop subprogram by adding a Loop Input node. Create this loop:

sRGB encoding loop

The numbers are truncated in the screenshot. Here they are, row by row:

0.003131
12.92
0.416667  1.055  0.055

Now, add 3 Invoke Subprogram nodes with Separate and Combine Vector nodes to encode the $R$, $G$, and $B$ channels.

sRGB encoding

Now, we need to get our vectors into a Vertex Color. @Omar Ahmed has already made fabulous explanation of how to do this. You can find his answer here. I will copy the relevant parts below, but I recommend reading his answer for a detailed (and illustrated!) explanation of how Vertex Colors work in Blender.

First, create this loop:

Get face loops loop

Then, create this script (make sure to name things properly):

Set Vertex Col script

Here is the script:

#Create a vertex color map named "Normals" if it is not already created.
if "Coords" in Object.data.vertex_colors:
    map = Object.data.vertex_colors["Coords"]
else:
    map = Object.data.vertex_colors.new("Coords")

#Loop over Colors and assign them to the vertex color map.
for i, color in enumerate(Colors):
    map.data[i].color = color

Finally, link up the last nodes (for an explanation of how they work, see the answer linked above). Here's the final tree:

final nodetree

Getting the coordinate system into Cycles

We will be using the Point Density node as I mentioned above. On the Cylinder object, create a new material. Add a Point Density node. Set it to Object Vertices, with the Coordinate Proxy as the object and the Color Source as Vertex Color. Choose the Coords Vertex Color. Next, disable visibility for the proxy object, and link the Color output of the Point Density node to an Emission node plugged into the Volume socket of the material output (in the picture I'm using the Node Wrangler add-on to get an Emission node):

point density node

Activate Rendered View in the viewport (Shift + Z). Position the 3D View so that you are looking at one of the "caps" of the cylinder. Adjust the Radius parameter of the Point Density node to be just big enough that there are no "gaps" ($0.1$ worked for me):

wrong vs right radius

Now we have our coordinates! Use the Color output just like you would normal texture coordinates, and volumetric texture-ize away!

final render

Just remember that anything you do to animate the cylinder also needs to be done to the proxy.



(Requires Animation Nodes)

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.