WebGL 渲染管线深度解析
基于
mrdoob/three.js源码(src/renderers/WebGLRenderer.js及其子系统)的完整技术博客
一、思维导图:WebGL 渲染管线全景
#mermaid-svg-tmP9FoKIH8XlXKH8{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tmP9FoKIH8XlXKH8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tmP9FoKIH8XlXKH8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tmP9FoKIH8XlXKH8 .error-icon{fill:#552222;}#mermaid-svg-tmP9FoKIH8XlXKH8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tmP9FoKIH8XlXKH8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tmP9FoKIH8XlXKH8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tmP9FoKIH8XlXKH8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tmP9FoKIH8XlXKH8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tmP9FoKIH8XlXKH8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tmP9FoKIH8XlXKH8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tmP9FoKIH8XlXKH8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tmP9FoKIH8XlXKH8 .marker.cross{stroke:#333333;}#mermaid-svg-tmP9FoKIH8XlXKH8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tmP9FoKIH8XlXKH8 p{margin:0;}#mermaid-svg-tmP9FoKIH8XlXKH8 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-tmP9FoKIH8XlXKH8 .cluster-label text{fill:#333;}#mermaid-svg-tmP9FoKIH8XlXKH8 .cluster-label span{color:#333;}#mermaid-svg-tmP9FoKIH8XlXKH8 .cluster-label span p{background-color:transparent;}#mermaid-svg-tmP9FoKIH8XlXKH8 .label text,#mermaid-svg-tmP9FoKIH8XlXKH8 span{fill:#333;color:#333;}#mermaid-svg-tmP9FoKIH8XlXKH8 .node rect,#mermaid-svg-tmP9FoKIH8XlXKH8 .node circle,#mermaid-svg-tmP9FoKIH8XlXKH8 .node ellipse,#mermaid-svg-tmP9FoKIH8XlXKH8 .node polygon,#mermaid-svg-tmP9FoKIH8XlXKH8 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-tmP9FoKIH8XlXKH8 .rough-node .label text,#mermaid-svg-tmP9FoKIH8XlXKH8 .node .label text,#mermaid-svg-tmP9FoKIH8XlXKH8 .image-shape .label,#mermaid-svg-tmP9FoKIH8XlXKH8 .icon-shape .label{text-anchor:middle;}#mermaid-svg-tmP9FoKIH8XlXKH8 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-tmP9FoKIH8XlXKH8 .rough-node .label,#mermaid-svg-tmP9FoKIH8XlXKH8 .node .label,#mermaid-svg-tmP9FoKIH8XlXKH8 .image-shape .label,#mermaid-svg-tmP9FoKIH8XlXKH8 .icon-shape .label{text-align:center;}#mermaid-svg-tmP9FoKIH8XlXKH8 .node.clickable{cursor:pointer;}#mermaid-svg-tmP9FoKIH8XlXKH8 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-tmP9FoKIH8XlXKH8 .arrowheadPath{fill:#333333;}#mermaid-svg-tmP9FoKIH8XlXKH8 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-tmP9FoKIH8XlXKH8 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-tmP9FoKIH8XlXKH8 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tmP9FoKIH8XlXKH8 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-tmP9FoKIH8XlXKH8 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tmP9FoKIH8XlXKH8 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-tmP9FoKIH8XlXKH8 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-tmP9FoKIH8XlXKH8 .cluster text{fill:#333;}#mermaid-svg-tmP9FoKIH8XlXKH8 .cluster span{color:#333;}#mermaid-svg-tmP9FoKIH8XlXKH8 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-tmP9FoKIH8XlXKH8 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-tmP9FoKIH8XlXKH8 rect.text{fill:none;stroke-width:0;}#mermaid-svg-tmP9FoKIH8XlXKH8 .icon-shape,#mermaid-svg-tmP9FoKIH8XlXKH8 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tmP9FoKIH8XlXKH8 .icon-shape p,#mermaid-svg-tmP9FoKIH8XlXKH8 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-tmP9FoKIH8XlXKH8 .icon-shape .label rect,#mermaid-svg-tmP9FoKIH8XlXKH8 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tmP9FoKIH8XlXKH8 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-tmP9FoKIH8XlXKH8 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-tmP9FoKIH8XlXKH8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} WebGL 渲染管线
- CPU 阶段(场景准备)
- 渲染列表构建
- 阴影通道 Shadow Pass
- 主渲染通道 Main Pass
- GPU 固定管线阶段
- 资源管理子系统
矩阵更新
updateMatrixWorld()
视锥体裁剪
Frustum Culling
场景图遍历
projectObject()
不透明队列 opaque
前→后排序 painterSortStable
透射队列 transmissive
后→前排序
透明队列 transparent
后→前排序 reversePainterSortStable
深度贴图生成
Depth Map
PCF / VSM 软阴影
背景渲染
WebGLBackground
光照设置
setupLights()
不透明物体绘制
透射通道
Transmission Pass
透明物体绘制
顶点着色器
Vertex Shader
图元装配
Primitive Assembly
光栅化
Rasterization
片元着色器
Fragment Shader
深度/模板测试
Depth & Stencil Test
混合
Blending
帧缓冲输出
Framebuffer
WebGLPrograms
着色器缓存
WebGLTextures
纹理管理
WebGLAttributes
VBO/EBO
WebGLState
GL状态缓存
WebGLShadowMap
阴影贴图
二、整体架构概览
WebGL 渲染管线可以分为两大部分:CPU 侧的 JavaScript 逻辑 (场景遍历、排序、状态设置)和 GPU 侧的硬件管线(顶点处理、光栅化、片元着色)。
Three.js 的 WebGLRenderer 是整个管线的总指挥,它协调超过 15 个子系统,将场景图数据转化为 GPU 绘制调用。 1
用户代码
│
▼
WebGLRenderer.render(scene, camera)
│
├─► 矩阵更新 & 视锥体计算
├─► 场景图遍历 & 渲染列表构建
├─► 阴影通道(Shadow Pass)
├─► 光照设置
├─► 背景渲染
├─► 不透明物体渲染(前→后)
├─► 透射通道(Transmission Pass)
└─► 透明物体渲染(后→前)
三、渲染管线各阶段详解
阶段 1:矩阵更新与视锥体裁剪
每帧渲染开始时,渲染器首先更新场景图中所有对象的世界矩阵,然后根据相机的投影矩阵构建视锥体(Frustum)。
js
// src/renderers/WebGLRenderer.js
if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld();
if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld();
_projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
_frustum.setFromProjectionMatrix( _projScreenMatrix, WebGLCoordinateSystem, camera.reversedDepth );
视锥体(Frustum) 由 6 个平面组成(上/下/左/右/近/远),用于快速判断物体是否在相机可见范围内。不在视锥体内的物体直接跳过,不进入渲染列表,这是最重要的性能优化之一。 3
阶段 2:场景图遍历与渲染列表构建
projectObject() 函数递归遍历整个场景图,对每个可见对象执行视锥体裁剪,并将通过裁剪的对象加入渲染列表。
js
function projectObject( object, camera, groupOrder, sortObjects ) {
if ( object.visible === false ) return;
const visible = object.layers.test( camera.layers );
if ( visible ) {
if ( object.isLight ) {
currentRenderState.pushLight( object );
if ( object.castShadow ) currentRenderState.pushShadow( object );
} else if ( object.isMesh || object.isLine || object.isPoints ) {
if ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) {
// 加入渲染列表
currentRenderList.push( object, geometry, material, groupOrder, _vector4.z, null );
}
}
}
// 递归处理子节点
const children = object.children;
for ( let i = 0, l = children.length; i < l; i ++ ) {
projectObject( children[ i ], camera, groupOrder, sortObjects );
}
}
渲染列表分为三个队列:
| 队列 | 内容 | 排序策略 | 原因 |
|---|---|---|---|
opaque |
不透明物体 | 前→后 (painterSortStable) |
利用 Early-Z 拒绝,减少过绘制 |
transmissive |
透射物体(玻璃等) | 后→前 | 需要正确的折射采样 |
transparent |
透明物体 | 后→前 (reversePainterSortStable) |
保证 Alpha 混合正确性 |
阶段 3:阴影通道(Shadow Pass)
在主渲染之前,WebGLShadowMap 为每个投射阴影的光源渲染一张深度贴图(Shadow Map)。
对每个投影光源:
1. 切换渲染目标到深度贴图 FBO
2. 从光源视角渲染场景(只写深度)
3. 恢复主渲染目标
Three.js 支持三种阴影类型:
BasicShadowMap:硬阴影,性能最好PCFShadowMap:百分比近似过滤,软阴影VSMShadowMap:方差阴影贴图,更柔和的软阴影
阶段 4:光照设置
currentRenderState.setupLights() 将场景中收集到的光源数据(方向、颜色、强度、阴影贴图等)打包成 Uniform 数据,准备传入着色器。 7
阶段 5:主渲染通道
主渲染通道按顺序执行:
background.render(scene) ← 渲染背景(纯色/天空盒/环境贴图)
↓
renderObjects(opaque, ...) ← 不透明物体(前→后)
↓
renderTransmissionPass(...) ← 透射通道(将不透明场景渲染到临时RT供折射采样)
↓
renderObjects(transparent, ...) ← 透明物体(后→前)
每个物体的绘制最终调用 renderBufferDirect(),它负责:
- 选择/编译 GLSL 着色器程序(
setProgram()) - 设置 GL 状态(
state.setMaterial()) - 绑定 VAO(
bindingStates.setup()) - 发出绘制调用(
gl.drawArrays()或gl.drawElements()) 9
四、GPU 固定管线阶段
#mermaid-svg-F0bVmdnSWagvV1lc{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-F0bVmdnSWagvV1lc .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-F0bVmdnSWagvV1lc .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-F0bVmdnSWagvV1lc .error-icon{fill:#552222;}#mermaid-svg-F0bVmdnSWagvV1lc .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-F0bVmdnSWagvV1lc .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-F0bVmdnSWagvV1lc .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-F0bVmdnSWagvV1lc .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-F0bVmdnSWagvV1lc .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-F0bVmdnSWagvV1lc .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-F0bVmdnSWagvV1lc .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-F0bVmdnSWagvV1lc .marker{fill:#333333;stroke:#333333;}#mermaid-svg-F0bVmdnSWagvV1lc .marker.cross{stroke:#333333;}#mermaid-svg-F0bVmdnSWagvV1lc svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-F0bVmdnSWagvV1lc p{margin:0;}#mermaid-svg-F0bVmdnSWagvV1lc .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-F0bVmdnSWagvV1lc .cluster-label text{fill:#333;}#mermaid-svg-F0bVmdnSWagvV1lc .cluster-label span{color:#333;}#mermaid-svg-F0bVmdnSWagvV1lc .cluster-label span p{background-color:transparent;}#mermaid-svg-F0bVmdnSWagvV1lc .label text,#mermaid-svg-F0bVmdnSWagvV1lc span{fill:#333;color:#333;}#mermaid-svg-F0bVmdnSWagvV1lc .node rect,#mermaid-svg-F0bVmdnSWagvV1lc .node circle,#mermaid-svg-F0bVmdnSWagvV1lc .node ellipse,#mermaid-svg-F0bVmdnSWagvV1lc .node polygon,#mermaid-svg-F0bVmdnSWagvV1lc .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-F0bVmdnSWagvV1lc .rough-node .label text,#mermaid-svg-F0bVmdnSWagvV1lc .node .label text,#mermaid-svg-F0bVmdnSWagvV1lc .image-shape .label,#mermaid-svg-F0bVmdnSWagvV1lc .icon-shape .label{text-anchor:middle;}#mermaid-svg-F0bVmdnSWagvV1lc .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-F0bVmdnSWagvV1lc .rough-node .label,#mermaid-svg-F0bVmdnSWagvV1lc .node .label,#mermaid-svg-F0bVmdnSWagvV1lc .image-shape .label,#mermaid-svg-F0bVmdnSWagvV1lc .icon-shape .label{text-align:center;}#mermaid-svg-F0bVmdnSWagvV1lc .node.clickable{cursor:pointer;}#mermaid-svg-F0bVmdnSWagvV1lc .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-F0bVmdnSWagvV1lc .arrowheadPath{fill:#333333;}#mermaid-svg-F0bVmdnSWagvV1lc .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-F0bVmdnSWagvV1lc .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-F0bVmdnSWagvV1lc .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-F0bVmdnSWagvV1lc .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-F0bVmdnSWagvV1lc .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-F0bVmdnSWagvV1lc .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-F0bVmdnSWagvV1lc .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-F0bVmdnSWagvV1lc .cluster text{fill:#333;}#mermaid-svg-F0bVmdnSWagvV1lc .cluster span{color:#333;}#mermaid-svg-F0bVmdnSWagvV1lc div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-F0bVmdnSWagvV1lc .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-F0bVmdnSWagvV1lc rect.text{fill:none;stroke-width:0;}#mermaid-svg-F0bVmdnSWagvV1lc .icon-shape,#mermaid-svg-F0bVmdnSWagvV1lc .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-F0bVmdnSWagvV1lc .icon-shape p,#mermaid-svg-F0bVmdnSWagvV1lc .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-F0bVmdnSWagvV1lc .icon-shape .label rect,#mermaid-svg-F0bVmdnSWagvV1lc .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-F0bVmdnSWagvV1lc .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-F0bVmdnSWagvV1lc .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-F0bVmdnSWagvV1lc :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 顶点数据
VBO/EBO
顶点着色器
Vertex Shader
GLSL
图元装配
Primitive Assembly
三角形/线/点
背面剔除
Face Culling
gl.CULL_FACE
光栅化
Rasterization
插值生成片元
片元着色器
Fragment Shader
GLSL
深度测试
Depth Test
gl.DEPTH_TEST
模板测试
Stencil Test
混合
Blending
gl.BLEND
帧缓冲
Framebuffer
最终像素
4.1 顶点着色器(Vertex Shader)
顶点着色器运行在 GPU 上,对每个顶点执行变换。Three.js 内置着色器的核心变换:
glsl
// 典型的 Three.js 顶点着色器核心
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
modelMatrix:模型空间 → 世界空间viewMatrix(modelViewMatrix):世界空间 → 相机空间projectionMatrix:相机空间 → 裁剪空间(NDC) 10
Three.js 使用 #include 机制将着色器代码模块化,例如 #include <project_vertex> 会被替换为实际的投影变换代码。 11
4.2 着色器编译与缓存(WebGLPrograms)
WebGLPrograms 是着色器的编译器和缓存系统。它根据材质参数(是否有法线贴图、是否使用蒙皮、光源数量等)生成唯一的缓存键 ,避免重复编译相同的着色器。
js
// 编译流程
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 );
4.3 片元着色器(Fragment Shader)
片元着色器决定每个像素的最终颜色,包含光照计算、纹理采样、色调映射等:
glsl
// 典型片元着色器输出链
diffuseColor → 光照计算 → 环境光遮蔽 → 自发光 → 色调映射 → 色彩空间转换 → gl_FragColor
4.4 深度测试、模板测试与混合(WebGLState)
WebGLState 是 GL 状态的缓存层,避免重复调用昂贵的 GL 状态切换 API:
js
function setMaterial( material, frontFaceCW ) {
// 背面剔除
material.side === DoubleSide ? disable( gl.CULL_FACE ) : enable( gl.CULL_FACE );
// 混合模式
( material.blending === NormalBlending && material.transparent === false )
? setBlending( NoBlending )
: setBlending( material.blending, ... );
// 深度测试
depthBuffer.setFunc( material.depthFunc );
depthBuffer.setTest( material.depthTest );
depthBuffer.setMask( material.depthWrite );
// 模板测试
stencilBuffer.setTest( material.stencilWrite );
}
深度测试函数 (depthFunc)支持:NEVER、ALWAYS、LESS、LEQUAL、EQUAL、GEQUAL、GREATER、NOTEQUAL。 15
混合模式 (blending)支持:NoBlending、NormalBlending(标准 Alpha 混合)、AdditiveBlending(加法混合,用于粒子/光效)、SubtractiveBlending、MultiplyBlending、CustomBlending。 16
五、资源管理子系统
#mermaid-svg-U6sEPUp3dagqvO8Z{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-U6sEPUp3dagqvO8Z .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-U6sEPUp3dagqvO8Z .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-U6sEPUp3dagqvO8Z .error-icon{fill:#552222;}#mermaid-svg-U6sEPUp3dagqvO8Z .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-U6sEPUp3dagqvO8Z .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-U6sEPUp3dagqvO8Z .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-U6sEPUp3dagqvO8Z .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-U6sEPUp3dagqvO8Z .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-U6sEPUp3dagqvO8Z .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-U6sEPUp3dagqvO8Z .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-U6sEPUp3dagqvO8Z .marker{fill:#333333;stroke:#333333;}#mermaid-svg-U6sEPUp3dagqvO8Z .marker.cross{stroke:#333333;}#mermaid-svg-U6sEPUp3dagqvO8Z svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-U6sEPUp3dagqvO8Z p{margin:0;}#mermaid-svg-U6sEPUp3dagqvO8Z .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-U6sEPUp3dagqvO8Z .cluster-label text{fill:#333;}#mermaid-svg-U6sEPUp3dagqvO8Z .cluster-label span{color:#333;}#mermaid-svg-U6sEPUp3dagqvO8Z .cluster-label span p{background-color:transparent;}#mermaid-svg-U6sEPUp3dagqvO8Z .label text,#mermaid-svg-U6sEPUp3dagqvO8Z span{fill:#333;color:#333;}#mermaid-svg-U6sEPUp3dagqvO8Z .node rect,#mermaid-svg-U6sEPUp3dagqvO8Z .node circle,#mermaid-svg-U6sEPUp3dagqvO8Z .node ellipse,#mermaid-svg-U6sEPUp3dagqvO8Z .node polygon,#mermaid-svg-U6sEPUp3dagqvO8Z .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-U6sEPUp3dagqvO8Z .rough-node .label text,#mermaid-svg-U6sEPUp3dagqvO8Z .node .label text,#mermaid-svg-U6sEPUp3dagqvO8Z .image-shape .label,#mermaid-svg-U6sEPUp3dagqvO8Z .icon-shape .label{text-anchor:middle;}#mermaid-svg-U6sEPUp3dagqvO8Z .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-U6sEPUp3dagqvO8Z .rough-node .label,#mermaid-svg-U6sEPUp3dagqvO8Z .node .label,#mermaid-svg-U6sEPUp3dagqvO8Z .image-shape .label,#mermaid-svg-U6sEPUp3dagqvO8Z .icon-shape .label{text-align:center;}#mermaid-svg-U6sEPUp3dagqvO8Z .node.clickable{cursor:pointer;}#mermaid-svg-U6sEPUp3dagqvO8Z .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-U6sEPUp3dagqvO8Z .arrowheadPath{fill:#333333;}#mermaid-svg-U6sEPUp3dagqvO8Z .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-U6sEPUp3dagqvO8Z .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-U6sEPUp3dagqvO8Z .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-U6sEPUp3dagqvO8Z .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-U6sEPUp3dagqvO8Z .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-U6sEPUp3dagqvO8Z .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-U6sEPUp3dagqvO8Z .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-U6sEPUp3dagqvO8Z .cluster text{fill:#333;}#mermaid-svg-U6sEPUp3dagqvO8Z .cluster span{color:#333;}#mermaid-svg-U6sEPUp3dagqvO8Z div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-U6sEPUp3dagqvO8Z .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-U6sEPUp3dagqvO8Z rect.text{fill:none;stroke-width:0;}#mermaid-svg-U6sEPUp3dagqvO8Z .icon-shape,#mermaid-svg-U6sEPUp3dagqvO8Z .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-U6sEPUp3dagqvO8Z .icon-shape p,#mermaid-svg-U6sEPUp3dagqvO8Z .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-U6sEPUp3dagqvO8Z .icon-shape .label rect,#mermaid-svg-U6sEPUp3dagqvO8Z .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-U6sEPUp3dagqvO8Z .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-U6sEPUp3dagqvO8Z .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-U6sEPUp3dagqvO8Z :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 着色器管线
Material
(JS)
WebGLPrograms
参数哈希/缓存
WebGLProgram
GLSL编译链接
WebGLProgram
(GPU)
纹理管线
Texture
(JS)
WebGLTextures
格式转换/Mipmap
gl.texImage2D()
gl.texStorage2D()
GPU纹理单元
几何体管线
BufferGeometry
(JS)
WebGLGeometries
生命周期管理
WebGLAttributes
gl.bufferData()
VBO/EBO
(GPU显存)
WebGLAttributes:几何数据上传
WebGLAttributes 负责将 BufferAttribute 数据上传到 GPU,使用 gl.bufferData() 或 gl.bufferSubData() 进行全量/增量更新。 17
WebGLTextures:纹理管理
WebGLTextures 处理纹理上传的复杂逻辑:
- 超出硬件限制时自动缩放图像
- 根据纹理格式、类型、色彩空间解析正确的 WebGL 内部格式
- 自动生成 Mipmap 18
六、渲染调用链(Call Graph)
#mermaid-svg-fMmdHVesIiuYxpws{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-fMmdHVesIiuYxpws .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-fMmdHVesIiuYxpws .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-fMmdHVesIiuYxpws .error-icon{fill:#552222;}#mermaid-svg-fMmdHVesIiuYxpws .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-fMmdHVesIiuYxpws .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-fMmdHVesIiuYxpws .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-fMmdHVesIiuYxpws .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-fMmdHVesIiuYxpws .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-fMmdHVesIiuYxpws .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-fMmdHVesIiuYxpws .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-fMmdHVesIiuYxpws .marker{fill:#333333;stroke:#333333;}#mermaid-svg-fMmdHVesIiuYxpws .marker.cross{stroke:#333333;}#mermaid-svg-fMmdHVesIiuYxpws svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-fMmdHVesIiuYxpws p{margin:0;}#mermaid-svg-fMmdHVesIiuYxpws .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-fMmdHVesIiuYxpws .cluster-label text{fill:#333;}#mermaid-svg-fMmdHVesIiuYxpws .cluster-label span{color:#333;}#mermaid-svg-fMmdHVesIiuYxpws .cluster-label span p{background-color:transparent;}#mermaid-svg-fMmdHVesIiuYxpws .label text,#mermaid-svg-fMmdHVesIiuYxpws span{fill:#333;color:#333;}#mermaid-svg-fMmdHVesIiuYxpws .node rect,#mermaid-svg-fMmdHVesIiuYxpws .node circle,#mermaid-svg-fMmdHVesIiuYxpws .node ellipse,#mermaid-svg-fMmdHVesIiuYxpws .node polygon,#mermaid-svg-fMmdHVesIiuYxpws .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-fMmdHVesIiuYxpws .rough-node .label text,#mermaid-svg-fMmdHVesIiuYxpws .node .label text,#mermaid-svg-fMmdHVesIiuYxpws .image-shape .label,#mermaid-svg-fMmdHVesIiuYxpws .icon-shape .label{text-anchor:middle;}#mermaid-svg-fMmdHVesIiuYxpws .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-fMmdHVesIiuYxpws .rough-node .label,#mermaid-svg-fMmdHVesIiuYxpws .node .label,#mermaid-svg-fMmdHVesIiuYxpws .image-shape .label,#mermaid-svg-fMmdHVesIiuYxpws .icon-shape .label{text-align:center;}#mermaid-svg-fMmdHVesIiuYxpws .node.clickable{cursor:pointer;}#mermaid-svg-fMmdHVesIiuYxpws .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-fMmdHVesIiuYxpws .arrowheadPath{fill:#333333;}#mermaid-svg-fMmdHVesIiuYxpws .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-fMmdHVesIiuYxpws .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-fMmdHVesIiuYxpws .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fMmdHVesIiuYxpws .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-fMmdHVesIiuYxpws .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fMmdHVesIiuYxpws .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-fMmdHVesIiuYxpws .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-fMmdHVesIiuYxpws .cluster text{fill:#333;}#mermaid-svg-fMmdHVesIiuYxpws .cluster span{color:#333;}#mermaid-svg-fMmdHVesIiuYxpws div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-fMmdHVesIiuYxpws .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-fMmdHVesIiuYxpws rect.text{fill:none;stroke-width:0;}#mermaid-svg-fMmdHVesIiuYxpws .icon-shape,#mermaid-svg-fMmdHVesIiuYxpws .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-fMmdHVesIiuYxpws .icon-shape p,#mermaid-svg-fMmdHVesIiuYxpws .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-fMmdHVesIiuYxpws .icon-shape .label rect,#mermaid-svg-fMmdHVesIiuYxpws .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-fMmdHVesIiuYxpws .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-fMmdHVesIiuYxpws .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-fMmdHVesIiuYxpws :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} render(scene, camera)
renderScene(renderList, scene, camera)
renderObjects(renderList, scene, camera)
renderObject(object, scene, camera, ...)
renderBufferDirect(camera, scene, geometry, material, object, group)
setProgram() → 选择/编译着色器
state.setMaterial() → 设置GL状态
bindingStates.setup() → 绑定VAO
bufferRenderer.render() → gl.drawElements()
七、性能优化关键点总结
| 优化技术 | 实现位置 | 原理 |
|---|---|---|
| 视锥体裁剪 | projectObject() + Frustum |
跳过相机外的物体,减少 Draw Call |
| 不透明前→后排序 | painterSortStable |
利用 Early-Z 拒绝,减少过绘制 |
| 着色器缓存 | WebGLPrograms |
相同参数的材质复用已编译的 GLSL 程序 |
| GL 状态缓存 | WebGLState |
只在状态真正改变时调用 GL API |
| VAO 绑定状态缓存 | WebGLBindingStates |
减少顶点属性绑定的重复设置 |
| 增量纹理上传 | WebGLTextures + bufferSubData |
只上传变化的纹理区域 |
| 阴影贴图复用 | WebGLShadowMap |
静态场景可关闭 autoUpdate |
八、完整渲染帧时序图

