化石原创文章,转载请注明来源并保留原文链接
接上一篇。有了vertex shader和fragment shader源码,我们能通过工具生成相应的javascript文件。这样就能直接用requirejs帮助我们加载,省去很多比较“脏”的异步加载。
不过,光是生成glsl对应的js不够,我们还得使用一个shader的管理部分来让应用方便的使用到对应的shader。所以,工具部分同时生成一个shaderCollection.js,用来放置所有收集到的shader。生成的shaderCollection.js大致长这样:
define([
'./defaultFragment',
'./defaultVertex',
], function(
defaultFragment,
defaultVertex,
){
var collection = [];
collection['defaultFragment'] = defaultFragment;
collection['defaultVertex'] = defaultVertex;
return collection;
});
也是一个符合requirejs规范的文件。
这样我们就得到了shader部分的大致封装。
工具是C#代码,使用了winform,界面如下:
图1:从glsl生成javascript内容的工具
只能两个可交互的地方:
选择目录,选取一个目录,该目录下的glsl的文件都会被收集(包含子目录)
生成,在每个文件同等目录生成名字一样,后缀为.js的文件。和一个shaderCollection.js文件。
注意:
因为最后的shaderCollection.js模块名的考虑,所有的glsl不能重名,即使是在不同目录下。工具本身不查这个错误。
工具github地址:
https://github.com/jycgame/WebGL_Tutorial_Shader-Tool-Gen
到这里,我们的shader部分差不多。也就是材质的核心就差不多了。我们希望我们使用材质的时候,可以这样使用:
material = new Material({vertexShader: shaderCollection['defaultVertex'], fragShader: shaderCollection['defaultFragment'], gl: gl});
在这个设想下面,我们便有了差不多这样的Material封装:
define(['../core/defineProperties'], function(defineProperties) {
/**
* To initialize a material instance.
*
* @param {Material} [material] The material to initialize
*
* @private
*/
function init(material) {
var error;
var gl = material._gl;
var vertShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertShader, material.vertexSource);
gl.compileShader(vertShader);
error = gl.getError();
if (error != gl.NO_ERROR) {
console.log("compile vertex shader error. Error code: " + error);
}
var fragShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragShader, material.fragmentSource);
gl.compileShader(fragShader);
error = gl.getError();
if (error != gl.NO_ERROR) {
console.log("compile fragment shader error. Error code: " + error);
}
var program = gl.createProgram();
gl.attachShader(program, vertShader);
gl.attachShader(program, fragShader);
gl.linkProgram(program);
error = gl.getError();
if (error != gl.NO_ERROR) {
console.log("link shader error. Error code: " + error);
}
material._shaderProgram = program;
}
/**
* class of material
*
* @param {Object} [options] Object with the following properties:
* @param {WebGL context} [options.gl] gl context of webgl
* @param {String} [options.vertexShader] string contetnt of vertext shader
* @param {String} [options.fragShader] string content of fragment shader
*
* @exception
*
* @example
*
* @private
*/
function Material(options) {
this._shaderProgram = null;
this._gl = options.gl;
this._vertexSource = options.vertexShader;
this._fragmentSource = options.fragShader;
init(this);
}
Material.prototype.setMatrix = function (name, matrix) {
var gl = this._gl;
var loc = gl.getUniformLocation(this._shaderProgram, name);
gl.uniformMatrix4fv(loc, false, matrix);
}
Material.prototype.setTexture = function(name, texture) {
var gl = this._gl;
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
var uSampler = gl.getUniformLocation(this._shaderProgram, name);
gl.uniform1i(uSampler, 0);
}
defineProperties(Material.prototype, {
shaderProgram: {
get: function() {
return this._shaderProgram;
}
},
vertexSource: {
get: function() {
return this._vertexSource;
}
},
fragmentSource: {
get: function() {
return this._fragmentSource;
}
},
});
return Material;
});
上述代码,setMatrix和setTexture方法都是Material开出来的API,还使用了javascript的特性做了一些属性上的设置,比如shaderProgram变量,是个只读的属性。用来给应用层得到该材质封装的Shader程序。
该代码离完整还远远不够,这里只讲个意思。后面随教程深入,会慢慢变完整。
使用这样封装的Material,我们的主代码就变成这样:
define([
'vertexReorganizer',
'core/defaultValue',
'gl-matrix/gl-matrix',
'shader/shaderCollection',
'renderer/context',
'renderer/Material'
], function(
helper,
defaultValue,
glMatrix,
shaderCollection,
Context,
Material ) {
'use strict';
var vertices;
var uvs;
var indices;
var texture;
var modelLoaded = false;
var textureLoaded = false;
var modelTransform, viewTransform, projectionTransform;
var modelPositon = glMatrix.vec3.fromValues(0, 0, 0);
var uv;
var coord;
var material;
//model transform
modelTransform = glMatrix.mat4.create();
glMatrix.mat4.fromTranslation(modelTransform, modelPositon);
function clearCanvas(r, g, b, a) {
gl.clearColor(49/255, 77/255, 121/255, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
}
function render(timestamp) {
clearCanvas();
gl.enable(gl.CULL_FACE);
gl.cullFace(gl.BACK);
//如果模型的顶点准备完毕,我们就可以渲染了
if (modelLoaded && textureLoaded) {
gl.useProgram(material.shaderProgram);
//顶点数据(CPU到GPU)
var vertex_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
coord = gl.getAttribLocation(material.shaderProgram, "aVertexPosition");
gl.vertexAttribPointer(coord, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(coord);
//贴图uv数据 (顶点属性之一)
var textureCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, textureCoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(uvs), gl.STATIC_DRAW);
uv = gl.getAttribLocation(material.shaderProgram, "aTextureCoord");
gl.vertexAttribPointer(uv, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(uv);
//索引数据(CPU到GPU)
var index_buffer = gl.createBuffer ();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index_buffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
gl.viewport(0, 0, canvas.width, canvas.height);
//view transform matrix
viewTransform = glMatrix.mat4.create();
var cameraPos = glMatrix.vec3.fromValues(0, 0, 50);
var focalPoint = glMatrix.vec3.fromValues(0, 0, 0);
var up = glMatrix.vec3.fromValues(0, 1, 0);
glMatrix.mat4.lookAt(viewTransform, cameraPos, focalPoint, up);
//projection transform
projectionTransform = glMatrix.mat4.create();
glMatrix.mat4.perspective(projectionTransform, glMatrix.glMatrix.toRadian(15), 1, 0.01, 100);
material.setMatrix("modelTransform", modelTransform);
material.setMatrix("viewTransform", viewTransform);
material.setMatrix("projectionTransform", projectionTransform);
material.setTexture("uSampler", texture);
gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);
gl.disableVertexAttribArray(uv);
gl.disableVertexAttribArray(coord);
}
}
// timestamp is the delta time from last time callback called. Use MS.
function frameUpdate(timestamp) {
//render
render(timestamp);
//schedule the next frame
requestAnimationFrame(frameUpdate);
}
function loadVerticesFromFile(path) {
let xmlHttpRequest = new XMLHttpRequest();
xmlHttpRequest.onreadystatechange = function() {
if (xmlHttpRequest.status == 200 && xmlHttpRequest.readyState == 4) {
var txt = xmlHttpRequest.responseText;
var lines = txt.split('\n');
//ignore lines not contain vertices
var index = 0;
while(lines[index].indexOf('v ') == -1) {
index++;
}
//1, 读取顶点数据
vertices = [];
while(lines[index].indexOf('v ') == 0) {
//这里是每一个顶点数据
var str = lines[index];
var values = str.split(' ');
vertices.push(parseFloat(values[1]));
vertices.push(parseFloat(values[2]));
vertices.push(parseFloat(values[3]));
index++;
}
//2,读取uv数据
uvs = [];
while(lines[index].indexOf('vt ') == 0) {
var str = lines[index];
var values = str.split(' ');
uvs.push(parseFloat(values[1]));
uvs.push(parseFloat(values[2]));
index++;
}
//3,处理法线数据
var normals = [];
while(lines[index].indexOf('vn ') == 0) {
var str = lines[index];
var values = str.split(' ');
normals.push(parseFloat(values[1]));
normals.push(parseFloat(values[2]));
normals.push(parseFloat(values[3]));
index++;
}
while(lines[index].indexOf('f ') == -1) {
index++;
}
//3,处理顶点索引:位置和UV,法线
while(lines[index].indexOf('f ') == 0) {
var line = lines[index];
var values = line.split(' ');
if (values.length == 5) {
// first vertex
helper.extractAndProcessVertex(vertices, uvs, normals, values[1]);
// second vertex
helper.extractAndProcessVertex(vertices, uvs, normals, values[2]);
// third vertex
helper.extractAndProcessVertex(vertices, uvs, normals, values[3]);
//第二个三角形
// 1st vertex
helper.extractAndProcessVertex(vertices, uvs, normals, values[1]);
// 2nd vertex
helper.extractAndProcessVertex(vertices, uvs, normals, values[3]);
// 3rd vertex
helper.extractAndProcessVertex(vertices, uvs, normals, values[4]);
}
else if(values.length == 4) {
// first vertex
helper.extractAndProcessVertex(vertices, uvs, normals, values[1]);
// second vertex
helper.extractAndProcessVertex(vertices, uvs, normals, values[2]);
// third vertex
helper.extractAndProcessVertex(vertices, uvs, normals, values[3]);
}
else {
console.log("Impossible!");
}
index++;
}
vertices = helper.getPositionArray();
uvs = helper.getUvArray();
indices = helper.getIndexArray();
modelLoaded = true;
}
}
xmlHttpRequest.open("GET", path);
xmlHttpRequest.send();
}
function loadTexture(path) {
const image = new Image();
image.onload = function() {
texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
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);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
textureLoaded = true;
}
image.src = path;
}
loadVerticesFromFile("./model/sphere3x.obj");
loadTexture("./texture/uv.jpg");
var canvas = document.getElementById('my_Canvas');
var gl = canvas.getContext("experimental-webgl");
material = new Material({vertexShader: shaderCollection['defaultVertex'], fragShader: shaderCollection['defaultFragment'], gl: gl});
// start frame
frameUpdate();
});
在原来没有这些封装的代码基础上改动而来,属于材质该管的地方,大多已经用上了。看起来代码精简了很多。
完整的代码:
https://github.com/jycgame/WebGL_Tutorial_Material
注意:
原来的一些代码,比如vertexReorganizer.js,我们都改成了符合requirejs规范的。
化石原创文章,转载请注明来源并保留原文链接