-->

OpenGL es 2.0 Gaussian blur on triangle

2019-02-20 18:03发布

问题:

I recently learn opengl es 2.0, and now I try to make a gaussian blur on triangles generate by myself. I have some difficult to understand examples on the web and most apply the blur on an image. I know I have to use framebuffer but I don't know how to draw triangle on this and apply blur. Is it possible to see a real and complete code in C++ with good explication ?

EDIT :

#include <stdio.h>
#include <stdlib.h>
#include <iostream>

#define GLFW_INCLUDE_ES2
#include <GLFW/glfw3.h>
#include "shaders.hpp"
#include "camera.hpp"

unsigned int vbo, cbo, tbo;
GLuint _fbo, _fbo2, _tex, _tex2;


static const GLuint WIDTH = 800;
static const GLuint HEIGHT = 600;
GLuint pos, col, tex, normal;
camera * _camera = new camera();

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

static const GLfloat colors[] = {
  0.0f,  0.5f, 1.0f,
  0.5f,  0.5f, 1.0f,
  0.5f,  0.5f, 1.0f
};

static const GLfloat texture[] = {
  1.0f, 1.0f,
  1.0f, 0.0f,
  0.0f, 1.0f
};

int main(void){
  GLFWwindow* window;
  shaders * shaderBasic;
  GLuint pId;

  glm::mat4 projection; static glm::mat4 view; static glm::mat4 model;

  glfwInit();
  glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
  glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
  glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
  window = glfwCreateWindow(WIDTH, HEIGHT, __FILE__, NULL, NULL);
  glfwMakeContextCurrent(window);

  printf("GL_VERSION  : %s\n", glGetString(GL_VERSION) );
  printf("GL_RENDERER : %s\n", glGetString(GL_RENDERER) );

  std::string vs, fs;
  vs = "basic.vs";
  fs = "basic.fs";
  shaderBasic = new shaders(vs, fs);
  shaderBasic->CompileShader();
  shaderBasic->LinkShader();
  pId = shaderBasic->getProgramId();

  pos = glGetAttribLocation(pId, "position");
  col = glGetAttribLocation(pId, "colors");
  tex = glGetAttribLocation(pId, "tex");

  fs = "lastBlur.fs";
  shaders * blurShader;
  GLuint pIdBlur;
  blurShader = new shaders(vs, fs);
  blurShader->CompileShader();
  blurShader->LinkShader();
  pIdBlur = blurShader->getProgramId();

  _camera->setPositionCamera(glm::vec3(0, 0, -1));
  _camera->setLookAtCamera(glm::vec3(0, 0, 0));
  _camera->setFieldOfView(45);
  _camera->setAspect(WIDTH, HEIGHT);
  _camera->setViewport(WIDTH, HEIGHT);
  _camera->getMatricies(projection, view, model);

  glGenFramebuffers(1, &_fbo);
  glGenTextures(1, &_tex);
  glBindFramebuffer(GL_FRAMEBUFFER, _fbo);
  glBindTexture(GL_TEXTURE_2D, _tex);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, WIDTH/2, HEIGHT/2, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _tex, 0);
  glBindTexture(GL_TEXTURE_2D, 0);
  glBindFramebuffer(GL_FRAMEBUFFER, 0);

  if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
    std::cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << std::endl;
  else{
    std::cout << "FRAMEBUFFER COMPLETE" << std::endl;
  }
  auto sampTex = glGetUniformLocation(pIdBlur, "texture0");
  std::cerr << "sampTex : " << sampTex << std::endl;
  glUniform1i(sampTex, 0);  
  while (!glfwWindowShouldClose(window)) {
    //    glViewport(0, 0, WIDTH, HEIGHT);

    glBindFramebuffer(GL_FRAMEBUFFER, _fbo);
    glClearColor(0.0f, 0.0f, 0.4f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    //    glViewport(0, 0, WIDTH/2, HEIGHT/2);
    glUseProgram(pIdBlur);
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    glVertexAttribPointer(pos, 3, GL_FLOAT, false, 0, 0);
    glEnableVertexAttribArray(pos);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    glGenBuffers(1, &cbo);
    glBindBuffer(GL_ARRAY_BUFFER, cbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(colors), colors, GL_STATIC_DRAW);
    glVertexAttribPointer(col, 2, GL_FLOAT, false, 0, 0);
    glEnableVertexAttribArray(col);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    glGenBuffers(1, &tbo);
    glBindBuffer(GL_ARRAY_BUFFER, tbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(texture), texture, GL_STATIC_DRAW);
    glVertexAttribPointer(tex, 2, GL_FLOAT, false, 0, 0);
    glEnableVertexAttribArray(tex);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    glDrawArrays(GL_TRIANGLES, 0, 3);


    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glUseProgram(pId);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, _tex);
    glDrawArrays(GL_TRIANGLES, 0, 3);    

    glfwPollEvents();
    glfwSwapBuffers(window);
  }
  glDeleteBuffers(1, &vbo);
  glfwTerminate();
  return EXIT_SUCCESS;
}

