ThreeJS-3D教学十二:ShaderMaterial

一、首先 Shader 是做什么的

Shader 可以自定义每个顶点、每个片元/像素如何显示,而控制顶点和片元显示是通过设置 vertexShader 顶点着色器和 fragmentShader 片元着色器,这两个着色器用在 ShaderMaterial 和 RawShaderMaterial 材质上。

我们先看一个例子:

<!DOCTYPE html>
<html lang="en">
<head>
	<title>three.js webgl - raw shader</title>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
</head>
<body>
<div id="container"></div>
<script id="vertexShader" type="x-shader/x-vertex">
	uniform mat4 modelViewMatrix;
	uniform mat4 projectionMatrix;
	attribute vec3 position;
	void main()	{
		gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
	}
</script>

<script id="fragmentShader" type="x-shader/x-fragment">
	void main()	{
		gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
	}

</script>
<script type="importmap">
	{
		"imports": {
          "three": "../three-155/build/three.module.js",
          "three/addons/": "../three-155/examples/jsm/"
        }
	}
</script>

<script type="module">
  import * as THREE from 'three';
  import Stats from 'three/addons/libs/stats.module.js';
  import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
  let container, stats, controls;
  let camera, scene, renderer;

  init();
  animate();
  initObject();

  function initObject() {
    // geometry
    // 第一个是生成几个三角形 
    const vertexCount = 2 * 3;
    const geometry = new THREE.BufferGeometry();
    const positions = [];
    const colors = [];

    for ( let i = 0; i < vertexCount; i ++ ) {
      // adding x,y,z
      positions.push( Math.random() - 0.5 );
      positions.push( Math.random() - 0.5 );
      positions.push( Math.random() - 0.5 );
      // adding r,g,b,a
      colors.push( Math.random() * 255 );
      colors.push( Math.random() * 255 );
      colors.push( Math.random() * 255 );
      colors.push( Math.random() * 255 );
    }

    const positionAttribute = new THREE.Float32BufferAttribute( positions, 3 );
    const colorAttribute = new THREE.Uint8BufferAttribute( colors, 4 );
    colorAttribute.normalized = true; // this will map the buffer values to 0.0f - +1.0f in the shader
    geometry.setAttribute( 'position', positionAttribute );
    geometry.setAttribute( 'color', colorAttribute );

    // material
    const material = new THREE.RawShaderMaterial({
      uniforms: {
        time: { value: 1.0 }
      },
      vertexShader: document.getElementById( 'vertexShader' ).textContent,
      fragmentShader: document.getElementById( 'fragmentShader' ).textContent,
      side: THREE.DoubleSide,
      transparent: true
    });
    const mesh = new THREE.Mesh( geometry, material );
    scene.add( mesh );
  }

  function init() {
    container = document.getElementById( 'container' );
    camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 10 );
    camera.position.z = 2;
    scene = new THREE.Scene();
    scene.background = new THREE.Color( 0x101010 );
    renderer = new THREE.WebGLRenderer();
    renderer.setPixelRatio( window.devicePixelRatio );
    renderer.setSize( window.innerWidth, window.innerHeight );
    container.appendChild( renderer.domElement );
    controls = new OrbitControls( camera, renderer.domElement );
    stats = new Stats();
    container.appendChild( stats.dom );
    window.addEventListener( 'resize', onWindowResize );

  }

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

  function animate() {
    requestAnimationFrame( animate );
    render();
    controls.update();
    stats.update();
  }
  function render() {
    const time = performance.now();
    const object = scene.children[0];
    if (object) {
      // object.rotation.y = time * 0.0005;
      object.material.uniforms.time.value = time * 0.005;
    }
    renderer.render( scene, camera );
  }
</script>
</body>
</html>

以上代码 顶点着色器 我们用的是固定写法计算顶点的位置

gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );

或者也可以这样:

gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4( position, 1.0 );

而片元着色器我们设置了一个白色

我相信大家会对 scropt 中 x-shader/x-vertex、x-shader/x-fragment 很陌生,没关系咱们学习 Shader 其实大部分就是学这里面怎么写:

它是一种类似C语言的 GLSL 语言------即 OpenGL Shading Language------,

JavaScript、C 等语言通常在 CPU 上执行,而着色器语言通常在 GPU 上执行,由 GPU 分别对每个顶点、每个片元独立执行

shader 程序可以单独写在诸如 vertex.glsl、fragment.glsl 的文件里再导入使用,也可以和示例一样写在script中,或者在 JavaScript 里用字符串格式表示(后面会介绍)

在顶点着色器里需要设置 gl_Position顶点位置,在片元着色器里需要设置 gl_FragColor 片元/像素颜色,两者都在没有返回值的 void main() {} 主函数里设置,并且 main 函数会被自动执行

着色器语言三种变量 attribute、uniform 和 varying

  • 简单总结
    顶点着色器渲染定位顶点位置
    片段着色器为该几何体的每个可见片元(像素)进行着色
    片段着色器在顶点着色器之后执行
    在每个顶点之间会有变化的数据(如顶点的位置)称为attribute,只能在顶点着色器中使用
    顶点之间不变的数据(如网格位置或颜色)称为uniform,可以在顶点着色器和片段着色器中使用
    从顶点着色器发送到片元着色器中的插值计算数据被称为varying

