The vertex shader takes two input attributes: the first is the upper left corner vertex position and the second is the four gradients at the corner of each cell. The geometry shader takes each point and constructs two triangles to form the cell and passes the gradients through to the fragment shader to calculate the brightness of the fragment.

Is there a more efficient approach?

Code:
Code :
#pragma once
 
#include <iostream>
#include <vector>
#include <chrono>
#include <random>
#include <glm.hpp>
#include <GL/glew.h>
#include <gtc/type_ptr.hpp>
#include <gtc/matrix_transform.hpp>
#include "../shaders/geometryShader.hpp"
 
class PerlinNoise {
	GLuint xSub, ySub;
	GLfloat width, height;
	GLuint VAO, VBO, GBO;
	GeometryShader shader;
	std::vector<GLfloat> xy, uv, quadVerts, quadGrads;
 
public:
	PerlinNoise(GLfloat width, GLfloat height, GLuint xSub, GLuint ySub) : shader("shaders/perlin/perlin.vs", "shaders/perlin/perlin.gs", "shaders/perlin/perlin.fs") {
		this->width = width;
		this->height = height;
		this->xSub = xSub;
		this->ySub = ySub;
 
		genGrid();
		initVAO();
	}
 
	void initVAO() {
		glGenVertexArrays(1, &VAO);
		glGenBuffers(1, &VBO);
		glGenBuffers(1, &GBO);
 
		glBindVertexArray(VAO);
 
		glBindBuffer(GL_ARRAY_BUFFER, VBO);
		glBufferData(GL_ARRAY_BUFFER, quadVerts.size() * sizeof(GLfloat), &quadVerts.front(), GL_DYNAMIC_DRAW);
		glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), (GLvoid*)0);
		glEnableVertexAttribArray(0);
 
		glBindBuffer(GL_ARRAY_BUFFER, GBO);
		glBufferData(GL_ARRAY_BUFFER, quadGrads.size() * sizeof(GLfloat), &quadGrads.front(), GL_DYNAMIC_DRAW);
		glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)0);
		glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(2 * sizeof(GLfloat)));
		glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(4 * sizeof(GLfloat)));
		glVertexAttribPointer(4, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));
		glEnableVertexAttribArray(1);
		glEnableVertexAttribArray(2);
		glEnableVertexAttribArray(3);
		glEnableVertexAttribArray(4);
		glBindBuffer(GL_ARRAY_BUFFER, 0);
 
		glBindVertexArray(0);
	}
 
	void genGrid() {
		GLfloat xSpacing = width / xSub,
			ySpacing = height / ySub;
 
		unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
		std::default_random_engine generator(seed);
		std::uniform_real_distribution<GLfloat> distribution(-1.0, 1.0);
		for (GLuint j = 0; j < (ySub + 1); j++) {
			for (GLuint i = 0; i < (xSub + 1); i++) {
				glm::vec2 grad = glm::normalize(glm::vec2(distribution(generator), distribution(generator)));
				xy.push_back(i*xSpacing - 1.0f);
				xy.push_back(1.0f - j*ySpacing);
				uv.push_back(grad.x);
				uv.push_back(grad.y);
			}
		}
 
		for (int j = 0; j < ySub; j++) {
			for (int i = 0; i < xSub; i++) {
				GLuint index1 = (i + j*(xSub + 1));
				GLuint index2 = (i + j*(xSub + 1) + 1);
				GLuint index3 = (i + (j + 1)*(xSub + 1));
				GLuint index4 = (i + (j + 1)*(xSub + 1) + 1);
 
				quadVerts.push_back(xy[2 * index1]);
				quadVerts.push_back(xy[2 * index1 + 1]);
 
				quadGrads.push_back(uv[2 * index1]);
				quadGrads.push_back(uv[2 * index1 + 1]);
 
				quadGrads.push_back(uv[2 * index2]);
				quadGrads.push_back(uv[2 * index2 + 1]);
 
				quadGrads.push_back(uv[2 * index3]);
				quadGrads.push_back(uv[2 * index3 + 1]);
 
				quadGrads.push_back(uv[2 * index4]);
				quadGrads.push_back(uv[2 * index4 + 1]);
			}
		}
	}
 
	void render() {
		glDisable(GL_CULL_FACE);
		shader.use();
		glUniform1f(glGetUniformLocation(shader.program, "xSpacing"), width / xSub);
		glUniform1f(glGetUniformLocation(shader.program, "ySpacing"), height / ySub);
		glBindVertexArray(VAO);
		glDrawArrays(GL_POINTS, 0, quadVerts.size() / 2);
		glBindVertexArray(0);
		glEnable(GL_CULL_FACE);
	}
 
	void cleanUp() {
		glDeleteVertexArrays(1, &VAO);
		glDeleteBuffers(1, &VBO);
		glDeleteBuffers(1, &GBO);
	}
};

