2D SDF 移动与合并图形
- 前五篇地址,建议按顺序学习
- 本篇使用到的初始代码
- 减小扩散范围
- clamp函数
- 修改maxDistance来修改扩散范围
- 移动扩散中心
- 添加第二个扩散点
- 降低点的同步率
- 调整参数来优化效果
- 添加更多扩散点
- 完整源码
- 如有不明白的,可以在下方留言或者加群
前五篇地址,建议按顺序学习
【Threejs进阶教程-着色器篇】1. Shader入门(ShadertoyShader和ThreejsShader入门)
【Threejs进阶教程-着色器篇】2. Uniform的基本用法与Uniform的调试
【Threejs进阶教程-着色器篇】3. Uniform的基本用法2与基本地球昼夜效果
【Threejs进阶教程-着色器篇】4. 2D SDF(一) SDF的基本用法
【Threejs进阶教程-着色器篇】5. 2D SDF(二)圆形波纹效果
本篇使用到的初始代码
【Threejs进阶教程-着色器篇】5. 2D SDF(二)圆形波纹效果
请在此文的最后,自行复制粘贴对应的上一篇完整代码,本篇将会以上一篇的代码为基础,扩展当前的效果
减小扩散范围
因为现在我们是用透明度来控制扩散效果的,所以,我们本次的修改也主要以透明度为准
我们现在的透明度情况,是以中心为准,每一圈的内圈起点处透明度最低,透明度为0,每一圈的外圈终点处,透明度为1,我们是用fract限制了这个值的最大值只能为1,但是并没有限制半径,所以我们从半径下手
- 先设定一个扩散半径
- 然后计算当前的auv值到扩散中心的距离,与半径的比值,
- 用这个比值,与最终计算的alp值相乘
以默认值为例,我们现阶段的默认的iFreq的值为10.0,也就是说,在图形的四个边缘处到中心的距离,等于半径,当前值为10,四个角上的顶点处,距离中心的距离为 sqrt(10 * 10),所以,我们可以先设置一个低于10的值
<!-- 片元着色器代码 -->
<script type="x-shader/x-fragment" id="fragmentShader">varying vec2 vUv;uniform float iTime;uniform float iFreq;uniform vec3 iColor;uniform float iSpeed;uniform float iPower;float sdCircle( vec2 p, float r ){return length(p) - r;}void main(){vec2 aUv = (vUv - 0.5) * iFreq;float alp = fract(sdCircle(aUv, 0.5) - iTime * iSpeed);alp = pow(alp,iPower);//对扩散的光波pow可以减少光波有颜色的部分的宽度float maxDistance = 5.0;//扩散半径alp *= distance(aUv,vec2(0.0)) / maxDistance;gl_FragColor = vec4(iColor,alp);}
</script>
但是,这个效果明显是不对的,不仅半径没有得到限制,且中心变的透明化了
既然是中心变的透明化了,说明最终的计算值已经是0了,那么想让中心实色化,就用1.0 - 计算结果即可,但是,因为计算的值,可能会小于1,那么,我们做个限制,让它取值为0~1即可
<!-- 片元着色器代码 -->
<script type="x-shader/x-fragment" id="fragmentShader">varying vec2 vUv;uniform float iTime;uniform float iFreq;uniform vec3 iColor;uniform float iSpeed;uniform float iPower;float sdCircle( vec2 p, float r ){return length(p) - r;}void main(){vec2 aUv = (vUv - 0.5) * iFreq;float alp = fract(sdCircle(aUv, 0.5) - iTime * iSpeed);alp = pow(alp,iPower);//对扩散的光波pow可以减少光波有颜色的部分的宽度float maxDistance = 5.0;//扩散半径alp *= 1.0 - distance(aUv,vec2(0.0)) / maxDistance;//一定注意,alp是float类型的,这里写大于的时候,后面的数字必须是float类型,不能是1,不然threejs会报错if(alp > 1.0){alp = 1.0;}else if(alp < 0.0){alp = 0.0;}gl_FragColor = vec4(iColor,alp);}
</script>
clamp函数
不过,上面的写法是可以优化的,glsl内置了clamp函数,来取代上面的if-else写法
//原写法if(alp > 1.0){alp = 1.0;}else if(alp < 0.0){alp = 0.0;}
//新写法alp = clamp(alp,0.0,1.0);
clamp函数不仅对float类型数据有效,对向量也可用,截图截取自《webgl编程指南》431页
注意使用的时候,看清楚函数的数据类型,以及,min值不要大于max值
修改maxDistance来修改扩散范围
通过修改maxDistance到2.0,我们就得到了这样的一个效果
移动扩散中心
既然我们可以更改扩散范围,那么,我们亦可更改扩散的中心点
在片元着色器中,修改 vUv - 0.5的位置为: vUv - vec2(0.2,0.5),即可让扩散中心向左侧偏移
这里的vec2(0.2,0.5),本质上就是扩散中心的位置
//原代码vec2 aUv = (vUv - 0.5) * iFreq;//新代码vec2 aUv = (vUv - vec2(0.2,0.5)) * iFreq;
但是,我们这样来搞就麻烦了,所以我们现在需要封装一个方法出来,为了方便后续的开发内容
<!-- 片元着色器代码 -->
<script type="x-shader/x-fragment" id="fragmentShader">varying vec2 vUv;uniform float iTime;uniform float iFreq;uniform vec3 iColor;uniform float iSpeed;uniform float iPower;float sdCircle( vec2 p, float r ){return length(p) - r;}//三个参数分别为: 扩散中心,基本半径,最大半径float createWave(vec2 center,float radius,float maxDistance){vec2 aUv = (vUv - center) * iFreq;float alp = fract(sdCircle(aUv,0.5) - iTime * iSpeed);alp = pow(alp,iPower);alp *= 1.0 - distance(aUv,vec2(0.0)) / maxDistance;alp = clamp(alp,0.0,1.0);return alp;}void main(){float alp = createWave(vec2(0.2,0.5),0.5,2.0);gl_FragColor = vec4(iColor,alp);}
</script>
添加第二个扩散点
第二个扩散点其实非常简单,就是利用一下上面我们封装好的函数,直接追加一份即可
<!-- 片元着色器代码 -->
<script type="x-shader/x-fragment" id="fragmentShader">varying vec2 vUv;uniform float iTime;uniform float iFreq;uniform vec3 iColor;uniform float iSpeed;uniform float iPower;float sdCircle( vec2 p, float r ){return length(p) - r;}//三个参数分别为: 扩散中心,基本半径,最大半径float createWave(vec2 center,float radius,float maxDistance){vec2 aUv = (vUv - center) * iFreq;float alp = fract(sdCircle(aUv,0.5) - iTime * iSpeed);alp = pow(alp,iPower);alp *= 1.0 - distance(aUv,vec2(0.0)) / maxDistance;alp = clamp(alp,0.0,1.0);return alp;}void main(){float alp = createWave(vec2(0.2,0.5),0.5,2.0);alp += createWave(vec2(0.8,0.5),0.5,2.0);alp += createWave(vec2(0.5,0.2),0.5,2.0);alp += createWave(vec2(0.5,0.8),0.5,2.0);gl_FragColor = vec4(iColor,alp);}
</script>
为什么直接添加就能出效果?
非常简单,那个位置的透明度为0,0加任何的数据,都等于任何数据
降低点的同步率
现在的四个点,同步率非常的高,我们只需要在iTime上做手脚,让他们的初始速度发生变化即可
- 在封装的createWave方法中,添加参数 waveValue
- 调用方法时,传入不同的值
<!-- 片元着色器代码 -->
<script type="x-shader/x-fragment" id="fragmentShader">varying vec2 vUv;uniform float iTime;uniform float iFreq;uniform vec3 iColor;uniform float iSpeed;uniform float iPower;float sdCircle( vec2 p, float r ){return length(p) - r;}//三个参数分别为: 扩散中心,基本半径,最大半径float createWave(vec2 center,float radius,float maxDistance, float waveValue){vec2 aUv = (vUv - center) * iFreq;float alp = fract(sdCircle(aUv,radius) - iTime * iSpeed + waveValue);alp = pow(alp,iPower);alp *= 1.0 - distance(aUv,vec2(0.0)) / maxDistance;alp = clamp(alp,0.0,1.0);return alp;}void main(){float alp = createWave(vec2(0.2,0.5),0.5,2.0,0.2);alp += createWave(vec2(0.8,0.5),0.5,2.0,0.4);alp += createWave(vec2(0.5,0.2),0.5,2.0,0.6);alp += createWave(vec2(0.5,0.8),0.5,2.0,0.8);gl_FragColor = vec4(iColor,alp);}
</script>
调整参数来优化效果
- 将maxDistance抽出,使用unifrom + lil.gui来控制
- 调整参数到效果接近想要的效果
添加更多扩散点
首先要说明的是,在shader中,没有随机数!随机只能借助noise函数来实现
现阶段,暂时仅建议多手敲几个点到图像上,简单看一下效果即可,在后续讲到noise函数的时候,这里还会再次提及
<!-- 片元着色器代码 -->
<script type="x-shader/x-fragment" id="fragmentShader">varying vec2 vUv;uniform float iTime;uniform float iFreq;uniform vec3 iColor;uniform float iSpeed;uniform float iPower;uniform float maxDistance;float sdCircle( vec2 p, float r ){return length(p) - r;}//三个参数分别为: 扩散中心,基本半径,最大半径float createWave(vec2 center,float radius, float waveValue){vec2 aUv = (vUv - center) * iFreq;float alp = fract(sdCircle(aUv,radius) - iTime * iSpeed + waveValue);alp = pow(alp,iPower);alp *= 1.0 - distance(aUv,vec2(0.0)) / maxDistance;alp = clamp(alp,0.0,1.0);return alp;}void main(){float alp = createWave(vec2(0.2,0.5),0.5,0.1);alp += createWave(vec2(0.12,0.35),0.5,0.2);alp += createWave(vec2(0.6,0.33),0.5,0.3);alp += createWave(vec2(0.49,0.24),0.5,0.4);alp += createWave(vec2(0.87,0.14),0.5,0.5);alp += createWave(vec2(0.35,0.88),0.5,0.6);alp += createWave(vec2(0.23,0.34),0.5,0.7);alp += createWave(vec2(0.65,0.44),0.5,0.8);alp += createWave(vec2(0.57,0.12),0.5,0.9);alp += createWave(vec2(0.88,0.92),0.5,0.9);alp += createWave(vec2(0.75,0.72),0.5,0.9);gl_FragColor = vec4(iColor,alp);}
</script>
完整源码
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><style>body{width:100vw;height: 100vh;overflow: hidden;margin: 0;padding: 0;border: 0;}</style>
</head>
<body><script type="importmap">{"imports": {"three": "../three/build/three.module.js","three/addons/": "../three/examples/jsm/"}}</script><script type="x-shader/x-vertex" id="vertexShader">varying vec2 vUv;void main(){vUv = vec2(uv.x,uv.y);vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );gl_Position = projectionMatrix * mvPosition;gl_Position = projectionMatrix * modelMatrix * viewMatrix * vec4( position, 1.0 );}</script><!-- 片元着色器代码 -->
<script type="x-shader/x-fragment" id="fragmentShader">varying vec2 vUv;uniform float iTime;uniform float iFreq;uniform vec3 iColor;uniform float iSpeed;uniform float iPower;uniform float maxDistance;float sdCircle( vec2 p, float r ){return length(p) - r;}//三个参数分别为: 扩散中心,基本半径,最大半径float createWave(vec2 center,float radius, float waveValue){vec2 aUv = (vUv - center) * iFreq;float alp = fract(sdCircle(aUv,radius) - iTime * iSpeed + waveValue);alp = pow(alp,iPower);alp *= 1.0 - distance(aUv,vec2(0.0)) / maxDistance;alp = clamp(alp,0.0,1.0);return alp;}void main(){float alp = createWave(vec2(0.2,0.5),0.5,0.1);alp += createWave(vec2(0.12,0.35),0.5,0.2);alp += createWave(vec2(0.6,0.33),0.5,0.3);alp += createWave(vec2(0.49,0.24),0.5,0.4);alp += createWave(vec2(0.87,0.14),0.5,0.5);alp += createWave(vec2(0.35,0.88),0.5,0.6);alp += createWave(vec2(0.23,0.34),0.5,0.7);alp += createWave(vec2(0.65,0.44),0.5,0.8);alp += createWave(vec2(0.57,0.12),0.5,0.9);alp += createWave(vec2(0.88,0.92),0.5,0.9);alp += createWave(vec2(0.75,0.72),0.5,0.9);gl_FragColor = vec4(iColor,alp);}
</script><script type="module">import * as THREE from "../three/build/three.module.js";import {OrbitControls} from "../three/examples/jsm/controls/OrbitControls.js";import {GUI} from "../three/examples/jsm/libs/lil-gui.module.min.js";window.addEventListener('load',e=>{init();addMesh();render();})let scene,renderer,camera;let orbit;function init(){scene = new THREE.Scene();renderer = new THREE.WebGLRenderer({alpha:true,antialias:true});renderer.setSize(window.innerWidth,window.innerHeight);document.body.appendChild(renderer.domElement);camera = new THREE.PerspectiveCamera(50,window.innerWidth/window.innerHeight,0.1,2000);camera.add(new THREE.PointLight());camera.position.set(15,15,15);scene.add(camera);orbit = new OrbitControls(camera,renderer.domElement);orbit.enableDamping = true;scene.add(new THREE.GridHelper(10,10));}let uniforms = {iTime:{value:0},iFreq:{value:21},//频率,光波圈数iColor:{value:new THREE.Color('#d1d1d1')},//光波颜色iSpeed:{value:2},//扩散速度iPower:{value:2.5},//光波强度maxDistance:{value:6.37},//最大扩散半径}function addMesh() {//常规情况下,planeGeometry,circleGeometry是与z轴垂直的,改成与y轴垂直,只需要沿着x轴让几何体旋转-90度即可//let geometry = new THREE.PlaneGeometry(10,10).rotateX(-Math.PI/2);//为了让效果更适用于圆形,我们从方形的planeGeometry换成CircleGeometrylet geometry = new THREE.CircleGeometry(5,32).rotateX(-Math.PI/2);let material = new THREE.ShaderMaterial({uniforms,vertexShader:document.getElementById('vertexShader').textContent,fragmentShader:document.getElementById('fragmentShader').textContent,transparent:true})let mesh = new THREE.Mesh(geometry,material);scene.add(mesh);let param = {color:"#d1d1d1" //lil.gui读取threejs的颜色比较麻烦,个人习惯在这里单独写一个来控制};let gui = new GUI();gui.add(uniforms.iFreq,'value',0,50,0.01).name('光波圈数');gui.add(uniforms.iPower,'value',0,50,0.01).name('光波强度');gui.add(uniforms.iSpeed,'value',0,50,0.01).name('扩散速度');gui.add(uniforms.maxDistance,'value',0,50,0.01).name('最大扩散半径');gui.addColor(param,'color').name('光波颜色').onChange(v=>{uniforms.iColor.value = new THREE.Color(v);})}function render() {uniforms.iTime.value += 0.01;renderer.render(scene,camera);orbit.update();requestAnimationFrame(render);}</script>
</body>
</html>
如有不明白的,可以在下方留言或者加群
如有其他不懂的问题,可以在下方留言,也可以加入qq群咨询,
Web3D+GIS开源社区为新群,群内相对来说学习气氛良好,群号131995948
本人的群,群号867120877
欢迎大家来群里交流技术