Skip to content

A guide to converting from Shadertoy to Code Node in Lens Studio 5.2.1.

License

Notifications You must be signed in to change notification settings

p-sun/Shadertoy-to-LensStudio-Conversion-Guide

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

31 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Shadertoy to Lens Studio Code Node 5.0 Conversion Guide

Convert Shadertoy shaders to Lens Studio!

First, read the official Shadertoy to Lens Studio Guide and watch Michael Porter's video.

This repository includes all the shaders from Micheal Porter's video, recreated in Lens Studio 5.0. All code in this guide is is in this repo.

Paste shader code from Shadertoy into the above link, and paste it into a Code Node in Lens Studio. I have forked and added upon Hart Woolery's original conversion tool to handle more cases.

Shadertoy Conversions

Basic Setup - iResolution / fragCoord / uv

In this Shadertoy of a blue circle at half the height of the screen, our main function looks like this.

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = fragCoord/iResolution.xy;
    fragColor = vec4(uv, 0, 1); // Draw red-green gradient

    // Blue circle of radius 1, at 0,0.
    if (abs(length(uv) - 1.0) < 0.04) { 
     fragColor = vec4(0, 0, 1, 1);
    }
}
Screenshot 2024-10-29 at 11 38 01 AM

To set that up for Lens Studio, see "1.1 - Circle - Using resolution" in this project.

  1. Make a new Screen Image. In the Inspector Panel for the Screen Image, set Stretch Mode to Stretch.
    • Optionally, for performance and if your shader don't need screen resolution and is a square, set Stretch Mode to Fill and skip setup from step 4 and on.
  2. Select the Screen Image > Select Material > Select the Shader Graph. Setup the Shader Graph like so.
  1. Convert the Shadertoy code into Code Node code using Shadertoy to Code Node (Improved!). In the Shader Graph in Lens Studio, paste that code into the Code Node.
// The shader output color
output_vec4 fragColor;
// The shader resolution
input_vec2 resolution;
vec3 getResolution() { if (resolution.x == 0.) return vec3(640,640,1); return vec3(resolution,1);}

#define iResolution getResolution()

void main()
{
    vec2 fragCoord = system.getSurfaceUVCoord0() * iResolution.xy;   
    // Nomalized to [0, 1] on the x-axis. 
    vec2 uv = (2.*fragCoord - iResolution.xy)/iResolution.x;

    // Red-green gradient
    fragColor = vec4(uv, 0, 1);
    
    // Blue circle of radius 1, at 0,0.
    if (abs(length(uv) - 1.0) < 0.04) { 
     fragColor = vec4(0, 0, 1, 1);
    }
}
  1. In the Inspector Panel for the Material, drag Device Camera Texture from the Assets Panel as the Resolution Texture. Set Filtering Mode to Nearest.

iResolution Tip - Get the resolution without sampling texture

In Lens Studio, textureSize() only works when the texture sample contributes to the output. If we don't need to sample the texture, we can add the following in the main() function of our Code Node.

This allows us to simplify the Shader Graph from step 2 above this.

// Texture used to get the resolution
input_texture_2d resolutionTexture;

void main() {
    //...
    // Add this to only sample one pixel on the first frame.
    if (system.getTimeElapsed() == 0.0 && system.getSurfaceUVCoord0().y == 1.0 && resolutionTexture.textureSize().x == 0.0) {
        fragColor = mix(resolutionTexture.sample(vec2(0)), fragColor, 0.9999999);
    }
}

See "1.3 - Circle - Using resolution (fewest nodes)" example in this project.

texture, textureLod, textureFetch, iChannel

Pixel Coordinates

Pixel coordinates, fragCoord,, uv, and resolution work exactly the same in Shadertoy and Lens Studio.

Ranges are the same in Shadertoy and LS.

