WebGL基础教程:第一部分

栏目: 编程工具 · 发布时间: 5年前

内容简介:WebGL是一种基于OpenGL的浏览器内置3D渲染器,它可以让你在HTML5页面中直接显示3维内容。 在本教程中,我会介绍你使用此框架所需的所有基础内容。开始学习之前,有几件事你是需要了解的。 WebGL是将3D内容渲染到HTML5的canvas元素上的一种JavaScript API。 它是利用"3D世界"中称为着色器的两种脚本来实现这一点的。 这两种着色器分别是:听到这些名词时也不要过于惊慌;它们只不过是"位置计算器"和"颜色选择器"的另一种说法罢了。 片元着色器容易理解;它只是告诉WebGL,模型上

WebGL是一种基于OpenGL的浏览器内置3D渲染器,它可以让你在HTML5页面中直接显示3维内容。 在本教程中,我会介绍你使用此框架所需的所有基础内容。

介绍

开始学习之前,有几件事你是需要了解的。 WebGL是将3D内容渲染到HTML5的canvas元素上的一种JavaScript API。 它是利用"3D世界"中称为着色器的两种脚本来实现这一点的。 这两种着色器分别是:

  • 顶点着色器
  • 片元着色器

听到这些名词时也不要过于惊慌;它们只不过是"位置计算器"和"颜色选择器"的另一种说法罢了。 片元着色器容易理解;它只是告诉WebGL,模型上的指点定应该是什么颜色。 而顶点着色器解释起来就需要点技术了,不过基本上它起到将3维模型转变为2维坐标的作用。 因为所有的计算机显示器都是2维平面,当你在屏幕上看3维物体时,它们只不过是透视后的幻象。

如果你想完整地理解这个计算过程,你最好是问一个数学家,因为这个过程中用到了高级的4x4矩阵乘法,实在是有点超过我们这个"基础"教程的范围呀。 幸运的是,你并不需要知道它所有的工作原理,因为WebGL会处理背后大部分的细节。 那么,我们开始吧。

第一步:设置WebGL

WebGL有许多细微的设置,而且每次你要在屏幕画什么东西之前都要设置一遍。 为了节约时间,并使代码整洁一些,我们把所有"幕后"的代码包装成一个JavaScript对象,并存于一个独立的文件中。 现在我们要开始了,先创建一个新文件'WebGL.js',并写入如下代码:

function WebGL(CID, FSID, VSID){
    var canvas = document.getElementById(CID);
    if(!canvas.getContext("webgl") && !canvas.getContext("experimental-webgl"))
        alert("Your Browser Doesn't Support WebGL");
    else
    {
        this.GL = (canvas.getContext("webgl")) ? canvas.getContext("webgl") : canvas.getContext("experimental-webgl");

        this.GL.clearColor(1.0, 1.0, 1.0, 1.0); // this is the color 
        this.GL.enable(this.GL.DEPTH_TEST); //Enable Depth Testing
        this.GL.depthFunc(this.GL.LEQUAL); //Set Perspective View
        this.AspectRatio = canvas.width / canvas.height;

        //Load Shaders Here
    }
} 复制代码

这个构造函数的参数是canvas无形的ID,以及两个着色器对象。 首先,我们要获得canvas元素,并确保它是支持WebGL的。 如果支持WebGL,我们就将WebGL上下文赋值给一个局部变量,称为"GL"。 清除颜色(clearColor)其实就是设置背景颜色,值得一提的是,WebGL中大部分参数的取值范围都是0.0到1.0,所以我们需要让通常的rgb值除以255。 所以,我们的示例中,1.0,1.0,1.0,1.0表示背景为白色,且100%可见 (即无透明)。 接下来两行要求WebGL计算深度和透视,这样离你近的对象会挡住离你远的对象。 最后,我们设置宽高比,即canvas的宽度除以它的高度。

继续前行之前,我们要准备好两个着色器。 我把这些着色器写到HTML文件里去,这个HTML文件里还包含了我们的画布元素 (canvas)。 创建一个HTML文件,并将下面的两个script元素放在body标签之前。

<script id="VertexShader" type="x-shader/x-vertex">

    attribute highp vec3 VertexPosition;
    attribute highp vec2 TextureCoord;


    uniform highp mat4 TransformationMatrix;
    uniform highp mat4 PerspectiveMatrix;

    varying highp vec2 vTextureCoord;

    void main(void) {
    gl_Position = PerspectiveMatrix * TransformationMatrix * vec4(VertexPosition, 1.0);
    vTextureCoord = TextureCoord;
}
</script>

