Напишите трехмерный глобус с помощью three.js

WebGL three.js
Напишите трехмерный глобус с помощью three.js

предисловие

это обучениеthree.js系列Четвертая, первые три:

Напишите анимацию дождя с помощью three.js

Напишите небольшую сцену с помощью three.js

Напишите отражающий мяч с помощью three.js

оthree.jsБазовые знания размещены в первой статье:Напишите анимацию дождя с помощью three.js, можете идти проверять, следующие случаи не повторятся.

从今天开始学习着色器,下面是一个简单使用着色器动画完成的3D地球。 Давайте начнем.

earth-gif-l.gif

Начало работы с шейдерами

Графика рисования Webgl — это механизм рисования на основе шейдеров. Шейдеры предоставляют гибкий и мощный метод рисования двухмерной или трехмерной графики. Все программы Webgl должны использовать его.

Язык шейдеров похож на язык C, когда мы пишем программы webgl, язык шейдеров встраивается в язык javascript в виде строк.

Например, чтобы нарисовать точку на экране, код выглядит следующим образом:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    body {
      margin: 0
    }
  </style>
</head>
<body>
  <canvas id="webgl"></canvas>
</body>
<script>
    //将canvas的大小设置为屏幕大小
    var canvas = document.getElementById('webgl')
    canvas.height = window.innerHeight
    canvas.width = window.innerWidth
    
    //获取webgl绘图上下文
    var gl = canvas.getContext('webgl')
    
    //将背景色设置为黑色
    gl.clearColor(0.0, 0.0, 0.0, 1.0)
    gl.clear(gl.COLOR_BUFFER_BIT)

    //顶点着色器代码(字符串形式)
    var VSHADER_SOURCE = 
    `void main () {
      gl_Position = vec4(0.5, 0.5, 0.0, 1.0);  //点的位置:x: 0.5, y: 0.5, z: 0。齐次坐标
      gl_PointSize = 10.0;                     //点的尺寸,非必须,默认是0
    }`

    //片元着色器代码(字符串形式)
    var FSHADER_SOURCE = 
    `void main () {
      gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);    //点的颜色:四个量分别代表 rgba
    }`
    
    //初始化着色器
    initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)
    
    //绘制一个点,第一个参数为gl.POINTS
    gl.drawArrays(gl.POINTS, 0, 1)
    
    function initShaders(gl, vshader, fshader) {
      var program = createProgram(gl, vshader, fshader);
      if (!program) {
        console.log('Failed to create program');
        return false;
      }
      gl.useProgram(program);
      gl.program = program;
      return true;
    }

    function createProgram(gl, vshader, fshader) {
      var vertexShader = loadShader(gl, gl.VERTEX_SHADER, vshader);
      var fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fshader);
      if (!vertexShader || !fragmentShader) {
          return null;
      }
      var program = gl.createProgram();
      if (!program) {
          return null;
      }
      gl.attachShader(program, vertexShader);
      gl.attachShader(program, fragmentShader);
      gl.linkProgram(program);
      var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
      if (!linked) {
          var error = gl.getProgramInfoLog(program);
          console.log('Failed to link program: ' + error);
          gl.deleteProgram(program);
          gl.deleteShader(fragmentShader);
          gl.deleteShader(vertexShader);
          return null;
      }
      return program;
    }

    function loadShader(gl, type, source) {
      // 创建着色器对象
      var shader = gl.createShader(type);
      if (shader == null) {
          console.log('unable to create shader');
          return null;
      }
      gl.shaderSource(shader, source);
      gl.compileShader(shader);
      var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
      if (!compiled) {
          var error = gl.getShaderInfoLog(shader);
          gl.deleteShader(shader);
          return null;
      }
      return shader;
    }
</script>
</html>

Приведенный выше код рисует точку в правой верхней части экрана.

image.png

Для рисования этой точки требуется три необходимых элемента информации: положение, размер и цвет.

  • Вершинный шейдер определяет положение и размер точки. (В приведенном ниже кодеgl_Position,gl_PointSizeа такжеgl_FragColorОбе являются встроенными глобальными переменными шейдера. )
var VSHADER_SOURCE = 
`void main () {
    gl_Position = vec4(0.5, 0.5, 0.0, 1.0);   //指定点的位置
    gl_PointSize = 10.0;                      //指定点的尺寸
}`
  • Фрагментный шейдер определяет цвет точки.
var FSHADER_SOURCE = 
`void main () {
   gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);   //指定点的颜色
}`

атрибутивные переменные и юниформ-переменные

В примере выше мы указали положение, размер и цвет точки непосредственно в шейдере. В реальной работе эта информация в основном передается шейдеру с помощью js.

Переменные, используемые для связи между кодом js и кодом шейдера,attribute变量а такжеuniform变量.

