Babylon.js学习之路《六、材质与纹理:为模型赋予真实的表面效果》

文章目录

1. 引言:材质与纹理的重要性

1.1 材质与纹理的核心作用

2. 基础材质:StandardMaterial

2.1 材质属性详解
2.2 实战:创建金属材质

3. 纹理贴图:从基础到高级

3.1 基础纹理映射
3.2 多纹理混合技术

4. 高级材质:PBRMaterial

4.1 PBR 材质基础
4.2 PBR 材质实战:生锈金属

5. 动态材质与自定义着色器

5.1 动态修改材质属性
5.2 自定义 GLSL 着色器

6. 实战任务

任务 1:创建雪地下雪效果
任务 2:创建下雨天气
任务 3:创建全球多云雷电效果

7. 性能优化与常见问题

7.1 纹理优化技巧
7.2 常见问题解答

8. 总结与下一章预告

8.1 关键知识点回顾
8.2 下一章预告


1. 引言:材质与纹理的重要性

上一章详解灯光与阴影,点光源、方向光、聚光灯的设置与阴影优化技巧。
这一章详细介绍一下Babylon中,材质与纹理的相关知识,包含基础材质、PBR材质、着色器等。
材质与纹理的好坏,决定着场景的显示效果,更决定着使用者的体验。

1.1 材质与纹理的核心作用

核心作用:材质定义物体的视觉属性(颜色、反光、粗糙度),纹理提供表面细节(图案、凹凸、磨损)。
案例对比

无材质:物体呈现单一颜色,缺乏真实感。
基础材质:通过颜色和反光增强立体感。
纹理+材质:表面细节丰富(如木纹、锈迹、布料纤维)。


2. 基础材质:StandardMaterial

2.1 材质属性详解

如图,是一个没有材质的地面,下面为这个地形添加材质

关键属性

  // 地面
  var ground = BABYLON.Mesh.CreateGroundFromHeightMap(
      "ground",
      "/textures/heightMap.png",
      250,
      250,
      200,
      0,
      50, scene, false
  );
  // 地面材质
  var groundMaterial = new BABYLON.StandardMaterial("ground", scene);
  // 地面材质贴图
  groundMaterial.diffuseTexture = new BABYLON.Texture("/textures/ground.jpg", scene);
  // 横向纵向重复次数
  groundMaterial.diffuseTexture.uScale = 6;
  groundMaterial.diffuseTexture.vScale = 6;
  // 漫反射颜色(基础色)
  groundMaterial.diffuseColor = new BABYLON.Color3(1, 1, 1);
  // 高光颜色
  groundMaterial.specularColor = new BABYLON.Color3(0, 0, 0);
  // 自发光颜色(如霓虹灯)
  groundMaterial.emissiveColor = new BABYLON.Color3(0, 0, 0);
  // 环境光影响颜色
  groundMaterial.ambientColor = new BABYLON.Color3(1, 1, 1);
  // 透明度(0 ~ 1)
  groundMaterial.alpha = 1;
  ground.position.y = -2.05;
  ground.material = groundMaterial;

光泽与粗糙度

  groundMaterial.specularPower = 64; // 高光聚焦程度(值越大,光斑越小)
  groundMaterial.roughness = 0.3;     // 表面粗糙度(0光滑,1粗糙)

图片[1] - Babylon.js学习之路《六、材质与纹理:为模型赋予真实的表面效果》 - 宋马
经过几行代码的处理,一个沙丘形状的地形就展现出来。

2.2 实战:创建金属材质

目标:模拟抛光金属表面(高反光、锐利高光)。
代码示例

  const metalMat = new BABYLON.StandardMaterial("metalMat", scene);
  metalMat.diffuseColor = new BABYLON.Color3(0.8, 0.8, 0.8); // 银灰色
  metalMat.specularColor = new BABYLON.Color3(1, 1, 1);      // 白色高光
  metalMat.specularPower = 256; // 高光聚焦
  metalMat.emissiveColor = new BABYLON.Color3(0.1, 0.1, 0.1); // 轻微自发光
  sphere.material = metalMat;

