Web3D编程入门总结——WebGL与Three.js基础介绍

/*在这里对这段时间学习的3D编程知识做个总结,以备再次出发。计划分成“webgl与three.js基础介绍”、“面向对象的基础3D场景框架编写”、“模型导入与简单3D游戏编写”三个部分,其他零散知识以后有机会再总结。*/
/*第一部分,webgl与three.js基础介绍,要求读者掌握JavaScript入门知识*/
//webgl原理:通过JavaScript语言在浏览器端生成glsl代码,把glsl代码送入显卡执行,把执行结果显示在浏览器中
//简单例程:
//根据Tony Parisi著《WebGL入门指南》第一章修改而来(简称T)
window.onload=webGLStart;
var gl;
function webGLStart()
{    
    var canvas = document.getElementById("can_main");//canvas是html5下的绘图标签,可以支持3D绘图
    gl=initGL(canvas);//初始化“绘制上下文”,以后的绘制都要通过它进行
    var square=createSquare(gl);//建立一个演示用的四边形,包括顶点坐标,顶点数组格式和顶点绘制方法
    var matrix=initMatrices();//定义两个矩阵
    
    var shaderProgram=initShaders();//定义着色器程序(glsl)

    draw(gl,square,matrix,shaderProgram);//调用显卡进行绘制
    onLoad();//稍后用来触发three.js方式的绘图
}
function initGL(canvas){
    var gl;
    try
    {
        gl = canvas.getContext("experimental-webgl");//从canvas中获取webgl上下文
        gl.viewport(0,0,canvas.width,canvas.height);//设置视口
    }
    catch(e)
    {
        var msg="Error creating WebGL Context!: "+ e.toString();
        alert(msg);  //弹出错误信息      
    }
    return gl;
}
function createSquare(gl)
{
    var vertexBuffer=gl.createBuffer();//建立一个顶点缓存
    gl.bindBuffer(gl.ARRAY_BUFFER,vertexBuffer);
    var verts = [
        1.0,  1.0,  0.0,
        -1.0,  1.0,  0.0,
        1.0, -1.0,  0.0,
        -1.0, -1.0,  0.0
    ];//把三维空间中的四个顶点存储在一个一维数组中
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verts), gl.STATIC_DRAW);//把数组元素的存储方式设为“32位浮点数”
    var square={buffer:vertexBuffer,vertSize:3,nVerts:4,primtype:gl.TRIANGLE_STRIP};    //使用一个JavaScript对象返回信息:使用vertexBuffer缓存顶点信息,三维空间顶点,共四个顶点,使用“三角形带”图元绘制方法
    return square;
}
function initMatrices()
{
    //定义姿态矩阵,即所有物体共用的相对于原点的位置和姿态
    var modelViewMatrix=new Float32Array(
        [1,0,0,0,
            0,1,0,0,
            0,0,1,0,
            0,0,-30.333,1]
    );
    //定义投影矩阵,即物体近大远小的透视程度
    var projectionMatrix=new Float32Array([
        2.41421,0,0,0,
        0,2.41421,0,0,
        0,0,-1.002002,-1,
        0,0,-0.2002002,0
    ]);
    var matrix={mvm:modelViewMatrix,pjm:projectionMatrix};
    return matrix;
}
function initShaders()
{
    /*着色器(Shader)位于显卡上,分为顶点着色器和片元着色器两种,数量各以千记。
    其中顶点着色器运行顶点着色器程序,负责对每个顶点的位置颜色信息的计算;
    片元着色器运行片元着色器程序,负责对顶点之间的内容进行“插值”,得出每个像素的颜色;
    顶点着色器+片元着色器+光栅化=显卡渲染管线*/
    //顶点着色器
    var vertexShaderSource=
        " attribute vec3 vertexPos;\n"+
        " uniform mat4 modelViewMatrix;\n"+
        " uniform mat4 projectionMatrix;\n"+
        " void main(void) {\n"+
        " //返回变换并投影后的顶点数据\n"+
        " gl_Position=projectionMatrix*modelViewMatrix*vec4(vertexPos,1.0);\n"+
        " }\n";
    //片元着色器
    var fragmentShaderSource=
        " void main(void){\n"+
        " //返回像素颜色:永远输出白色\n"+
        " gl_FragColor=vec4(1.0,1.0,1.0,1.0);\n"+
        " }\n";
    //glsl的注释是“//”

    //这里是对shader代码的编译
    var vertexShader=gl.createShader(gl.VERTEX_SHADER);
    gl.shaderSource(vertexShader, vertexShaderSource);
    gl.compileShader(vertexShader);
    if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
        alert(gl.getShaderInfoLog(shader));
    }

    var fragmentShader=gl.createShader(gl.FRAGMENT_SHADER);
    gl.shaderSource(fragmentShader, fragmentShaderSource);
    gl.compileShader(fragmentShader);
    if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
        alert(gl.getShaderInfoLog(shader));
    }

    //对glsl进行连接
    var shaderProgram = gl.createProgram();
    gl.attachShader(shaderProgram, vertexShader);
    gl.attachShader(shaderProgram, fragmentShader);
    gl.linkProgram(shaderProgram);
    gl.getProgramParameter(shaderProgram, gl.LINK_STATUS);

    return shaderProgram;//返回编译连接之后的着色器程序
}
function draw(gl,obj,matrix,shaderProgram)//这里只进行了一个物体的一次绘制,事实上obj完全可以是一个物体数组
{
    gl.clearColor(0.0,0.0,0.0,1.0);//使用完全不透明的黑色清屏
    gl.clear(gl.COLOR_BUFFER_BIT);

    gl.bindBuffer(gl.ARRAY_BUFFER,obj.buffer);//将gl对象与这个物体的缓存暂时绑定

    gl.useProgram(shaderProgram);//让gl使用上面定义的着色器程序

    gl.enableVertexAttribArray(gl.getAttribLocation(shaderProgram, "vertexPos"));//告诉WebGL我们将通过一个array提供顶点信息
    gl.vertexAttribPointer(gl.getAttribLocation(shaderProgram, "vertexPos"),obj.vertSize,gl.FLOAT,false,0,0);//提供顶点信息,顶点信息被保存在顶点着色器的vertexPos属性里(变量映射)
    gl.uniformMatrix4fv(gl.getUniformLocation(shaderProgram, "projectionMatrix"),false,matrix.pjm);
    gl.uniformMatrix4fv(gl.getUniformLocation(shaderProgram, "modelViewMatrix"),false,matrix.mvm);//将两个矩阵的信息发送给显卡

    gl.drawArrays(obj.primtype,0,obj.nVerts);//通知显卡按照顶点数组画图
}