Blur Shader:

#version 100
precision mediump float;

uniform sampler2D texture0;
varying vec3 vColor;
varying vec2 TexCoords;

vec4 blur13(sampler2D image, vec2 uv, vec2 resolution, vec2 direction) {
    vec4 color = vec4(0.0);
    vec2 off1 = vec2(1.411764705882353) * direction;
    vec2 off2 = vec2(3.2941176470588234) * direction;
    vec2 off3 = vec2(5.176470588235294) * direction;
    color += texture2D(image, uv) * 0.1964825501511404;
    color += texture2D(image, uv + (off1 / resolution)) * 0.2969069646728344;
    color += texture2D(image, uv - (off1 / resolution)) * 0.2969069646728344;
    color += texture2D(image, uv + (off2 / resolution)) * 0.09447039785044732;
    color += texture2D(image, uv - (off2 / resolution)) * 0.09447039785044732;
    color += texture2D(image, uv + (off3 / resolution)) * 0.010381362401148057;
    color += texture2D(image, uv - (off3 / resolution)) * 0.010381362401148057;
    return color;
}

void main(){
    gl_FragColor = blur13(texture0, TexCoords, vec2(400, 300), vec2(1.0, 0.0));
}

回答1:

I assume you have swapped pIdBlur and pId.

I' will give you introductions for gaussian blur shader with 2 passes. This is an approximation which first blurs along the X-Axis in the 1st pass and along the Y-Axis in the 2nd pass. This results in a better performance for strong blurring. The blur shader uses a normal (or Gaussian) distribution. For the 2 passes is used the same shader program, with individual direction settings for the 2 passes, stored in the uniform vec2 u_dir. The strength of the blur effect can be varied with the uniform variable float u_sigma in the range [0.0, 1.0].

Blur Vertex shader

precision mediump float;
attribute vec2 inPos;
varying   vec2 pos;

void main()
{
    pos = inPos;
    gl_Position = vec4( inPos, 0.0, 1.0 );
}

Blur Fragment shader

precision mediump float;
varying vec2 pos;

uniform sampler2D u_texture;
uniform vec2      u_textureSize;
uniform float     u_sigma;
uniform vec2      u_dir;

float CalcGauss( float x, float sigma )
{
    if ( sigma <= 0.0 )
        return 0.0;
    return exp( -(x*x) / (2.0 * sigma) ) / (2.0 * 3.14157 * sigma);
}

void main()
{
    vec2 texC     = pos.st * 0.5 + 0.5;
    vec4 texCol   = texture2D( u_texture, texC );
    vec4 gaussCol = vec4( texCol.rgb, 1.0 );
    vec2 step     = u_dir / u_textureSize;
    for ( int i = 1; i <= 32; ++ i )
    {
        float weight = CalcGauss( float(i) / 32.0, u_sigma * 0.5 );
        if ( weight < 1.0/255.0 )
            break;
        texCol    = texture2D( u_texture, texC + step * float(i) );
        gaussCol += vec4( texCol.rgb * weight, weight );
        texCol    = texture2D( u_texture, texC - step * float(i) );
        gaussCol += vec4( texCol.rgb * weight, weight );
    }
    gaussCol.rgb = clamp( gaussCol.rgb / gaussCol.w, 0.0, 1.0 );
    gl_FragColor = vec4( gaussCol.rgb, 1.0 );
}

After the program has been linked the uniform locations and attribute indices can be read from:

GLint attrInxPos = glGetAttribLocation( pIdBlur, "inPos" );
GLint locTexture = glGetUniformLocation( pIdBlur, "u_texture" );
GLint locTexSize = glGetUniformLocation( pIdBlur, "u_textureSize" );
GLint locSigma   = glGetUniformLocation( pIdBlur, "u_sigma" );
GLint locDir     = glGetUniformLocation( pIdBlur, "u_dir" );

A vertex array object, containing a quad, which later will be drawn over the whole viewport, for a screen space blur pass, has to be created:

