Can I generate a random number inside a pixel shader?
Asked Answered
K

4

12

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?

Kaikaia answered 1/3, 2011 at 1:25 Comment(0)
R
6

There's nothing that says you have to reuse the seed for a random generator from run to run, you just need any seed.

If you use, say, the pixel coordinate, then you will end up with a deterministic result (i.e. pixel x, y will always have the same random flare to it), but overall the entire face it will be randomly distributed.

Now, if you have available as input something that changes based on the environment (I don't really know anything about pixel shaders), like the overall placement of the pixel in the global space of the scene/camera combination than relative to the polygon itself, then, especially in a fast moving environment, your results will effectively random.

If everything in the global environment happens to be exactly the same, then, yes, you will have the exact same "random" distribution. But it any of those factors change (notably based on user input, which it likely you most dynamic "noise source") then overall the effect will be, likely, "random enough".

So, the key take away is that your seed does not have to be the result of a previous run of the random number generator. It can be anything. So, contriving a seed for each pixel based on inputs to the shader to your own RNG may give you the effect you need.

Rogation answered 1/3, 2011 at 1:38 Comment(1)
While both answers are helpful, this one better fits what I was looking for. Thanks!Kaikaia
D
30

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:

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

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

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.

Dingus answered 16/5, 2012 at 19:52 Comment(7)
Interesting. Although I disagree that the texture method is over-engineering. You're trading a texture fetch for a not-insignificant number of instructions. And you still have to pass in a random offset each frame. I'd say both methods are valid.Welldisposed
@AndrewRussell With modern GPUs it is is (usually) far faster to do a calculation (CPU bound) then texture lookup (IO bound). If you need a "random" number per pixel it is trivial to just pass in the texture coordinates. I put the word usually in parentheses because the only way to know for your platform is to time both. ;-)Dingus
@Michaelangelo How exactly does this work? Also much more visual patterns start to appear at higher resolutions.Countryfied
It is best to break it down and see how each step slowly moves to pseudo-random output. i.e. Copy paste the next post into [link]www.shadertoy.com/new[/link]Dingus
@STRAIGHTOUTTACOMPTON you might need to play around with the '123456789.' number. Try either less digits, or more digits depending on your needs. i.e. '1234567890.'Dingus
@Michaelangelo This method is amazing. Thank you very much! Is there explanation for the underneath mathematics? How the vec2 r is chosen? And why it uses cos() for generating good random values?Meras
@WeiXiang The trick has been known in the "demoscene" long before me --- I don't believe anyone has ever documented it though as far as I know. The gist is that a random number generator is just a function where it's output is hard to guess. Most (all?) linear congruential generator are deterministic. Cos() was chosen since cos(0) == 1, but you could use any "phase offset" other then zero, say using your favorite irrational number.Dingus
W
9

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.

Welldisposed answered 1/3, 2011 at 3:34 Comment(0)
R
6

There's nothing that says you have to reuse the seed for a random generator from run to run, you just need any seed.

If you use, say, the pixel coordinate, then you will end up with a deterministic result (i.e. pixel x, y will always have the same random flare to it), but overall the entire face it will be randomly distributed.

Now, if you have available as input something that changes based on the environment (I don't really know anything about pixel shaders), like the overall placement of the pixel in the global space of the scene/camera combination than relative to the polygon itself, then, especially in a fast moving environment, your results will effectively random.

If everything in the global environment happens to be exactly the same, then, yes, you will have the exact same "random" distribution. But it any of those factors change (notably based on user input, which it likely you most dynamic "noise source") then overall the effect will be, likely, "random enough".

So, the key take away is that your seed does not have to be the result of a previous run of the random number generator. It can be anything. So, contriving a seed for each pixel based on inputs to the shader to your own RNG may give you the effect you need.

Rogation answered 1/3, 2011 at 1:38 Comment(1)
While both answers are helpful, this one better fits what I was looking for. Thanks!Kaikaia
P
0

For anyone reading this years later, I personally found functions without trigonometry to remain most stable for longest periods of time, with no noticeable artifacts or patterns appearing. It does break at some point, but first artifacts start appearing at absurdly high values, at a point where solutions from Book of Shaders and the one suggested by Michaelangel007 are already outputting a single value for all inputs.

Check out Hash without Sine by Dave_Hoskins (https://www.shadertoy.com/view/4djSRW#). He lists multiple functions depending on the amount of inputs and outputs you need, so for 2D noise (2 inputs, 1 output) the code is as follows:

float hash12(vec2 p)
{
    vec3 p3  = fract(vec3(p.xyx) * .1031);
    p3 += dot(p3, p3.yzx + 33.33);
    return fract((p3.x + p3.y) * p3.z);
}

When converted to HLSL, which is what OP uses, it should look like this:

float hash12(float2 p)
{
    vec3 p3  = frac(float3(p.xyx) * .1031);
    p3 += dot(p3, p3.yzx + 33.33);
    return frac((p3.x + p3.y) * p3.z);
}
Preoccupation answered 28/2 at 8:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.