WebGL自学教程——WebGL示例:10. 透视投影:看起来更真实

 

10. 透视投影:看起来更真实

 

     前面章节中的的投影,都是正视投影,如你看到的立方体,总是方方正正。这和真实世界的情况有所出入:在真实世界,离我们远的那一面会小一点。这是一种透视投影,本章我们就实现它。

     我们需要计算一个透视投影矩阵。幸运的是,已近有人在js中为我们实现好了:mat4的perspective函数。该函数通过y方向上可见区域的夹角、纵横比、和近远距离计算透视投影矩阵。投影矩阵需要配合视图矩阵使用。视图矩阵使用mat4.lookAt函数计算。该函数的前三个参数是三个vec3类型,分别表示眼睛的位置、场景中心的位置和从观察者的角度往上的向量;第四个参数可选,用来保存计算好的矩阵。

     着色器中需要用到的矩阵已近比较多:阴影矩阵、旋转矩阵和投影矩阵。如果把它们都作为uniform传递给着色器,这并不是好的做法:一来,你要写n个传值语句;二来,着色器中可以使用的uniform的数目是有限的,因而是宝贵的;三来,你需要在着色器中对这些矩阵作出运算,并且很多时候是重复的运算,这造成了资源浪费,是可耻的。较好的做法是在js中就把各种矩阵算好,算出的结果是一个综合的转换矩阵,在顶点着色器中,只需要简单地将该矩阵和顶点位置相乘即可。

<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=gb2312">
<script type="text/javascript" src="glMatrix-0.9.5.js"></script>

<script type="text/javascript" src="glt.js"></script> <!--glt系列函数已转存在glt.js文件中-->


<script id="shader-vs" type="x-shader/x-vertex">
attribute vec3 v3Position;
uniform mat4 um4ModelView;
varying vec3 v_texCoord;
uniform int uiShadowMode;

void main(void)
{
    if(uiShadowMode == 0) v_texCoord = v3Position;
    gl_Position = um4ModelView * vec4(v3Position, 1.0);//已近不再计算旋转矩阵、阴影矩阵等;这些矩阵和投影矩阵已经全部合并到um4ModelView中。

}
</script>

<script id="shader-fs" type="x-shader/x-fragment">
#ifdef GL_FRAGMENT_PRECISION_HIGH
    precision highp float;
#else
    precision mediump float;
#endif
uniform samplerCube s_texture;
varying vec3 v_texCoord;
uniform int uiShadowMode;

