Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I generate a random number inside a pixel shader?

I'm trying to write a very simple shader that adds random sparkle to applicable objects. The way I'd like to do this is by adding a random shade of white (R = G = B) to the pixel value within the pixel shader.

It seems that noise() doesn't work the way I hope it does:

float multiplier = noise(float3(Input.Position[0], Input.Position[1], time));

It gives me "error X4532: cannot map expression to pixel shader instruction set" referring to the call to noise().

Since I don't know of a way to retain a number between calls to the shader, I don't think I can just write a simple random number producing function based on a seed passed in before rendering.

Is there a way to produce a random number from inside a pixel shader? If there is a way, how?

like image 672
chaosTechnician Avatar asked Mar 01 '11 01:03

chaosTechnician


2 Answers

UPDATE July 2017: I've made the "pseudo randomness" more stable

// Version 3
float random( vec2 p )
{
    vec2 K1 = vec2(
        23.14069263277926, // e^pi (Gelfond's constant)
         2.665144142690225 // 2^sqrt(2) (Gelfond–Schneider constant)
    );
    return fract( cos( dot(p,K1) ) * 12345.6789 );
}

This are the versions:

float random( vec2 p )
{
   // e^pi (Gelfond's constant)
   // 2^sqrt(2) (Gelfond–Schneider constant)
     vec2 K1 = vec2( 23.14069263277926, 2.665144142690225 );

   //return fract( cos( mod( 12345678., 256. * dot(p,K1) ) ) ); // ver1
   //return fract(cos(dot(p,K1)) * 123456.); // ver2
     return fract(cos(dot(p,K1)) * 12345.6789); // ver3
}

// Minified version 3:
float random(vec2 p){return fract(cos(dot(p,vec2(23.14069263277926,2.665144142690225)))*12345.6789);}

Passing in a texture to generate noise is (usually) over engineered. There are times where it is handy but for the majority of the cases it is simpler and faster to just calculate a random number.

Since shader variables are independent per fragment they are unable to re-use existing variables between them. The problem then becomes one of how to use a "good" random number seed. Irrational numbers seems to fit the bill to start with. Then it is just a 'simple' matter of picking a good "permute" function.

Here is some free code which does the trick:

// Input: It uses texture coords as the random number seed.
// Output: Random number: [0,1), that is between 0.0 and 0.999999... inclusive.
// Author: Michael Pohoreski
// Copyright: Copyleft 2012 :-)
// NOTE: This has been upgraded to version 3 !!
float random( vec2 p )
{
  // We need irrationals for pseudo randomness.
  // Most (all?) known transcendental numbers will (generally) work.
  const vec2 r = vec2(
    23.1406926327792690,  // e^pi (Gelfond's constant)
     2.6651441426902251); // 2^sqrt(2) (Gelfond–Schneider constant)
  return fract( cos( mod( 123456789., 1e-7 + 256. * dot(p,r) ) ) );  
}

To understand how this works if we break the formula down into its constituent parts it becomes easier to visualize what is going on:

const vec2 k = vec2(23.1406926327792690,2.6651441426902251);
float rnd0( vec2 uv ) {return dot(uv,k); }
float rnd1( vec2 uv ) { return 1e-7 + 256. + dot(uv,k); }
float rnd2( vec2 uv ) { return mod( 123456789., 256. * dot(uv,k) ); }
float rnd3( vec2 uv ) { return cos( mod( 123456789., 256. * dot(uv,k) ) ); }

// We can even tweak the formula
float rnd4( vec2 uv ) { return fract( cos( mod( 1234., 1024. * dot(uv,k) ) ) ); }
float rnd5( vec2 uv ) { return fract( cos( mod( 12345., 1024. * dot(uv,k) ) ) ); }
float rnd6( vec2 uv ) { return fract( cos( mod( 123456., 1024. * dot(uv,k) ) ) ); }
float rnd7( vec2 uv ) { return fract( cos( mod( 1234567., 1024. * dot(uv,k) ) ) ); }
float rnd8( vec2 uv ) { return fract( cos( mod( 12345678., 1024. * dot(uv,k) ) ) ); }
float rnd9( vec2 uv ) { return fract( cos( mod( 123456780., 1024. * dot(uv,k) ) ) ); }

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    mediump vec2 uv = fragCoord.xy / iResolution.xy;
    float i = rnd9(uv);
    fragColor = vec4(i,i,i,1.);
}

Paste the above into:

  • https://www.shadertoy.com/new

I've also created a "comparison" ShaderToy example with 2 noise functions, and 2 random functions:

  • https://www.shadertoy.com/view/XtX3D4

Demo of using noise "[2TC 15] Speckle Cross Fade"

  • https://www.shadertoy.com/view/4lfGW4

A "classic" random function, sometimes called snoise3 is this bad one:

return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453);

If you want to compare "pseudo random" functions check out Dave's Hash without sine shader.

like image 192
Michaelangel007 Avatar answered Sep 21 '22 15:09

Michaelangel007


What you generally do when you want a random value in a pixel shader is to pass in a texture containing noise. While it's not actually "random" - it looks random.

For example, here's some code from a pixel shader I have lying around:

float3 random = (tex2D(noiseTexture, texCoord * noiseScale + noiseOffset));

The texture I use is an RGB-noise texture, which can come in handy some times. But the same technique would work for a grayscale one.

By scaling it I ensure that the pixels in the noise texture line up to on-screen pixels (you may also want to set the texture sampler to "point" mode so you don't blur the noise texture).

By using an offset you can scroll the texture - which is kind of like seeding a random number generator. Use a random offset if you want to avoid that "scrolling" look.

like image 44
Andrew Russell Avatar answered Sep 23 '22 15:09

Andrew Russell