九、关键源码文件索引
| 文件 | 职责 |
|---|---|
src/renderers/WebGLRenderer.js |
渲染器主入口,协调所有子系统 |
src/renderers/webgl/WebGLState.js |
GL 状态缓存(深度/模板/混合/剔除) |
src/renderers/webgl/WebGLPrograms.js |
着色器参数生成与缓存 |
src/renderers/webgl/WebGLProgram.js |
GLSL 编译、链接、Uniform 绑定 |
src/renderers/webgl/WebGLRenderLists.js |
渲染列表(opaque/transmissive/transparent) |
src/renderers/webgl/WebGLShadowMap.js |
阴影贴图生成 |
src/renderers/webgl/WebGLTextures.js |
纹理上传与格式管理 |
src/renderers/webgl/WebGLAttributes.js |
VBO/EBO 数据上传 |
src/renderers/webgl/WebGLBindingStates.js |
VAO 绑定状态管理 |
src/math/Frustum.js |
视锥体裁剪数学 |
Citations
File: src/renderers/WebGLRenderer.js (L26-54)
javascript
import { WebGLAnimation } from './webgl/WebGLAnimation.js';
import { WebGLAttributes } from './webgl/WebGLAttributes.js';
import { WebGLBackground } from './webgl/WebGLBackground.js';
import { WebGLBindingStates } from './webgl/WebGLBindingStates.js';
import { WebGLBufferRenderer } from './webgl/WebGLBufferRenderer.js';
import { WebGLCapabilities } from './webgl/WebGLCapabilities.js';
import { WebGLClipping } from './webgl/WebGLClipping.js';
import { WebGLEnvironments } from './webgl/WebGLEnvironments.js';
import { WebGLExtensions } from './webgl/WebGLExtensions.js';
import { WebGLGeometries } from './webgl/WebGLGeometries.js';
import { WebGLIndexedBufferRenderer } from './webgl/WebGLIndexedBufferRenderer.js';
import { WebGLInfo } from './webgl/WebGLInfo.js';
import { WebGLMorphtargets } from './webgl/WebGLMorphtargets.js';
import { WebGLObjects } from './webgl/WebGLObjects.js';
import { WebGLOutput } from './webgl/WebGLOutput.js';
import { WebGLPrograms } from './webgl/WebGLPrograms.js';
import { WebGLProperties } from './webgl/WebGLProperties.js';
import { WebGLRenderLists } from './webgl/WebGLRenderLists.js';
import { WebGLRenderStates } from './webgl/WebGLRenderStates.js';
import { WebGLRenderTarget } from './WebGLRenderTarget.js';
import { WebGLShadowMap } from './webgl/WebGLShadowMap.js';
import { WebGLState } from './webgl/WebGLState.js';
import { WebGLTextures } from './webgl/WebGLTextures.js';
import { WebGLUniforms } from './webgl/WebGLUniforms.js';
import { WebGLUtils } from './webgl/WebGLUtils.js';
import { WebXRManager } from './webxr/WebXRManager.js';
import { WebGLMaterials } from './webgl/WebGLMaterials.js';
import { WebGLUniformsGroups } from './webgl/WebGLUniformsGroups.js';
import { createCanvasElement, probeAsync, error, warn, log } from '../utils.js';
File: src/renderers/WebGLRenderer.js (L63-63)
javascript
class WebGLRenderer {
File: src/renderers/WebGLRenderer.js (L451-452)
javascript
textures = new WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info );
environments = new WebGLEnvironments( _this );
File: src/renderers/WebGLRenderer.js (L453-456)
javascript
attributes = new WebGLAttributes( _gl );
bindingStates = new WebGLBindingStates( _gl, attributes );
geometries = new WebGLGeometries( _gl, attributes, info, bindingStates );
objects = new WebGLObjects( _gl, geometries, attributes, bindingStates, info );
File: src/renderers/WebGLRenderer.js (L1184-1242)
javascript
this.renderBufferDirect = function ( camera, scene, geometry, material, object, group ) {
if ( scene === null ) scene = _emptyScene; // renderBufferDirect second parameter used to be fog (could be null)
const frontFaceCW = ( object.isMesh && object.matrixWorld.determinant() < 0 );
const program = setProgram( camera, scene, geometry, material, object );
state.setMaterial( material, frontFaceCW );
//
let index = geometry.index;
let rangeFactor = 1;
if ( material.wireframe === true ) {
index = geometries.getWireframeAttribute( geometry );
if ( index === undefined ) return;
rangeFactor = 2;
}
//
const drawRange = geometry.drawRange;
const position = geometry.attributes.position;
let drawStart = drawRange.start * rangeFactor;
let drawEnd = ( drawRange.start + drawRange.count ) * rangeFactor;
if ( group !== null ) {
drawStart = Math.max( drawStart, group.start * rangeFactor );
drawEnd = Math.min( drawEnd, ( group.start + group.count ) * rangeFactor );
}
if ( index !== null ) {
drawStart = Math.max( drawStart, 0 );
drawEnd = Math.min( drawEnd, index.count );
} else if ( position !== undefined && position !== null ) {
drawStart = Math.max( drawStart, 0 );
drawEnd = Math.min( drawEnd, position.count );
}
const drawCount = drawEnd - drawStart;
if ( drawCount < 0 || drawCount === Infinity ) return;
//
bindingStates.setup( object, material, program, geometry, index );
File: src/renderers/WebGLRenderer.js (L1635-1659)
javascript
if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld();
// update camera matrices and frustum
if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld();
if ( xr.enabled === true && xr.isPresenting === true && ( output === null || output.isCompositing() === false ) ) {
if ( xr.cameraAutoUpdate === true ) xr.updateCamera( camera );
camera = xr.getCamera(); // use XR camera for rendering
}
//
if ( scene.isScene === true ) scene.onBeforeRender( _this, scene, camera, _currentRenderTarget );
currentRenderState = renderStates.get( scene, renderStateStack.length );
currentRenderState.init( camera );
currentRenderState.state.textureUnits = textures.getTextureUnits();
renderStateStack.push( currentRenderState );
_projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
_frustum.setFromProjectionMatrix( _projScreenMatrix, WebGLCoordinateSystem, camera.reversedDepth );
File: src/renderers/WebGLRenderer.js (L1702-1708)
javascript
if ( _clippingEnabled === true ) clipping.beginShadows();
const shadowsArray = currentRenderState.state.shadowsArray;
shadowMap.render( shadowsArray, scene, camera );
if ( _clippingEnabled === true ) clipping.endShadows();
File: src/renderers/WebGLRenderer.js (L1718-1757)
javascript
if ( skipSceneRender === false ) {
const opaqueObjects = currentRenderList.opaque;
const transmissiveObjects = currentRenderList.transmissive;
currentRenderState.setupLights();
if ( camera.isArrayCamera ) {
const cameras = camera.cameras;
if ( transmissiveObjects.length > 0 ) {
for ( let i = 0, l = cameras.length; i < l; i ++ ) {
const camera2 = cameras[ i ];
renderTransmissionPass( opaqueObjects, transmissiveObjects, scene, camera2 );
}
}
if ( _renderBackground ) background.render( scene );
for ( let i = 0, l = cameras.length; i < l; i ++ ) {
const camera2 = cameras[ i ];
renderScene( currentRenderList, scene, camera2, camera2.viewport );
}
} else {
if ( transmissiveObjects.length > 0 ) renderTransmissionPass( opaqueObjects, transmissiveObjects, scene, camera );
if ( _renderBackground ) background.render( scene );
renderScene( currentRenderList, scene, camera );
File: src/renderers/WebGLRenderer.js (L1831-1885)
javascript
function projectObject( object, camera, groupOrder, sortObjects ) {
if ( object.visible === false ) return;
const visible = object.layers.test( camera.layers );
if ( visible ) {
if ( object.isGroup ) {
groupOrder = object.renderOrder;
} else if ( object.isLOD ) {
if ( object.autoUpdate === true ) object.update( camera );
} else if ( object.isLightProbeGrid ) {
currentRenderState.pushLightProbeGrid( object );
} else if ( object.isLight ) {
currentRenderState.pushLight( object );
if ( object.castShadow ) {
currentRenderState.pushShadow( object );
}
} else if ( object.isSprite ) {
if ( ! object.frustumCulled || _frustum.intersectsSprite( object ) ) {
if ( sortObjects ) {
_vector4.setFromMatrixPosition( object.matrixWorld )
.applyMatrix4( _projScreenMatrix );
}
const geometry = objects.update( object );
const material = object.material;
if ( material.visible ) {
currentRenderList.push( object, geometry, material, groupOrder, _vector4.z, null );
}
}
} else if ( object.isMesh || object.isLine || object.isPoints ) {
if ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) {
File: src/math/Frustum.js (L18-37)
javascript
class Frustum {
/**
* Constructs a new frustum.
*
* @param {Plane} [p0] - The first plane that encloses the frustum.
* @param {Plane} [p1] - The second plane that encloses the frustum.
* @param {Plane} [p2] - The third plane that encloses the frustum.
* @param {Plane} [p3] - The fourth plane that encloses the frustum.
* @param {Plane} [p4] - The fifth plane that encloses the frustum.
* @param {Plane} [p5] - The sixth plane that encloses the frustum.
*/
constructor( p0 = new Plane(), p1 = new Plane(), p2 = new Plane(), p3 = new Plane(), p4 = new Plane(), p5 = new Plane() ) {
/**
* This array holds the planes that enclose the frustum.
*
* @type {Array<Plane>}
*/
this.planes = [ p0, p1, p2, p3, p4, p5 ];
File: src/renderers/webgl/WebGLRenderLists.js (L162-168)
javascript
function sort( customOpaqueSort, customTransparentSort ) {
if ( opaque.length > 1 ) opaque.sort( customOpaqueSort || painterSortStable );
if ( transmissive.length > 1 ) transmissive.sort( customTransparentSort || reversePainterSortStable );
if ( transparent.length > 1 ) transparent.sort( customTransparentSort || reversePainterSortStable );
}
File: src/renderers/shaders/ShaderLib/meshnormal.glsl.js (L20-47)
javascript
void main() {
#include <uv_vertex>
#include <batching_vertex>
#include <beginnormal_vertex>
#include <morphinstance_vertex>
#include <morphnormal_vertex>
#include <skinbase_vertex>
#include <skinnormal_vertex>
#include <defaultnormal_vertex>
#include <normal_vertex>
#include <begin_vertex>
#include <morphtarget_vertex>
#include <skinning_vertex>
#include <displacementmap_vertex>
#include <project_vertex>
#include <logdepthbuf_vertex>
#include <clipping_planes_vertex>
#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP_TANGENTSPACE )
vViewPosition = - mvPosition.xyz;
#endif
}
File: src/renderers/webgl/WebGLProgram.js (L790-799)
javascript
vertexShader = resolveIncludes( vertexShader );
vertexShader = replaceLightNums( vertexShader, parameters );
vertexShader = replaceClippingPlaneNums( vertexShader, parameters );
fragmentShader = resolveIncludes( fragmentShader );
fragmentShader = replaceLightNums( fragmentShader, parameters );
fragmentShader = replaceClippingPlaneNums( fragmentShader, parameters );
vertexShader = unrollLoops( vertexShader );
fragmentShader = unrollLoops( fragmentShader );
File: src/renderers/webgl/WebGLProgram.js (L832-857)
javascript
const vertexGlsl = versionString + prefixVertex + vertexShader;
const fragmentGlsl = versionString + prefixFragment + fragmentShader;
// log( '*VERTEX*', vertexGlsl );
// log( '*FRAGMENT*', fragmentGlsl );
const glVertexShader = WebGLShader( gl, gl.VERTEX_SHADER, vertexGlsl );
const glFragmentShader = WebGLShader( gl, gl.FRAGMENT_SHADER, fragmentGlsl );
gl.attachShader( program, glVertexShader );
gl.attachShader( program, glFragmentShader );
// Force a particular attribute to index 0.
if ( parameters.index0AttributeName !== undefined ) {
gl.bindAttribLocation( program, 0, parameters.index0AttributeName );
} else if ( parameters.morphTargets === true ) {
// programs with morphTargets displace position out of attribute 0
gl.bindAttribLocation( program, 0, 'position' );
}
gl.linkProgram( program );
File: src/renderers/webgl/WebGLOutput.js (L74-106)
javascript
fragmentShader: /* glsl */`
precision highp float;
uniform sampler2D tDiffuse;
varying vec2 vUv;
#include <tonemapping_pars_fragment>
#include <colorspace_pars_fragment>
void main() {
gl_FragColor = texture2D( tDiffuse, vUv );
#ifdef LINEAR_TONE_MAPPING
gl_FragColor.rgb = LinearToneMapping( gl_FragColor.rgb );
#elif defined( REINHARD_TONE_MAPPING )
gl_FragColor.rgb = ReinhardToneMapping( gl_FragColor.rgb );
#elif defined( CINEON_TONE_MAPPING )
gl_FragColor.rgb = CineonToneMapping( gl_FragColor.rgb );
#elif defined( ACES_FILMIC_TONE_MAPPING )
gl_FragColor.rgb = ACESFilmicToneMapping( gl_FragColor.rgb );
#elif defined( AGX_TONE_MAPPING )
gl_FragColor.rgb = AgXToneMapping( gl_FragColor.rgb );
#elif defined( NEUTRAL_TONE_MAPPING )
gl_FragColor.rgb = NeutralToneMapping( gl_FragColor.rgb );
#elif defined( CUSTOM_TONE_MAPPING )
gl_FragColor.rgb = CustomToneMapping( gl_FragColor.rgb );
#endif
#ifdef SRGB_TRANSFER
gl_FragColor = sRGBTransferOETF( gl_FragColor );
#endif
}`,
File: src/renderers/webgl/WebGLState.js (L135-191)
javascript
setFunc: function ( depthFunc ) {
if ( currentReversed ) depthFunc = ReversedDepthFuncs[ depthFunc ];
if ( currentDepthFunc !== depthFunc ) {
switch ( depthFunc ) {
case NeverDepth:
gl.depthFunc( gl.NEVER );
break;
case AlwaysDepth:
gl.depthFunc( gl.ALWAYS );
break;
case LessDepth:
gl.depthFunc( gl.LESS );
break;
case LessEqualDepth:
gl.depthFunc( gl.LEQUAL );
break;
case EqualDepth:
gl.depthFunc( gl.EQUAL );
break;
case GreaterEqualDepth:
gl.depthFunc( gl.GEQUAL );
break;
case GreaterDepth:
gl.depthFunc( gl.GREATER );
break;
case NotEqualDepth:
gl.depthFunc( gl.NOTEQUAL );
break;
default:
gl.depthFunc( gl.LEQUAL );
}
currentDepthFunc = depthFunc;
}
File: src/renderers/webgl/WebGLState.js (L609-627)
javascript
function setBlending( blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha, blendColor, blendAlpha, premultipliedAlpha ) {
if ( blending === NoBlending ) {
if ( currentBlendingEnabled === true ) {
disable( gl.BLEND );
currentBlendingEnabled = false;
}
return;
}
if ( currentBlendingEnabled === false ) {
enable( gl.BLEND );
currentBlendingEnabled = true;
File: src/renderers/webgl/WebGLState.js (L754-790)
javascript
function setMaterial( material, frontFaceCW ) {
material.side === DoubleSide
? disable( gl.CULL_FACE )
: enable( gl.CULL_FACE );
let flipSided = ( material.side === BackSide );
if ( frontFaceCW ) flipSided = ! flipSided;
setFlipSided( flipSided );
( material.blending === NormalBlending && material.transparent === false )
? setBlending( NoBlending )
: setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst, material.blendEquationAlpha, material.blendSrcAlpha, material.blendDstAlpha, material.blendColor, material.blendAlpha, material.premultipliedAlpha );
depthBuffer.setFunc( material.depthFunc );
depthBuffer.setTest( material.depthTest );
depthBuffer.setMask( material.depthWrite );
colorBuffer.setMask( material.colorWrite );
const stencilWrite = material.stencilWrite;
stencilBuffer.setTest( stencilWrite );
if ( stencilWrite ) {
stencilBuffer.setMask( material.stencilWriteMask );
stencilBuffer.setFunc( material.stencilFunc, material.stencilRef, material.stencilFuncMask );
stencilBuffer.setOp( material.stencilFail, material.stencilZFail, material.stencilZPass );
}
setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits );
material.alphaToCoverage === true
? enable( gl.SAMPLE_ALPHA_TO_COVERAGE )
: disable( gl.SAMPLE_ALPHA_TO_COVERAGE );
}