1

I am using webgl and what I want to do is first render to a texture, then use that texture to render on screen, and I encounter a problem with an attribute in the first step render.

I will try to explain in a few words what I am trying to do. First I want to use a fragment shader to render to a texture something that uses an attribute, then use that texture to render to screen, and repeat the steps for the next frame. But when I try to render with the framebuffer bound that attribute (pos) has invalid values, I mean, no value at all.

I wrote a little demo that shows my problem:

var canvas = document.getElementById("c");
var gl = getWebGLContext(canvas); 

var program_init, program_pers, tex, fb;

function init() {
 var verts = [
    1,  1,
   -1,  1,
   -1, -1,
    1,  1,
   -1, -1,
    1, -1,
 ];
    
    var vertBuffer = gl.createBuffer();
 gl.bindBuffer(gl.ARRAY_BUFFER, vertBuffer);
 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verts), gl.STATIC_DRAW);
 gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
 gl.enableVertexAttribArray(0);
    
    tex = gl.createTexture();
    fb = gl.createFramebuffer();

    program_init = createProgramFromScripts(gl, ["vshader-init", "fshader-init"], ["pos"]);
    program_pers = createProgramFromScripts(gl, ["vshader-pers", "fshader-pers"], ["a_position"]);
}

function renderToTexture(gl, time) {
 gl.useProgram(program_init);
 
 var timeLocation = gl.getUniformLocation(program_init, 'time');
 gl.uniform1f(timeLocation, time);

 gl.bindTexture(gl.TEXTURE_2D, tex);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);

 gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
 gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);

 gl.drawArrays(gl.TRIANGLES, 0, 6);
    
 gl.bindFramebuffer(gl.FRAMEBUFFER, null);
};

function renderToScreen(gl) {
 gl.useProgram(program_pers);
 gl.drawArrays(gl.TRIANGLES, 0, 6);
}; 

init();
requestAnimationFrame(function(){
 renderToTexture(gl, arguments[0]);
 renderToScreen(gl);
 
 requestAnimationFrame( arguments.callee );
});
canvas { border: 1px solid black; }
body { background-color: darkslategrey }
<script src="http://greggman.github.com/webgl-fundamentals/webgl/resources/webgl-utils.js"></script>

<script id="vshader-init" type="vs/shader">
attribute vec4 pos;
varying vec2 uv;

void main() {
 gl_Position = pos;
 uv = pos.xy * .5 + .5;
}    
</script>
<script id="fshader-init" type="fs/shader">
precision mediump float;
varying vec2 uv;

uniform float time;

void main() {
 float t = floor(time / 1000.);
 vec3 color;
 color.x = sin(t*uv.x);
 color.y = tan(t*uv.y);
 color.z = cos(t);
 
    gl_FragColor = vec4(color, 1.);
}
</script>

<script id="vshader-pers" type="vs/shader">
attribute vec4 a_position;
varying vec2 v_texcoord;

void main() {
 gl_Position = a_position;
 v_texcoord = a_position.xy * .5 + .5;
}    
</script>
<script id="fshader-pers" type="fs/sahder">
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D u_sampler;
void main() {
 gl_FragColor = texture2D(u_sampler, v_texcoord);
}
</script>

<body>
 <canvas id="c" width="400" height="400"></canvas>
</body>

If I comment the line 38,39 from the javascript code (the binding of the frameBuffer) and line 54 (the rendering to screen code, that runs a different program) we can see that it renders corectly and the name "pos" is given the right values.

var canvas = document.getElementById("c");
var gl = getWebGLContext(canvas); 

var program_init, program_pers, tex, fb;

function init() {
 var verts = [
    1,  1,
   -1,  1,
   -1, -1,
    1,  1,
   -1, -1,
    1, -1,
 ];
    
    var vertBuffer = gl.createBuffer();
 gl.bindBuffer(gl.ARRAY_BUFFER, vertBuffer);
 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verts), gl.STATIC_DRAW);
 gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
 gl.enableVertexAttribArray(0);
    
    tex = gl.createTexture();
    fb = gl.createFramebuffer();

    program_init = createProgramFromScripts(gl, ["vshader-init", "fshader-init"], ["pos"]);
    program_pers = createProgramFromScripts(gl, ["vshader-pers", "fshader-pers"], ["a_position"]);
}

function renderToTexture(gl, time) {
 gl.useProgram(program_init);
 
 var timeLocation = gl.getUniformLocation(program_init, 'time');
 gl.uniform1f(timeLocation, time);

 gl.bindTexture(gl.TEXTURE_2D, tex);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);

 //gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
 //gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);

 gl.drawArrays(gl.TRIANGLES, 0, 6);
    
 gl.bindFramebuffer(gl.FRAMEBUFFER, null);
};

function renderToScreen(gl) {
 gl.useProgram(program_pers);
 gl.drawArrays(gl.TRIANGLES, 0, 6);
}; 

init();
requestAnimationFrame(function(){
 renderToTexture(gl, arguments[0]);
 //renderToScreen(gl);
 
 requestAnimationFrame( arguments.callee );
});
canvas { border: 1px solid black; }
body { background-color: darkslategrey }
<script src="http://greggman.github.com/webgl-fundamentals/webgl/resources/webgl-utils.js"></script>

