LAUNCH OFFER€25 in free credits when you sign up, no card requiredClaim the offer
    TURNA, Blender render farm
    Back to the Blender guides

    Open Shading Language (OSL): what it is and how to write OSL shaders in Blender

    T
    TURNA technical team
    29 October 2026
    13 min

    Short answer

    Open Shading Language (OSL) is a C-like shading language created by Sony Pictures Imageworks and supported by renderers such as Arnold, V-Ray, RenderMan, Redshift and Cycles. In Blender it only works in Cycles, on the CPU or on an NVIDIA card with OptiX: tick Open Shading Language under Device in Render Properties, then add a Script node that reads your code. EEVEE, CUDA, HIP, oneAPI and Metal do not run it.

    • Enable it: Render Properties, Render Engine: Cycles, Device: CPU (or GPU Compute with OptiX), Open Shading Language checkbox.
    • Script node: Shader Editor, Shift+A > Utilities > Script, Mode: Internal or External.
    • Write the code: Text Editor (Shift+F11), Templates > Open Shading Language.
    • Compile: the node's Update button, the refresh icon.
    • Read errors: Window > Toggle System Console on Windows, a terminal on Linux and macOS.

    Menu names below are checked against the Blender 5.2 manual and source code. The example shader was compiled and rendered in Blender 5.2 LTS before publishing.

    What is Open Shading Language (OSL)?

    Open Shading Language, OSL for short, is a small programming language for shading: working out what surfaces look like inside a renderer. The project describes it as a language for materials, lights, displacement and pattern generation. If you have written C, the syntax will look familiar: typed variables, functions, curly braces and semicolons.

    Sony Pictures Imageworks originally built OSL for the studio's in-house renderer, used on its animated features and visual effects work, then released it as open source so other studios and software vendors could adopt it. The code is under the 3-clause BSD license, and the project now lives under the Academy Software Foundation. The work on OSL received an Academy Award for Technical Achievement in 2017.

    The project's own list of renderers with OSL support includes Arnold, V-Ray, RenderMan, Redshift, 3Delight, Clarisse, appleseed and Blender's Cycles, plus 3ds Max 2019. On the film side it lists The Amazing Spider-Man and Finding Dory, among many others. The Blender manual points out the practical upside: an OSL shader can be shared between applications that support the standard.

    How OSL differs from GLSL and other shading languages

    In most shading languages, a surface shader computes a final color as seen from one direction. The Blender manual notes that OSL, unlike GLSL or the RenderMan Shading Language (RSL), has no light loop: a surface shader cannot see the lights in the scene and does not trace lighting rays. Instead it returns a closure, a description of how the surface scatters light, and the renderer evaluates it, picks the directions worth sampling and does the lighting. The manual calls this more limited, but it is also what lets the renderer optimize shaders and importance sample all of them.

    Source code lives in a .osl file. The oslc compiler turns it into intermediate code, a .oso file, and according to the project, the OSL library then compiles that to machine code on the fly with LLVM at render time, using the actual parameter values. In Blender all of this is automatic: you write the .osl, Blender produces the .oso.

    Where OSL works in Blender

    In Blender, OSL lets you write custom surface, volume and displacement shaders for Cycles. The manual mentions procedural textures, custom BRDFs and research prototypes. You write the code in Blender's Text Editor or in an external file, and it enters the material through a Script node whose inputs and outputs are the parameters declared in the code.

    Rendering withOSLNotes
    Cycles on the CPUYesSupports every feature, trace() included
    Cycles with OptiX (NVIDIA)Yes, since Blender 3.5Constant texture paths only, no Cell, Simplex or Gabor noise, no trace()
    Cycles with CUDA, HIP, oneAPI or MetalNoThe checkbox disappears and Script nodes are ignored
    EEVEENoThe Script node is Cycles only

    In the Blender 5.2 source code, only the CPU and OptiX devices declare OSL support. The guide to OptiX vs CUDA in Blender shows how to select OptiX in Preferences and which NVIDIA cards qualify.

    Since Blender 4.5, OSL can also drive cameras: in the camera's properties, Lens panel, set Type to Custom. Custom cameras do not depend on the Open Shading Language checkbox, but they also need the CPU or OptiX.

    How to enable Open Shading Language in Cycles

    1. In Render Properties, set Render Engine to Cycles.
    2. Set Device to CPU. To render on an NVIDIA card, pick GPU Compute instead, after selecting the OptiX tab in Edit > Preferences > System > Cycles Render Devices.
    3. Tick Open Shading Language, right below Device. It is a scene setting, saved in the .blend file.

    If the checkbox is not there, look at Device: Blender only shows it when rendering on the CPU or with OptiX. With CUDA, HIP, oneAPI or Metal set as GPU Compute, it vanishes from the panel. A file saved with the box ticked and rendered on one of those devices falls back to Cycles' built-in node system, and its Script nodes are ignored. The manual adds that rendering with OSL is typically slower than with built-in nodes, so only enable it in scenes that need it.

    The Script node: internal text or external file

    In the Shader Editor, Shift+A > Utilities > Script adds the node (Blender 5.0 and later; in 4.x it is Add > Script > Script). The menu only offers it when the render engine is Cycles, and the node is only evaluated when Open Shading Language is ticked. Its Mode field gives you two ways to supply the code.

    • Internal: the code is a text data-block inside the .blend file, written in the Text Editor, and the compiled .oso bytecode is stored in the node itself. Everything travels with the file, which makes this the mode to use when you hand a scene to someone else.
    • External: the node points to a .osl file on disk, which Blender compiles to a .oso in the same folder; a precompiled .oso is used as is. You can also give a bare module name, looked up in the shaders folder of Blender's configuration directory, on Windows C:\Users\<name>\AppData\Roaming\Blender Foundation\Blender\5.2\shaders\. The Auto Update option, in the Shader Editor sidebar, recompiles the shader whenever the .osl file changes.

    The refresh icon next to the script name is Script Node Update: it recompiles the code and adds or removes sockets to match the parameters. In Internal mode, renders use the bytecode stored in the node, so an edit to the text only takes effect after you click it. For production work, the manual suggests wrapping Script nodes in a node group and linking that group into other files, so there is only one place to update when sockets change.

    Writing the code in the Text Editor

    Open the Text Editor with Shift+F11 or from the Scripting workspace. Its Templates > Open Shading Language menu holds nine templates in Blender 5.2: six shaders, including empty_shader (a blank shader), basic_shader (float and color inputs and outputs), noise (the noise functions) and ramp_closure (closures), plus three cameras. Give the text a name ending in .osl: the editor header then swaps the Run Script button for a Shader Update button that refreshes every Script node using that text.

    Your first OSL shader, line by line

    This shader draws two-color stripes. It has two outputs: a color you can plug in anywhere, such as the Base Color of a Principled BSDF, and a complete diffuse shader. Blender's built-in procedural textures (Noise, Voronoi, Wave) are covered in the guide to Blender textures; OSL takes over when none of them fits.

    // stripes.osl: procedural stripes for the Base Color.
    shader stripes(
        color Color_A = color(0.8, 0.1, 0.1),  // first color
        color Color_B = color(0.9, 0.9, 0.9),  // second color
        float Scale = 10.0,                    // periods per unit
        point Position = P,                    // unlinked: P
        output color Color = 0.0,              // color output
        output closure color BSDF = 0)         // shader output
    {
        // x times the scale, wrapped into the 0 to 1 range.
        float t = mod(Position[0] * Scale, 1.0);
        // step() is 0 for the first half of each period, 1 after.
        Color = mix(Color_A, Color_B, step(0.5, t));
        BSDF = Color * diffuse(N);
    }
    1. Open the Text Editor, click New, rename the text stripes.osl and paste the code.
    2. In the material's Shader Editor, add a Script node, leave Mode on Internal and pick stripes.osl.
    3. Click the Update button. The Color_A, Color_B, Scale and Position inputs appear, along with the Color and BSDF outputs.
    4. Connect Color to the Base Color of a Principled BSDF, or BSDF straight into the Surface input of the Material Output. The guide to materials in Blender covers the Principled BSDF.
    5. Switch the viewport to Rendered shading, or press F12 to render (see how to render in Blender).

    What each line does

    • shader stripes(...) declares the shader and its parameters. The OSL specification requires a default value for every parameter. Blender creates one node input per parameter, and one output for each parameter marked output.
    • color, float and point are OSL types; closure color is the type of a surface shader. Comments work as in C++, after // or between /* and */.
    • point Position = P: P is the position of the point being shaded, a global variable OSL provides without any declaration. Because this default is not a constant, an unlinked Position input takes the value of P. In Cycles, P is in world space, so the stripes stay put when the object moves. Connect the Object output of a Texture Coordinate node to Position to make them follow it.
    • mod(a, b) returns a - b * floor(a / b), which is never negative, unlike fmod(), so the stripes stay regular on both sides of the origin. Position[0] reads the x component (Position.x works too).
    • step(0.5, t) is 0 when t is below 0.5 and 1 otherwise; mix(a, b, f) returns a * (1 - f) + b * f. Together they pick one color or the other.
    • diffuse(N) is the Lambertian diffuse closure, with N the surface normal. Multiplied by a color, it gives a diffuse material of that color.

    Metadata, in double brackets after the default value, controls how a parameter shows up on the node: [[ string label = "Stripe scale" ]] gives it a readable name, [[ string widget = "null" ]] hides it, and [[ string widget = "checkBox" ]] (or "boolean") displays an integer parameter as a checkbox.

    Closures available in Cycles

    Cycles closures map to its shader nodes. They include diffuse(), oren_nayar(), translucent(), reflection(), refraction(), transparent(), emission(), holdout() and background(), absorption() and henyey_greenstein() for volumes, and bssrdf() for subsurface scattering. Since Blender 4.0, glossy reflections go through the standard OSL microfacet() closure, with the ggx, multi_ggx, beckmann, sharp, ashikhmin_shirley and clearcoat distributions. For example, microfacet("ggx", N, 0.2, 1.5, 0) gives a GGX reflection with an alpha of 0.2 and an IOR of 1.5, with no refraction (1 means refraction only, 2 means both). The MaterialX closures dielectric_bsdf(), conductor_bsdf() and generalized_schlick_bsdf() are available too.

    Geometry attributes are read with getattribute(): UV maps, color attributes, attributes output by Geometry Nodes, and built-ins such as geom:uv, object:index or object:random, a random number that differs for every object instance. Two lines are enough to vary a tint across duplicated objects: float r = 0.0; followed by getattribute("object:random", r);.

    OSL limitations and render time cost

    • Speed. The manual warns that rendering is typically slower than with built-in nodes, because of shader compilation and interpretation. Keep OSL for what nodes cannot do.
    • Baking and volumes. The manual also says some features, such as baking and certain volume shaders, may not work with OSL.
    • No access to lights. An OSL shader cannot replace lighting. The trace() function does cast rays from the shader, but on the CPU only, and to probe nearby geometry: a projected texture that geometry can block, extra wear on exposed areas, ambient occlusion-like effects.
    • OptiX limitations. Every texture call must resolve to a constant image path; some noise functions are missing, including Cell, Simplex and Gabor; and trace() does not work, so the Ambient Occlusion and Bevel nodes do not work either while OSL is enabled. The guide to ambient occlusion covers other ways to get that darkening.
    • Textures in Blender 5.2. Textures read by OSL now go through the Cycles texture cache instead of OpenImageIO. The release notes promise better performance and GPU support, but some advanced options of texture() and gettextureinfo() are gone.

    For a heavy shader, the safest approach is to measure: render the same frame with the script and with an equivalent node setup, and keep the faster one. The other settings that weigh on render time are collected in the guide to rendering faster in Cycles.

    Common OSL errors

    The material renders black

    The Script node is not being evaluated. There are three likely causes: Open Shading Language is unchecked, the render runs on CUDA, HIP, oneAPI or Metal, or compilation failed, which leaves the node with no sockets at all. Plugged straight into Surface, an ignored node leaves the material black; plugged into a color input, that input falls back to its own value.

    The material renders pink

    A texture() call that points to a missing file returns magenta, which shows up as pink in the render, and the console prints "Image file … does not exist". Check the image path in your code.

    "OSL script compilation failed, see console for errors"

    This appears when you click Update and the code does not compile. The details are in the system console: on Windows, Window > Toggle System Console; on Linux and macOS, start Blender from a terminal. Each error comes with a line number, as in file.osl:4: error: Syntax error: syntax error. For an internal script the file name belongs to a temporary file, so only the line number matters. A missing semicolon is reported on the next line, where the compiler trips over it.

    "function 'microfacet_ggx' was not declared in this scope"

    The code comes from a tutorial written before Blender 4.0, which removed Cycles-specific closures such as microfacet_ggx() and microfacet_beckmann(). Replace them with microfacet("ggx", ...) or microfacet("beckmann", ...). The OSL page of the Blender 5.2 manual still lists the old closures, but the compiler rejects them.

    The node's sockets do not update

    In Internal mode, Blender does not reread the code on its own: click Update on the node, or Shader Update in the Text Editor if the text has a .osl extension. Clicking Update with no text selected shows "No text or file specified in node, nothing to compile". In External mode, the file must end in .osl or .oso, and Blender must be able to write the .oso next to it.

    Frequently asked questions

    What is Open Shading Language (OSL)?

    Open Shading Language is a C-like shading language created by Sony Pictures Imageworks and released as open source. It describes materials, lights, displacement and procedural patterns as closures that the renderer evaluates, rather than as final colors. Arnold, V-Ray, RenderMan, Redshift and Blender's Cycles all support it.

    How do I enable OSL in Blender?

    In Render Properties, set Render Engine to Cycles, set Device to CPU (or to GPU Compute with OptiX selected in Preferences), and tick Open Shading Language right below Device. Then add a Script node in the Shader Editor (Add > Utilities > Script), pick your text or .osl file, and click the node's Update button.

    Does Blender OSL work on the GPU?

    Only with OptiX on NVIDIA cards, since Blender 3.5, and with limitations: texture paths must be constant, the Cell, Simplex and Gabor noises are missing, and trace() does not work, so neither do the Ambient Occlusion and Bevel nodes. With CUDA, HIP, oneAPI or Metal the checkbox disappears and Script nodes are ignored.

    Does OSL work in EEVEE?

    No. The Script node is Cycles only: the Add menu only offers it when Render Engine is set to Cycles, and EEVEE does not run it. To get a similar look in EEVEE, you have to rebuild the effect with the built-in shader nodes.

    Why is my OSL shader black or pink?

    Black means the Script node is not being evaluated: Open Shading Language is unchecked, the render device does not support OSL, or the shader failed to compile. Pink means a texture() call points to an image that cannot be found. Either way, the system console has the details.

    Rendering the animation on TURNA

    When a scene is already well optimised and the frame count is what takes the time, the remaining lever is rendering several frames at once. TURNA does this for Blender: an add-on (Blender 4.0 to 5.2, Cycles and Eevee) sends the scene in one click, and the frames are split across 1, 2, 4 or 8 GPUs in parallel, RTX 5090 first and RTX 4090 when none is free. The add-on shows the estimated cost before anything starts. Prices start at €4.90 per hour, billed on the time actually used, with a 2-minute minimum (30 seconds until November 9), and new accounts get €25 of free credit. A single still image is still rendered by one machine, so the gain is on animations. Tiers and worked examples are on the Blender render farm page.