Какую переменную использовать, зависит от самих данных, которые необходимо передать,attribute变量используется для передачи данных, связанных с вершиной,uniform变量Используется для передачи независимых от вершин данных.

В следующем примере координаты точки, которую нужно нарисовать, будут переданы через js.

  //顶点着色器
var VSHADER_SOURCE = 
`attribute vec4 a_Position;    //声明一个attribute变量a_Position,用于接受js传递的顶点位置
void main () {
  gl_Position = a_Position;    //将a_Position赋值给gl_Position
  gl_PointSize = 10.0;
}`

//片元着色器
var FSHADER_SOURCE = 
`void main () {
  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}`

initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)

//js代码中,获取a_Position的存储位置,并向其传递数据
var a_Position = gl.getAttribLocation(gl.program, 'a_Position')
gl.vertexAttrib3f(a_Position, 0.5, 0.5, 0.0)

gl.drawArrays(gl.POINTS, 0, 1)

переменная переменная

Мы проходим от JS Shader, как правило, связанные данные вершины вершины, например, мы хотим нарисовать треугольник, а положение вершины и цвет вершины треугольника передаются через js. Положение трех вершин может определить положение треугольника, так что же определяет цвет всего треугольника?

Это требует появления различных переменных.

Расчет цвета в webgl:

существует顶点着色器, получить данные о положении и цвете каждой вершины, переданной js. Система webgl будет, основываясь на данных вершин,插值计算Снаружи, в области между вершинами, значение цвета каждого фрагмента (под которым можно понимать наименьшую точку рендеринга, из которой состоит изображение). Расчет интерполяции выполняется автоматически системой webgl.

Вычислите значение цвета каждого фрагмента, а затем передайте его в片元着色器.片元着色器Визуализирует изображение на основе значения цвета каждого фрагмента.

от顶点着色器прибыть片元着色器, пропуская работу мимоvarying变量Заканчивать.

image.png

код показывает, как показано ниже.

  • Код вершины шейдера
var VSHADER_SOURCE = 
`attribute vec4 a_Position;    //顶点位置
attribute vec4 a_Color;        //顶点颜色
varying vec4 v_Color;          //根据顶点颜色,计算出三角形中每个片元的颜色值,然后将每个片元的颜色值传递给片元着色器。
void main () {
  gl_Position = a_Position;    
  v_Color = a_Color;         // a_Color 赋值给 v_Color
}`
  • Код фрагментного шейдера
var FSHADER_SOURCE = 
`precision mediump float;
varying vec4 v_Color;    //每个片元的颜色值
void main () {
  gl_FragColor = v_Color;
}`
  • js-код
var verticesColors = new Float32Array([     //顶点位置和颜色
  0.0, 0.5, 1.0, 0.0, 0.0,      // 第一个点,前两个是坐标(x,y;  z默认是0),后三个是颜色
  -0.5, -0.5, 0.0, 1.0, 0.0,   // 第二个点
  0.5, -0.5, 0.0, 0.0, 1.0     // 第三个点
])

//以下是通过缓冲区向顶点着色器传递顶点位置和颜色
var vertexColorBuffer = gl.createBuffer()  

gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorBuffer)
gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW)

var FSIZE = verticesColors.BYTES_PER_ELEMENT
var a_Position = gl.getAttribLocation(gl.program, 'a_Position')
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 5, 0)
gl.enableVertexAttribArray(a_Position)

var a_Color = gl.getAttribLocation(gl.program, 'a_Color')
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 5, FSIZE * 2)
gl.enableVertexAttribArray(a_Color)

//绘制一个三角形,第一个参数为gl.TRIANGLES
gl.drawArrays(gl.TRIANGLES, 0, 3)

Окончательный эффект отрисовки выглядит следующим образом:

image.png

Простое понимание наложения текстур

В приведенном выше примере мы присваиваем значения цвета каждой вершине.

Чтобы расширить, отображение текстуры заключается в указании координат текстуры для каждой вершины, а затем система webgl будет интерполировать и вычислять координаты текстуры каждого фрагмента на основе координат текстуры вершины.

Затем в фрагментном шейдере, согласно изображению входящей текстуры и координаты текстуры каждого фрагмента, значение цвета (Texel) на соответствующей координате текстуры в текстуру изображения вынимается как цветное значение фрагмента, а также для рендера.

Особенности текстурных координат:

  • Нижний левый угол изображения текстуры — это начало координат (0, 0).
  • Вправо — это положительное направление горизонтальной оси, а максимальное значение горизонтальной оси равно 1 (правый край изображения).
  • Вверх — это положительное направление вертикальной оси, а максимальное значение вертикальной оси равно 1 (верхний край изображения).

image.pngНезависимо от размера изображения текстуры диапазон координат текстуры: ось x: 0-1, ось y: 0-1

Нарисуйте трехмерную Землю

