当前位置: 首页 > news >正文

Babylon.js的Shader入门一(从只有一个颜色的Shader说起)

一、顶点着色器

文件名BaseColor.vertex.fx

attribute vec3 position;

uniform mat4 worldViewProjection;

void main() {
    gl_Position = worldViewProjection * vec4(position, 1.0);
}

1、关于attribute 和 uniform

在 GLSL 着色器和 Babylon.js 的实现中,attribute 和 uniform 是两种完全不同的变量类型,它们的区别体现在以下核心维度:


1)本质差异对比表

特性attributeuniform
作用阶段仅在顶点着色器可用顶点/片段着色器均可使用
数据来源从顶点缓冲区(VBO)读取由 CPU 端设置(开发者或引擎)
更新频率每个顶点都会变化同一绘制调用中所有顶点/片元相同
典型用途顶点位置、法线、UV坐标等变换矩阵、颜色参数、时间值等
Babylon.js 传递方式需在 attributes 数组中声明需在 uniforms 数组中声明

2)数据流示意图

[CPU 端]
│
├── uniform 数据(如矩阵、颜色) → 通过 setXXX() 方法传入 → [Shader]
│
└── 顶点缓冲区(VBO) → attribute 数据 → [顶点着色器]

3)在示例代码中的具体表现

(1) attribute vec3 position
  • 作用:接收来自网格顶点缓冲区的坐标数据

  • 数据来源

    const box = MeshBuilder.CreateBox(...); // 自动生成顶点坐标
  • 更新规则:每个顶点的位置值不同(如立方体有8个顶点,每个顶点都有独立的position值)

(2) uniform mat4 worldViewProjection
  • 作用:传递组合变换矩阵

  • 数据来源

    // Babylon.js 每帧自动计算:
    const matrix = mesh.getWorldMatrix() * camera.getViewMatrix() * camera.getProjectionMatrix();
  • 更新规则:同一物体的所有顶点共享同一个矩阵值

(3) uniform vec3 diffuseColor
  • 作用:自定义颜色参数

  • 数据设置

    myMaterial.setColor3("diffuseColor", ...); // 手动设置
  • 更新规则:整个物体使用同一颜色值

(4)Babylon.js 的特殊处理机制
  • attribute 自动绑定

        当在 attributes 数组中声明 "position" 时,Babylon.js 会自动关联到网格的顶点位置缓冲区,其他常用自动绑定的 attribute:normal:顶点法线、uv:纹理坐标、color:顶点颜色

  • uniform 更新策略
uniform 类型更新频率典型示例
引擎管理每帧worldViewProjection
开发者控制按需diffuseColor
  • Babylon.js 还支持自动传递这些标准矩阵:
Uniform 名称对应矩阵典型用途
world世界矩阵物体自身变换
view视图矩阵摄像机视角
projection投影矩阵透视/正交效果
worldView世界视图矩阵组合变换计算

2、关于gl_Position

1)gl_Position 的真实含义

gl_Position 存储的是顶点在**裁剪空间(Clip Space)**中的坐标,并非直接的屏幕位置。这是图形渲染管线中最重要的中间坐标阶段。


2)坐标系转换全流程(逐步分析)

模型空间(Model Space)
  • 原始顶点坐标(示例中的 position 属性)
  • 示例值:(0,0,0) 到 (1,1,1)(立方体顶点)
世界空间(World Space)
  • 通过 worldMatrix 变换
  • 物体在场景中的实际位置和姿态
vec4 worldPos = worldMatrix * vec4(position, 1.0);
观察空间(View Space)
  • 通过 viewMatrix 变换
  • 以摄像机为原点的坐标系
vec4 viewPos = viewMatrix * worldPos;
裁剪空间(Clip Space)
  • 通过 projectionMatrix 变换
  • 坐标范围:[-w, w](w 是齐次坐标的w分量)
gl_Position = projectionMatrix * viewPos; // 等价于 worldViewProjection * modelPos
标准化设备坐标(NDC)
  • 通过透视除法自动转换
  • 范围:[-1, 1]^3
vec3 ndc = gl_Position.xyz / gl_Position.w;
视口变换(Viewport Transform)
  • 最终映射到屏幕像素
  • 由 gl.viewport 设置决定
vec2 screenPos = (ndc.xy * 0.5 + 0.5) * viewportSize;

3)关键特性对比表

特性裁剪空间坐标 (gl_Position)屏幕坐标
坐标系类型齐次坐标系笛卡尔坐标系
坐标范围任意(需满足 -w ≤ x,y,z ≤ w)[0, width] x [0, height]
自动转换阶段由硬件完成透视除法和视口变换最终输出结果
开发者控制权完全控制间接受 viewport 设置影响

4)通过实验验证坐标变换

(1)修改顶点着色器输出:
// 强制所有顶点位于裁剪空间中心
gl_Position = vec4(0, 0, 0, 1); 

效果:所有顶点将集中在屏幕中心点(NDC的(0,0)映射到屏幕中心)