GLuint screenVAO;
glGenVertexArrays( 1, &screenVAO );
glBindVertexArray( screenVAO );
GLuint quadBuf;
glGenBuffers( 1, &quadBuf );
glBindBuffer( GL_ARRAY_BUFFER, quadBuf );
GLfloat screenRect[] = { -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f };
glBufferData( GL_ARRAY_BUFFER, 8 * sizeof( float ), screenRect, GL_STATIC_DRAW );
glEnableVertexAttribArray( attrInxPos );
glVertexAttribPointer( attrInxPos, 2, GL_FLOAT, GL_FALSE, 0, nullptr );

2 frame buffers, with a texture attached to its color plane, have to be created. In the 1st one the scene is drawn. The 2nd one is used by the 1st blur pass. The 2nd blur pass draws directly to the drawing buffer.

GLuint texObj[2];
GLuint fbObj[2];
glGenTextures(2, texObj);
glGenFramebuffers(2, fbObj);
glActiveTexture(GL_TEXTURE0);
for ( int i = 0; i < 2; i ++ )
{
    glBindTexture(GL_TEXTURE_2D, texObj[i]);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
    glBindFramebuffer(GL_FRAMEBUFFER, fbObj[i]);
    glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texObj[i], 0 );
    GLuint renderbuffer;
    glGenRenderbuffers(1, &renderbuffer);
    glBindRenderbuffer( GL_RENDERBUFFER, renderbuffer );
    glRenderbufferStorage( GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height );
    glFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, renderbuffer );
}
glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);

Now everything what is needed for the blur passes has been generated.

To draw and blur the scene the following steps have to be applied. First you have to bind and clear the 1st frame buffer

glBindFramebuffer(GL_FRAMEBUFFER, fbObj[0]);
glClearColor(0.0f, 0.0f, 0.4f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

and use the shader program for drawing the objects:

glUseProgram(pId);

Now draw the object(s) of the scene.

.....
glDrawArrays(GL_TRIANGLES, 0, 3);

The second step is the 1st blur pass. The blur program has to be use and the 2nd framebuffer has to be bound. After the frame 1st buffer has been released, you can use the texture, that is attached to its color plane, as an input for the blur shader.
Note, a texture can't be source and destination at the same time, this would cause undefined behavior.
To bind the texture to the shader, you have to bind the texture to a texture unit and assign the index of the texture unit to the uniform sampler of the shader.

int texUnitIndex = 1;
GLfloat texSize  = { width, height };
GLfloat dirX[]   = { 1.0f, 0.0f };  
GLfloat sigma    = .....; // 0.0 <= sigma <= 1.0

glBindFramebuffer(GL_FRAMEBUFFER, fbObj[1]);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUseProgram(pIdBlur);
glActiveTexture(GL_TEXTURE0 + texUnitIndex);
glBindTexture(GL_TEXTURE_2D, texObj[0]);
glUniform1i(locTexture, texUnitIndex); 
glUniform2fv(locTexSize, texSize);
glUniform2fv(locTexSize, dirX);
glUniform1f(locTexSize, sigma);

To apply the blur pass a quad has to be drawn of the viewport area.

glBindVertexArray( screenVAO );
glDrawArrays( GL_TRIANGLE_STRIP, 0, 4 );

The 2nd and final blur pass, is similar to the 1st blur pass. The target texture of the 1st blur pass is the source texture, and the target is the drawing buffer. The blur direction has to be set up for the Y axis of the viewport.

GLfloat dirY[] = { 0.0f, 1.0f }; 

glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glBindTexture(GL_TEXTURE_2D, texObj[1]);
glUniform2fv(locTexSize, dirY);

See also the answers to the following question:

  • How to get a "Glow" shader effect in OpenGL ES 2.0?
  • What kind of blurs can be implemented in pixel shaders?

See alos a similar WebGL example:

(function loadscene() {

var resize, gl, progDraw, progBlur, vp_size, blurFB;
var canvas, camera, bufCube = {}, bufQuad = {}; 
var shininess = 10.0, glow = 10.0, sigma = 0.8, radius = 1.0;

function render(deltaMS){

  var sliderScale = 100;
  sigma  = document.getElementById( "sigma" ).value / sliderScale;
  radius = document.getElementById( "radius" ).value / sliderScale;

  vp_size = [canvas.width, canvas.height];
  camera.Update( vp_size );
      
  gl.enable( gl.DEPTH_TEST );
  gl.clearColor( 0.0, 0.0, 0.0, 1.0 );
  gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );

  // set up framebuffer
  gl.bindFramebuffer( gl.FRAMEBUFFER, blurFB[0] );
  gl.viewport( 0, 0, blurFB[0].width, blurFB[0].height );
  gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );

  // setup view projection and model
  var prjMat = camera.Perspective();
  var viewMat = camera.LookAt();
  var modelMat = RotateAxis( IdentM44(), Fract( deltaMS / 13000.0 ) * 2.0 * Math.PI, 0 );
  modelMat = RotateAxis( modelMat, Fract( deltaMS / 17000.0 ) * 2.0 * Math.PI, 1 );
  
  // set up draw shader
  ShProg.Use( progDraw );
  ShProg.SetM44( progDraw, "u_projectionMat44", prjMat );
  ShProg.SetM44( progDraw, "u_modelViewMat44", Multiply(viewMat, modelMat) );
  ShProg.SetF1( progDraw, "u_shininess", shininess );
  
  // draw scene
  VertexBuffer.Draw( bufCube );

  // set blur-X framebuffer and bind frambuffer texture
  gl.bindFramebuffer( gl.FRAMEBUFFER, blurFB[1] );
  gl.viewport( 0, 0, blurFB[1].width, blurFB[1].height );
  gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
  var texUnit = 1;
  gl.activeTexture( gl.TEXTURE0 + texUnit );
  gl.bindTexture( gl.TEXTURE_2D, blurFB[0].color0_texture );

  // set up blur-X shader
  ShProg.Use( progBlur );
  ShProg.SetI1( progBlur, "u_texture", texUnit )
  ShProg.SetF2( progBlur, "u_textureSize", vp_size );
  ShProg.SetF1( progBlur, "u_sigma", sigma )
  ShProg.SetF1( progBlur, "u_radius", radius )
  ShProg.SetF2( progBlur, "u_dir", [1.0, 0.0] )

  // draw full screen space
  gl.enableVertexAttribArray( progBlur.inPos );
  gl.bindBuffer( gl.ARRAY_BUFFER, bufQuad.pos );
  gl.vertexAttribPointer( progBlur.inPos, 2, gl.FLOAT, false, 0, 0 ); 
  gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufQuad.inx );
  gl.drawElements( gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 );
  gl.disableVertexAttribArray( progBlur.inPos );

  // reset framebuffer and bind frambuffer texture
  gl.bindFramebuffer( gl.FRAMEBUFFER, null );
  gl.viewport( 0, 0, vp_size[0], vp_size[1] );
  gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
  texUnit = 2;
  gl.activeTexture( gl.TEXTURE0 + texUnit );
  gl.bindTexture( gl.TEXTURE_2D, blurFB[1].color0_texture );

  // set up pst process shader
  ShProg.SetI1( progBlur, "u_texture", texUnit )
  ShProg.SetF1( progBlur, "u_radius", radius )
  ShProg.SetF2( progBlur, "u_dir", [0.0, 1.0] )

  // draw full screen space
  gl.enableVertexAttribArray( progBlur.inPos );
  gl.bindBuffer( gl.ARRAY_BUFFER, bufQuad.pos );
  gl.vertexAttribPointer( progBlur.inPos, 2, gl.FLOAT, false, 0, 0 ); 
  gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufQuad.inx );
  gl.drawElements( gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 );
  gl.disableVertexAttribArray( progBlur.inPos );

  requestAnimationFrame(render);
}