//为提高编程效率,人们编写了一些基于WebGL的绘图引擎,Three.js是其中应用较广的一种:
//使用Three.js实现同样的绘制
function onLoad()
{
    var container=document.getElementById("container");

    //Three.js定义的“场景”对象
    var scene=new THREE.Scene();
    //进行WebGL兼容性判断
    if(webglAvailable()){
        var renderer=new THREE.WebGLRenderer();
    }else{
        var renderer=new THREE.CanvasRenderer();//对于环境支持html5但不支持webgl的情况,可以尝试使用更慢一些的2Dcanvas来软件绘图,但效果差强人意
    }
    renderer.setSize(container.offsetWidth,container.offsetHeight);//render可以看成是对canvas的一种扩展
    container.appendChild(renderer.domElement);

    //定义相机,类似于WebGL中的定义投影矩阵,
    var camera=new THREE.PerspectiveCamera(45,container.offsetWidth/container.offsetHeight,1,4000);
    camera.position.set(0,0,30.3333);//起原版中姿态矩阵的共用部分的作用,但不同的是:这里是观察者向相反的方向移动了。
    scene.add(camera);//相机对象被添加到了场景中,可以认为场景对象是用来与显卡进行交互的东西,其中包含了变量映射

    var geometry=new THREE.PlaneGeometry(2,2);//库中包含预制的几何体,通过少量参数即可生成完整的顶点数组,这里是一个宽高为二的四边形
    var mesh=new THREE.Mesh(geometry,new THREE.MeshBasicMaterial());//赋予这个几何体材质,材质对应反光性
    scene.add(mesh);

    renderer.render(scene,camera);// 渲染
}
function webglAvailable()
{//webgl的绘图是建立在显卡的基础上的,如果这台计算机没有显卡,或者浏览器不支持和显卡的通信,webgl上下文的建立将会失败
    try{
        var canvas=document.createElement("canvas");
        return !!(window.WebGLRenderingContext
        &&(canvas.getContext("webgl")||canvas.getContext("experimental-webgl"))
        );
    }catch(e){
        return false;
    }
}

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>webgl与three.js基础介绍</title>
</head>
<body>
    <!--在canvas中使用webglAPI绘制-->
    <canvas id="can_main" width="500" height="500"
            style="width:500px;height:500px;float: left"></canvas>
    <!--使用Three.js库绘制-->
    <div id="container" style="width:500px;height:500px;background-color: #000000;float: left"></div>
