threejs - src - WebGLProgram是如何组建Shader的?

threejs - src - WebGLProgram是如何组建Shader的?

WebGLProgram的构建

WebGLProgram构建的时候需要的参数如下:

// \param renderer 渲染器用于获取上下文
// \param cacheKey 区别program的key
// \param parameters 所有参数的集合
// \param bindingStates WebGLBindingStates
function WebGLProgram(renderer, cacheKey, parameters, bindingStates)

这种所有的shader相关的参数都放到parameters的方式,并不友好。

下面给出WebGLProgram构建的基本流程:

parameters的defines,定义的是shader中#define的名称和值。最后会组装成#define name value。目前不清楚RawShaderMaterial具体是什么作用,此处认为parameters内带的isRawShaderMaterial是false。那么在构造WebGLProgram的时候,先会构建vertex,fragment shader的前置片段,vertex的prefix如下(中间忽略了很多内容):

		prefixVertex = [

			generatePrecision( parameters ),

			'#define SHADER_NAME ' + parameters.shaderName,

			customDefines,

			parameters.instancing ? '#define USE_INSTANCING' : '',
			parameters.instancingColor ? '#define USE_INSTANCING_COLOR' : '',

			parameters.supportsVertexTextures ? '#define VERTEX_TEXTURES' : '',

			'#define MAX_BONES ' + parameters.maxBones,
			( parameters.useFog && parameters.fog ) ? '#define USE_FOG' : '',
			( parameters.useFog && parameters.fogExp2 ) ? '#define FOG_EXP2' : '',

			parameters.map ? '#define USE_MAP' : '',
			parameters.envMap ? '#define USE_ENVMAP' : '',
			parameters.envMap ? '#define ' + envMapModeDefine : '',
			parameters.lightMap ? '#define USE_LIGHTMAP' : '',
			parameters.aoMap ? '#define USE_AOMAP' : '',
			parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '',
			parameters.bumpMap ? '#define USE_BUMPMAP' : '',
			parameters.normalMap ? '#define USE_NORMALMAP' : '',
			( parameters.normalMap && parameters.objectSpaceNormalMap ) ? '#define OBJECTSPACE_NORMALMAP' : '',
			( parameters.normalMap && parameters.tangentSpaceNormalMap ) ? '#define TANGENTSPACE_NORMALMAP' : '',

            ......
			
			'\n'

		].filter( filterEmptyLine ).join( '\n' );

对于片段着色器也是同样的处理,很容易的可以体会到,这种将所有的内容都写到一起的方式对于后续的扩展是很不友好的。

Shader拼接方式

threejs中的shader拼接方式是采用的#include片段的方式。即,每个shader片段文件中包含独立的逻辑。入口代码可以参见ShaderLib.js。以phong的模式为例:

{
    phong: {

        uniforms: mergeUniforms( [
            UniformsLib.common,
            UniformsLib.specularmap,
            UniformsLib.envmap,
            UniformsLib.aomap,
            UniformsLib.lightmap,
            UniformsLib.emissivemap,
            UniformsLib.bumpmap,
            UniformsLib.normalmap,
            UniformsLib.displacementmap,
            UniformsLib.fog,
            UniformsLib.lights,
            {
                emissive: { value: new Color( 0x000000 ) },
                specular: { value: new Color( 0x111111 ) },
                shininess: { value: 30 }
            }
        ] ),

        vertexShader: ShaderChunk.meshphong_vert,
        fragmentShader: ShaderChunk.meshphong_frag
	}
}

一个shader组成,分为uniforms,vertexShader,fragmentShader.看一下shader组成的是啥?

不同的include片段组成了vertex string。这种方式是不是就是较好的实现方式呢???

Uniforms的构建

具体如下:

function WebGLUniforms( gl, program ) {

	this.seq = [];
	this.map = {};

	const n = gl.getProgramParameter( program, gl.ACTIVE_UNIFORMS );

	for ( let i = 0; i < n; ++ i ) {

		const info = gl.getActiveUniform( program, i ),
			addr = gl.getUniformLocation( program, info.name );

		parseUniform( info, addr, this );

	}

}

seq对应的示例如下:

map对应的示例如下:

uniform参数传递简述

那么program中的shader参数是如何传递进来的?这里可以先提一下,是在WebGLRenderer中,将material中的uniform参数通过,WebGLUniforms的setValue,以及upload接口传递进来的。具体如下:

WebGLUniforms.upload = function ( gl, seq, values, textures ) {

	for ( let i = 0, n = seq.length; i !== n; ++ i ) {

		const u = seq[ i ],
			v = values[ u.id ];

		if ( v.needsUpdate !== false ) {

			// note: always updating when .needsUpdate is undefined
			u.setValue( gl, v.value, textures );

		}

	}

};

WebGLUniforms.prototype.setValue = function ( gl, name, value, textures ) {

	const u = this.map[ name ];

	if ( u !== undefined ) u.setValue( gl, value, textures );

};

待办项

  • shader define相关的所有参数都放在了parameters中是否合理,有没有更优雅的实现方案?
  • shader的拼接方式,有没有更优雅的实现方案?
  • materials中的参数具体是如何传递到shader uniform中的?
原文链接:,转发请注明来源!