鍍金池/ 教程/ HTML/ WebGL - 更少的代碼,更多的樂趣
WebGL 文本 HTML
WebGL 文本 Canvas 2D
WebGL 2D 圖像旋轉(zhuǎn)
WebGL 圖像處理(續(xù))
WebGL 2D 矩陣
WebGL 繪制多個東西
WebGL 圖像處理
WebGL 2D 圖像轉(zhuǎn)換
WebGL 3D 透視
WebGL 是如何工作的
WebGL 文本 紋理
WebGL 2D 圖像伸縮
WebGL 場景圖
WebGL 3D 攝像機
WebGL 文本 使用字符紋理
WebGL 正交 3D
WebGL 基本原理
WebGL - 更少的代碼,更多的樂趣
WebGL 著色器和 GLSL

WebGL - 更少的代碼,更多的樂趣

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;
}

你最終不得不像以下這樣編寫代碼,來對所有要繪制的各種各樣的值進(jìn)行查找和設(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ù)的例子

讓我們向前更進(jìn)一小步。在上面代碼中,我們設(shè)置了一個擁有我們創(chuàng)建的緩沖區(qū)的變量 attribs。在代碼中不顯示設(shè)置這些緩沖區(qū)的代碼。例如,如果你想要設(shè)置位置,法線和紋理坐標(biāo),你可能會需要這樣的代碼

// 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ū)和屬性。

最后我們可以進(jìn)展到我之前認(rèn)為可能太遠(yuǎn)的地步。給出的 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],
};

以下是另一個版本。

我不確認(rèn)我個人喜歡那種版本。我可能猜測出錯,因為它可能猜錯。例如,我可能選擇在我的 texcoord 屬性中添加額外的一組結(jié)構(gòu)坐標(biāo),然后它會猜測 2,是錯誤的。當(dāng)然,如果它猜錯了,你可以像以上示例中那樣指定個數(shù)。我想我擔(dān)心的,如果猜測代碼改變了人們的日常的情況可能會打破。全都取決于你。一些人喜歡讓東西盡量像他們考慮的那樣簡單。

我們?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 程序的風(fēng)格。在這些教程中的課程中,盡管我已經(jīng)感覺到我需要使用標(biāo)準(zhǔn)的 verbose 方法,這樣人們就不會對 WebGL 是什么和什么是我自己的風(fēng)格感到困惑。在一些點上,盡管顯示所有的可以獲取這些點的方式的步驟,所以繼續(xù)學(xué)習(xí)這些課程的可以在這種風(fēng)格中被使用。

在你自己的代碼中隨便使用這種風(fēng)格。 createUniformSetters, createAttributeSetters, createBufferInfoFromArrays, setUniformssetBuffersAndAttributes 這些函數(shù)都包含在 webgl-utils.js 文件中,可以在所有的例子中使用。如果你想要一些更多有組織的東西,可以查看 TWGL.js。

上一篇:WebGL 場景圖