WebGL uses FBO to complete the cube Paster effect (with demo source code download), webglfbo
This article describes how WebGL uses FBO to implement the Cube Texture effect. We will share this with you for your reference. The details are as follows:
This article mainly records some basic points of WebGL, and also learns how to use FBO and environment textures. Take a look (WebGL, Chrome, Firefox, and IE11 are supported ).
The main implementation process is as follows: first use FBO to output the current environment in the cube texture, then draw the current cube, and finally draw the ball, and paste the texture associated with FBO on this sphere.
When starting WebGL, it is best to have some OpenGL basics. When we talked about Obj perfection and MD2 earlier, we may have discovered that, because of the addition and use of the shader, most of Opengl APIs have not been used. WebGL is similar to this. Most of the functions are the main functions of the coloring tool. Record the main process and you can compare them to see if they are similar. to familiarize yourself with the basic functions of WebGL, in this paper, we do not use a perfect framework, but only use a framework (gl-matrix.js) to help calculate the matrix ).
Like using OpenGL, We need to initialize the use environment and extract some global usage. The related code is as follows:
Initialization:
Var gl; // WebGLRenderingContextvar cubeVBO; // Cube buffer IDvar sphereVBO; // sphere VBOvar sphereEBO; // sphere EBOvar cubeTexID; // Cube Texture IDvar fboBuffer; // The callback cache object var glCubeProgram; // The Cube shader applies var glSphereProgram; // The Sphere shader applies var fboWidth = 512; // The callback cache binds the texture width var fboHeight = 512; // rotate cache bind the texture height var targets; // var pMatrix = mat4.create () in six directions of the cube texture; // perspective matrix var vMatrix = mat4.create (); // view matrix var eyePos = vec3.fromValues (0.0, 1.0, 0.0); // eye Location var eyeLookat = vec3.fromValues (0.0,-0.0, 0.0); // eye direction var spherePos = vec3.fromValues (0.0,-0.0, 0.0); // sphere location var canvanName; function webGLStart (cName) {canvanName = cName; InitWebGL (); encrypt (); InitSphereShader (); InitCubeBuffer (); InitSphereBuffer (); InitFBOCube (); // RenderFBO (); gl. clearColor (0.0, 0.0, 0.0, 1.0); gl. enable (gl. DEPTH_TEST); tick ();} function InitWebGL () {// var canvas = Document. getElementById (canvanName); InitGL (canvanName);} function InitGL (canvas) {try {// WebGLRenderingContext gl = canvas. getContext ("experimental-webgl"); gl. viewportWidth = canvas. width; gl. viewportHeight = canvas. height; targets = [gl. TEXTURE_CUBE_MAP_POSITIVE_X, gl. TEXTURE_CUBE_MAP_NEGATIVE_X, gl. TEXTURE_CUBE_MAP_POSITIVE_Y, gl. TEXTURE_CUBE_MAP_NEGATIVE_Y, gl. TEXTURE_CUBE_MAP_POSITIVE_Z, gl. TEXTURE_CUBE_MAP_NEGATIVE_Z];} catch (e) {}if (! Gl) {alert ("your browser does not support WebGL ");}}
Here, we initialize the WebGL's upstream and downstream environment on the webpage and provide a series of initialization processes. The following describes the code of the cube in the room.
Cube:
function InitCubeShader() { //WebGLShader var shader_vertex = GetShader("cubeshader-vs"); var shader_fragment = GetShader("cubeshader-fs"); //WebglCubeProgram glCubeProgram = gl.createProgram(); gl.attachShader(glCubeProgram, shader_vertex); gl.attachShader(glCubeProgram, shader_fragment); gl.linkProgram(glCubeProgram); if (!gl.getProgramParameter(glCubeProgram, gl.LINK_STATUS)) { alert("Shader hava error."); } gl.useProgram(glCubeProgram); glCubeProgram.positionAttribute = gl.getAttribLocation(glCubeProgram, "a_position"); glCubeProgram.normalAttribute = gl.getAttribLocation(glCubeProgram, "a_normal"); glCubeProgram.texCoordAttribute = gl.getAttribLocation(glCubeProgram, "a_texCoord"); glCubeProgram.view = gl.getUniformLocation(glCubeProgram, "view"); glCubeProgram.perspective = gl.getUniformLocation(glCubeProgram, "perspective");}function InitCubeBuffer() { var cubeData = [ -10.0, -10.0, -10.0, 0.0, 0.0, -10.0, 1.0, 0.0, -10.0, 10.0, -10.0, 0.0, 0.0, -10.0, 1.0, 1.0, 10.0, 10.0, -10.0, 0.0, 0.0, -10.0, 0.0, 1.0, 10.0, 10.0, -10.0, 0.0, 0.0, -10.0, 0.0, 1.0, 10.0, -10.0, -10.0, 0.0, 0.0, -10.0, 0.0, 0.0, -10.0, -10.0, -10.0, 0.0, 0.0, -10.0, 1.0, 0.0, -10.0, -10.0, 10.0, 0.0, 0.0, 10.0, 0.0, 0.0, 10.0, -10.0, 10.0, 0.0, 0.0, 10.0, 1.0, 0.0, 10.0, 10.0, 10.0, 0.0, 0.0, 10.0, 1.0, 1.0, 10.0, 10.0, 10.0, 0.0, 0.0, 10.0, 1.0, 1.0, -10.0, 10.0, 10.0, 0.0, 0.0, 10.0, 0.0, 1.0, -10.0, -10.0, 10.0, 0.0, 0.0, 10.0, 0.0, 0.0, -10.0, -10.0, -10.0, 0.0, -10.0, 0.0, 0.0, 0.0, 10.0, -10.0, -10.0, 0.0, -10.0, 0.0, 1.0, 0.0, 10.0, -10.0, 10.0, 0.0, -10.0, 0.0, 1.0, 1.0, 10.0, -10.0, 10.0, 0.0, -10.0, 0.0, 1.0, 1.0, -10.0, -10.0, 10.0, 0.0, -10.0, 0.0, 0.0, 1.0, -10.0, -10.0, -10.0, 0.0, -10.0, 0.0, 0.0, 0.0, 10.0, -10.0, -10.0, 10.0, 0.0, 0.0, 0.0, 0.0, 10.0, 10.0, -10.0, 10.0, 0.0, 0.0, 1.0, 0.0, 10.0, 10.0, 10.0, 10.0, 0.0, 0.0, 1.0, 1.0, 10.0, 10.0, 10.0, 10.0, 0.0, 0.0, 1.0, 1.0, 10.0, -10.0, 10.0, 10.0, 0.0, 0.0, 0.0, 1.0, 10.0, -10.0, -10.0, 10.0, 0.0, 0.0, 0.0, 0.0, 10.0, 10.0, -10.0, 0.0, 10.0, 0.0, 0.0, 0.0, -10.0, 10.0, -10.0, 0.0, 10.0, 0.0, 1.0, 0.0, -10.0, 10.0, 10.0, 0.0, 10.0, 0.0, 1.0, 1.0, -10.0, 10.0, 10.0, 0.0, 10.0, 0.0, 1.0, 1.0, 10.0, 10.0, 10.0, 0.0, 10.0, 0.0, 0.0, 1.0, 10.0, 10.0, -10.0, 0.0, 10.0, 0.0, 0.0, 0.0, -10.0, 10.0, -10.0, -10.0, 0.0, 0.0, 0.0, 0.0, -10.0, -10.0, -10.0, -10.0, 0.0, 0.0, 1.0, 0.0, -10.0, -10.0, 10.0, -10.0, 0.0, 0.0, 1.0, 1.0, -10.0, -10.0, 10.0, -10.0, 0.0, 0.0, 1.0, 1.0, -10.0, 10.0, 10.0, -10.0, 0.0, 0.0, 0.0, 1.0, -10.0, 10.0, -10.0, -10.0, 0.0, 0.0, 0.0, 0.0, ]; cubeVBO = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, cubeVBO); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(cubeData), gl.STATIC_DRAW);}function RenderCube() { gl.useProgram(glCubeProgram); gl.bindBuffer(gl.ARRAY_BUFFER, cubeVBO); gl.vertexAttribPointer(glCubeProgram.positionAttribute, 3, gl.FLOAT, false, 32, 0); gl.enableVertexAttribArray(glCubeProgram.positionAttribute); gl.vertexAttribPointer(glCubeProgram.normalAttribute, 3, gl.FLOAT, false, 32, 12); gl.enableVertexAttribArray(glCubeProgram.normalAttribute); gl.vertexAttribPointer(glCubeProgram.texCoordAttribute, 2, gl.FLOAT, false, 32, 24); gl.enableVertexAttribArray(glCubeProgram.texCoordAttribute); gl.uniformMatrix4fv(glCubeProgram.view, false, vMatrix); gl.uniformMatrix4fv(glCubeProgram.perspective, false, pMatrix); gl.drawArrays(gl.TRIANGLES, 0, 36);}
The above code is mainly divided into the initialization of the cube's coloring machine object, initialization of the relevant cache, and then draw the cube, it can be said that in Opengl, if you use the coloring machine to draw, the process is similar, in Opengl, there are no fixed pipeline functions, such as InterleavedArrays, to specify whether it is a vertex, a normal, or a texture. vertexAttribPointer is used to transmit data between the application and the shader. In the previous MD2 animated animation implementation, the parameter transfer later in the animated version also has related applications.
The main code of the cube coloring tool is as follows:
Cube coloring er implementation:
<Script id = "cubeshader-fs" type = "x-shader/x-fragment"> precision mediump float; varying vec3 normal; varying vec3 tex1; varying vec3 tex2; void main (void) {float x = tex1.x * 6.28*8.0; // 2 Wu * 8 float y = tex1.y * 6.28*8.0; // 2 Wu * 8 // cos (x) = 8 (1-1 1) gl_FragColor = vec4 (tex2, 1.0) * vec4 (sign (cos (x) + cos (y); // gl_FragColor = vec4 (normal * vec3 (0.5) + vec3 (0.5), 1 );} </script> <script id = "cubeshader-vs" type = "x-shader/x-vertex"> attribute vec3 a_position; attribute vec3 a_normal; attribute vec2 a_texCoord; uniform mat4 view; uniform mat4 perspective; varying vec3 normal; varying vec3 tex1; varying vec3 tex2; void main (void) {gl_Position = perspective * view * vec4 (a_position, 1.0 ); normal = a_normal; tex1 = vec3 (a_texCoord, 0.0); tex2 = normalize (a_position) * 0.5 + 0.5;} </script>
The ftransform () function is no longer available for calling in the coloring tool. You need to pass the model, view, and perspective matrix by yourself. Here, the model is painted centered on the origin, the model view matrix is also the view matrix. Therefore, the calculation of the screen position only requires the view and perspective matrix. In the fragment coloring tool, x and y are transmitted from the texture coordinates in the vertex coloring tool. The corresponding process is 6.28*8.0, which is equivalent to 8 360 degrees. It is used to control the square display on the cube, tex2 is the vertex ing value of [0, 1] In the coloring machine. It sets different meanings for the six sides of the cube respectively, and then uses the product of two vectors to mix the two colors for display, gl_FragColor = vec4 (tex2, 1.0) * vec4 (sign (cos (x) + cos (y ))).
Before displaying the sphere, you should plot the cube in the current environment. Here, use FBO to cache and associate the cube with the cube ing, and then take the origin as the center, draw from the top, bottom, left, right, and then use the trim buffer to output the six faces on the cube respectively. The main code is as follows:
FBO and Cube Texture:
function InitFBOCube() { // WebGLFramebuffer fboBuffer = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, fboBuffer); fboBuffer.width = 512; fboBuffer.height = 512; cubeTexID = gl.createTexture(); gl.bindTexture(gl.TEXTURE_CUBE_MAP, cubeTexID); gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR); for (var i = 0; i < targets.length; i++) { gl.texImage2D(targets[i], 0, gl.RGBA, fboBuffer.width, fboBuffer.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); } gl.bindFramebuffer(gl.FRAMEBUFFER, null);}function RenderFBO() { gl.disable(gl.DEPTH_TEST); gl.viewport(0, 0, fboBuffer.width, fboBuffer.height); gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.bindFramebuffer(gl.FRAMEBUFFER, fboBuffer); for (var i = 0; i < targets.length; i++) { gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, targets[i], cubeTexID, null); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); } mat4.perspective(pMatrix, 45, fboBuffer.width / fboBuffer.height, 0.1, 100.0); for (var i = 0; i < targets.length; i++) { gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, targets[i], cubeTexID, null); var lookat = vec3.create(); var up = vec3.create(); up[1] = 1.0; if (i == 0) { lookat[0] = -1.0; } else if (i == 1) { lookat[0] = 1.0; } else if (i == 2) { lookat[1] = -1.0; up[0] = 1.0; } else if (i == 3) { lookat[1] = 1.0; up[0] = 1.0; } else if (i == 4) { lookat[2] == -1.0; } else if (i == 5) { lookat[2] = 1.0; } else { } //vec3.fromValues(0.0, 0.0, 0.0) vMatrix = mat4.create(); mat4.lookAt(vMatrix, vec3.fromValues(0.0, 0.0, 0.0), lookat, up); //mat4.scale(vMatrix, vMatrix, vec3.fromValues(-1.0, -1.0, -1.0)); //mat4.translate(vMatrix, vMatrix, spherePos); RenderCube(); } gl.bindFramebuffer(gl.FRAMEBUFFER, null); gl.enable(gl.DEPTH_TEST);}
I don't know whether there is a problem with the matrix algorithm provided by gl-matrix above. It should have been like this. The texture image generated at the top and bottom is incorrect and the upward vector of the camera needs to be skewed. This is because the camera position is parallel to the Z axis generated by the target and the Set UP axis. As a result, the X axis cannot be correctly calculated and the corresponding UP axis cannot be calculated, an error occurs in the corresponding view matrix.
The last is the sphere painting. The code is similar to the cube. Pay attention to the vertex algorithm of the sphere.
Sphere:
function InitSphereShader() { //WebGLShader var shader_vertex = GetShader("sphereshader-vs"); var shader_fragment = GetShader("sphereshader-fs"); //WebglCubeProgram glSphereProgram = gl.createProgram(); gl.attachShader(glSphereProgram, shader_vertex); gl.attachShader(glSphereProgram, shader_fragment); gl.linkProgram(glSphereProgram); if (!gl.getProgramParameter(glSphereProgram, gl.LINK_STATUS)) { alert("Shader hava error."); } glSphereProgram.positionAttribute = gl.getAttribLocation(glSphereProgram, "a_position"); glSphereProgram.normalAttribute = gl.getAttribLocation(glSphereProgram, "a_normal"); glSphereProgram.eye = gl.getUniformLocation(glSphereProgram, "eye"); glSphereProgram.mapCube = gl.getUniformLocation(glSphereProgram, "mapCube"); glSphereProgram.model = gl.getUniformLocation(glSphereProgram, "model"); glSphereProgram.view = gl.getUniformLocation(glSphereProgram, "view"); glSphereProgram.perspective = gl.getUniformLocation(glSphereProgram, "perspective");}function InitSphereBuffer() { var radius = 1; var segments = 16; var rings = 16; var length = segments * rings * 6; var sphereData = new Array(); var sphereIndex = new Array(); for (var y = 0; y < rings; y++) { var phi = (y / (rings - 1)) * Math.PI; for (var x = 0; x < segments; x++) { var theta = (x / (segments - 1)) * 2 * Math.PI; sphereData.push(radius * Math.sin(phi) * Math.cos(theta)); sphereData.push(radius * Math.cos(phi)); sphereData.push(radius * Math.sin(phi) * Math.sin(theta)); sphereData.push(Math.sin(phi) * Math.cos(theta)); sphereData.push(radius * Math.cos(phi)) sphereData.push(Math.sin(phi) * Math.sin(theta)); } } for (var y = 0; y < rings - 1; y++) { for (var x = 0; x < segments - 1; x++) { sphereIndex.push((y + 0) * segments + x); sphereIndex.push((y + 1) * segments + x); sphereIndex.push((y + 1) * segments + x + 1); sphereIndex.push((y + 1) * segments + x + 1); sphereIndex.push((y + 0) * segments + x + 1) sphereIndex.push((y + 0) * segments + x); } } sphereVBO = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, sphereVBO); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(sphereData), gl.STATIC_DRAW); sphereVBO.numItems = segments * rings; sphereEBO = gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, sphereEBO); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(sphereIndex), gl.STATIC_DRAW); sphereEBO.numItems = sphereIndex.length;}function RenderSphere() { gl.useProgram(glSphereProgram); gl.bindBuffer(gl.ARRAY_BUFFER, sphereVBO); gl.vertexAttribPointer(glSphereProgram.positionAttribute, 3, gl.FLOAT, false, 24, 0); gl.enableVertexAttribArray(glSphereProgram.positionAttribute); gl.vertexAttribPointer(glSphereProgram.normalAttribute, 3, gl.FLOAT, false, 24, 12); gl.enableVertexAttribArray(glSphereProgram.normalAttribute); var mMatrix = mat4.create(); mat4.translate(mMatrix, mMatrix, spherePos); gl.uniform3f(glSphereProgram.eye, eyePos[0],eyePos[1],eyePos[2]); gl.uniformMatrix4fv(glSphereProgram.model, false, mMatrix); gl.uniformMatrix4fv(glSphereProgram.view, false, vMatrix); gl.uniformMatrix4fv(glSphereProgram.perspective, false, pMatrix); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_CUBE_MAP, cubeTexID); //gl.uniformMatrix4fv(glSphereProgram.mapCube, 0); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, sphereEBO); gl.drawElements(gl.TRIANGLES, sphereEBO.numItems, gl.UNSIGNED_SHORT, 0); gl.bindTexture(gl.TEXTURE_2D, null);}
We can see that the three steps are the same as those of the cube. initialize the shader, initialize the vertex and normal, and draw. The following is the code of the Shadow:
Sphere shader:
<script id="sphereshader-fs" type="x-shader/x-fragment"> precision mediump float; varying vec3 normal; varying vec3 eyevec; uniform samplerCube mapCube; void main( void ) { gl_FragColor = textureCube(mapCube, reflect(normalize(-eyevec), normalize(normal))); }</script><script id="sphereshader-vs" type="x-shader/x-vertex"> attribute vec3 a_position; attribute vec3 a_normal; uniform mat4 model; uniform mat4 view; uniform mat4 perspective; uniform vec3 eye; varying vec3 normal; varying vec3 eyevec; void main( void ) { gl_Position = perspective * view * model * vec4(a_position,1.0); eyevec = -eye;// a_position.xyz; normal = a_normal; }</script>
Unlike the previous cube, the sphere has its own model matrix, which is also a normal usage, and then transmits the vertex vector and normal of the sphere corresponding to the eyes to the part shader, the Cube Texture generated above is used in the part coloring tool, we can obtain the environmental color of the current sphere by reflecting the points on the texture of the stereo body through the corresponding normal vectors of the eye through the vertex. Here, we can directly call textureCube to complete the above process without manual computation.
The use of GetShader function, refer to the http://msdn.microsoft.com/zh-TW/library/ie/dn302360 (v = vs.85) here to explain.
It can be said that the above main draw functions have been completed, but we can do this, so we need to simulate how often the client environment is drawn, the main code is as follows:
Animation:
Function tick () {Update (); OnDraw (); setTimeout (function () {tick ()}, 15) ;} function OnDraw () {// fbo rander CUBE_MAP RenderFBO (); // element rander gl. viewport (0, 0, gl. viewportWidth, gl. viewportHeight); gl. clear (gl. COLOR_BUFFER_BIT | gl. DEPTH_BUFFER_BIT); mat4.perspective (pMatrix, 45, gl. viewportWidth/gl. viewportHeight, 0.1, 200.0); mat4.lookAt (vMatrix, eyePos, eyeLookat, vec3.fromValues (0.0, 1.0, 0.0); RenderCube (); RenderSphere ();} var lastTime = new Date (). getTime (); function Update () {var timeNow = new Date (). getTime (); if (lastTime! = 0) {var elapsed = timeNow-lastTime; // 3000 controls the rotation speed of the human eye. 8. Control the human eye distance. eyePos [0] = Math. cos (elapsed/3000) * 8; eyePos [2] = Math. sin (elapsed/2000) * 8; spherePos [0] = Math. cos (elapsed/4000) * 3; spherePos [2] = Math. cos (elapsed/4000) * 3 ;}}
The Update and Draw functions are called every 15 milliseconds. The Update function is used to Update the eye and sphere positions and Draw the Draw.
Click here to download the complete instance code.