Variable Shadertoy Lens Studio Range
vec2 pixelCoord vec2(floor(uv * iResolution.xy)) vec2(floor(uv * iResolution.xy)) [0, resolution - 1]
vec2 uv fragCoord/iResolution.xy or (pixelCoords + 0.5) / iResolution.xy system.getSurfaceUVCoord0() or (pixelCoords + 0.5) / iResolution.xy [0, 1]
vec2 fragCoord fragCoord uv * iResolution.xy [0.5, iResolution.x - 0.5]
vec3 iResolution iResolution input_texture_2d inputTexture; vec3(inputTexture.textureSize(), 1.0) Constant whole number depending on screen size

Examples for the x-axis for resolution of 4 and 3. The value below the arrow ^ is the position on the horizonal number line.

resolution = 4                     |     resolution = 3
             _________________     |                  _____________
pixelCoord   | 0 | 1 | 2 | 3 |     |     pixelCoord   | 0 | 1 | 2 |
             ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾     |                  ‾‾‾‾‾‾‾‾‾‾‾‾‾
             ^   ^   ^   ^   ^     |                  ^   ^   ^   ^
position     0   1   2   3   4     |     position     0   1   2   3
                                   |
               ^   ^   ^   ^       |                    ^   ^   ^  
fragCoord     0.5 1.5 2.5 3.5      |     fragCoord     0.5 1.5 2.5 
                                   |
               ^   ^   ^   ^       |                    ^   ^   ^  
uv            1/8 3/8 5/8 7/8      |     uv            1/6 3/6 5/6

Here are two useful functions to convert between uv and pixel coordinates.

vec2 uvToPixelCoords(vec2 uv) {
	return vec2(floor(uv * iResolution.xy));
}

vec2 pixelCoordsToUV(vec2 pixelCoords) {
	return (pixelCoords + 0.5) / iResolution.xy;
}

Read and Write to Pixel Coordinates

In Shadertoy, we use iChannel0/iChannel1/iChannel2/iChannel3 to insert 2D textures, such as images, videos, or 2D buffers. We use sample or sampleLod as above to read the color at a position. In Shadertoy, we can pass data from one frame to the next by encoding data as a color on a specific pixel, and reading that pixel on the next frame.

For the full code, see Shadertoy Example and "3.4 - Read and write to pixel" for the Lens Studio equivalent.

Write to pixel in Shadertoy or Lens Studio:

// Step1: Draw an aqua pixel at (30, 30)
vec2 pixelCoord = uvToPixelCoords(uv);	 
if (pixelCoord.x == POS.x && pixelCoord.y == POS.y) {
    fragColor = vec4(0.1, 0.6, 0.4, 0.3);
}

To read from a pixel in Shadertoy we use texture, textureLod or textureFetch.

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uvToRead = pixelCoordsToUV(vec2(30.0, 30.0));

    // Read the pixel at (30, 30). These are equivalent.
    vec4 pixelColor = texture(iChannel0, uvToRead);
    vec4 pixelColor = textureLod(iChannel0, uvToRead, 0.0);
    vec4 pixelColor = texelFetch(iChannel0, ivec2(30,30), 0);

To read from a pixel in Lens Studio we use sample or sampleLod.

input_texture_2d iChannel0;