</body>

<script>
    
</script>
<!---->
<script src="mygl1.js"></script>
<script src="mygl1b.js"></script>

<!--引用three.js库文件-->
<!--three库是由多个文件联合成的一个大库,链接方式不同、库版本不同会产生不同的库文件!!-->

<!--这个版本旧一些但是只需要引用一个文件-->
<!--https://github.com/tparisi/WebGLBook/tree/master/libs-->
<!--script src="../LIBS/Three.js"></script-->

<!--这个版本较新但需要引用额外的文件-->
<!--https://github.com/mrdoob/three.js/tree/master/build-->
<!--https://github.com/mrdoob/three.js/tree/master/examples/js/renderers-->
<script src="three.min.js"></script>
<script src="Projector.js"></script>
<script src="CanvasRenderer.js"></script>

</html>

<!DOCTYPE html>
<html>
<head lang="en">
<title>加入光照、纹理、运动效果</title>
<meta charset="UTF-8">
<!--稍微复杂一点的场景,根据Giles Thomas的learningwebgl.com第七章修改而来(简称G)-->
<!--一个数学库用来实现一些数学计算-->
<script src="glMatrix-0.9.5.min.js" type="text/javascript"></script>
<!--google的一个帧动画辅助库-->
<script src="webgl-utils.js" type="text/javascript"></script>

<!--G把着色器代码放在了script标签里-->
<script id="shader-vs" type="x-shader/x-vertex">
    attribute vec3 aVertexPosition;//顶点位置数组
    attribute vec3 aVertexNormal;//法线向量数组
    attribute vec2 aTextureCoord;//纹理坐标数组

    uniform mat4 uMVMatrix;//模型姿态矩阵
    uniform mat4 uPMatrix;//投影矩阵
    uniform mat3 uNMatrix;

    uniform vec3 uAmbientColor;//环境光

    uniform vec3 uLightingDirection;//方向光方向
    uniform vec3 uDirectionalColor;//方向光颜色

    uniform bool uUseLighting;//是否使用光照
//在glsl中,attribute和uniform是输入顶点着色器的只读常量,varying是从顶点着色器中输入片元着色器的变量
//vec3是含有三个分量的元素组成的数组,mat4是4*4矩阵    
    varying vec2 vTextureCoord;
    varying vec3 vLightWeighting;//顶点光照

    void main(void) {
        gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);//顶点位置数组经过模型姿态矩阵和投影矩阵变化后得到顶点位置
        vTextureCoord = aTextureCoord;
        
        if (!uUseLighting) {
            vLightWeighting = vec3(1.0, 1.0, 1.0);//如果选择不使用光照则设为最强白光
        } else {
            vec3 transformedNormal = uNMatrix * aVertexNormal;//对法线向量数组进行姿态变换
            float directionalLightWeighting = max(dot(transformedNormal, uLightingDirection), 0.0);//按照法线方向和方向光方向计算反射光强度
            vLightWeighting = uAmbientColor + uDirectionalColor * directionalLightWeighting;//反射光加环境光得到顶点光照情况,这里使用的是最简单的光照模型,没有涉及材质。            
        }
    }
</script>
<script id="shader-fs" type="x-shader/x-fragment">
    precision mediump float;//显卡处理浮点数的精度

    varying vec2 vTextureCoord;
    varying vec3 vLightWeighting;

    uniform sampler2D uSampler;//二维纹理句柄

    void main(void) {
        vec4 textureColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));//根据纹理坐标对纹理进行切割
        //OpenGL标准显卡为提高模型缩放时的渲染效率,需要为模型纹理建立多个逐渐缩小的缩略图,受此限制图片的边长必须为2的整数次方,但实际纹理基本不会符合此要求,所以实际纹理图片会把不规则纹理放在规则图片中,再使用纹理坐标进行切割提取
        gl_FragColor = vec4(textureColor.rgb * vLightWeighting, textureColor.a);//纹理颜色乘以光照,再加上透明度信息作为实际片元颜色
    }
</script>
<!--gl_FragColor是glsl的保留字,表示片元着色器的输出-->