function initScene() {

  canvas = document.getElementById( "canvas");
  gl = canvas.getContext( "experimental-webgl" );
  if ( !gl )
      return null;

  progDraw = ShProg.Create( 
  [ { source : "draw-shader-vs", stage : gl.VERTEX_SHADER },
      { source : "draw-shader-fs", stage : gl.FRAGMENT_SHADER }
  ] );
  if ( !progDraw.progObj )
      return null;
  progDraw.inPos = gl.getAttribLocation( progDraw.progObj, "inPos" );
  progDraw.inNV  = gl.getAttribLocation( progDraw.progObj, "inNV" );
  progDraw.inCol = gl.getAttribLocation( progDraw.progObj, "inCol" );

  progBlur = ShProg.Create( 
  [ { source : "post-shader-vs", stage : gl.VERTEX_SHADER },
      { source : "blur-shader-fs", stage : gl.FRAGMENT_SHADER }
  ] );
  progBlur.inPos = gl.getAttribLocation( progBlur.progObj, "inPos" );
  if ( !progBlur.progObj )
      return;    
  
  // create cube
  var cubePos = [
  -1.0, -1.0,  1.0,  1.0, -1.0,  1.0,  1.0,  1.0,  1.0, -1.0,  1.0,  1.0,
  -1.0, -1.0, -1.0,  1.0, -1.0, -1.0,  1.0,  1.0, -1.0, -1.0,  1.0, -1.0 ];
  var cubeCol = [ 1.0, 0.0, 0.0, 1.0, 0.5, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0 ];
  var cubeHlpInx = [ 0, 1, 2, 3, 1, 5, 6, 2, 5, 4, 7, 6, 4, 0, 3, 7, 3, 2, 6, 7, 1, 0, 4, 5 ];  
  var cubePosData = [];
  for ( var i = 0; i < cubeHlpInx.length; ++ i ) {
  cubePosData.push( cubePos[cubeHlpInx[i]*3], cubePos[cubeHlpInx[i]*3+1], cubePos[cubeHlpInx[i]*3+2] );
  }
  var cubeNVData = [];
  for ( var i1 = 0; i1 < cubeHlpInx.length; i1 += 4 ) {
  var nv = [0, 0, 0];
  for ( i2 = 0; i2 < 4; ++ i2 ) {
      var i = i1 + i2;
      nv[0] += cubePosData[i*3]; nv[1] += cubePosData[i*3+1]; nv[2] += cubePosData[i*3+2];
  }
  for ( i2 = 0; i2 < 4; ++ i2 )
  cubeNVData.push( nv[0], nv[1], nv[2] );
  }
  var cubeColData = [];
  for ( var is = 0; is < 6; ++ is ) {
  for ( var ip = 0; ip < 4; ++ ip ) {
      cubeColData.push( cubeCol[is*3], cubeCol[is*3+1], cubeCol[is*3+2] ); 
  }
  }
  var cubeInxData = [];
  for ( var i = 0; i < cubeHlpInx.length; i += 4 ) {
  cubeInxData.push( i, i+1, i+2, i, i+2, i+3 );   
  }
  bufCube = VertexBuffer.Create(
  [ { data : cubePosData, attrSize : 3, attrLoc : progDraw.inPos },
  { data : cubeNVData,  attrSize : 3, attrLoc : progDraw.inNV },
  { data : cubeColData, attrSize : 3, attrLoc : progDraw.inCol } ],
  cubeInxData );

  bufQuad.pos = gl.createBuffer();
  gl.bindBuffer( gl.ARRAY_BUFFER, bufQuad.pos );
  gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( [ -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0 ] ), gl.STATIC_DRAW );
  bufQuad.inx = gl.createBuffer();
  gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bufQuad.inx );
  gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( [ 0, 1, 2, 0, 2, 3 ] ), gl.STATIC_DRAW );  
  
  camera = new Camera( [0, 3, 0.0], [0, 0, 0], [0, 0, 1], 90, vp_size, 0.5, 100 );

  window.onresize = resize;
  resize();
  requestAnimationFrame(render);
}

