Better approach to rendering perlin noise to a texture?

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:


#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:


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

[QUOTE=cmcrook;1289464]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?[/QUOTE]
Normally, Perlin noise is formed as a rectangular grid of cells, where the gradient at each vertex is shared by the four cells sharing that vertex. In which case, you may as well ditch the geometry shader and just render either a grid of quads (triangle pairs) with a gradient per vertex, or a single quad with the gradients stored in a texture (the fragment shader divides the surface coordinates by the cell size, with the quotient identifying the cell and the remainder the position within the cell).