Skip to content

着色器程序与编译

本文档解释 Three.js 如何编译和管理 WebGL 着色器程序,将材质属性和场景光照转换为优化的 GLSL 代码。该系统基于材质特性动态生成着色器,缓存已编译程序以供重用,并管理 WebGL 编译管线。

有关 uniform 如何上传到 GPU 的信息,见更广泛的状态管理文档。有关着色器内执行的光照计算,见光照与阴影

系统概览

着色器编译系统由两个主要组件组成:WebGLPrograms 管理程序缓存和参数生成,而 WebGLProgram 处理 GLSL 代码的实际编译。该系统为每个材质特性(贴图、光照、效果)组合生成唯一着色器变体,并缓存它们以避免冗余编译。

SVG
100%

图表:着色器编译管线

流程从材质需要渲染时开始。WebGLPrograms.getParameters() 分析材质、场景和几何体以产生包含特性标志和定义的参数对象。这生成标识唯一着色器变体的缓存键。如果程序存在于 programsMap 中,则重用;否则,WebGLProgram 通过从 ShaderLib 模板和 ShaderChunk 片段组装 GLSL 来编译新程序。

WebGLPrograms:缓存管理

WebGLPrograms 类维护已编译程序的缓存并生成定义每个唯一着色器变体的参数。它将缓存键映射到 WebGLProgram 实例并跟踪使用计数以进行垃圾回收。

SVG
100%

图表:程序获取流

参数生成

getParameters() 函数分析材质属性以确定启用哪些着色器特性。它产生带有布尔标志、数值计数器和字符串标识符的参数对象。

参数类型用途示例
材质特性启用/禁用着色器块map, normalMap, envMap, clearcoat
光照计数大小 uniform 数组numDirLights, numPointLights, numSpotLights
纹理通道UV 通道选择mapUv, normalMapUv, alphaMapUv
渲染状态输出配置toneMapping, shadowMapType, outputColorSpace
几何体特性顶点属性使用vertexColors, skinning, morphTargets

该函数检查 material.mapmaterial.normalMap 等材质属性,并将它们转换为布尔标志(HAS_MAPHAS_NORMALMAP)。它按类型计数光源,确定使用哪些纹理通道,并检查几何体属性:

// 参数生成逻辑示例(概念性)
const HAS_MAP = !! material.map;
const HAS_NORMALMAP = !! material.normalMap;
const HAS_ANISOTROPY = material.anisotropy > 0;

parameters.map = HAS_MAP;
parameters.normalMap = HAS_NORMALMAP;
parameters.anisotropy = HAS_ANISOTROPY;
parameters.numDirLights = lights.directional.length;

缓存键生成

getProgramCacheKey() 函数通过将值连接将参数转换为字符串键。具有相同参数的两个程序产生相同键,实现高效缓存。

