【Threejs进阶教程-着色器篇】5. 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
欢迎大家来群里交流技术,互相帮助互相进步
在这里插入图片描述
在这里插入图片描述

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值