Cesium的着色器(shader)


化石原创文章,转载请注明来源并保留原文链接


Shader源码在:

Source\Shaders

文件夹下。

这个文件夹包含了.glsl和.js的shader文件。.glsl是原始的、手写的着色器代码。.glsl通过工具移除注释、空格,然后AMD规范,就是我们看到的.js文件。也就是说,我们通过.glsl写出原始的shader代码,但是Cesium要用的是.js的。

注意:一般源代码刚下载的时候是没有这个js文件的,必须通过运行命令:

npm run build

才能生成。这个命令内容可以在package.json看到,build就是呼叫gulp build。官方这么说:

Cesium uses gulp for build tasks, but they are all abstracted away by npm run scripts.

在Cesium.js中,Cesium._shaders是个数组,引入了所有的shader模块(vs和fs)。在实际使用中(我找的是SkyBox.js),不使用这个全局变量,而是直接使用RequireJS从文件中得到。

模块的组合我们通过SkyBox类中的shader使用来说明。

SkyBox在update()中,会检测自己对应的shaderProgram(vs和fs的组合)。如果shaderProgram不存在,就会通过我们刚说过的,RequireJS装载进来的,vs文件和fs文件,创建出一个对应的shaderProgram对象出来。

//SkyBox.prototype.update (SkyBox.js)
if (!defined(command.shaderProgram) || this._useHdr !== useHdr) {
    var fs = new ShaderSource({
        defines : [useHdr ? 'HDR' : ''],
        sources : [SkyBoxFS]
    });

    command.shaderProgram = ShaderProgram.fromCache({
        context : context,
        vertexShaderSource : SkyBoxVS,
        fragmentShaderSource : fs,
        attributeLocations : this._attributeLocations
    });

    this._useHdr = useHdr;
}

上面的代码片段第一行说明了代码的来源。也就是说,这里的shader模块在update方法中,由第8行ShaderProgram.fromCache()开始发起。

ShaderProgram.fromCache()具体执行如下:

//ShaderProgram.js
ShaderProgram.fromCache = function(options) {
	options = defaultValue(options, defaultValue.EMPTY_OBJECT);

	//>>includeStart('debug', pragmas.debug);
	Check.defined('options.context', options.context);
	//>>includeEnd('debug');

	return options.context.shaderCache.getShaderProgram(options);
};

基本上相当于直接调用了下面这个实现:

//ShaderCache.js
ShaderCache.prototype.getShaderProgram = function(options) {
// convert shaders which are provided as strings into ShaderSource objects
// because ShaderSource handles all the automatic including of built-in functions, etc.

	var vertexShaderSource = options.vertexShaderSource;
	var fragmentShaderSource = options.fragmentShaderSource;
	var attributeLocations = options.attributeLocations;

	if (typeof vertexShaderSource === 'string') {
		vertexShaderSource = new ShaderSource({
			sources : [vertexShaderSource]
		});
	}

	if (typeof fragmentShaderSource === 'string') {
		fragmentShaderSource = new ShaderSource({
			sources : [fragmentShaderSource]
		});
	}

	var vertexShaderText = vertexShaderSource.createCombinedVertexShader(this._context);
	var fragmentShaderText = fragmentShaderSource.createCombinedFragmentShader(this._context);

	var keyword = vertexShaderText + fragmentShaderText + JSON.stringify(attributeLocations);
	var cachedShader;

	if (defined(this._shaders[keyword])) {
	    cachedShader = this._shaders[keyword];

	    // No longer want to release this if it was previously released.
	    delete this._shadersToRelease[keyword];
	} else {
		var context = this._context;
		var shaderProgram = new ShaderProgram({
			gl : context._gl,
			logShaderCompilation : context.logShaderCompilation,
			debugShaders : context.debugShaders,
			vertexShaderSource : vertexShaderSource,
			vertexShaderText : vertexShaderText,
			fragmentShaderSource : fragmentShaderSource,
			fragmentShaderText : fragmentShaderText,
			attributeLocations : attributeLocations
		});

		cachedShader = {
			cache : this,
			shaderProgram : shaderProgram,
			keyword : keyword,
			derivedKeywords : [],
			count : 0
		};

		// A shader can't be in more than one cache.
		shaderProgram._cachedShader = cachedShader;
		this._shaders[keyword] = cachedShader;
		++this._numberOfShaders;
	}

	++cachedShader.count;
	return cachedShader.shaderProgram;
};

到上面的getShaderProgram结束,能得到一个ShaderProgram的实例。这个时候给GPU的着色器代码还没有形成。实例此刻只是一个文字版的vs、fs和关联信息的集合而已。 最终的适合GPU运行的代码,一直到真正绘制前才编译、连接出来。这个过程我用下图,也就是Chrome截的一个callstack(程序调用堆栈)来说明。

堆栈图

堆栈的最上层,是createAndLinkProgram()方法,这个方法代码截出来放到下面:

