绘制不断变色的台体,BufferGeometry和ShaderMaterial的黄金组合(three.js实战5)

Posted 点燃火柴

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了绘制不断变色的台体,BufferGeometry和ShaderMaterial的黄金组合(three.js实战5)相关的知识,希望对你有一定的参考价值。

1. 前言与demo效果

上一篇文章学习了如何使用BufferGeometry 向缓存中传输几何体的顶点坐标、面索引、顶点颜色、顶点法向量、顶点UV等, 也提到了使用ShaderMaterial,但最后没有具体说明,这一次专门说一说BufferGeometry和ShaderMaterial如何配合使用,先来看看做的demo效果
在这里插入图片描述

2. 如何使用ShaderMaterial

这里只介绍一下使用ShaderMaterial材质最主要的三个属性,vertexShader,fragmentShader和uniforms

2.1 vertexShader属性

vertexShader 表示顶点着色器,通过该属性可以定义GLSL语言编写的程序,可以直接运行在GPU上,通过顶点着色器,可以处理几何体体上的每一个顶点。如果对WebGL有一定的了解,这部分会非常容易理解

2.2 fragmentShader属性

fragmentShader 表示片元着色器,与顶点着色器一样,也可以自定义GLSL程序,可以用来处理几何体上的每一个像素。

2.3 uniforms属性

这个属性是javascript程序与着色器程序之间的通道,通过uniforms属性向顶点着色器和片元着色器传递uniform变量,顶点着色器和片元着色器接收到对应的变量后,可以根据需要做相应的处理

创建ShaderMaterial材质 的官方示例如下

var material = new THREE.ShaderMaterial( {
	uniforms: {
		time: { value: 1.0 },
		resolution: { value: new THREE.Vector2() }
	},

	vertexShader: document.getElementById( 'vertexShader' ).textContent,
	fragmentShader: document.getElementById( 'fragmentShader' ).textContent
} );

3. demo实现要点

3.1 创建ShaderMaterial材质

创建ShaderMaterial主要有三大块,设置uniforms属性,顶点着色器和片元着色器,示例中unforms定义了三个变量r、g、b分别用了传递颜色分量,顶点着色器是通用的着色器,使用投影矩阵,模型视图矩阵和顶点坐标相乘,赋值给内置变量gl_Position,片元着色器中使用接收到的r、g、b颜色分量创建vec4类型的颜色赋值给内置变量 gl_FragColor,具体实现如下:

let boxMaterialShader = {
  uniforms: {
    "r": {
      type: "f",
      value: 1.0
    },
    "g": {
      type: "f",
      value: 0.6
    },
    "b": {
      type: "f",
      value: 0.8
    }
  },
  vertexShader: `
  void main(){
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
  }
`,
  fragmentShader: `
  uniform float r;
  uniform float g;
  uniform float b;
  void main(){
    gl_FragColor = vec4(vec3(r,g,b),0.9);
  }
`
};

//创建ShaderMaterial材质
const material = new THREE.ShaderMaterial({
  uniforms: boxMaterialShader.uniforms,
  vertexShader: boxMaterialShader.vertexShader,
  fragmentShader: boxMaterialShader.fragmentShader,
  side: THREE.DoubleSide
})
material.needsUpdate = true

3.2 绘制台体和边框线

首先创建BufferGeometry几何体,然后设置position属性和index属性,使用创建好的ShaderMaterial材质绘制台体,然后克隆一份几何体对象,设置边框线的索引然后使用LineSegments绘制边框线,具体如下:

