Hello you all :), so this weekend I was in a cabin with some friends, and one talked to me about Blink using Nuke. It was like 7 am in the morning, after having somewhat a party night… Anyways, I googled and saw that it was a very powerfull tool, so I made my hands dirty and wrote a classic ray-marcher using Nuke.

Note: Before having this discussion with my friends, I never used Nuke, and this is not a Nuke Tutorial, but rather some words on using Blink from a graphics programmer mindset.

What we will do:

To be honest, I suck at compositing and VFX… But I love to explore those tools! Nuke is a powerfull tool and I am far from understanding how it actually works… I am not into VFX. That being said, I was curious about the Blink Node, in short: It is very powerfull. In this article, we will write a volumetric ray-marcher node that will output this kind of image.

Nuke is not meant to be used that way once would ask, but not really giving a shit I would answer! It’s all about exploring Nuke’s very own Blink rather than making a specific usecase.

Let’s dive into Blink

To create a new Blink node in wich we can write code, can be apply like this:

I just send a checkerboard image to the Blink node…

The Kernel Source is where we will write our code for this node! So let’s write a basic source code to output Red Image. As you can see, it is quite simple to make the dst() to a float4.

kernel Custom_Color : ImageComputationKernel<ePixelWise>
{
  Image<eWrite> dst;                                                      //output image
  
  param:
    float4 color;                                                         //parameter
  
  void define() {

  }
  
  void process() {
    dst() = float4(1.0f, 0.0f, 0.0f, 1.0f);                                                        //output
  }
};

If you are new to code, it’s ok to not really care about all the code-poutine! 🙂 The only line you want to get is this one:

dst() = float4(1.0f, 0.0f, 0.0f, 1.0f);

As you can see, the Blink node outputs a texture, in our case we simply made this to red.

You can changer the RGBA value of this BlinkNode such as…

For dst() -> you pass the color of the pixel. In our case it is red! But the power of Blink comes with per-pixel operations: More on that later!

So, in shorts, this node runs for every pixel… You can feed this node image or simply starts from scratch as I did in this tutorial. A bit like a fragment shader, there are very clever techniques and algorithms to generate nice stuff.

So let’s assume that we want to build some 3D images on a basic Blink node… We do not have some sexy vertices to raster, but simply a programs that will run for every pixels using the Blink SIMD(Single instruction, multiple data) architecture-ish.

So yeah, let’s write 3D using ray-marching! This way, we can have sphere and shit to shade onto a single texture, from a single node!

Note: Normally you would have all of these from your footages, but I am not a footage guy… So I will simply use Blink to write a ray-marcher in Nuke, because: Why the fuck not?

Ray marching in Blink!

I know it is not the proper way to use this node… But let’s use this node to generate a 3D scene, create normals, make a basic sun lighting and repeating objects.

kernel UVmap : ImageComputationKernel<ePixelWise>
{
  Image<eRead,eAccessRandom, eEdgeClamped> src;    //I
  Image<eWrite> dst;                               //O  
  local:
    float width;                                   
    float height;                                  
  
  void init() {
    width = src.bounds.x2;                         //function to find right edge
    height = src.bounds.y2;                        //function to find top edge
  }
  float sphere(float3 pos, float r)
  {
    return length(pos) - r;
  }
  float map(float3 pos)
  {
  
    float3 q = pos;
    q.x = fmod(q.x + 4.0f, 8.0f) - 4.0f;
 q.z = fmod(q.z + 4.0f, 8.0f) - 4.0f;
 q.y = fmod(q.y + 4.0f, 8.0f) - 4.0f;


     float d = sphere(q, 1.5f);
 
      return d;
  }
  
  float3 computeNormal(float3 pos)
  { 
    return normalize(float3(
        map(pos + float3(0.1f, 0.0f, 0.0f)) - map(pos - float3(0.1f, 0.0f, 0.0f)),
        map(pos + float3(0.0f, 0.1f, 0.0f)) - map(pos - float3(0.0f, 0.1f, 0.0f)),
        map(pos + float3(0.0f, 0.0f, 0.1f)) - map(pos - float3(0.0f, 0.0f, 0.1f))
    ));
  }
  void process(int2 pos) {

    float2 fg = float2(pos.x, pos.y);
    float2 ires = float2(width, height);
    // (fragCoord -.5 * iResolution.xy) / iResolution.y;
    float2 uv = (fg - .5f * ires)/ires.y;    
    
   float3 rd = normalize(float3(uv,1.0));
    float3 pos2 = float3(25.0f, 5.0f, -15.0f);
   float3 color = float3(1.0f,0.0f, 0.0f);
   
  color = float3(uv.x, uv.y, uv.x * uv.y);


    for(int i=0; i < 512; i++)
    {
      float d = map(pos2);
      if(d < 0.01)
      {
        
         color = float3(1.0f); 
         float3 nws = computeNormal(pos2);
         float3 sunPos = float3(-2.f, 2.f, -4.f);
         float sunFactor = dot(normalize(nws), normalize(sunPos));
         
         float3 basicColor = float3(.2f, .3f, .88f);
         color = basicColor * sunFactor * normalize(pos2);
        
      }
       pos2 += d * rd;    
    }
//rendering with a fog calculation (further is darker)
    
    dst(0) = color.x;                        
    dst(1) = color.y;                          
    dst(2) = color.z;
    dst(3) = 1.0f; 
  }
};

Leave a Reply

Your email address will not be published. Required fields are marked *