2D SDF 圆形波纹效果
前四篇地址,请按顺序学习
【Threejs进阶教程-着色器篇】1. Shader入门(ShadertoyShader和ThreejsShader入门)
【Threejs进阶教程-着色器篇】2. Uniform的基本用法与Uniform的调试
【Threejs进阶教程-着色器篇】3. Uniform的基本用法2与基本地球昼夜效果
【Threejs进阶教程-着色器篇】4. 2D SDF(一) SDF的基本用法
本博客使用模板代码中的Shader模板
请各位自取【模板代码】用于编写Threejs Demo的模板代码
绘制第一圈波纹
上一篇中我们讲到了圆形的sdf,我们依然在片元着色器中引入sdCircle这个函数
<script type="x-shader/x-fragment" id="fragmentShader">
varying vec2 vUv;
float sdCircle( vec2 p, float r ){
return length(p) - r;
}
void main(){
gl_FragColor = vec4(1.0,0.0,0.0,1.0);
}
</script>
一般来说,咱们看到的波纹效果,应该是这样的
我们先考虑绘制第一圈波纹,可以看出,越靠近边缘的地方,就越亮,越靠近中心的地方就越暗,因为在项目上,波纹基本上都是透明的,所以本次我们以操作透明度的方式来开发这个效果
<script type="x-shader/x-fragment" id="fragmentShader">
varying vec2 vUv;
float sdCircle( vec2 p, float r ){
return length(p) - r;
}
void main(){
vec2 aUv = vUv - 0.5;
vec3 color = vec3(1.0,0.0,0.0);
float alp = sdCircle(aUv, 0.5);
gl_FragColor = vec4(color,alp);
}
</script>
首先我们先分析上面的代码
先对vUv - 0.5,将坐标系平移到中心
然后创建一个三维向量,作为颜色来使用
然后我们使用sdf函数,计算透明度,计算结果如上图所示
根据被移动后的坐标系,我们可以推测出,越接近圆心的,length的取值会越小,再-0.1的时候,就会越小,透明度小于0时会显示为0,所以一直到边缘处,u=0.5,v=0.5的时候,最后计算下来的透明度约为 sqrt( 0.5 * 0.5 * 2 ) ≈ 0.707
绘制多圈波纹
那么,我们可以此时,通过把uv * 4.0,来扩大坐标系,达到调整边缘的效果
//vec2 aUv = vUv - 0.5; //旧代码
vec2 aUv = (vUv - 0.5) * 4.0;//新代码
但是这样我们又有了新问题,此时坐标系变成了 uv的最大值 = 0.5 * 4 = 2.0,那么,在uv都为2.0的地方,透明度是多少呢? 计算一下就知道了 sqrt(2 * 2 * 2) = 2.82,透明度最大值是1,超过1了按照1来显示,那么,我们可以考虑只取小数部分
fract函数
fract只取小数部分,可以作为周期函数来使用
fract函数的图像大致如图所示,如果要做类似波纹的效果,且不是圆滑的变化规则,可以使用fract函数来做
我们接下来用fract函数来处理我们的效果
<!-- 片元着色器内容 -->
<script type="x-shader/x-fragment" id="fragmentShader">
varying vec2 vUv;
float sdCircle( vec2 p, float r ){
return length(p) - r;
}
void main(){
vec2 aUv = (vUv - 0.5) * 4.0;
vec3 color = vec3(1.0,0.0,0.0);
float alp = fract(sdCircle(aUv, 0.5));
gl_FragColor = vec4(color,alp);
}
</script>
我们其实可以再对uv放大一点,比如说放大到10
让光波动起来
然后我们还需要让它动起来,因为fracrt本身也是周期性函数,那么我们可以通过追加iTime的方式,让fract函数的计算结果发生改变
我们对几何体和材质也稍微做一下修改
let uniforms = {
iTime:{value:0}
}
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);
}
function render() {
uniforms.iTime.value += 0.01;
renderer.render(scene,camera);
orbit.update();
requestAnimationFrame(render);
}
修改片元着色器响应动态变化
<script type="x-shader/x-fragment" id="fragmentShader">
varying vec2 vUv;
uniform float iTime;
float sdCircle( vec2 p, float r ){
return length(p) - r;
}
void main(){
vec2 aUv = (vUv - 0.5) * 10.0;
vec3 color = vec3(1.0,0.0,0.0);
//有fract控制,最终数值只会取小数部分
float alp = fract(sdCircle(aUv, 0.5) - iTime);
gl_FragColor = vec4(color,alp);
}
</script>
使用uniform控制最终效果
首先,我们分析一下现在代码中的常量,一般来说常量都可以单独拎出来做uniform
我们光波的圈数由 aUv后面乘的10.0来控制
我们光波的颜色由vec3 color来控制
我们光波的扩散速度,可以给iTime乘一个常量来控制
我们的光波宽度,可以通过对alp做pow计算来控制
那么,我们接下来添加这四个uniform,并且添加GUI控制
如果看不懂接下来的代码,请移步前三篇,全部的代码在前三篇都有解释
追加uniform,以及lil.gui控制器
let uniforms = {
iTime:{value:0},
iFreq:{value:10.0},//频率,光波圈数
iColor:{value:new THREE.Color('#ff0000')},//光波颜色
iSpeed:{value:1},//扩散速度
iPower:{value:2},//光波强度
}
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:"#ff0000" //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.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 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可以减少光波有颜色的部分的宽度
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;
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可以减少光波有颜色的部分的宽度
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:10.0},//频率,光波圈数
iColor:{value:new THREE.Color('#ff0000')},//光波颜色
iSpeed:{value:1},//扩散速度
iPower:{value:2},//光波强度
}
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:"#ff0000" //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.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
欢迎大家来群里交流技术,互相帮助互相进步