ThreeJS-3D教学十三:ShaderMaterial:飘扬的国旗

发布于:2024-07-04 ⋅ 阅读:(19) ⋅ 点赞:(0)

效果图:
在这里插入图片描述
代码直接上:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <style>
    * {
      margin: 0;
      padding: 0;
    }
  </style>
</head>
<body>

<script type="module">
  import * as THREE from '../js_three-0.108.0/build/three.module.js';
  import { OrbitControls } from '../js_three-0.108.0/examples/jsm/controls/OrbitControls.js';
  import Stats from '../js_three-0.108.0/examples/jsm/libs/stats.module.js';
  import { GUI } from '../js_three-0.108.0/examples/jsm/libs/dat.gui.module.js';
  let camera, scene, stats, renderer;
  const clock = new THREE.Clock();
  let box = document.querySelector('body');
  let width = window.innerWidth;
  let height = window.innerHeight;
  let gui = new GUI();

  init();
  light();
  addLight();

  let vertexShader = `
        uniform vec2 uFrequency;
        uniform float uTime;

        varying vec2 vUv;
        varying float vElevation;
        void main()
        {
            vec4 modelPosition = modelMatrix * vec4(position, 1.0);

            float elevation = sin(modelPosition.x * uFrequency.x - uTime) * 0.1;
            elevation += sin(modelPosition.y * uFrequency.y - uTime) * 0.1;
            
            // 平面向上移动
            modelPosition.y += 10.2;
            // 平面移动
            modelPosition.z += elevation;
            // 平面变波浪形
            modelPosition.z += sin(modelPosition.x * uFrequency.x + uTime) * 2.9;
            modelPosition.z += sin(modelPosition.y * uFrequency.y + uTime) * 2.9;

            vec4 viewPosition = viewMatrix * modelPosition;
            vec4 projectedPosition = projectionMatrix * viewPosition;

            gl_Position = projectedPosition;
            vUv = uv;
            vElevation = elevation;
        }
    `;

  // 下面要将我们的旗帜纹理的颜色应用于所有可见片元上,为此我们必须使用texture2D()方法,
  // 该方法第一个参数就是应用的纹理,第二个参数是由在纹理上拾取的颜色的坐标组成,
  // 我们还没有这些坐标,而这听起来很熟悉,我们正在寻找可以帮助我们在几何体上投射纹理的坐标,也就是UV坐标。
  let fragmentShader = `
        precision mediump float;
        uniform sampler2D uTexture;
        varying vec2 vUv;
        varying float vElevation;
        void main()
        {
            vec4 textureColor = texture2D(uTexture, vUv);
            textureColor.rgb *= vElevation * 2.0 + 0.8;
            gl_FragColor = textureColor;
        }
    `;
  // 我们在其他材质中介绍的大多数常见属性(如wireframe、side、transparent、flatShading)
  // 仍然适用于RawShaderMaterial
  // 但是像map、alphaMap、opacity、 color等属性将不再生效,因为我们需要自己在着色器中编写这些特性

  const textureLoader = new THREE.TextureLoader();
  const flagTexture = textureLoader.load('../materials/guo.png');

  const material = new THREE.ShaderMaterial({
    vertexShader: vertexShader,
    fragmentShader: fragmentShader,
    transparent: false,
    wireframe: false,
    side: THREE.DoubleSide,
    // 然后你会发现结果是一样的,但现在我们可以在js中控制波形频率了。
    // 让我们把频率frequency改为vec2来控制水平和垂直方向的波,这里使用Three.js中一个简单的vec2:
    uniforms:{
      uFrequency: {
        value: new THREE.Vector2(10, 5)
      },
      // 现在让我们再新加一个uniform来让平面像在风中飘动的旗帜。
      // 我们将使用统一变量向着色器发送一个时间值,并在sin函数中使用:
      uTime: { value: 0 },
      uTexture: { value: flagTexture }
    }
  });
  const geometry = new THREE.PlaneBufferGeometry(200, 100, 5, 5);

  const flag = new THREE.Mesh(geometry, material);
  scene.add(flag);

  gui.add(material.uniforms.uFrequency.value, 'x')
    .min(0)
    .max(20)
    .step(0.01)
    .name('frequencyX');
  gui.add(material.uniforms.uFrequency.value, 'y')
    .min(0)
    .max(20)
    .step(0.01)
    .name('frequencyY');

  animate();

  function addLight() {
    const light = new THREE.SpotLight();
    light.position.set(0, 30, 30);
    light.intensity = 2;
    scene.add(light);
    light.castShadow = true
  }

  function init() {

    scene = new THREE.Scene();

    camera = new THREE.PerspectiveCamera(45, width / height, 10, 1000);
    camera.position.set(0, 150, 200);
    camera.lookAt(scene.position);

    stats = new Stats();
    stats.domElement.style.position = 'absolute';
    stats.domElement.style.top = '8px';
    stats.domElement.style.zIndex = 100;

    renderer = new THREE.WebGLRenderer({antialias: true});
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setClearColor(0x000000, 0.0);
    renderer.setSize(width, height);

    box.appendChild(renderer.domElement);
    box.appendChild(stats.dom);

    let controls = new OrbitControls(camera, renderer.domElement);

    window.addEventListener('resize', onWindowResize, false);
    onWindowResize();
  }

  function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
  }

  function light() {
    let light = new THREE.AmbientLight(0xFFFFFF);
    scene.add(light);
    let directionalLight = new THREE.DirectionalLight(0x101010);
    directionalLight.position.set(-1, 1, 1).normalize();
    scene.add(directionalLight);
  }

  function animate() {
    requestAnimationFrame(animate);
    stats.update();
    render();
  }

  function render() {
    const elapsedTime = clock.getElapsedTime();
    material.uniforms.uTime.value = elapsedTime * 5;
    renderer.render(scene, camera);
  }
</script>
</body>
</html>

这个案例算是一个比较好的,案例很形象的展示了 Shader 的用法:
1)这里我们用了 ShaderMaterial,所以默认变量不需要再声明。
2)我们可以在 vertexShader 中通过改变顶点位置直接偏移物体,即 modelPosition.y
3)我们在 GLSL语言 和 js 之间能通信吗,案例展示了这种用法,通过 ShaderMaterial 的 uniforms 我们可以将 js中的变量传给 GLSL ,方便控制动画,
4)uniforms 也可以传递图片,通过 gl_FragColor实现
5)展示了 varying 这个vertexShader 和 fragmentShader 之间通信的能力
6)大家可以通过 gui 这个方法,更好的为我们展示 Shader 的效果

附上国旗图片,方便大家使用
在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到