9

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

这似乎noise()不像我希望的那样工作:

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

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

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

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

4

4 回答 4

26

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

将以上内容粘贴到:

我还创建了一个带有 2 个噪声函数和 2 个随机函数的“比较”ShaderToy 示例:

使用噪声“[2TC 15] Speckle Cross Fade”的演示

一个“经典”随机函数,有时被称为snoise3是这个函数:

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

如果您想比较“伪随机”函数,请查看 Dave's Hash without sine shader。

于 2012-05-16T19:52:02.607 回答
8

当您想要像素着色器中的随机值时,您通常会做的是传入包含噪声的纹理。虽然它实际上不是“随机的” - 它看起来是随机的。

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

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

我使用的纹理是 RGB 噪声纹理,有时会派上用场。但同样的技术也适用于灰度技术。

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

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

于 2011-03-01T03:34:04.527 回答
6

没有什么说您必须从运行到运行重复使用随机生成器的种子,您只需要任何种子。

如果你使用,比如说,像素坐标,那么你最终会得到一个确定性的结果(即像素 x,y 总是有相同的随机光斑),但总的来说,它会随机分布在整个面部。

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

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

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

于 2011-03-01T01:38:47.670 回答
0

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

于 2018-08-01T07:43:30.457 回答