setProgram
setProgram 方法会根据当前渲染上下文(相机、场景、几何体、材质、物体)的状态,判断是否需要切换着色器程序,并完成着色器程序的绑定、uniforms 变量的更新等工作,是连接 JavaScript 3D 逻辑与 WebGL 底层渲染的关键环节。
function setProgram( camera, scene, geometry, material, object ) {
// 如果传入的场景不是Scene类型(可能是Mesh、Line等对象),则使用空场景作为默认场景
if ( scene.isScene !== true ) scene = _emptyScene;
// 重置纹理单元,确保纹理使用的GPU资源正确初始化
textures.resetTextureUnits();
// 获取场景中的雾效配置
const fog = scene.fog;
// 如果材质是MeshStandardMaterial,获取场景的环境贴图;否则为null
const environment = material.isMeshStandardMaterial ? scene.environment : null;
// 确定颜色空间:如果当前没有渲染目标,使用渲染器的输出颜色空间;
// 如果是XR渲染目标,使用其纹理的颜色空间;否则使用线性SRGB颜色空间
const colorSpace = ( _currentRenderTarget === null ) ? _this.outputColorSpace : ( _currentRenderTarget.isXRRenderTarget === true ? _currentRenderTarget.texture.colorSpace : LinearSRGBColorSpace );
// 获取环境贴图:MeshStandardMaterial使用cubeuvmaps缓存,其他材质使用cubemaps缓存;
// 优先使用材质自身的envMap,没有则使用场景的environment
const envMap = ( material.isMeshStandardMaterial ? cubeuvmaps : cubemaps ).get( material.envMap || environment );
// 判断是否使用顶点alpha通道:需开启顶点颜色,且几何体有颜色属性,且颜色属性包含4个分量(rgba)
const vertexAlphas = material.vertexColors === true && !! geometry.attributes.color && geometry.attributes.color.itemSize === 4;
// 判断是否使用顶点切线:需几何体有切线属性,且材质使用法线贴图或各向异性过滤>0
const vertexTangents = !! geometry.attributes.tangent && ( !! material.normalMap || material.anisotropy > 0 );
// 判断是否包含顶点动画目标(位置)
const morphTargets = !! geometry.morphAttributes.position;
// 判断是否包含顶点动画法线
const morphNormals = !! geometry.morphAttributes.normal;
// 判断是否包含顶点动画颜色
const morphColors = !! geometry.morphAttributes.color;
// 初始化色调映射为无映射
let toneMapping = NoToneMapping;
// 如果材质需要色调映射
if ( material.toneMapped ) {
// 当没有渲染目标或为XR渲染目标时,使用渲染器的色调映射配置
if ( _currentRenderTarget === null || _currentRenderTarget.isXRRenderTarget === true ) {
toneMapping = _this.toneMapping;
}
}
// 获取任意存在的动画属性(位置/法线/颜色)
const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color;
// 计算动画目标的数量(如果有动画属性则取其长度,否则为0)
const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0;
// 获取材质的缓存属性(存储渲染相关的状态信息)
const materialProperties = properties.get( material );
// 获取当前渲染状态中的灯光信息
const lights = currentRenderState.state.lights;
// 如果启用了裁剪平面功能
if ( _clippingEnabled === true ) {
// 如果启用了局部裁剪或相机发生变化
if ( _localClippingEnabled === true || camera !== _currentCamera ) {
// 判断是否使用缓存的裁剪状态:相机未变且材质未变时可使用缓存
const useCache =
camera === _currentCamera &&
material.id === _currentMaterialId;
// 设置裁剪状态(未来可能支持ClippingGroup对象,见#8465, #8379)
clipping.setState( material, camera, useCache );
}
}
/**
* 判断是否需要切换着色器程序
*/
// 声明变量用于标记是否需要切换着色器程序,初始值为false(默认不需要)
let needsProgramChange = false;
// 检查材质的版本是否与缓存的版本一致(用于判断材质是否有更新)
if ( material.version === materialProperties.__version ) {
// 1. 如果材质需要光照,且当前光照状态版本与缓存的版本不一致,需切换程序
if ( materialProperties.needsLights && ( materialProperties.lightsStateVersion !== lights.state.version ) ) {
needsProgramChange = true;
// 2. 如果缓存的输出颜色空间与当前需要的颜色空间不一致,需切换程序
} else if ( materialProperties.outputColorSpace !== colorSpace ) {
needsProgramChange = true;
// 3. 如果当前对象是批处理网格,但缓存中标记为非批处理,需切换程序
} else if ( object.isBatchedMesh && materialProperties.batching === false ) {
needsProgramChange = true;
// 4. 如果当前对象不是批处理网格,但缓存中标记为批处理,需切换程序
} else if ( ! object.isBatchedMesh && materialProperties.batching === true ) {
needsProgramChange = true;
// 5. 如果当前对象是实例化网格,但缓存中标记为非实例化,需切换程序
} else if ( object.isInstancedMesh && materialProperties.instancing === false ) {
needsProgramChange = true;
// 6. 如果当前对象不是实例化网格,但缓存中标记为实例化,需切换程序
} else if ( ! object.isInstancedMesh && materialProperties.instancing === true ) {
needsProgramChange = true;
// 7. 如果当前对象是骨骼动画网格,但缓存中标记为非骨骼动画,需切换程序
} else if ( object.isSkinnedMesh && materialProperties.skinning === false ) {
needsProgramChange = true;
// 8. 如果当前对象不是骨骼动画网格,但缓存中标记为骨骼动画,需切换程序
} else if ( ! object.isSkinnedMesh && materialProperties.skinning === true ) {
needsProgramChange = true;
// 9. 如果当前对象是实例化网格,缓存中标记启用实例颜色,但实际没有实例颜色
} else if ( object.isInstancedMesh && materialProperties.instancingColor === true && object.instanceColor === null ) {
needsProgramChange = true;
// 10. 如果当前对象是实例化网格,缓存中标记禁用实例颜色,但实际有实例颜色
} else if ( object.isInstancedMesh && materialProperties.instancingColor === false && object.instanceColor !== null ) {
needsProgramChange = true;
// 11. 如果当前对象是实例化网格,缓存中标记启用实例变形,但实际没有变形纹理
} else if ( object.isInstancedMesh && materialProperties.instancingMorph === true && object.morphTexture === null ) {
needsProgramChange = true;
// 12. 如果当前对象是实例化网格,缓存中标记禁用实例变形,但实际有变形纹理
} else if ( object.isInstancedMesh && materialProperties.instancingMorph === false && object.morphTexture !== null ) {
needsProgramChange = true;
// 13. 如果缓存的环境贴图与当前需要的环境贴图不一致
} else if ( materialProperties.envMap !== envMap ) {
needsProgramChange = true;
// 14. 如果材质启用了雾化效果,且缓存的雾化设置与当前场景的雾化设置不一致
} else if ( material.fog === true && materialProperties.fog !== fog ) {
needsProgramChange = true;
// 15. 如果缓存的裁剪平面数量或裁剪交集数量与当前不一致
} else if ( materialProperties.numClippingPlanes !== undefined &&
( materialProperties.numClippingPlanes !== clipping.numPlanes ||
materialProperties.numIntersection !== clipping.numIntersection ) ) {
needsProgramChange = true;
// 16. 如果缓存的顶点透明度设置与当前几何体的顶点透明度状态不一致
} else if ( materialProperties.vertexAlphas !== vertexAlphas ) {
needsProgramChange = true;
// 17. 如果缓存的顶点切线设置与当前几何体的顶点切线状态不一致
} else if ( materialProperties.vertexTangents !== vertexTangents ) {
needsProgramChange = true;
// 18. 如果缓存的变形目标设置与当前几何体的变形目标状态不一致
} else if ( materialProperties.morphTargets !== morphTargets ) {
needsProgramChange = true;
// 19. 如果缓存的变形法线设置与当前几何体的变形法线状态不一致
} else if ( materialProperties.morphNormals !== morphNormals ) {
needsProgramChange = true;
// 20. 如果缓存的变形颜色设置与当前几何体的变形颜色状态不一致
} else if ( materialProperties.morphColors !== morphColors ) {
needsProgramChange = true;
// 21. 如果缓存的色调映射设置与当前需要的色调映射不一致
} else if ( materialProperties.toneMapping !== toneMapping ) {
needsProgramChange = true;
// 22. 如果缓存的变形目标数量与当前几何体的变形目标数量不一致
} else if ( materialProperties.morphTargetsCount !== morphTargetsCount ) {
needsProgramChange = true;
}
// 如果材质版本不一致(材质发生了更新)
} else {
// 需要更换着色器程序,并更新缓存的材质版本
needsProgramChange = true;
materialProperties.__version = material.version;
}
/**
* 获取或创建着色器程序
*/
let program = materialProperties.currentProgram;
if ( needsProgramChange === true ) {
program = getProgram( material, scene, object );
}
let refreshProgram = false;
let refreshMaterial = false;
let refreshLights = false;
/**
* 更新渲染状态与 uniforms 变量
*/
const p_uniforms = program.getUniforms(),
m_uniforms = materialProperties.uniforms;
if ( state.useProgram( program.program ) ) {
// 切换了新的着色器程序,需要刷新状态
refreshProgram = true;
refreshMaterial = true;
refreshLights = true;
}
if ( material.id !== _currentMaterialId ) {
// 更新相机相关的 uniforms(投影矩阵、视图矩阵、相机位置等)
_currentMaterialId = material.id;
refreshMaterial = true;
}
// 如果需要刷新程序(着色器程序)或者当前相机与之前的相机不同
if ( refreshProgram || _currentCamera !== camera ) {
// 处理通用的相机相关 uniforms(着色器 uniform 变量)
// 将相机的投影矩阵传递给着色器的 'projectionMatrix' 变量
p_uniforms.setValue( _gl, 'projectionMatrix', camera.projectionMatrix );
// 将相机的视图矩阵(世界矩阵的逆矩阵)传递给着色器的 'viewMatrix' 变量
p_uniforms.setValue( _gl, 'viewMatrix', camera.matrixWorldInverse );
// 获取着色器中 'cameraPosition' 变量的引用
const uCamPos = p_uniforms.map.cameraPosition;
// 如果着色器中存在 'cameraPosition' 变量
if ( uCamPos !== undefined ) {
// 计算相机在世界空间中的位置,并传递给着色器的 'cameraPosition' 变量
uCamPos.setValue( _gl, _vector3.setFromMatrixPosition( camera.matrixWorld ) );
}
// 如果浏览器支持对数深度缓冲
if ( capabilities.logarithmicDepthBuffer ) {
// 计算对数深度缓冲的系数,并传递给着色器的 'logDepthBufFC' 变量
p_uniforms.setValue( _gl, 'logDepthBufFC', 2.0 / ( Math.log( camera.far + 1.0 ) / Math.LN2 ) );
}
// 考虑将 isOrthographic 移至 UniformLib 和 WebGLMaterials(参考 GitHub 讨论)
// 对特定类型的材质,传递相机是否为正交相机的标志
if ( material.isMeshPhongMaterial ||
material.isMeshToonMaterial ||
material.isMeshLambertMaterial ||
material.isMeshBasicMaterial ||
material.isMeshStandardMaterial ||
material.isShaderMaterial ) {
// 将相机是否为正交相机的布尔值传递给着色器的 'isOrthographic' 变量
p_uniforms.setValue( _gl, 'isOrthographic', camera.isOrthographicCamera === true );
}
// 如果当前相机与之前记录的相机不同(相机发生了切换)
if ( _currentCamera !== camera ) {
// 更新当前相机的引用为新相机
_currentCamera = camera;
// 光照相关的 uniform 变量依赖于相机,因此需要强制更新
// 立即更新(如果当前材质支持光照)或在下次激活支持光照的材质时更新
refreshMaterial = true; // 在材质变化时设为 true,标记需要刷新材质相关 uniform在材质变化时设为 true,标记需要刷新材质相关 uniform
refreshLights = true; // 保持为 true 直到光照相关 uniform 更新完成
}
}
/**
* 检查当前渲染的对象是否是骨骼动画网格(SkinnedMesh)
*/
if ( object.isSkinnedMesh ) {
// 向着色器传递可选的绑定矩阵(bindMatrix)及其逆矩阵(bindMatrixInverse)
// 这些矩阵用于将骨骼空间中的顶点坐标转换到模型空间
p_uniforms.setOptional( _gl, object, 'bindMatrix' );
p_uniforms.setOptional( _gl, object, 'bindMatrixInverse' );
// 获取骨骼动画网格关联的骨骼系统(Skeleton)
const skeleton = object.skeleton;
// 如果骨骼系统存在
if ( skeleton ) {
// 如果骨骼纹理(boneTexture)尚未创建,则计算并生成它
// 骨骼纹理是一种高效传递大量骨骼变换矩阵的方式(尤其当骨骼数量较多时)
if ( skeleton.boneTexture === null ) skeleton.computeBoneTexture();
// 将骨骼纹理传递给着色器的'boneTexture'变量,供GPU读取骨骼变换数据
// 第三个参数textures用于管理纹理单元分配
p_uniforms.setValue( _gl, 'boneTexture', skeleton.boneTexture, textures );
}
}
if ( object.isBatchedMesh ) {
// 批处理网格:矩阵纹理、ID 纹理、颜色纹理(批量传递多个子网格数据)
// 向着色器传递可选的批处理纹理(batchingTexture)变量
// 若着色器中定义了该变量则传递,否则忽略
p_uniforms.setOptional( _gl, object, 'batchingTexture' );
// 将批处理网格中存储的矩阵纹理(_matricesTexture)传递给着色器的'batchingTexture'变量
// 第三个参数textures用于管理WebGL纹理单元的分配
p_uniforms.setValue( _gl, 'batchingTexture', object._matricesTexture, textures );
}
/**
* 变形目标(Morph Targets)更新
*/
// 获取几何体中存储的变形目标属性集合(包含位置、法线、颜色等可能的变形数据)
const morphAttributes = geometry.morphAttributes;
// 检查是否存在位置、法线或颜色的变形目标数据
if ( morphAttributes.position !== undefined || morphAttributes.normal !== undefined || ( morphAttributes.color !== undefined ) ) {
// 如果存在任何变形目标数据,调用morphtargets.update更新相关着色器变量
// 该方法会将变形目标的权重、偏移量等数据传递给GPU,实现模型的变形动画效果
morphtargets.update( object, geometry, program );
}
// 检查是否需要更新阴影接收状态:
// 1. 当材质需要刷新(refreshMaterial为true)时
// 2. 或缓存的阴影接收状态与当前对象的阴影接收状态不一致时
if ( refreshMaterial || materialProperties.receiveShadow !== object.receiveShadow ) {
// 更新缓存中的阴影接收状态为当前对象的状态
materialProperties.receiveShadow = object.receiveShadow;
// 将当前对象的阴影接收状态(布尔值)传递给着色器的'receiveShadow'变量
// 用于控制该对象是否接收其他物体投射的阴影
p_uniforms.setValue( _gl, 'receiveShadow', object.receiveShadow );
}
// 检查当前材质是否为MeshGouraudMaterial,且设置了环境贴图
if ( material.isMeshGouraudMaterial && material.envMap !== null ) {
// 将环境贴图(envMap)赋值给材质uniform变量的envMap属性
// 环境贴图用于模拟物体反射周围环境的效果
m_uniforms.envMap.value = envMap;
// 设置flipEnvMap变量:当环境贴图是普通立方体贴图(非渲染目标纹理)时,值为-1,否则为1
// 该值用于处理立方体贴图的纹理坐标翻转,确保反射方向正确
m_uniforms.flipEnvMap.value = ( envMap.isCubeTexture && envMap.isRenderTargetTexture === false ) ? - 1 : 1;
}
// 检查当前材质是否为MeshStandardMaterial,且自身未设置envMap,但场景设置了全局环境
if ( material.isMeshStandardMaterial && material.envMap === null && scene.environment !== null ) {
// 当材质未单独设置环境贴图但场景有全局环境时,使用场景的环境强度
// 控制材质受全局环境影响的程度
m_uniforms.envMapIntensity.value = scene.environmentIntensity;
}
/**
* 材质与光照相关 uniforms
*/
// 当需要刷新材质(refreshMaterial为true)时执行以下逻辑
if ( refreshMaterial ) {
// 向着色器传递色调映射曝光度(toneMappingExposure),控制画面整体亮度
p_uniforms.setValue( _gl, 'toneMappingExposure', _this.toneMappingExposure );
// 如果当前材质需要光照信息(如Phong、Standard等材质)
if ( materialProperties.needsLights ) {
// 标记所有光照相关的Uniform变量需要更新(根据refreshLights状态)
// 例如环境光、平行光、点光源等的参数
markUniformsLightsNeedsUpdate( m_uniforms, refreshLights );
}
// 刷新多个材质共有的Uniform变量
// 如果场景有雾(fog)且当前材质启用了雾效果
if ( fog && material.fog === true ) {
// 更新雾相关的Uniform变量(如雾的颜色、密度、近/远平面等)
materials.refreshFogUniforms( m_uniforms, fog );
}
// 刷新当前材质特有的Uniform变量
// 传递材质、像素比、高度、透射渲染目标等参数,处理如颜色、纹理、透明度等材质属性
materials.refreshMaterialUniforms( m_uniforms, material, _pixelRatio, _height, currentRenderState.state.transmissionRenderTarget[ camera.id ] );
// 将所有材质Uniform变量上传到GPU
WebGLUniforms.upload( _gl, getUniformList( materialProperties ), m_uniforms, textures );
}
// 当材质是ShaderMaterial且标记了uniforms需要更新时
if ( material.isShaderMaterial && material.uniformsNeedUpdate === true ) {
// 强制上传Uniform变量到GPU,并重置更新标记
WebGLUniforms.upload( _gl, getUniformList( materialProperties ), m_uniforms, textures );
material.uniformsNeedUpdate = false;
}
// 当材质是SpriteMaterial(精灵材质)时
if ( material.isSpriteMaterial ) {
// 向着色器传递精灵的中心点坐标(用于精灵的旋转/缩放基准点计算)
p_uniforms.setValue( _gl, 'center', object.center );
}
/**
* 处理通用的模型矩阵(将模型的空间变换信息传递给着色器)
*/
// 模型视图矩阵:将模型坐标转换到视图(相机)坐标
p_uniforms.setValue( _gl, 'modelViewMatrix', object.modelViewMatrix );
// 法向量矩阵:用于将模型的法向量从模型空间转换到视图空间(考虑缩放影响)
p_uniforms.setValue( _gl, 'normalMatrix', object.normalMatrix );
// 模型矩阵:将模型坐标转换到世界坐标
p_uniforms.setValue( _gl, 'modelMatrix', object.matrixWorld );
// 处理UBO(Uniform Buffer Object,Uniform缓冲对象)
// 仅对自定义着色器材质(ShaderMaterial)和原始着色器材质(RawShaderMaterial)生效
if ( material.isShaderMaterial || material.isRawShaderMaterial ) {
// 获取材质中定义的Uniform缓冲组
const groups = material.uniformsGroups;
// 遍历所有Uniform缓冲组
for ( let i = 0, l = groups.length; i < l; i ++ ) {
const group = groups[ i ];
// 更新缓冲组的数据(将最新的Uniform值写入缓冲)
uniformsGroups.update( group, program );
// 将缓冲组绑定到着色器程序的指定绑定点
uniformsGroups.bind( group, program );
}
}
return program;
}