看了上面的内容,我们再来看一个案例,提前说下 在这里我无意将所有 GLSL 语言的方法一一列出,我相信大家也不愿意看,毕竟网上一大堆类似文章,官网上也能看,我只是通过案例,把一些常用的知识点给到大家,能让各位对 编写Shader有个初步的认知:

let vertexShader = `
    precision mediump float;
	precision mediump int;
	uniform mat4 modelViewMatrix;
	uniform mat4 projectionMatrix;
	attribute vec3 position;
	attribute vec4 color;
	varying vec3 vPosition;
	varying vec4 vColor;
	void main()	{
		vPosition = position;
		vColor = color;
		gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
	}
`;
let fragmentShader = `
    precision mediump float;
	precision mediump int;
	uniform float time;
	varying vec3 vPosition;
	varying vec4 vColor;
	void main()	{
		vec4 color = vec4( vColor );
		color.g += sin( vPosition.x * 10.0 + time ) * 0.5;
		gl_FragColor = color;
	}
`;

上面有几点知识点,先从简单的来,

1、通过 varying 可以将vPosition、vColor 从 vertexShader 到 fragmentShader

2、上面的写法是 shader 程序刚才提到的 字符串格式写法

3、precision 一个新的知识点 - 着色器运算精度设置

通过设置着色器数值的精度可以更好的配置资源,可以根据需要,在不太影响渲染效果前提下,可以尽量降低运算精度。

lowp、mediump和highp关键字 分别对应 低、中、高三个精度

1)通过precision关键字可以批量声明一些变量精度。

比如顶点着色器代码设置precision highp float;,表示顶点着色器中所有浮点数精度为高精度。

2)比如片元着色器代码设置precision lowp int;,表示片元着色器中所有整型数精度为低精度。

3)顶点和片元着色器不同类型数据默认精度

顶点着色器默认精度

数据类型 默认精度
int 高精度hight
float 高度hight
sampler2D 低精度lowp
samplerCube 低精度lowp

片元着色器默认精度

数据类型 默认精度
int 中精度mediump
float 无默认值,如果片元着色器用到浮点数,注意一定手动设置
sampler2D 低精度lowp
samplerCube 低精度lowp

4、我们发现有申明 modelViewMatrix、projectionMatrix、position、color变量,这是因为我们使用 RawShaderMaterial材质,这个方法没有默认的内置变量声明,与之对应的我们可以用 ShaderMaterial 材质,此时就可以这样写:

let vertexShader = `
    precision mediump float;
	precision mediump int;
	varying vec3 vPosition;
	varying vec4 vColor;
	void main()	{
		vPosition = position;
		vColor = color;
		gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
	}
`;

更多的 内置变量请看这里

通过以上案例我们算是简单的了解了 Shader,发现 Shader其实就是改变 顶点位置 和 片元/像素颜色

二、图形构成的基础 三角形

我们将 geometry 换为一个球体来认识一下图形的基础构成

const geometry = new THREE.SphereGeometry( 0.5, 16, 8 );
const material = new THREE.RawShaderMaterial({

          uniforms: {
            time: { value: 1.0 }
          },
          vertexShader: document.getElementById( 'vertexShader1' ).textContent,
          fragmentShader: document.getElementById( 'fragmentShader1' ).textContent,
          side: THREE.DoubleSide,
          transparent: false,
          wireframe: true  // 将几何体渲染为线框,默认值为false(即渲染为平面多边形)。
        });

通过这个球体 可以很好的理解 图形的构成,其实就是一个个的三角形,三角形越是多,图形效果越好 当然对电脑性能要求越高,

const geometry = new THREE.SphereGeometry( 0.5, 128, 64);

可以看到 这个球体就很完美了,通过这个案例也能更好的帮大家理解

顶点着色器渲染顶点位置的顶点 是哪些点、从哪来的点

片段着色器为该几何体的每个可见片元(像素)进行着色

相关推荐
白葵新8 小时前
Open3D 删除点云中重叠的点(方法二)
图像处理·人工智能·python·算法·计算机视觉·3d
木木阳16 小时前
WACV2023论文速览3D相关
论文阅读·3d·wacv
mirrornan1 天前
什么是Web3D交互展示?有什么优势?
3d·webgl·3d模型·web3d·3d展示
VRARvrnew3d1 天前
采煤机作业3D虚拟仿真教学线上展示增强应急培训效果
安全·3d·vr·虚拟现实·虚拟仿真·3d展示·采煤机作业
LhcyyVSO1 天前
Maya崩溃闪退常见原因及解决方案
3d·3d建模·云渲染·动画渲染·maya·3d渲染·渲染农场
q567315231 天前
Python 3.x 下的 3D 游戏引擎
开发语言·后端·python·3d·django·游戏引擎
q567315232 天前
matplotlib mplot3d模块在Ubuntu 10.04中的问题与解决方法
ubuntu·3d·matplotlib
MediTechInsight2 天前
「opengl」光照和材质在3D图形渲染中的作用与实现
3d·图形渲染·材质
久数君2 天前
数据驱动制造业升级,免费可视化工具成关键
大数据·网络·物联网·3d·信息可视化
枝上棉蛮2 天前
用免费的可视化工具制作3D智慧城市大屏,融合数字孪生,引领数据升级
3d·免费·数字孪生·数据可视化·3d可视化大屏·数据可视化工具·山海鲸可视化