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

Can I generate a random number inside a pixel shader?

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

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

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

它为我提供了"错误X4532:无法将表达式映射到像素着色器指令集",这是对noise()的调用。

由于我不知道在着色器调用之间保留数字的方法,因此我认为我无法基于渲染前传递的种子编写简单的随机数产生函数。

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


2017年7月更新:我使"伪随机性"更加稳定

1
2
3
4
5
6
7
8
9
// Version 3
float random( vec2 p )
{
    vec2 K1 = vec2(
        23.14069263277926, // e^pi (Gelfond's constant)
         2.665144142690225 // 2^sqrt(2) (Gelfonda€"Schneider constant)
    );
    return fract( cos( dot(p,K1) ) * 12345.6789 );
}

这些是版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
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);}

传递纹理以产生噪声(通常)是过度设计的。有时它很方便,但在大多数情况下,仅计算一个随机数会更简单,更快。

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 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) ) ) );  
}

要了解如果将公式分解成几个组成部分,这是如何工作的,则可以更直观地看到正在发生的事情:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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.);
}

将以上内容粘贴到:

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

我还创建了一个具有两个噪声函数和两个随机函数的"比较" ShaderToy示例:

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

使用噪声" [2TC 15]散斑淡入淡出"的演示

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

一个"经典"随机函数(有时称为snoise3)是一个不好的函数:

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

如果要比较"伪随机"函数,请查看没有正弦着色器的Dave哈希。

  • 有趣。尽管我不同意纹理方法是过度设计的。您将纹理获取交易为数量可观的指令。而且您仍然必须每帧传递随机偏移。 id表示这两种方法均有效。
  • @AndrewRussell使用现代GPU,(通常)执行计算(与CPU绑定)然后进行纹理查找(与IO绑定)要快得多。如果您需要每个像素一个"随机"数字,则只需传递纹理坐标就可以了。我通常在括号中加上这个词,因为了解您的平台的唯一方法是同时计时这两个时间。 ;-)
  • @Michaelangelo这是如何工作的?同样,更多的视觉图案开始以更高的分辨率出现。
  • 最好将其分解,看看每个步骤如何缓慢地移至伪随机输出。即将下一篇文章复制粘贴到[link] www.shadertoy.com/new [/ link]
  • @STRAIGHTOUTTACOMPTON,您可能需要使用123456789号。根据您的需要尝试减少数字位数或增加数字位数。即1234567890。
  • @Michaelangelo这种方法很棒。非常感谢你!底层数学有解释吗?如何选择vec2 r?为什么使用cos()生成良好的随机值?
  • @WeiXiang这个技巧早在我之前的" demoscene"中就已经知道了---据我所知,没有人记录过该技巧。要点是,随机数生成器只是一个函数,其输出很难猜测。大多数(全部?)线性同余生成器都是确定性的。选择cos()是因为cos(0)== 1,但是您可以使用除零以外的任何"相位偏移",例如使用您喜欢的无理数。
  • 非常感谢你!


当您想要像素着色器中的随机值时,通常要做的是传递包含噪点的纹理。虽然它实际上不是"随机"的,但它看起来是随机的。

例如,这是我周围的像素着色器的一些代码:

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

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

通过缩放,可以确保噪声纹理中的像素与屏幕上的像素对齐(您可能还希望将纹理采样器设置为"点"模式,以免使噪声纹理模糊)。

通过使用偏移量,您可以滚动纹理-就像播种随机数生成器一样。如果要避免出现"滚动"外观,请使用随机偏移。


没有什么可以说您必须在每次运行之间将种子重用于随机生成器,而您只需要任何种子。

如果使用像素坐标,那么最终将获得确定性的结果(即像素x,y始终具有相同的随机光斑),但是总体上将随机分布整个面部。

现在,如果您可以输入一些根据环境而变化的东西(我对像素着色器一无所知),例如像素在场景/相机组合的全局空间中的整体位置,而不是相对于多边形的整体位置那么,尤其是在快速变化的环境中,您的结果将有效地随机出现。

如果全局环境中的所有事物都完全相同,那么,是的,您将具有完全相同的"随机"分布。但是,无论这些因素中的任何一个发生变化(特别是基于用户输入,这很可能是您最动态的"噪声源"),那么总的效果可能就是"足够随机"。

因此,关键在于,您的种子不必是先前随机数生成器运行的结果。可以是任何东西。因此,根据着色器对您自己的RNG的输入为每个像素构造一个种子,可能会给您带来所需的效果。

  • 虽然这两个答案都很有帮助,但这个答案更适合我的需求。谢谢!


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