<!--程序入口在webGLStart()函数-->
<script type="text/javascript">

var gl;
//初始化weblg上下文
function initGL(canvas) {
    try {
        gl = canvas.getContext("experimental-webgl");
        gl.viewportWidth = canvas.width;
        gl.viewportHeight = canvas.height;
    } catch (e) {
    }
    if (!gl) {
        alert("Could not initialise WebGL, sorry :-(");
    }
}

//生成着色器代码
function getShader(gl, id) {
    var shaderScript = document.getElementById(id);
    if (!shaderScript) {
        return null;
    }

    var str = "";
    var k = shaderScript.firstChild;
    while (k) {
        if (k.nodeType == 3) {
            str += k.textContent;
        }
        k = k.nextSibling;
    }

    var shader;
    if (shaderScript.type == "x-shader/x-fragment") {
        shader = gl.createShader(gl.FRAGMENT_SHADER);
    } else if (shaderScript.type == "x-shader/x-vertex") {
        shader = gl.createShader(gl.VERTEX_SHADER);
    } else {
        return null;
    }

    gl.shaderSource(shader, str);//根据网页标签里的内容去生成shader program内容
    gl.compileShader(shader);

    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        alert(gl.getShaderInfoLog(shader));
        return null;
    }

    return shader;
}


var shaderProgram;//用来和显卡交互的对象,是很多属性的结合体
//初始化着色器
function initShaders() {
    var fragmentShader = getShader(gl, "shader-fs");
    var vertexShader = getShader(gl, "shader-vs");//根据标签内容编译着色器代码

    shaderProgram = gl.createProgram();
    gl.attachShader(shaderProgram, vertexShader);
    gl.attachShader(shaderProgram, fragmentShader);//把两个着色器的代码分别装填到shaderProgram中
    gl.linkProgram(shaderProgram);//连接着色器代码

    if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {//通过gl的属性验证连接是否成功
        alert("Could not initialise shaders");
    }

    gl.useProgram(shaderProgram);
    //下面是需要传给显卡的参数(变量映射),通过这种方式把JavaScript中的变量和glsl中的输入量关联起来
    shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
    gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);

    shaderProgram.vertexNormalAttribute = gl.getAttribLocation(shaderProgram, "aVertexNormal");
    gl.enableVertexAttribArray(shaderProgram.vertexNormalAttribute);

    shaderProgram.textureCoordAttribute = gl.getAttribLocation(shaderProgram, "aTextureCoord");
    gl.enableVertexAttribArray(shaderProgram.textureCoordAttribute);

    shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
    shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");
    shaderProgram.nMatrixUniform = gl.getUniformLocation(shaderProgram, "uNMatrix");
    shaderProgram.samplerUniform = gl.getUniformLocation(shaderProgram, "uSampler");
    shaderProgram.useLightingUniform = gl.getUniformLocation(shaderProgram, "uUseLighting");
    shaderProgram.ambientColorUniform = gl.getUniformLocation(shaderProgram, "uAmbientColor");
    shaderProgram.lightingDirectionUniform = gl.getUniformLocation(shaderProgram, "uLightingDirection");
    shaderProgram.directionalColorUniform = gl.getUniformLocation(shaderProgram, "uDirectionalColor");
}


//处理载入的图片文件
function handleLoadedTexture(texture) {
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);

    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.image);//将图片对象变成纹理对象
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);//图片放大或扭曲时的插值方式
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
    gl.generateMipmap(gl.TEXTURE_2D);//缩略图

    gl.bindTexture(gl.TEXTURE_2D, null);
}


var crateTexture;
var neheTexture;
//载入图片文件
function initTexture() {
    crateTexture = gl.createTexture();
    crateTexture.image = new Image();
    crateTexture.image.onload = function () {//onload事件代表图片载入完成
        handleLoadedTexture(crateTexture)
    }
    crateTexture.image.src = "crate.gif";//使用google chrome进行本地测试时注意跨域设置!!

    neheTexture = gl.createTexture();
    neheTexture.image = new Image();
    neheTexture.image.onload = function () {
        handleLoadedTexture(neheTexture)
    }
    neheTexture.image.src = "nehe.gif";
    //这里载入了两个图片,其实完全可以把两幅图片合在一起使用纹理坐标切割选择
}


var mvMatrix = mat4.create();
var mvMatrixStack = [];
var pMatrix = mat4.create();