void main() {
    vec2 uvToRead = pixelCoordsToUV(vec2(30.0, 30.0));

    // Read the pixel at (30, 30). These are equivalent.
    vec4 pixelColor = iChannel0.sample(uvToRead);
    vec4 pixelColor = iChannel0.sampleLod(uvToRead, 0.0);

(Optional) Read and Write to Pixel Coordinates - to and from the same iChannel

In Shadertoy we may want to write to one buffer, and then use that same buffer in the next frame.

In this project, see my conversion of this shadertoy in "3.4 - Read and write to pixel" for the most basic example. We know the Shadertoy is writing to and from the same channel, because in the code for "Buffer A"/iChannel0, we see that we're reading from iChannel0.

vec4 pixelColor = texture(iChannel0, uvToRead);

To set this up in Lens Studio:

  1. Create a new Render Target, and name it Feedback Render Target, and set it to get its texture from the Render Target your shader is outputting to.

    Screenshot 2024-10-29 at 2 44 27 PM

  2. Create a new empty Orthographic Camera, set it to a different layer (e.g. "feedback a"). Set it to output to Feedback Render Target.

    Screenshot 2024-10-29 at 2 37 16 PM

  3. In the Code Node, we declare iChannel0.

input_texture_2d iChannel0;
  1. In the Shader Graph, we add a Texture 2D Object Parameter node, and set it as the input of iChannel0 in the Code Node, and set the Title to Feedback Buffer.

    image

  2. In the Material for the shader, we set Feedback Buffer to Feedback Render Target.

    image

Important Differences when Reading/Writing to Pixels

  • Important buffer size difference:
    1. Shadertoy buffers has four channels of 32-bit floats, for a total of 128 bits per pixel.
    2. Lens Studio has only four channels of 8-bit floats between 0 and 1, for a total of 32 bits per pixel. i.e. In Lens Studio, each 8 bit channel only take on 256 different values, from 0.0/255.0, 1.0/255.0, 2.0/255.0 ... 255.0/255.0.
  • If we need to precisely save all 32 bits of a float, in Lens Studio we can pack that into 4 pixels. Set min and max for values specific to your shader.
    // Pack a 32-bit signed float into a Lens Studio rgba color.
    vec4 system.pack32Bit( float value, float min, float max )
    
    // Unpack a Lens Studio rbga color into a 32-bit signed float.
    float system.unpack32Bit( vec4 value, float min, float max )	
  • In order to save data on the alpha channel in the output color in Lens Studio, in the Asset Browser, click the Render Target that the shader outputs to, then in the Inspector Panel,
    • Set the Clear Color Option to None. If you don't do this, the alpha channel read with sample or sampleLod will always be 1.0.
    • Set Filtering Mode to Nearest so sampling will get the original color of a pixel instead of averaging colors from neighbouring pixels.

iFrame

(Option A) Faster. If you don't need the specific Frame.

In Lens Studio, add the following. This guarentees that iFrame is a value that increases with each new frame, and that pixels in the same frame get the same iFrame value.

#define iFrame (int(system.getTimeElapsed()*32.))

(Option B) Slower. If you DO need the specific frame.

  1. In the code node of the shader, add a new parameter.
input_int frameCount;

#define iFrame frameCount
  1. In the Shader Graph, add a Int Parameter node, set the Script Name to frameCount, and hook it up to the code node.

  2. In an object at the top of the scene hierachy, add the following Script Component. In the Script Component, set the material.

// @input Asset.Material material

var frameCount = 0;

function onUpdate(eventData) {
    script.material.mainPass.frameCount = frameCount;
    frameCount++;
}

script.createEvent("UpdateEvent").bind(onUpdate);

Debugging Tip - Comparing Values

We can compare whether some variable float testValue is roughly the same in Lens Studio and Shadertoy by mapping that float to a color with fragColor = floatToColor(testValue);. This is a useful strategy to see where our Lens Studio shader differs from the original.

vec3 hsv2rgb(vec3 c) {
    vec4 K = vec4(1.0, 2.0/3.0, 1.0/3.0, 3.0);
    vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
    return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}

vec4 floatToColor(float value) {
    // Normalize the value to the range [0, 1]
    float hue = log2(value + 1.0) / log2(1001.0);
    vec3 rgb = hsv2rgb(vec3(hue, 1.0, 1.0));
    return vec4(rgb, 1.0);
}

Included shaders in this repository

Galaxy.mp4
CRT.mp4
Ascii.Camera.mp4

Unincluded shaders due to license

Using these strategies, we can even convert Iq's infamous Rainforest Shader (top) to run in Lens Studio (bottom).

_rainforest.compressed.mov

About

A guide to converting from Shadertoy to Code Node in Lens Studio 5.2.1.

Resources

License

Stars

Watchers

Forks