function createAndLinkProgram(gl, shader) {
	var vsSource = shader._vertexShaderText;
	var fsSource = shader._fragmentShaderText;

	var vertexShader = gl.createShader(gl.VERTEX_SHADER);
	gl.shaderSource(vertexShader, vsSource);
	gl.compileShader(vertexShader);

	var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
	gl.shaderSource(fragmentShader, fsSource);
	gl.compileShader(fragmentShader);

	var program = gl.createProgram();
	gl.attachShader(program, vertexShader);
	gl.attachShader(program, fragmentShader);

	gl.deleteShader(vertexShader);
	gl.deleteShader(fragmentShader);

	var attributeLocations = shader._attributeLocations;
	if (defined(attributeLocations)) {
		for ( var attribute in attributeLocations) {
			if (attributeLocations.hasOwnProperty(attribute)) {
				gl.bindAttribLocation(program, attributeLocations[attribute], attribute);
			}
		}
	}

	gl.linkProgram(program);

	var log;
	if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
		var debugShaders = shader._debugShaders;

		// For performance, only check compile errors if there is a linker error.
		if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
			log = gl.getShaderInfoLog(fragmentShader);
			console.error(consolePrefix + 'Fragment shader compile log: ' + log);
			if (defined(debugShaders)) {
				var fragmentSourceTranslation = debugShaders.getTranslatedShaderSource(fragmentShader);
				if (fragmentSourceTranslation !== '') {
					console.error(consolePrefix + 'Translated fragment shader source:\n' + fragmentSourceTranslation);
				} else {
					console.error(consolePrefix + 'Fragment shader translation failed.');
				}
			}

			gl.deleteProgram(program);
			throw new RuntimeError('Fragment shader failed to compile.  Compile log: ' + log);
		}

		if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
			log = gl.getShaderInfoLog(vertexShader);
			console.error(consolePrefix + 'Vertex shader compile log: ' + log);
			if (defined(debugShaders)) {
				var vertexSourceTranslation = debugShaders.getTranslatedShaderSource(vertexShader);
				if (vertexSourceTranslation !== '') {
					console.error(consolePrefix + 'Translated vertex shader source:\n' + vertexSourceTranslation);
				} else {
					console.error(consolePrefix + 'Vertex shader translation failed.');
				}
			}

			gl.deleteProgram(program);
			throw new RuntimeError('Vertex shader failed to compile.  Compile log: ' + log);
		}

		log = gl.getProgramInfoLog(program);
		console.error(consolePrefix + 'Shader program link log: ' + log);
		if (defined(debugShaders)) {
			console.error(consolePrefix + 'Translated vertex shader source:\n' + debugShaders.getTranslatedShaderSource(vertexShader));
			console.error(consolePrefix + 'Translated fragment shader source:\n' + debugShaders.getTranslatedShaderSource(fragmentShader));
		}

		gl.deleteProgram(program);
		throw new RuntimeError('Program failed to link.  Link log: ' + log);
	}

	var logShaderCompilation = shader._logShaderCompilation;

	if (logShaderCompilation) {
		log = gl.getShaderInfoLog(vertexShader);
		if (defined(log) && (log.length > 0)) {
			console.log(consolePrefix + 'Vertex shader compile log: ' + log);
		}
	}

	if (logShaderCompilation) {
		log = gl.getShaderInfoLog(fragmentShader);
		if (defined(log) && (log.length > 0)) {
			console.log(consolePrefix + 'Fragment shader compile log: ' + log);
		}
	}

	if (logShaderCompilation) {
		log = gl.getProgramInfoLog(program);
		if (defined(log) && (log.length > 0)) {
			console.log(consolePrefix + 'Shader program link log: ' + log);
		}
	}

	return program;
}

可以看到,代码通过gl方法编译了vs(第5到第7行)、编译了fs(第9到第11行)、把这两个编译好的结果,连接成了OpenGL ES 可用的GPU程序(第13到第15行、第29行)。

通过上面的图(堆栈图),我们可以看到,这个过程是在Scene.render()【从底往上看的第二行】中发起的,物体在被检测出可见后,为这个物体定制的绘制command就会被执行。而要真正的draw,我们就需要告知GPU该物体用哪个shader程序,给GPU备好。这也就是我们刚分析的工作的内容。关于这个堆栈,涉及的主要是渲染,这个可以参考我的另外一篇文章:这里


化石原创文章,转载请注明来源并保留原文链接


5 评论

  1. 博主,您好;
    根据您的代码可以找到着色器的代码;
    但有许多primitive的类,不知是何时添加上着色器代码的,如circlegeometry
    circlegeometry类下面没有关于着色器代码的,shaders文件夹下也没有
    那么创建的时候是怎么出来的呢?

    1. @giser,我有一阵没看这块东西了。所以你问的这个问题我暂时没法清楚的回答。但是思路一定是这样,3d引擎中一定是”模型(也就是网格群)“和”材质“的组合,而shader只是材质的一个子部分。如果要我追的话,我会从primitive的材质追起。希望给你一点思路。

发表评论

电子邮件地址不会被公开。 必填项已用*标注