function resize() {
  //vp_size = [gl.drawingBufferWidth, gl.drawingBufferHeight];
  vp_size = [window.innerWidth, window.innerHeight]
  //vp_size = [256, 256]
  canvas.width = vp_size[0];
  canvas.height = vp_size[1];

  var fbsize = Math.max(vp_size[0], vp_size[1]);
  fbsize = 1 << 31 - Math.clz32(fbsize); // nearest power of 2

  blurFB = [];
  for ( var i = 0; i < 2; ++ i ) {
    fb = gl.createFramebuffer();
    fb.width = fbsize;
    fb.height = fbsize;
    gl.bindFramebuffer( gl.FRAMEBUFFER, fb );
    fb.color0_texture = gl.createTexture();
    gl.bindTexture( gl.TEXTURE_2D, fb.color0_texture );
    gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST );
    gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST );
    gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, fb.width, fb.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null );
    fb.renderbuffer = gl.createRenderbuffer();
    gl.bindRenderbuffer( gl.RENDERBUFFER, fb.renderbuffer );
    gl.renderbufferStorage( gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, fb.width, fb.height );
    gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, fb.color0_texture, 0 );
    gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, fb.renderbuffer );
    gl.bindTexture( gl.TEXTURE_2D, null );
    gl.bindRenderbuffer( gl.RENDERBUFFER, null );
    gl.bindFramebuffer( gl.FRAMEBUFFER, null );
    blurFB.push( fb );
  }
}

function Fract( val ) { 
  return val - Math.trunc( val );
}
function CalcAng( deltaTime, intervall ) {
  return Fract( deltaTime / (1000*intervall) ) * 2.0 * Math.PI;
}
function CalcMove( deltaTime, intervall, range ) {
  var pos = self.Fract( deltaTime / (1000*intervall) ) * 2.0
  var pos = pos < 1.0 ? pos : (2.0-pos)
  return range[0] + (range[1] - range[0]) * pos;
}    
function EllipticalPosition( a, b, angRag ) {
  var a_b = a * a - b * b
  var ea = (a_b <= 0) ? 0 : Math.sqrt( a_b );
  var eb = (a_b >= 0) ? 0 : Math.sqrt( -a_b );
  return [ a * Math.sin( angRag ) - ea, b * Math.cos( angRag ) - eb, 0 ];
}

