WebGL 更少代碼,更多樂趣

2018-10-03 11:32 更新

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

你最終不得不像以下這樣編寫代碼,來對所有要繪制的各種各樣的值進行查找和設(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, setUniformssetBuffersAndAttributes 這些函數(shù)都包含在 webgl-utils.js 文件中,可以在所有的例子中使用。如果你想要一些更多有組織的東西,可以查看 TWGL.js。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號