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

<p><code>int xi = int(round(st.x)) &amp; 255;</code></p>

<p>It uses the round() function even though it is not necessary. Moreover, it seems that the <a href="https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/round.xhtml" rel="nofollow noreferrer">rounding direction can be implementation-specific</a>. Changing it to simply <code>int xi = int(st.x) &amp; 255</code> solves the problem.</p>

CC BY-SA 4.0
2020-03-28T20:49:46.400Z
2020-03-28T20:49:46.400Z
Nipun Garg
<p>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.</p>

<p>I used <a href="https://flafla2.github.io/2014/08/09/perlinnoise.html" rel="nofollow noreferrer">Understanding Perlin Noise</a> 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.</p>

<p>This is the resulting noise that I'm getting:
<a href="https://i.sstatic.net/1oz6b.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/1oz6b.png" alt="Perlin Noise with edges"></a></p>

<p>This is the noise I was getting in my Canvas implementation:
<a href="https://i.sstatic.net/WdN3V.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/WdN3V.png" alt="Canvas Perlin Noise"></a></p>

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

<pre><code>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 &amp; 255];
    int b = p[yi &amp; 255];
    int val = p[(a + b) &amp; 255];
    return grad[val];
}

float perlinAux(vec2 st) {
    int xi = int(round(st.x)) &amp; 255;
    int yi = int(round(st.y)) &amp; 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 &lt; 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);
</code></pre>

<algorithm><noise>
Perlin Noise implementation looks blocky in WebGL2
Nipun Garg
# Perlin Noise implementation looks blocky in WebGL2

(Asked by: username_0 on 2020-03-27)

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](https://flafla2.github.io/2014/08/09/perlinnoise.html) 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](https://i.sstatic.net/1oz6b.png)](https://i.sstatic.net/1oz6b.png)

This is the noise I was getting in my Canvas implementation:
[![Canvas Perlin Noise](https://i.sstatic.net/WdN3V.png)](https://i.sstatic.net/WdN3V.png)

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

```

**Tags:** algorithm, noise


---

## 1 answers

### Answer by username_0 on 2020-03-28. Score: 1

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](https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/round.xhtml). Changing it to simply `int xi = int(st.x) & 255` solves the problem.


---

