I'm looking for a method that compares two vectors in order to use a mix color/shader.

Example : If the camera looks at the object from atop (as the two vectors align) it should select material A. As the camera moves to a different angle away for top of the object, the material gradually change for B.

What I want to do :

  • It has to be based on the object orientation and not the world origin (x,y,z).
  • It should allow to select any angle as an input. This means calculations with the object orientation and the input vector.
  • It cannot be a Facing as it is based on face Normals and wouldn't select the material for the whole object but only for faces.

I thought I had to get:

  • Camera angle
  • Object Angle (pivot) + Any angle I choose (as an input)

Input>Camera Data is the one for the camera (obviously).

I cannot find the orientation of the object (or something related). The nodes Input>Geometry and input>Object data do not seem to provide that data.

Converter>Vector Math seemed to be a good candidate.

Basically I thought a subtract or a Vec1,Vec2 angle comparison would be ok.

But everything I try is not working. Not even close. Any help is welcomed.

I hope those images will help understand the question.

Situation 1 : The camera is aligned with the purple vector. Suzanne has a blue material. BTW This vector is determined by user. When the camera is aligned with the desired vector

Situation 2 : The camera is NOT aligned with the purple vector. Suzanne has a completely different material. The camera not aligned

EDIT 'going further with @Shady' @Shady puck has provided step one solution. When the camera aligns with the Z axis of Suzanne the material is properly selected. I made a blend file and attached it here to document it properly.

more testing on solution A by @Shady puck

Edit 'Going further with batFINGER'

Remarkable. The concept of using one object to provide the necessary data is good as it provides levers (sort of) that can be easily manipulated in the scene. I did implement your solution and did some tests. I only took the liberty of changing some names in your script. Also I took care of respecting name conventions (sort of speak).

A/ It seems I have to select the control object (in this case the yellow cone) before using the animation slider to be able to see a change. It non the less render.

B/ It seems when I press space (B2.80) the animation has trouble changing the colors unless I move the mouse cursor over the custom panel/TextInput named “angle” (in and out). Rendering it, doesn’t seems to work.

C/ If I clear all links and then “link” Suzanne and the Camera, it works but only sometimes and I don’t know why. Selecting one or the other isn’t a guaranty of success. Which makes me wondering about A a little more. Rendering in EEVEE also make the material flicker between 2 colors when I’m luck to see any change.

