Three.js Journey基础与进阶

328 阅读11分钟

Showcase :-D

GIF 2023-4-24 17-12-23.gif

portofio.gif

GIF 2023-4-17 21-57-35.jpg

更多作品及思维导图: github.com/boombb12138…

相机

PerspectiveCamera

透视相机

const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 1, 100)
const camera = new THREE.PerspectiveCamera(垂直方向视野角度,纵横比,near,far)
比near更近或比far更远的对象将不会显示

OrthographicCamera

正交相机

OrbitControls

创建相机后再新建OrbitControls


const controls = new OrbitControls(camera,canvas)

窗口操作

使画布适合窗口


const sizes = {
width: window.innerWidth,
height: window.innerHeight
}

响应式


window.addEventListener("resize", () => {
  sizes.width = window.innerWidth;
  sizes.height = window.innerHeight;
  // 更新相机aspect属性
  camera.aspect = sizes.width / sizes.height;
  camera.updateProjectionMatrix(); //更新矩阵
  // 更新renderer
  renderer.setSize(sizes.width, sizes.height);
});

处理锯齿 ?


renderer.setPixelRatio(Math.min(window.devicePixelRatio,2))
//window.devicePixelRatio 可以使用的屏幕像素比

双击全屏


window.addEventListener("dblclick", () => {
  // 判断是否出于全屏
  const fullscreenElement =
    document.fullscreenElement || document.webkitFullscreenElement;
  if (!fullscreenElement) {
    // 请求使canvas元素全屏
    canvas.requestFullscreen() ?? canvas.webkitRequestFullscreen;
  } else {
    // 离开全屏
    document.exitFullscreen() ?? document.webkitExitFullscreen();
  }
  //webkitRequestFullscreen webkitFullscreenElement等带webkit前缀都是为了适配
});

几何体

BoxGeometry

  • widthSegments 在x轴上有多少个部分
  • heightSegments 在y轴上有多少个部分
  • depthSegments 在z轴上有多少个部分

自定义几何体


// 1.创建空的几何体
const identityGeometry = new THREE.BufferGeometry();
// 2.创建顶点 Float32Array是数组类型 只能存储浮点数,且长度是固定的
const positionsArray = new Float32Array([0, 0, 0, 0, 1, 0, 1, 0, 0]);
// 3.将数组转为BufferAttribute
const positionsAttribute = new THREE.BufferAttribute(positionsArray, 3);
// 4.将属性添加到几何体
identityGeometry.setAttribute("position", positionsAttribute);
const identityMaterial = new THREE.MeshBasicMaterial({ color: "red" });
const identityMesh = new THREE.Mesh(identityGeometry, identityMaterial);
identityMesh.position.y = -1;
scene.add(identityMesh);

调试面板

借助lil-gui


npm install --save lil-gui

重启server


import * as dat from 'lil-gui'
const gui = new dat.GUI()

向面板添加元素


gui.add(对象,要调整的对象的属性,最小值,最大值,精度)

另一种写法:


gui.add(对象,要调整的对象的属性).min(最小值).max(最大值).step(精度)

更改标签:


gui.add(对象,要调整的对象的属性).min(最小值).max(最大值).step(精度).name('elevation')

gui.add(mesh, "visible"); //控制显示和隐藏

处理颜色


const parameters = {
  color : 0xff0000
}//创建一个对象 该对象包含color属性
gui
.addColor(parameters,'color')
.onChange(() => {
  material.color.set(parameters.color)
})

在颜色值改变时提醒 链式调用

触发函数eg:动画

给parameters添加spin属性


const parameters = {
  color : 0xff0000,
  spin: () => {
    gsap.to(mesh.rotation,{duration:1, y:mesh.rotation.y + Math.PI * 2})
  }
}
gui.add(parameters,'spin')

更改面板宽度


const gui = new dat.GUI({ width: 400 })

GUI API文档

github.com/dataarts/da…示例:jsfiddle.net/ikatyang/18…

