Can I generate a random number inside a pixel shader?
我正在尝试编写一个非常简单的着色器,以向适用对象添加随机闪光。 我要这样做的方法是在像素着色器内的像素值中添加随机的白色阴影(R = G = B)。
似乎
1 | float multiplier = noise(float3(Input.Position[0], Input.Position[1], time)); |
它为我提供了"错误X4532:无法将表达式映射到像素着色器指令集",这是对
由于我不知道在着色器调用之间保留数字的方法,因此我认为我无法基于渲染前传递的种子编写简单的随机数产生函数。
有没有办法从像素着色器内部生成随机数? 如果有办法,怎么办?
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
一个"经典"随机函数(有时称为
1 | return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453); |
如果要比较"伪随机"函数,请查看没有正弦着色器的Dave哈希。
当您想要像素着色器中的随机值时,通常要做的是传递包含噪点的纹理。虽然它实际上不是"随机"的,但它看起来是随机的。
例如,这是我周围的像素着色器的一些代码:
1 | float3 random = (tex2D(noiseTexture, texCoord * noiseScale + noiseOffset)); |
我使用的纹理是RGB噪声纹理,有时可以派上用场。但是,相同的技术将适用于灰度级技术。
通过缩放,可以确保噪声纹理中的像素与屏幕上的像素对齐(您可能还希望将纹理采样器设置为"点"模式,以免使噪声纹理模糊)。
通过使用偏移量,您可以滚动纹理-就像播种随机数生成器一样。如果要避免出现"滚动"外观,请使用随机偏移。
没有什么可以说您必须在每次运行之间将种子重用于随机生成器,而您只需要任何种子。
如果使用像素坐标,那么最终将获得确定性的结果(即像素x,y始终具有相同的随机光斑),但是总体上将随机分布整个面部。
现在,如果您可以输入一些根据环境而变化的东西(我对像素着色器一无所知),例如像素在场景/相机组合的全局空间中的整体位置,而不是相对于多边形的整体位置那么,尤其是在快速变化的环境中,您的结果将有效地随机出现。
如果全局环境中的所有事物都完全相同,那么,是的,您将具有完全相同的"随机"分布。但是,无论这些因素中的任何一个发生变化(特别是基于用户输入,这很可能是您最动态的"噪声源"),那么总的效果可能就是"足够随机"。
因此,关键在于,您的种子不必是先前随机数生成器运行的结果。可以是任何东西。因此,根据着色器对您自己的RNG的输入为每个像素构造一个种子,可能会给您带来所需的效果。
如果您可以获得整个帧的校验和并将其用作种子该怎么办?
这样,如果框架上的任何东西都在移动,则您尝试随机化的选择将随框架移动。