Skip to main content
Amended for relative filename of image file
Source Link
Rich Sedman
  • 45.2k
  • 2
  • 109
  • 233

Set the Script node to the script and click the refresh button to ensure it's compiled. Note that for OSL you'll need to use a version of Blender compiled with OSL support and have enabled Open Shader Language in the Render properties. I've driven the input coordinates in the Combine XYZ nodes from the locations of empties within the scene to allow them to be easily manipulated. Note also that the FileName node must be set toindicates the absolute path to your source image unless prefixed with ‘//‘ to indicate a path relative to your saved .blend (ie,note that it cannot be a packed file and cannot be referenced in a relative directoryor ‘internal’ image).

Set the Script node to the script and click the refresh button to ensure it's compiled. Note that for OSL you'll need to use a version of Blender compiled with OSL support and have enabled Open Shader Language in the Render properties. I've driven the input coordinates in the Combine XYZ nodes from the locations of empties within the scene to allow them to be easily manipulated. Note also that the FileName node must be set to the absolute path to your source image (ie, it cannot be a packed file and cannot be referenced in a relative directory).

Set the Script node to the script and click the refresh button to ensure it's compiled. Note that for OSL you'll need to use a version of Blender compiled with OSL support and have enabled Open Shader Language in the Render properties. I've driven the input coordinates in the Combine XYZ nodes from the locations of empties within the scene to allow them to be easily manipulated. Note also that the FileName indicates the absolute path to your source image unless prefixed with ‘//‘ to indicate a path relative to your saved .blend (note that it cannot be a packed or ‘internal’ image).

added 67 characters in body
Source Link
Rich Sedman
  • 45.2k
  • 2
  • 109
  • 233

animated

Obviously the more rays that are traced from the 'screen' out to the 'reflector', the more chance of hitting one which will make it back to the projection source - but also the more work will be involved in the render. Two factors control this - the 'resolution' and the 'threshold'. The 'resolution' forms a grid of points sent out from each point on the screen to the reflector - ie, resolution x resolution rays (doubling the resolution will result in 4 times as many rays). The 'threshold' is a measure of how close to the projection source the reflected ray is before it is considered as a 'hit', so smaller values for threshold will reduce the chance of any one ray from 'hitting' the projector but will give a higher quality result.

Obviously the more rays that are traced from the 'screen' out to the 'reflector', the more chance of hitting one which will make it back to the projection source - but also the more work will be involved in the render. Two factors control this - the 'resolution' and the 'threshold'. The 'resolution' forms a grid of points sent out from each point on the screen to the reflector - ie, resolution x resolution rays (doubling the resolution will result in 4 times as many rays). The 'threshold' is a measure of how close to the projection source the reflected ray is before it is considered as a 'hit', so smaller values for threshold will reduce the chance of any one ray from 'hitting' the projector but will give a higher quality result.

animated

Obviously the more rays that are traced from the 'screen' out to the 'reflector', the more chance of hitting one which will make it back to the projection source - but also the more work will be involved in the render. Two factors control this - the 'resolution' and the 'threshold'. The 'resolution' forms a grid of points sent out from each point on the screen to the reflector - ie, resolution x resolution rays (doubling the resolution will result in 4 times as many rays). The 'threshold' is a measure of how close to the projection source the reflected ray is before it is considered as a 'hit', so smaller values for threshold will reduce the chance of any one ray from 'hitting' the projector but will give a higher quality result.

added 810 characters in body
Source Link
Rich Sedman
  • 45.2k
  • 2
  • 109
  • 233

Cycles is capable of this by using an OSL shader. The trick is to use the 'trace' function to trace multiple rays from the surface receiving the projection to the reflective surface and calculating the reflected ray using the surface normal at that point. Most of the rays will not find their way back to the projection source but some will. The shader sums the results of all those rays that reflect back to the projector source (or, at least, close enough).

