-->

Can I generate a random number inside a pixel shad

2019-01-11 05:06发布

问题:

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?

回答1:

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.



回答2:

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.



回答3:

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.



回答4:

what if you can get a checksum of the whole frame and use it as a seed. that way if anything on the frame is moving the selection you are trying to randomize will be shifting with the frame.