(2) 超出裁剪空间的顶点:
gl_Position = vec4(2.0, 2.0, 0.0, 1.0); // x/w=2.0 > 1.0

效果:该顶点会被裁剪(不生成片元),因为不满足 -1 ≤ x/w ≤ 1


5)Babylon.js 的矩阵自动化

当使用 worldViewProjection 矩阵时,Babylon.js 已经帮你完成了:

\begin{aligned}
\text{worldViewProjection} &= \text{World} \times \text{View} \times \text{Projection} \\
&= \begin{bmatrix}
\cdots & \text{组合变换} & \cdots \\
\end{bmatrix}
\end{aligned}

这使得开发者无需手动处理各阶段的矩阵乘法。


6)典型错误认知纠正

❌ 误区:"gl_Position 直接对应屏幕像素坐标"

正确理解

  • gl_Position 需要经过:

  1. 透视除法(x/w, y/w, z/w)
  2. 视口映射:将 NDC 的 [-1,1]² 映射到屏幕区域
❌ 误区:"w 分量可以忽略"

关键作用

  • w 分量控制透视效果(实现近大远小)

  • 深度值(z/w) 影响深度测试

  • 齐次坐标的归一化


7)实际开发建议

(1)调试裁剪空间坐标
// 在片段着色器中可视化裁剪空间坐标
gl_FragColor = vec4(gl_FragCoord.xyz / gl_FragCoord.w, 1.0);
(2)处理超大场景
  • 当物体超出远裁剪面(通常 z/w > 1),会自动被裁剪
  • 需合理设置投影矩阵参数:
camera.minZ = 0.1;  // 近裁剪面
camera.maxZ = 100.0; // 远裁剪面
(3)高级应用示例(变形动画):
// 在裁剪空间添加正弦波动画
float wave = sin(time + position.x * 5.0) * 0.2;
gl_Position = worldViewProjection * vec4(position.x, position.y + wave, position.z, 1.0);

二、片元着色器

文件BaseColor.fragment.fx

precision mediump float;
        
uniform vec3 diffuseColor;

void main() {
    gl_FragColor = vec4(diffuseColor, 1.0);
}

1、精度声明

 precision mediump float;这一行是 GLSL(OpenGL Shading Language)中的精度修饰符声明,在 WebGL 着色器中有以下核心作用:

1)定义浮点数精度:

  • highp 表示高精度浮点数(通常是32位)
  • mediump 表示中精度(通常是16位)
  • lowp 表示低精度(通常≤10位)

2)在WebGL环境中的必要性:

  • 顶点着色器中默认有 highp 精度
  • 片段着色器必须显式声明精度(否则可能报错)
  • 在移动设备中建议使用mediump

3)健壮的写法:

#ifdef GL_FRAGMENT_PRECISION_HIGH
 precision highp float;
#else
 precision mediump float;
#endif

2、关于gl_FragColor

gl_FragColor 是片段着色器的输出颜色,但严格来说它还不是屏幕上的最终颜色。这是图形渲染中一个关键但需要细致理解的概念,让我们通过完整的渲染流程来解析它的真实角色:


1)gl_FragColor 的定位与转换流程

以下是完整的颜色处理流程:

片段着色器计算 
→ [gl_FragColor] 
→ 模板测试(Stencil Test) 
→ 深度测试(Depth Test) 
→ 混合(Blending) 
→ 颜色缓冲(Color Buffer) 
→ 最终屏幕像素

2)各阶段对颜色的影响详解

(1)片段着色器输出阶段
gl_FragColor = vec4(diffuseColor, 1.0); // 输出 RGBA 值
  • 角色:定义当前片段的"候选颜色"
  • 存储位置:暂存于片段处理单元
  • 此时的状态:可能被后续流程修改或丢弃
(2)模板测试(Stencil Test)
  • 作用:通过模板缓冲区值决定是否保留片段
  • 示例场景:制作UI遮罩、镜面反射区域
  • 影响结果:若未通过测试,直接丢弃该片段
(3)深度测试(Depth Test)
  • 判断条件
if(current_fragment.depth > depth_buffer_value) discard;
  • 典型表现:遮挡关系处理(近处物体覆盖远处物体)
  • Babylon.js 控制方式
material.depthFunction = BABYLON.Engine.NEVER; // 自定义测试规则
(4)混合(Blending)
  • 触发条件:当材质开启 alpha 混合时
  • 计算公式
最终颜色 = srcColor * srcAlpha + dstColor * (1 - srcAlpha)

Babylon.js 设置

material.alphaMode = BABYLON.Engine.ALPHA_COMBINE; // 启用混合
material.separateCullingPass = true; // 优化半透明渲染
(5)颜色缓冲写入
  • 最终存储:通过所有测试的颜色值写入帧缓冲区
  • 后期处理影响:可能经过抗锯齿、HDR 色调映射等处理

3)关键特性对比表

特性gl_FragColor屏幕最终颜色
决定阶段片段着色器输出所有测试/混合后的结果
修改可能性可能被后续测试丢弃实际可见的稳定值
数据存储临时片段数据帧缓冲区(最终像素)
开发者控制程度完全控制间接影响