纹理Texture

加载纹理

使用TextureLoader加载纹理


const textureLoader = new THREE.TextureLoader()
const doorColorTexture = textureLoader.load('/textures/door/color.jpg')

使用加载管理器和TextureLoader加载纹理


//  在加载图像时收到通知 用loadingManager
const loadingManager = new THREE.LoadingManager();
const textureLoader = new THREE.TextureLoader(loadingManager);
loadingManager.onStart = () => {
  console.log("loading started");
};
// 开始加载所需的所有纹理
const colorTexture = textureLoader.load("/door.jpg");

使用纹理


const material = new THREE.MeshBasicMaterial({ map: colorTexture })

处理纹理


colorTexture.wrapS = THREE.RepeatWrapping //重复纹理
colorTexture.wrapT = THREE.RepeatWrapping
colorTexture.rotation = Math.PI * 0.25; //旋转

哪里找Texture

材料Material

MeshBasicMaterial

最基本的材料

map 在几何体的表面上应用纹理

color 在几何体的表面上应用统一的颜色

wireframe 用细线展示几何体

opacity transparent 控制透明度


  const material = new THREE.MeshBasicMaterial();
  material.map = doorColorTexture;
  material.color = new THREE.Color("#ff0000"); //給紋理著色
  material.wireframe = true; //細綫顯示
  material.transparent = true;
  material.opacity = 0.5; //transparent 和 opacity控制透明度
  material.side = THREE.DoubleSide; //控制正反面都可見 會降低性能

MeshNormalMaterial

显示渐变彩色


const material = new THREE.MeshNormalMaterial();
// material.flatShading = true; //每个组成面都有棱有角

MeshMatcapMaterial

需要一个球体纹理


const material = new THREE.MeshMatcapMaterial()
material.matcap = matcapTexture

哪里可以找到 matcaps 纹理:

observablehq.com/@makio135/matcaps?ui=classic

对光做出反应的材料

MeshLambertMaterial

MeshPhongMaterial


const material = new THREE.MeshPhongMaterial()
material.shininess = 100//光反射强度  值越高 表面越亮
material.specular = new THREE.Color(0x1188ff)//更改反射的颜色

MeshToonMaterial

卡通风材料


const material = new THREE.MeshToonMaterial()

MeshStandardMaterial


const material = new THREE.MeshStandardMaterial();
material.metalness = 0.7;
material.roughness = 0.2;

aoMap 环境光遮蔽贴图,将在纹理较暗的地方添加阴影,增加对比度,真实性


 sphere.geometry.setAttribute(
    "uv2",
   new THREE.BufferAttribute(sphere.geometry.attributes.uv.array, 2)
 );
  plane.geometry.setAttribute(
    "uv2",
    new THREE.BufferAttribute(sphere.geometry.attributes.uv.array, 2)
  );
  torus.geometry.setAttribute(
    "uv2",
    new THREE.BufferAttribute(sphere.geometry.attributes.uv.array, 2)
  );
  material.aoMap = doorAmbientOcclusionTexture; //使用纹理
  material.aoMapIntensity = 1; //强度
  material.displacementMap = doorHeightTexture; // 移动顶点以创建真正的浮雕
  material.displacementScale = 0.05; //调顶点

环境贴图

环境贴图 需要用CubeTextureLoader加载纹理集合,

const cubeTextureLoader = ne THREE.CubeTextureLoader()
​
const environmentMapTexture = cubeTextureLoader.load([
'/textures/environmentMaps/0/px.jpg',
'/textures/environmentMaps/0/nx.jpg',
'/textures/environmentMaps/0/py.jpg',
'/textures/environmentMaps/0/ny.jpg',
'/textures/environmentMaps/0/pz.jpg',
'/textures/environmentMaps/0/nz.jpg'])
​
material.envMap = environmentMapTexture

查找纹理地图:

polyhaven.com/

将HDRI转为立方体地图:

