WebGL 程序要求你編寫必須編譯和鏈接的著色器程序,然后你需要查看對于這些著色器程序的輸入的位置。這些輸入被稱為制服和屬性,同時用來查找它們的位置的代碼可能是冗長而乏味的。
假設(shè)我們已經(jīng)有了用來編譯和鏈接著色器程序的 WebGL 代碼的典型樣本。下面給出了一組著色器。
頂點著色器:
uniform mat4 u_worldViewProjection;
uniform vec3 u_lightWorldPos;
uniform mat4 u_world;
uniform mat4 u_viewInverse;
uniform mat4 u_worldInverseTranspose;
attribute vec4 a_position;
attribute vec3 a_normal;
attribute vec2 a_texcoord;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
void main() {
v_texCoord = a_texcoord;
v_position = (u_worldViewProjection * a_position);
v_normal = (u_worldInverseTranspose * vec4(a_normal, 0)).xyz;
v_surfaceToLight = u_lightWorldPos - (u_world * a_position).xyz;
v_surfaceToView = (u_viewInverse[3] - (u_world * a_position)).xyz;
gl_Position = v_position;
}
片段著色器:
precision mediump float;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
uniform vec4 u_lightColor;
uniform vec4 u_ambient;
uniform sampler2D u_diffuse;
uniform vec4 u_specular;
uniform float u_shininess;
uniform float u_specularFactor;
vec4 lit(float l ,float h, float m) {
return vec4(1.0,
max(l, 0.0),
(l > 0.0) ? pow(max(0.0, h), m) : 0.0,
1.0);
}
void main() {
vec4 diffuseColor = texture2D(u_diffuse, v_texCoord);
vec3 a_normal = normalize(v_normal);
vec3 surfaceToLight = normalize(v_surfaceToLight);
vec3 surfaceToView = normalize(v_surfaceToView);
vec3 halfVector = normalize(surfaceToLight + surfaceToView);
vec4 litR = lit(dot(a_normal, surfaceToLight),
dot(a_normal, halfVector), u_shininess);
vec4 outColor = vec4((
u_lightColor * (diffuseColor * litR.y + diffuseColor * u_ambient +
u_specular * litR.z * u_specularFactor)).rgb,
diffuseColor.a);
gl_FragColor = outColor;
}
你最終不得不像以下這樣編寫代碼,來對所有要繪制的各種各樣的值進行查找和設(shè)置。
// At initialization time
var u_worldViewProjectionLoc = gl.getUniformLocation(program, "u_worldViewProjection");
var u_lightWorldPosLoc = gl.getUniformLocation(program, "u_lightWorldPos");
var u_worldLoc = gl.getUniformLocation(program, "u_world");
var u_viewInverseLoc = gl.getUniformLocation(program, "u_viewInverse");
var u_worldInverseTransposeLoc = gl.getUniformLocation(program, "u_worldInverseTranspose");
var u_lightColorLoc= gl.getUniformLocation(program, "u_lightColor");
var u_ambientLoc = gl.getUniformLocation(program, "u_ambient");
var u_diffuseLoc = gl.getUniformLocation(program, "u_diffuse");
var u_specularLoc = gl.getUniformLocation(program, "u_specular");
var u_shininessLoc = gl.getUniformLocation(program, "u_shininess");
var u_specularFactorLoc= gl.getUniformLocation(program, "u_specularFactor");
var a_positionLoc = gl.getAttribLocation(program, "a_position");
var a_normalLoc= gl.getAttribLocation(program, "a_normal");
var a_texCoordLoc = gl.getAttribLocation(program, "a_texcoord");
// At init or draw time depending on use.
var someWorldViewProjectionMat = computeWorldViewProjectionMatrix();
var lightWorldPos = [100, 200, 300];
var worldMat = computeWorldMatrix();
var viewInverseMat = computeInverseViewMatrix();
var worldInverseTransposeMat = computeWorldInverseTransposeMatrix();
var lightColor = [1, 1, 1, 1];
var ambientColor = [0.1, 0.1, 0.1, 1];
var diffuseTextureUnit = 0;
var specularColor = [1, 1, 1, 1];
var shininess = 60;
var specularFactor = 1;
// At draw time
gl.useProgram(program);
// Setup all the buffers and attributes
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.enableVertexAttribArray(a_positionLoc);
gl.vertexAttribPointer(a_positionLoc, positionNumComponents, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
gl.enableVertexAttribArray(a_normalLoc);
gl.vertexAttribPointer(a_normalLoc, normalNumComponents, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
gl.enableVertexAttribArray(a_texcoordLoc);
gl.vertexAttribPointer(a_texcoordLoc, texcoordNumComponents, gl.FLOAT, 0, 0);
// Setup the textures used
gl.activeTexture(gl.TEXTURE0 + diffuseTextureUnit);
gl.bindTexture(gl.TEXTURE_2D, diffuseTexture);
// Set all the uniforms.
gl.uniformMatrix4fv(u_worldViewProjectionLoc, false, someWorldViewProjectionMat);
gl.uniform3fv(u_lightWorldPosLoc, lightWorldPos);
gl.uniformMatrix4fv(u_worldLoc, worldMat);
gl.uniformMatrix4fv(u_viewInverseLoc, viewInverseMat);
gl.uniformMatrix4fv(u_worldInverseTransposeLoc, worldInverseTransposeMat);
gl.uniform4fv(u_lightColorLoc, lightColor);
gl.uniform4fv(u_ambientLoc, ambientColor);
gl.uniform1i(u_diffuseLoc, diffuseTextureUnit);
gl.uniform4fv(u_specularLoc, specularColor);
gl.uniform1f(u_shininessLoc, shininess);
gl.uniform1f(u_specularFactorLoc, specularFactor);
gl.drawArrays(...);
這是大量的輸入。
這里有許多方法可以用來簡化它。其中一項建議是要求 WebGL 告訴我們所有的制服和位置,然后設(shè)置函數(shù),來幫助我們建立它們。然后我們可以通過 JavaScript 對象來使設(shè)置我們的設(shè)置更加容易。如果還是不清楚,我們的代碼將會跟以下代碼類似
// At initialiation time
var uniformSetters = createUniformSetters(gl, program);
var attribSetters = createAttributeSetters(gl, program);
var attribs = {
a_position: { buffer: positionBuffer, numComponents: 3, },
a_normal: { buffer: normalBuffer, numComponents: 3, },
a_texcoord: { buffer: texcoordBuffer, numComponents: 2, },
};
// At init time or draw time depending on use.
var uniforms = {
u_worldViewProjection: computeWorldViewProjectionMatrix(...),
u_lightWorldPos: [100, 200, 300],
u_world: computeWorldMatrix(),
u_viewInverse: computeInverseViewMatrix(),
u_worldInverseTranspose: computeWorldInverseTransposeMatrix(),
u_lightColor:[1, 1, 1, 1],
u_ambient: [0.1, 0.1, 0.1, 1],
u_diffuse: diffuseTexture,
u_specular: [1, 1, 1, 1],
u_shininess: 60,
u_specularFactor:1,
};
// At draw time
gl.useProgram(program);
// Setup all the buffers and attributes
setAttributes(attribSetters, attribs);
// Set all the uniforms and textures used.
setUniforms(uniformSetters, uniforms);
gl.drawArrays(...);
這對于我來說,看起來是很多的更小,更容易,更少的代碼。
你甚至可以使用多個 JavaScript 對象,如果那樣適合你的話。如下所示
// At initialiation time
var uniformSetters = createUniformSetters(gl, program);
var attribSetters = createAttributeSetters(gl, program);
var attribs = {
a_position: { buffer: positionBuffer, numComponents: 3, },
a_normal: { buffer: normalBuffer, numComponents: 3, },
a_texcoord: { buffer: texcoordBuffer, numComponents: 2, },
};
// At init time or draw time depending
var uniformsThatAreTheSameForAllObjects = {
u_lightWorldPos: [100, 200, 300],
u_viewInverse: computeInverseViewMatrix(),
u_lightColor:[1, 1, 1, 1],
};
var uniformsThatAreComputedForEachObject = {
u_worldViewProjection: perspective(...),
u_world: computeWorldMatrix(),
u_worldInverseTranspose: computeWorldInverseTransposeMatrix(),
};
var objects = [
{ translation: [10, 50, 100],
materialUniforms: {
u_ambient: [0.1, 0.1, 0.1, 1],
u_diffuse: diffuseTexture,
u_specular: [1, 1, 1, 1],
u_shininess: 60,
u_specularFactor:1,
},
},
{ translation: [-120, 20, 44],
materialUniforms: {
u_ambient: [0.1, 0.2, 0.1, 1],
u_diffuse: someOtherDiffuseTexture,
u_specular: [1, 1, 0, 1],
u_shininess: 30,
u_specularFactor:0.5,
},
},
{ translation: [200, -23, -78],
materialUniforms: {
u_ambient: [0.2, 0.2, 0.1, 1],
u_diffuse: yetAnotherDiffuseTexture,
u_specular: [1, 0, 0, 1],
u_shininess: 45,
u_specularFactor:0.7,
},
},
];
// At draw time
gl.useProgram(program);
// Setup the parts that are common for all objects
setAttributes(attribSetters, attribs);
setUniforms(uniformSetters, uniformThatAreTheSameForAllObjects);
objects.forEach(function(object) {
computeMatricesForObject(object, uniformsThatAreComputedForEachObject);
setUniforms(uniformSetters, uniformThatAreComputedForEachObject);
setUniforms(unifromSetters, objects.materialUniforms);
gl.drawArrays(...);
});
這里有一個使用這些幫助函數(shù)的例子
讓我們向前更進一小步。在上面代碼中,我們設(shè)置了一個擁有我們創(chuàng)建的緩沖區(qū)的變量 attribs
。在代碼中不顯示設(shè)置這些緩沖區(qū)的代碼。例如,如果你想要設(shè)置位置,法線和紋理坐標,你可能會需要這樣的代碼
// a single triangle
var positions = [0, -10, 0, 10, 10, 0, -10, 10, 0];
var texcoords = [0.5, 0, 1, 1, 0, 1];
var normals = [0, 0, 1, 0, 0, 1, 0, 0, 1];
var positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
var texcoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texcoordsBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texcoords), gl.STATIC_DRAW);
var normalBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normals), gl.STATIC_DRAW);
看起來像一種我們也可以簡化的模式。
// a single triangle
var arrays = {
position: { numComponents: 3, data: [0, -10, 0, 10, 10, 0, -10, 10, 0], },
texcoord: { numComponents: 2, data: [0.5, 0, 1, 1, 0, 1], },
normal: { numComponents: 3, data: [0, 0, 1, 0, 0, 1, 0, 0, 1],},
};
var bufferInfo = createBufferInfoFromArrays(gl, arrays);
更短!現(xiàn)在我們可以在渲染時間這樣做
// Setup all the needed buffers and attributes.
setBuffersAndAttributes(gl, attribSetters, bufferInfo);
...
// Draw the geometry.
gl.drawArrays(gl.TRIANGLES, 0, bufferInfo.numElements);
如下所示
如果我們有 indices,這可能會奏效。setAttribsAndBuffers 將會設(shè)置所有的屬性,同時用你的 indices
來設(shè)置 ELEMENT-ARRAY-BUFFER
。 所以你可以調(diào)用 gl.drawElements
.
// an indexed quad
var arrays = {
position: { numComponents: 3, data: [0, 0, 0, 10, 0, 0, 0, 10, 0, 10, 10, 0], },
texcoord: { numComponents: 2, data: [0, 0, 0, 1, 1, 0, 1, 1], },
normal: { numComponents: 3, data: [0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1], },
indices: { numComponents: 3, data: [0, 1, 2, 1, 2, 3], },
};
var bufferInfo = createBufferInfoFromTypedArray(gl, arrays);
同時在渲染時,我們可以調(diào)用 gl.drawElements
,而不是 gl.drawArrays
。
// Setup all the needed buffers and attributes.
setBuffersAndAttributes(gl, attribSetters, bufferInfo);
...
// Draw the geometry.
gl.drawElements(gl.TRIANGLES, bufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
如下所示
createBufferInfoFromArrays
基本上使一個對象與如下代碼相似
bufferInfo = {
numElements: 4,// or whatever the number of elements is
indices: WebGLBuffer, // this property will not exist if there are no indices
attribs: {
a_position: { buffer: WebGLBuffer, numComponents: 3, },
a_normal: { buffer: WebGLBuffer, numComponents: 3, },
a_texcoord: { buffer: WebGLBuffer, numComponents: 2, },
},
};
同時 setBuffersAndAttributes
使用這個對象來設(shè)置所有的緩沖區(qū)和屬性。
最后我們可以進展到我之前認為可能太遠的地步。給出的 position
幾乎總是擁有 3 個組件 (x, y, z),同時 texcoords
幾乎總是擁有 2 個組件,indices 幾乎總是有 3 個組件,同時 normals 總是有 3 個組件,我們就可以讓系統(tǒng)來猜想組件的數(shù)量。
// an indexed quad
var arrays = {
position: [0, 0, 0, 10, 0, 0, 0, 10, 0, 10, 10, 0],
texcoord: [0, 0, 0, 1, 1, 0, 1, 1],
normal: [0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1],
indices: [0, 1, 2, 1, 2, 3],
};
以下是另一個版本。
我不確認我個人喜歡那種版本。我可能猜測出錯,因為它可能猜錯。例如,我可能選擇在我的 texcoord 屬性中添加額外的一組結(jié)構(gòu)坐標,然后它會猜測 2,是錯誤的。當然,如果它猜錯了,你可以像以上示例中那樣指定個數(shù)。我想我擔心的,如果猜測代碼改變了人們的日常的情況可能會打破。全都取決于你。一些人喜歡讓東西盡量像他們考慮的那樣簡單。
我們?yōu)槭裁床辉谥鞒绦蛑胁榭催@些屬性來得出組件的數(shù)量?那是因為,從一個緩沖區(qū)中提供 3 組件 (x, y, z),但是在著色器中使用 vec4
是非常普遍的 。對于屬性 WebGL 會自動設(shè)置 w = 1
.但是那意味著,我們不可能很容易的就知道用戶的意圖,因為他們在他們的著色器中聲明的可能與他們提供的組件的數(shù)量不匹配。
如果想要尋找更多的模式,如下所示
var program = createProgramFromScripts(gl, ["vertexshader", "fragmentshader"]);
var uniformSetters = createUniformSetters(gl, program);
var attribSetters = createAttributeSetters(gl, program);
讓我們將上述代碼簡化成如下代碼
var programInfo = createProgramInfo(gl, ["vertexshader", "fragmentshader"]);
它將返回與下面代碼類似的東西
programInfo = {
program: WebGLProgram, // program we just compiled
uniformSetters: ...,// setters as returned from createUniformSetters,
attribSetters: ..., // setters as returned from createAttribSetters,
}
那是另一個更小的簡化。在我們開始使用多個程序時,它將會派上用場,因為它自動保持與它們的相關(guān)聯(lián)的程序的設(shè)定。
無論如何,這是我想要編寫我自己的 WebGL 程序的風格。在這些教程中的課程中,盡管我已經(jīng)感覺到我需要使用標準的 verbose 方法,這樣人們就不會對 WebGL 是什么和什么是我自己的風格感到困惑。在一些點上,盡管顯示所有的可以獲取這些點的方式的步驟,所以繼續(xù)學習這些課程的可以在這種風格中被使用。
在你自己的代碼中隨便使用這種風格。 createUniformSetters
, createAttributeSetters
, createBufferInfoFromArrays
, setUniforms
和 setBuffersAndAttributes
這些函數(shù)都包含在 webgl-utils.js 文件中,可以在所有的例子中使用。如果你想要一些更多有組織的東西,可以查看 TWGL.js。
更多建議: