WebGL是一种基于OpenGL的浏览器内置3D渲染器,它可让你在HTML5页面中直接显示3维内容。 在本教程中,我会介绍你使用此框架所需的全部基础内容。javascript
开始学习以前,有几件事你是须要了解的。 WebGL是将3D内容渲染到HTML5的canvas元素上的一种JavaScript API。 它是利用"3D世界"中称为着色器的两种脚原本实现这一点的。 这两种着色器分别是:html
听到这些名词时也不要过于惊慌;它们只不过是"位置计算器"和"颜色选择器"的另外一种说法罢了。 片元着色器容易理解;它只是告诉WebGL,模型上的指点定应该是什么颜色。 而顶点着色器解释起来就须要点技术了,不过基本上它起到将3维模型转变为2维坐标的做用。 由于全部的计算机显示器都是2维平面,当你在屏幕上看3维物体时,它们只不过是透视后的幻象。前端
若是你想完整地理解这个计算过程,你最好是问一个数学家,由于这个过程当中用到了高级的4x4矩阵乘法,实在是有点超过咱们这个"基础"教程的范围呀。 幸运的是,你并不须要知道它全部的工做原理,由于WebGL会处理背后大部分的细节。 那么,咱们开始吧。java
WebGL有许多细微的设置,并且每次你要在屏幕画什么东西以前都要设置一遍。 为了节约时间,并使代码整洁一些,咱们把全部"幕后"的代码包装成一个JavaScript对象,并存于一个独立的文件中。 如今咱们要开始了,先建立一个新文件'WebGL.js',并写入以下代码:node
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的宽度除以它的高度。web
继续前行以前,咱们要准备好两个着色器。 我把这些着色器写到HTML文件里去,这个HTML文件里还包含了咱们的画布元素 (canvas)。 建立一个HTML文件,并将下面的两个script元素放在body标签以前。canvas
<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):数组
接下来,咱们建立变换和透视矩阵等变量。 它们被用于将3D模型转化为2D图像。 下一行是建立一个与片元着色器共享的变量vTextureCoord,在主函数中,咱们计算gl_Position (即最终的2D位置)。 而后,咱们将'当前纹理坐标'赋给这个共享变量vTextureCoord。浏览器
在片元着色器中,咱们取出定义在顶点着色器中的这个坐标,而后用这个坐标来对纹理进行'采样'。 基本上,经过这个过程,咱们获得了咱们几何体上的当前点处的纹理的颜色。缓存
如今写完了着色器,咱们可回过头去在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中画出对象,你须要以下三个数组:
这个过程称为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函数。
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); };复制代码
顶点着色器对你的对象进行放置,旋转和缩放时,依据的都是变换和透视矩阵。 在本教程第二部分中,咱们会更深刻地介绍变换。
我已经添加了两个函数: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
];
}复制代码
更多精彩内容,请微信关注”前端达人”公众号!
原文连接:https://code.tutsplus.com/zh-hans/articles/webgl-essentials-part-i--net-25856
原文做者:Gabriel Manricks