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

Posted 点燃火柴

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了绘制彩色立方体,使用attribute变量向着色器传值,BufferGeometry和ShaderMaterial配合使用(three.js实战6)相关的知识,希望对你有一定的参考价值。

1. 前言与demo效果

上一篇文章学习了如何使用uniform 变量向着色器传值,接下来该学习如何使用attribute变量向着色器传值,demo效果非常简单绘制了一个六面不同色的立方体
在这里插入图片描述

2. 如何使用attribute变量向着色器传值

uniform 变量可以通过ShaderMaterial里的 uniforms 属性向着色器传值,早期版本的three.js可以通过ShaderMaterial里的 attributes 属性向着色器传值,但最新的版本不支持了,接下来说说,新版本如何使用attribute变量

2.1 创建BufferGeometry实例

创建BufferGeometry实例,实际上是开辟一段缓冲区,用于后续向它写入各种需要绘制图形的信息,创建BufferGeometry实例方法非常简单,如下

const bufferGeom = new THREE.BufferGeometry()

2.2 使用setAttribute设置attribute变量与变量值

上一步创建了BufferGeometry实例,这一步向这个实例上添加自定义的attribute变量,demo中添加了两个变量,一个是存放顶点坐标信息的aPosition变量,一个是存放顶点颜色信息的aColor,具体如下

//设置顶点信息
const v0 = [1.0, 1.0, 1.0];
const v1 = [-1.0, 1.0, 1.0];
const v2 = [-1.0, -1.0, 1.0];
const v3 = [1.0, -1.0, 1.0];
const v4 = [1.0, -1.0, -1.0];
const v5 = [1.0, 1.0, -1.0];
const v6 = [-1.0, 1.0, -1.0];
const v7 = [-1.0, -1.0, -1.0];
const positions = new Float32Array([
  ...v0, ...v1, ...v2, ...v3, // 前 index 0-3
  ...v0, ...v3, ...v4, ...v5, // 右 index 4-7
  ...v0, ...v5, ...v6, ...v1, // 上 index 8-11
  ...v1, ...v6, ...v7, ...v2, // 左 index 12-15
  ...v7, ...v4, ...v3, ...v2, // 下 index 16-19
  ...v4, ...v7, ...v6, ...v5, // 后 index 20-23 
]);
bufferGeom.setAttribute('aPosition', new THREE.BufferAttribute(positions, 3));


//设置顶点颜色信息
const fontColor = [1.0, 0.0, 0.0, 0.9];
const backColor = [1.0, 0.4, 0.9, 0.2];
const leftColor = [0.0, 1.0, 0.0, 0.1];
const rightColor = [0.0, 0.3, 0.6, 0.8];
const topColor = [0.0, 0.8, 1.0, 0.4];
const downColor = [0.7, 0.0, 0.2, 0.6];
const colors = new Float32Array([
  ...fontColor, ...fontColor, ...fontColor, ...fontColor, // 前 index 0-3
  ...rightColor, ...rightColor, ...rightColor, ...rightColor, // 右 index 4-7
  ...topColor, ...topColor, ...topColor, ...topColor, // 上 index 8-11
  ...leftColor, ...leftColor, ...leftColor, ...leftColor, // 左 index 12-15
  ...downColor, ...downColor, ...downColor, ...downColor, // 下 index 16-19
  ...backColor, ...backColor, ...backColor, ...backColor, // 后 index 20-23 

]);

bufferGeom.setAttribute('aColor', new THREE.BufferAttribute(colors, 4));

2.3 创建ShaderMaterial材质

上一步中完成了向缓冲区中写入顶点信息和颜色信息,接下来该介绍如何使用这些信息了,three.js为我们提供了一个强大的载体ShaderMaterial,它可以帮助你实现任何你想实现的效果。先来看看demo中的ShaderMaterial材质是如何创建的

//创建ShaderMaterial材质
let material = new THREE.ShaderMaterial({
  vertexShader: boxMaterialShader.vertexShader,
  fragmentShader: boxMaterialShader.fragmentShader,
  side: THREE.DoubleSide
});

代码非常简单就是new了一个ShaderMaterial实例,重点还是顶点着色器和片元着色器内容。接下来就简单介绍一下

  • 顶点着色器-vertexShader
    顶点着色器定义了两个attribute变量aPosition和aColor,没错这两个变量就是用来接收上一步中存入缓冲区的顶点信息和顶点颜色信息。除此之外还定义了一个varying变量vColor这个变量用来向片元着色器传输顶点颜色信息。具体实现如下:
