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

grassofsky發表於2022-01-24

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,這些#include的檔案中有些僅僅是片段,並不是一個個函式封裝起來的,在具體編寫的時候,很有可能不知道當前程式碼片段能夠訪問到的變數到底是什麼。這種方式是不是就是較好的實現方式呢???

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

};

待辦項

相關文章