键包含:

  • 着色器 ID 或自定义着色器哈希值(shaderID / customVertexShaderID
  • 来自 material.defines 的所有定义
  • 数值参数(精度、光源计数、UV 通道)
  • 通过 _programLayers 编码为位掩码的布尔标志
  • 来自 material.customProgramCacheKey() 的自定义缓存键

布尔标志使用 Layers 对象将多个标志打包为整数,减少键长度:

// 标志打包进 _programLayers.mask
_programLayers.enable(0); // instancing
_programLayers.enable(1); // instancingColor
_programLayers.enable(4); // envMap
array.push(_programLayers.mask); // 单个整数

程序生命周期

程序通过 usedTimes 引用计数。当材质被渲染时,acquireProgram() 递增计数。当通过 releaseProgram() 释放时,计数递减,在零时删除程序。

WebGLProgram:编译过程

WebGLProgram 构造函数编译顶点和片元着色器并将它们链接为 WebGL 程序。它注入预处理器定义,从 ShaderChunk 解析包含项,并处理 GLSL 版本转换。

图表:着色器编译步骤

前缀注入

构造函数组装包含 GLSL 版本、精度指令、定义和 uniform 声明的着色器前缀。对于非原始材质,广泛的前缀启用/禁用着色器特性:

顶点着色器前缀(摘录):

#version 300 es
precision highp float;
#define SHADER_TYPE MeshStandardMaterial
#define SHADER_NAME MeshStandardMaterial
#define STANDARD
#define USE_MAP
#define USE_NORMALMAP
#define USE_ENVMAP
#define ENVMAP_MODE_REFLECTION
uniform mat4 modelMatrix;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
// ... 更多 uniform 和定义

片元着色器前缀(摘录):

#version 300 es
precision highp float;
#define SHADER_TYPE MeshStandardMaterial
#define STANDARD
#define USE_MAP
#define USE_NORMALMAP
#define TONE_MAPPING
// 内联色调映射函数
vec3 toneMapping( vec3 color ) { return ACESFilmicToneMapping( color ); }
// 内联色彩空间转换
vec4 linearToOutputTexel( vec4 value ) { return sRGBTransferOETF( vec4( value.rgb * mat3(...), value.a ) ); }

前缀注入:

  • 定义:如 USE_MAPUSE_NORMALMAPUSE_ENVMAP 的特性标志
  • 精度:所有类型的 highpmediumplowp
  • Uniform:标准矩阵和向量
  • 内联函数:色调映射、色彩空间转换、亮度

包含项解析

resolveIncludes() 函数用来自 ShaderChunk 的 GLSL 替换 #include <name> 指令。它递归处理嵌套包含项:

// 模式:#include <chunk_name>
const includePattern = /^[ t]*#include +<([wd./]+)>/gm;

function resolveIncludes( string ) {
    return string.replace( includePattern, includeReplacer );
}

function includeReplacer( match, include ) {
    let string = ShaderChunk[ include ];
    // 如果未找到块则报错
    return resolveIncludes( string ); // 递归
}

这允许 ShaderLib 中的着色器模板是模块化的。例如,meshphysical_frag 包含:

#include <common>
#include <packing>
#include <lights_pars_begin>
#include <lights_physical_pars_fragment>
#include <shadowmap_pars_fragment>

每个 #include 被替换为来自相应 ShaderChunk 条目的实际 GLSL 代码。

宏替换

包含项解析后,代码用实际值替换占位符宏:

replaceLightNums() 替换光源计数的常量:

// 替换前
#if NUM_DIR_LIGHTS > 0
uniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];
#endif

// 替换后(如果 parameters.numDirLights === 3)
#if 3 > 0
uniform DirectionalLight directionalLights[ 3 ];
#endif

replaceClippingPlaneNums() 替换剪切平面计数:

// NUM_CLIPPING_PLANES → parameters.numClippingPlanes
// UNION_CLIPPING_PLANES → (numClippingPlanes - numClipIntersection)

循环展开

unrollLoops() 函数在编译时展开带 pragma 注解的循环以提高 GPU 性能:

// 展开前
#pragma unroll_loop_start
for ( int i = 0; i < 3; i ++ ) {
    lights[ i ] = computeLight( i );
}
#pragma unroll_loop_end

// 展开后
lights[ 0 ] = computeLight( 0 );
lights[ 1 ] = computeLight( 1 );
lights[ 2 ] = computeLight( 2 );

循环展开消除在动态循环上有困难的 GPU 的分支开销。当光源计数在编译时已知时,系统自动为光源迭代展开循环。

GLSL 3.0 转换

对于非原始材质,系统将 GLSL 1.0 语法转换为 GLSL 3.0 (ES 300):

GLSL 1.0GLSL 3.0
attributein(顶点着色器)
varyingout(顶点),in(片元)
texture2D()texture()
gl_FragColorlayout(location=0) out vec4 pc_fragColor

此转换允许 ShaderLib 模板使用更简单语法同时输出现代 GLSL。

着色器编译与链接

预处理后,代码创建 WebGL 着色器对象并编译它们:

const glVertexShader = WebGLShader( gl, gl.VERTEX_SHADER, vertexGlsl );
const glFragmentShader = WebGLShader( gl, gl.FRAGMENT_SHADER, fragmentGlsl );

gl.attachShader( program, glVertexShader );
gl.attachShader( program, glFragmentShader );

