我能否在像素着色器中生成随机数?

11

我试图编写一个非常简单的着色器,为适用的对象添加随机闪烁效果。我希望通过在像素着色器中将白色的随机阴影(R = G = B)添加到像素值中来实现这一点。

看起来noise()并不像我希望的那样工作:

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

当我调用noise()时,它给我返回了“错误X4532:无法将表达式映射到像素着色器指令集”。

由于我不知道如何在着色器调用之间保留数字,因此我认为我不能仅基于渲染前传递的种子编写一个简单的生成随机数的函数。

是否有一种方法可以从像素着色器内部生成随机数?如果有,该怎么做?

4个回答

27

更新于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);}

将纹理传递到生成噪声中通常是过度设计(通常情况下)。虽然有时使用它会很方便,但在大多数情况下,只需计算随机数即可使代码更简单、更快。

由于着色器变量对每个片段都是独立的,因此它们无法在它们之间重复使用现有变量。那么问题就变成了如何使用“好”的随机数种子。无理数似乎是一个不错的选择。然后就只需要选择一个好的“permute”函数。

以下是一些自由代码,它可以解决这个问题:

// 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 示例,其中包括两个噪声函数和两个随机函数: 演示如何使用噪声的 "[2TC 15] Speckle Cross Fade": 一种"经典"的随机函数,有时被称为 snoise3, 可以参考这个 不好的函数:
return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453);

如果你想比较“伪随机”函数,可以查看戴夫的无正弦哈希着色器。


@AndrewRussell 现代GPU通常比纹理查找(IO bound)更快,因此进行计算(CPU bound)要快得多。如果您需要每个像素的“随机”数字,只需传递纹理坐标即可。我在括号中放置了单词“usually”,因为确定您的平台最好的方法是同时测试两种方式。;-) - Michaelangel007
@Michaelangelo,这个到底是怎么工作的?另外,在更高分辨率下会出现更多的视觉模式。 - Levi H
最好将其分解并查看每个步骤如何缓慢地移动到伪随机输出。即将下一篇帖子复制粘贴到[link]www.shadertoy.com/new[/link]。 - Michaelangel007
@STRAIGHTOUTTACOMPTON,你可能需要尝试一下'123456789.'这个数字。根据你的需求,可以尝试使用更少或更多的数字。例如:'1234567890.' - Michaelangel007
1
@WeiXiang 在我之前,“演示场景”早已知道这个技巧 --- 据我所知,没有人曾经记录过它。要点是随机数生成器只是一个函数,其输出很难猜测。大多数(全部?)线性同余生成器都是确定性的。选择cos()是因为cos(0) == 1,但你可以使用任何“相位偏移”,而不是零,比如使用你最喜欢的无理数。 - Michaelangel007
显示剩余2条评论

9

当你想在像素着色器中获得随机值时,通常的做法是传入一个包含噪声的纹理。虽然它并不是真正的“随机” - 它看起来是随机的。

例如,这是我拥有的一个像素着色器中的代码:

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

我使用的纹理是RGB噪声纹理,有时会很方便。但对于灰度纹理,同样的技术也适用。
通过缩放它,我确保噪声纹理中的像素与屏幕像素对齐(您可能还想将纹理采样器设置为“点”模式,以避免模糊噪声纹理)。
通过使用偏移量,您可以滚动纹理 - 这有点像播种随机数生成器。如果要避免“滚动”外观,请使用随机偏移量。

6

并没有规定你必须在每次运行时重复使用随机生成器的种子,你只需要任何一个种子即可。

如果你使用像素坐标作为种子,那么你将得到确定性的结果(即像素x、y始终具有相同的随机效果),但整个面部的分布将是随机的。

现在,如果你有一个基于环境变化的输入(我不太了解像素着色器),比如像素在场景/相机组合的全局空间中的整体位置而不是相对于多边形本身,则特别是在快速移动的环境下,你的结果将实际上是随机的。

如果全局环境中的所有内容恰好完全相同,那么,是的,你将得到完全相同的“随机”分布。但是如果其中任何一个因素发生了变化(尤其是基于用户输入的因素,这很可能是最动态的“噪声源”),那么总体效果很可能是“足够随机”的。

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


虽然两个答案都有帮助,但这个更符合我所寻找的。谢谢! - chaosTechnician

-1
如果您可以获取整个帧的校验和并将其用作种子,那会怎样呢?这样,如果帧上的任何内容在移动选择时发生变化,您正在尝试随机化的选择也将随着帧移动。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接