threejs-自定义着色器材质

文章目录

  • 前言
  • 一、GLSL基本使用
    • 1.GLSL中的存储关键字
      • attribute
      • uniform
      • varying
    • 2.精度限定关键字
    • 3.模型转换矩阵
  • 二、使用RawShaderMaterial
    • 1.顶点着色器向片元着色器传递数据
    • 2.着色器变换操作
    • 3.让着色器动起来
  • 总结


前言

看了这么久的threejs了,今天使用着色器RawShaderMaterial绘出了第一个图形,特来记录下,例子比较简单,讲的也比较详细,相信不熟悉着色器编程的小伙伴们也能看懂哦。


一、GLSL基本使用

首先了解编写本程序用到的GLSL基本知识点,webgl中着色器的介绍与流程之前有分享过:传送门

1.GLSL中的存储关键字

attribute

attribute变量只能在顶点着色器中使用,它是一个全局变量,被用来表示顶点信息,举例:

attribute vec3 position; // 存储坐标信息
attribute vec2 uv; // 贴图坐标
attribute vec4 a_Color // 颜色

uniform

uniform变量既可以在顶点着色器中使用,也可以在片元着色器中使用,它也是一个全局变量,可以是除了数组与结构体的任何类型,在顶点着色器和片元着色器定义了同名uniform变量时会被二者共享,即会被所有顶点和片元共用,它可以被用来存储变换矩阵,时间纹理等举例:

uniform mat4 modelMatrix; // 模型转换矩阵
uniform mat4 viewMatrix; // 视图矩阵
uniform mat4 projectionMatrix; // 投影矩阵
uniform float uTime; // 时间

varying

vary它也是一个全局变量,与uniform不同的一点是:他必须同时在顶点着色器和片元着色器中定义同名同类型的varying变量,它的作用是把顶点着色器的数据传递给片元着色器,举例:

在顶点着色器,使用varying定义一个变量v_uv,它是二维向量类型,用它获取每个点的uv坐标

varying vec2 v_uv;
void main() {
  v_uv = uv;
  ···
}

在片元着色器中接收来自顶点着色器中的uv信息,并把它作为rbga中的前俩个值

varying vec2 v_uv;
void main() {
  gl_FragColor = vec4(v_uv, 0.0, 1.0); 
}

2.精度限定关键字

在GLSL中需要指定精度以提高运行效率,减少内存损耗,可以在开头指定以下三个之一。

precision lowp float;
precision medium float;
precision highp float;

3.模型转换矩阵

将一个(外部引入)模型的最终显示到二维屏幕上,实际经历了一系列模型转换过程,流程如下。后续我会单独记录一下流程原理,其中相机坐标系又叫做视图坐标系;目前只需要知道他们在threejs中是如何代表的:

模型转换矩阵
视图矩阵
投影矩阵
模型坐标系
世界坐标系
相机坐标系
裁剪坐标系

在webgl中它们的类型和名称如下:

uniform mat4 modelMatrix; // 模型转换矩阵
uniform mat4 viewMatrix; // 视图矩阵
uniform mat4 projectionMatrix; // 投影矩阵
position // 模型坐标系中的坐标

二、使用RawShaderMaterial

Threejs中提供了ShaderMaterial和RawShaderMaterial两种材质可进行GLSL编写,ShaderMaterial内置了许多常用的GLSL变量,我们使用RawShaderMaterial从头编写。

1.顶点着色器向片元着色器传递数据

在外部引入写好的着色器,基本场景的构建可以参考这里

	import basicVertexShader from '../shader/vertexShader.glsl'
	import basicFragmentShader from '../shader/fragmentShader.glsl'
	···
	 const shaderMaterial = new THREE.RawShaderMaterial({
       vertexShader: vertexShader,
       fragmentShader: fragmentShader
     })

在顶点着色器中,主要做了这么几件事:

  • 获取模型的每个顶点的模型坐标和uv坐标
  • 使用varying定义v_uv,获取顶点的uv值传递给片元着色器
  • 计算每个顶点的裁剪坐标 gl_Position(在RawShaderMaterial中需要手动计算)

顶点着色器内容:

	attribute vec3 position; // 顶点的坐标信息
	attribute vec2 uv; // 顶点的uv坐标信息

	uniform mat4 modelMatrix;
	uniform mat4 viewMatrix;
	uniform mat4 projectionMatrix;

	varying vec2 v_uv;  // 要传递给片元着色器的数据

	precision lowp float;

	void main() {
 		 v_uv = uv; // uv坐标信息
 		 // gl_Position为每个点裁剪坐标,在RawShaderMaterial可以使用下式获取
  		gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);

在顶点着色器中,主要做了:

  • 接收来自顶点着色器的信息——顶点坐标的uv
  • 将来自顶点着色器的信息赋给颜色rgba中的rg值(gl_FragColor 表示片元颜色)

片元着色器内容:

	varying vec2 v_uv; // 接收来自顶点着色器的信息
	precision lowp float;

	void main() {
 		 gl_FragColor = vec4(v_uv, 0.0, 1.0);  // 将来自顶点着色器的信息赋给颜色rg值(gl_FragColor 表示片元颜色)
	}

最终效果如下:
threejs-自定义着色器材质_第1张图片
threejs采用右手空间坐标系,红轴为X轴,绿轴为Y轴,在左下角uv坐标为(0.0,0.0),所以他的rgba就变成了(0.0,0.0,0.0,1.0)的纯黑色,同理右上角的颜色是rgba(1.0,1.0,1.0,1.0)的黄色。