function initModel() {

  //创建BufferGeometry几何体
  const bufferGeom = new THREE.BufferGeometry()

  //设置顶点信息
  const positions = new Float32Array([
    -5.0, 2.0, -5.0,
    5.0, 2.0, -5.0,
    5.0, 2.0, 5.0,
    -5.0, 2.0, 5.0,
    -6.0, -2.0, -6.0,
    6.0, -2.0, -6.0,
    6.0, -2.0, 6.0,
    -6.0, -2.0, 6.0,
  ])
  bufferGeom.setAttribute('position', new THREE.BufferAttribute(positions, 3))

  //设置索引信息
  const indexs = new Uint16Array([
    0, 1, 2, //上
    0, 2, 3, //上
    4, 5, 6, //下
    4, 6, 7, //下
    0, 3, 4, //左
    3, 4, 7, //左
    1, 2, 5, //右
    2, 5, 6, //右
    2, 3, 6, //前
    3, 6, 7, //前
    0, 1, 4, //后
    1, 4, 5, //后
  ])
  bufferGeom.index = new THREE.BufferAttribute(indexs, 1)

  //创建Mesh对象并添加到场景
  const mesh = new THREE.Mesh(bufferGeom, material)
  scene.add(mesh)

  // 绘制边框
  const lineGeom = bufferGeom.clone() //克隆一份BufferGeometry对象

  //设置绘制线的索引
  const lineIndexs = new Uint16Array([
    0, 1, 1, 2, 2, 3, 3, 0,
    4, 5, 5, 6, 6, 7, 7, 4,
    0, 4, 1, 5, 2, 6, 3, 7
  ])
  lineGeom.index = new THREE.BufferAttribute(lineIndexs, 1)

  //创建绘制线的材质
  const lineMaterial = new THREE.LineBasicMaterial({
    color: 0x018BF5,
    side: THREE.DoubleSide
  })

  //使用LineSegments绘制线段,渲染时mode为gl.LINES
  const line = new THREE.LineSegments(lineGeom, lineMaterial)
  scene.add(line)

}

3.3 render中更新uniforms变量

在render函数中不断更新uniforms变量,顶点着色器和片元着色器接收的新的变量后重新绘制,就可以得到不断变色的台体

//更新传给着色器材质的uniform变量
boxMaterialShader.uniforms.r.value += 0.01
boxMaterialShader.uniforms.g.value += 0.01
if (boxMaterialShader.uniforms.r.value > 1) {
  boxMaterialShader.uniforms.r.value = 0.1
}
if (boxMaterialShader.uniforms.g.value > 1) {
  boxMaterialShader.uniforms.g.value = 0.1
}

4. demo代码

<!DOCTYPE html>

<html>

<head>
  <title>Example 01 - flowing</title>
  <script type="text/javascript" src="../three/build/three.js"></script>
  <script type="text/javascript" src="../three/examples/js/controls/OrbitControls.js"></script>
  <script type="text/javascript" src="../three/examples/js/loaders/OBJLoader.js"></script>
  <script type="text/javascript" src="../three/examples/js/libs/stats.min.js"></script>
  <!-- EffectComposer要先于其他后期处理文件引入,否则其他文件无法正确引入 -->
  <script type="text/javascript" src="../three/examples/js/postprocessing/EffectComposer.js"></script>
  <script type="text/javascript" src="../three/examples/js/postprocessing/RenderPass.js"></script>
  <script type="text/javascript" src="../three/examples/js/postprocessing/ShaderPass.js"></script>
  <script type="text/javascript" src="../three/examples/js/shaders/CopyShader.js"></script>


  <style>
    body {
      margin: 0;
      overflow: hidden;
    }
  </style>
</head>

<body>

  <div id="Stats-output"></div>
  <div id="WebGL-output"></div>

  <script type="text/javascript">
    let stats, controls, composer, customPass;
    let camera, scene, renderer;
    let boxMaterialShader = {
      uniforms: {
        "r": {
          type: "f",
          value: 1.0
        },
        "g": {
          type: "f",
          value: 0.6
        },
        "b": {
          type: "f",
          value: 0.8
        }
      },
      vertexShader: `
      void main(){
        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
      }
    `,
      fragmentShader: `
      uniform float r;
      uniform float g;
      uniform float b;
      void main(){
        gl_FragColor = vec4(vec3(r,g,b),0.9);
      }
    `
    };

    //创建ShaderMaterial材质
    const material = new THREE.ShaderMaterial({
      uniforms: boxMaterialShader.uniforms,
      vertexShader: boxMaterialShader.vertexShader,
      fragmentShader: boxMaterialShader.fragmentShader,
      side: THREE.DoubleSide
    })
    material.needsUpdate = true

    function initScene() {
      scene = new THREE.Scene();
    }

    function initCamera() {
      camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
      camera.position.set(30, 80, 80)
      camera.lookAt(new THREE.Vector3(0, 0, 0));
    }

    function initLight() {
      //添加环境光
      var ambientLight = new THREE.AmbientLight(0xffffff);
      scene.add(ambientLight);

      var spotLight = new THREE.SpotLight(0xffffff);
      spotLight.position.set(5, 10, 20);
      spotLight.castShadow = true;
      scene.add(spotLight);
    }


    function initModel() {

      //创建BufferGeometry几何体
      const bufferGeom = new THREE.BufferGeometry()

      //设置顶点信息
      const positions = new Float32Array([
        -5.0, 2.0, -5.0,
        5.0, 2.0, -5.0,
        5.0, 2.0, 5.0,
        -5.0, 2.0, 5.0,
        -6.0, -2.0, -6.0,
        6.0, -2.0, -6.0,
        6.0, -2.0, 6.0,
        -6.0, -2.0, 6.0,
      ])
      bufferGeom.setAttribute('position', new THREE.BufferAttribute(positions, 3))

      //设置索引信息
      const indexs = new Uint16Array([
        0, 1, 2, //上
        0, 2, 3, //上
        4, 5, 6, //下
        4, 6, 7, //下
        0, 3, 4, //左
        3, 4, 7, //左
        1, 2, 5, //右
        2, 5, 6, //右
        2, 3, 6, //前
        3, 6, 7, //前
        0, 1, 4, //后
        1, 4, 5, //后
      ])
      bufferGeom.index = new THREE.BufferAttribute(indexs, 1)

      //创建Mesh对象并添加到场景
      const mesh = new THREE.Mesh(bufferGeom, material)
      scene.add(mesh)



      // 绘制边框
      const lineGeom = bufferGeom.clone() //克隆一份BufferGeometry对象

      //设置绘制线的索引
      const lineIndexs = new Uint16Array([
        0, 1, 1, 2, 2, 3, 3, 0,
        4, 5, 5, 6, 6, 7, 7, 4,
        0, 4, 1, 5, 2, 6, 3, 7
      ])
      lineGeom.index = new THREE.BufferAttribute(lineIndexs, 1)

      //创建绘制线的材质
      const lineMaterial = new THREE.LineBasicMaterial({
        color: 0x018BF5,
        side: THREE.DoubleSide
      })

      //使用LineSegments绘制线段,渲染时mode为gl.LINES
      const line = new THREE.LineSegments(lineGeom, lineMaterial)
      scene.add(line)

    }

    function initRender() {

      renderer = new THREE.WebGLRenderer({
        antialias: true,
        alpha: true
      })
      //renderer.shadowMap.enabled = true // 显示阴影
      renderer.setPixelRatio(window.devicePixelRatio);
      renderer.setSize(window.innerWidth, window.innerHeight);
      renderer.setClearColor(0x0f2d48, 1) // 设置背景颜色
      renderer.toneMapping = THREE.ACESFilmicToneMapping;
      document.getElementById("WebGL-output").appendChild(renderer.domElement);
    }

    //初始化轨道控制器
    function initControls() {
      clock = new THREE.Clock() // 创建THREE.Clock对象,用于计算上次调用经过的时间
      controls = new THREE.OrbitControls(camera, renderer.domElement)
      //controls.autoRotate = true // 是否自动旋转
    }



    function init() {
      initScene();
      initCamera();
      initLight();
      initRender();
      initStats();
      initControls();
      initModel();
      render();
    }



    function updateFun() {
      stats.update();
      const delta = clock.getDelta() // 获取自上次调用的时间差
      controls.update(delta) // 相机更新

      //更新传给着色器材质的uniform变量
      boxMaterialShader.uniforms.r.value += 0.01
      boxMaterialShader.uniforms.g.value += 0.01
      if (boxMaterialShader.uniforms.r.value > 1) {
        boxMaterialShader.uniforms.r.value = 0.1
      }
      if (boxMaterialShader.uniforms.g.value > 1) {
        boxMaterialShader.uniforms.g.value = 0.1
      }

    }

    function render() {
      updateFun()
      requestAnimationFrame(render);
      renderer.render(scene, camera);
    }

    function initStats() {
      stats = new Stats();
      stats.setMode(0); // 0: fps, 1: ms

      document.getElementById("Stats-output").appendChild(stats.domElement);
    }

    window.onload = init;
  </script>
</body>

</html>

以上是关于绘制不断变色的台体,BufferGeometry和ShaderMaterial的黄金组合(three.js实战5)的主要内容,如果未能解决你的问题,请参考以下文章

绘制彩色立方体,使用attribute变量向着色器传值,BufferGeometry和ShaderMaterial配合使用(three.js实战6)

如何快速更新大的BufferGeometry?

实例化 vs bufferGeometry vs interleavedBuffer

几何画板演示柱锥台体的变换

canvas学习绘制渐变色

如何使用 CAGradientLayer 绘制渐变色轮?