<script id="FragmentShader" type="x-shader/x-fragment">
    varying highp vec2 vTextureCoord;

uniform sampler2D uSampler;

void main(void) {
    highp vec4 texelColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
    gl_FragColor = texelColor;
}
</script>复制代码

先来看顶点着色器,我们定义了两个属性 (attributes):

  • 顶点位置,它存储了当前顶点 (你的模型上的点) 的位置,包括x,y,z坐标。
  • 纹理坐标,即赋给这个点的纹理在纹理图像中的位置

接下来,我们创建变换和透视矩阵等变量。 它们被用于将3D模型转化为2D图像。 下一行是创建一个与片元着色器共享的变量vTextureCoord,在主函数中,我们计算gl_Position (即最终的2D位置)。 然后,我们将'当前纹理坐标'赋给这个共享变量vTextureCoord。

在片元着色器中,我们取出定义在顶点着色器中的这个坐标,然后用这个坐标来对纹理进行'采样'。 基本上,通过这个过程,我们得到了我们几何体上的当前点处的纹理的颜色。

WebGL基础教程:第一部分

现在写完了着色器,我们可回过头去在JS文件中加载这些着色器。 将"//Load Shaders Here"换成如下代码:

var FShader = document.getElementById(FSID);
var VShader = document.getElementById(VSID);

if(!FShader || !VShader)
    alert("Error, Could Not Find Shaders");
else
{
    //Load and Compile Fragment Shader
    var Code = LoadShader(FShader);
    FShader = this.GL.createShader(this.GL.FRAGMENT_SHADER);
    this.GL.shaderSource(FShader, Code);
    this.GL.compileShader(FShader);

    //Load and Compile Vertex Shader
    Code = LoadShader(VShader);
    VShader = this.GL.createShader(this.GL.VERTEX_SHADER);
    this.GL.shaderSource(VShader, Code);
    this.GL.compileShader(VShader);

    //Create The Shader Program
    this.ShaderProgram = this.GL.createProgram();
    this.GL.attachShader(this.ShaderProgram, FShader);
    this.GL.attachShader(this.ShaderProgram, VShader);
    this.GL.linkProgram(this.ShaderProgram);
    this.GL.useProgram(this.ShaderProgram);

    //Link Vertex Position Attribute from Shader
    this.VertexPosition = this.GL.getAttribLocation(this.ShaderProgram, "VertexPosition");
    this.GL.enableVertexAttribArray(this.VertexPosition);

    //Link Texture Coordinate Attribute from Shader
    this.VertexTexture = this.GL.getAttribLocation(this.ShaderProgram, "TextureCoord");
    this.GL.enableVertexAttribArray(this.VertexTexture);
}复制代码

你的纹理必须是偶数字节大小,否则会出错。。。比如2x2,4x4,16x16,32x32。。。

首先,我们要确保这些着色器是存在的,然后,我们逐一地加载它们。 这个过程基本上是:得到着色器源码,编译,附着到核心的着色程序上。 从HTML文件中提取着色器源码的代码,封装到了一个函数中,称为LoadShader;稍后会讲到。 我们使用这个'着色器程序'将两个着色器链接起来,通过它,我们可以访问到着色器中的变量。 我们将数据储存到定义在着色器中的属性;然后,我们就可以将几何体输入到着色器中了。

现在,让我们看一下LoadShader函数,你应该将它置于WebGL函数之外。

function LoadShader(Script){
    var Code = "";
    var CurrentChild = Script.firstChild;
    while(CurrentChild)
    {
        if(CurrentChild.nodeType == CurrentChild.TEXT_NODE)
            Code += CurrentChild.textContent;
        CurrentChild = CurrentChild.nextSibling;
    }
    return Code;
}复制代码

基本上,这个函数通过遍历着色器来收集源码。

第二步:“简单”立方体

为了在WebGL中画出对象,你需要如下三个数组:

  • 顶点 (vertices):构造你的对象的那些点
  • 三角形 (triangles):告诉WebGL如何将顶点连接成面
  • 纹理坐标 (texture coordinates):定义顶点如何被映射到纹理图像上

这个过程称为UV映射。 我们的例子是构造一个简单的立方体。 我将这个立方体分成4个顶点一组,每一组又连成两个三角形。 我们可以用一个变量来存储立方体的这些数组。

