Rendering 3D Primitives in Nuke 16 with BlinkScript

What if you could render 3D primitives directly inside Nuke, with lighting, shadows, reflections, and even depth maps without using scanline render or ray render node ?

While many use it for image filters, color processing, or procedural textures, BlinkScript is powerful enough to handle raymarching algorithms.

By combining BlinkScript with SDF math, we can essentially create a lightweight renderer inside Nuke.

Signed Distance Fields (SDF) in NukeSigned Distance Fields are mathematical functions that describe how far a point in space is from the surface of a shape.

For example:A sphere is defined by the distance from its center minus its radius.A box is defined by checking distances against its half-extents.

More complex shapes like toruses and cylinders are also possible with compact formulas.The magic happens when you combine these formulas with a raymarching loop.

Each pixel on the image plane casts a ray into 3D space, and the script marches forward step by step until it “hits” the surface of a primitive.Features of the SDF Primitives RendererThis BlinkScript kernel is designed to be flexible and artist-friendly.

Here’s what you can control:🎲 Primitive SelectionSphereBox (with adjustable roundness for beveled edges)Torus (major + minor radii)Capped CylinderEach primitive can be positioned, scaled, and rotated using yaw, pitch, roll controls.

🌍 Ground PlaneYou can enable a ground plane to catch shadows and anchor your objects. The plane color and height are adjustable.

💡 Lighting & ShadingThe renderer includes a physically-inspired shading model:

Direct Lighting: Directional light with intensity and color controlsAmbient Light: Base fill to soften shadows

Specular Highlights: With adjustable roughness and specular gainShadows: Hard raytraced shadows with tweakable hardness

Ambient Occlusion: Adds depth and realismA subtle Fresnel rim lighting effect is also included, giving objects a cinematic glow at glancing angles.

🎨 Output ModesYou can choose between four output passes:

Beauty – Final shaded renderNormals – Useful for relighting inside NukeDepth – Normalized Z-depth for compositing tricks like fog or DOFMask – Simple white silhouette for mattes