3. 纹理贴图:从基础到高级

3.1 基础纹理映射

加载纹理贴图

  const diffuseTexture = new BABYLON.Texture(
    "textures/wood.jpg", // 纹理路径
    scene
  );
  material.diffuseTexture = diffuseTexture; // 应用漫反射贴图

纹理参数调整

  diffuseTexture.uScale = 2; // 横向重复次数
  diffuseTexture.vScale = 2; // 纵向重复次数
  diffuseTexture.wrapU = BABYLON.Texture.WRAP_REPEAT; // 横向重复模式
  diffuseTexture.wrapV = BABYLON.Texture.WRAP_MIRROR; // 纵向镜像重复

3.2 多纹理混合技术

法线贴图(Normal Map):模拟表面凹凸细节。

  const normalTexture = new BABYLON.Texture("textures/brick_normal.jpg", scene);
  material.bumpTexture = normalTexture;
  material.bumpTexture.level = 0.5; // 凹凸强度

高光贴图(Specular Map):控制表面反光区域。

  const specularTexture = new BABYLON.Texture("textures/brick_specular.jpg", scene);
  material.specularTexture = specularTexture;

环境光遮蔽贴图(AO Map):增强阴影细节。

  const aoTexture = new BABYLON.Texture("textures/brick_ao.jpg", scene);
  material.ambientTexture = aoTexture;

4. 高级材质:PBRMaterial

4.1 PBR 材质基础

PBR 核心原理:基于物理的渲染(金属度、粗糙度、环境反射)。
代码示例

  const pbrMat = new BABYLON.PBRMaterial("pbrMat", scene);
  pbrMat.albedoTexture = new BABYLON.Texture("textures/metal_albedo.png", scene);
  pbrMat.metallic = 0.9;      // 金属度(0非金属,1金属)
  pbrMat.roughness = 0.2;      // 粗糙度(0光滑,1粗糙)
  pbrMat.environmentTexture = BABYLON.CubeTexture.CreateFromPrefilteredData(
    "textures/env.dds", scene
  ); // 环境反射贴图

4.2 PBR 材质实战:生锈金属

纹理组合

  pbrMat.albedoTexture = new BABYLON.Texture("textures/rusted_iron_albedo.png", scene);
  pbrMat.metallicTexture = new BABYLON.Texture("textures/rusted_iron_metallic.png", scene);
  pbrMat.roughnessTexture = new BABYLON.Texture("textures/rusted_iron_roughness.png", scene);
  pbrMat.normalTexture = new BABYLON.Texture("textures/rusted_iron_normal.png", scene);
  pbrMat.ambientTexture = new BABYLON.Texture("textures/rusted_iron_ao.png", scene);

5. 动态材质与自定义着色器

5.1 动态修改材质属性

颜色渐变效果

  let time = 0;
  scene.registerBeforeRender(() => {
              
    material.diffuseColor = new BABYLON.Color3(
      Math.sin(time) * 0.5 + 0.5, // R通道
      Math.cos(time) * 0.5 + 0.5, // G通道
      0.5                         // B通道
    );
    time += 0.02;
  });

5.2 自定义 GLSL 着色器

着色器材质(ShaderMaterial)

  // 创建基于着色器中的材质效果【名称、场景、着色器代码的路径、创建着色器的选项】
  const customShader = new BABYLON.ShaderMaterial(
      "waveEffect", 
      scene, 
      {
              
          vertex: "waveEffect",
          fragment: "waveEffect",
      },
      {
              
          attributes: ["position", "uv"],
          uniforms: [
              "worldViewProjection",
              "time",
              "effectIntensity",
              "effectMultiplier"
          ],
          samplers: ["map", "noiseMap"],
          defines: ["#define PI 3.1415", "#define TAU 6.2832"]
      }
  );

  // 实例化新纹理【加载为纹理的图片】
  const noiseTexture = new BABYLON.Texture("textures/fur.jpg", scene);
  // 在着色器中设置纹理
  customShader.setTexture("noiseMap", noiseTexture);
  // 动画循环
  scene.registerBeforeRender(() => {
              
      // 在着色器中设置浮动
      customShader.setFloat("time", performance.now() / 1000);
      customShader.setFloat("effectIntensity", 1.0);
      customShader.setFloat("effectMultiplier", 1.0);
  });
  groundMesh.material = customShader;

