Skip to content

BabylonJS - ShaderMaterial

ShaderMaterial 允许你使用用 GLSL (OpenGL Shading Language) 编写的自定义着色器程序来定义网格体的外观。这为你提供了对渲染过程的最大控制权,从而实现超出标准材质的独特视觉效果。

它充当一座桥梁,将数据(如网格体顶点、法线、UV 和自定义 Uniforms)从你的 JavaScript 代码传递到运行在 GPU 上的顶点着色器 (Vertex shader) 和片段着色器 (Fragment shader)。

注意: 编写 GLSL 着色器需要理解 GPU 渲染概念。对于许多效果,NodeMaterial 编辑器提供了一个强大的可视化替代方案,无需直接编写 GLSL 代码。

你可以创建一个 BABYLON.ShaderMaterial 的实例:

const myShaderMaterial = new BABYLON.ShaderMaterial(
"myShader", // Unique name for the material 材质的唯一名称
scene, // The Babylon scene Babylon 场景
{
vertex: "customVertexShaderName", // Identifier for vertex shader code 顶点着色器代码的标识符
fragment: "customFragmentShaderName" // Identifier for fragment shader code 片段着色器代码的标识符
// Or use vertexSource/fragmentSource for direct strings
// 或者使用 vertexSource/fragmentSource 直接使用字符串
// Or vertexElement/fragmentElement for script tag IDs
// 或者使用 vertexElement/fragmentElement 指定 script 标签 ID
// Or provide a base path string like "./shaders/myShader"
// 或者提供一个基本路径字符串,如 "./shaders/myShader"
// (loads myShader.vertex.glsl and myShader.fragment.glsl)
// (加载 myShader.vertex.glsl 和 myShader.fragment.glsl)
},
{
attributes: ["position", "normal", "uv"], // Standard attributes Babylon provides Babylon 提供的标准属性
uniforms: ["worldViewProjection", "time", "customColor"], // Uniforms expected by shaders 着色器期望的 Uniforms
samplers: ["textureSampler"] // Texture samplers expected by shaders 着色器期望的纹理 Samplers
}
);
  • 名称 (Name): 标识此材质的唯一字符串。
  • 场景 (Scene): BabylonJS 场景实例。
  • 着色器路径/对象 (Shader Path/Object): 指定 GLSL 代码的位置。有几种选项:
    • { vertex: "name", fragment: "name" }:使用存储在 BABYLON.Effect.ShadersStore["nameVertexShader"] 和 BABYLON.Effect.ShadersStore["nameFragmentShader"] 中的着色器。
    • { vertexSource: "glsl code string", fragmentSource: "glsl code string" }:直接将 GLSL 代码嵌入为字符串。
    • { vertexElement: "id", fragmentElement: "id" }:使用嵌入在 HTML <script> 标签中(通过其 id 标识)的 GLSL 代码。
    • "./path/shaderName":(推荐用于组织)从相对于 HTML 文件加载名为 shaderName.vertex.glsl 和 shaderName.fragment.glsl 的外部着色器文件。
  • 选项对象 (Options Object): 声明你的着色器期望的数据:
    • attributes:所需每个顶点属性的字符串数组(例如,position,normal,uv,color)。如果网格体具有这些属性,BabylonJS 会自动提供标准的属性。
    • uniforms:所需 Uniform 变量的字符串数组。Uniform 在单次绘制调用中对所有顶点/片段保持不变(例如,变换矩阵如 worldViewProjection,时间,颜色,自定义值)。BabylonJS 会自动提供标准矩阵。
    • samplers:所需纹理 Sampler 的字符串数组(例如,textureSampler)。

你需要两个着色器程序:

  1. 顶点着色器 (Vertex Shader): 每个顶点运行一次。其主要工作是计算顶点的最终屏幕位置 (gl_Position)。它还可以通过 varying 变量将数据(如 UV 坐标或计算出的光照值)传递给片段着色器。
  2. 片段(像素)着色器 (Fragment (Pixel) Shader): 每个被网格体覆盖的像素运行一次。其主要工作是计算该像素的最终颜色 (gl_FragColor),通常使用从顶点着色器传递的数据和纹理查找。

常见的 GLSL 变量类型:

  • float:单精度浮点数。
  • vec2、vec3、vec4:由 2、3 或 4 个浮点数组成的向量(用于位置、颜色、UV)。
  • mat3、mat4:3x3 或 4x4 矩阵(用于变换)。
  • sampler2D:表示一个 2D 纹理。
  • attribute:每个顶点的输入数据(通过 attributes 选项从 JavaScript 提供)。
  • uniform:每次绘制调用中保持不变的输入数据(通过 uniforms 选项从 JavaScript 提供)。
  • varying:用于将数据从顶点着色器传递给片段着色器。
  • gl_Position (顶点着色器输出):顶点的最终裁剪空间位置。
  • gl_FragColor (片段着色器输出):像素的最终颜色。

GLSL 着色器通常以 precision highp float; 开头,以指定浮点计算的默认精度。

让我们创建一个简单的材质,它仅使用 ShaderMaterial 将纹理应用于网格体。在本例中,我们将把 GLSL 代码存储在 ShadersStore 中。

import * as BABYLON from '@babylonjs/core';
const canvas = document.getElementById('renderCanvas');
const engine = new BABYLON.Engine(canvas, true);
const createScene = async function() {
const scene = new BABYLON.Scene(engine);
const camera = new BABYLON.ArcRotateCamera("camera", -Math.PI / 2, Math.PI / 2.5, 5, BABYLON.Vector3.Zero(), scene);
camera.attachControl(canvas, true);
const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(0, 1, 0), scene);
// --- GLSL Shader Code ---
// --- GLSL 着色器代码 ---
// Vertex Shader: Pass position and UVs through
// 顶点着色器:传递位置和 UV
BABYLON.Effect.ShadersStore["customVertexShader"] = `
precision highp float;
// Attributes (provided by Babylon based on mesh data)
// Attributes (由 Babylon 根据网格体数据提供)
attribute vec3 position;
attribute vec2 uv;
// Uniforms (provided by Babylon)
// Uniforms (由 Babylon 提供)
uniform mat4 worldViewProjection;
// Varying (data to pass to fragment shader)
// Varying (传递给片段着色器的数据)
varying vec2 vUV;
void main(void) {
// Calculate final screen position
// 计算最终屏幕位置
gl_Position = worldViewProjection * vec4(position, 1.0);
// Pass UV coordinates to the fragment shader
// 将 UV 坐标传递给片段着色器
vUV = uv;
}
`;
// Fragment Shader: Sample texture using UVs
// 片段着色器:使用 UV 采样纹理
BABYLON.Effect.ShadersStore["customFragmentShader"] = `
precision highp float;
// Varying (received from vertex shader)
// Varying (从顶点着色器接收)
varying vec2 vUV;
// Uniforms (texture sampler we'll set from JS)
// Uniforms (我们将从 JS 设置的纹理 Sampler)
uniform sampler2D textureSampler;
void main(void) {
// Look up color in the texture at the interpolated UV coordinate
// 在插值的 UV 坐标处查找纹理中的颜色
gl_FragColor = texture2D(textureSampler, vUV);
}
`;
// --- Create ShaderMaterial Instance ---
// --- 创建 ShaderMaterial 实例 ---
const shaderMaterial = new BABYLON.ShaderMaterial("simpleTextureShader", scene,
{
vertex: "custom", // Corresponds to ShadersStore entry name 对应 ShadersStore 入口名称
fragment: "custom"
},
{
attributes: ["position", "uv"], // We need position and UVs from the mesh 我们需要网格体的位置和 UV
uniforms: ["worldViewProjection"], // Babylon provides this matrix Babylon 提供此矩阵
samplers: ["textureSampler"] // Declare the sampler uniform we'll use 声明我们将使用的 sampler uniform
}
);
// --- Set Uniforms ---
// --- 设置 Uniforms ---
// Load the texture
// 加载纹理
const mainTexture = new BABYLON.Texture("path/to/your/texture.jpg", scene);
// Set the texture sampler uniform
// 设置纹理 sampler uniform
shaderMaterial.setTexture("textureSampler", mainTexture);
// Optional: Disable backface culling if needed
// 可选:如果需要,禁用背面剔除
shaderMaterial.backFaceCulling = false;
// --- Apply to Mesh ---
// --- 应用到网格体 ---
const plane = BABYLON.MeshBuilder.CreatePlane("plane", {size: 2}, scene);
plane.material = shaderMaterial;
return scene;
};
createScene().then(scene => {
engine.runRenderLoop(function() { if (scene) { scene.render(); } });
});
window.addEventListener('resize', function() { engine.resize(); });
  1. GLSL 代码: 我们定义了基本的顶点和片段着色器。顶点着色器使用 worldViewProjection 矩阵(由 BabylonJS 提供)变换 position,并通过 vUV varying 将 uv 坐标传递给片段着色器。
  2. 片段着色器: 它接收插值的 vUV,并使用内置函数 texture2D 在该坐标处从 textureSampler 查找颜色,将结果设置为 gl_FragColor。
  3. ShaderMaterial 创建: 我们实例化 ShaderMaterial,通过 "custom" 标识符引用存储在 ShadersStore 中的着色器。我们声明了必要的 attributes (position,uv),uniforms (worldViewProjection) 和 samplers (textureSampler)。
  4. 设置 Uniforms: 我们创建一个 BABYLON.Texture,并使用 shaderMaterial.setTexture("textureSampler", mainTexture) 将其绑定到片段着色器中的 uniform sampler2D textureSampler 变量。
  5. 应用: 我们将创建的 shaderMaterial 分配给平面网格体的 material 属性。

你可以使用 Uniforms 将自己的数据(例如用于动画的时间、颜色、阈值等)传递到着色器。

// --- Inside createScene ---
// --- 在 createScene 内部 ---
// Modify Fragment Shader to use a custom color
// 修改片段着色器以使用自定义颜色
BABYLON.Effect.ShadersStore["customColorFragmentShader"] = `
precision highp float;
varying vec2 vUV;
uniform sampler2D textureSampler;
uniform vec4 customColor; // Added uniform 添加的 uniform
uniform float time; // Added uniform 添加的 uniform
void main(void) {
vec4 textureColor = texture2D(textureSampler, vUV);
// Modulate texture color by customColor and add a time-based effect
// 将纹理颜色与 customColor 相乘,并添加基于时间的效果
gl_FragColor = textureColor * customColor * (0.75 + sin(time) * 0.25);
}
`;
// Create ShaderMaterial, adding new uniforms
// 创建 ShaderMaterial,添加新的 uniforms
const customShaderMaterial = new BABYLON.ShaderMaterial("customColorShader", scene,
{ vertex: "custom", fragment: "customColor" }, // Use the new fragment shader 使用新的片段着色器
{
attributes: ["position", "uv"],
uniforms: ["worldViewProjection", "customColor", "time"], // Added uniforms 添加的 uniforms
samplers: ["textureSampler"]
}
);
// Set the texture
// 设置纹理
const myTexture = new BABYLON.Texture("path/to/your/texture.jpg", scene);
customShaderMaterial.setTexture("textureSampler", myTexture);
// Set initial custom uniform values
// 设置初始自定义 uniform 值
const colorToSend = new BABYLON.Color4(0.5, 1.0, 0.5, 1.0); // Greenish tint 微绿色调
customShaderMaterial.setColor4("customColor", colorToSend);
customShaderMaterial.setFloat("time", 0);
// Apply to a mesh
// 应用到网格体
const sphere = BABYLON.MeshBuilder.CreateSphere("sphere", {diameter: 2}, scene);
sphere.material = customShaderMaterial;
// Animate the 'time' uniform in the render loop
// 在渲染循环中动画 'time' uniform
let time = 0;
scene.onBeforeRenderObservable.add(() => {
time += engine.getDeltaTime() / 1000.0; // Time in seconds 时间(秒)
customShaderMaterial.setFloat("time", time);
});

在这里,我们在片段着色器中添加了 uniform vec4 customColor 和 uniform float time。在 JavaScript 中,我们在创建 ShaderMaterial 时在 uniforms 数组中声明它们。然后使用 setColor4("uniformName", value) 和 setFloat("uniformName", value) 等方法来传递数据。其他方法包括 setVector3、setMatrix 等。在渲染循环中更新 time uniform 会在着色器内部创建动画效果。

ShaderMaterial 是一个强大的工具,用于高级渲染。从简单的示例开始,逐步增加复杂性。如果你的着色器没有按预期工作,请务必检查浏览器的开发者控制台中的 GLSL 编译错误。