2.着色器变换操作

在顶点着色器中,将模型坐标单独提取出来:

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

modelPosition就是我们说的模型坐标,即右手坐标系,注意它和空间直角坐标系是不同的。(图片来自网络)

右手坐标系
threejs-自定义着色器材质_第2张图片

空间直角坐标系
threejs-自定义着色器材质_第3张图片

对模型的xyz坐标进行处理,修改顶点着色器,对于每个点顶点:

  • XY坐标整体移动1.0个单位
  • Z坐标随着其X坐标成正弦分布
void main() {
	v_uv = uv; // uv坐标信息
    vec4 modelPosition = modelMatrix * vec4(position, 1.0);
    modelPosition.x += 1.0;
    modelPosition.y += 1.0;
    modelPosition.z += 0.1 * sin(modelPosition.x * 10.0);
    gl_Position = projectionMatrix * viewMatrix * modelPosition;
}

threejs为了提高效率默认只渲染单面,需要把 side:THREE.DoubleSide给设置上

    const shaderMaterial = new THREE.RawShaderMaterial({
      // 投影矩阵 * 视图矩阵 * 模型矩阵 * 顶点坐标
      vertexShader: vertexShader,
      fragmentShader: fragmentShader,
      side:THREE.DoubleSide
    })

效果也如预计中的一样:
threejs-自定义着色器材质_第4张图片

对模型的片元着色器再进行处理,按照Z坐标的大小为其设置颜色:

  • 顶点着色器中设置varying float f_height, 用来向片元着色器传递模型的Z坐标
  • 片元着色器中声明arying float f_height接收来自顶点着色器的信息,在main函数中接受它,把这个值赋给rgba中的第一位

顶点着色器:

attribute vec3 position;
attribute vec2 uv;

uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;

varying vec2 v_uv;
varying float f_height;// 传递Z

precision lowp float;

void main() {
	// v_uv = uv; // uv坐标信息
    vec4 modelPosition = modelMatrix * vec4(position, 1.0);
    modelPosition.x += 1.0;
    modelPosition.y += 1.0;
    modelPosition.z += 0.1 * sin(modelPosition.x * 10.0); 
  f_height = modelPosition.z // 放入传递的数据
    gl_Position = projectionMatrix * viewMatrix * modelPosition;
}

片元着色器:

// varying vec2 v_uv;
varying float f_height; // 传递来的Z坐标
precision lowp float;

void main() {
  float height = f_height + 1.0;  // 创建变量接收数据
  // gl_FragColor = vec4(v_uv, 0.0, 1.0); 
  gl_FragColor = vec4(height * 1.0, 0.0, 0.0, 1.0);
}

波峰处R值很高,而波谷Z坐标为0,接近纯黑色。
threejs-自定义着色器材质_第5张图片

3.让着色器动起来

在RawSahderMaterial中,需要我们手动将时间传递给着色器,为此我们需要:

  • 定义Three.Clock变量,获取每帧的当前时间
  • 在 RawShaderMaterial中定义uniform,将获取到的时间传递给它
  • 片元着色器中接收时间,并把它传给坐标,实现“动起来”的效果

为此,需要对原有代码进行一些改造,同时也调整一下片元着色器的亮度。

  const clock = useRef(new THREE.Clock()).current // 定义Three.Clock
  const shaderMaterial = useRef(null) // 将材质提到全局
  ···
	
// 初始化地板
  const initGeometry = () => {

    // 原始着色器材质
    shaderMaterial.current = new THREE.RawShaderMaterial({
      // 投影矩阵 * 视图矩阵 * 模型矩阵 * 顶点坐标
      vertexShader,
      fragmentShader,
      uniforms: {
        uTime: { value: 0 }
      },
      side: THREE.DoubleSide
    })

    const floor = new THREE.Mesh(
      new THREE.PlaneBufferGeometry(1, 1, 32, 32),
      shaderMaterial.current
    );
    scence.add(floor)
  }	

  ···
  // 渲染器执行渲染
  const renderScene = useCallback(() => {
    console.log('renderScene')
    timer.current = window.requestAnimationFrame(() => renderScene())
    controls.update();

    const curElapsedTime = clock.getElapsedTime()
    shaderMaterial.current.uniforms.uTime.value = curElapsedTime
    // console.log(curElapsedTime,'---')
    render.render(scence, camera);
  }, [render])

顶点着色器:

		precision lowp float;
      attribute vec3 position;
      attribute vec2 uv;
      
      uniform mat4 modelMatrix;
      uniform mat4 viewMatrix;
      uniform mat4 projectionMatrix;
      uniform float uTime; // 接收时间
      
      varying vec2 v_uv;
      varying float f_height;

      void main() {
        v_uv = uv; // uv坐标信息
        vec4 modelPosition = modelMatrix * vec4(position, 1.0);
        modelPosition.x += 1.0;
        modelPosition.y += 1.0;
        modelPosition.z = 0.05 * sin((modelPosition.x + uTime)* 10.0);
        modelPosition.z += 0.05 * sin((modelPosition.y + uTime)* 10.0);
        f_height = modelPosition.z;
       gl_Position = projectionMatrix * viewMatrix * modelPosition;
    }

实现动画效果:
threejs-自定义着色器材质_第6张图片

总结

以上就是基本使用了,流程还是比较简单的,有要看完整代码的朋友评论区留一下联系方式我会私发的哈。

你可能感兴趣的:(THREE.JS,前端,着色器)