//考虑到场景中存在多个物体,它们的姿态矩阵有公用的部分也有私有的部分,故在设置私有部分时把共有部分入栈
function mvPushMatrix() {
    var copy = mat4.create();
    mat4.set(mvMatrix, copy);
    mvMatrixStack.push(copy);
}
//设置完私有部分后将共有部分出栈,再去设置下一个物体
function mvPopMatrix() {
    if (mvMatrixStack.length == 0) {
        throw "Invalid popMatrix!";
    }
    mvMatrix = mvMatrixStack.pop();
}

//三个矩阵的变量映射
function setMatrixUniforms() {
    gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, pMatrix);//投影
    gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, mvMatrix);//顶点

    var normalMatrix = mat3.create();//法线
    mat4.toInverseMat3(mvMatrix, normalMatrix);
    mat3.transpose(normalMatrix);
    gl.uniformMatrix3fv(shaderProgram.nMatrixUniform, false, normalMatrix);
}

//角度转化为弧度,webgl默认支持弧度
function degToRad(degrees) {
    return degrees * Math.PI / 180;
}



var xRot = 0;
var xSpeed = 3;

var yRot = 0;
var ySpeed = -3;

var z = -5.0;

//键盘处理,通过这种方式可以处理同时按下多个按键
var currentlyPressedKeys = {};

function handleKeyDown(event) {
    currentlyPressedKeys[event.keyCode] = true;
}


function handleKeyUp(event) {
    currentlyPressedKeys[event.keyCode] = false;
}


function handleKeys() {
    if (currentlyPressedKeys[33]) {
        // Page Up
        z -= 0.05;
    }
    if (currentlyPressedKeys[34]) {
        // Page Down
        z += 0.05;
    }
    if (currentlyPressedKeys[37]) {
        // Left cursor key
        ySpeed -= 1;
    }
    if (currentlyPressedKeys[39]) {
        // Right cursor key
        ySpeed += 1;
    }
    if (currentlyPressedKeys[38]) {
        // Up cursor key
        xSpeed -= 1;
    }
    if (currentlyPressedKeys[40]) {
        // Down cursor key
        xSpeed += 1;
    }
}

