Pure Shader Solution!
Hey look, high-school maths does have a purpose......
From what I could understand with a little research, holographic/reticle sights display a holograph that appears (when looking through the sights glass) to be fixed to a spatial position a certain distance (
d) from the glass. Basically, we need to work out the x,y coordinates of the point
d distance down the camera ray for each point we render on our sight. We can then use that to map our texture.
This would be a piece of cake if we had matrices in cycles nodes (in particular, the transform matrices of the sight object and the camera, as well as the projection matrix). We don't, so lets improvise.
The camera data node (which is where you'd expect to find the view and projection matrices) is a good starting place. While we don't see any juicy matrices, we do get one vector.
The View Vector
- Location: Camera Data Node
- Age... No idea when it was added
- Height: ....Dude, this is a node
- Weakness: Its normalized
The view vector provides a vector representing the ray that was shot out to hit that point of our mesh. This direction is in camera space. Therefore Z is always forwards, x is always left/right, and y is always up down. Each vector is normalized, so they always have a length of one.
Lets visualize this a bit. We'll do this in 2D because
I can't draw this in 3D it will be easier for you to understand.
Looking at our camera top down in camera space, z is forwards (now up relative to our view), and x is left to right. This is drawn with the dark colored axis. If we look at our ray (in yellow) which has a length of 1, you will see that it can be broken up into an x and a z component (light colored axis). This is the ratio of x to z movement needed to travel down the ray.
I am available to illustrate geometry textbooks.
Do you remember when I said
Basically, we need to work out the x,y coordinates of the point
d distance down the camera ray for each point we render on our sight.
If we multiply the View Vector by
d, we will get the x,y coordinates of a point
d distance from the camera in that direction. Progress!
However, our sight is now fixed to the camera. We can improve this, using the vector transform node.
- Strength: Secret sauce that makes everything work
- Weakness: Doesn't seem to understand the idea of location
Obviously, our hologram should not always be
d distance infront of the camera. It should be
d distance in front of our sight. To do this, we will need to go from camera space to object space. The vector transform does exactly this:
To explain exactly how this space conversion works, we will need to understand object space. Object space is where the origin of our space is the origin of the object, and all the axis (plural) line up with the objects local axis (plural). Pretty simple. Our sight just needs to be
d distance down the
z axis is object space.
So how does this space conversion work?
If you will direct your eyes to my next graphic (again drawn looking top down), you will see that a plane as been added (our sight), and the camera has been rotated. In light colors are the axis of the object space, and in dark the axis of the camera space.
If we ignore position for a moment, and overlay the two spaces, we can see how the axis don't align:
If we line the axis up as such:
We can see that the ray, which is in camera space, is pointing the wrong way. If we apply the inverse of the camera's rotation to the data, the ray will go back to pointing the correct direction.
This is what the vector transform does.
However, this space transformation does not take the relative positions of the camera and the object into account. Therefore, if we move the camera, our sight slips:
To fix this, we need to take position into account.
Object Texture Coordinate
- Location: Texture Coordinate node
- Weakness: Weird positions for object origins
The object texture coordinate give the object space location of the ray/mesh intersection. Since our rays are traveling at the right angle, all they need is to start at the right place. If we take the object space coordinate, and travel
d distance along the ray direct (in object space), we will get our desired x,y coordinates. This can be achieve simply be Adding the two vectors together (using the vector math node).
Now it works:
We take the view vector, transform it to object space, multiply it by the distance
d (scalar multiplication). We then add it to the object texture coordinate.
Since we sample textures in 0,1 space, and our xy coordinates range between --25 and + 25, we then divide by
d, and add .5 to the xy coordinates, Clamp to avoid the texture repeating, then sample it. The whole setup looks like this:
I created my own vector clamp and scalar multiply node groups. Those can be obtained in the provided blend file.
Denormalizing the View Vector
The view vector travels out from the camera, which is a point in space. We adjust the position, transforming it to the origin of the object. Because the vector is normalize, our "projection" plane is actually a sphere
d distance from the origin of the object.
In the examples, we view a very small slice of the sphere, so the curvature is not very noticeable
However, if we were to place a camera with a very wide FOV very close to the glass, we would see a much greater amount of curvature:
Which would produce very visible distortion (clamping is turned off because the repeating pattern makes to distortion more visible):
To fix this we need project the point represented by the normalize view vector (Yellow) onto a abstract plane (Teal) that is one unit away from the camera along the z (depth) axis.
The target point is in magenta. RGB lines represent components of the view vector. Also, 2.8 grease pencil improvements are awesome.
This diagram is a little confusing, a top down view provides some more clarity:
If we scale the vector's components by the ratio of the distance to the plane and the z components
1.0/z (big purple bracket divided by little purple bracket) We can extend the vector to the plane.
Implemented in nodes:
And now the warping is gone:
The center target is still roughly the same size. They appear to have shrunk since the spherical projection was moving the outer targets closer.
This is a pretty robust shader. It handles object rotation, scaling moving, and zooming (focal length changes) in stride. The scope texture will always be centered around the object origin, and be visible when looking down the z axis of the object. Because the output of view vector is normalized, the output x,y coordinates used to map the texture will be projected along a sphere
d distance from the objects origin, rather than a plane. This shouldn't be too noticeable. Pity they don't let the user decide whether it should be normalized.