// 为 morphTargets 强制 'position' 到索引 0
if ( parameters.morphTargets === true ) {
    gl.bindAttribLocation( program, 0, 'position' );
}

gl.linkProgram( program );

WebGLShader 函数包装 gl.createShader()gl.shaderSource()gl.compileShader(),返回已编译着色器句柄。

错误处理

编译错误在 onFirstUse() 中检测,该方法在程序首次访问时(对于 uniform 或属性)惰性调用。这通过 KHR_parallel_shader_compile 支持异步编译:

function onFirstUse( self ) {
    if ( renderer.debug.checkShaderErrors ) {
        const programLog = gl.getProgramInfoLog( program );
        const vertexLog = gl.getShaderInfoLog( glVertexShader );
        const fragmentLog = gl.getShaderInfoLog( glFragmentShader );
        
        if ( gl.getProgramParameter( program, gl.LINK_STATUS ) === false ) {
            const vertexErrors = getShaderErrors( gl, glVertexShader, 'vertex' );
            const fragmentErrors = getShaderErrors( gl, glFragmentShader, 'fragment' );
            error( 'THREE.WebGLProgram: Shader Error ... ', vertexErrors, fragmentErrors );
        }
    }
    
    // 提取 uniform 和属性
    cachedUniforms = new WebGLUniforms( gl, program );
    cachedAttributes = fetchAttributeLocations( gl, program );
}

getShaderErrors() 函数解析错误消息以提取行号并显示源的上下文:

function getShaderErrors( gl, shader, type ) {
    const errors = gl.getShaderInfoLog( shader ).trim();
    const errorMatches = /ERROR: 0:(d+)/.exec( errors );
    if ( errorMatches ) {
        const errorLine = parseInt( errorMatches[ 1 ] );
        return type.toUpperCase() + 'nn' + errors + 'nn' + handleSource( gl.getShaderSource( shader ), errorLine );
    }
    return errors;
}

function handleSource( string, errorLine ) {
    const lines = string.split( 'n' );
    const from = Math.max( errorLine - 6, 0 );
    const to = Math.min( errorLine + 6, lines.length );
    // 返回带行号的格式化源,突出显示错误行
}

并行编译

如果 KHR_parallel_shader_compile 扩展可用,程序异步编译。isReady() 方法检查完成:

let programReady = ( parameters.rendererExtensionParallelShaderCompile === false );

this.isReady = function () {
    if ( programReady === false ) {
        programReady = gl.getProgramParameter( program, COMPLETION_STATUS_KHR );
    }
    return programReady;
};

这允许渲染器启动多个编译而不阻塞,在后续帧中检查完成。

ShaderLib:内置着色器模板

ShaderLib 对象包含标准材质的着色器模板。每个条目定义 uniformsvertexShaderfragmentShader

SVG
100%

图表:ShaderLib 材质着色器

着色器结构

每个着色器条目合并 UniformsLib 组以获得通用功能:

示例:physical 着色器

ShaderLib.physical = {
    uniforms: mergeUniforms([
        ShaderLib.standard.uniforms,
        {
            clearcoat: { value: 0 },
            clearcoatMap: { value: null },
            clearcoatRoughness: { value: 0 },
            iridescence: { value: 0 },
            sheen: { value: 0 },
            transmission: { value: 0 },
            thickness: { value: 0 },
            // ... 更多物理属性
        }
    ]),
    vertexShader: ShaderChunk.meshphysical_vert,
    fragmentShader: ShaderChunk.meshphysical_frag
};

physical 着色器通过添加清漆、彩虹色、光泽和透射的 uniform 来扩展 standard。顶点和片元着色器引用 ShaderChunk 条目,它们是带 #include 指令的 GLSL 字符串。

Uniform 库

UniformsLib 对象分组相关 uniform:

用途Uniforms
common基础材质diffuse, opacity, map, alphaMap, alphaTest
envmap环境映射envMap, envMapRotation, reflectivity, ior
lights光照数组directionalLights, pointLights, spotLights, shadow matrices
fog雾效参数fogColor, fogNear, fogFar, fogDensity
normalmap法线映射normalMap, normalScale
bumpmap凹凸映射bumpMap, bumpScale
displacementmap顶点置换displacementMap, displacementScale, displacementBias