Используя webgl для рисования, шаги и API относительно громоздки, но, к счастью, мы можем использоватьthree.js.

three.jsсерединаShaderMaterialМы можем создавать свои собственные шейдеры и напрямую манипулировать пикселями. Нам просто нужно понять основы шейдеров.

Начните рисовать землю.

базовая сфера

Нарисовать базовую сферу относительно просто, используяthree.jsМатериал, предусмотренный на линии. На основе материала, вНапишите отражающий мяч с помощью three.jsЕсть более подробное введение.

var loader = new THREE.TextureLoader() 
var group = new THREE.Group() 

//创建本体
var geometry = new THREE.SphereGeometry(20,30,30)   //创建球形几何体
var earthMaterial = new THREE.MeshPhongMaterial({    //创建材质
    map: loader.load( './images/earth.png' ),        //基础纹理
    specularMap: loader.load('./images/specular.png'),  //高光纹理,指定物体表面中哪部分比较闪亮,哪部分相对暗淡
    normalMap:  loader.load('./images/normal.png'),   //法向纹理,创建更加细致的凹凸和褶皱
    normalScale: new THREE.Vector2(3, 3)     
})
var sphere = new THREE.Mesh(geometry, earthMaterial)   //创建基础球体
group.add(sphere)

image.png

мобильная атмосфера

использоватьShaderMaterialПользовательские шейдеры. Поток атмосферы через каждыйrequestAnimationFrameИзмените реализацию координат текстуры в цикле рендеринга. Чтобы сделать поток более естественным, добавьте шум.

//顶点着色器
var VSHADER_SOURCE = `
  varying vec2 v_Uv;   
  void main () {
    v_Uv = uv;       //顶点纹理坐标
    gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4( position, 1.0 );
  }
`

//片元着色器
var FSHADER_SOURCE = `
  uniform float time;      //时间变量
  uniform sampler2D fTexture;    //大气纹理图像
  uniform sampler2D nTexture;    //噪声纹理图像
  varying vec2 v_Uv;              //片元纹理坐标
  void main () {
    vec2 new_Uv= v_Uv + vec2( 0, 0.02 ) * time;   //向量加法,根据时间变量计算新的纹理坐标
    
    //利用噪声随机使纹理坐标随机化
    vec4 noise_Color = texture2D( nTexture, new_Uv );    
    new_Uv.x += noise_Color.r * 0.2;
    new_Uv.y += noise_Color.g * 0.2;
    
    gl_FragColor = texture2D( fTexture, new_Uv );  //提取大气纹理图像的颜色值(纹素)
  }
`

var flowTexture = loader.load('./images/flow.png')
flowTexture.wrapS = THREE.RepeatWrapping
flowTexture.wrapT = THREE.RepeatWrapping

var noiseTexture = loader.load('./images/noise.png')
noiseTexture.wrapS = THREE.RepeatWrapping
noiseTexture.wrapT = THREE.RepeatWrapping

//着色器材质
var flowMaterial = new THREE.ShaderMaterial({
    uniforms: {
      fTexture: {
        value: flowTexture,  
      },
      nTexture: {
        value: noiseTexture,
      },
      time: {
        value: 0.0
      },
    },
    // 顶点着色器
    vertexShader: VSHADER_SOURCE,
    // 片元着色器
    fragmentShader: FSHADER_SOURCE,
    transparent: true
})
var fgeometry = new THREE.SphereGeometry(20.001,30,30)   //创建比基础球体略大的球状几何体
var fsphere = new THREE.Mesh(fgeometry, flowMaterial)    //创建大气球体
group.add(fsphere)
scene.add( group )

созданныйgroup, базовая сфера и большой воздушный шар добавляются кgroup, в целом задайте вращение и положение, оба напрямую изменяютgroupхарактеристики.

var clock = new THREE.Clock()
  //渲染循环
var animate = function () {
    requestAnimationFrame(animate)
    var delta = clock.getDelta()
    group.rotation.y -= 0.002       //整体转动
    flowMaterial.uniforms.time.value += delta  //改变uniforms.time的值,用于片元着色器中的纹理坐标计算
    renderer.render(scene, camera)
}

animate()

image.png

гало

Спрайт используется для создания ореола.Спрайт представляет собой плоскость, которая всегда обращена к камере.Здесь он используется для имитации ореола.Независимо от того, как вращается сфера, она всегда оказывается в ореоле.

var ringMaterial = new THREE.SpriteMaterial( {  //创建点精灵材质
  map: loader.load('./images/ring.png') 
} )
var sprite = new THREE.Sprite( ringMaterial )    //创建精灵,和普通物体的创建不一样
sprite.scale.set(53,53, 1)             //设置精灵的尺寸
scene.add( sprite )

Финальные рендеры:

earth-gif-l.gif