1-WebGLProgramMini的代码
src\renderers\webgl\WebGLPrograms.js
ini
import { WebGLUniformsMini } from './WebGLUniformsMini.js';
import { WebGLShader } from './WebGLShader.js';
let programIdCount = 0;
/** 链接完成后,查询 shader 中所有 active attribute 的 location */
function fetchAttributeLocations( gl, program ) {
const attributes = {};
console.log( 'gl.getProgramParameter( program, gl.ACTIVE_ATTRIBUTES )' );
/** 获取 program 中所有 active attribute 的个数 */
const n = gl.getProgramParameter( program, gl.ACTIVE_ATTRIBUTES );
for ( let i = 0; i < n; i ++ ) {
// 获取 program 中第 i 个 active attribute 的信息
const info = gl.getActiveAttrib( program, i );
// 获取 active attribute 的名称
const name = info.name;
// 获取 active attribute 的 location 大小
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;
}
function generatePrecision( parameters ) {
return `precision ${parameters.precision} float;`;
}
/**
* 单个 WebGL Program 的封装:拼接 GLSL → 编译 → 链接 → 缓存 uniform/attribute 位置。
* prefix 注入 modelViewMatrix、projectionMatrix、position 等 Mini 版固定声明。
* parameters: 编译 shader 所需的参数(源码、精度等)
* {
* shaderID: shaderID,
* shaderType: material.type,
* shaderName: material.name,
* vertexShader: vertexShader,
* fragmentShader: fragmentShader,
* precision: precision,
* uniforms:{}
* }
*/
function WebGLProgramMini( renderer, cacheKey, parameters ) {
const gl = renderer.getContext();
let vertexShader = parameters.vertexShader;
let fragmentShader = parameters.fragmentShader;
const program = gl.createProgram();
// WebGL2 固定使用 GLSL 300 es
let versionString = '#version 300 es\n';
// 顶点 shader 前缀:精度 + 矩阵 uniform + position 输入
const prefixVertex = [
generatePrecision( parameters ),
'uniform mat4 modelViewMatrix;',
'uniform mat4 projectionMatrix;',
'in vec3 position;',
].join( '\n' ) + '\n';
// 片元 shader 前缀:指定 color attachment 0 的输出变量
const prefixFragment = [
'layout(location = 0) out highp vec4 pc_fragColor;',
generatePrecision( parameters ),
].join( '\n' ) + '\n';
const vertexGlsl = versionString + prefixVertex + vertexShader;
const fragmentGlsl = versionString + prefixFragment + fragmentShader;
const glVertexShader = WebGLShader( gl, gl.VERTEX_SHADER, vertexGlsl );
const glFragmentShader = WebGLShader( gl, gl.FRAGMENT_SHADER, fragmentGlsl );
gl.attachShader( program, glVertexShader );
gl.attachShader( program, glFragmentShader );
gl.linkProgram( program );
/** 首次访问 uniform/attribute 时懒加载 location 信息 */
function onFirstUse( self ) {
gl.deleteShader( glVertexShader );
gl.deleteShader( glFragmentShader );
cachedUniforms = new WebGLUniformsMini( gl, program );
cachedAttributes = fetchAttributeLocations( gl, program );
}
let cachedUniforms;
this.getUniforms = function () {
if ( cachedUniforms === undefined ) {
onFirstUse( this );
}
return cachedUniforms;
};
let cachedAttributes;
this.getAttributes = function () {
if ( cachedAttributes === undefined ) {
onFirstUse( this );
}
return cachedAttributes;
};
this.destroy = function () {
bindingStates.releaseStatesOfProgram( this );
gl.deleteProgram( program );
this.program = undefined;
};
function releaseProgram( program ) {
if ( -- program.usedTimes === 0 ) {
// Remove from unordered set
const i = programs.indexOf( program );
programs[ i ] = programs[ programs.length - 1 ];
programs.pop();
// Remove from map
programsMap.delete( program.cacheKey );
// Free WebGL resources
program.destroy();
}
}
this.type = parameters.shaderType;
this.name = parameters.shaderName;
this.id = programIdCount ++;
this.cacheKey = cacheKey;
this.usedTimes = 1;
this.program = program;
this.vertexShader = glVertexShader;
this.fragmentShader = glFragmentShader;
return this;
}
export { WebGLProgramMini };
2-WebGLProgramMini 的功能定位
WebGLProgram 是WebGL 渲染器里真正创建、编译、链接 GPU Shader Program 的核心模块。
WebGLProgram 不负责"选哪个材质用哪个 shader"(那是 WebGLPrograms 的事),而是把最终 GLSL 源码拼出来,交给 WebGL 编译成可执行的 gl.Program。
在渲染管线中的位置:
css
Material + Scene + Object + Lights
↓
WebGLPrograms.getParameters() ← 收集参数、生成 cacheKey
↓
WebGLProgram ← 拼 GLSL、编译、链接(本文件)
↓
WebGLUniforms / attribute 缓存
↓
WebGLRenderer 绘制
WebGLProgramMini 亦是如此,只是具体功能做了删减。
WebGLProgramMini 中的依赖关系。
| 模块 | 作用 |
|---|---|
WebGLShader.js |
创建 shader、注入源码、compileShader |
WebGLUniformsMini.js |
链接后解析 active uniform,提供 upload() |
bindingStates |
销毁 program 时释放 VAO / attribute 绑定状态 |
3-辅助函数
1. fetchAttributeLocations
在shader program 链接shader 成功后,可以根据webgl 的内置方法获取shader program 中的所有attribute 变量。
ini
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 );
// ...
attributes[ name ] = {
type: info.type,
location: gl.getAttribLocation( program, name ),
locationSize: locationSize
};
}
return attributes;
}
locationSize 用于矩阵型 attribute(mat2/mat3/mat4 占多个 location),普通 vec3 position 为 1。
2-generatePrecision
生成精度声明字符串。
javascript
function generatePrecision( parameters ) {
return `precision ${parameters.precision} float;`;
}
3-WebGLProgramMini 构造函数
参数
| 参数 | 含义 |
|---|---|
renderer |
渲染器,用于取 WebGL context |
cacheKey |
shader program 缓存键(如 "mini") |
parameters |
顶点/片元源码、精度、材质类型等参数 |
第一步:拼接完整 GLSL
Mini 版固定使用 WebGL2 / GLSL 300 es:
ini
let versionString = '#version 300 es\n';
// 顶点 shader 前缀:精度 + 矩阵 uniform + position 输入
const prefixVertex = [
generatePrecision( parameters ),
'uniform mat4 modelViewMatrix;',
'uniform mat4 projectionMatrix;',
'in vec3 position;',
].join( '\n' ) + '\n';
// 片元 shader 前缀:指定 color attachment 0 的输出变量
const prefixFragment = [
'layout(location = 0) out highp vec4 pc_fragColor;',
generatePrecision( parameters ),
].join( '\n' ) + '\n';
const vertexGlsl = versionString + prefixVertex + vertexShader;
const fragmentGlsl = versionString + prefixFragment + fragmentShader;
设计要点:
- 顶点 shader 前缀注入 MVP 所需声明:
modelViewMatrix、projectionMatrix、position。 - 片元 shader 前缀声明
pc_fragColor作为 color attachment 0 的输出(GLSL 300 es 要求显式out)。 - 业务 shader(如
meshmini.glsl.js)只写main()逻辑,不必重复声明这些固定变量。
第二步:编译与链接
ini
const vertexGlsl = versionString + prefixVertex + vertexShader;
const fragmentGlsl = versionString + prefixFragment + fragmentShader;
const glVertexShader = WebGLShader( gl, gl.VERTEX_SHADER, vertexGlsl );
const glFragmentShader = WebGLShader( gl, gl.FRAGMENT_SHADER, fragmentGlsl );
gl.attachShader( program, glVertexShader );
gl.attachShader( program, glFragmentShader );
gl.linkProgram( program );
标准 WebGL 流程:createProgram → 编译两个 shader → attach → linkProgram。
懒加载:首次使用时才解析 uniform/attribute
ini
function onFirstUse( self ) {
gl.deleteShader( glVertexShader );
gl.deleteShader( glFragmentShader );
cachedUniforms = new WebGLUniformsMini( gl, program );
cachedAttributes = fetchAttributeLocations( gl, program );
}
let cachedUniforms;
this.getUniforms = function () {
if ( cachedUniforms === undefined ) {
onFirstUse( this );
}
return cachedUniforms;
};
let cachedAttributes;
this.getAttributes = function () {
if ( cachedAttributes === undefined ) {
onFirstUse( this );
}
return cachedAttributes;
};
首次调用 getUniforms() 或 getAttributes() 时:
- 删除已 link 的 shader 对象(program 仍保留,可继续用)。
- 创建
WebGLUniformsMini,解析所有 active uniform 的 location。 - 调用
fetchAttributeLocations缓存 attribute 信息。
这样 program 创建后若从未渲染,就不会做 uniform / attribute 查询,减少无效开销。
4-实例上的公开属性
ini
this.type = parameters.shaderType;
this.name = parameters.shaderName;
this.id = programIdCount ++;
this.cacheKey = cacheKey;
this.usedTimes = 1;
this.program = program;
this.vertexShader = glVertexShader;
this.fragmentShader = glFragmentShader;
| 属性 / 方法 | 含义 |
|---|---|
id |
全局递增 ID,用于调试 |
cacheKey |
缓存键(如 "selectProgram 时区分 program) |
type / name |
材质类型与名称 |
usedTimes |
引用计数(WebGLProgramsMini.acquireProgram 维护) |
program |
链接后的 WebGL program 句柄 |
getUniforms() |
返回 WebGLUniformsMini 实例 |
getAttributes() |
返回 attribute location 映射 |
destroy() |
释放 binding state 并 deleteProgram |
5-与 WebGLProgramsMini 的配合
ini
function acquireProgram( parameters, cacheKey ) {
let program = programsMap.get( cacheKey );
if ( program !== undefined ) {
++ program.usedTimes;
} else {
program = new WebGLProgramMini( renderer, cacheKey, parameters,bindingStates );
programs.push( program );
programsMap.set( cacheKey, program );
}
return program;
}
- 相同
cacheKey(当前仅shaderID,如"mini")共享同一个WebGLProgramMini。 - 渲染时
WebGLRendererMini会调用program.getUniforms(),再通过WebGLUniformsMini.upload()把材质 uniform(如diffuse、矩阵)上传到 GPU。
6-与完整版 Three.js WebGLProgram 的对比
| 完整版 | Mini 版 |
|---|---|
大量 #include、宏、特性开关 |
固定 prefix,shader 极简 |
| 支持 WebGL1 / WebGL2 分支 | 固定 GLSL 300 es |
| 复杂 cacheKey(灯光、贴图、雾等) | cacheKey 只有 shaderID |
| 完整 uniform 类型支持 | WebGLUniformsMini 仅 vec3、mat4 |
数据流小结
scss
Material (MeshMiniMaterial)
↓ WebGLProgramsMini.getParameters()
ShaderLibMini.mini(vertex + fragment 源码)
↓ WebGLProgramMini
完整 GLSL(version + prefix + 业务代码)
↓ compile + link
WebGL Program
↓ getUniforms() / getAttributes()
GPU location 缓存
↓ 每帧 render
WebGLUniformsMini.upload() + attribute 绑定 → draw