threejs自定义shader

源码:

源码做者:jam3html

下载地址:https://github.com/Jam3/jam3-lesson-webgl-shader-threejs前端

这是一个很好的如何在threejs中使用自定义shader的入门教程。git

若github下载太慢,可可关注公众号"实用图形学游玩指南及源码",回复“tsh1”使用国内网盘下载。github

下面的内容翻译自readme.mdweb

WebGL 课程 — ThreeJS Shaders

在这堂课里,咱们将学习如何在THREEJS中使用片断和顶点着色器。请确保你对着色器和有基本的了解。npm

为何要用 ThreeJS?

使用原生WebGL,那么就要写不少样板文件,增长了很大工做量。而使用ThreeJS提供了便利的封装,让咱们更加专一于本身的事情。浏览器

若是你对ThreeJS和WebGL有基本的了解,那么学习起来将轻松不少。babel

Setup

你能够先直接下载ThreeJS,但在这里咱们将直接使用npmbudo来更快投入开发。less

💡 npm和budo是前端的模组,在这里你并不须要知道有关的太多信息。

第一步,你应该首先克隆这个项目到本地而且安装相关依赖。函数

git clone https://github.com/Jam3/jam3-lesson-webgl-shader-threejs.git

# move into directory
cd jam3-lesson-webgl-shader-threejs

# install dependencies
npm install

# start dev server
npm run start

你应该看到以下画面,这生成一个供index.html使用的bundle.js

如今来浏览器中打开http://localhost:9966/你就能看到一个白色的bunny,你可用鼠标拖动摄像机来从不一样角度观察这个3D的bunny。

这个项目经过babelify使用了Babel,也使用了brfs

代码概览