var cubeVertexPositionBuffer;
var cubeVertexNormalBuffer;
var cubeVertexTextureCoordBuffer;
var cubeVertexIndexBuffer;
var cubeVertexIndexBuffer2;
//初始化缓存
function initBuffers() {
    cubeVertexPositionBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer);
    //一个正方形的顶点数组
    vertices = [
        // Front face
        -1.0, -1.0,  1.0,
        1.0, -1.0,  1.0,
        1.0,  1.0,  1.0,
        -1.0,  1.0,  1.0,

        // Back face
        -1.0, -1.0, -1.0,
        -1.0,  1.0, -1.0,
        1.0,  1.0, -1.0,
        1.0, -1.0, -1.0,

        // Top face
        -1.0,  1.0, -1.0,
        -1.0,  1.0,  1.0,
        1.0,  1.0,  1.0,
        1.0,  1.0, -1.0,

        // Bottom face
        -1.0, -1.0, -1.0,
        1.0, -1.0, -1.0,
        1.0, -1.0,  1.0,
        -1.0, -1.0,  1.0,

        // Right face
        1.0, -1.0, -1.0,
        1.0,  1.0, -1.0,
        1.0,  1.0,  1.0,
        1.0, -1.0,  1.0,

        // Left face
        -1.0, -1.0, -1.0,
        -1.0, -1.0,  1.0,
        -1.0,  1.0,  1.0,
        -1.0,  1.0, -1.0,
    ];
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
    cubeVertexPositionBuffer.itemSize = 3;
    cubeVertexPositionBuffer.numItems = 24;

    //正方形每个面的法线向量
    cubeVertexNormalBuffer = gl.createBuffer();//法线向量
    gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexNormalBuffer);
    var vertexNormals = [
        // Front face
        0.0,  0.0,  1.0,
        0.0,  0.0,  1.0,
        0.0,  0.0,  1.0,
        0.0,  0.0,  1.0,

        // Back face
        0.0,  0.0, -1.0,
        0.0,  0.0, -1.0,
        0.0,  0.0, -1.0,
        0.0,  0.0, -1.0,

        // Top face
        0.0,  1.0,  0.0,
        0.0,  1.0,  0.0,
        0.0,  1.0,  0.0,
        0.0,  1.0,  0.0,

        // Bottom face
        0.0, -1.0,  0.0,
        0.0, -1.0,  0.0,
        0.0, -1.0,  0.0,
        0.0, -1.0,  0.0,

        // Right face
        1.0,  0.0,  0.0,
        1.0,  0.0,  0.0,
        1.0,  0.0,  0.0,
        1.0,  0.0,  0.0,

        // Left face
        -1.0,  0.0,  0.0,
        -1.0,  0.0,  0.0,
        -1.0,  0.0,  0.0,
        -1.0,  0.0,  0.0
    ];
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexNormals), gl.STATIC_DRAW);
    cubeVertexNormalBuffer.itemSize = 3;
    cubeVertexNormalBuffer.numItems = 24;

    //每个面的纹理坐标
    cubeVertexTextureCoordBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexTextureCoordBuffer);
    var textureCoords = [
        // Front face
        0.0, 0.0,
        1.0, 0.0,
        1.0, 1.0,
        0.0, 1.0,

        // Back face
        1.0, 0.0,
        1.0, 1.0,
        0.0, 1.0,
        0.0, 0.0,

        // Top face
        0.0, 1.0,
        0.0, 0.0,
        1.0, 0.0,
        1.0, 1.0,

        // Bottom face
        1.0, 1.0,
        0.0, 1.0,
        0.0, 0.0,
        1.0, 0.0,

        // Right face
        1.0, 0.0,
        1.0, 1.0,
        0.0, 1.0,
        0.0, 0.0,

        // Left face
        0.0, 0.0,
        1.0, 0.0,
        1.0, 1.0,
        0.0, 1.0
    ];
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoords), gl.STATIC_DRAW);
    cubeVertexTextureCoordBuffer.itemSize = 2;
    cubeVertexTextureCoordBuffer.numItems = 24;

    //顶点绘制顺序,即存在24个顶点,分别使用其中的哪几个顶点绘制三角形
    //因为使用了两个纹理句柄,这里将正方形分成了两个物体,降低了效率
    cubeVertexIndexBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer);
    var cubeVertexIndices = [
        //0, 1, 2,      0, 2, 3,    // Front face
        //4, 5, 6,      4, 6, 7,    // Back face
        //8, 9, 10,     8, 10, 11,  // Top face
        12, 13, 14,   12, 14, 15, // Bottom face
        16, 17, 18,   16, 18, 19, // Right face
        20, 21, 22,   20, 22, 23  // Left face
    ];
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(cubeVertexIndices), gl.STATIC_DRAW);
    //cubeVertexIndexBuffer.itemSize = 1;
    //cubeVertexIndexBuffer.numItems = 36;

    cubeVertexIndexBuffer2 = gl.createBuffer();
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer2);
    var cubeVertexIndices2 = [
        0, 1, 2,      0, 2, 3,    // Front face
        4, 5, 6,      4, 6, 7,    // Back face
        8, 9, 10,     8, 10, 11  // Top face
        //12, 13, 14,   12, 14, 15, // Bottom face
        //16, 17, 18,   16, 18, 19, // Right face
        //20, 21, 22,   20, 22, 23  // Left face
    ];
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(cubeVertexIndices2), gl.STATIC_DRAW);
}