function IdentM44() {
  return [ 1, 0, 0, 0,    0, 1, 0, 0,    0, 0, 1, 0,    0, 0, 0, 1 ];
};

function RotateAxis(matA, angRad, axis) {
  var aMap = [ [1, 2], [2, 0], [0, 1] ];
  var a0 = aMap[axis][0], a1 = aMap[axis][1]; 
  var sinAng = Math.sin(angRad), cosAng = Math.cos(angRad);
  var matB = matA.slice(0);
  for ( var i = 0; i < 3; ++ i ) {
      matB[a0*4+i] = matA[a0*4+i] * cosAng + matA[a1*4+i] * sinAng;
      matB[a1*4+i] = matA[a0*4+i] * -sinAng + matA[a1*4+i] * cosAng;
  }
  return matB;
}

function Rotate(matA, angRad, axis) {
  var s = Math.sin(angRad), c = Math.cos(angRad);
  var x = axis[0], y = axis[1], z = axis[2]; 
  matB = [
      x*x*(1-c)+c,   x*y*(1-c)-z*s, x*z*(1-c)+y*s, 0,
      y*x*(1-c)+z*s, y*y*(1-c)+c,   y*z*(1-c)-x*s, 0,
      z*x*(1-c)-y*s, z*y*(1-c)+x*s, z*z*(1-c)+c,   0,
      0,             0,             0,             1 ];
  return Multiply(matA, matB);
}    

function Multiply(matA, matB) {
  matC = IdentM44();
  for (var i0=0; i0<4; ++i0 )
      for (var i1=0; i1<4; ++i1 )
          matC[i0*4+i1] = matB[i0*4+0] * matA[0*4+i1] + matB[i0*4+1] * matA[1*4+i1] + matB[i0*4+2] * matA[2*4+i1] + matB[i0*4+3] * matA[3*4+i1]  
  return matC;
}

function Cross( a, b ) { return [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0], 0.0 ]; }
function Dot( a, b ) { return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]; }
function Normalize( v ) {
  var len = Math.sqrt( v[0] * v[0] + v[1] * v[1] + v[2] * v[2] );
  return [ v[0] / len, v[1] / len, v[2] / len ];
}

Camera = function( pos, target, up, fov_y, vp, near, far ) {
this.Time = function() { return Date.now(); }
this.pos = pos;
this.target = target;
this.up = up;
this.fov_y = fov_y;
this.vp = vp;
this.near = near;
this.far = far;
this.Perspective = function() {
    var n = this.near;
    var f = this.far;
    var fn = f + n;
    var f_n = f - n;
    var r = this.vp[0] / this.vp[1];
    var t = 1 / Math.tan( Math.PI * this.fov_y / 360 );
    return [
        t/r, 0, 0,          0,
        0,   t, 0,          0,
        0,   0, -fn/f_n,   -1,
        0,   0, -2*f*n/f_n, 0 ];
}; 
this.LookAt = function() {
    var mz = Normalize( [ this.pos[0]-this.target[0], this.pos[1]-this.target[1], this.pos[2]-this.target[2] ] );
    var mx = Normalize( Cross( this.up, mz ) );
    var my = Normalize( Cross( mz, mx ) );
    var tx = Dot( mx, this.pos );
    var ty = Dot( my, this.pos );
    var tz = Dot( [-mz[0], -mz[1], -mz[2]], this.pos ); 
    return [mx[0], my[0], mz[0], 0, mx[1], my[1], mz[1], 0, mx[2], my[2], mz[2], 0, tx, ty, tz, 1]; 
};
this.Update = function(vp_size) {
    if (vp_size)
        this.vp = vp_size;
};
}