代码被放在不一样的文件里。

  • [./lib/index.js](https://link.zhihu.com/?target=https%3A//github.com/Jam3/jam3-lesson-webgl-shader-threejs/blob/master/lib/index.js)
    这里是咱们demo的核心部分,建立了咱们的应用,几何,网格和渲染循环。咱们确保THREE能正常工做,咱们经过require命令从其余文件引入。
global.THREE = require('three');
  • [./lib/createApp.js](https://link.zhihu.com/?target=https%3A//github.com/Jam3/jam3-lesson-webgl-shader-threejs/blob/master/lib/createApp.js)
    这个样本程序使用orbit-controls,这样你就能用鼠标控制摄像机了。这不是这节课的重点。
  • [./lib/createBunnyGeometry.js](https://link.zhihu.com/?target=https%3A//github.com/Jam3/jam3-lesson-webgl-shader-threejs/blob/master/lib/createBunnyGeometry.js)
    这个文件从网格多边形(mesh primitive)中建立了ThreeJS几何体,也就是一个小兔子
  • [./lib/shader.frag](https://link.zhihu.com/?target=https%3A//github.com/Jam3/jam3-lesson-webgl-shader-threejs/blob/master/lib/shader.frag)
    这是你将在本节课使用的片断着色器。
  • [./lib/shader.vert](https://link.zhihu.com/?target=https%3A//github.com/Jam3/jam3-lesson-webgl-shader-threejs/blob/master/lib/shader.vert)
    这是你在本节课使用的顶点着色器。
  • [./lib/reference/](https://link.zhihu.com/?target=https%3A//github.com/Jam3/jam3-lesson-webgl-shader-threejs/blob/master/lib/reference)
    这里你能够看到一些写好的着色器示例。

Mesh & Shader

在 ThreeJS,Mesh是最基本的渲染3D形状的材料。它由GeometryMaterial组成,例以下面这段代码在常见中添加一个正方体。

const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 'red' });
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

./lib/index.js, 咱们使用相似的代码建立3D的bunny的几何模型。

// Get a nicely prepared geometry
const geometry = createBunnyGeometry({ flat: true });

如今要为咱们的bunny建立材质。

咱们使用RawShaderMaterial, 这是一种可使用vertexShaderfragmentShader的特殊材质。咱们使用brfs, 一种 source transform,这样咱们就能在开发时在单独的文件里编写shader,并在打包时将它们包括进去。

const path = require('path');
const fs = require('fs');

// Create our vertex/fragment shaders
const material = new THREE.RawShaderMaterial({
  vertexShader: fs.readFileSync(path.join(__dirname, 'shader.vert'), 'utf8'),
  fragmentShader: fs.readFileSync(path.join(__dirname, 'shader.frag'), 'utf8')
});
💡 也许 glslifybrfs更合你意。

就像建立box的mesh同样,咱们建立了 bunny的mesh,

// Setup our mesh
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

步骤一:你的第一个shader

参考:shader.vert,shader.frag

这个简单的Shader使用白色的纹理覆盖了bunny。在你挑战复杂的事情,先把简单的事情作好总时没错的。

顶点着色器

这个代码位于[./lib/shader.vert](https://link.zhihu.com/?target=https%3A//github.com/Jam3/jam3-lesson-webgl-shader-threejs/blob/master/lib/shader.vert)

attribute vec3 position;

uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;

void main () {
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

片断着色器和顶点着色器须要 一个main()函数,WebGL将让它被每个像素或每个顶点调用。

顶点着色器引进了两个新概念:attributesuniforms.

Attributes

顶点着色器将在几何体的每一个顶点上运行,包含一些_attributes,好比_Position (XYZ), UV coordinates (XY), Normal (XYZ)。须要在JavaScript程序中预先给attributes赋值,而后传到顶点着色器中。

例如,一个三角形有3个顶点,每一个顶点有一个_attribute_保存了XYZ左边。对每一个三角形来讲,GPU将执行三次顶点着色器程序。

咱们的 bunny 顶点着色器只有一个 attribute 。

attribute vec3 position;

Uniforms

顶点和片断着色器都能使用_uniforms,_这是一个全部片断使用的常量,在JavaScript中赋值好,而后再着色器中读取。

咱们咱们使用一个 uniform来为咱们的mesh规定一个常量RGB颜色。

ThreeJS提供一些给着色器的内建uniforms,例如以下的四个矩阵,都是mat4的

  • projectionMatrix— 将3D世界坐标系转换为2D屏幕坐标系
  • viewMatrixPerspectiveCamera世界矩阵的逆
  • modelMatrixMesh的局部坐标矩阵
  • modelViewMatrix— view 和 model 矩阵的联合

你须要以下定义它们才能使用。

uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;

顶点投影

顶点着色器的目标是将咱们的3D数据转换为能被WebGL光栅化投影到2D屏幕上的数据。

为了作到这点,咱们使用以下代码。

gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
💡 咱们如今没必要关心 1.0为何要做为位置里的 w份量。但你也能够看看 here.

咱们也能够写的不一样,用vec4定义attribute,WebGL将用 w= 1.0默认扩展每一个向量,而后分别乘上三个矩阵

attribute vec4 position;

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

void main () {
  gl_Position = projectionMatrix * viewMatrix * modelMatrix * position;
}

片断着色器

若是你打开了[./lib/shader.frag](https://link.zhihu.com/?target=https%3A//github.com/Jam3/jam3-lesson-webgl-shader-threejs/blob/master/lib/shader.frag), 你将看到以下代码

precision highp float;

void main () {
  gl_FragColor = vec4(1.0);
}

第一行precision, 定义了GPU使用的浮点数的精度。当使用RawShaderMaterial, 你就得在片断着色器中第一行这样定义。你可使用lowp,mediump, 或highp, 但通常来讲将使用highp

💡 顶点着色浮点数据默认精度是 highp, 因此以前咱们不须要定义它。

而后,咱们使用gl_FragColor, 这是一个vec4做为颜色输出,你应该将这个向量的四个份量都写上。这里咱们将它写为纯白:(1.0, 1.0, 1.0, 1.0)。与ShaderToy中的fragColor同样。

步骤2:与中心的距离

参见:shader2.vert,shader2.frag

这里,咱们将更加每一个像素离中心(0, 0, 0)的距离XYZ来决定它们的颜色。

顶点着色器:

这一步,顶点着色器相似于这样:

attribute vec4 position;

uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;

varying float distFromCenter;

void main () {
  distFromCenter = length(position.xyz);
  gl_Position = projectionMatrix * modelViewMatrix * position;
}

这个着色器使用了新概念:varyings. 这个值只能在顶点着色器中写入,并被传递到片断着色器中。 这里,咱们使用position向量的长度,即这个顶点于vec3(0.0)的距离。

💡 你也可使用内置的 distance,例如:
distFromCenter = distance(position.xyz, vec3(0.0));

片断着色器

precision highp float;

varying float distFromCenter;
void main () {
  gl_FragColor = vec4(vec3(distFromCenter), 1.0);
}

在片断着色器中,varyings是只读的,来自于顶点着色器。片断着色器将用顶点间的插值着色。好比一个顶点的值是0.0,另外一个顶点的值是1.0,这两个顶点中间的像素的值将是介于这两个值之间的值。

这里,像素的颜色将有distFromCenter来决定 ,也就是说黑色(0.0,0.0,0.0)表明靠近中心点,白色(1.0,1.0,1.0)表明远离中心点。

步骤三:可视化法向量

参见:shader3.vert,shader3.frag

这里,咱们将在mesh里可视化法向量。这是一个新的attribute,已经在createBunnyGeometry.js定义好。法向量是用来定义三角形朝向的, RGB对应着XYZ,也就是说你看到的蓝色地方的三角形对着Z轴,其余同理。

顶点着色器

attribute vec4 position;
attribute vec3 normal;

uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;

varying vec3 vNormal;

void main () {
  vNormal = normal;
  gl_Position = projectionMatrix * modelViewMatrix * position;
}

这个着色器里,咱们使用了一个新的attribute,normal, 而且使用varying 类型的vNormal传递给片断着色器。

片断着色器

precision highp float;

varying vec3 vNormal;

void main () {
  gl_FragColor = vec4(vNormal, 1.0);
}

Here, we simply render the passedvNormalfor each fragment. It ends up looking nice, even though some values will be clamped because they are less than 0.0.

这里,咱们知识简单的用法向量当成颜色来渲染,看起来效果很不错,虽然小于0的值会被转换为0。

💡 index.js中建立的几何体使用了 { flat: true }, 参数,意味着法向量是独立的。你也能够改变这个参数看看联合的法向量是什么效果。

步骤4:爆炸三角形

参考:shader4.vert,shader4.frag

而后,咱们将沿着表面法向量推进每一个三角形来实现爆炸效果!

顶点着色器

attribute vec4 position;
attribute vec3 normal;

uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;

varying vec3 vNormal;

void main () {
  vNormal = normal;

  vec4 offset = position;
  float dist = 0.25;
  offset.xyz += normal * dist;
  gl_Position = projectionMatrix * modelViewMatrix * offset;
}

这个着色器里,咱们将使用一个向量来调节每一个顶点的位置。这里咱们直接使用normal来做为位移的方向,dist做为位移的程度。

片断着色器无需改变。

步骤5:动画

参考:shader5.vert,shader5.frag

最后,咱们将实现爆炸动画,咱们只须要给着色器添加一个uniform

咱们须要在js中设置好,咱们打开index.js而且定义一个uniforms

// Create our vertex/fragment shaders
const material = new THREE.RawShaderMaterial({
  vertexShader: fs.readFileSync(path.join(__dirname, 'shader.vert'), 'utf8'),
  fragmentShader: fs.readFileSync(path.join(__dirname, 'shader.frag'), 'utf8'),
  uniforms: {
    time: { type: 'f', value: 0 }
  }
});

咱们定义了timeuniform (着色器中将使用相同的名字), 数据类型是浮点型 (ThreeJS 使用'f'来表示 — 参见here), 而且提供默认值。

而后,咱们的渲染循环将在每帧增长这个值。

// Time since beginning
let time = 0;

// Start our render loop
createLoop((dt) => {
  // update time
  time += dt / 1000;

  // set value
  material.uniforms.time.value = time;

  // render
  ...
}).start();

顶点着色器

attribute vec4 position;
attribute vec3 normal;

uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;

uniform float time;

varying vec3 vNormal;

void main () {
  vNormal = normal;

  vec4 offset = position;

  // Animate between 0 and 1
  // sin(x) returns a value in [-1...1] range
  float dist = sin(time) * 0.5 + 0.5;

  offset.xyz += normal * dist;
  gl_Position = projectionMatrix * modelViewMatrix * offset;
}

在这个着色器中,咱们将在main函数签名定义咱们的uniform。因为这是一个uniform,这将在渲染时对全部顶点保持同样的值。

uniform float time;

咱们使用GLSL内建的sin函数(与JS中的Math.sin同样),将返回-1.0到1.0的值。咱们将它归一化到0到1,这样咱们的mesh就只向外面爆炸。

float dist = sin(time) * 0.5 + 0.5;

Voilà! 咱们有了个爆炸效果的 bunny! 🐰

附录: ShaderMaterial vs RawShaderMaterial

你可能很疑惑为何ThreeJS 同时拥有 ShaderMaterial 和 RawShaderMaterial。我建议使用RawShaderMaterial,由于不容易出错,但你得多花一些时间来调节各类参数。

你也可使用ShaderMaterial这样就能够跳过一些设置,直接使用 ThreeJS's的内建attributes, uniforms。

void main () {
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position.xyz, 1.0);
}

下一步

在知乎专栏上持续关注图形学内容!

若是github下载太慢,可关注公众号,回复“tsh1”得到源码国内网盘下载地址,以及持续跟踪最新消息!

相关文章
相关标签/搜索