var Cube = {
    Vertices : [ // X, Y, Z Coordinates

        //Front

        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

        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

        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

        -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

        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

        1.0, -1.0,  1.0,
        -1.0, -1.0,  1.0,
        1.0, -1.0, -1.0,
        -1.0, -1.0, -1.0

    ],
    Triangles : [ // Also in groups of threes to define the three points of each triangle
        //The numbers here are the index numbers in the vertex array

        //Front

        0, 1, 2,
        1, 2, 3,

        //Back

        4, 5, 6,
        5, 6, 7,

        //Right

        8, 9, 10,
        9, 10, 11,

        //Left

        12, 13, 14,
        13, 14, 15,

        //Top

        16, 17, 18,
        17, 18, 19,

        //Bottom

        20, 21, 22,
        21, 22, 23

    ],
    Texture : [ //This array is in groups of two, the x and y coordinates (a.k.a U,V) in the texture
        //The numbers go from 0.0 to 1.0, One pair for each vertex

        //Front

        1.0, 1.0,
        1.0, 0.0,
        0.0, 1.0,
        0.0, 0.0,


        //Back

        0.0, 1.0,
        0.0, 0.0,
        1.0, 1.0,
        1.0, 0.0,

        //Right

        1.0, 1.0,
        1.0, 0.0,
        0.0, 1.0,
        0.0, 0.0,

        //Left

        0.0, 1.0,
        0.0, 0.0,
        1.0, 1.0,
        1.0, 0.0,

        //Top

        1.0, 0.0,
        1.0, 1.0,
        0.0, 0.0,
        0.0, 1.0,

        //Bottom

        0.0, 0.0,
        0.0, 1.0,
        1.0, 0.0,
        1.0, 1.0
    ]
};复制代码

这样一个简单的立方体用到的数据似乎有点过多,不过,在我们教程的第二部分中,我们写一个导入3D模型的脚本,所以你现在不必计较这些。

你可能还在想,为什么需要24个顶点 (每一面4个) 呢,实际上只有8个呀? 我这样做是因为,你可以只用为每个顶点指定一个纹理坐标;而如果你用8个顶点,则整个立方体将看起来一样,因为它会将一个纹理值传播到顶点接触的所有面上。 通过我们的方式,每个面都有它独有的点,所以我们可以在每一面上指定不同的纹理区域。

现在,我们有了这样一个立方体变量 cube,然后,我们可以准备画它了。 我们还是回到WebGL方法中,并添加一个Draw函数。

第三步:Draw函数

WebGL中绘制对象的过程有许多步骤;所以最好是将每个步骤写成函数,来简化这个过程的代码。 基本的想法是将三个数组加载到WebGL的缓存中去。 然后,我们将这些缓存连接到着色器中定义的属性,以及变换和透视矩阵。 接下来,我们需要将纹理加载到内存中,并且最后调用draw命令。 那么,我们开始吧。

接下来的代码进入到WebGL函数中:

this.Draw = function(Object, Texture)
{
    var VertexBuffer = this.GL.createBuffer(); //Create a New Buffer

    //Bind it as The Current Buffer
    this.GL.bindBuffer(this.GL.ARRAY_BUFFER, VertexBuffer);

    // Fill it With the Data
    this.GL.bufferData(this.GL.ARRAY_BUFFER, new Float32Array(Object.Vertices), this.GL.STATIC_DRAW);

    //Connect Buffer To Shader's attribute
    this.GL.vertexAttribPointer(this.VertexPosition, 3, this.GL.FLOAT, false, 0, 0);

    //Repeat For The next Two
    var TextureBuffer = this.GL.createBuffer();
    this.GL.bindBuffer(this.GL.ARRAY_BUFFER, TextureBuffer);
    this.GL.bufferData(this.GL.ARRAY_BUFFER, new Float32Array(Object.Texture), this.GL.STATIC_DRAW);
    this.GL.vertexAttribPointer(this.VertexTexture, 2, this.GL.FLOAT, false, 0, 0);
    var TriangleBuffer = this.GL.createBuffer();
    this.GL.bindBuffer(this.GL.ELEMENT_ARRAY_BUFFER, TriangleBuffer);
    //Generate The Perspective Matrix
    var PerspectiveMatrix = MakePerspective(45, this.AspectRatio, 1, 10000.0);

    var TransformMatrix = MakeTransform(Object);

    //Set slot 0 as the active Texture
    this.GL.activeTexture(this.GL.TEXTURE0);

    //Load in the Texture To Memory
    this.GL.bindTexture(this.GL.TEXTURE_2D, Texture);

    //Update The Texture Sampler in the fragment shader to use slot 0
    this.GL.uniform1i(this.GL.getUniformLocation(this.ShaderProgram, "uSampler"), 0);

    //Set The Perspective and Transformation Matrices
    var pmatrix = this.GL.getUniformLocation(this.ShaderProgram, "PerspectiveMatrix");
    this.GL.uniformMatrix4fv(pmatrix, false, new Float32Array(PerspectiveMatrix));

    var tmatrix = this.GL.getUniformLocation(this.ShaderProgram, "TransformationMatrix");
    this.GL.uniformMatrix4fv(tmatrix, false, new Float32Array(TransformMatrix));

    //Draw The Triangles
    this.GL.drawElements(this.GL.TRIANGLES, Object.Trinagles.length, this.GL.UNSIGNED_SHORT, 0);
};复制代码
WebGL基础教程:第一部分

