在利用緩沖區(qū)并在學會利用 mode
繪制圖形動畫以后斟冕,繼續(xù)研究二維圖形的顏色渲染以及紋理操作
1. 顏色渲染
1.1 利用步進和偏移拆分緩沖區(qū)數(shù)據(jù)
利用緩沖區(qū)可以一次存儲點的多個信息,繼續(xù)利用這一特性,并在利用vertexAttribPointer()
函數(shù)的時候酥馍,利用上stride
和offset
兩個參數(shù):
function main() {
const canvas = document.querySelector('#glCanvas');
const gl = canvas.getContext('webgl');
// 著色器程序
const VSHADER_SOURCE = `
attribute vec4 a_Position;
attribute float a_PointSize;
void main() {
gl_Position = a_Position;
gl_PointSize = a_PointSize;
}
`;
const FSHADER_SOURCE = `
precision mediump float;
uniform vec4 u_FragColor;
void main() {
gl_FragColor = u_FragColor;
}
`
let program = init(gl, VSHADER_SOURCE, FSHADER_SOURCE);
let a_Position = gl.getAttribLocation(program, 'a_Position');
if (a_Position < 0) {
console.log('Cant find the position');
return;
}
let a_PointSize = gl.getAttribLocation(program, 'a_PointSize');
if (a_PointSize < 0) {
console.log('Cant find the pointsize');
return;
}
gl.vertexAttrib1f(a_PointSize, 10.0);
initPoint(gl, a_Position, a_PointSize);
let u_FragColor = gl.getUniformLocation(program, 'u_FragColor');
gl.uniform4f(u_FragColor, 1.0, 0.0, 0.0, 1.0);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.POINTS, 0, 3);
}
function initPoint(gl, a_Position, a_PointSize) {
// 將點的坐標和尺寸信息放入一個類型化數(shù)組中
let pointData = new Float32Array([
-0.5, -0.5, 10.0,
0, 0.5, 20.0,
0.5, -0.5, 30.0
]);
// 利用類型化數(shù)組提供的內(nèi)部屬性摔认,獲取到元素的尺寸
let FSIZE = pointData.BYTES_PER_ELEMENT;
let buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, pointData, gl.STATIC_DRAW);
// 設置第五個參數(shù)stride為3*FSIZE(每兩個點之間有三個元素,也就是每三個數(shù)據(jù)進行一次拆分)
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 3*FSIZE, 0);
gl.enableVertexAttribArray(a_Position);
// 設置第六個參數(shù)offset偏移龄减,將每組數(shù)據(jù)的第三個元素設置給a_PointSize屬性
gl.vertexAttribPointer(a_PointSize, 1, gl.FLOAT, false, 3*FSIZE, 2*FSIZE);
gl.enableVertexAttribArray(a_PointSize);
}
主要改變在于:
1.利用了類型化數(shù)組的
BYTE_PER_ELEMENT
屬性似忧,獲取了每個元素的大小
- 利用
vertexAttribPointer()
的第五個參數(shù)渣叛,指定兩個相鄰頂點間的字節(jié)數(shù)丈秩,或者理解為每個點的信息由多少個元素組成盯捌。- 利用
vertexAttribPointer()
的第六個參數(shù),指定緩沖區(qū)對象中每次讀取時的偏移蘑秽。
當然饺著,要完成以上的操作箫攀,也可以將點的位置信息和尺寸信息進行分開放置:
function initPoint(gl, a_Position, a_PointSize) {
// 將類型化數(shù)組增加點的尺寸的參數(shù)
let pointData = new Float32Array([
-0.5, -0.5,
0, 0.5,
0.5, -0.5
]);
let pointSizeData = new Float32Array([10.0, 20.0, 30.0]);
// 設置點的位置信息
let buffer1 = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer1);
gl.bufferData(gl.ARRAY_BUFFER, pointData, gl.STATIC_DRAW);
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_Position);
// 設置點的尺寸信息
let buffer2 = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer2);
gl.bufferData(gl.ARRAY_BUFFER, pointSizeData, gl.STATIC_DRAW);
gl.vertexAttribPointer(a_PointSize, 1, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(a_PointSize);
}
但是很明顯操作多余而且復雜了
1.2 利用varying變量進行插值操作
想要從Vertex Shader
到Fragment Shader
進行數(shù)據(jù)傳遞,需要用到varying
聲明的變量幼衰,為什么利用從Vertex Shader
到Fragment Shader
傳遞數(shù)據(jù)靴跛?
由于Fragment Shader
中是只能使用uniform
變量的,unifrom
信息是和頂點信息不相關(guān)聯(lián)的渡嚣,如果要單獨對某個頂點的顏色進行設置梢睛,是沒辦法的,所以為了給每個頂點設置顏色识椰,就要從Vertex Shader
傳遞數(shù)據(jù)到Fragment Shader
了绝葡。
如果繪制一個三角形,并將頂點顏色傳遞給Fragment Shader
:
// 修改著色器程序腹鹉,增加varying
const VSHADER_SOURCE = `
attribute vec4 a_Position;
attribute vec4 a_Color;
varying vec4 v_Color;
void main() {
gl_Position = a_Position;
v_Color = a_Color;
}
`;
const FSHADER_SOURCE = `
precision mediump float;
varying vec4 v_Color;
void main() {
gl_FragColor = v_Color;
}
`
// 修改點的數(shù)據(jù)信息加載
function initPoint(gl, a_Position, a_Color) {
// 將點的坐標信息和顏色信息放到同一個類型化數(shù)組中
let pointData = new Float32Array([
-0.5, -0.5, 1.0, 0.0, 0.0,
0, 0.5, 0.0, 1.0, 0.0,
0.5, -0.5, 0.0, 0.0, 1.0
]);
// 利用類型化數(shù)組提供的方法藏畅,獲取到元素的尺寸
let FSIZE = pointData.BYTES_PER_ELEMENT;
let buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, pointData, gl.STATIC_DRAW);
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 5*FSIZE, 0);
gl.enableVertexAttribArray(a_Position);
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, 5*FSIZE, 2*FSIZE);
gl.enableVertexAttribArray(a_Color);
}
varying
變量的作用其實不僅是進行數(shù)值的傳遞,如果將圖形繪制的mode
修改為gl.TRANGLES
功咒,可以發(fā)現(xiàn)繪制的三角形顏色是漸變的愉阎,造成這一結(jié)果是varying
的作用。
繼續(xù)深入著色器過程力奋,Vertex Shader
到Fragment Shader
要進行兩步操作:
- 圖形裝配:將
Vertex Shader
的頂點信息榜旦,根據(jù)drawArray
的mode
配置在對應的幾何圖形頂點處- 光柵化:將裝配好的圖形分割為一個一個的片元(像素點),因此片元的數(shù)目就是像素點
varying
在賦值顏色給頂點的過程當中景殷,會自動計算從一個頂點到另一個頂點變化的插值章办,并將每一個片元染成對應計算后的顏色,所以才看到一個從頂點顏色漸變的三角形滨彻,這一過程也可以被稱為內(nèi)插藕届。
2. 紋理添加
除了使用單獨的顏色渲染外,也可以直接將圖片貼到幾何圖形的表面亭饵,該圖片也就是紋理休偶。圖片是由像素點組成的,每個像素點就是紋素辜羊,是使用了RGB/RGBA的編碼塊
WebGL的給圖形添加紋理很簡單踏兜,但是要解決三個問題
2.1 瀏覽器跨域
由于WebGL的圖片請求機制,所以如果直接請求本地圖片的時候會出現(xiàn)跨域問題八秃,為了使得開發(fā)和調(diào)試的方便碱妆,可以使用以下方式臨時關(guān)閉瀏覽器的跨域驗證,以Chrome為例:
Windows:
"C:\Program Files\Google\Chrome\Application\chrome.exe" --args --disable-web-security
MAC:
open -a "Google Chrome" --args --disable-web-security --user-data-dir
2.2 紋理坐標和圖片坐標
WebGL中的紋理坐標Y軸正方向是朝上昔驱,其中坐標的四個角為左下(0.0, 0.0)
疹尾,右下(1.0, 0.0)
,右上(1.0, 1.0)
,左上(0.0, 1.0)
纳本。
圖片坐標和WebGL坐標的Y軸方向是相反的窍蓝,正方向朝下,所以在進行紋理映射的時候繁成,需要將坐標軸進行反轉(zhuǎn)吓笙。
2.3 紋理添加
進行紋理映射最主要的有以下五步操作,以繪制一個矩形并貼圖為例:
2.3.1. 將頂點坐標和紋理坐標都加入到類型化數(shù)組中
// 每一行前兩個數(shù)據(jù)為頂點坐標巾腕,后兩個數(shù)據(jù)為紋理坐標
let pointData = new Float32Array([
-0.5, 0.5, 0.0, 1.0,
-0.5, -0.5, 0.0, 0.0,
0.5, 0.5, 1.0, 1.0,
0.5, -0.5, 1.0, 0.0
]);
2.3.2 創(chuàng)建紋理對象和圖片加載
// 創(chuàng)建image對象和紋理對象
let texture = gl.createTexture();
let image = new Image();
// 給image執(zhí)行onload方法的時候進行紋理渲染
image.onload = function() { initTexture() };
// 加載紋理圖片
image.src = 'xxx.png';
PS:圖片的加載過程是異步C婢Α!尊搬!
2.3.3 設置紋理
有6個主要的步驟侮穿,但是實際上去掉第2和6步也是可以運行的,目前對機制還不甚了解毁嗦,之后會持續(xù)關(guān)注
// 1. y軸反轉(zhuǎn)亲茅,因為圖像坐標系y軸向下,而webgl坐標系y軸向上
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
// 2. 開啟紋理單元(0號)狗准,一共有8個
gl.activeTexture(gl.TEXTURE0);
// 3. 綁定紋理對象
gl.bindTexture(gl.TEXTURE_2D, texture);
// 4. 配置紋理參數(shù)(對于非2的冪次尺寸的圖片克锣,需要設置圖片拉伸方式)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
// 如果圖片尺寸為2的冪次,則可以使用以下方式
// gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
// 5. 將圖像分配給紋理對象
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image);
// 6. 將0號紋理傳遞給著色器中的取樣器變量
gl.uniform1i(u_Sampler, 0);
2.3.4 修改著色器程序
// 增加紋理坐標的數(shù)據(jù)接收
const VSHADER_SOURCE = `
attribute vec4 a_Position;
attribute vec2 a_TexCoord;
varying vec2 v_TexCoord;
uniform mat4 u_Matrix;
void main() {
gl_Position = u_Matrix * a_Position;
v_TexCoord = a_TexCoord;
}
`;
// 給片元著色器增加 simlper2D的屬性以及紋理坐標數(shù)據(jù)
const FSHADER_SOURCE = `
precision mediump float;
uniform sampler2D u_Sampler;
varying vec2 v_TexCoord;
void main() {
gl_FragColor = texture2D(u_Sampler, v_TexCoord);
}
`
PS:一定要注意其中關(guān)于圖片的選擇腔长,2.3.3 中關(guān)于配置紋理參數(shù)袭祟,一定要優(yōu)先確定圖片的長和寬是否為2的冪次,從而來決定使用哪種方案進行紋理的匹配捞附,否則將導致紋理解析錯誤:
It maybe non-power-of-2 and have incompatible texture filtering.
當然目前在測試的過程當中巾乳,還存在一個瀏覽器警告提示沒有解決
RENDER WARNING: there is no texture bound to the unit 0
目前雖然有警告,但是紋理渲染并沒有問題鸟召,而且根據(jù)描述來說胆绊,我已經(jīng)對紋理單元0進行了利用了,所以目前還不知道導致的原因欧募,之后如果解決會在評論區(qū)補充原因說明压状。
4. 總結(jié)
基本上來說二維圖形的繪制,渲染就大致這些內(nèi)容跟继,不過仍然存在很多細節(jié)需要自己去嘗試摸索种冬,接下來繼續(xù)=學習進入真正的WebGL發(fā)揮實力的三維圖形的領(lǐng)域!
5. 參考
《WebGL編程指南》