//场景绘制
function drawScene() {
    gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    //光照
    var lighting = document.getElementById("lighting").checked;
    gl.uniform1i(shaderProgram.useLightingUniform, lighting);
    //从html中读取光照设置
    if (lighting) {
        gl.uniform3f(
                shaderProgram.ambientColorUniform,
                parseFloat(document.getElementById("ambientR").value),
                parseFloat(document.getElementById("ambientG").value),
                parseFloat(document.getElementById("ambientB").value)
        );
        var lightingDirection = [
            parseFloat(document.getElementById("lightDirectionX").value),
            parseFloat(document.getElementById("lightDirectionY").value),
            parseFloat(document.getElementById("lightDirectionZ").value)
        ];
        var adjustedLD = vec3.create();
        vec3.normalize(lightingDirection, adjustedLD);

        vec3.scale(adjustedLD, -1);
        gl.uniform3fv(shaderProgram.lightingDirectionUniform, adjustedLD);
        gl.uniform3f(
                shaderProgram.directionalColorUniform,
                parseFloat(document.getElementById("directionalR").value),
                parseFloat(document.getElementById("directionalG").value),
                parseFloat(document.getElementById("directionalB").value)
        );
    }

    //变换姿态矩阵,创造出物体运动效果
    mat4.perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0, pMatrix);
    mat4.identity(mvMatrix);
    mat4.translate(mvMatrix, [0.0, 0.0, z]);
    mat4.rotate(mvMatrix, degToRad(xRot), [1, 0, 0]);
    mat4.rotate(mvMatrix, degToRad(yRot), [0, 1, 0]);
    setMatrixUniforms();//考虑到多个物体的变化,这个矩阵设置加入在绘制之前,如果需要在另一个位置绘制物体,就再设置一次姿态矩阵

    //顶点数组变量映射
    gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer);
    gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, cubeVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);
    gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexNormalBuffer);
    gl.vertexAttribPointer(shaderProgram.vertexNormalAttribute, cubeVertexNormalBuffer.itemSize, gl.FLOAT, false, 0, 0);
    gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexTextureCoordBuffer);
    gl.vertexAttribPointer(shaderProgram.textureCoordAttribute, cubeVertexTextureCoordBuffer.itemSize, gl.FLOAT, false, 0, 0);

    //纹理与顶点索引
    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, crateTexture);
    gl.uniform1i(shaderProgram.samplerUniform, 0);//传给着色器

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer);
    gl.drawElements(gl.TRIANGLES, 18, gl.UNSIGNED_SHORT, 0);//与上一例程中的“三角形带”方式不同,包含顶点绘制顺序时使用drawElements方法绘制

    gl.activeTexture(gl.TEXTURE1);
    gl.bindTexture(gl.TEXTURE_2D, neheTexture);
    gl.uniform1i(shaderProgram.samplerUniform, 1);//传给着色器,替换了上一个samplerUniform变量的值

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer2);
    gl.drawElements(gl.TRIANGLES, 18, gl.UNSIGNED_SHORT, 0);
}


var lastTime = 0;
//根据延迟时间计算动画效果,使得动画效果不受帧率影响
function animate() {
    var timeNow = new Date().getTime();
    if (lastTime != 0) {
        var elapsed = timeNow - lastTime;

        xRot += (xSpeed * elapsed) / 1000.0;
        yRot += (ySpeed * elapsed) / 1000.0;
    }
    lastTime = timeNow;
}


function tick() {
    requestAnimFrame(tick);//在浏览器认为可以时重调循环,这样可以随着系统负载情况不同产生动态帧率
    handleKeys();//处理键盘
    drawScene();//绘制场景
    animate();//帧动画
}


function webGLStart() {
    var canvas = document.getElementById("lesson07-canvas");
    initGL(canvas);//上下文
    initShaders();//着色器
    initBuffers();//缓存
    initTexture();//纹理

    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.enable(gl.DEPTH_TEST);//深度探测打开,webgl认为被遮挡的三角形将不被渲染,处理半透明物体时需关掉深度探测

    document.onkeydown = handleKeyDown;//声明键盘事件
    document.onkeyup = handleKeyUp;

    tick();//绘制循环
}
/*可以看到,随着页面复杂度的提高,webglAPI绘制方法的变量映射和glsl代码越来越多,因此在实际应用中往往使用运行库对这部分代码进行封装。至于顶点数组、法线向量、纹理坐标、纹理图片则使用标准绘制函数或模型文件进行封装*/
</script>


</head>


<body onload="webGLStart();">
<a href="http://learningwebgl.com/blog/?p=684"><< Back to Lesson 7</a><br>

<canvas width="500" height="500" id="lesson07-canvas" style="border: currentColor; border-image: none;"></canvas>

<br>
<input id="lighting" type="checkbox" checked=""> Use lighting<br>
(Use cursor keys to spin the box and <code>Page Up</code>/<code>Page Down</code> to zoom out/in)

<br>
<h2>Directional light:</h2>

<table style="padding: 10px; border: 0px currentColor; border-image: none;">
    <tbody><tr>
        <td><b>Direction:</b>
        <td>X: <input id="lightDirectionX" type="text" value="-0.25">
        <td>Y: <input id="lightDirectionY" type="text" value="-0.25">
        <td>Z: <input id="lightDirectionZ" type="text" value="-1.0">
    </tr>
    <tr>
        <td><b>Colour:</b>
        <td>R: <input id="directionalR" type="text" value="0.8">
        <td>G: <input id="directionalG" type="text" value="0.8">
        <td>B: <input id="directionalB" type="text" value="0.8">
    </tr>
    </tbody></table>