顶点着色器对你的对象进行放置,旋转和缩放时,依据的都是变换和透视矩阵。 在本教程第二部分中,我们会更深入地介绍变换。

我已经添加了两个函数:MakePerspective()和MakeTransform()。 它们只不过生成了WebGL所需的4x4矩阵。 MakePerspective()函数接受几个参数:视场竖直高度,宽高比,最近和最远点。 任何比1个单位近或比10000个单位远的对象都不会被显示,但是你可以调整这些值,以得到你所期望的效果。 现在,让我们看一看这两个函数:

function MakePerspective(FOV, AspectRatio, Closest, Farest){
    var YLimit = Closest * Math.tan(FOV * Math.PI / 360);
    var A = -( Farest + Closest ) / ( Farest - Closest );
    var B = -2 * Farest * Closest / ( Farest - Closest );
    var C = (2 * Closest) / ( (YLimit * AspectRatio) * 2 );
    var D = (2 * Closest) / ( YLimit * 2 );
    return [
        C, 0, 0, 0,
        0, D, 0, 0,
        0, 0, A, -1,
        0, 0, B, 0
    ];
}
function MakeTransform(Object){
    return [
        1, 0, 0, 0,
        0, 1, 0, 0,
        0, 0, 1, 0,
        0, 0, -6, 1
    ];
}复制代码

这些矩阵都会影响到你的对象的最终视觉效果,但透视矩阵影响的是你的“3维世界”,比如视场和可见对象,而变换矩阵影响的是单个对象,比如它们的旋转和位置。 完成这些之后,我们几何可以开始画了,剩下的工作只是将一个图像转变为一个WebGL纹理。

第四步:加载纹理

加载一个纹理分两步。 首先,我们要用JavaScript的标准做法来加载一幅图像,然后,我们将其转化为一个WebGL纹理。 所以,我们先从第二步开始吧,毕竟我们正在讨论的是JS文件。 将下面的代码加到WebGL函数的底部,恰好在Draw命令之后。

this.LoadTexture = function(Img){
    //Create a new Texture and Assign it as the active one
    var TempTex = this.GL.createTexture();
    this.GL.bindTexture(this.GL.TEXTURE_2D, TempTex);

    //Flip Positive Y (Optional)
    this.GL.pixelStorei(this.GL.UNPACK_FLIP_Y_WEBGL, true);

    //Load in The Image
    this.GL.texImage2D(this.GL.TEXTURE_2D, 0, this.GL.RGBA, this.GL.RGBA, this.GL.UNSIGNED_BYTE, Img);

    //Setup Scaling properties
    this.GL.texParameteri(this.GL.TEXTURE_2D, this.GL.TEXTURE_MAG_FILTER, this.GL.LINEAR);
    this.GL.texParameteri(this.GL.TEXTURE_2D, this.GL.TEXTURE_MIN_FILTER, this.GL.LINEAR_MIPMAP_NEAREST);
    this.GL.generateMipmap(this.GL.TEXTURE_2D);

    //Unbind the texture and return it.
    this.GL.bindTexture(this.GL.TEXTURE_2D, null);
    return TempTex;
};复制代码

值得一提的是,你的纹理大小必须是偶数字节,否则你会得到错误信息;比如它们可能的维度包括:2x2,4x4,16x16,32x32,等等。 我另加了一行来翻转Y坐标,只是因为我的3D应用的Y坐标是朝后的,但是否这样做完全取决于你。 这是因为一些程序取Y的零点为左上角,而其它则为左下角。 我设置的这些缩放性质只是告诉WebGL,图像应该如何向上采样和向下采样。 你可以使用其它的选项来得到不同的效果,不过我认为这个组合效果最佳。