4)通过实验验证流程

实验1:强制修改深度值
// 在片段着色器中:
gl_FragColor = vec4(1,0,0,1);
gl_FragDepth = 0.0; // 强制深度值为最近

效果:该片段会覆盖所有后续渲染的更深层片段

实验2:半透明混合效果
// 在材质设置中:
material.alpha = 0.5; // 设置半透明
material.backFaceCulling = false; // 显示双面

效果:物体呈现半透明状态,能看到后方景物


5)现代 WebGL 的扩展用法(WebGL2+)

在更现代的实践中:

// 使用显式输出定位(替代 gl_FragColor)
out vec4 finalColor;
void main() {
    finalColor = vec4(diffuseColor, 1.0);
}
  • 优势:支持多渲染目标(MRT)

  • 兼容性:需要 WebGL2 上下文支持


6)Babylon.js 的封装逻辑

引擎在底层自动处理:

  1. 帧缓冲区管理:自动创建/绑定颜色缓冲

  2. 测试状态控制:默认开启深度测试和背面剔除

  3. 混合模式预设:提供多种混合模式选项
material.alphaMode = BABYLON.Engine.ALPHA_ADD; // 叠加模式

7)实际开发注意事项

(1)性能优化

  • 尽早通过 discard; 丢弃不需要的片段
if(alpha < 0.01) discard; // 减少后续计算

(2)颜色空间

  • WebGL 默认使用线性颜色空间
  • 需要手动做 Gamma 校正:
vec3 gammaCorrected = pow(color, vec3(1.0/2.2));
gl_FragColor = vec4(gammaCorrected, 1.0);

(3)HDR 处理

// 使用浮点帧缓冲区时
gl_FragColor = vec4(color * exposure, 1.0);

总之gl_FragColor 不是屏幕最终颜色,而是:

  • 片段着色器输出的"候选颜色"

  • 需要经过深度测试、模板测试、混合等后续处理

  • 最终可能被修改、混合或完全丢弃

在示例代码中,由于:

  • 没有开启混合(alpha=1.0)

  • 默认通过深度测试

  • 没有后期处理效果

因此 gl_FragColor 会直接写入颜色缓冲,但严格来说它仍然要经过渲染管线的完整处理流程才能成为最终像素颜色。

三、Babylon.js使用示例

// 创建自定义着色器材质
const myMaterial = new BABYLON.ShaderMaterial("myShader", scene, 
    "./src/Shader/BaseColor",
    {
        attributes: ["position"],
        uniforms: ["worldViewProjection", "diffuseColor"]
    });
// 设置默认颜色(可以后续通过material.setColor3修改)
myMaterial.setColor3("diffuseColor", BABYLON.Color3.Red());

// 应用到网格
const box = BABYLON.MeshBuilder.CreateBox("box", {size: 2}, scene);
box.material = myMaterial;

        这里需要说明的是,BABYLON.ShaderMaterial的第三个参数"./src/Shader/BaseColor"描述了shader文件的保存位置("./src/Shader")和总体名称("BaseColor"),Babylon可据此找到"BaseColor.vertex.fx"文件和 "BaseColor.fragment.fx"文件。

相关文章:

  • 关于Go中使用goroutine协程实现的算法
  • 前端面试:axios 请求的底层依赖是什么?
  • 服务器上的nginx因漏洞扫描需要升级
  • 数据结构(排序)
  • 【MySQL基础-3.2】MySQL DDL 语句详解:数据表操作篇
  • 扩展学习 | DeepSeek R1本地部署指南
  • Flutter桌面开发(三、widget布局与表单)
  • Qt的QToolButton设置弹出QMenu下拉菜单
  • 如何实现Spring Boot与Oracle数据库的完美对接?
  • 2025-03-12 学习记录--C/C++-PTA 习题8-4 报数
  • 12. Pandas :使用pandas读Excel文件的常用方法
  • WPF 制作机械手动画
  • 在线教育网站项目第三步 :通过wsl 2 安装ubuntu24.04
  • nginx中proxy_pass和root的区别
  • Flask
  • 抖音生活服务联动监管开展专项整治 济南66家违规餐饮商家下架
  • XSS跨站脚本攻击
  • ESP32芯片模组方案,设备物联网无线通信,WiFi蓝牙交互控制应用
  • Java中main函数中public static void main2(String[] args) 的String[] args是什么意思?
  • 用SpringBoot做一个web小案例实现登录
  • 东航C919航线上新!正式投入上海虹桥—深圳航线运营
  • “马上涨价”再到“吞下关税”,美政策让沃尔玛“输两次”
  • 首次带人形机器人走科技节红毯,傅利叶顾捷:机器人行业没包袱,很多事都能从零开始
  • 俄乌代表团抵达谈判会场
  • 车主质疑零跑汽车撞车后AEB未触发、气囊未弹出,4S店:其把油门当刹车
  • 青海省交通运输厅副厅长田明有接受审查调查