<h2>Ambient light:</h2>
<table style="padding: 10px; border: 0px currentColor; border-image: none;">
    <tbody><tr>
        <td><b>Colour:</b>
        <td>R: <input id="ambientR" type="text" value="0.2">
        <td>G: <input id="ambientG" type="text" value="0.2">
        <td>B: <input id="ambientB" type="text" value="0.2">
    </tr>
    </tbody></table>

<a href="http://learningwebgl.com/blog/?p=684"><< Back to Lesson 7</a>



</body></html>
<!DOCTYPE html>
<html>
<head lang="en">
<title>使用Three.js实现上述效果</title>
<meta charset="UTF-8">
    <link rel="stylesheet" href="../css/webglbook.css" /> 
    <script src="Three.js"></script>
    <script src="webgl-utils.js"></script>
    <script>
    
    var renderer = null, 
        scene = null, 
        camera = null,
        cube = null,
        animating = false;
    
    function onLoad()
    {
        // Grab our container div
        //取得作为容器的div,事实上容器不局限于div标签
        var container = document.getElementById("container");

        // Create the Three.js renderer, add it to our div
        //建立Three.js渲染器,并把它加入容器div中
        renderer = new THREE.WebGLRenderer( { antialias: true } );
        renderer.setSize(container.offsetWidth, container.offsetHeight);
        container.appendChild( renderer.domElement );

        // Create a new Three.js scene
        //建立一个Three.js场景
        scene = new THREE.Scene();

        // Put in a camera
        //引入一个相机
        camera = new THREE.PerspectiveCamera( 45, container.offsetWidth / container.offsetHeight, 1, 4000 );
        camera.position.set( 0, 0, 3 );

        // Create a directional light to show off the object
        //建立一个方向光来照亮场景中的物体
        var light = new THREE.DirectionalLight( 0xffffff, 1.5);
        light.position.set(0, 0, 1);
        scene.add( light );

        // Create a shaded, texture-mapped cube and add it to the scene
        //建立一个具有阴影效果和纹理效果的立方体,并把它加入到场景中
        // First, create the texture map
        //第一步,建立纹理图片(Three.js不要求图片尺寸是2的整数次方)
        var mapUrl = "molumen_small_funny_angry_monster.jpg";
        var map = THREE.ImageUtils.loadTexture(mapUrl);
        
        // Now, create a Phong material to show shading; pass in the map
        //建立一个Phong类型的材质对象来表现光照和阴影;把这个材质传入纹理图中
        var material = new THREE.MeshPhongMaterial({ map: map });

        // Create the cube geometry
        //建立一个默认的几何体
        var geometry = new THREE.CubeGeometry(1, 1, 1);

        // And put the geometry and material together into a mesh
        //把“多边形”和“材质”组合成一个“网格”
        cube = new THREE.Mesh(geometry, material);

        // Turn it toward the scene, or we won't see the cube shape!
        //稍微旋转一下物体,否则我们看不出立体的效果!
        cube.rotation.x = Math.PI / 5;
        cube.rotation.y = Math.PI / 5;

        // Add the cube to our scene
        //把物体加入到场景中
        scene.add( cube );

        // Add a mouse up handler to toggle the animation
        //添加鼠标监听
        addMouseHandler();

        // Run our render loop
        //启动渲染循环
        run();
    }

    function run()
    {
        // Render the scene
        //渲染场景,renderer是canvas的绘制上下文,scene是变量映射接口,camera是姿态矩阵和投影矩阵的结合
        renderer.render( scene, camera );

        // Spin the cube for next frame
        //旋转物体生成下一帧画面
        if (animating)
        {
            cube.rotation.y -= 0.01;
        }
            
        // Ask for another frame
        //请求下一帧(循环)
        requestAnimFrame(run);    
    }

    function addMouseHandler()
    {
        var dom = renderer.domElement;
        
        dom.addEventListener( 'mouseup', onMouseUp, false);
    }
    
    function onMouseUp    (event)
    {
        event.preventDefault();

        animating = !animating;
    }    
    
    </script>

</head>
<body onLoad="onLoad();" style="">
    <center><h1>Welcome to WebGL!</h1></center>
    <div id="container" style="width:95%; height:80%; position:absolute;"></div>
    <div id="prompt" style="width:95%; height:6%; bottom:0; text-align:center; position:absolute;">Click to animate the cube</div>

</body>
</html>
VR网

猜你喜欢

转载自blog.csdn.net/vrlook/article/details/80343352