6. 实战任务

任务 1:创建雪地下雪效果

目标:雪地山谷间的雪人。
实现步骤

引入山地模型
引入雪人模型
创建粒子系统

代码示例

// 引入山地
BABYLON.SceneLoader.ImportMesh("", "https://raw.githubusercontent.com/Ethan-J13/Promp_Models/main/Weather/", "snow_mountain.glb", scene, function(newMeshes, particleSystems, skeletons) {
              
    var Mountains = newMeshes[0]
    Mountains.scaling.x = 4
    Mountains.scaling.y = 2
    Mountains.scaling.z = 2
    Mountains.addRotation(-0.2, 0.15, -0.1);
    Mountains.position = new BABYLON.Vector3(-120, -22, 100);
}) 
// 引入雪人模型
let [snowMan] = await Promise.all([
    BABYLON.SceneLoader.ImportMeshAsync("", "https://assets.babylonjs.com/meshes/Demos/Snow_Man_Scene/", "snowMan.glb", scene)
])
snowMan = snowMan.meshes[0];
snowMan.name = "snowMan";
snowMan.scaling = new BABYLON.Vector3(12, 12, 12); // set the scaling to 20 times bigger
snowMan.position.y += 28;
snowMan.position.z += -85;
// 创建粒子系统【代码段,场景,GPU开关,URL,定义系统容量】
BABYLON.ParticleHelper.CreateFromSnippetAsync("VK7R6H#269", scene, false);

任务 2:创建下雨天气

目标:通过粒子系统创建下雨天气。

引入天空盒
创建地面
创建粒子系统

代码示例

// 创建不同粒子系统
BABYLON.ParticleHelper.CreateAsync("rain", scene, false).then((set) => {
              
    set.start();
});
// 天空盒
var skybox = BABYLON.Mesh.CreateBox("skyBox", 100.0, scene);
var skyboxMaterial = new BABYLON.StandardMaterial("skyBox", scene);
skyboxMaterial.backFaceCulling = false;
skyboxMaterial.reflectionTexture = new BABYLON.CubeTexture("textures/skybox", scene);
skyboxMaterial.reflectionTexture.coordinatesMode = BABYLON.Texture.SKYBOX_MODE;
skyboxMaterial.diffuseColor = new BABYLON.Color3(0, 0, 0);
skyboxMaterial.specularColor = new BABYLON.Color3(0, 0, 0);
skyboxMaterial.disableLighting = true;
skybox.material = skyboxMaterial;
// 创建地面
var ground = BABYLON.Mesh.CreateGroundFromHeightMap("ground", "textures/worldHeightMap.jpg", 200, 200, 250, 0, 10, scene, false);
var groundMaterial = new BABYLON.StandardMaterial("ground", scene);
groundMaterial.diffuseTexture = new BABYLON.Texture("textures/ground.jpg", scene);
groundMaterial.diffuseTexture.uScale = 6;
groundMaterial.diffuseTexture.vScale = 6;
groundMaterial.specularColor = new BABYLON.Color3(0, 0, 0);
ground.material = groundMaterial;

任务 3:创建全球多云雷电效果

目标:通过着色器自定义的材质,应用到地球表面,模拟地球自转和雷电。
代码示例

const createScene = async function () {
              
    const scene = new BABYLON.Scene(engine);
    const light = new BABYLON.PointLight("Omni", new BABYLON.Vector3(20, 20, 100), scene);
    const camera = new BABYLON.ArcRotateCamera("Camera", 0, 0.8, 200, BABYLON.Vector3.Zero(), scene);
    camera.attachControl(canvas, false);

    let time = 0;
    const rate = 0.01;
    scene.registerBeforeRender(function () {
              
        camera.alpha = time;
        light.position = camera.position;
        time += scene.getAnimationRatio() * rate;
    });
    // PostProcess可用于在渲染纹理后将着色器应用于纹理【片段着色器的url】
    const postEffect = new BABYLON.PostProcess("customEffect", "customEffect", ["iTime", "iResolution"], [], 1, camera);
    // 添加到onApplyObservable的函数
    postEffect.onApply = function (effect) {
              
        effect.setVector2('iResolution', new BABYLON.Vector2(postEffect.width, postEffect.height));
        effect.setFloat('iTime', time);
    };

    return scene;
}

