Lire une texture et écrire son contenu dans une autre

le but de cette partie est d'expliquer le concept de "binding".

le code complet de l'exemple


"use strict";

let lx=4;
let ly=2;
const canvas = document.createElement('canvas');
document.body.appendChild(canvas);
canvas.width = lx;
canvas.height=ly;
const gl = canvas.getContext('webgl');
gl.getExtension('OES_texture_float');

let vertex = `
  attribute vec2 vertexPosition;
  void main() {
    gl_Position = vec4(vertexPosition, 1, 1);
  }
`;

let fragment = `
  #ifdef GL_FRAGMENT_PRECISION_HIGH
  precision highp float;
  #else
  precision mediump float;
  #endif

  uniform sampler2D texture;
  
  void main() {

    vec4 data = texture2D(
        texture ,
        gl_FragCoord.xy  / vec2(${lx},${ly})
    );
    gl_FragColor=data;

  }
`;
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertex);
gl.compileShader(vertexShader);
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
  throw new Error(gl.getShaderInfoLog(vertexShader))
};

const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragment);
gl.compileShader(fragmentShader);
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
  throw new Error(gl.getShaderInfoLog(fragmentShader))
};

const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
  throw new Error(gl.getProgramInfoLog(program))
};

gl.useProgram(program);


let vertexPositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, 
  new Float32Array([
    -1, -1,
    +1, -1,
    -1, +1,
    -1, +1,
    +1, -1,
    +1, +1
  ]),gl.STATIC_DRAW
);

const vertexPositionAttributeLocation = gl.getAttribLocation( program,"vertexPosition");
gl.enableVertexAttribArray(vertexPositionAttributeLocation);
gl.vertexAttribPointer(vertexPositionAttributeLocation, 2, gl.FLOAT, false, 0, 0);



//gl.activeTexture(gl.TEXTURE1);
//gl.bindTexture(gl.TEXTURE_2D, targetTexture);
//let textureUniformLocation = gl.getUniformLocation(program,"texture");
//gl.uniform1i(textureUniformLocation, 1);



//gl.activeTexture(gl.TEXTURE0);

let targetTexture2 = gl.createTexture();
//gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, targetTexture2);
gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,lx,ly,0,gl.RGBA,gl.FLOAT,null);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
      
let fbEcran = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER,fbEcran);
gl.framebufferTexture2D(
  gl.FRAMEBUFFER,
  gl.COLOR_ATTACHMENT0,
  gl.TEXTURE_2D,
  targetTexture2,
  0
);

//gl.uniform1i(textureUniformLocation, 0);

let targetTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, targetTexture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
let fuck= new Float32Array(
  [ 10, 10, 10, 255,  20, 20, 20, 255,  30, 30, 30, 255,     40, 40, 40, 255, 
    50, 50, 50, 255,  60, 60, 60, 255,  70, 70, 70, 255,     80, 80, 80, 255 
    ]
  );
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, lx, ly, 0, gl.RGBA, gl.FLOAT, fuck);

gl.drawArrays(gl.TRIANGLES, 0, 6);

let data = new Float32Array(4 * gl.canvas.width * gl.canvas.height);
gl.readPixels(0, 0, gl.canvas.width, gl.canvas.height, gl.RGBA, gl.FLOAT, data);

console.log(data);

Maintenant j'explique le code

création du contexte WebGL


    let lx=4;
    let ly=2;
    const canvas = document.createElement('canvas');
    document.body.appendChild(canvas);
    canvas.width = lx;
    canvas.height = ly;
    const gl = canvas.getContext('webgl');
  

ajout d'une extension pour stocker dans les textures les valeurs des couleurs (R,G,B et A) au format "32bit float formats". C'est à dire non restreintes à l'intervalle [0,1].


    gl.getExtension('OES_texture_float');
  

le shader de vertex et le shader de fragment


let vertex = `
  attribute vec2 vertexPosition;
  void main() {
    gl_Position = vec4(vertexPosition, 1, 1);
  }
`;

let fragment = ` 
 #ifdef GL_FRAGMENT_PRECISION_HIGH
  precision highp float;
#else
  precision mediump float;
#endif

  uniform sampler2D texture;
  
  void main() {

    vec4 data = texture2D(
        texture ,
        gl_FragCoord.xy  / vec2(${lx},${ly})
        );

        gl_FragColor=data;

  }
`;

compilation du vertex et du shader


  const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertex);
gl.compileShader(vertexShader);
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
  throw new Error(gl.getShaderInfoLog(vertexShader))
};

const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragment);
gl.compileShader(fragmentShader);
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
  throw new Error(gl.getShaderInfoLog(fragmentShader))
};

const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
  throw new Error(gl.getProgramInfoLog(program))
};

un buffer qui contiendra les coordonnées des 6 points des 2 triangles


let vertexPositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, 
  new Float32Array([
    -1, -1,
    +1, -1,
    -1, +1,
    -1, +1,
    +1, -1,
    +1, +1
  ]),gl.STATIC_DRAW
);

On crée une texture de taille 4x2. On donne à ses 8 points les valeurs R,G,B et A.

  
let targetTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, targetTexture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
let fuck= new Float32Array(
  [ 10, 10, 10, 255,  20, 20, 20, 255,  30, 30, 30, 255,     40, 40, 40, 255, 
    50, 50, 50, 255,  60, 60, 60, 255,  70, 70, 70, 255,     80, 80, 80, 255 
    ]
  );
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, lx, ly, 0, gl.RGBA, gl.FLOAT, fuck);
    
  

On établit le lien entre ce buffer et la variable de type 'attribute'


const vertexPositionAttributeLocation = gl.getAttribLocation( program,"vertexPosition");
gl.enableVertexAttribArray(vertexPositionAttributeLocation);
gl.vertexAttribPointer(vertexPositionAttributeLocation, 2, gl.FLOAT, false, 0, 0);