shader volume_meshproximityreflect_texture(
    vector Point = P,
    vector ReflectorTarget = P,
    vector ProjectorOrigin = P,
    string FileName = "",
    int Resolution = 10,
    float Threshold = 0.999,
    float ImageScale = 1.0,
    output color Color = color(0.5,0.5,0.5)
    ) {
    
    color Accumulation = color(0,0,0);
    int AccumulationCount = 0;
    float Distance = 0.0;
    vector Normal = vector(0,0,0);
    float Hit = 0.0;

    vector Target_to_Projector = ProjectorOrigin - ReflectorTarget;
    vector imageHorizVector = normalize(cross(Target_to_Projector, vector(0,0,1)));
    vector imageVertVector = normalize(cross(imageHorizVector, Target_to_Projector));
    
    float Step = 2.0 / Resolution;
    vector randomvect = noise("cell", Point*5000);
    float randoffsetx = randomvect[0]*Step;
    float randoffsetz = randomvect[2]*Step;
    
    
    for (float x = -1.0; x <= 1.0 ; x+= Step)
    {
        for (float z = -1.0; z <= 1.0 ; z+= Step)
        {
            vector ReflectorPoint = vector(ReflectorTarget[0]+x+randoffsetx, ReflectorTarget[1], ReflectorTarget[2]+z+randoffsetz);
            vector point_to_reflector = ReflectorPoint - Point;
            
            // trace the ray to find the normal at the point it hits
            if(trace(Point,normalize(point_to_reflector)))
            {
                getmessage("trace", "hitdist", Distance);
                getmessage("trace", "N", Normal);
                //getmessage("trace", "hit", Hit);
            }
            else
            {
                continue;
            }
 
            // reflect it
            vector reflected_ray = reflect(point_to_reflector, Normal);
            
            vector reflector_to_projectororigin = ProjectorOrigin - ReflectorPoint;
            
            vector separationVect = (normalize(reflector_to_projectororigin) - normalize(reflected_ray));
            float separationDist = sqrt(dot(separationVect, separationVect));
            
            if (separationDist < (Threshold*3)) 
            {
                //...it's a hit...
                float x_offset = (dot(imageHorizVector, normalize(reflected_ray)) / ImageScale ) + 0.5;
                float y_offset = (dot(imageVertVector, normalize(reflected_ray)) / ImageScale ) + 0.5;
                if ((x_offset >=0.0 ) && (x_offset <= 1.0) && (y_offset >= 0.0) && (y_offset <= 1.0))
                {
                    Accumulation += texture(FileName, x_offset, y_offset)/pow(2,separationDist/Threshold);
                    AccumulationCount++;
                }
            }
        }
    }
    Color = Accumulation/(Threshold*Threshold)/Resolution/Resolution/100;
}

Blend file attached

EDIT : Here's another rendered result to show the effect of reducing the Threshold for a sharper result. Here I decreased the Threshold to 0.001 and increased the Resolution to 200. I also added a Subsurface modifier to the reflector mesh (set to a factor of 3) and reduced the size of the ripples on the reflector so that the distortion was not so pronounced.

result - high quality