BABYLON.Effect.ShadersStore['customEffectFragmentShader'] = `
    uniform float iTime;
    uniform vec2 iResolution;
    uniform sampler2D textureSampler;

    varying vec3 vPosition;
    varying vec2 vUV;

    const mat2 m = mat2(1.616, 1.212, -1.212, 1.616);

    float hash12(vec2 p) {
        p = fract(p * vec2(5.3983, 5.4427));
        p += dot(p.yx, p.xy + vec2(21.5351, 14.3137));
        return fract(p.x * p.y * 95.4337);
    }

    vec2 hash21(float p) {
        vec2 p2 = fract(p * vec2(5.3983, 5.4427));
        p2 += dot(p2.yx, p2.xy +  vec2(21.5351, 14.3137));
        return fract(vec2(p2.x * p2.y * 95.4337, p2.x * p2.y * 97.597));
    }

    float noise(in vec2 p) {
        vec2 i = floor(p);
        vec2 f = fract(p);
        vec2 u = f * f * (3.0 - 2.0 * f);

        return mix(mix(hash12(i + vec2(0.0, 0.0)), 
                    hash12(i + vec2(1.0, 0.0)), u.x),
                mix(hash12(i + vec2(0.0, 1.0)), 
                    hash12(i + vec2(1.0, 1.0)), u.x), u.y);
    }

    float hash12_3(vec2 p) {
        float f = hash12(p);
        return f * f * f;
    }

    float noise_3(in vec2 p) {
        vec2 i = floor(p);
        vec2 f = fract(p);
        vec2 u = f * f * (3.0 - 2.0 * f);

        return mix(mix(hash12_3(i + vec2(0.0, 0.0)), 
                    hash12_3(i + vec2(1.0, 0.0)), u.x),
                mix(hash12_3(i + vec2(0.0, 1.0)), 
                    hash12_3(i + vec2(1.0, 1.0)), u.x), u.y);
    }

    float fbm(vec2 p) {
        float f = 0.0;
        f += 0.5 * noise(p); p = m * p;
        f += 0.25 * noise(p); p = m * p;
        f += 0.125 * noise(p); p = m * p;
        f += 0.0625 * noise(p); p = m * p;
        f += 0.03125 * noise(p); p = m * p;
        f += 0.015625 * noise(p);
        return f / 0.984375;
    }

    vec3 getDir(vec2 screenPos) {
        screenPos -= 0.5;
        screenPos.x *= iResolution.x / iResolution.y;
        
        return normalize(vec3(0.0, -1.0, -3.0)
                        + screenPos.x * vec3(1.0, 0.0, 0.0)
                        - screenPos.y * vec3(0.0, -0.948683298, 0.316227766));
    }

    bool getPosition(in vec3 camera, in vec3 dir, out vec2 pos) {
        bool valid = false;
        
        float b = dot(camera, dir);
        float c = dot(camera, camera) - 1.0;
        float h = b * b - c;
        if (h > 0.0) {
            valid = true;
            
            vec3 p = camera + (-b - sqrt(h)) * dir;
            pos = p.xz + iTime * vec2(0.005, 0.02);
        }

        return valid;
    }

    void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
        vec2 screen = fragCoord.xy / iResolution.xy;
        
        vec3 camera = vec3(0.0, 1.2, 0.7);
        vec3 dir = getDir(screen);
        
        vec3 earth = vec3(0.0, 0.0, 0.0);
        vec2 position;
        if (getPosition(camera, dir, position)) {
            float geography = fbm(6.0 * position);

            float coast = 0.2 * pow(geography + 0.5, 50.0);
            float population = smoothstep(0.2, 0.6, fbm(2.0 * position) + coast);
            vec2 p = 40.0 * position;
            population *= (noise_3(p) + coast); p = m * p;
            population *= (noise_3(p) + coast); p = m * p;
            population *= (noise_3(p) + coast); p = m * p;
            population *= (noise_3(p) + coast); p = m * p;
            population *= (noise_3(p) + coast);
            population = smoothstep(0.0, 0.02, population);

            vec3 land = vec3(0.1 + 2.0 * population, 0.07 + 1.3 * population, population);
            vec3 water = vec3(0.0, 0.05, 0.1);
            vec3 ground = mix(land, water, smoothstep(0.49, 0.5, geography));

            vec2 wind = vec2(fbm(30.0 * position), fbm(60.0 * position));
            float weather = fbm(20.0 * (position + 0.03 * wind)) * (0.6 + 0.4 * noise(10.0 * position));

            float clouds = 0.8 * smoothstep(0.35, 0.45, weather) * smoothstep(-0.25, 1.0, fbm(wind));
            earth = mix(ground, vec3(0.5, 0.5, 0.5), clouds); 

            float lightning = 0.0;
            vec2 strike;
            // 模拟闪电
            if (getPosition(camera, getDir(hash21(iTime)), strike)) {
                vec2 diff = position - strike;
                lightning += clamp(1.0 - 1500.0 * dot(diff, diff), 0.0, 1.0);
            }
            lightning *= smoothstep(0.65, 0.75, weather);
            earth += lightning * vec3(1.0, 1.0, 1.0);
        }
        
        vec3 altitude = camera - dir * dot(camera, dir);
        float horizon = sqrt(dot(altitude, altitude));
        
        vec3 atmosphere = vec3(0.2, 0.25, 0.3);
        atmosphere = mix(atmosphere, vec3(0.05, 0.1, 0.3), smoothstep(0.992, 1.004, horizon));
        atmosphere = mix(atmosphere, vec3(0.1, 0.0, 0.0), smoothstep(1.0, 1.004, horizon));
        atmosphere = mix(atmosphere, vec3(0.2, 0.17, 0.1), smoothstep(1.008, 1.015, horizon));
        atmosphere = mix(atmosphere, vec3(0.0, 0.0, 0.0), smoothstep(1.015, 1.02, horizon));

        horizon = clamp(pow(horizon, 20.0), 0.0, 1.0);
        fragColor = vec4(mix(earth, atmosphere, horizon), 1.0);
    }
    void main() {
        mainImage(gl_FragColor, vUV * iResolution.xy);
    }
`;