création d'une texture, elle contiendra le résultat de gl.drawArrays(...)


let targetTexture2 = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, targetTexture2);
gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,lx,ly,0,gl.RGBA,gl.FLOAT,null);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);

On attache cette texture à un framebuffer, fbEcran. Par défaut le framebuffer est null, c'est à dire que la zone d'affichage sera l'écran. Ici on veut que l'affichage se fasse dans une texture.

  
      let fbEcran = gl.createFramebuffer();
  gl.bindFramebuffer(gl.FRAMEBUFFER,fbEcran);
  gl.framebufferTexture2D(
          gl.FRAMEBUFFER,
          gl.COLOR_ATTACHMENT0,
          gl.TEXTURE_2D,
          targetTexture2,
          0
  );
  

On dessine.

  
    gl.drawArrays(gl.TRIANGLES, 0, 6);

  

Comme l'affichage ne se fait pas sur l'écran mais dans une texture (via un framebuffer). On lit le contenu de cette texture avec l'instruction gl.readPixels(...)

  
    let data = new Float32Array(4 * gl.canvas.width * gl.canvas.height);
gl.readPixels(0, 0, gl.canvas.width, gl.canvas.height, gl.RGBA, gl.FLOAT, data);

console.log(data);
  

'use strict';

let lx=4;
let ly=2;

const canvas = document.querySelector('canvas');
canvas.width = lx;
canvas.height=ly;
const gl = canvas.getContext('webgl');

const vsGLSL = `
  attribute vec2 vertexPosition;
  void main() {
    gl_Position = vec4(vertexPosition, 1, 1);
  }
`;

const fsGLSL = `
 
 #ifdef GL_FRAGMENT_PRECISION_HIGH
  precision highp float;
#else
  precision mediump float;
#endif

  uniform sampler2D texture;
  
  void main() {

    vec4 data = texture2D(
        texture ,
        gl_FragCoord.xy  / vec2(${lx},${ly})
        );

        gl_FragColor=data;

  }
`;
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vsGLSL);
gl.compileShader(vertexShader);
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
  throw new Error(gl.getShaderInfoLog(vertexShader))
};

const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fsGLSL);
gl.compileShader(fragmentShader);
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
  throw new Error(gl.getShaderInfoLog(fragmentShader))
};

const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
  throw new Error(gl.getProgramInfoLog(program))
};


gl.detachShader(program, vertexShader);
gl.deleteShader(vertexShader);
gl.detachShader(program, fragmentShader);
gl.deleteShader(fragmentShader);

gl.useProgram(program);


const vertexPositionAttributeLocation = gl.getAttribLocation( program,"vertexPosition");

let vertexPositionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, 
  new Float32Array([
    -1, -1,
     1, -1,
    -1,  1,
    -1,  1,
     1, -1,
     1,  1
  ]),
  gl.STATIC_DRAW);

gl.enableVertexAttribArray(vertexPositionAttributeLocation);
gl.vertexAttribPointer(vertexPositionAttributeLocation, 2, gl.FLOAT, false, 0, 0);


        /*
        gl = {
          activeTextureUnit: 0,
          textureUnits: []
        }
        */



let textureCopie = gl.createTexture();

gl.bindTexture(gl.TEXTURE_2D, textureCopie);

        /*
        gl = {
          activeTextureUnit: 0,
          textureUnits:
            [
              {texture2D: textureCopie},
            ]
        }
        */


gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,lx,ly,0,gl.RGBA,gl.UNSIGNED_BYTE,null);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
      
let fbEcran = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER,fbEcran);
        /*
        gl = {
          activeTextureUnit: 0,
          textureUnits:
            [
              {texture2D: textureCopie},
            ],
          framebufferBinding: fbEcran

        }
        */

gl.framebufferTexture2D(
        gl.FRAMEBUFFER,
        gl.COLOR_ATTACHMENT0,
        gl.TEXTURE_2D,
        textureCopie,
        0
);

        /*
        gl = {
          activeTextureUnit: 0,
          textureUnits:
            [
              {texture2D: textureCopie},
            ],
          framebufferBinding: fbEcran

        }
        framebuffer[fbEcran]= {
          attachmentPoint: COLOR_ATTACHMENT0,
          attachment: textureCopie
        }
        */


let textureOriginal = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, textureOriginal);

/*
gl = {
  activeTextureUnit: 0,
  textureUnits:
    [
      {texture2D: textureOriginal},
    ],
  framebufferBinding: fbEcran

}
framebuffer[fbEcran]= {
  attachmentPoint: COLOR_ATTACHMENT0,
  attachment: textureCopie
}
*/


gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
let couleurs= new Uint8Array(
  [ 10, 10, 10, 255,  20, 20, 20, 255,  30, 30, 30, 255,     40, 40, 40, 255, 
    50, 50, 50, 255,  60, 60, 60, 255,  70, 70, 70, 255,     80, 80, 80, 255 
    ]
  );
gl.texImage2D(
  gl.TEXTURE_2D,
  0,
  gl.RGBA,
  lx,
  ly,
  0,
  gl.RGBA,
  gl.UNSIGNED_BYTE,
  couleurs);


the texture affected is the current active texture
on the specified bing point
*/

/*
gl = {
  activeTextureUnit: 0,
  textureUnits:
    [
      {texture2D: textureOriginal},
    ],
  framebufferBinding: fbEcran

}
framebuffer[fbEcran]= {
  attachmentPoint: COLOR_ATTACHMENT0,
  attachment: textureCopie
}

texture[textureOriginal]={
  mips: [50,50,50,255,.....]
}
*/


gl.drawArrays(gl.TRIANGLES, 0, 6);