Maybe I did something wrong. Blend file provided.

  • 1
    $\begingroup$ Are you looking for Dot Product? This outputs 1 if the angle is 0 degrees and 0 if the angle is 90 degrees (and anything in between 0-90 is on a scale of 1-0. $\endgroup$
    – Shady Puck
    Commented Aug 5, 2019 at 15:42
  • $\begingroup$ @ShadyPuck I tried it. But I'm definitely not an expert in this "vector' area. It has to cover 180° so that when you're looking at it from the bottom the right material is selected. Maybe it won't work. $\endgroup$
    – 4E71-NOP
    Commented Aug 5, 2019 at 15:58
  • $\begingroup$ Drivers allowed? $\endgroup$
    – lemon
    Commented Aug 5, 2019 at 18:04
  • $\begingroup$ @lemon Why not ? $\endgroup$
    – 4E71-NOP
    Commented Aug 5, 2019 at 18:36

4 Answers 4


You can use a modification of a Vector Product. Here is the node setup:

enter image description here

Let me explain how this works. First, Geometry > Normal gets the normal vector of each point on the object. The Separate XYZ and Combine XYZ nodes respectively isolate the z-component of each vector and return that value as a vector. Because vectors that lie more in the xy-plane than others will have a smaller z-component, it is necessary to normalize every vector.

At this point, it would generally do to use the Dot Product to compare the magnitudes of the two vectors. The Dot Product maximizes when vectors are parallel and minimizes when they are perpendicular. For some reason (someone please edit if they know why), the Dot Product is rather glitchy in this case. So I substituted the Cross Product, which maximizes when vectors are perpendicular and minimizes when they are parallel. This had the desired effect. I compared the z-vectors with the Geometry > Incoming vector, which gives the vector from a point to the camera.

Normal vector: A vector that is perpendicular to the surface at the given point.
Normalize: To make a vector or vectors all a standard length (usually 1 unit).

Vector math is a fascinating subject. Consider reading more about the cross product (sometimes referred to as the "Vector Product") here.

Edit 1

As the OP referenced, the above solution separates out the view from the $\pm$z-axis for material selection. Now for a custom view, the so-called pink arrow. Try as I might, I could not find an automated solution that directly pulls the rotation data from the pink arrow in the scene.

First, a note on how vectors are defined in three dimensions.

enter image description here
Google Images

A vector, like the one represented by the pink object above, has an x, y, and z-component. In the picture above, to "get" from the origin of the graph to the tip of the vector would require you to move +2 units in the x-direction, +3 units in the y-direction, and +5 units in the z-direction.

This data can be directly (by hand) entered into the node editor. To compare the camera view to the pictured vector, enter the pictured data into the Vector Math > Normalize node, as follows. This has the added advantage of normalizing (see above) the vector at the same time.

enter image description here

Now, with mostly the same node setup...

enter image description here

...we get this result (my pink vector is the (2,3,5) one). Note that the sphere aligns with the pink vector while Suzanne aligns with the z-axis.

enter image description here

For your vector of choice, use the above info on defining vectors to define yours and substitute its components.

Component: A part of a vector that gives its magnitude in one, specific dimension.

Edit 2

Looks to me like my edit worked, but you defined your vector in terms of local as opposed to global coordinates. I have modified your nodes in your .blend file to line up with the pink vector as follows.

I took these two screenshots of the global coordinates -- first of the base of the vector and second of the tip of the vector:

enter image description here

enter image description here

Let's call the ordered pair of the base $(x_0,y_0,z_0)$ and the ordered pair of the tip $(x_1,y_1,z_1)$. From this data, it is possible to find each component of the vector by subtracting$$n_0-n_1=\vec{n}$$ Therefore,$$\begin{align} x_1-x_0&=\vec{x}=0.799\\ y_1-y_0&=\vec{y}=-2.203\\ z_1-z_0&=\vec{z}=0.657\\ \end{align}$$

If we plug these values into the Normalize node, we get the expected behvior.

Ordered pair: A group of elements denoting the one dimensional translations necessary to move from the origin to a point.

  • $\begingroup$ You got it for step 1. The material is properly selected when the camera aligns with the Z or the -Z axis of Suzanne. Now how can i make it so it aligns with the purple vector? I thought i use a vector math/Add but it's not it. NB: OP updated with Step one blend. $\endgroup$
    – 4E71-NOP
    Commented Aug 6, 2019 at 9:42
  • 1
    $\begingroup$ Good to see the puckster of shadiness $\endgroup$
    – batFINGER
    Commented Aug 6, 2019 at 11:32
  • $\begingroup$ @4E71-NOP Please see if my edited answer satisfies your question. If not, let me know what I'm still missing, thanks! $\endgroup$
    – Shady Puck
    Commented Aug 6, 2019 at 14:59
  • $\begingroup$ @ShadyPuck After more testing; apparently ; this solution isn’t working. In the blend file « Bs_htctv_A001b.blend » ( under « edit 3 » in OP) I used a ColorRamp to make the areas A/B séparation thinner. I could see then that the circle area was not aligned as it was supposed to. It seems it’s aligned with the global origin. Therefore not on the desired vector for both solutions. I understand a little better than before (It’s gods magic !) those nodes but it’s still is a mystery to me. Maybe I didn’t implemented it as it should be. I’ll edit the OP when you confirm this (or not of course). $\endgroup$
    – 4E71-NOP
    Commented Aug 7, 2019 at 12:30
  • $\begingroup$ @4E71-NOP Tried to clarify -- looks like the issue was your choice of local as opposed to global coordinates for your mesh vector. See my edit. $\endgroup$
    – Shady Puck
    Commented Aug 7, 2019 at 14:23

Using constraints

enter image description here

The node setting is the following:

enter image description here

It uses two textures coordinates with 'object' output.

One is for the purple arrow.

One is for an empty which copies the camera location (just a parenting at its origin).

From that we make the difference for their X and Y coordinates and check if they are both under a given value.

The trick is the empty also copies the purple arrow rotations:

enter image description here

What is that for?

If the empty copies the orientation of the arrow, they will have the same coordinate space alignment in X, Y (and Z) in the texture coordinates. So that they are comparable.

But as the empty is stuck to the camera, their positions will vary and so we can see if they are matching or not from the texturing point of view.

The value node which gives the limit is a bit a magic number... no explanation about it. You can change it a bit, but I don't know why 1 works well.


If you don't want the material to change when the camera is aligned to the purple arrow but behind it, just add these nodes testing the sign of the Z difference:

enter image description here


Few words about what have called magic number above.

Of course this value should be relative to Dx/Z and Dy/Z (so that you'll have the sinus of an angle) but that depends on what you want to do with it (be less accurate if more far from the object or not, for instance).

Also you may want to add a cut off, for instance saying that if Dz is too high, then you stop spotting the mesh.


The above approach is simple but can be enhanced using a second empty at the front of the camera. This empty will be set the same way as the previous one (parented to the camera and constrained rotations).

We now have 3 objects (purple arrow, empty at the back of the camera, empty at the front) so that we can check their alignment (in X and Y).

This additional setting will allow to avoid Suzan to be spotted if we see the scene from another point of view than the camera. If you don't need to render from outside of this camera this is nearly useless though.

  • $\begingroup$ Yay! The snappiest solution yet. Of course.. object-spaces can tell you everything you need to know, and many variations possible. $\endgroup$
    – Robin Betts
    Commented Aug 7, 2019 at 17:27
  • $\begingroup$ @RobinBetts, well snappiest no! Took me days to find it ; ) $\endgroup$
    – lemon
    Commented Aug 7, 2019 at 17:31

A method using drivers .. a bit tedious, maybe it can be improved?

Create a driver function that can extract the orientation of an object into Custom Properties, and add it to the driver namespace, by running this script:

import bpy

def z_dir(self,axis):      
    return (self.matrix_world [axis][2])

bpy.app.driver_namespace["z_dir"] = z_dir

Create Custom Properties on the object to be shaded, which will contain the world X, Y, and Z components of the object's local positive Z axis, (set the 'axis' parameter to 0,1 and 2 for the X,Y and Z components):

enter image description here

Take care to edit the Custom Properties, and ensure their values are permitted to range from -1 to 1.

In the Object's material, add a 'Combine XYZ' node, whose values are driven by the Custom Properties of the object:

enter image description here

Add two more 'Combine XYZ' nodes to the node tree, whose values will be driven by the locations of the object and camera:

enter image description here

Now you're kitted up to create a tree like this:

enter image description here

Before the color ramp, the tree returns a value between 0 and 1, dependent on the angle between the (Object to Camera vector), and the (Object's own local Z vector). It's 1 when the camera is looking straight down the object's Z, 0 when the camera is looking straight up it:

enter image description here

You could use the 0-1 value to as the factor in a mix between materials.

  • $\begingroup$ The main problem with this is that it's hard to transfer the material between objects. Perhaps a bit more use of self in drivers would help.. exposing the object's location through more of its own driven custom properties? All suggestions welcome. $\endgroup$
    – Robin Betts
    Commented Aug 6, 2019 at 9:47
  • $\begingroup$ As I'm new to drivers and I need a little time to analyze your answer. Great job non the less. In the mean time I see that the material is apparently changing on the whole object while the Shady Puck solution shows an edge if you use a ColorRamp to make a thin B&W limit (see blend file). In other words: Your solution seems a bit more precise but more technical as mentioned in comment #1. So it seems you got it for Step One too. Now the funny part : the purple vector. $\endgroup$
    – 4E71-NOP
    Commented Aug 6, 2019 at 10:13
  • $\begingroup$ @4E71-NOP Unless I've misunderstood you, the purple vector is already accounted for? the 0-1 value is determined by the angle between the view and the object's local Z.. whether the camera moves, or the object rotates. it could be tweaked to use another local reference vector, if that's what you need. I'm sorry it's such a fiddly business. $\endgroup$
    – Robin Betts
    Commented Aug 6, 2019 at 10:18
  • $\begingroup$ Cool. Nodes aint my thing. Simple things like switches. Could you use a rotational difference driver (suzanne and camera) variable here 1 if var else 0 or some tolerance, to switch the material? $\endgroup$
    – batFINGER
    Commented Aug 6, 2019 at 11:14
  • 1
    $\begingroup$ Yes the purple is accounted for. I wasn't sure by just reading the solution. Vector nodes aren't my cup of tea. After testing it; your solution is accurate. A little tricky for beginners to implement but it's ok. Now we have @batFINGER who delivered a piece of code that i must study (I like code). Where would be my gentleman manners otherwise ? I'll put some time in it. Great job! I was thinking it was a question nobody would care about. A now I have 3 experts on the line up. Niiiiiice. $\endgroup$
    – 4E71-NOP
    Commented Aug 7, 2019 at 14:39

Make a propertygroup property to drive from

Making a property to calculate the rotation between a custom local vector on object A, and one on object B.

enter image description here

Using a property getter on a propertygroup property, to feed a driver variable.

For want of a better name set up an Xxxx propertygroup that will be an object property. The group will contain an object pointer target to the object to match rotation. the vector up is the local direction to match to. With subtype DIRECTION get the novel layout

In the image above, up is displayed via single axis empties.

  • Two cones named Cube and Cone
  • Cone is made the target for Cube, their normals match so Cube turns blue.
  • With Cube made the target for Cone, the driver will update. Will update on animation without, and other property changes, just not rotating or changing 'up' of target

Property Group Code.

import bpy
from bpy.props import PointerProperty, FloatVectorProperty, FloatProperty
from math import pi

class Xxxx(bpy.types.PropertyGroup):
    def rotdif(self):
        if self.target:
            up = self.up
            ob = self.id_data
            mw = ob.matrix_world.to_3x3()
            target = self.target
            tmw = target.matrix_world.to_3x3()

            return (mw @ up).angle(
                    tmw @ target.xxxx.up) 

        return 2 * pi

    target : PointerProperty(type=bpy.types.Object)
    up : FloatVectorProperty(
            default=(0, 0, 1),
    angle : FloatProperty(

    def draw(self, layout):

        layout.prop(self, "target")
        layout.prop(self, "up")
        layout.label(text=f"{tuple(round(v, 3) for v in self.up)}")
        for i in range(3):
            layout.prop(self, "up", index=i)
        layout.prop(self, "angle")

def draw(self, context):
    ob = context.object

bpy.types.Object.xxxx = PointerProperty(type=Xxxx)

Once you run that code, go to the 3D cursor panel, and change settings.

By pointing the other object back, it refreshes the display when rotating either object or target.

With the simplest of material setups red and blue, and a value node being driven by

enter image description here

a driver variable named angle a single property of the cube object with datapath xxxx.angle

The expression

1 if angle > radians(5) else 0

drives a switch that is 1 when the "up" vectors match within 5 (arbitrary) degrees

  • $\begingroup$ Wow. An education, as usual ;) $\endgroup$
    – Robin Betts
    Commented Aug 6, 2019 at 20:31
  • $\begingroup$ @batFINGER I updated the OP in Edit ‘Going further with batFINGER’ $\endgroup$
    – 4E71-NOP
    Commented Aug 14, 2019 at 11:32

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .