BabylonJS - ShaderMaterial
BabylonJS - 使用 ShaderMaterial
Section titled “BabylonJS - 使用 ShaderMaterial”ShaderMaterial 允许你使用用 GLSL (OpenGL Shading Language) 编写的自定义着色器程序来定义网格体的外观。这为你提供了对渲染过程的最大控制权,从而实现超出标准材质的独特视觉效果。
它充当一座桥梁,将数据(如网格体顶点、法线、UV 和自定义 Uniforms)从你的 JavaScript 代码传递到运行在 GPU 上的顶点着色器 (Vertex shader) 和片段着色器 (Fragment shader)。
注意: 编写 GLSL 着色器需要理解 GPU 渲染概念。对于许多效果,NodeMaterial 编辑器提供了一个强大的可视化替代方案,无需直接编写 GLSL 代码。
创建 ShaderMaterial
Section titled “创建 ShaderMaterial”你可以创建一个 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)。
ShaderMaterial GLSL 基础知识
Section titled “ShaderMaterial GLSL 基础知识”你需要两个着色器程序:
- 顶点着色器 (Vertex Shader): 每个顶点运行一次。其主要工作是计算顶点的最终屏幕位置 (
gl_Position)。它还可以通过varying变量将数据(如 UV 坐标或计算出的光照值)传递给片段着色器。 - 片段(像素)着色器 (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; 开头,以指定浮点计算的默认精度。
示例:简单的纹理 Shader
Section titled “示例:简单的纹理 Shader”让我们创建一个简单的材质,它仅使用 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(); });- GLSL 代码: 我们定义了基本的顶点和片段着色器。顶点着色器使用
worldViewProjection矩阵(由 BabylonJS 提供)变换position,并通过vUVvarying 将uv坐标传递给片段着色器。 - 片段着色器: 它接收插值的
vUV,并使用内置函数texture2D在该坐标处从textureSampler查找颜色,将结果设置为gl_FragColor。 - ShaderMaterial 创建: 我们实例化
ShaderMaterial,通过"custom"标识符引用存储在ShadersStore中的着色器。我们声明了必要的attributes(position,uv),uniforms(worldViewProjection) 和samplers(textureSampler)。 - 设置 Uniforms: 我们创建一个
BABYLON.Texture,并使用shaderMaterial.setTexture("textureSampler", mainTexture)将其绑定到片段着色器中的uniform sampler2D textureSampler变量。 - 应用: 我们将创建的
shaderMaterial分配给平面网格体的material属性。
传递自定义数据 (Uniforms)
Section titled “传递自定义数据 (Uniforms)”你可以使用 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,添加新的 uniformsconst 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' uniformlet 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 编译错误。