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

问题描述 投票:0回答:4

我正在尝试编写一个非常简单的着色器,为适用的对象添加随机闪光。我想要执行此操作的方法是向像素着色器内的像素值添加随机的白色阴影 (R = G = B)。

似乎

noise()
并没有像我希望的那样工作:

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

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

noise()
的调用。

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

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

random hlsl directx-9 pixel-shader
4个回答
28
投票

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
就是这个 坏函数:

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

如果您想比较“伪随机”函数,请查看 Dave 的 Hash without sine 着色器。


9
投票

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

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

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

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

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

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


6
投票

没有任何内容表明您必须在每次运行中重复使用随机生成器的种子,您只需要任何种子即可。

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

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

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

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


0
投票

对于几年后阅读的人来说,我个人发现没有三角学的函数可以在最长的时间内保持最稳定,不会出现明显的伪影或模式。它在某个时刻确实会崩溃,但第一个伪像开始出现在荒谬的高值,此时来自 Book of Shaders 的解决方案和 Michaelangel007 建议的解决方案已经为所有输入输出单个值。 查看 Dave_Hoskins 的 Hash without Sine (

https://www.shadertoy.com/view/4djSRW#

)。他根据你需要的输入和输出的数量列出了多个函数,因此对于 2D 噪声(2 个输入,1 个输出),代码如下: float hash12(vec2 p) { vec3 p3 = fract(vec3(p.xyx) * .1031); p3 += dot(p3, p3.yzx + 33.33); return fract((p3.x + p3.y) * p3.z); }

当转换为 OP 使用的 HLSL 时,它应该如下所示:

float hash12(float2 p) { vec3 p3 = frac(float3(p.xyx) * .1031); p3 += dot(p3, p3.yzx + 33.33); return frac((p3.x + p3.y) * p3.z); }

© www.soinside.com 2019 - 2024. All rights reserved.