着色器通过 mergeUniforms() 组合这些库:

// Lambert 着色器 uniform
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) }, envMapIntensity: { value: 1 } }
])

ShaderChunk:GLSL 片段库

ShaderChunk 对象包含可重用的 GLSL 代码片段。着色器通过 #include <chunk_name> 指令包含这些:

SVG
100%

图表:关键 ShaderChunk 片段

通用片段

common 定义数学常数和实用函数:

#define RECIPROCAL_PI 0.3183098861837907
#define EPSILON 1e-6

float pow2( const in float x ) { return x*x; }
vec3 pow2( const in vec3 x ) { return x*x; }
float saturate( const in float a ) { return clamp( a, 0.0, 1.0 ); }

bsdfs 提供 BRDF(双向反射分布函数)计算:

float D_BlinnPhong( const in float shininess, const in float dotNH ) {
    return RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess );
}

vec3 BRDF_BlinnPhong( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in vec3 specularColor, const in float shininess ) {
    vec3 halfDir = normalize( lightDir + viewDir );
    float dotNH = saturate( dot( normal, halfDir ) );
    vec3 F = F_Schlick( specularColor, 1.0, dotVH );
    float D = D_BlinnPhong( shininess, dotNH );
    return F * D * 0.25;
}

光照片段

lights_pars_begin 声明光源结构和 uniform:

uniform vec3 ambientLightColor;

struct DirectionalLight {
    vec3 direction;
    vec3 color;
};
uniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];

struct PointLight {
    vec3 position;
    vec3 color;
    float distance;
    float decay;
};
uniform PointLight pointLights[ NUM_POINT_LIGHTS ];

lights_physical_pars_fragmentMeshStandardMaterialMeshPhysicalMaterial 定义 PBR 函数:

struct PhysicalMaterial {
    vec3 diffuseColor;
    vec3 specularColor;
    float roughness;
    float metalness;
    #ifdef USE_CLEARCOAT
        float clearcoat;
        float clearcoatRoughness;
    #endif
    #ifdef USE_IRIDESCENCE
        float iridescence;
        float iridescenceIOR;
    #endif
};

vec3 BRDF_GGX( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in PhysicalMaterial material ) {
    // GGX 微表面 BRDF 实现
}

lights_fragment_begin 遍历光源并累积贡献:

#pragma unroll_loop_start
for ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {
    directionalLight = directionalLights[ i ];
    getDirectionalLightInfo( directionalLight, directLight );
    
    #if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )
        directionalLightShadow = directionalLightShadows[ i ];
        directLight.color *= getShadow( directionalShadowMap[ i ], ... );
    #endif
    
    RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, material, reflectedLight );
}
#pragma unroll_loop_end

阴影片段

shadowmap_pars_fragment 提供阴影采样函数:

float getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowIntensity, float shadowBias, float shadowRadius, vec4 shadowCoord ) {
    // PCF(百分比接近过滤)实现
    // 在 shadowCoord 周围的网格模式中采样阴影图
}

float getPointShadow( samplerCube shadowMap, vec2 shadowMapSize, ... ) {
    // 点光源的立方体贴图阴影采样
}

shadowmap_vertex 在顶点着色器中计算阴影坐标:

#pragma unroll_loop_start
for ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {
    shadowWorldPosition = worldPosition + vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0 );
    vDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition;
}
#pragma unroll_loop_end

动态着色器生成

着色器基于材质和场景属性定制。系统注入定义、组合片段并生成专门代码路径。

特性定义

参数对象包含成为预处理器定义的布尔标志:

// 在 WebGLPrograms.getParameters() 中
const parameters = {
    map: !! material.map,
    normalMap: !! material.normalMap,
    envMap: !! envMap,
    clearcoat: material.clearcoat > 0,
    transmission: material.transmission > 0,
    shadowMapEnabled: renderer.shadowMap.enabled && shadows.length > 0,
    // ... 数百个更多
};