// SDF_PrimitivesRenderer : sphere/box/torus/cylinder with lighting, AO, shadow, ground plane// Output modes: 0=Beauty, 1=Normals, 2=Depth, 3=Mask// Paste into a BlinkScript node. Set output RGBA.kernel SDF_PrimitivesRenderer : ImageComputationKernel<ePixelWise>{Image<eWrite, eAccessPoint> dst;param:// Render / camerafloat2 res; // (width,height) — set to your formatfloat fov; // vertical FOV in degreesfloat3 camPos;float3 camTarget;float3 camUp;// Primitive selectionint primType; // 0=Sphere, 1=Box, 2=Torus, 3=Cylinderfloat3 primSize; // xyz: radius/halfsize/major-minor where applicablefloat3 primPos; // object positionfloat3 rotYPR; // yaw,pitch,roll in degreesfloat roundness; // box edge roundness (for Box only)// Ground planeint usePlane; // 0/1float planeY; // plane height (Y)float3 planeColor;// Shadingfloat3 baseColor; // albedo for primitivefloat3 lightDir; // normalized-ishfloat3 lightCol;float3 ambientCol;float roughness; // smaller = sharperfloat specGain;float aoGain;float shadowHard;// Outputint outputMode; // 0 Beauty, 1 Normals, 2 Depth, 3 Maskfloat maxDepth; // for depth normalizationfloat time; // seconds (for anim if desired)void define(){defineParam(res, "Resolution", float2(1280.0f,720.0f));defineParam(fov, "FOV", 35.0f);defineParam(camPos, "CamPos", float3(0.0f, 0.4f, 3.6f));defineParam(camTarget, "CamTarget", float3(0.0f, 0.3f, 0.0f));defineParam(camUp, "CamUp", float3(0.0f, 1.0f, 0.0f));defineParam(primType, "Primitive", 0);defineParam(primSize, "PrimSize", float3(0.6f,0.6f,0.6f));defineParam(primPos, "PrimPos", float3(0.0f, 0.6f, 0.0f));defineParam(rotYPR, "Rot_YPR", float3(20.0f, 10.0f, 0.0f));defineParam(roundness, "BoxRound", 0.08f);defineParam(usePlane, "UsePlane", 1);defineParam(planeY, "PlaneY", 0.0f);defineParam(planeColor, "PlaneColor", float3(0.45f,0.46f,0.48f));defineParam(baseColor, "BaseColor", float3(0.85f,0.78f,0.65f));defineParam(lightDir, "LightDir", float3(0.6f,0.7f,0.4f));defineParam(lightCol, "LightColor", float3(1.0f,0.98f,0.95f));defineParam(ambientCol, "Ambient", float3(0.07f,0.08f,0.09f));defineParam(roughness, "Roughness", 0.30f);defineParam(specGain, "SpecGain", 0.7f);defineParam(aoGain, "AOGain", 0.55f);defineParam(shadowHard, "ShadowHard", 1.0f);defineParam(outputMode, "OutputMode", 0);defineParam(maxDepth, "MaxDepth", 6.0f);defineParam(time, "Time", 0.0f);}// ---------- math helpers ----------float3 normalizeSafe(float3 v){ float L=length(v); return (L>0.0f)? v/L : float3(0.0f); }float3 rotateY(float3 p,float a){ float c=cos(a),s=sin(a); return float3(c*p.x + s*p.z, p.y, -s*p.x + c*p.z); }float3 rotateX(float3 p,float a){ float c=cos(a),s=sin(a); return float3(p.x, c*p.y - s*p.z, s*p.y + c*p.z); }float3 rotateZ(float3 p,float a){ float c=cos(a),s=sin(a); return float3(c*p.x - s*p.y, s*p.x + c*p.y, p.z); }// SDF primitivesfloat sdSphere(float3 p, float r){ return length(p) - r; }float sdBox(float3 p, float3 b, float r){ // rounded boxfloat3 q = float3(fabs(p.x),fabs(p.y),fabs(p.z)) - b + float3(r,r,r);float3 m = float3(max(q.x,0.0f), max(q.y,0.0f), max(q.z,0.0f));float outside = length(m) - r;float inside = min(max(q.x, max(q.y,q.z)), 0.0f);return outside + inside;}float sdTorus(float3 p, float2 t){ // t.x=major, t.y=minorfloat2 q = float2(length(float2(p.x,p.z)) - t.x, p.y);return length(q) - t.y;}float sdCappedCylinder(float3 p, float h, float r){float2 d = float2(length(float2(p.x,p.z)) - r, fabs(p.y) - h);float outside = length(float2(max(d.x,0.0f), max(d.y,0.0f)));float inside = min(max(d.x,d.y),0.0f);return outside + inside;}// Scene SDFfloat mapScene(float3 p){// transform into object space for primitivefloat yaw=rotYPR.x*0.01745329252f, pitch=rotYPR.y*0.01745329252f, roll=rotYPR.z*0.01745329252f;float3 po = p - primPos;po = rotateY(rotateX(rotateZ(po, -roll), -pitch), -yaw);float d = 1e9f;if (primType==0){ // Sphered = sdSphere(po, primSize.x);} else if (primType==1){ // Box (rounded)d = sdBox(po, primSize, roundness);} else if (primType==2){ // Torusd = sdTorus(po, float2(primSize.x, primSize.y)); // x=major, y=minor} else { // Cylinder (capped)d = sdCappedCylinder(po, primSize.y, primSize.x); // x=radius, y=half-height}if (usePlane!=0){// infinite plane at y=planeYfloat dp = p.y - planeY;d = min(d, dp); // union with plane}return d;}float3 calcNormal(float3 p){float e=0.0015f;float3 ex=float3(e,0,0), ey=float3(0,e,0), ez=float3(0,0,e);float nx = mapScene(p+ex) - mapScene(p-ex);float ny = mapScene(p+ey) - mapScene(p-ey);float nz = mapScene(p+ez) - mapScene(p-ez);return normalizeSafe(float3(nx,ny,nz));}float ambientOcclusion(float3 p, float3 n){float occ=0.0f, sca=1.0f;for (int i=1;i<=5;i++){float hr = 0.03f*(float)i;float dd = mapScene(p + n*hr);occ += (hr - dd) * sca;sca *= 0.6f;}float ao = 1.0f - aoGain*occ;if(ao<0.0f) ao=0.0f; if(ao>1.0f) ao=1.0f;return ao;}float hardShadow(float3 ro, float3 rd){float t=0.02f;for (int i=0;i<72;i++){float3 pos = ro + rd*t;float h = mapScene(pos);if(h < 0.0012f) return 0.0f;t += max(0.01f, h*0.9f) * shadowHard;if(t > 8.0f) break;}return 1.0f;}void process(int2 ip){// camera rayfloat2 uv = float2((float)ip.x,(float)ip.y);float2 ndc = float2((uv.x/res.x - 0.5f)*(res.x/res.y), (0.5f - uv.y/res.y));float vfov = fov * 0.01745329252f;float3 fwd = normalizeSafe(camTarget - camPos);float3 right = normalizeSafe(cross(fwd, camUp));float3 upv = normalizeSafe(cross(right, fwd));float3 rd = normalizeSafe(fwd + right*ndc.x*tan(vfov*0.5f)*2.0f + upv*ndc.y*tan(vfov*0.5f)*2.0f);float3 ro = camPos;// raymarchfloat t=0.0f; bool hit=false; float d=0.0f;for (int i=0;i<160;i++){float3 pos = ro + rd*t;d = mapScene(pos);if (d < 0.0012f){ hit=true; break; }t += clamp(d, 0.002f, 0.08f);if (t > maxDepth) break;}float3 col=float3(0.0f);float alpha=0.0f;if (hit){float3 pos = ro + rd*t;float3 nrm = calcNormal(pos);// beauty / shadingfloat3 L = normalizeSafe(lightDir);float3 V = normalizeSafe(-rd);float ndotl = clamp(dot(nrm, L), 0.0f, 1.0f);float3 H = normalizeSafe(L + V);float ndoth = clamp(dot(nrm, H), 0.0f, 1.0f);float specExp = max(2.0f, 1.0f / max(0.02f, roughness));float spec = pow(ndoth, specExp) * specGain;float ao = ambientOcclusion(pos, nrm);float sh = hardShadow(pos + nrm*0.002f, L);// pick color by surface (primitive vs plane)float3 surfCol = baseColor;if (usePlane!=0){// if our hit is the plane, tweak color (simple test)// sample 1 step below point: if distance small -> on planefloat onPlane = (fabs((pos.y - planeY)) < 0.0025f) ? 1.0f : 0.0f;surfCol = (onPlane>0.5f) ? planeColor : baseColor;}float3 beauty = ambientCol*ao + surfCol*(ndotl*sh)*lightCol + spec*lightCol;if (outputMode==0){col = beauty;// Fresnel rim for freefloat fres = pow(1.0f - clamp(dot(nrm,V),0.0f,1.0f), 5.0f);col += surfCol * (0.05f * fres);} else if (outputMode==1){col = nrm * 0.5f + 0.5f; // normals 0..1} else if (outputMode==2){float z = t / maxDepth; if(z<0.0f) z=0.0f; if(z>1.0f) z=1.0f;col = float3(z,z,z);} else {col = float3(1.0f,1.0f,1.0f); // mask}alpha = 1.0f;}SampleType(dst) o(0.0f);o[0]=col.x; o[1]=col.y; o[2]=col.z; o[3]=alpha;dst() = o;}};

B

Tagged:
5 1 vote
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x