0
$\begingroup$

I'm trying to implement Perlin Noise. Earlier I implemented it using HTML Canvas and then converted it to WebGL2 since I wanted a faster implementation. But the WebGL2 code has obvious edge artifacts in the generated noise where the noise function is not smooth.

I used Understanding Perlin Noise as my reference for understanding Perlin Noise. However, I did use a different implementation for calculating the dot product of the gradient and distance vectors since I could not get the default implementation to work.

This is the resulting noise that I'm getting: Perlin Noise with edges

This is the noise I was getting in my Canvas implementation: Canvas Perlin Noise

My algorithm for both the Canvas and WebGL is the same. I have attached my WebGL code below. Any ideas why this is happening?

let gl = canvas.getContext("webgl2");
if (!gl) {
    console.log("WebGL2 support not found");
}

let octaveCount = 6;
let persistence = 1.0;

canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

const vertexSrc = `
#version 300 es
precision mediump float;

vec2 quad[6] = vec2[](
    vec2(-1.0, -1.0),
    vec2(-1.0, 1.0),
    vec2(1.0, -1.0),
    vec2(-1.0, 1.0),
    vec2(1.0, -1.0),
    vec2(1.0, 1.0)
);

void main() {
    gl_Position = vec4(quad[gl_VertexID], 0.0, 1.0);
}
`;

const fragmentSrc = `
#version 300 es
precision mediump float;

uniform vec2 offset;
uniform int octaveCount;
uniform float persistence;
uniform vec2 resolution;
out vec4 color;

int p[256] = int[](151,.../*abbreviated for conveinence*/..180);

vec2 grad[256] = vec2[](
    vec2(1, 0),.../*abbreviated for conveinence*/...vec2(0.999698818696, -0.0245412285229)
);

float fade(float t) {
    return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);
}

vec2 getVec(int xi, int yi) {
    int a = p[xi & 255];
    int b = p[yi & 255];
    int val = p[(a + b) & 255];
    return grad[val];
}

float perlinAux(vec2 st) {
    int xi = int(round(st.x)) & 255;
    int yi = int(round(st.y)) & 255;
    float xf = st.x - float(xi);
    float yf = st.y - float(yi);
    float u = fade(xf);
    float v = fade(yf);

    float n00 = dot(vec2(xf, yf), getVec(xi, yi));
    float n01 = dot(vec2(xf, yf - 1.0), getVec(xi, yi + 1));
    float n10 = dot(vec2(xf - 1.0, yf), getVec(xi + 1, yi));
    float n11 = dot(vec2(xf - 1.0, yf - 1.0), getVec(xi + 1, yi + 1));

    float x1 = mix(n00, n10, u);
    float x2 = mix(n01, n11, u);
    return (mix(x1, x2, v) + 1.0) / 2.0;
}

float perlin(vec2 offset, vec2 st, int octaveCount, float persistence) {
    float total = 0.0;
    float freq = 1.0;
    float amp = 1.0;
    float maxValue = 0.0;
    for (int i = 0; i < octaveCount; i++) {
        total += perlinAux(offset + st * freq) * amp;
        maxValue += amp;
        amp *= persistence;
        freq *= 2.0;
    }

    return total / maxValue;
}

void main() {
    vec2 st = gl_FragCoord.xy / resolution;
    float perlinVal = perlin(offset, st, octaveCount, persistence);
    color = vec4(perlinVal, perlinVal, perlinVal, 1.0);
}
`;

function compileShader(shaderSource, shaderType) {
    const shader = gl.createShader(shaderType);
    gl.shaderSource(shader, shaderSource);
    gl.compileShader(shader);
    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        throw "Shader compile failed with: " + gl.getShaderInfoLog(shader);
    }
    return shader;
}

const vertexShader = compileShader(vertexSrc.trim(), gl.VERTEX_SHADER);
const fragmentShader = compileShader(fragmentSrc.trim(), gl.FRAGMENT_SHADER);

const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
    throw "Program link failed with: " + gl.getProgramInfoLog(program);
}

const offsetLOC = gl.getUniformLocation(program, "offset");
const resolutionLOC = gl.getUniformLocation(program, "resolution");
const octaveCountLOC = gl.getUniformLocation(program, "octaveCount");
const persistenceLOC = gl.getUniformLocation(program, "persistence");

gl.useProgram(program);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clearColor(0, 0, 0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);

gl.uniform2f(offsetLOC, 0.0, 0.0);
gl.uniform2f(resolutionLOC, gl.canvas.width, gl.canvas.height);
gl.uniform1i(octaveCountLOC, octaveCount);
gl.uniform1f(persistenceLOC, persistence);

gl.drawArrays(gl.TRIANGLES, 0, 6);
$\endgroup$
1
$\begingroup$

I made a mistake while translating the code from JavaScript to GLSL. The first line in the perlinAux function:

int xi = int(round(st.x)) & 255;

It uses the round() function even though it is not necessary. Moreover, it seems that the rounding direction can be implementation-specific. Changing it to simply int xi = int(st.x) & 255 solves the problem.

| improve this answer | |
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.