本系列教程主要讲解利用WebGL开发网页版的三维图形程序。这里主要用到的OpenGL和FlyMath内容。本系列内容较难。要求学生对几何和编程有一定的了解。建议初三以上同学学习。
离屏渲染也叫渲染到纹理。也就FBO(帧缓冲对象)其实就是两次渲染。第一次渲染的图像保存在一张纹理上,第二次渲染时把第一次渲染出来的纹理使用上。第一次的渲染结果只保存在纹理上,不在屏幕上显示。所以叫“离屏渲染”。
离屏渲染分为8个部份:
- 创建帧缓冲区对象。
- 创建纹理对象,并设置其尺寸。一般与第二次渲染时使用的纹理大小相同。
- 创建洝缓冲区对象。
- 绑定渲染缓冲区对象,并设置尺寸。大小和纹理大小相对。
- 将纹理对象关联到帧缓冲区对象。
- 将渲染缓冲区对象关联到帧缓冲区对象。
- 检查帧缓冲区的配置。
- 开始在帧缓冲区进行绘图渲染。
离屏渲染主要用在镜子和阴影上。比如一个镜子,镜子前方有一物体。摄像机在物体后方看向镜子。应该看到真实的物体和镜子里的物体背影。渲染过程是这样的:1、在摄像机的对称方向(镜子里面)看向物体(这时不渲染镜子,只渲染物体)。把渲染到的物体图像存在纹理里。2、摄像机复位,渲染得到的纹理,应用在镜子上面。这时渲染时来的镜子就有了物体的背影图。
渲染这节的例子是这样的:先把一个红色的三角形渲染到纹理。然后把这张纹理用在一个正方形上。渲染三角形时把背景颜色调为(0,0,0.3),比后面的淡一些,以示区别。
我们继续在上节的例子上面修改。给正方形加上一张纹理图片。
javascript部分源码
//定义全局GL上下文
var gl_context;
var canvas;
//循环控制参数
var curTime;
var isStop=1;
//定义Camera位置
var camera = [-0.1,0,1];
var camera_to = [0,0,0];
var camera_up = [0,1,0];
//定义颜色数组
var tri_color = [1,0,0];
var quad_color = [1,1,0];
//定义camera矩阵和工程矩阵
var matView = new Array;
var matProject = new Array;
//定义离屏缓冲区参数
var office_width = 512;
var office_height = 512;
var framebuffer;
var frame_texture;
var depthbuffer;
//定义引用shader中的参数
var shader_pos;
var shader_uv;
var ColorUniform;
var shaderModelViewMatrixUniform;
var shaderProjectionMatrixUniform;
var SamplerUniform;
//定义顶点shader和片断shader
var vs_tri_src = "attribute vec3 a_Position;\n" +
"uniform mat4 projectionMatrix;\n" +
"uniform mat4 modelViewMatrix;\n" +
"uniform vec3 a_color;\n" +
"varying vec3 A_color;\n" +
"void main() {\n" +
"gl_Position = projectionMatrix * modelViewMatrix * vec4(a_Position,1);\n"+
"A_color = a_color;\n"+
"}\n";
var fs_tri_src = "precision highp float;\n" +
"varying vec3 A_color;\n" +
"void main() {\n" +
" gl_FragColor = vec4(A_color.xyz,1);\n" +
"}\n";
var vs_quad_src = "attribute vec4 a_Position;\n" +
"attribute vec2 a_UV;\n" +
"uniform mat4 projectionMatrix;\n" +
"uniform mat4 modelViewMatrix;\n" +
"varying vec2 A_UV;\n" +
"void main() {\n" +
"gl_Position = projectionMatrix * modelViewMatrix * a_Position;\n"+
"A_UV = a_UV;" +
"}\n";
var fs_quad_src = "precision highp float;\n" +
"varying vec2 A_UV;\n" +
"uniform sampler2D A_Texture;\n" +
"void main() {\n" +
" vec4 imgColor = texture2D(A_Texture,A_UV);\n" +
" gl_FragColor = imgColor;\n" +
"}\n";
//初始离屏渲染缓冲区
function initFrameBuffer()
{
framebuffer = gl_context.createFramebuffer();
framebuffer.width = office_width;
framebuffer.height = office_height;
//创建帧缓冲区,保存颜色信息
frame_texture = gl_context.createTexture();
gl_context.bindTexture(gl_context.TEXTURE_2D,frame_texture);
gl_context.texParameteri(gl_context.TEXTURE_2D, gl_context.TEXTURE_MIN_FILTER, gl_context.NEAREST);
gl_context.texParameteri(gl_context.TEXTURE_2D, gl_context.TEXTURE_MAG_FILTER, gl_context.NEAREST);
gl_context.texParameteri(gl_context.TEXTURE_2D, gl_context.TEXTURE_WRAP_S, gl_context.CLAMP_TO_EDGE);
gl_context.texParameteri(gl_context.TEXTURE_2D, gl_context.TEXTURE_WRAP_T, gl_context.CLAMP_TO_EDGE);
gl_context.texImage2D(gl_context.TEXTURE_2D,0,gl_context.RGBA,
office_width,office_height,0,gl_context.RGBA,gl_context.UNSIGNED_BYTE,null);
framebuffer.texture = frame_texture;
//创建深度缓冲区,保存深度值
depthbuffer = gl_context.createRenderbuffer();
gl_context.bindRenderbuffer(gl_context.RENDERBUFFER,depthbuffer);
gl_context.renderbufferStorage(gl_context.RENDERBUFFER,gl_context.DEPTH_COMPONENT16,office_width,office_height);
gl_context.bindFramebuffer(gl_context.FRAMEBUFFER,framebuffer);
gl_context.framebufferTexture2D(gl_context.FRAMEBUFFER,gl_context.COLOR_ATTACHMENT0,gl_context.TEXTURE_2D,frame_texture,0);
gl_context.framebufferRenderbuffer(gl_context.FRAMEBUFFER,gl_context.DEPTH_ATTACHMENT,gl_context.RENDERBUFFER,depthbuffer);
var e = gl_context.checkFramebufferStatus(gl_context.FRAMEBUFFER);
if(e!=gl_context.FRAMEBUFFER_COMPLETE)
{
alert(e.toString());
return false;
}
return true;
}
//初始化WebGL
function init_webgl()
{
canvas = document.getElementById("webGL");
if(!canvas){
alert("获取<Canvas>标签失败!");
return;
}
//获取webGL统计图上下文
gl_context = canvas.getContext('webgl',
{ antialias:false,
stencil:true});
if(!gl_context){
alert("获取WebGL上下文失败!");
return;
}
canvas.width = $("#area").width();
canvas.height = $("#area").width()*3/5;
canvas.onmousedown = function(ev){ click(ev);}
//设置视口大小
gl_context.viewport(0,0,canvas.width,canvas.height);
//设置matView和matProject矩阵
FlyMath.Matrix.LookAtRH(matView,camera,camera_to,camera_up);
FlyMath.Matrix.PerspectiveRH(matProject,FlyMath_PI/3,1,1,100);
draw();
}
//编译vs和fs,并创建program对象
function init_program(vs_src,fs_src)
{
var vs = gl_context.createShader(gl_context.VERTEX_SHADER);
gl_context.shaderSource(vs,vs_src);
gl_context.compileShader(vs);
if(!gl_context.getShaderParameter(vs,gl_context.COMPILE_STATUS)){
alert(gl_context.getShaderInfoLog(vs));
return;
}
var fs = gl_context.createShader(gl_context.FRAGMENT_SHADER);
gl_context.shaderSource(fs,fs_src);
gl_context.compileShader(fs);
if(!gl_context.getShaderParameter(vs,gl_context.COMPILE_STATUS)){
alert(gl_context.getShaderInfoLog(fs));
return;
}
//加载选择的顶点和片断
var shaderProgram = gl_context.createProgram();
gl_context.attachShader(shaderProgram, vs);
gl_context.attachShader(shaderProgram, fs);
gl_context.linkProgram(shaderProgram);
if (!gl_context.getProgramParameter(shaderProgram, gl_context.LINK_STATUS)) {
alert("Could not initialise shaders");
return;
}
return shaderProgram;
}
function draw()
{
//===================绘制三角形到缓冲区
var shaderProgram1 = init_program(vs_tri_src,fs_tri_src);
//绑定shader中的参数变量
shader_pos = gl_context.getAttribLocation(shaderProgram1, "a_Position");
ColorUniform = gl_context.getUniformLocation(shaderProgram1,"a_color");
shaderModelViewMatrixUniform = gl_context.getUniformLocation(shaderProgram1,"modelViewMatrix");
shaderProjectionMatrixUniform = gl_context.getUniformLocation(shaderProgram1,"projectionMatrix");
if(false==initFrameBuffer())
{
alert("error initFrameBuffer");
return;
}
gl_context.useProgram(shaderProgram1);
gl_context.bindFramebuffer(gl_context.FRAMEBUFFER,framebuffer);
gl_context.viewport(0,0,office_width,office_height);
gl_context.clearColor(0,0,0.3,1);
//清空webgl颜色缓冲区里的内容
gl_context.clear(gl_context.COLOR_BUFFER_BIT | gl_context.DEPTH_BUFFER_BIT);
gl_context.enable(gl_context.DEPTH_TEST);
draw_triangle();
//=====================绘制正方形
//初始化quad用的program对象
var shaderProgram2 = init_program(vs_quad_src,fs_quad_src);
//绑定shader中的参数变量
shader_pos = gl_context.getAttribLocation(shaderProgram2, "a_Position");
shader_uv = gl_context.getAttribLocation(shaderProgram2, "a_UV");
shaderModelViewMatrixUniform = gl_context.getUniformLocation(shaderProgram2,"modelViewMatrix");
shaderProjectionMatrixUniform = gl_context.getUniformLocation(shaderProgram2,"projectionMatrix");
SamplerUniform = gl_context.getUniformLocation(shaderProgram2,"A_Texture");
gl_context.useProgram(shaderProgram2);
gl_context.bindFramebuffer(gl_context.FRAMEBUFFER,null);
gl_context.viewport(0,0,canvas.width,canvas.height);
//清空canvas的背景颜色
gl_context.clearColor(0,0,0.5,1);
//清空webgl颜色缓冲区里的内容
gl_context.clear(gl_context.COLOR_BUFFER_BIT | gl_context.DEPTH_BUFFER_BIT);
gl_context.enable(gl_context.DEPTH_TEST);
draw_quad();
}
//绘制三角形
function draw_triangle()
{
var vertices = new Float32Array([0.5,0.5,-1,0.3,0.0,-1,0.7,0.0,-1]);
var tri_buf = gl_context.createBuffer();
gl_context.bindBuffer(gl_context.ARRAY_BUFFER, tri_buf);
gl_context.bufferData(gl_context.ARRAY_BUFFER, new Float32Array(vertices), gl_context.STATIC_DRAW);
//激活缓冲区,渲染
gl_context.enableVertexAttribArray(shader_pos);
gl_context.vertexAttribPointer(shader_pos, 3, gl_context.FLOAT, false, 0, 0);
//绑定矩阵
gl_context.uniformMatrix4fv(shaderModelViewMatrixUniform,false,matView);
gl_context.uniformMatrix4fv(shaderProjectionMatrixUniform,false,matProject);
//绑定顶点顔色
gl_context.uniform3f(ColorUniform,tri_color[0],tri_color[1],tri_color[2]);
gl_context.drawArrays(gl_context.TRIANGLES,0,3);
}
//绘制正方形
function draw_quad()
{
/* 正方形顶点位置
0 , 1
2 , 3
*/
var vertices = new Float32Array([-1.0,1.0,-3,1.0,1.0,-3,-1.0,-1.0,-3,1.0,-1.0,-3]);
var indices = [0,1,2,1,2,3];
var uvs = [0,1,1,1,0,0,1,0];
var quad_buf = gl_context.createBuffer();
var indexBuffer = gl_context.createBuffer();
var uvBuffer = gl_context.createBuffer();
//激活缓冲区,渲染
gl_context.enableVertexAttribArray(shader_pos);
gl_context.enableVertexAttribArray(shader_uv);
gl_context.bindBuffer(gl_context.ARRAY_BUFFER, quad_buf);
gl_context.bufferData(gl_context.ARRAY_BUFFER, new Float32Array(vertices), gl_context.STATIC_DRAW);
gl_context.vertexAttribPointer(shader_pos, 3, gl_context.FLOAT, false, 0, 0);
gl_context.bindBuffer(gl_context.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl_context.bufferData(gl_context.ELEMENT_ARRAY_BUFFER, new Uint8Array(indices), gl_context.STATIC_DRAW);
gl_context.bindBuffer(gl_context.ARRAY_BUFFER,uvBuffer);
gl_context.bufferData(gl_context.ARRAY_BUFFER,new Float32Array(uvs),gl_context.STATIC_DRAW);
gl_context.vertexAttribPointer(shader_uv, 2, gl_context.FLOAT, false, 0, 0);
//绑定矩阵
gl_context.uniformMatrix4fv(shaderModelViewMatrixUniform,false,matView);
gl_context.uniformMatrix4fv(shaderProjectionMatrixUniform,false,matProject);
//绑定纹理图片,设置纹理坐标方式
gl_context.texParameteri(gl_context.TEXTURE_2D, gl_context.TEXTURE_MIN_FILTER, gl_context.NEAREST);
gl_context.texParameteri(gl_context.TEXTURE_2D, gl_context.TEXTURE_MAG_FILTER, gl_context.NEAREST);
gl_context.texParameteri(gl_context.TEXTURE_2D, gl_context.TEXTURE_WRAP_S, gl_context.CLAMP_TO_EDGE);
gl_context.texParameteri(gl_context.TEXTURE_2D, gl_context.TEXTURE_WRAP_T, gl_context.CLAMP_TO_EDGE);
gl_context.activeTexture(gl_context.TEXTURE0);
gl_context.bindTexture(gl_context.TEXTURE_2D, framebuffer.texture);
gl_context.uniform1i(SamplerUniform, 0);
gl_context.drawElements(gl_context.TRIANGLES,indices.length,gl_context.UNSIGNED_BYTE, 0);
}