Shader Tutorial 着色器
稍稍修shader绘制的顶点坐标
一个10*10的网格平面,正常绘制时,其VertexShader如下:
1 | void main(){ |
但是如果稍稍做一些修改,将使用三角函数对position进行变换:
1 | void main(){ |
1 | void main(){ |
1 | void main(){ |
传入uniform变量
shader存在两种可以从js传入的变量类型,
一种是attribute,
另一种是uniform,
attribute针对不同顶点提供不同的值,
uniform类型的变量能够作用于每一个顶点,
使用threejs传入uniform类型的变量的位置,和传入shader程序的位置相同,
也是在定义Shader材质的时候作为属性传入:
1 | const uniforms = { |
动画的关键属性就是时间,
因此还需要考虑属性随时间的变化,
可以在animate函数中对属性进行修改:
1 | function animate(){ |
以上是在js中的配置,着色器程序中也需要定义需要接收的uniform变量:
1 | <script id="vertex-shader" type="x-vertex"> |
shader中参与计算的数据附着上时间属性后,构成了动画:
要实现片元颜色沿x轴显现出渐变色,
首先需要知道该片元的x轴坐标,
这个值在FragmentShader中是提供的:gl_FragCoord.x,
这是这个片元的绝对位置,
除此之外还需要知道这个x值相对整个画布的位置,
因为只有这样才能把x值归一化到-1和1之间,
因此画布大小这个变量就需要从js中传入:
1 | const uniforms = { |
在fragment shader中接收画布分辨率之后,
计算出片元的相对位置,
再根据这个相对位置设置片元颜色:
1 | <script id="fragment-shader" type="x-fragment"> |
最终绘制的结果呈现渐变效果:
鼠标坐标需要从js中传入,
需要注意的是鼠标依赖的是浏览器坐标系,
从左上角开始到右下角,
其y轴方向与webgl中的y轴方向相反
因此一方面需要对鼠标坐标进行归一化处理,
另一方面需要对y轴反向
1 | const uniforms = { |
1 | uniform vec2 u_mouse; |
shader绘制纹理
纹理的传入使用的也是uniform类型变量,
纹理类型需要限制为sampler2D取样器类型,
gles中的取样方法为texture2D
这个方法接收两个参数:
- sampler2D类型的图片取样器
- vec2类型的图片坐标
在Vertex Shader中,
ThreeJs提供了uv变量作为当前被渲染物体的纹理坐标
可以直接拿来使用
第一步:将纹理图片作为uniform变量传入
1 | import nebula from '../img/nebula.jpg'; |
第二步:将uv纹理坐标使用varing变量从vertex shader传入fragment shader中
1 | varying vec2 v_uv; |
第三步: 在fragment shader中读入传入的纹理图片,然后绘制
1 | uniform vec2 u_resolution; |
GLSL Snacks
视口大小变化导致光标偏移
使用ShaderMaterial创建一个材质,
要求材质的颜色以 距离鼠标的位置 为标准变化,
1 | <script type="x-vertex" id="v-shader"> |
1 | <script type="x-fragment" id="f-shader"> |
需要向Fragment Shader中传入两个vec2类型的uniform变量:
- u_resolution 分辨率
- u_mouse 鼠标坐标
1 | const uniforms = { |
但是在屏幕尺寸改变时,会发现对鼠标位置的计算出现偏差,
对于这个问题,可以在resize函数中更新分辨率变量u_resolution的值:
1 | window.addEventListener('resize', function() { |
纹理镂空效果
这里的纹理镂空指的是对纹理进行动态映射,
根据 片元坐标相对视口位置 来获取 纹理的映射范围,
1 | <script type="x-vertex" id="v-shader"> |
1 | <script type="x-fragment" id="f-shader"> |
使用ShaderMaterial传入uniform变量:
1 | const uniforms = { |
实现镂空效果:
将 纹理镂空 效果转换为 纹理映射 效果,只需要对 texture2D 采样使用的坐标进行修改,
这里传入的uVu(uv)参数,通常代表的是 当前顶点的纹理坐标 :
1 | <script type="x-vertex" id="v-shader"> |
1 | <script type="x-fragment" id="f-shader"> |
纹理动画效果
动画的实现需要引入一个重要的变量:时间
步骤1:实现半透明蒙版效果
首先有一张渐变灰度图:
将这张渐变灰度图映射到目标物体表面,
为了实现半透明的效果:
- 将纹理的r通道映射到alpha通道中
- 开启纹理材质的transparent属性
1 | <script type="x-fragment" id="f-shader"> |
这样能够实现半透明的渐变效果:
步骤2:实现纹理旋转效果
要做出旋转的效果:
- 传入u_time时间参数
- 在纹理映射的时候,对uv坐标进行旋转处理
1 | <script type="x-fragment" id="f-shader"> |
u_time的刷新:
1 | function animate(time) { |
步骤3:实现纹理叠加效果
将gl_FragColor的rgb通道,换成需要映射的纹理即可:
1 | <script type="x-fragment" id="f-shader"> |
过滤效果
两张纹理之间的过渡,
如果不引入任何效果,
只需要使用 mix 函数就能实现。
1 | gl_FragColor = mix(i1Texel, i2Texel, mixRatio); |
想要实现其他过渡效果,
需要引入一张纹理材质图片:
使用 clump 函数将纹理图片映射到 mixer 处理过的图片的alpha通道上:
1 | vec4 transitionTexel = texture2D(transition, vUv); // 纹理 |
需要传入的uniform有这些:
1 | <script type="x-fragment" id="f-shader"> |
Perlin Noise 噪点动画
讲到这里, Wael Yasmina 又甩出来一个很强的网站:
是一位大佬的技术分享网站
噪点,需要在原物体顶点坐标的基础上进行偏移,
能够实现光滑物体表面粗糙不平的效果,
如果这种偏移是震荡连续的,
就能够实现波浪形的动画效果。
噪点的实现需要借助 Parcel Noise:Ashima写好的webgl噪点程序
准备好基础球体,然后就开始…..复制粘贴,
webgl-noise/src下提供了各种噪点程序,
复制粘贴需要的到vertex shader中,
这里以 classicnoise3D 为例,里面提供了两种噪点程序:
- cnoise
- pnoise
选用pnoise噪点程序,最后的vertex shader如下所示:
1 |
|
fragment shader如下:
1 | <script type="x-fragment" id="f-shader"> |
Audio Visualize Animation 音频可视化
音频可视化在threejs中并不难实现,
关键是: 音频值加入噪点的计算
1 | const uniforms = { |
1 | <script type="x-vertex" id="v-shader"> |
1 | <script type="x-vertex" id="v-shader"> |
音频作为一个实体,具有坐标属性,
这里可以将音频加入camera中
1 | // 创建一个AudioListener,放入camera |
准备好音乐,需要创建AudioAnalyser对音频进行解析
1 | const analyser = new THREE.AudioAnalyser(sound, 32) |
在animation中提取出每一帧的平均频率,
作为变量传入vertex shader:
1 | const uniforms = { |
1 | function animate() { |
在顶点着色器中,加入u_frequency计算顶点偏移量
1 | <script type="x-vertex" id="v-shader"> |
引入gui,将r、g、b三个通道的颜色作为float类型变量传入。
1 | const uniforms = { |
1 | <script type="x-fragment" id="f-shader"> |
1 | import { RenderPass } from 'three/examples/jsm/postprocessing/renderpass'; |
受光照影响的ShaderMaterial
想让自定义的ShaderMaterial受光照影响,
有2种方式:
- 在shader程序中经过计算实现光照效果
- 重写其它受光照影响的材质的部分shader
已经有实现第二种方式的库:
npm安装:
1 | npm install three-custom-shader-material |
1 | import CustomShaderMaterial from 'three-custom-shader-material/vanilla' |
CustomShaderMaterial中使用shader,不能仅仅给原本GLSL输出变量进行修改,
还需要修改一些以csm(custom shader material)开头的输出变量,
比如:
- gl_Position → csm_Position
- gl_FragColor → csm_FragColor
1 | const mat = new THREE.ShaderMaterial({ |
1 | <script type="x-vertex" id="v-shader"> |