7. 性能优化与常见问题

7.1 纹理优化技巧

纹理压缩:使用 .ktx.basis 格式减少加载时间。
Mipmap 配置

const texture = new BABYLON.Texture("texture.jpg", scene, {
              
    generateMipMaps: true, // 启用 Mipmap
    samplingMode: BABYLON.Texture.TRILINEAR_SAMPLINGMODE // 三线性过滤
});

7.2 常见问题解答

问题 1:纹理拉伸或错位

解决方案:调整模型的 UV 映射,或在代码中设置 uScale/vScale

问题 2:透明材质显示异常

解决方案:启用透明度混合并设置渲染顺序:

material.alpha = 0.5;
material.backFaceCulling = false; // 禁用背面剔除
material.needDepthPrePass = true; // 深度预渲染

问题 3:PBR 材质过暗

解决方案:添加环境光或 HDR 天空盒:

scene.environmentTexture = BABYLON.CubeTexture.CreateFromPrefilteredData(
   "textures/environment.dds", scene
);

8. 总结与下一章预告

8.1 关键知识点回顾

标准材质与 PBR 材质配置、多纹理混合、动态材质控制。
扩展方向:

程序化纹理生成:通过代码动态生成纹理图案。
后处理链:结合泛光(Bloom)、景深(Depth of Field)提升画面效果。

8.2 下一章预告

《用户交互:鼠标点击、拖拽与射线检测》:实现物体点击事件、拖拽操作与射线碰撞检测。

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容