您们的点赞、评论是我不断向前的动力
1、创建平面
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
camera.position.z = 5
camera.position.y = 5
const wave = new THREE.PlaneGeometry(6, 2, 100)
const material = new THREE.MeshStandardMaterial()
const waveMesh = new THREE.Mesh(wave, material)
waveMesh.rotation.x = -Math.PI / 2;
scene.add(waveMesh)
2、创建材质
import vertexShader from './main.vert'
import fragmentShader from './main.frag'
const material = new THREE.ShaderMaterial({
fragmentShader: fragmentShader,
vertexShader: vertexShader,
uniforms: {
uTime: { value: 0 },
},
side: THREE.DoubleSide,
})
顶点着色器 添加波浪浮动
uniform float uTime;
varying vec2 vUv;
void main(){
vec4 mPosition=modelMatrix*vec4(position,1.);
// mPosition.y+=sin(mPosition.x*3.-uTime)/5.;
// mPosition.y+=cos(mPosition.z*3.-uTime)/10.;
mPosition.y+=sin(mPosition.x*3.)/4.;
mPosition.y+=cos(mPosition.z*3.)/4.;
gl_Position=projectionMatrix*viewMatrix*mPosition;
vUv = uv;
}
片段着色器 添加海浪颜色
varying vec2 vUv;
uniform float uTime;
void main(){
vec3 seaCol = vec3(.1608,.251,.7608);
gl_FragColor=vec4(seaCol,1.);
}
3、片段着色器中创建波浪
参考之前的文章
uv 范围是 0~1 即 x y 最小值为0 最大值为1 比值 1:1
我们创建的平面 x为6 y为2 xy的比值 3:1
为了设置显示为同样的比例 st*=vec2(6.,2.);
使用 distance距离方法计算 xy 到 0点的距离
距离大于1 的像素都显示为白色 是因为超过1的值都为显示白色
vec2 st=vUv;
st*=vec2(6.,2.);
float strength=1.;
float d=distance(st,vec2(0.));
vec3 seaCol = vec3(d);
gl_FragColor=vec4(seaCol,1.);
4、对超出值超出1的像素做处理
利用 fract 函数对 d 取小数
会有一个扩散的效果 扩散的起点在(0.0, 0.0)左下角
void main(){
vec2 st=vUv;
st*=vec2(6.,2.);
float strength=1.;
float d=distance(st,vec2(0.));
strength=fract(d);
vec3 seaCol = vec3(strength);
gl_FragColor=vec4(seaCol,1.);
}
5、移动扩散的中心位置
在改变里uv比例之前 通过st - 0.5 把uv 范围修改为 -0.5~0.5
把扩散的起点移动到中心
void main(){
vec2 st=vUv;
st.y-=.5;
st.x-=.5;
st*=vec2(6.,2.);
float strength=1.;
float d=distance(st,vec2(0.));
strength=fract(d);
vec3 seaCol = vec3(strength);
gl_FragColor=vec4(seaCol,1.);
这时再将 d 减去传入的时间参数就可以让图形动起来了
strength=fract(d-uTime);
只取到右边的波浪效果 将x轴 向左偏移 st.x-=.5; => st.x +=.3;
6、改变对比度
通过pow 幂运算 和 乘以1.5 提高光暗对比度 并抽离成 wave 方法
float wave(vec2 st){
st.y-=.5;
st.x+=.3;
st*=vec2(6.,2.);
float strength=1.;
float d=distance(st,vec2(0.));
// strength=fract(d-uTime);
strength=fract(d);
strength=pow(strength+.05,3.);
strength*=1.5;
return strength;
}
void main(){
float strength=wave(vUv);
vec3 seaCol=vec3(strength);
// vec3 seaCol = vec3(.1608,.251,.7608) + strength;
gl_FragColor=vec4(seaCol,1.);
}
7、网格噪声
简单介绍一下voronoi noise 网格噪声
把空间分割成网格,计算每个像素点到它所在网格中的那个特征点的距离。 将图像拆分3*3的小格子
定义特征点(0.5,0.5)
计算当前网格中的像素到特征点的距离
如何计算像素到特征点距离?
#ifdef GL_ES
precision mediump float;
#endif
uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;
void main(){
vec2 st=gl_FragCoord.xy/u_resolution.xy;
// st.x *= u_resolution.x/u_resolution.y;
vec3 color=vec3(.0);
st*=3.;
vec2 i_st=floor(st);
vec2 f_st=fract(st);
vec2 point=vec2(.0);
// 网格中的特征点
point=.5;
// 网格中的像素到特征点的距离
vec2 diff=point-f_st;
float dist=length(diff);
color+=dist;
// 绘制中心点
color+=1.-step(.02,dist);
// 绘制网格
color.r+=step(.98,f_st.x)+step(.98,f_st.y);
gl_FragColor=vec4(color,1.);
}
random2 会返回 0.0~1.0之间的小数
通过random与sin函数 让特征点在当前网格偏移而不超出网格范围
vec2 random2(vec2 p){
return fract(sin(vec2(dot(p,vec2(127.1,311.7)),dot(p,vec2(269.5,183.3))))*43758.5453);
}
void main(){
vec2 st=gl_FragCoord.xy/u_resolution.xy;
// st.x *= u_resolution.x/u_resolution.y;
vec3 color=vec3(.0);
st*=3.;
vec2 i_st=floor(st);
vec2 f_st=fract(st);
vec2 point=random2(i_st); // 新增
// 网格中的特征点
point=.5+.5*sin(6.2831*point); // 修改
// 网格中的像素到特征点的距离
vec2 diff=point-f_st;
float dist=length(diff);
color+=dist;
// 绘制中心点
color+=1.-step(.02,dist);
// 绘制网格
color.r+=step(.98,f_st.x)+step(.98,f_st.y);
gl_FragColor=vec4(color,1.);
}
需要增加每个网格内像素与相邻网格的特征点之间的距离
通过变两个for循环遍历,计算当前像素到其他相邻八个网格的特征点的距离,取最小值
void main(){
vec2 st=gl_FragCoord.xy/u_resolution.xy;
// st.x *= u_resolution.x/u_resolution.y;
vec3 color=vec3(.0);
st*=3.;
vec2 i_st=floor(st);
vec2 f_st=fract(st);
float m_dist=1.;
for(int y=-1;y<=1;y++){
for(int x=-1;x<=1;x++){
// 相邻网格
vec2 neighbor=vec2(float(x),float(y));
vec2 point=random2(i_st+neighbor);
// 网格中的特征点
point=.5+.5*sin(6.2831*point);// 修改
// 网格中的像素到特征点的距离
vec2 diff = neighbor + point - f_st;
float dist=length(diff);
// Keep the closer distance
m_dist=min(m_dist,dist);
}
}
vec2 point=random2(i_st);// 新增
color+=m_dist;
// 绘制中心点
color+=1.-step(.02,m_dist);
// 绘制网格
color.r+=step(.98,f_st.x)+step(.98,f_st.y);
gl_FragColor=vec4(color,1.);
}
8、第二种方式实现不规则网格效果
多个旋转变形后的基础距离图叠加
vec2 rotate(vec2 uv,float rotation,vec2 mid){
return vec2(
cos(rotation)*(uv.x-mid.x)+sin(rotation)*(uv.y-mid.y)+mid.x,
cos(rotation)*(uv.y-mid.y)-sin(rotation)*(uv.x-mid.x)+mid.y
);
}
float voronoi(vec2 st){
vec2 i_st=floor(st);
vec2 f_st=fract(st);
vec2 point=vec2(.5);
vec2 diff=point-f_st;
float dist=length(diff)+.1;
return dist;
}
uv*=4.;
float dist1=voronoi(uv*vec2(1.1,1.3)+vec2(.0,.0/3.));
float dist2=voronoi(rotate(uv*vec2(1.1,1.3),PI*.25,vec2(.5)));
float dist3=voronoi(rotate(uv*vec2(1.5,.9),PI*-.13,vec2(.5)));
叠加取最小值
dist=min(dist1,dist2);
dist=min(dist,dist3);
dist = pow(dist,2.);
#ifdef GL_ES
precision mediump float;
#endif
#define PI 3.14159
uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;
vec2 rotate(vec2 uv,float rotation,vec2 mid){
return vec2(
cos(rotation)*(uv.x-mid.x)+sin(rotation)*(uv.y-mid.y)+mid.x,
cos(rotation)*(uv.y-mid.y)-sin(rotation)*(uv.x-mid.x)+mid.y
);
}
float voronoi(vec2 st){
vec2 i_st=floor(st);
vec2 f_st=fract(st);
vec2 point=vec2(.5);
vec2 diff=point-f_st;
float dist=length(diff)+.1;
return dist;
}
void main(){
vec2 st=gl_FragCoord.xy/u_resolution.xy;
st.x*=u_resolution.x/u_resolution.y;
vec3 color=vec3(.0);
vec2 uv=st;
uv*=4.;
// uv+=u_time/15.;
float dist=1.;
// 移动的
// float dist1=voronoi(uv*vec2(1.1,1.3)+vec2(.0,u_time/3.));
// float dist2=voronoi(rotate(uv*vec2(1.1,1.3),PI*.25,vec2(.5)));
// float dist3=voronoi(rotate(uv*vec2(1.5,.9)+u_time/3.,PI*-.13,vec2(.5)));
float dist1=voronoi(uv*vec2(1.1,1.3)+vec2(.0,.0/3.));
float dist2=voronoi(rotate(uv*vec2(1.1,1.3),PI*.25,vec2(.5)));
float dist3=voronoi(rotate(uv*vec2(1.5,.9),PI*-.13,vec2(.5)));
dist=min(dist1,dist2);
dist=min(dist,dist3);
// dist = pow(dist,2.);
// dist = dist1;
// dist = dist2;
// dist = dist3;
color+=dist;
color+=vec3(.1529,.251,.8235);
gl_FragColor=vec4(color,.8);
}
9、另一种voronoi图
前面是求像素到特征点 下面是求像素到边缘最小值
#ifdef GL_ES
precision mediump float;
#endif
uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;
vec2 random2(vec2 p){
return fract(sin(vec2(dot(p,vec2(127.1,311.7)),dot(p,vec2(269.5,183.3))))*43758.5453);
}
vec3 voronoi(vec2 st){
vec2 n = floor(st);
vec2 f = fract(st);
vec2 mg, mr;
float md = 8.0;
for (int j= -1; j <= 1; j++) {
for (int i= -1; i <= 1; i++) {
vec2 g = vec2(float(i),float(j));
vec2 o = random2( n + g );
// o = 0.5 + 0.5*sin( u_time + 6.2831*o );
o = 0.5 + 0.5*sin( 6.2831*o );
vec2 r = g + o - f;
float d = dot(r,r);
if( d<md ) {
md = d;
mr = r;
mg = g;
}
}
}
md = 8.0;
for (int j= -2; j <= 2; j++) {
for (int i= -2; i <= 2; i++) {
vec2 g = mg + vec2(float(i),float(j));
vec2 o = random2( n + g );
// o = 0.5 + 0.5*sin( u_time + 6.2831*o );
o = 0.5 + 0.5*sin( 6.2831*o );
vec2 r = g + o - f;
md = min(md, dot( 0.5*(mr+r), normalize(r-mr) ));
}
}
return vec3(md, mr);
}
void main(){
vec2 st=gl_FragCoord.xy/u_resolution.xy;
// st.x *= u_resolution.x/u_resolution.y;
vec3 color=vec3(.0);
st*=3.;
vec2 i_st=floor(st);
vec2 f_st=fract(st);
vec3 c=voronoi(st);
color=c.x*vec3(1.);
// 绘制网格
color.r+=step(.98,f_st.x)+step(.98,f_st.y);
gl_FragColor=vec4(color,1.);
}
color=mix(vec3(0.8431, 0.5412, 0.0196),vec3(0.5804, 0.251, 0.0118),c.x);
color=mix(vec3(1.),color,smoothstep(.01,.1,c.x));
10、海浪拖尾
使用到前面提到的网格噪声 抽离一个新的网格噪声方法
float gridNoise(vec2 st){
st*=vec2(5.,5.);
vec2 i_st=floor(st);
vec2 f_st=fract(st);
float m_dist=1.;
for(int y=-1;y<=1;y++){
for(int x=-1;x<=1;x++){
vec2 neighbor=vec2(float(x),float(y));
vec2 point=random2(i_st+neighbor);
point=.5+.5*sin(6.2831*point);
vec2 diff=neighbor+point-f_st;
float dist=length(diff);
m_dist=min(m_dist,dist);
}
}
m_dist=smoothstep(.1,.6,m_dist*.8);
return m_dist;
}
修改一下频率
float gridNoise(vec2 st){
st*=vec2(1.,5.); // 修改
st*=20.; // 新增
.......
return m_dist;
}
11、与前面的扩散波叠加
void main(){
float seaWave=wave(vUv);
float waveTail=gridNoise(vUv);
waveTail*=seaWave;
waveTail+=seaWave;
vec3 seaCol=vec3(waveTail);
// vec3 seaCol=vec3(.1608,.251,.7608)+waveTail;
// vec3 seaCol = vec3(.1608,.251,.7608);
gl_FragColor=vec4(seaCol,1.);
}
12、海浪头部不规则
这里利用到 fbm 分形布朗运动(Fractal Brownian Motion)
// Author @patriciogv - 2015
// http://patriciogonzalezvivo.com
#ifdef GL_ES
precision mediump float;
#endif
uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;
float random (in vec2 st) {
return fract(sin(dot(st.xy,
vec2(12.9898,78.233)))*
43758.5453123);
}
// Based on Morgan McGuire @morgan3d
// https://www.shadertoy.com/view/4dS3Wd
float noise (in vec2 st) {
vec2 i = floor(st);
vec2 f = fract(st);
// Four corners in 2D of a tile
float a = random(i);
float b = random(i + vec2(1.0, 0.0));
float c = random(i + vec2(0.0, 1.0));
float d = random(i + vec2(1.0, 1.0));
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(a, b, u.x) +
(c - a)* u.y * (1.0 - u.x) +
(d - b) * u.x * u.y;
}
#define OCTAVES 6
float fbm (in vec2 st) {
// Initial values
float value = 0.0;
float amplitude = .5;
float frequency = 0.;
//
// Loop of octaves
for (int i = 0; i < OCTAVES; i++) {
value += amplitude * noise(st);
st *= 2.;
amplitude *= .5;
}
return value;
}
void main() {
vec2 st = gl_FragCoord.xy/u_resolution.xy;
st.x *= u_resolution.x/u_resolution.y;
// st.x += u_time/5.;
vec3 color = vec3(0.0);
color += fbm(st*6.0);
gl_FragColor = vec4(color,1.0);
}
生成海浪时使用fbm返回结果 对uv x轴做偏移
float wave(vec2 st){
float fbmdata=fbm(st*6.); //新增
st.y-=.5;
// st.x+=.2; //新增
st.x+=.2+fbmdata/7.; //新增
st*=vec2(6.,2.);
float strength=1.;
float d=distance(st,vec2(0.));
strength=fract(d-uTime);
strength=fract(d);
strength=pow(strength,3.);
strength=min(1.,strength);
strength*=1.2;
return strength;
}
可以调整偏移量
st.x+=.2+fbmdata/10.;
13、上色+取消注释时间参数+对样式做些调整 让效果动起来
varying vec2 vUv;
uniform float uTime;
#define PI 3.14159265359;
vec2 random2(vec2 p){
return fract(sin(vec2(dot(p,vec2(127.1,311.7)),dot(p,vec2(269.5,183.3))))*43758.5453);
}
float random(vec2 p){
return fract(sin(dot(p,vec2(12.9898,78.233)))*43758.5453123);
}
float noise(vec2 st){
vec2 i=floor(st);
vec2 f=fract(st);
float a=random(i);
float b=random(i+vec2(1.,0.));
float c=random(i+vec2(0.,1.));
float d=random(i+vec2(1.,1.));
vec2 u=f*f*(3.-2.*f);
return mix(a,b,u.x)+
(c-a)*u.y*(1.-u.x)+
(d-b)*u.x*u.y;
}
#define OCTAVES 6
float fbm(vec2 st){
st.y -= uTime/4.;
float value=0.;
float amplitude=.5;
float frequency=0.;
for(int i=0;i<OCTAVES;i++){
value+=amplitude*noise(st);
st*=2.;
amplitude*=.5;
}
return value;
}
float wave(vec2 st){
float fbmdata=fbm(st*6.);
st.y-=.5;
st.x+=.2;
st.x+=+fbmdata/10.;
st*=vec2(6.,2.);
float strength=1.;
float d=distance(st,vec2(0.));
strength=fract(d-uTime);
// strength=fract(d);
strength=pow(strength,3.);
strength=min(1.,strength);
strength*=1.2;
return strength;
}
float gridNoise(vec2 st){
st*=vec2(2.,5.); // 修改
st*=5.; // 新增
vec2 i_st=floor(st);
vec2 f_st=fract(st);
float m_dist=1.;
for(int y=-1;y<=1;y++){
for(int x=-1;x<=1;x++){
vec2 neighbor=vec2(float(x),float(y));
vec2 point=random2(i_st+neighbor);
point=.5+.5*sin(6.2831*point);
vec2 diff=neighbor+point-f_st;
float dist=length(diff);
m_dist=min(m_dist,dist);
}
}
m_dist=smoothstep(.1,.7,m_dist*.8);
return m_dist;
}
void main(){
float seaWave=wave(vUv);
float waveTail=gridNoise(vUv);
waveTail*=seaWave;
waveTail+=seaWave;
waveTail=pow(waveTail,2.);
// vec3 seaCol=vec3(waveTail);
vec3 seaCol=vec3(.1608,.251,.7608)+waveTail;
// vec3 seaCol = vec3(.1608,.251,.7608);
gl_FragColor=vec4(seaCol,1.);
}
<script setup lang="ts">
import { onBeforeUnmount, onMounted } from 'vue'
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import vertexShader from './main.vert'
import fragmentShader from './main.frag'
import vertexShader2 from './main02.vert'
import fragmentShader2 from './main02.frag'
onMounted(() => {})
let scene = new THREE.Scene()
// 天空盒
const urls = ['/skybox/px.jpg', '/skybox/nx.jpg', '/skybox/py.jpg', '/skybox/ny.jpg', '/skybox/pz.jpg', '/skybox/nz.jpg']
const cubeLoader = new THREE.CubeTextureLoader()
scene.background = cubeLoader.load(urls)
const renderer = new THREE.WebGLRenderer({
antialias: true, // 抗锯齿
// alpha: true
})
renderer.setScissorTest(true)
renderer.setClearColor(0x000000)
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
camera.position.z = 1
camera.position.y = 6
// 添加相机控制器
new OrbitControls(camera, renderer!.domElement)
// 设置环境光
const ambientLight = new THREE.AmbientLight(0xffffff) // 环境光
scene.add(ambientLight)
// 平行光
const directionalLight = new THREE.DirectionalLight(0x000000, 1)
// 设置光源的方向
directionalLight.position.set(50, 50, 50)
scene.add(directionalLight)
// 波浪
const wave = new THREE.PlaneGeometry(6, 2, 100, 100)
const material = new THREE.ShaderMaterial({
fragmentShader: fragmentShader,
vertexShader: vertexShader,
uniforms: {
uTime: { value: 0 },
// uColor: new THREE.Uniform(new THREE.Color('#70c1ff')),
},
side: THREE.DoubleSide,
// transparent: true,
// depthWrite: false,
// depthTest: false,
})
const waveMesh = new THREE.Mesh(wave, material)
waveMesh.rotation.x = -Math.PI / 2
scene.add(waveMesh)
// 水面
const water = new THREE.PlaneGeometry(2, 2, 100, 100)
const material2 = new THREE.ShaderMaterial({
fragmentShader: fragmentShader2,
vertexShader: vertexShader2,
uniforms: {
uTime: { value: 0 },
// uColor: new THREE.Uniform(new THREE.Color('#70c1ff')),
},
side: THREE.DoubleSide,
transparent: true,
// depthWrite: false,
// depthTest: false,
})
const waterMesh = new THREE.Mesh(water, material2)
waterMesh.rotation.x = -Math.PI / 2
waterMesh.position.z = 3
scene.add(waterMesh)
const render = (): void => {
renderer.setScissor(0, 0, window.innerWidth, window.innerHeight)
renderer.render(scene, camera)
}
const clock = new THREE.Clock()
const animate = (): void => {
requestAnimationFrame(animate)
// mesh.rotation.x += 0.01
// mesh.rotation.y += 0.01
material.uniforms.uTime.value = clock.getElapsedTime()
material2.uniforms.uTime.value = clock.getElapsedTime()
render()
}
animate()
const destroyThreejs = () => {
try {
renderer.dispose()
renderer.forceContextLoss()
// scene.traverse((child) => {
// if (child.material) {
// child.material.dispose();
// }
// if (child.geometry) {
// child.geometry.dispose();
// }
// child = null;
// })
// scene = null;
} catch (e) {
console.error('Failed to destroy threejs', e)
}
}
onBeforeUnmount(() => {
destroyThreejs()
document.body.removeChild(renderer.domElement)
})
</script>
<template>
<div class="demo"></div>
</template>
<style scoped></style>
代码地址
xzw199509/shader-demo (github.com)
xzw199509/xiao-three-template (github.com)