<script id="vshader-init" type="vs/shader">
attribute vec4 pos;
varying vec2 uv;

void main() {
 gl_Position = pos;
 uv = pos.xy * .5 + .5;
}    
</script>
<script id="fshader-init" type="fs/shader">
precision mediump float;
varying vec2 uv;

uniform float time;

void main() {
 float t = floor(time / 1000.);
 vec3 color;
 color.x = sin(t*uv.x);
 color.y = tan(t*uv.y);
 color.z = cos(t);
 
    gl_FragColor = vec4(color, 1.);
}
</script>

<script id="vshader-pers" type="vs/shader">
attribute vec4 a_position;
varying vec2 v_texcoord;

void main() {
 gl_Position = a_position;
 v_texcoord = a_position.xy * .5 + .5;
}    
</script>
<script id="fshader-pers" type="fs/shader">
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D u_sampler;
void main() {
 gl_FragColor = texture2D(u_sampler, v_texcoord);
}
</script>

<body>
 <canvas id="c" width="400" height="400"></canvas>
</body>

I do not know much about how it is supposed to work and I am a bit into the dark. I am sure I'm missing something important, but I can't find anywhere what. Any help will be appreciated.

khael
  • 2,600
  • 1
  • 15
  • 36
  • You're not supposed to recreate your whole state in every frame. Initialize everything first, use it to render in the loop. – Bartek Banachewicz Nov 13 '15 at 12:13
  • @BartekBanachewicz thank you, I have modified my code to initialize all in one place. But still can't figure out why I can't use 'pos' in vshader-init – khael Nov 13 '15 at 12:46

1 Answers1

2

Your framebuffer texture is 1x1 pixels big. What do you expect to see? You're rendering a single pixel.

Also note that unless your framebuffer is the same size as the canvas you'll want to call gl.viewport and set it to the size of the thing you rendering after each call to gl.bindFramebuffer

You probably also want to not create the texture every frame. Here's your code with those things changed.

var canvas = document.getElementById("c");
var gl = canvas.getContext("webgl"); 

var program_init, program_pers, tex, fb;
var fbWidth = 400;
var fbHeight = 300;

function init() {
 var verts = [
    1,  1,
   -1,  1,
   -1, -1,
    1,  1,
   -1, -1,
    1, -1,
 ];
    
    var vertBuffer = gl.createBuffer();
 gl.bindBuffer(gl.ARRAY_BUFFER, vertBuffer);
 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verts), gl.STATIC_DRAW);
 gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
 gl.enableVertexAttribArray(0);
    
    tex = gl.createTexture();
    fb = gl.createFramebuffer();

    program_init = webglUtils.createProgramFromScripts(gl, ["vshader-init", "fshader-init"], ["pos"]);
    program_pers = webglUtils.createProgramFromScripts(gl, ["vshader-pers", "fshader-pers"], ["a_position"]);
  
   gl.bindTexture(gl.TEXTURE_2D, tex);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, fbWidth, fbHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
    // make non power-of-2 texture renderable
    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.LINEAR);

 gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
 gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
}

function renderToTexture(gl, time) {
    gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
    gl.viewport(0, 0, fbWidth, fbHeight);
  
    gl.useProgram(program_init);
 
 var timeLocation = gl.getUniformLocation(program_init, 'time');
 gl.uniform1f(timeLocation, time);

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

function renderToScreen(gl) {    
 gl.bindFramebuffer(gl.FRAMEBUFFER, null);
    gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
 gl.useProgram(program_pers);
 gl.drawArrays(gl.TRIANGLES, 0, 6);
}; 

init();
requestAnimationFrame(function(){
 renderToTexture(gl, arguments[0]);
 renderToScreen(gl);
 
 requestAnimationFrame( arguments.callee );
});
canvas { border: 1px solid black; }
body { background-color: darkslategrey }
<script src="https://webglfundamentals.org/webgl/resources/webgl-utils.js"></script>

<script id="vshader-init" type="vs/shader">
attribute vec4 pos;
varying vec2 uv;

void main() {
 gl_Position = pos;
 uv = pos.xy * .5 + .5;
}    
</script>
<script id="fshader-init" type="fs/shader">
precision mediump float;
varying vec2 uv;

uniform float time;

void main() {
 float t = floor(time / 1000.);
 vec3 color;
 color.x = sin(t*uv.x);
 color.y = tan(t*uv.y);
 color.z = cos(t);
 
    gl_FragColor = vec4(color, 1.);
}
</script>

<script id="vshader-pers" type="vs/shader">
attribute vec4 a_position;
varying vec2 v_texcoord;

void main() {
 gl_Position = a_position;
 v_texcoord = a_position.xy * .5 + .5;
}    
</script>
<script id="fshader-pers" type="fs/sahder">
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D u_sampler;
void main() {
 gl_FragColor = texture2D(u_sampler, v_texcoord);
}
</script>

<body>
 <canvas id="c" width="400" height="400"></canvas>
</body>
gman
  • 100,619
  • 31
  • 269
  • 393