// 在 WebGLProgram 构造函数中
prefixFragment = [
    '#define SHADER_TYPE ' + parameters.shaderType,
    parameters.map ? '#define USE_MAP' : '',
    parameters.normalMap ? '#define USE_NORMALMAP' : '',
    parameters.envMap ? '#define USE_ENVMAP' : '',
    parameters.clearcoat ? '#define USE_CLEARCOAT' : '',
    // ...
].filter( filterEmptyLine ).join( 'n' );

着色器代码使用这些定义进行条件编译:

#ifdef USE_MAP
    vec4 sampledDiffuseColor = texture2D( map, vMapUv );
    diffuseColor *= sampledDiffuseColor;
#endif

#ifdef USE_NORMALMAP
    vec3 mapN = texture2D( normalMap, vNormalMapUv ).xyz * 2.0 - 1.0;
    normal = normalize( tbn * mapN );
#endif

#ifdef USE_CLEARCOAT
    material.clearcoat = clearcoat;
    material.clearcoatRoughness = clearcoatRoughness;
    #ifdef USE_CLEARCOATMAP
        material.clearcoat *= texture2D( clearcoatMap, vClearcoatMapUv ).x;
    #endif
#endif

这在编译时消除非活动代码路径,优化性能。

UV 通道选择

材质可为每个纹理贴图使用不同 UV 通道。系统生成如 MAP_UVNORMALMAP_UV 的定义:

// 在 getParameters() 中
parameters.mapUv = HAS_MAP && getChannel( material.map.channel );
parameters.normalMapUv = HAS_NORMALMAP && getChannel( material.normalMap.channel );

function getChannel( value ) {
    _activeChannels.add( value );
    if ( value === 0 ) return 'uv';
    return `uv${ value }`;
}

这变为:

#define MAP_UV uv
#define NORMALMAP_UV uv1

varying vec2 vMapUv;
varying vec2 vNormalMapUv;

// 在顶点着色器中
vMapUv = ( uvTransform * vec3( uv, 1 ) ).xy;
vNormalMapUv = ( normalMapTransform * vec3( uv1, 1 ) ).xy;

光源计数特化

光源数量决定数组大小和循环边界:

parameters.numDirLights = lights.directional.length;
parameters.numPointLights = lights.point.length;
parameters.numSpotLights = lights.spot.length;

这些计数替换占位符:

// 替换前
uniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];
for ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) { ... }

// 替换后(如果 numDirLights = 2)
uniform DirectionalLight directionalLights[ 2 ];
for ( int i = 0; i < 2; i ++ ) { ... }

然后循环展开展开循环:

// 展开后
directionalLight = directionalLights[ 0 ];
getDirectionalLightInfo( directionalLight, directLight );
RE_Direct( directLight, ... );

directionalLight = directionalLights[ 1 ];
getDirectionalLightInfo( directionalLight, directLight );
RE_Direct( directLight, ... );

色调映射注入

片元着色器前缀基于 parameters.toneMapping 内联注入色调映射函数:

const toneMappingFunctions = {
    [ LinearToneMapping ]: 'Linear',
    [ ReinhardToneMapping ]: 'Reinhard',
    [ CineonToneMapping ]: 'Cineon',
    [ ACESFilmicToleMapping ]: 'ACESFilmic',
};

function getToneMappingFunction( functionName, toneMapping ) {
    const toneMappingName = toneMappingFunctions[ toneMapping ];
    return 'vec3 ' + functionName + '( vec3 color ) { return ' + toneMappingName + 'ToneMapping( color ); }';
}

// 注入到片元前缀中
prefixFragment = [
    // ...
    ( parameters.toneMapping !== NoToneMapping ) ? '#define TONE_MAPPING' : '',
    ( parameters.toneMapping !== NoToneMapping ) ? getToneMappingFunction( 'toneMapping', parameters.toneMapping ) : '',
    // ...
];

这生成:

#define TONE_MAPPING
vec3 toneMapping( vec3 color ) { return ACESFilmicToneMapping( color ); }

