随机与噪声:Value Noise

Number of views 138

我们已经对White Noise有了比较全面的了解,也已经能对White Noise做一些简单的应用了,但是White Noise的缺点还是很明显的,比如:像素点间的过渡过于生硬,无法产生平滑的过渡效果。这也是我们引入Value Noise的原因。

#define STEP 200.

float hash(float x) {
	return fract(sin(x * 13452.) * 1000.);
}

float hash2(vec2 v) {
	return fract(sin(dot(v, vec2(64.24232, 87.873)))*10232.);
}

float hash3(vec3 v) {
	return fract(sin(dot(v, vec3(64.24232, 87.873, 76.635)))*10232.);
}

float valueNoise(vec2 v) {
	vec2 f = floor(v);
    vec2 r = smoothstep(vec2(0.), vec2(1.), fract(v));
    
    float lb = hash2(f);
    float rb = hash2(f+vec2(1.,0.));
    float lt = hash2(f+vec2(0.,1.));
    float rt = hash2(f+vec2(1.,1.));
    
    float b_line = mix(lb, rb, r.x);
    float t_line = mix(lt, rt, r.x);
    
    return mix(b_line, t_line, r.y);
}

float valueNoise3(vec3 v) {
	vec3 f = floor(v);
    vec3 r = smoothstep(vec3(0.), vec3(1.), fract(v));
    
    return mix(
    	mix(mix(hash3(f), hash3(f+vec3(1.,0.,0.)), r.x), 
            mix(hash3(f + vec3(0.,1.,0.)), hash3(f + vec3(1.,1.,0.)), r.x), 
            r.y),
        mix(mix(hash3(f+vec3(0.,0.,1.)), hash3(f+vec3(1.,0.,1.)), r.x), 
            mix(hash3(f + vec3(0.,1.,1.)), hash3(f + vec3(1.,1.,1.)), r.x), 
            r.y),
        r.z
    );
}

float sphere(vec3 p) {
    // p.x += dot(p.yz, p.yz);
    // p.z -= dot(p.xy, p.xy) * cos(iTime);
    float fireK = smoothstep(0.9,.5,valueNoise3(p*10.+iTime*3.));
    // vec3 fire = mix(vec3(1.,0.,0.), vec3(1., 1., 1.), fireK);
    p.y -=  fireK;	
	return length(p) - 1.;
}

vec2 dist(vec3 p) {
    vec2 sphereDist = vec2(sphere(p-vec3(0., 0., 0.)), 2.);
	vec2 tableDist = vec2(p.y + 1., 1.);
    return tableDist.x < sphereDist.x?tableDist:sphereDist;
}

vec3 calNormal(vec3 p) {
	vec2 k = vec2(0.001, 0.);
    return normalize(vec3(
    	dist(p + k.xyy).x - dist(p - k.xyy).x,
        dist(p + k.yxy).x - dist(p - k.yxy).x,
        dist(p + k.yyx).x - dist(p - k.yyx).x
    ));
}

vec2 raymarch(vec3 ro, vec3 rd) {
    float t = 0.;
    float y = -1.;
    for(float i = 0.; i<STEP; i++) {
    	vec2 d = dist(ro + rd*t);
        if(d.x < .001) {
            y = d.y;
        	break;
        }
        if(t > STEP) {
            t = -1.;
            break;
        }
        t += d.x;
    }
    return vec2(t, y);
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    // Normalized pixel coordinates (from -1 to 1)
    vec2 uv = 2. * (fragCoord - 0.5*iResolution.xy) / iResolution.y;

    // Time varying pixel color
    vec3 skyCol = vec3(0.2, 0.6, 1.) - 0.3 * uv.y;
	vec3 col = skyCol;
    vec3 ro = vec3(0.,0.,3.);
    vec3 rd = normalize(vec3(uv.xy, -1.));
    vec2 t = raymarch(ro, rd);
    if(t.x > 0.) {
        vec3 mate = vec3(0.2);
        vec3 sunCol = vec3(1.);
        vec3 p = ro + rd*t.x;
        vec3 n = calNormal(p);
        vec3 sunDir = normalize(vec3(0.8,0.4,0.2));
        float sunDiff = max(0., dot(n, sunDir));
        float skyDiff = clamp(0.5 + 0.5*dot(vec3(0.,1.,0.), n), 0., 1.);
    	float sunShadow = step(raymarch(p + n*0.01, sunDir).x, 0.);
        
        col = mate * vec3(8.0,5.0,3.0) * sunDiff * sunShadow;
        col += mate * skyDiff * vec3(0.2, 0.6, 1.);
        if(t.y > 1.) {
            float fireK = smoothstep(0.2,.5,valueNoise3(p*10.+iTime*3.));
            vec3 fire = mix(vec3(1.), vec3(1., 0.5, 1.), fireK);
        	col *= fire;
        } else {
        	// col += hash(uv.y);
        }
    }
    col = pow(col, vec3(0.4545));
    // Output to screen
    fragColor = vec4(col,1.0);
}

