Author: Romain Vergne (website)
Please cite my name and add a link to my web page if you use this course

Image synthesis and OpenGL: mini-project

Project

The project consists in rendering a terrain using the different techniques seen during the courses:





Algorithm

The rendering will need at least 5 passes:

About the Vertex Buffer Objects (VAOs)


About the FrameBuffer Objects (FBOs)


About the noise shader

Look at the function perlinNoise in shader noise.frag:

About the normal shader

About the rendering

About the post-processing

About the shadow map

Annexes:


grid.h
#ifndef GRID_H
#define GRID_H

#include <vector>

class Grid {
 public:
  Grid(unsigned int size=1024,float minval=-1.0f,float maxval=1.0f);
  ~Grid();

  inline unsigned int nbVertices() const {return _nbVertices;}
  inline unsigned int nbFaces   () const {return _nbFaces;   }

  inline float *vertices() {return &_vertices[0];}
  inline int   *faces   () {return &_faces[0];   }
 
 private:
  unsigned int _nbVertices;
  unsigned int _nbFaces;

  std::vector<float> _vertices;
  std::vector<int>   _faces;
};

#endif //GRID_H

grid.cpp
#include "grid.h"

using namespace std;

Grid::Grid(unsigned int size,float minval,float maxval) {
  const float w = maxval-minval;
  const float h = w;

  const float stepW  = w/(float)size;
  const float stepH  = h/(float)size;
  const float startx = minval;
  const float starty = minval;

  for(unsigned int i=0;i<size;++i) {
    for(unsigned int j=0;j<size;++j) {
     
      const float currentx = startx+stepW*(float)j;
      const float currenty = starty+stepH*(float)i;
    
      _vertices.push_back(currentx);
      _vertices.push_back(currenty);
      _vertices.push_back(0.0f);

      if(i>0 && j>0) {
    int i1 = i*size+j;
    int i2 = (i-1)*size+j;
    int i3 = (i-1)*size+j-1;
    int i4 = i*size+j-1;
   
    _faces.push_back(i1);
    _faces.push_back(i2);
    _faces.push_back(i3);
    _faces.push_back(i3);
    _faces.push_back(i4);
    _faces.push_back(i1);
      }
    }
  }

  _nbVertices = _vertices.size()/3;
  _nbFaces    = _faces.size()/3;
}

Grid::~Grid() {
  _vertices.clear();
  _faces.clear();
}

VAO creation/suppression
void Viewer::createVAO() {
  //the variable _grid should be an instance of Grid
  //the .h file should contain the following VAO/buffer ids
  //GLuint _vaoTerrain;
  //GLuint _vaoQuad;
  //GLuint _terrain[2];
  //GLuint _quad;

  const GLfloat quadData[] = {
    -1.0f,-1.0f,0.0f, 1.0f,-1.0f,0.0f, -1.0f,1.0f,0.0f, -1.0f,1.0f,0.0f, 1.0f,-1.0f,0.0f, 1.0f,1.0f,0.0f };

  glGenBuffers(2,_terrain);
  glGenBuffers(1,&_quad);
  glGenVertexArrays(1,&_vaoTerrain);
  glGenVertexArrays(1,&_vaoQuad);

  // create the VBO associated with the grid (the terrain)
  glBindVertexArray(_vaoTerrain);
  glBindBuffer(GL_ARRAY_BUFFER,_terrain[0]); // vertices
  glBufferData(GL_ARRAY_BUFFER,_grid->nbVertices()*3*sizeof(float),_grid->vertices(),GL_STATIC_DRAW);
  glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,(void *)0);
  glEnableVertexAttribArray(0);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,_terrain[1]); // indices
  glBufferData(GL_ELEMENT_ARRAY_BUFFER,_grid->nbFaces()*3*sizeof(int),_grid->faces(),GL_STATIC_DRAW);

  // create the VBO associated with the screen quad
  glBindVertexArray(_vaoQuad);
  glBindBuffer(GL_ARRAY_BUFFER,_quad); // vertices
  glBufferData(GL_ARRAY_BUFFER, sizeof(quadData),quadData,GL_STATIC_DRAW);
  glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,(void *)0);
  glEnableVertexAttribArray(0);
}

void Viewer::deleteVAO() {
  glDeleteBuffers(2,_terrain);
  glDeleteBuffers(1,&_quad);
  glDeleteVertexArrays(1,&_vaoTerrain);
  glDeleteVertexArrays(1,&_vaoQuad);
}




noise.vert
#version 330

// input attributes
layout(location = 0) in vec3 position;

out vec2 pos;

void main() {
  // no need for any particular transformation (Identity matrices)
  pos = position.xy*0.5+0.5;
  gl_Position = vec4(position,1);
}

noise.frag
#version 330

in vec2 pos;

out vec4 outBuffer;

vec2 hash(vec2 p) {
  p = vec2( dot(p,vec2(127.1,311.7)),
        dot(p,vec2(269.5,183.3)) );
 
  return -1.0 + 2.0*fract(sin(p)*43758.5453123);
}

float gnoise(in vec2 p) {
  vec2 i = floor( p );
  vec2 f = fract( p );
   
  vec2 u = f*f*(3.0-2.0*f);
 
  return mix( mix( dot( hash( i + vec2(0.0,0.0) ), f - vec2(0.0,0.0) ),
           dot( hash( i + vec2(1.0,0.0) ), f - vec2(1.0,0.0) ), u.x),
          mix( dot( hash( i + vec2(0.0,1.0) ), f - vec2(0.0,1.0) ),
           dot( hash( i + vec2(1.0,1.0) ), f - vec2(1.0,1.0) ), u.x), u.y);
}

float pnoise(in vec2 p,in float amplitude,in float frequency,in float persistence, in int nboctaves) {
  float a = amplitude;
  float f = frequency;
  float n = 0.0;
 
  for(int i=0;i<nboctaves;++i) {
    n = n+a*gnoise(p*f);
    f = f*2.;
    a = a*persistence;
  }
 
  return n;
}


void main() {
  vec3 motion = vec3(0.); // could be controlled via a global uniform variable
  float p = pnoise(pos+motion.xy,2.0,4.0,0.5,10)+motion.z;

  outBuffer = vec4(p*0.5+0.5);
}

normal.vert
layout(location = 0) in vec3 position;

out vec2 texcoord;


void main() {

gl_Position = vec4(vertex,0,1);

texcoord = position.xy*0.5+0.5;

}



normal.frag

out vec4 outBuffer;
uniform sampler2D heightmap;

in vec2 texcoord;


float value(in vec4 c) {

// gradient of what:

return c.x;// the height is stored in all channels (take the first one)

}


void main() {

vec2 ps = 1./vec2(textureSize(heightmap,0));

vec2 g = vec2( value(texture(heightmap,texcoord+vec2(ps.x,0.))) -

value(texture(heightmap,texcoord-vec2(ps.x,0.))),

value(texture(heightmap,texcoord+vec2(0.,ps.y))) -

value(texture(heightmap,texcoord-vec2(0.,ps.y))))/2.;

float scale = 100.;

vec3 n1 = vec3(1.,0.,g.x*scale);

vec3 n2 = vec3(0.,1.,-g.y*scale);

vec3 n = normalize(cross(n1,n2));


outbuffer = vec4(n,value(texture(heightmap,texcoord)));

}





PREVIOUS: 09 - POST-PROCESS