var ShProg = {
Create: function (shaderList) {
  var shaderObjs = [];
  for (var i_sh = 0; i_sh < shaderList.length; ++i_sh) {
    var shderObj = this.Compile(shaderList[i_sh].source, shaderList[i_sh].stage);
    if (shderObj) shaderObjs.push(shderObj);
  }
  var prog = {}
  prog.progObj = this.Link(shaderObjs)
  if (prog.progObj) {
    prog.attrInx = {};
    var noOfAttributes = gl.getProgramParameter(prog.progObj, gl.ACTIVE_ATTRIBUTES);
    for (var i_n = 0; i_n < noOfAttributes; ++i_n) {
      var name = gl.getActiveAttrib(prog.progObj, i_n).name;
      prog.attrInx[name] = gl.getAttribLocation(prog.progObj, name);
    }
    prog.uniLoc = {};
    var noOfUniforms = gl.getProgramParameter(prog.progObj, gl.ACTIVE_UNIFORMS);
    for (var i_n = 0; i_n < noOfUniforms; ++i_n) {
      var name = gl.getActiveUniform(prog.progObj, i_n).name;
      prog.uniLoc[name] = gl.getUniformLocation(prog.progObj, name);
    }
  }
  return prog;
},
AttrI: function (prog, name) { return prog.attrInx[name]; },
UniformL: function (prog, name) { return prog.uniLoc[name]; },
Use: function (prog) { gl.useProgram(prog.progObj); },
SetI1: function (prog, name, val) { if (prog.uniLoc[name]) gl.uniform1i(prog.uniLoc[name], val); },
SetF1: function (prog, name, val) { if (prog.uniLoc[name]) gl.uniform1f(prog.uniLoc[name], val); },
SetF2: function (prog, name, arr) { if (prog.uniLoc[name]) gl.uniform2fv(prog.uniLoc[name], arr); },
SetF3: function (prog, name, arr) { if (prog.uniLoc[name]) gl.uniform3fv(prog.uniLoc[name], arr); },
SetF4: function (prog, name, arr) { if (prog.uniLoc[name]) gl.uniform4fv(prog.uniLoc[name], arr); },
SetM33: function (prog, name, mat) { if (prog.uniLoc[name]) gl.uniformMatrix3fv(prog.uniLoc[name], false, mat); },
SetM44: function (prog, name, mat) { if (prog.uniLoc[name]) gl.uniformMatrix4fv(prog.uniLoc[name], false, mat); },
Compile: function (source, shaderStage) {
  var shaderScript = document.getElementById(source);
  if (shaderScript)
    source = shaderScript.text;
  var shaderObj = gl.createShader(shaderStage);
  gl.shaderSource(shaderObj, source);
  gl.compileShader(shaderObj);
  var status = gl.getShaderParameter(shaderObj, gl.COMPILE_STATUS);
  if (!status) alert(gl.getShaderInfoLog(shaderObj));
  return status ? shaderObj : null;
},
Link: function (shaderObjs) {
  var prog = gl.createProgram();
  for (var i_sh = 0; i_sh < shaderObjs.length; ++i_sh)
    gl.attachShader(prog, shaderObjs[i_sh]);
  gl.linkProgram(prog);
  status = gl.getProgramParameter(prog, gl.LINK_STATUS);
  if ( !status ) alert(gl.getProgramInfoLog(prog));
  return status ? prog : null;
} };

var VertexBuffer = {
Create: function(attribs, indices, type) {
  var buffer = { buf: [], attr: [], inx: gl.createBuffer(), inxLen: indices.length, primitive_type: type ? type : gl.TRIANGLES };
  for (var i=0; i<attribs.length; ++i) {
    buffer.buf.push(gl.createBuffer());
    buffer.attr.push({ size : attribs[i].attrSize, loc : attribs[i].attrLoc, no_of: attribs[i].data.length/attribs[i].attrSize });
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer.buf[i]);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array( attribs[i].data ), gl.STATIC_DRAW);
  }
  gl.bindBuffer(gl.ARRAY_BUFFER, null);
  if ( buffer.inxLen > 0 ) {
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer.inx);
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( indices ), gl.STATIC_DRAW);
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
  }
  return buffer;
},
Draw: function(bufObj) {
  for (var i=0; i<bufObj.buf.length; ++i) {
    gl.bindBuffer(gl.ARRAY_BUFFER, bufObj.buf[i]);
    gl.vertexAttribPointer(bufObj.attr[i].loc, bufObj.attr[i].size, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray( bufObj.attr[i].loc);
  }
  if ( bufObj.inxLen > 0 ) {
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, bufObj.inx);
    gl.drawElements(bufObj.primitive_type, bufObj.inxLen, gl.UNSIGNED_SHORT, 0);
    gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, null );
  }
  else
    gl.drawArrays(bufObj.primitive_type, 0, bufObj.attr[0].no_of );
  for (var i=0; i<bufObj.buf.length; ++i)
    gl.disableVertexAttribArray(bufObj.attr[i].loc);
  gl.bindBuffer( gl.ARRAY_BUFFER, null );
} };

initScene();

})();
html,body { margin: 0; overflow: hidden; }
#gui { position : absolute; top : 0; left : 0; }
<script id="draw-shader-vs" type="x-shader/x-vertex">
precision mediump float;