My shader code:

Code :
//Vertex Shader
#version 440 core
 
layout(location = 0) in vec2 position;
layout(location = 1) in vec2 gradient[4];
 
out vec2 gradient_pass[4];
 
void main(){
	gradient_pass = gradient;
	gl_Position = vec4(position, -1.0,1.0);
}
 
//Geometry Shader
#version 440 core
 
layout (points) in;
layout (triangle_strip, max_vertices = 4) out;
 
uniform float xSpacing;
uniform float ySpacing;
 
in vec2 gradient_pass[][4];
 
out vec2 fragPos;
flat out vec2 gradient_fs_pass[4];
 
void main(){
	vec4 corner[4];
	corner[0] = gl_in[0].gl_Position;
	corner[1] = gl_in[0].gl_Position + vec4(xSpacing, 0.0, 0.0, 0.0);
	corner[2] = gl_in[0].gl_Position + vec4(0.0,-ySpacing, 0.0, 0.0);
	corner[3] = gl_in[0].gl_Position + vec4(xSpacing, -ySpacing, 0.0, 0.0);
 
	gl_Position = corner[0];
	fragPos = vec2(0.0,0.0);
	gradient_fs_pass = gradient_pass[0];
	EmitVertex();
 
	gl_Position = corner[1];
	fragPos = vec2(1.0,0.0);
	gradient_fs_pass = gradient_pass[0];
	EmitVertex();
 
	gl_Position = corner[2];
	fragPos = vec2(0.0,1.0);
	gradient_fs_pass = gradient_pass[0];
	EmitVertex();
 
	gl_Position = corner[3];
	fragPos = vec2(1.0,1.0);
	gradient_fs_pass = gradient_pass[0];
	EmitVertex();
	EndPrimitive();
}
 
//Fragment Shader
#version 440 core
 
in vec2 fragPos;
flat in vec2 gradient_fs_pass[4];
 
out vec4 color;
 
float smootherstep(float t){
	return 6*pow(t,5) - 15*pow(t,4)+10*pow(t,3);
}
 
void main(){
	vec2 s11 = vec2(fragPos.x, -fragPos.y);
	vec2 s12 = vec2(fragPos.x-1, -fragPos.y);
	vec2 s21 = vec2(fragPos.x, 1-fragPos.y);
	vec2 s22 = vec2(fragPos.x-1, 1-fragPos.y);
 
	float a11 = (dot(s11, gradient_fs_pass[0])+1.0)/2.0;
	float a12 = (dot(s12, gradient_fs_pass[1])+1.0)/2.0;
	float a21 = (dot(s21, gradient_fs_pass[2])+1.0)/2.0;
	float a22 = (dot(s22, gradient_fs_pass[3])+1.0)/2.0;
 
	float aTop = a11 + smootherstep(fragPos.x)*(a12-a11);
	float aBot = a21 + smootherstep(fragPos.x)*(a22-a21);
	float a = aTop + smootherstep(fragPos.y)*(aBot-aTop);
 
	color = vec4(a,a,a,1.0);
}