现在,我们完成了JS文件,我们可以回到HTML文件,来完成最后一步了。

第五步:合起来

如前所述,WebGL是在canvas元素上画画。 因此,在body部分里,我们所需要的就只是一个canvas画布。 在添加canvas元素之后,你的html页面看起来像下面这样:

<html>
<head>
    <!-- Include Our WebGL JS file -->
    <script src="WebGL.js" type="text/javascript"></script>
    <script>

    </script>
</head>
<body onload="Ready()">
<canvas id="GLCanvas" width="720" height="480">
    Your Browser Doesn't Support HTML5's Canvas.
</canvas>

<!-- Your Vertex Shader -->

<!-- Your Fragment Shader -->

</body>
</html>复制代码

这个页面相当简单。 在head区域,我链接了JS文件。 现在,让我们实现Ready函数,它在页面加载时调用。

//This will hold our WebGL variable
var GL;

//Our finished texture
var Texture;

//This will hold the textures image 
var TextureImage;

function Ready(){
    GL = new WebGL("GLCanvas", "FragmentShader", "VertexShader");
    TextureImage = new Image();
    TextureImage.onload = function(){
        Texture = GL.LoadTexture(TextureImage);
        GL.Draw(Cube, Texture);
    };
    TextureImage.src = "Texture.png";
}复制代码

所以,我们创建一个新的WebGL对象,并将canvas和着色器的ID传递进去。 接下来,我们加载纹理图像。 一旦加载完成,我们对立方体Cube和纹理Texture调用Draw()方法。 如果你一路跟下来,你的屏幕上应该有一个覆盖有纹理的静止立方体。

虽然我说了下一次再讲变换,但我们不可能只丢给你一个静止矩形,这还不够三维。 让我们回过头去,再添加一个小小的旋转吧。 在HTML文件中,修改onload函数,使之如下面的代码:

TextureImage.onload = function(){
    Texture = GL.LoadTexture(TextureImage);
    setInterval(Update, 33);
};复制代码

这会使得每隔33毫秒调用一个称为Update()的函数,因而我们得到约30fps的帧率。 下面是这个更新函数:

function Update(){
    GL.GL.clear(16384 | 256);
    GL.Draw(GL.Cube, Texture);
}复制代码

这个函数相当简单;它只不过清除屏幕,然后绘制更新后的立方体。 现在,让我们进入JS文件,添加旋转代码。

第六步:添加一些旋转

我们不会完全实现变换的代码,因为我说了要等到下次现说,这次我们只是加一个绕Y轴的旋转。 要做的第一件事就是在Cube对象中加一个Rotation变量。 它会跟踪当前的角度,并让我们可以递增地保持旋转。 所以你的Cube变量的顶部代码应该如下面这样:

var Cube = {
    Rotation : 0,
    //The Other Three Arrays
};复制代码

现在,让我们修改MakeTransform()函数,添加旋转功能:

function MakeTransform(Object){
    var y = Object.Rotation * (Math.PI / 180.0);
    var A = Math.cos(y);
    var B = -1 * Math.sin(y);
    var C = Math.sin(y);
    var D = Math.cos(y);
    Object.Rotation += .3;
    return [
        A, 0, B, 0,
        0, 1, 0, 0,
        C, 0, D, 0,
        0, 0, -6, 1
    ];
}复制代码
WebGL基础教程:第一部分

更多精彩内容,请微信关注”前端达人”公众号!

WebGL基础教程:第一部分

原文链接:https://code.tutsplus.com/zh-hans/articles/webgl-essentials-part-i--net-25856

原文作者:Gabriel Manricks


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

深入理解OpenCV

深入理解OpenCV

[巴西]Daniel Lelis Baggio / 刘波 / 机械工业出版社 / 2014-9 / 59

opencv是最常见的计算机视觉库之一,它提供了许多经过优化的复杂算法。本书对已掌握基本opencv技术同时想提高计算机视觉的实践经验的开发者来讲是一本非常好的书。每章都有一个单独的项目,其背景也在这些章节中进行了介绍。因此,读者可以依次学习这些项目,也可以直接跳到感兴趣的项目进行学习。 《深入理解opencv:实用计算机视觉项目解析》详细讲解9个实用的计算机视觉项目,通过本书的学习,读者可......一起来看看 《深入理解OpenCV》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具