attribute vec3 aPosition;
attribute vec4 aColor;
varying vec4 vColor;
void main(){
  vColor = aColor;
  gl_Position = projectionMatrix * modelViewMatrix * vec4(aPosition, 1.0);
}
  • 片元着色器-fragmentShader
    片元着色器就做一件事,接收从顶点着色器传来的顶点颜色信息,然后赋值给内置变量gl_FragColor,内容如下
varying vec4 vColor;
void main(){
  gl_FragColor = vColor;
}

2.4 使用BufferGeometry实例和ShaderMaterial材质创建网格对象

几何体对象和材质都准备好了,接下来就是非常熟悉的一个步骤,用二者创建网格对象。并添加到场景中

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

3. demo代码

<!DOCTYPE html>

<html>

<head>
  <title>Example 06 - attribute</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/libs/stats.min.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;
    let camera, scene, renderer;
    let boxMaterialShader = {
      vertexShader: `
      attribute vec3 aPosition;
      attribute vec4 aColor;
      varying vec4 vColor;
      void main(){
        vColor = aColor;
        gl_Position = projectionMatrix * modelViewMatrix * vec4(aPosition, 1.0);
      }
    `,
      fragmentShader: `
      varying vec4 vColor;
      void main(){
        gl_FragColor = vColor;
      }
    `
    };

    //创建ShaderMaterial材质
    let material = new THREE.ShaderMaterial({
      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(3, 4, 5);
      camera.lookAt(new THREE.Vector3(0, 0, 0));
    }

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

      const 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 v0 = [1.0, 1.0, 1.0];
      const v1 = [-1.0, 1.0, 1.0];
      const v2 = [-1.0, -1.0, 1.0];
      const v3 = [1.0, -1.0, 1.0];
      const v4 = [1.0, -1.0, -1.0];
      const v5 = [1.0, 1.0, -1.0];
      const v6 = [-1.0, 1.0, -1.0];
      const v7 = [-1.0, -1.0, -1.0];
      const positions = new Float32Array([
        ...v0, ...v1, ...v2, ...v3, // 前 index 0-3
        ...v0, ...v3, ...v4, ...v5, // 右 index 4-7
        ...v0, ...v5, ...v6, ...v1, // 上 index 8-11
        ...v1, ...v6, ...v7, ...v2, // 左 index 12-15
        ...v7, ...v4, ...v3, ...v2, // 下 index 16-19
        ...v4, ...v7, ...v6, ...v5, // 后 index 20-23 
      ]);
      bufferGeom.setAttribute('aPosition', new THREE.BufferAttribute(positions, 3));


      //设置顶点颜色信息
      const fontColor = [1.0, 0.0, 0.0, 0.9];
      const backColor = [1.0, 0.4, 0.9, 0.2];
      const leftColor = [0.0, 1.0, 0.0, 0.1];
      const rightColor = [0.0, 0.3, 0.6, 0.8];
      const topColor = [0.0, 0.8, 1.0, 0.4];
      const downColor = [0.7, 0.0, 0.2, 0.6];
      const colors = new Float32Array([
        ...fontColor, ...fontColor, ...fontColor, ...fontColor, // 前 index 0-3
        ...rightColor, ...rightColor, ...rightColor, ...rightColor, // 右 index 4-7
        ...topColor, ...topColor, ...topColor, ...topColor, // 上 index 8-11
        ...leftColor, ...leftColor, ...leftColor, ...leftColor, // 左 index 12-15
        ...downColor, ...downColor, ...downColor, ...downColor, // 下 index 16-19
        ...backColor, ...backColor, ...backColor, ...backColor, // 后 index 20-23 

      ]);

      bufferGeom.setAttribute('aColor', new THREE.BufferAttribute(colors, 4));

      //设置索引信息
      const indexs = new Uint16Array([
        0, 1, 2, 0, 2, 3, // 前
        4, 5, 6, 4, 6, 7, // 右
        8, 9, 10, 8, 10, 11, // 上
        12, 13, 14, 12, 14, 15, // 左
        16, 17, 18, 16, 18, 19, // 下
        20, 21, 22, 20, 22, 23 // 后

      ]);
      bufferGeom.index = new THREE.BufferAttribute(indexs, 1);

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

    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); // 相机更新

    }

    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>

以上是关于绘制彩色立方体,使用attribute变量向着色器传值,BufferGeometry和ShaderMaterial配合使用(three.js实战6)的主要内容,如果未能解决你的问题,请参考以下文章

使用VBOIBO创建彩色甜圈圈(WebGL进阶01)

为啥在向着色器存储缓冲区发送结构时会收到垃圾数据?

WebGL 3D 入门系列 --- 绘制渐变三角形:深入理解缓冲区

Qtopengl,为啥不能使用不同的vbo绘制两个立方体

OpenGL彩色方块

PyOpenGL 如何选择颜色来绘制每个形状?