上面代码是本篇实验的实践结果,具体做法需要结合Raymarching部分进行讲解,完整代码后续在Shader实验室: RayMarching(二)中进行详细分析。我们对Noise的拆解还是按照从1D到3D的步骤进行描述。

1D Value Noise

在开篇部分我们说过Value Noise与White Noise的区别是像素点的过渡是否平滑,而要实现平滑过渡很直观的想法是通过线性插值来进行处理,而线性插值的前提是:必须是两个不同的值才可实现插值。根据推论,我们可取floor(x)与floor(x)+1.0作为数据源,再结合随机与噪声:White Noise篇中推出的随机算法进行计算,形如:

float random(float x) {
     return fract(sin(x * 457.87) * 10000.0);
}
y1 = floor(x);
y2 = floor(x)+1.0;
// 以下为伪代码,?号为未知数值, 代表如何插值还未知
float res = mix(random(y1), random(y2), ?);

上述代码如何进行插值是我们接下来要讨论的重点,我们可结合mix函数的定义来分析(等同于Cg中的lerp函数)。

// 当w为0时,返回a,当w为1时返回b,否则返回a与b的插值
float mix(float a, float b, float w) {
   return a + w*(b-a);
} 

根据mix的定义可知,mix函数的第三个参数最好是一个0~1间的数,分析到这,我们可以很清晰的想到可以使用fract(x)作为mix函数的第三个参数,原因可看Shader实验室:fract函数。我们把fract(x)代入:

float res = mix(random(y1), random(y2), fract(x));

效果如下:

image.png

生成结果发现线与线间的连接处很生硬,如何让其更平滑?或者我们分析过的函数中,哪个函数可以做到平滑?是的,smoothstep函数,那就上吧:

float res = mix(random(y1), random(y2), smoothstep(0., 1., fract(x)));

image.png

好了,掌握了1D Value Noise的生成,一起分析下2D Value Noise吧。

2D Value Noise

2D Value Noise与1D Value Noise的原理类似,区别在于2D Value Noise在平面上做插值,如图:

image.png

在代码实现上,可通过下图所示方式:

image.png

示意代码如下:

vec2 f = floor(vec2(x, y));
vec2 i = fract(vec2(x, y)); 
float ceil1 = random(f); 
float ceil2 = random(f+vec2(1,0)); 
float ceil3 = random(f+vec2(0,1)); 
float ceil4 = random(f+vec2(1,1)); 
float ceil12 = mix(ceil1, ceil2, smoothstep(0, 1, i.x)); 
float ceil34 = mix(ceil3, ceil4, smoothstep(0, 1, i.x)); 
float result = mix(ceil12, ceil34, i.y);

2D Value Noise的输出结果如下:

image.png

3D Value Noise

3D Value Noise相比2D Value Noise多了一个维度,但是不管是1D Value Noise 还是 2D Value Noise,还是我们现在要分析的3D Value Noise原理都是一样的,3D Value Noise的原理如下:

image.png

上图是3D立方体,其中绿色线框与蓝色线框分别为立方体的前面与背面,绿色线框与蓝色线框部分的Value Noise值可通过2D Value Noise的插值方式求取,最后通过mix( cell(green), cell(blue), smoothstep(0, 1, fract(z)))求得;示例代码如下:

float valueNoise3(vec3 v) {
     vec3 f = floor(v);
     vec3 r = smoothstep(vec3(0.), vec3(1.), fract(v));
     return mix(
         mix(mix(random(f), random(f+vec3(1.,0.,0.)), r.x),
              mix(random(f + vec3(0.,1.,0.)), random(f + vec3(1.,1.,0.)), r.x),
              r.y),
         mix(mix(random(f+vec3(0.,0.,1.)), random(f+vec3(1.,0.,1.)), r.x),
              mix(random(f + vec3(0.,1.,1.)), random(f + vec3(1.,1.,1.)), r.x),
              r.y),
         r.z
     );
}

Value Noise部分就到这里啦,感兴趣的伙伴可自己实现开篇的案例,由于Value Noise实现的效果会有块状的效果产生(可查看2D Value Noise的输出图片),这也是Perlin Noise提出的原因。

感兴趣的伙伴可扫描下方二维码关注我们吧

image.png

0 Answers