我可以在像素着色器中生成随机数吗?

时间:2011-03-01 01:25:12

标签: random hlsl directx-9 pixel-shader

我正在尝试编写一个非常简单的着色器,它将随机闪光添加到适用的对象中。我想这样做的方法是在像素着色器中为像素值添加一个白色的随机阴影(R = G = B)。

似乎noise()不能按照我希望的方式运作:

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

它给出了“错误X4532:无法将表达式映射到像素着色器指令集”,指的是对noise()的调用。

由于我不知道在着色器调用之间保留数字的方法,我认为我不能只根据渲染前传入的种子编写一个简单的随机数生成函数。

有没有办法从像素着色器中生成随机数?如果有办法,怎么办?

4 个答案:

答案 0 :(得分:24)

2017年7月更新:我已将“伪随机性”更加稳定

// 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 );
}

这是版本:

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);}

传递纹理以产生噪声(通常)过度设计。有时它很方便,但对于大多数情况来说,计算随机数更简单,更快捷。

由于着色器变量对于每个片段是独立的,因此它们无法重用它们之间的现有变量。然后问题变成如何使用“好的”随机数种子。不合理的数字似乎适合开始的法案。那么选择一个好的“置换”功能只是一个“简单”的问题。

这是一些可以解决这个问题的免费代码:

// 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) ) ) );  
}

如果我们将公式分解为其组成部分,要理解这是如何工作的,可以更容易地看到正在发生的事情:

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.);
}

将以上内容粘贴到:

我还创建了一个“比较”ShaderToy示例,其中包含2个噪声函数和2个随机函数:

使用噪音的演示“[2TC 15]斑点交叉渐变”

“经典”随机函数(有时称为snoise3)是bad one

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

如果你想比较“伪随机”功能,请查看Dave的Hash without sine着色器。

答案 1 :(得分:8)

当你想在像素着色器中使用随机值时,通常要做的是传递包含噪声的纹理。虽然它实际上并非“随机” - 但 看起来 是随机的。

例如,这里有一些来自我所在的像素着色器的代码:

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

我使用的纹理是RGB噪点纹理,有时可以派上用场。但是相同的技术适用于灰度级技术。

通过缩放它,我确保噪声纹理中的像素排列到屏幕上的像素(您可能还希望将纹理采样器设置为“点”模式,这样您就不会模糊噪声纹理)。

通过使用偏移,您可以滚动纹理 - 这有点像播种随机数生成器。如果您想避免“滚动”外观,请使用随机偏移。

答案 2 :(得分:6)

没有任何内容表明你必须重新使用种子来运行随机生成器,你只需要任何种子。

如果你使用像素坐标,那么你将得到一个确定性结果(即像素x,y将始终具有相同的随机光斑),但总体而言,它将随机分布。

现在,如果您可以根据环境改变输入的内容(我对像素着色器一无所知),就像场景/相机组合的全局空间中像素的整体位置而不是相对然后,对于多边形本身,特别是在快速移动的环境中,您的结果将有效地随机。

如果全局环境中的所有内容恰好相同,那么,是的,您将拥有完全相同的“随机”分布。但是这些因素中的任何一个都会发生变化(特别是基于用户输入,这可能是你最具动态性的“噪音源”),那么整体效果可能会“足够随机”。

因此,关键是你的种子不必是以前运行的随机数生成器的结果。它可以是任何东西。因此,根据着色器对您自己的RNG的输入为每个像素设计种子可能会为您提供所需的效果。

答案 3 :(得分:0)

如何获得整个框架的校验和并将其用作种子。 这样,如果框架上的任何东西都在移动,则您尝试随机化的选择将随框架移动。