attribute vec3 inPos;
attribute vec3 inNV;
attribute vec3 inCol;

varying vec3 vertPos;
varying vec3 vertNV;
varying vec3 vertCol;

uniform mat4 u_projectionMat44;
uniform mat4 u_modelViewMat44;

void main()
{
    vertNV      = mat3( u_modelViewMat44 ) * normalize( inNV );
    vertCol     = inCol;
    vec4 pos    = u_modelViewMat44 * vec4( inPos, 1.0 );
    vertPos     = pos.xyz / pos.w;
    gl_Position = u_projectionMat44 * pos;
}
</script>

<script id="draw-shader-fs" type="x-shader/x-fragment">
precision mediump float;

varying vec3 vertPos;
varying vec3 vertNV;
varying vec3 vertCol;

uniform float u_shininess;

void main()
{
    vec3 color = vertCol;
    vec3  normalV  = normalize( vertNV );
    vec3  eyeV     = normalize( -vertPos );
    vec3  halfV    = normalize( eyeV + normalV );
    float NdotH    = max( 0.0, dot( normalV, halfV ) );
    float shineFac = ( u_shininess + 2.0 ) * pow( NdotH, u_shininess ) / ( 2.0 * 3.14159265 );
    gl_FragColor   = vec4( color.rgb * (0.2 + NdotH), 1.0 );
} 
</script>

<script id="post-shader-vs" type="x-shader/x-vertex">
precision mediump float;

attribute vec2 inPos;

varying   vec2 pos;

void main()
{
    pos = inPos;
    gl_Position = vec4( inPos, 0.0, 1.0 );
}
</script>

<script id="blur-shader-fs" type="x-shader/x-fragment">
precision mediump float;

varying vec2 pos;

uniform sampler2D u_texture;
uniform vec2      u_textureSize;
uniform float     u_sigma;
uniform float     u_radius;
uniform vec2      u_dir;

float CalcGauss( float x, float sigma )
{
    if ( sigma <= 0.0 )
        return 0.0;
  return exp( -(x*x) / (2.0 * sigma) ) / (2.0 * 3.14157 * sigma);
}

void main()
{
    vec2 texC     = pos.st * 0.5 + 0.5;
    vec4 texCol   = texture2D( u_texture, texC );
    vec4 gaussCol = vec4( texCol.rgb, 1.0 );
    vec2 step     = u_dir / u_textureSize;
    for ( int i = 1; i <= 32; ++ i )
    {
        float weight = CalcGauss( float(i) / 32.0, u_sigma * 0.5 );
        if ( weight < 1.0/255.0 )
            break;
        texCol    = texture2D( u_texture, texC + u_radius * step * float(i) );
        gaussCol += vec4( texCol.rgb * weight, weight );
        texCol    = texture2D( u_texture, texC - u_radius * step * float(i) );
        gaussCol += vec4( texCol.rgb * weight, weight );
    }
    gaussCol.rgb = clamp( gaussCol.rgb / gaussCol.w, 0.0, 1.0 );
    gl_FragColor = vec4( gaussCol.rgb, 1.0 );
}
</script>

<div>
<form id="gui" name="inputs">
<table>
<tr> <td> <font color= #CCF>radius</font> </td> 
        <td> <input type="range" id="radius" min="1" max="1000" value="200"/></td> </tr>
<tr> <td> <font color= #CCF>blur</font> </td> 
        <td> <input type="range" id="sigma" min="1" max="50" value="10"/></td> </tr>
</table>
</form>
</div>

<canvas id="canvas" style="border: none;"></canvas>



回答2:

In general you need to draw the scene you want to blur to a frame buffer object (FBO) with attached texture.

  • Create a frame buffer
  • Create an empty texture (data parameter should be null)
  • Bind frame buffer and texture
  • Attach the texture to frame buffer as color

At this point the rest of the drawing should be exactly the same as on your main buffer but make sure you setup a correct viewport. This procedure will make you draw to the texture.

Now that you have a texture with your scene you need to go to the same procedure as is for blurring an image.

  • Bind your main buffer (usually indexed 0)
  • Bind texture
  • Draw the texture to main buffer with blur shader

You can then optimize it with horizontal and vertical blur shaders having 2 calls which uses yet another FBO...

So I would try doing some steps in your application:

  • Create a scene which draws and shows a triangle
  • Create a FBO, draw to it and draw the FBO texture on your main buffer
  • Create a scene which draws and shows a blurred image through texture
  • Create a FBO on which the scene is drawn then draws a blurred texture from FBO on the main buffer

If you find yourself in trouble at any of these points you might want to ask a specific question about it.