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