void main(void)
{
    if(uiShadowMode == 0) gl_FragColor = textureCube(s_texture, v_texCoord);
    else if(uiShadowMode == 1) gl_FragColor = vec4(0.7, 0.7, 0.7, 1.0);
    else gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
</script>

<script>
function ShaderSourceFromScript(scriptID)
{
    var shaderScript = document.getElementById(scriptID);
    if (shaderScript == null) return "";

    var sourceCode = "";
    var child = shaderScript.firstChild;
    while (child)
    {
        if (child.nodeType == child.TEXT_NODE ) sourceCode += child.textContent;
        child = child.nextSibling;
    }

    return sourceCode;
}

var webgl = null;
var vertexShaderObject = null;
var fragmentShaderObject = null;
var programObject = null;
var cubeBuffer = null;
var cubeIndexBuffer = null;
var v3PositionIndex = 0;
var textureObject = null;
var samplerIndex = -1;
var interval = 300;
var angle = 0;
var um4ModelViewIndex = -1;
var leftKeyDown = false;
var rightKeyDown = false;
var angleX = 0;
var upKeyDown = false;
var downKeyDown = false;
var shadowMat4 = null;//预先计算好的阴影矩阵
var uiShadowModeIndex = -1;
var shadowPlaneBuffer = null;
var mousePosition = [0, 0];
var w = 1;
var h = 1;
var perspectiveMat4 = null;//预先计算好的投影矩阵


function LoadData()
{
    var jsCubeData = [
        0.3, 0.3, 0.3,
        0.3, -0.3, 0.3,
        -0.3, -0.3, 0.3,
        -0.3, 0.3, 0.3,
        0.3, 0.3, -0.3,
        0.3, -0.3, -0.3,
        -0.3, -0.3, -0.3,
        -0.3, 0.3, -0.3
    ];

    cubeBuffer = webgl.createBuffer();
    webgl.bindBuffer(webgl.ARRAY_BUFFER, cubeBuffer);
    webgl.bufferData(webgl.ARRAY_BUFFER, new Float32Array(jsCubeData), webgl.STATIC_DRAW);


    var jsCubeIndex = [
        //前
        1,2,3,
        3,4,1,

        //后
        5,8,7,
        7,6,5,
       
        //左
        4,3,7,
        7,8,4,
       
        //右
        5,6,2,
        2,1,5,
       
        //上
        5,1,4,
        4,8,5,

        //下
        2,6,7,
        7,3,2
    ];
    for(var i=0; i<jsCubeIndex.length; ++i) --jsCubeIndex[i];
   
    cubeIndexBuffer = webgl.createBuffer();
    webgl.bindBuffer(webgl.ELEMENT_ARRAY_BUFFER, cubeIndexBuffer);
    webgl.bufferData(webgl.ELEMENT_ARRAY_BUFFER, new Uint8Array(jsCubeIndex), webgl.STATIC_DRAW);
    
 
    textureObject = webgl.createTexture();
    webgl.bindTexture(webgl.TEXTURE_CUBE_MAP, textureObject);
    webgl.texImage2D(webgl.TEXTURE_CUBE_MAP_POSITIVE_X, 0, webgl.RGB, webgl.RGB, webgl.UNSIGNED_BYTE, document.getElementById('myTexture1'));
    webgl.texImage2D(webgl.TEXTURE_CUBE_MAP_NEGATIVE_X, 0, webgl.RGB, webgl.RGB, webgl.UNSIGNED_BYTE, document.getElementById('myTexture2'));
    webgl.texImage2D(webgl.TEXTURE_CUBE_MAP_POSITIVE_Y, 0, webgl.RGB, webgl.RGB, webgl.UNSIGNED_BYTE, document.getElementById('myTexture3'));
    webgl.texImage2D(webgl.TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, webgl.RGB, webgl.RGB, webgl.UNSIGNED_BYTE, document.getElementById('myTexture4'));
    webgl.texImage2D(webgl.TEXTURE_CUBE_MAP_POSITIVE_Z, 0, webgl.RGB, webgl.RGB, webgl.UNSIGNED_BYTE, document.getElementById('myTexture5'));
    webgl.texImage2D(webgl.TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, webgl.RGB, webgl.RGB, webgl.UNSIGNED_BYTE, document.getElementById('myTexture6'));
 
 
    var jsShadowPlaneData = [-1.0, -1.0, 1.0,   -1.0,-0.25,-1.0,   1.0,-0.25,-1.0,   1.0, -1.0, 1.0];
    shadowPlaneBuffer  = webgl.createBuffer();
    webgl.bindBuffer(webgl.ARRAY_BUFFER, shadowPlaneBuffer  );
    webgl.bufferData(webgl.ARRAY_BUFFER, new Float32Array(jsShadowPlaneData ), webgl.STATIC_DRAW);

    shadowMat4 = mat4.create();
    gltMakeShadowMatrix([[-1, -1, 1], [1, -1, 1], [0, -0.25, -1]], [-1, 1, 0, 1], shadowMat4);//预先计算好阴影矩阵,这样就不用重复计算
 

    var lookAtMat4 = mat4.create();
    mat4.lookAt([0, 0, 1], [0,0,0], [0,1,0], lookAtMat4);
    perspectiveMat4 = mat4.create();
    mat4.perspective(60, w/h, 0.1, 2, perspectiveMat4);
    mat4.multiply(perspectiveMat4, lookAtMat4, perspectiveMat4);

    return 0;
}

function RenderScene()
{
    webgl.clearColor(0.0, 0.0, 0.0, 1.0);
    webgl.clearDepth(1.0);
    webgl.clear(webgl.COLOR_BUFFER_BIT|webgl.DEPTH_BUFFER_BIT);
   
    webgl.texParameteri(webgl.TEXTURE_CUBE_MAP, webgl.TEXTURE_MIN_FILTER, webgl.NEAREST);
    webgl.texParameteri(webgl.TEXTURE_CUBE_MAP, webgl.TEXTURE_MAG_FILTER, webgl.NEAREST);
    webgl.texParameteri(webgl.TEXTURE_CUBE_MAP, webgl.TEXTURE_WRAP_S, webgl.CLAMP_TO_EDGE);
    webgl.texParameteri(webgl.TEXTURE_CUBE_MAP, webgl.TEXTURE_WRAP_T, webgl.CLAMP_TO_EDGE);

    webgl.activeTexture(webgl.TEXTURE0);
    webgl.bindTexture(webgl.TEXTURE_CUBE_MAP, textureObject);
    webgl.uniform1i(samplerIndex, 0);
   
    webgl.frontFace(webgl.CW);
    webgl.cullFace(webgl.BACK);

    //画平面
    webgl.disable(webgl.DEPTH_TEST);//相当于地面,禁止深度测试,此时深度缓冲中值保持1.0不变,表示可以被任何其它物体所遮挡
    webgl.bindBuffer(webgl.ARRAY_BUFFER, shadowPlaneBuffer);
    webgl.enableVertexAttribArray(v3PositionIndex);
    webgl.vertexAttribPointer(v3PositionIndex, 3, webgl.FLOAT, false, 0, 0);
    webgl.uniform1i(uiShadowModeIndex, 2);
    //只需要透视投影
    webgl.uniformMatrix4fv(um4ModelViewIndex, false, perspectiveMat4);
    webgl.drawArrays(webgl.TRIANGLE_FAN, 0, 4);

    var mat4Temp = null;
    //画立方体
    //先旋转,再透视投影
    var m4Rotate = mat4.create();
    mat4.identity(m4Rotate);
    mat4.rotateZ(m4Rotate, angle*Math.PI/180);
    mat4.rotateX(m4Rotate, angleX*Math.PI/180);
    webgl.bindBuffer(webgl.ARRAY_BUFFER, cubeBuffer);
    webgl.bindBuffer(webgl.ELEMENT_ARRAY_BUFFER, cubeIndexBuffer);
    webgl.enableVertexAttribArray(v3PositionIndex);
    webgl.vertexAttribPointer(v3PositionIndex, 3, webgl.FLOAT, false, 0, 0);
    //画阴影
    webgl.disable(webgl.DEPTH_TEST);
    webgl.uniform1i(uiShadowModeIndex, 1);
    mat4Temp = mat4.create();
    mat4.multiply(shadowMat4, m4Rotate, mat4Temp);
    mat4.multiply(perspectiveMat4, mat4Temp, mat4Temp);//perspectiveMat4*(shadowMat4*m4Rotate)
    webgl.uniformMatrix4fv(um4ModelViewIndex, false, mat4Temp);

    webgl.drawElements(webgl.TRIANGLES, 36, webgl.UNSIGNED_BYTE, 0);
    //画立方体
    webgl.enable(webgl.DEPTH_TEST);
    webgl.depthFunc(webgl.LEQUAL);
    webgl.uniform1i(uiShadowModeIndex, 0);
    mat4Temp = mat4.create();
    mat4.multiply(perspectiveMat4, m4Rotate, mat4Temp);
    webgl.uniformMatrix4fv(um4ModelViewIndex, false, mat4Temp);

    webgl.drawElements(webgl.TRIANGLES, 36, webgl.UNSIGNED_BYTE, 0);
}

function RotateTriangle()
{
    if(leftKeyDown) angle += 10;
    if(rightKeyDown) angle -= 10;
    if(angle >= 360) angle -= 360;
    if(angle < 0) angle += 360;
 
    if(upKeyDown) angleX += 10;
    if(downKeyDown) angleX -= 10;
    if(angleX >= 360) angleX -= 360;
    if(angleX < 0) angleX += 360;

    RenderScene();
}

document.onkeydown = function(e)
{
    if(e.keyCode == 37) leftKeyDown = true;
    if(e.keyCode == 39) rightKeyDown = true;
    if(e.keyCode == 38) upKeyDown = true;
    if(e.keyCode == 40) downKeyDown = true;
}
document.onkeyup = function(e)
{
    if(e.keyCode == 37) leftKeyDown = false;
    if(e.keyCode == 39) rightKeyDown = false;
    if(e.keyCode == 38) upKeyDown = false;
    if(e.keyCode == 40) downKeyDown = false;
}
//这些事件代码仍然只适合FF浏览器
function OnMouseEnter(e)
{
    angle = 0;
    angleX = 0;
    mousePosition = [parseInt(e.screenX), parseInt(e.screenY)];
}
function OnMouseLeave(e)
{
    angle = 0;
    angleX = 0;
}
function OnMouseMove(e)
{
    var x = parseInt(e.screenX);
    var y = parseInt(e.screenY);
    angle -= (x-mousePosition[0]);//X方向上的位移决定绕Z轴转动的角度
    angleX -= (y-mousePosition[1]);//Y方向上的位移决定绕X轴转动的角度

    mousePosition = [x, y];
}

function Init()
{
    var myCanvasObject = document.getElementById('myCanvas');
    myCanvasObject.onmouseenter = OnMouseEnter;
    myCanvasObject.onmouseleave = OnMouseLeave;
    myCanvasObject.onmousemove = OnMouseMove;
   
    webgl = myCanvasObject.getContext("experimental-webgl");

    w = myCanvasObject.clientWidth;
    h = myCanvasObject.clientHeight;//将视见区的宽度和高度保存下来,以在后面使用(如计算纵横比)
    webgl.viewport(0, 0, w, h);


    vertexShaderObject = webgl.createShader(webgl.VERTEX_SHADER);
    fragmentShaderObject = webgl.createShader(webgl.FRAGMENT_SHADER);

    webgl.shaderSource(vertexShaderObject, ShaderSourceFromScript("shader-vs"));
    webgl.shaderSource(fragmentShaderObject, ShaderSourceFromScript("shader-fs"));

    webgl.compileShader(vertexShaderObject);
    webgl.compileShader(fragmentShaderObject);

    if(!webgl.getShaderParameter(vertexShaderObject, webgl.COMPILE_STATUS)){alert(webgl.getShaderInfoLog(vertexShaderObject));return;}
    if(!webgl.getShaderParameter(fragmentShaderObject, webgl.COMPILE_STATUS)){alert(webgl.getShaderInfoLog(fragmentShaderObject));return;}

    programObject = webgl.createProgram();

    webgl.attachShader(programObject, vertexShaderObject);
    webgl.attachShader(programObject, fragmentShaderObject);

    webgl.bindAttribLocation(programObject, v3PositionIndex, "v3Position");

    webgl.linkProgram(programObject);
    if(!webgl.getProgramParameter(programObject, webgl.LINK_STATUS)){alert(webgl.getProgramInfoLog(programObject));return;}

    samplerIndex = webgl.getUniformLocation(programObject, "s_texture");
    um4ModelViewIndex = webgl.getUniformLocation(programObject, "um4ModelView");
    uiShadowModeIndex = webgl.getUniformLocation(programObject, "uiShadowMode");

    webgl.useProgram(programObject);

    if(LoadData() != 0){alert("error:LoadData()!");return;}


    window.setInterval("RotateTriangle()", interval);
}

</script>
</head>
<body οnlοad='Init()'>
<canvas id="myCanvas" style="border:1px solid red;" width='600px' height='450px'></canvas><br>
<img id="myTexture1" src='cubeTexture1.bmp'>
<img id="myTexture2" src='cubeTexture2.bmp'>
<img id="myTexture3" src='cubeTexture3.bmp'><br>
<img id="myTexture4" src='cubeTexture4.bmp'>
<img id="myTexture5" src='cubeTexture5.bmp'>
<img id="myTexture6" src='cubeTexture6.bmp'>
</body>
</html>

    运行效果如下:

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值