着色器稍后调用 toneMapping( outgoingLight ) 应用色调曲线。

色彩空间转换

与色调映射类似,色彩空间输出函数被注入:

function getTexelEncodingFunction( functionName, colorSpace ) {
    const components = getEncodingComponents( colorSpace );
    return [
        `vec4 ${functionName}( vec4 value ) {`,
        `  return ${components[1]}( vec4( value.rgb * ${components[0]}, value.a ) );`,
        '}'
    ].join( 'n' );
}

function getEncodingComponents( colorSpace ) {
    ColorManagement._getMatrix( _m0, ColorManagement.workingColorSpace, colorSpace );
    const encodingMatrix = `mat3( ${ _m0.elements.map( v => v.toFixed(4) ) } )`;
    
    switch ( ColorManagement.getTransfer( colorSpace ) ) {
        case LinearTransfer:
            return [ encodingMatrix, 'LinearTransferOETF' ];
        case SRGBTransfer:
            return [ encodingMatrix, 'sRGBTransferOETF' ];
    }
}

这生成矩阵乘法和传递函数:

vec4 linearToOutputTexel( vec4 value ) {
    return sRGBTransferOETF( vec4( value.rgb * mat3( 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0 ), value.a ) );
}

着色器调用此函数从线性转换为输出色彩空间(sRGB、Display P3 等)。


Uniform 管理

程序链接后,系统通过 WebGLUniforms 提取 uniform 位置并创建缓存结构。这在 onFirstUse() 中惰性发生。

Uniform 位置提取

WebGLUniforms 构造函数查询 WebGL 程序的所有活动 uniform:

cachedUniforms = new WebGLUniforms( gl, program );

// 在 WebGLUniforms 内
const n = gl.getProgramParameter( program, gl.ACTIVE_UNIFORMS );
for ( let i = 0; i < n; i++ ) {
    const info = gl.getActiveUniform( program, i );
    const name = info.name;
    const addr = gl.getUniformLocation( program, name );
    // 存储位置和类型
}

这创建将名称映射到 WebGL 位置的 uniform 缓存,无需重复 getUniformLocation() 调用即可高效更新。

属性位置提取

类似地,fetchAttributeLocations() 查询顶点属性:

function fetchAttributeLocations( gl, program ) {
    const attributes = {};
    const n = gl.getProgramParameter( program, gl.ACTIVE_ATTRIBUTES );
    
    for ( let i = 0; i < n; i++ ) {
        const info = gl.getActiveAttrib( program, i );
        const name = info.name;
        
        let locationSize = 1;
        if ( info.type === gl.FLOAT_MAT2 ) locationSize = 2;
        if ( info.type === gl.FLOAT_MAT3 ) locationSize = 3;
        if ( info.type === gl.FLOAT_MAT4 ) locationSize = 4;
        
        attributes[ name ] = {
            type: info.type,
            location: gl.getAttribLocation( program, name ),
            locationSize: locationSize
        };
    }
    
    return attributes;
}

矩阵占用多个属性位置(例如,mat4 使用 4 个连续位置),因此 locationSize 跟踪此信息。

惰性初始化

Uniform 和属性提取延迟到首次使用以支持异步编译:

this.getUniforms = function () {
    if ( cachedUniforms === undefined ) {
        onFirstUse( this ); // 填充 cachedUniforms
    }
    return cachedUniforms;
};

this.getAttributes = function () {
    if ( cachedAttributes === undefined ) {
        onFirstUse( this ); // 填充 cachedAttributes
    }
    return cachedAttributes;
};

这允许渲染器启动编译,继续其他工作,仅在程序就绪时访问 uniform。

程序使用示例

完整着色器编译流:

SVG
100%

图表:完整程序获取流

过程从渲染器需要绘制材质时开始。它生成描述材质特性的参数,创建缓存键,并检查是否存在匹配程序。缓存未命中时,WebGLProgram 从着色器模板编译新程序,插入定义和处理包含项。程序为具有 identical 参数的未来绘制缓存。Uniform 和属性在首次访问时惰性提取,支持异步编译。