matheowis.github.io/HDRI-to-Cub…

字体

Three.js只接受特定json格式的字体

转换字体:gero3.github.io/facetype.js…

或者直接从Three.js的example中导入字体


import typefaceFont from 'three/examples/fonts/helvetiker_regular.typeface.json'

将helvetiker_regular.typeface.json文件放在static/fonts文件夹中

加载字体

从three/examples/jsm/loaders导入FontLoader

加载成功后,配置文本内容;文本样式


fontLoader.load("/fonts/helvetiker_regular.typeface.json", (font) => {
  //  使用文本几何
  const textGeometry = new TextGeometry("Li Naomi", {
    font: font,
    size: 0.5,
    height: 0.2,
    curveSegments: 12,
    bevelEnabled: true,
    bevelThickness: 0.03,
    bevelSize: 0.02,
    bevelOffset: 0,
    bevelSegments: 5,
  });
  // 使文本居中
  textGeometry.computeBoundingBox(); //计算几何体的框边界
  console.log(textGeometry.boundingBox);
  textGeometry.center();

使用matcap纹理

**下载matcap纹理:

github.com/nidorx/matc…


const textureLoader = new THREE.TextureLoader();
const matcapTexture = textureLoader.load("/textures/bgGreen.png");
const material = new THREE.MeshMatcapMaterial({ matcap: matcapTexture });
const text = new THREE.Mesh(textGeometry, material);

对光有反应的材料: MeshStandardMaterial

环境光

const ambientLight = new THREE.AmbientLight(color, intensity)

在调试面板中添加环境光强度元素

gui.add(ambientLight,'intensity').min(0).max(1).step(0.001)

定向光

const directionalLight = new THREE.DirectionalLight(0x00fffc, 0.8);
directionalLight.position.set(20, 0.25, 0); //改变环境光的方向
scene.add(directionalLight);

半球灯

const hemisphereLight = new THREE.HemisphereLight(color,groundColor,intensity);
const hemisphereLight = new THREE.HemisphereLight(0xff0000, 0x0000ff, 0.3);
scene.add(hemisphereLight);

点光源


// 点光源 就像打火机
const pointLight = new THREE.PointLight(0xffffff, 0.5);
pointLight.position.x = -1;
pointLight.position.y = -0.5;
pointLight.position.z = 1;
scene.add(pointLight);

矩形区域灯


const rectAreaLight = new THREE.RectAreaLight(color,intensity,width,height);
const rectAreaLight = new THREE.RectAreaLight(0x4e00ff, 2, 1, 1);
rectAreaLight.position.set(1.5, 0, 1);

聚光灯


const spotLight = new THREE.SpotLight(
  0x78ff00,颜色
  0.5,强度
  10,强度下降到的距离
  Math.PI * 0.1,角度
  0.25,光束轮廓的扩散长度
  1光线变暗的速度
);
spotLight.position.set(0, 2, 3);
scene.add(spotLight);
// 移动聚光灯
spotLight.target.position.x = -0.75;
scene.add(spotLight.target);
  • 性能高 环境光 半球灯
  • 性能低 聚光灯 矩形区域灯

灯光助手

实例化类并添加到场景中


const hemisphereLightHelper = new THREE.HemisphereLightHelper(
  hemisphereLight,
  0.2//helper的size
);
scene.add(hemisphereLightHelper);
const directionalLightHelper = new THREE.DirectionalLightHelper(
  directionalLight,
  0.2
);
scene.add(directionalLightHelper);

点光源助手没有参数 在移动物体后需要在下一帧更新helper


const spotLightHelper = new THREE.SpotLightHelper(spotLight)
scene.add(spotLightHelper)
window.requestAnimationFrame(() =>
{
spotLightHelper.update()
})

矩形光源助手需要单独引入


import { RectAreaLightHelper } from 'three/examples/jsm/helpers/RectAreaLightHelper.js';
​
const rectAreaLightHelper = new RectAreaLightHelper(rectAreaLight)
scene.add(rectAreaLightHelper)

阴影

激活阴影


renderer.shadowMap.enabled = true;
sphere.castShadow = true; //投射阴影
plane.receiveShadow = true; //接收阴影
directionalLight.castShadow = true; //激活光源上的阴影

阴影

设置阴影贴图大小 值必须是2的幂


directionalLight.shadow.mapSize.width = 1024;
directionalLight.shadow.mapSize.height = 1024;

设置相机的最远最近面 可以修复看不到阴影或阴影突然被裁减的错误

directionalLight.shadow.camera.near = 1
directionalLight.shadow.camera.far = 6

波幅(可视范围)控制相机每一侧可以看到的距离 值越小阴影越准确 但阴影将被裁剪


directionalLight.shadow.camera.top = 2;
directionalLight.shadow.camera.right = 2;
directionalLight.shadow.camera.bottom = -2;
directionalLight.shadow.camera.left = -2;

阴影模糊

directionalLight.shadow.radius = 10;

阴影贴图算法

renderer.shadowMap.type = THREE.PCFSoftShadowMap;

模型

Blender制作模型

快捷键文档

docs.google.com/document/d/…

数字键盘上 1 3 7 切换坐标轴

tab键切换模式

SHIFT + D复制

A 全选

导出gltf的时候 Mesh要apply modifier

在Edit Mode下,ctrl + R 可以增加切面

调整芝士片曲面的边角

Edit Mode, 选中芝士片,右键选择Subdivide,设置Number of Cuts为10, 选中一个角

开启Proportional Editing,调整芝士片的边角 滚轮可以调整影响范围

最后在Object Mode,右键选择 Shade Smooth

导入模型

在哪里找模型?

github.com/KhronosGrou…

GLTF格式

  • glTF
  • glTF-Binary
  • glTF-Draco
  • glTF-Embedded

加载模型

对于glTF,glTF-Binary,glTF-Embedded

import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
gltfLoader.load(
'/models/Duck/glTF/Duck.gltf',
  (gltf) =>
  {
  scene.add(gltf.scene.children[0])//children只有1个元素
  }
)

对于children有多个元素


gltfLoader.load("/models/FlightHelmet/glTF/FlightHelmet.gltf", (gltf) => {  
  const children = [...gltf.scene.children];
   for (const child of children) {
     scene.add(child);
   }
});
对于glTF-Draco

Draco版本,有利于加载大模型


import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath("/draco/");
gltfLoader.setDRACOLoader(dracoLoader);
gltfLoader.load("/models/Duck/glTF-Draco/Duck.gltf", (gltf) => {
  scene.add(gltf.scene);
});

模型动画

Fox中 gltf.scene.children是一个3D模型 所以添加到场景直接用scene.add(gltf.scene)


let mixer = null;
gltfLoader.load("/models/Fox/glTF/Fox.gltf", (gltf) => {
  //...
​
  //   处理动画
  mixer = new THREE.AnimationMixer(gltf.scene);
  const action = mixer.clipAction(gltf.animations[1]);
  action.play();
});
​
const tick = () => {
  //...
  
   //   动画需要每一帧都更新
  //   当mixer有值 即模型加载hou
  if (mixer) {
    mixer.update(deltaTime);
  }
  
  //...
}
​

综合案例:Portfolio

清除背景色

方法1

const renderer = new THREE.WebGLRenderer({
  canvas: canvas,
  antialias: true,
  alpha: true, ////清除背景色法1 使canvas背景为透明
});
方法2
renderer.setClearAlpha(0); 

之后在css文件中设置

html
{
background: #1e1a20;
}

让相机跟随页面滚动而滚动

如果用户滚动页面到下一部分,那么相机将向下移动到下一个对象


// 1.获取页面滚动位置
let scrollY = window.scrollY;
window.addEventListener("scroll", () => {
  scrollY = window.scrollY;
​
  const newSection = Math.round(scrollY / sizes.height);
  console.log(newSection);
//...
});
​
const tick = () => {
  const elapsedTime = clock.getElapsedTime();
  const deltaTime = elapsedTime - previousTime; //现在这一帧和上一帧花费的时间
  previousTime = elapsedTime;
​
  //2.更新相机位置
  // -scrollY因为three.js y轴往下是正
  // -scrollY / sizes.height得到 n 个section
  camera.position.y = (-scrollY / sizes.height) * objectsDistance;
}

综合案例:渲染真实效果

  • 方向光

    将光源调得更真实:

    renderer.physicallyCorrectLights = true
    

    处理shadow acne阴影线:

     directionalLight.shadow.normalBias = 0.05;//圆表面 
    normalBias应用于圆表面 可以处理shadow acne阴影线 处理平面用bias
    

  • 环境贴图
    1. 加载环境地图
    
    const cubeTextureLoader = new THREE.CubeTextureLoader();
    const environmentMap = cubeTextureLoader.load([
    '/textures/environmentMaps/0/px.jpg',
    '/textures/environmentMaps/0/nx.jpg',
    '/textures/environmentMaps/0/py.jpg',
    '/textures/environmentMaps/0/ny.jpg',
    '/textures/environmentMaps/0/pz.jpg',
    '/textures/environmentMaps/0/nz.jpg'
    ])
    
    1. 把环境贴图添加到场景
    scene.background = environmentMap
    
    1. 让模型反映环境

    环境贴图应用于具有MeshStandardMaterial的Mesh。

    
    const debugObject = {}//用来调envMapIntensity
    ​
    const updateAllMaterials = () => {
      // 将环境映射到所有的mesh中 具有MeshStandard的mesh
      scene.traverse((child) => {
        if (
          child instanceof THREE.Mesh &&
          child.material instanceof THREE.MeshStandardMaterial
        ) {
          child.material.envMap = environmentMap;
          child.material.envMapIntensity = debugObject.envMapIntensity;
    ​
          //5 激活Mesh上的阴影
          child.castShadow = true;
          child.receiveShadow = true;
        }
      });
    };debugObject.envMapIntensity = 2.5;
    gui.add(debugObject, 'envMapIntensity').min(0).max(10).step(0.001) 
    

加载模型


const gltfLoader = new GLTFLoader();
gltfLoader.load("/models/FlightHelmet/glTF/FlightHelmet.gltf", (gltf) => {
  // 调位置
  gltf.scene.scale.set(10, 10, 10);
  gltf.scene.position.set(0, -4, 0);
  gltf.scene.rotation.y = Math.PI * 0.5;
  // 向调试面板中添加rotation
  gui
    .add(gltf.scene.rotation, "y")
    .min(-Math.PI)
    .max(Math.PI)
    .step(0.001)
    .name("rotation");
  scene.add(gltf.scene);
  updateAllMaterials();
});

调整编码

renderer.outputEncoding = THREE.sRGBEncoding; //设置输出渲染编码 让场景更真实
environmentMap.encoding = THREE.sRGBEncoding; //设置环境贴图的编码

滤镜


renderer.toneMapping = THREE.ACESFilmicToneMapping;
gui
  .add(renderer, "toneMapping", {
    No: THREE.NoToneMapping,
    Linear: THREE.LinearToneMapping,
    Reinhard: THREE.ReinhardToneMapping,
    Cineon: THREE.ACESFilmicToneMapping,
    ACESFilmic: THREE.ACESFilmicToneMapping,
  })
  .onFinishChange(() => {
    renderer.toneMapping = Number(renderer.toneMapping);
  });

阴影


renderer.shadowMap.enabled = true; //1 开启渲染器的阴影
renderer.shadowMap.type = THREE.PCFSoftShadowMap; //2 改阴影类型
directionalLight.castShadow = true; //3 开启方向光的投射阴影
directionalLight.shadow.camera.far = 15;
directionalLight.shadow.mapSize.set(1024, 1024); //4 增加阴影真实性和精确度
 scene.traverse((child) => {
    if (
      child instanceof THREE.Mesh &&
      child.material instanceof THREE.MeshStandardMaterial
    ) {
      ...
​
      //5 激活Mesh上的阴影
      child.castShadow = true;
      child.receiveShadow = true;
    }
  });

着色器Shaders

着色器的概念

用GLSL编写的 会被发送到GPU。

作用

定位几何体的顶点,并为该几何体的每个可见像素(片段)着色

  • 顶点着色器

    作用:定位几何体的顶点 可以发送顶点位置,Mesh的变换,相机信息。GPU拿到顶点着色器的信息后,会将顶点投射到canvas上. 换句话说,顶点着色器会将 3D 顶点坐标转换为我们的 2D 画布坐标。

    • 在每个顶点之间变化的数据:Attributes

    • 在每个顶点之间一样的数据:Uniforms 可以在顶点着色器和片段着色器中使用。

      片段着色器在顶点着色器之后执行。

      
       
          // 矩阵 他们的值对几何体所有顶点都是相同的 因此用uniform
          uniform mat4 projectionMatrix;//将坐标转换为剪辑空间坐标
          uniform mat4 viewMatrix;//处理和相机相关的所有变换
          uniform mat4 modelMatrix;//处理和Mesh相关的所有变换 比如缩放、旋转、移动网格
          
          attribute vec3 position;//检索顶点 
          
          void main()
          {
              vec4 modelPosition = modelMatrix * vec4(position,1.0);
              modelPosition.z += sin(modelPosition.x * 10.0) * 0.1;//将平整的面改为波形
              vec4 viewPosition = viewMatrix * modelPosition;
              vec4 projectedPosition = projectionMatrix * viewPosition;
              // 要把mat4赋给一个变量 该变量的类型必须是vec4
              gl_Position = projectedPosition;
          }
       
        
      

  • 片段着色器

    作用:为几何体的每个可见片段着色 可以像顶点着色器那样发送统一数据到GPU,也可以将数据从顶点着色器发送到片段着色器(这种数据称为varing)

        precision mediump float;//决定float的精确度 一般用mediump
        
        void main()
        {
        gl_FragColor = vec4(0.5, 0.0, 1.0, 1.0); //vec4(r, g, b, a) 当a = 1,要设置new THREE.RawShaderMaterial({transparent: true})
        }
    

    Attributes

    将平整的面改为锯齿形
    1. 自定义属性aRandom

      
      // 将平整的面改为锯齿形
      const count = geometry.attributes.position.count; //获取几何体有多少顶点
      const randoms = new Float32Array(count); //count此时是数组长度
      for (let i = 0; i < count; i++) {
        randoms[i] = Math.random();
      }
      geometry.setAttribute("aRandom", new THREE.BufferAttribute(randoms, 1)); //最后一个参数 1:randoms中的元素只有1个值 不是vec2,如果是就是2
      
    2. 在顶点着色器中获取此属性并使用它来移动顶点

       modelPosition.z +=  aRandom * 0.1 ;//将平整的面改为锯齿形
      
    3. 使用aRandom属性为fragment着色. 由于不能在片段着色器中使用该属性。就得从顶点着色器发送到片段着色器

      //顶点着色器
        varying float vRandom;
        
        void main(){
          //...
           // 将数据从顶点着色器发送到名为片段着色器
              vRandom = aRandom;
        }
      
      //片段着色器
       varying float vRandom;
          void main()
          {
          gl_FragColor = vec4(0.5, vRandom, 1.0, 1.0); //vec4(r, g, b, a) 当a = 1,设置new THREE.RawShaderMaterial({transparent: true})
          }
      

    Uniforms

    Uniforms是一种将数据从js发送到着色器的方法。

    自定义Uniforms
    1. 将uniforms添加到到material

      const material = new THREE.RawShaderMaterial({
       //...
        uniforms: {
          uFrequency: { value: new THREE.Vector2(10, 5) }, //为了区分uniforms类型的数据和其他数据,将uniforms类型的数据以u开头
        },
      });
      
    2. 在顶点着色器中检索并使用

       uniform vec2 uFrequency;
        void main()
          {
            //... 
              modelPosition.z += sin(modelPosition.x * uFrequency.x) * 0.1; //将平整的面改为波形 采用自定义Uniform,可以控制波
              modelPosition.z += sin(modelPosition.y * uFrequency.y) * 0.1;  
             //...
      
          }
      
    3. 加动画

      
      const material = new THREE.RawShaderMaterial({
       //...
        uniforms: {
          uFrequency: { value: new THREE.Vector2(10, 5) },  
          //新增uTime
          uTime: { value: 0 },
        },
      });
      ​
      const tick = () => {
        const elapsedTime = clock.getElapsedTime();
      ​
        // ...
      ​
        // Update material
        material.uniforms.uTime.value = elapsedTime;
      ​
        // ..
      };
      ​
      
      //顶点着色器
      
          uniform float uTime;
      
          void main()
          {
             //...
      
              modelPosition.z += sin(modelPosition.x * uFrequency.x - uTime) * 0.1; ////将平整的面改为波形 加动画
              modelPosition.z += sin(modelPosition.y * uFrequency.y - uTime) * 0.1;  
          }
      

    材质

    1. 加载材质

      const flagTexture = textureLoader.load("/textures/bubble.jpg");
      
    2. 将材质作为uniforms发送

      const material = new THREE.RawShaderMaterial({
      // ...
      uniforms:
      {
      // ...
      uTexture: { value: flagTexture }
      }
      })
      

      在几何体上投射纹理的坐标 即为 uv坐标

      材质应用到片段着色器是调用texture2D方法,该方法第2个参数是 uv坐标

      所以需要将uv坐标传递给片段着色器 平面几何体自动生成uv属性

      // console.log(geometry.attributes.uv);

      所以在顶点着色器中直接检索

       attribute vec2 uv;
      
      //将数据从顶点着色器发送到片段着色器
       varying vec2 vUv;
      
      //片段着色器
          uniform sampler2D uTexture;
          varying vec2 vUv;
      
          void main()
          {
          //...
      	//    使用材质
      	vec4 textureColor = texture2D(uTexture,vUv);
      	gl_FragColor = textureColor;
          }
      

GLSL

类似c语言 是一种静态语言

浮点数
float bar = 1.0//必须始终提供小数 即使值是整数
整数
int foo = 123
float a = 1.0;
int b = 2;
float c = a * float(b);//动态转换类型
布尔值
bool foo = true;
Vector2
vec2 foo = vec2(1.0, 2.0);
vec2 foo = vec2();//将报错

//创建Vector2
vec2 foo = vec2(0.0 );
foo.x = 1.0;
foo.y = 2.0;
Vector3
vec3 foo = vec3(1.0, 2.0, 3.0);
或者
vec2 foo = vec2(1.0, 2.0);
vec3 bar = vec3(foo, 3.0);

取一部分vec3来生成一个vec2

vec3 foo = vec3(1.0, 2.0, 3.0);
vec2 bar = foo.xy;

Functions

函数必须以返回的值的类型开头

float add(float a, float b){
  return a + b;
}

关于Shader的文档

www.shaderific.com/glsl-functi…

www.khronos.org/registry/Op…

thebookofshaders.com/

gl_Position

为什么要给gl_Position4个值?

因为我们绘制顶点实际上最终是在剪辑空间中定位的,gl_Position的功能是在剪辑空间中定位顶点,需要4个方向

剪辑空间是有4个方向的,范围从-1到+1的空间。超出此范围的内容都会被剪辑。其中3个方向分别是x,y,z 另外一个方向是w,负责透视