本系列教程主要讲解利用WebGL开发网页版的三维图形程序。这里主要用到的OpenGL和FlyMath内容。本系列内容较难。要求学生对几何和编程有一定的了解。建议初三以上同学学习。
在3D环境中,对3D物体进行点击选择是一个常用的技巧。常用有三种方法来对物体选择。
第1种是获取渲染屏幕的颜色值来判断选中了那些物体。这个方法效率很高,但是要求物体是纯色或杂色较少。应用环境很窄。
第2种是把3D物体的三维坐标重新转换成屏幕坐标,再判断是否选中。在模型复杂的环境时这种方法效率很低,别外无法对选中模型的深度进行排序。
第3种方法是较为通用的,就是把屏幕点击位置坐标转换成空间三维射线坐标,对所有模型进行射线与模型的碰撞检测,另外还要根据物体距离摄像机的距离进行排序,那些物体先被选中。我这里主要介绍第3种方法。
在OpenGL里把屏幕坐标转换成空间坐标。用到一个很重要的函数gluUnProject函数。在这里我借助FlyMath矢量数学库里的UnProject函数来实现。并使用Ray2Quad函数来检测是否拾取到物体。这里要注意一点,不管在OpenGL还是WebGL,拾取物品的过程都是通过CPU来计算完成的。
这节程序绘制一个正方形图形,在屏幕上左右来回移动。当鼠标点击正方形时,正方形停止移动;再点击继续移动。
javascript部分源码
//定义全局GL上下文
var gl_context;
//循环控制参数
var curTime;
var isStop=0;
//偏移步长
var cur_x = 0;
var x_step = 0.05;
//定义Camera位置
var camera = [0,0,1];
var camera_to = [0,0,0];
var camera_up = [0,1,0];
//定义颜色数组
var color = [1,0,0];
//定义camera矩阵和工程矩阵
var matView = new Array;
var matProject = new Array;
var matOffset = new Array;
//定义纹理图片
var curImg;
var imgSrc = "webgl.png";
//拾取判断函数
function pick_obj(mx,my)
{
var canvas = document.getElementById("webGL");
var view = [0,0,canvas.width,canvas.height];
var matView1 = [];
var pStart = [];
var pEnd = [];
FlyMath.Matrix.Multiply(matView1,matOffset,matView);
FlyMath.Matrix.UnProject(pStart,mx,my,0,matView1,matProject,view);
FlyMath.Matrix.UnProject(pEnd,mx,my,1,matView1,matProject,view);
//正方形坐标([-0.3,0.3,-1,0.3,0.3,-1,-0.3,-0.3,-1,0.3,-0.3,-1]);
var p1 = [-0.3,0.3,-1];
var p2 = [0.3,0.3,-1];
var p3 = [-0.3,-0.3,-1];
var p4 = [0.3,-0.3,-1];
var dist = 0;
if(true==FlyMath.Phy.Ray2Quad(pStart,pEnd,p1,p2,p3,p4,dist))
{
if(isStop==1)
isStop=0;
else
isStop=1;
if(isStop==0)
onTime();
else
clearTimeout(curTime);
}
return false;
}
//时间函数
function onTime()
{
cur_x += x_step;
if(cur_x>0.5 || cur_x<-0.5)
x_step *=-1;
draw_quad();
curTime = setTimeout("onTime()",100);
}
//处理点击事件
function click(ev)
{
//获取点击的位置
var cx = ev.pageX - $("#webGL").offset().left;
var cy = ev.pageY - $("#webGL").offset().top;
if(pick_obj(cx,cy)==false)
return;
}
//加载纹理图片
function pre_loadImg()
{
curImg = new Image();
curImg.src = imgSrc;
curImg.onload = function()
{
onTime();
return;
}
curImg.onerror = function()
{
alert("加载纹理图片失败!");
return;
}
}
//初始化WebGL
function init_webgl()
{
var canvas = document.getElementById("webGL");
if(!canvas){
alert("获取<Canvas>标签失败!");
return;
}
//获取webGL统计图上下文
gl_context = canvas.getContext('webgl',
{ antialias:true,
depth:true,
stencil:true});
if(!gl_context){
alert("获取WebGL上下文失败!");
return;
}
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);
pre_loadImg();
}
//绘制正方形
function draw_quad()
{
//清空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);
//定义顶点shader和片断shader
var vs_src = "attribute vec4 a_Position;\n" +
"attribute vec2 a_UV;\n" +
"uniform mat4 projectionMatrix;\n" +
"uniform mat4 modelViewMatrix;\n" +
"uniform mat4 offsetMatrix;\n" +
"varying vec2 A_UV;\n" +
"void main() {\n" +
"gl_Position = projectionMatrix * modelViewMatrix * offsetMatrix * a_Position;\n"+
"A_UV = a_UV;\n" +
"}\n";
var fs_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";
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;
}
//绑定shader中的参数变量
var shader_pos = gl_context.getAttribLocation(shaderProgram, "a_Position");
var shader_uv = gl_context.getAttribLocation(shaderProgram, "a_UV");
var shaderModelViewMatrixUniform = gl_context.getUniformLocation(shaderProgram,"modelViewMatrix");
var shaderProjectionMatrixUniform = gl_context.getUniformLocation(shaderProgram,"projectionMatrix");
var shaderOffsetMatrixUniform = gl_context.getUniformLocation(shaderProgram,"offsetMatrix");
var SamplerUniform = gl_context.getUniformLocation(shaderProgram,"A_Texture");
/* 正方形顶点位置
0 , 1
2 , 3
*/
var vertices = new Float32Array([-0.3,0.3,-1,0.3,0.3,-1,-0.3,-0.3,-1,0.3,-0.3,-1]);
var indices = [0,1,2,1,2,3];
var uvs = [0,0,1,0,0,1,1,1];
FlyMath.Matrix.Translation(matOffset,cur_x,0,0);
var quad_buf = gl_context.createBuffer();
var indexBuffer = gl_context.createBuffer();
var uvBuffer = gl_context.createBuffer();
//使用选择的程序,激活缓冲区,渲染
gl_context.useProgram(shaderProgram);
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.uniformMatrix4fv(shaderOffsetMatrixUniform,false,matOffset);
//绑定纹理图片,设置纹理坐标方式
var img_texture = gl_context.createTexture();
gl_context.bindTexture(gl_context.TEXTURE_2D, img_texture);
gl_context.texImage2D(gl_context.TEXTURE_2D, 0, gl_context.RGBA, gl_context.RGBA, gl_context.UNSIGNED_BYTE, curImg);
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, img_texture);
gl_context.uniform1i(SamplerUniform, 0);
gl_context.drawElements(gl_context.TRIANGLES,indices.length,gl_context.UNSIGNED_BYTE, 0);
}