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换成CircleGeometry
let 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
欢迎大家来群里交流技术