源码做者:jam3html
下载地址:https://github.com/Jam3/jam3-lesson-webgl-shader-threejs前端
这是一个很好的如何在threejs中使用自定义shader的入门教程。git
若github下载太慢,可可关注公众号"实用图形学游玩指南及源码",回复“tsh1”使用国内网盘下载。github
下面的内容翻译自readme.mdweb
在这堂课里,咱们将学习如何在THREEJS中使用片断和顶点着色器。请确保你对着色器和有基本的了解。npm
使用原生WebGL,那么就要写不少样板文件,增长了很大工做量。而使用ThreeJS提供了便利的封装,让咱们更加专一于本身的事情。浏览器
若是你对ThreeJS和WebGL有基本的了解,那么学习起来将轻松不少。babel
你能够先直接下载ThreeJS,但在这里咱们将直接使用npm和budo来更快投入开发。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)
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)
[./lib/createBunnyGeometry.js](https://link.zhihu.com/?target=https%3A//github.com/Jam3/jam3-lesson-webgl-shader-threejs/blob/master/lib/createBunnyGeometry.js)
[./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)
在 ThreeJS,Mesh
是最基本的渲染3D形状的材料。它由Geometry
和Material
组成,例以下面这段代码在常见中添加一个正方体。
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
, 这是一种可使用vertexShader
和fragmentShader
的特殊材质。咱们使用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') });
💡 也许
glslify比
brfs
更合你意。
就像建立box的mesh同样,咱们建立了 bunny的mesh,
// Setup our mesh const mesh = new THREE.Mesh(geometry, material); scene.add(mesh);
这个简单的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将让它被每个像素或每个顶点调用。
顶点着色器引进了两个新概念:attributes和uniforms.
顶点着色器将在几何体的每一个顶点上运行,包含一些_attributes,好比_Position (XYZ), UV coordinates (XY), Normal (XYZ)。须要在JavaScript程序中预先给attributes赋值,而后传到顶点着色器中。
例如,一个三角形有3个顶点,每一个顶点有一个_attribute_保存了XYZ左边。对每一个三角形来讲,GPU将执行三次顶点着色器程序。
咱们的 bunny 顶点着色器只有一个 attribute 。
attribute vec3 position;
顶点和片断着色器都能使用_uniforms,_这是一个全部片断使用的常量,在JavaScript中赋值好,而后再着色器中读取。
咱们咱们使用一个 uniform来为咱们的mesh规定一个常量RGB颜色。
ThreeJS提供一些给着色器的内建uniforms,例如以下的四个矩阵,都是mat4的
projectionMatrix
— 将3D世界坐标系转换为2D屏幕坐标系viewMatrix
—PerspectiveCamera
世界矩阵的逆modelMatrix
—Mesh
的局部坐标矩阵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
同样。
这里,咱们将更加每一个像素离中心(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)表明远离中心点。
这里,咱们将在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 passedvNormal
for 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 }
, 参数,意味着法向量是独立的。你也能够改变这个参数看看联合的法向量是什么效果。
而后,咱们将沿着表面法向量推进每一个三角形来实现爆炸效果!
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
做为位移的程度。
片断着色器无需改变。
最后,咱们将实现爆炸动画,咱们只须要给着色器添加一个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 } } });
咱们定义了time
uniform (着色器中将使用相同的名字), 数据类型是浮点型 (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! 🐰
你可能很疑惑为何ThreeJS 同时拥有 ShaderMaterial 和 RawShaderMaterial。我建议使用RawShaderMaterial,由于不容易出错,但你得多花一些时间来调节各类参数。
你也可使用ShaderMaterial
这样就能够跳过一些设置,直接使用 ThreeJS's的内建attributes, uniforms。
void main () { gl_Position = projectionMatrix * modelViewMatrix * vec4(position.xyz, 1.0); }
若是github下载太慢,可关注公众号,回复“tsh1”得到源码国内网盘下载地址,以及持续跟踪最新消息!