This is now sharp enough to make out the text at the bottom of the image (although it's obviously distorted by the reflection and is mirrored left to right). This obviously took much longer to render. Quality could be further improved by increasing the number of render samples and/or decreasing the Threashold parameter further (but requiring longer render times)

Cycles is capable of this using an OSL shader. The trick is to use the 'trace' function to trace multiple rays from the surface receiving the projection to the reflective surface and calculating the reflected ray using the surface normal at that point. Most of the rays will not find their way back to the projection source but some will. The shader sums the results of all those rays that reflect back to the projector source (or, at least, close enough).

shader volume_meshproximity(
    vector Point = P,
    vector ReflectorTarget = P,
    vector ProjectorOrigin = P,
    string FileName = "",
    int Resolution = 10,
    float Threshold = 0.999,
    float ImageScale = 1.0,
    output color Color = color(0.5,0.5,0.5)
    ) {
    
    color Accumulation = color(0,0,0);
    int AccumulationCount = 0;
    float Distance = 0.0;
    vector Normal = vector(0,0,0);
    float Hit = 0.0;

    vector Target_to_Projector = ProjectorOrigin - ReflectorTarget;
    vector imageHorizVector = normalize(cross(Target_to_Projector, vector(0,0,1)));
    vector imageVertVector = normalize(cross(imageHorizVector, Target_to_Projector));
    
    float Step = 2.0 / Resolution;
    vector randomvect = noise("cell", Point*5000);
    float randoffsetx = randomvect[0]*Step;
    float randoffsetz = randomvect[2]*Step;
    
    
    for (float x = -1.0; x <= 1.0 ; x+= Step)
    {
        for (float z = -1.0; z <= 1.0 ; z+= Step)
        {
            vector ReflectorPoint = vector(ReflectorTarget[0]+x+randoffsetx, ReflectorTarget[1], ReflectorTarget[2]+z+randoffsetz);
            vector point_to_reflector = ReflectorPoint - Point;
            
            // trace the ray to find the normal at the point it hits
            if(trace(Point,normalize(point_to_reflector)))
            {
                getmessage("trace", "hitdist", Distance);
                getmessage("trace", "N", Normal);
                //getmessage("trace", "hit", Hit);
            }
            else
            {
                continue;
            }
 
            // reflect it
            vector reflected_ray = reflect(point_to_reflector, Normal);
            
            vector reflector_to_projectororigin = ProjectorOrigin - ReflectorPoint;
            
            vector separationVect = (normalize(reflector_to_projectororigin) - normalize(reflected_ray));
            float separationDist = sqrt(dot(separationVect, separationVect));
            
            if (separationDist < (Threshold*3)) 
            {
                //...it's a hit...
                float x_offset = (dot(imageHorizVector, normalize(reflected_ray)) / ImageScale ) + 0.5;
                float y_offset = (dot(imageVertVector, normalize(reflected_ray)) / ImageScale ) + 0.5;
                if ((x_offset >=0.0 ) && (x_offset <= 1.0) && (y_offset >= 0.0) && (y_offset <= 1.0))
                {
                    Accumulation += texture(FileName, x_offset, y_offset)/pow(2,separationDist/Threshold);
                    AccumulationCount++;
                }
            }
        }
    }
    Color = Accumulation/(Threshold*Threshold)/Resolution/Resolution/100;
}

Blend file attached

Cycles is capable of this by using an OSL shader. The trick is to use the 'trace' function to trace multiple rays from the surface receiving the projection to the reflective surface and calculating the reflected ray using the surface normal at that point. Most of the rays will not find their way back to the projection source but some will. The shader sums the results of all those rays that reflect back to the projector source (or, at least, close enough).

shader reflect_texture(
    vector Point = P,
    vector ReflectorTarget = P,
    vector ProjectorOrigin = P,
    string FileName = "",
    int Resolution = 10,
    float Threshold = 0.999,
    float ImageScale = 1.0,
    output color Color = color(0.5,0.5,0.5)
    ) {
    
    color Accumulation = color(0,0,0);
    int AccumulationCount = 0;
    float Distance = 0.0;
    vector Normal = vector(0,0,0);
    float Hit = 0.0;

    vector Target_to_Projector = ProjectorOrigin - ReflectorTarget;
    vector imageHorizVector = normalize(cross(Target_to_Projector, vector(0,0,1)));
    vector imageVertVector = normalize(cross(imageHorizVector, Target_to_Projector));
    
    float Step = 2.0 / Resolution;
    vector randomvect = noise("cell", Point*5000);
    float randoffsetx = randomvect[0]*Step;
    float randoffsetz = randomvect[2]*Step;
    
    
    for (float x = -1.0; x <= 1.0 ; x+= Step)
    {
        for (float z = -1.0; z <= 1.0 ; z+= Step)
        {
            vector ReflectorPoint = vector(ReflectorTarget[0]+x+randoffsetx, ReflectorTarget[1], ReflectorTarget[2]+z+randoffsetz);
            vector point_to_reflector = ReflectorPoint - Point;
            
            // trace the ray to find the normal at the point it hits
            if(trace(Point,normalize(point_to_reflector)))
            {
                getmessage("trace", "hitdist", Distance);
                getmessage("trace", "N", Normal);
                //getmessage("trace", "hit", Hit);
            }
            else
            {
                continue;
            }
 
            // reflect it
            vector reflected_ray = reflect(point_to_reflector, Normal);
            
            vector reflector_to_projectororigin = ProjectorOrigin - ReflectorPoint;
            
            vector separationVect = (normalize(reflector_to_projectororigin) - normalize(reflected_ray));
            float separationDist = sqrt(dot(separationVect, separationVect));
            
            if (separationDist < (Threshold*3)) 
            {
                //...it's a hit...
                float x_offset = (dot(imageHorizVector, normalize(reflected_ray)) / ImageScale ) + 0.5;
                float y_offset = (dot(imageVertVector, normalize(reflected_ray)) / ImageScale ) + 0.5;
                if ((x_offset >=0.0 ) && (x_offset <= 1.0) && (y_offset >= 0.0) && (y_offset <= 1.0))
                {
                    Accumulation += texture(FileName, x_offset, y_offset)/pow(2,separationDist/Threshold);
                    AccumulationCount++;
                }
            }
        }
    }
    Color = Accumulation/(Threshold*Threshold)/Resolution/Resolution/100;
}

Blend file attached

EDIT : Here's another rendered result to show the effect of reducing the Threshold for a sharper result. Here I decreased the Threshold to 0.001 and increased the Resolution to 200. I also added a Subsurface modifier to the reflector mesh (set to a factor of 3) and reduced the size of the ripples on the reflector so that the distortion was not so pronounced.

result - high quality

This is now sharp enough to make out the text at the bottom of the image (although it's obviously distorted by the reflection and is mirrored left to right). This obviously took much longer to render. Quality could be further improved by increasing the number of render samples and/or decreasing the Threashold parameter further (but requiring longer render times)

Source Link
Rich Sedman
  • 45.2k
  • 2
  • 109
  • 233
Loading