【Threejs进阶教程-着色器篇】2. Uniform的基本用法与Uniform的调试

news2024/11/18 23:45:35

Uniform的基本用法与Uniform的调试

  • 关于本Shader教程
  • 优化上一篇的效果
    • 优化光栅栏高度
    • 让透明度和颜色变的更平滑
      • pow()函数
      • 借助数学工具更好的理解函数
  • Unifoms简介
    • 编写uniforms
    • 修改片元着色器代码
    • 借助lil.gui调试uniforms
    • 使用uniform控制颜色
      • 继续在uniforms添加颜色
      • 在着色器中接收颜色
      • 使用lil.gui来调试颜色
      • 最终效果
  • Uniform数据类型对应
  • 让光栅栏动起来
    • 加入持续变化的变量
    • 让vTime持续变化
    • 修改Shader部分代码响应变化
      • 三角函数sin() 和 绝对值函数abs()
      • 最终效果
  • 常用的片元着色器开发流程
    • 先使用Shadertoy开发基本效果
    • 将Shadertoy中的数据常量以及颜色,在Threejs的Shader中,修改为uniform传递
    • 最后将ShaderMaterial应用到你的Mesh上即可
  • 本篇全部源码

关于本Shader教程

  1. 本教程着重讲解Shadertoy的shader和Threejs的Shader,与原生WebGLShader略有不同,如果需要学习原生WebGL的shader,请参考《WebGL编程指南》
  2. 本人的shader水平也比较基础,文章中所写代码,不一定是最佳的代码,思路也不一定是最好的思路,所以一切本人的Shader教程下,所有的代码及思路以及学习建议均仅供参考,且目前本教程可能不适用于WebGPU,如果有大佬路过看到本人文章,觉得有可以指点之处,可以在下面留言,我们一起进步
  3. 数学水平不行的人,尤其是高中数学都及格不了的,不建议入坑Shader
  4. 本教程会在讲解片元着色器时,使用Shadertoy来编写demo,所以教程中会出现一部分Shadertoy的代码
  5. 本段内容将会出现在本人所有的【进阶教程-着色器篇】的文章中

优化上一篇的效果

上一篇的效果,其实并不怎么好看,所以我们先做一下优化

优化光栅栏高度

首先,我们在创建Plane的时候,修改plane的高度,让它看起来更像是围墙,栅栏的样子

    function addMesh() {

        let planeHeight = 5;

        let geometry = new THREE.PlaneGeometry(10,planeHeight);
        let material = new THREE.ShaderMaterial({
            uniforms,
            vertexShader:document.getElementById('vertexShader').textContent,
            fragmentShader:document.getElementById('fragmentShader').textContent,
            transparent:true,
            side:THREE.DoubleSide
        })
        let mesh = new THREE.Mesh(geometry,material);

        let mesh1 = mesh.clone();
        mesh1.position.set(5,planeHeight/2,0);
        mesh1.rotation.y = Math.PI/2;
        scene.add(mesh1);

        let mesh2 = mesh.clone();
        mesh2.position.set(-5,planeHeight/2,0);
        mesh2.rotation.y = Math.PI/2;
        scene.add(mesh2);

        let mesh3 = mesh.clone();
        mesh3.position.set(0,planeHeight/2,-5);
        scene.add(mesh3);

        let mesh4 = mesh.clone();
        mesh4.position.set(0,planeHeight/2,5);
        scene.add(mesh4);

    }

修改后的效果

在这里插入图片描述

让透明度和颜色变的更平滑

首先我们介绍本篇Shader的第一个函数,pow函数

pow()函数

pow函数在《webgl编程指南》429页有相关文档
在这里插入图片描述
简单说

float a = 2.0; //注意,float类型必须写成1.0,否则会判断为 int类型的数据
pow(a, 2.0); //结果是 a^2 = 2.0 ^ 2.0 = 4.0
pow(a, 3.0); //结果是 a^3 = 2.0 ^ 3.0 = 8.0
注意,pow函数的第二个参数,是float类型,也就意味着,不仅仅可以使用整数,还可以使用小数,负数等等

借助数学工具更好的理解函数

这是本人找的一个非常不错的数学学习工具

我们来借助这个工具,来复习一下指数函数
在这里插入图片描述
可以看出,指数函数的图像是这样,那么我们把指数函数带入到我们的Shader中是什么样子呢

我们把上一篇的代码的基础部分,改成

<script type="x-shader/x-fragment" id="fragmentShader">
    varying vec2 vUv;
    void main(){
        float vy = 1.0 - vUv.y;
        vy = pow(vy,4.0);
        gl_FragColor = vec4(vy,0.0,0.0,vy);
    }
</script>

在这里插入图片描述
这里我们发现,红色的区域明显变小了

这里我们的pow()的参数1,是vUv.y,参数2是个固定常量2.0,所以,此时在函数图像上,vUv.y 对应的x值,而计算结果,对应的是y值,接下来我们看一下在Shadertoy中的效果

在这里插入图片描述
我们把写的公式,带入到desmos中生成图像
在这里插入图片描述
这里,我们的vy对应desmos里的x,pow的计算结果对应desmos里的y
可以看出,在desmos里,当x逐渐增大的时候,结果值会无限趋近于0,而且前期变化幅度很大,后期变化幅度小
也就意味着对应在Shadertoy中,uv.y = 0时颜色最深,uv.y=1时完全没有颜色,且前面颜色渐变变化很明显,后面颜色变化不够明显

我们可以把pow函数的第二个参数直接改到10,来让变化更明显一些
在这里插入图片描述

所以,我们可以通过pow,来控制光栅栏的有颜色部分的宽度

Unifoms简介

Uniforms在《WebGL编程指南》中介绍的比较多,有想了解Uniform完整介绍的请自行参阅此书,这里笔者仅介绍怎么用
uniforms的主要用途,是传递一个参数给到glsl,此参数限定类型,最常用的参数类型就是float,vec2,vec3, vec4等

这里介绍如何利用uniform传递一个float类型的数据,给到glsl

  1. 在js部分的代码中,创建一个对象,这里通常命名为uniforms
  2. 在uniforms中,添加一个属性,命名为key,key的值也是一个对象,对象内容为: {value: [value]},后面的value是你需要传递给glsl的数值(不要问我为什么这么麻烦,threejs就这么规定的)
  3. 在着色器代码的上面,追加代码 uniform float key; 你的数据类型是什么,这里就要声明为什么类型,且命名一定要与unifomrs中的[key]命名一致,一定不要漏分号,否则会报错

这里我们演示一下如何用uniform来控制光栅栏的光效高度

编写uniforms

    let uniforms = {
        vPow:{value:2.0}
    }

    function addMesh() {

        let planeHeight = 5;

        let geometry = new THREE.PlaneGeometry(10,planeHeight);
        let material = new THREE.ShaderMaterial({
            //uniforms在这里传入到js的ShaderMaterial中
            uniforms, //这里使用了对象的新语法,uniforms:uniforms可以简写为 uniforms,
            vertexShader:document.getElementById('vertexShader').textContent,
            fragmentShader:document.getElementById('fragmentShader').textContent,
            transparent:true,
            side:THREE.DoubleSide
        })
        let mesh = new THREE.Mesh(geometry,material);

        let mesh1 = mesh.clone();
        mesh1.position.set(5,planeHeight/2,0);
        mesh1.rotation.y = Math.PI/2;
        scene.add(mesh1);

        let mesh2 = mesh.clone();
        mesh2.position.set(-5,planeHeight/2,0);
        mesh2.rotation.y = Math.PI/2;
        scene.add(mesh2);

        let mesh3 = mesh.clone();
        mesh3.position.set(0,planeHeight/2,-5);
        scene.add(mesh3);

        let mesh4 = mesh.clone();
        mesh4.position.set(0,planeHeight/2,5);
        scene.add(mesh4);

    }

修改片元着色器代码

<script type="x-shader/x-fragment" id="fragmentShader">
    varying vec2 vUv;
    uniform float vPow; //使用uniform关键字引入float类型的vPow,注意不带s
    void main(){
        float vy = 1.0 - vUv.y;
        vy = pow(vy,vPow);
        gl_FragColor = vec4(vy,0.0,0.0,vy);
    }
</script>

验证效果后,基本上与原效果一致,这里就不放图了

借助lil.gui调试uniforms

	//引入lil.gui并初始化
    import {GUI} from "../three/examples/jsm/libs/lil-gui.module.min.js";

    let gui = new GUI();

添加gui控制代码

function addMesh() {
	//...这里省略上面的代码

	gui.add(uniforms.vPow,'value',0,10).name('光栅栏高度');
}

我们也修改一下相机的位置,让相机初始视角高一点,省的操作

	camera.position.set(10,10,10);

最终效果
在这里插入图片描述

使用uniform控制颜色

继续在uniforms添加颜色

其他代码不变

    let uniforms = {
        vPow:{value:2.0},
        vColor:{value:new THREE.Color("#ff0000")}
    }

颜色在threejs中,可以作为vec3类型的数据,传递给glsl,必须是 THREE.Color()类型的颜色对象,才可以作为glsl的传递参数

在着色器中接收颜色

<script type="x-shader/x-fragment" id="fragmentShader">
    varying vec2 vUv;
    uniform float vPow;//这里对应vPow
    uniform vec3 vColor;//这里对应vColor
    void main(){
        float vy = 1.0 - vUv.y;
        vy = pow(vy,vPow);
        gl_FragColor = vec4(vColor,vy);
    }
</script>

这里,我们就不用vy来控制红色高度了,仅需要用透明度控制高度即可

在glsl的语法中,没有color类型的数据,要使用vec3类型的来接收颜色数据
在glsl的语法中,vec4的构造器,允许传入 一个vec3类型的和一个float类型的

使用lil.gui来调试颜色

        gui.add(uniforms.vPow,'value',0,10).name('光栅栏高度');

        let colors = {
            vColor:"#ff0000"
        };
        gui.addColor(colors,'vColor').name('光栅栏颜色').onChange(v=>{
            uniforms.vColor.value = new THREE.Color(v);
        })

如果忘了lil.gui怎么用,可以回顾一下本人的基础教程,基础教程中有很详细的用法说明

最终效果

在这里插入图片描述

Uniform数据类型对应

这里我们截取Threejs的文档,ThreejsShaderMaterial文档
在这里插入图片描述

让光栅栏动起来

加入持续变化的变量

    let uniforms = {
        vTime:{value:0},
        vPow:{value:2.0},
        vColor:{value:new THREE.Color("#ff0000")}
    }

让vTime持续变化

这里我们在Render函数中,让vTime持续变化,每帧增加0.01

    function render() {
        renderer.render(scene,camera);
        orbit.update();
        requestAnimationFrame(render);
    	uniforms.vTime.value += 0.01;
    }

修改Shader部分代码响应变化

<script type="x-shader/x-fragment" id="fragmentShader">
    varying vec2 vUv;
    uniform float vTime;
    uniform float vPow;
    uniform vec3 vColor;
    void main(){
        float vy = 1.0 - vUv.y;
        vy = pow(vy,vPow - abs(sin(vTime)));//让vPow跟随时间变化
        gl_FragColor = vec4(vColor,vy);
    }
</script>

三角函数sin() 和 绝对值函数abs()

这俩函数完全不需要解释吧

上述的计算结果,会让vPow后面增加一个周期性变化的效果
在这里插入图片描述

最终效果

在这里插入图片描述

常用的片元着色器开发流程

一般情况下,我们在开发片元着色器的时候,可以先考虑在shadertoy上来编写效果,然后将写好的效果
比如说上面的效果

先使用Shadertoy开发基本效果

在这里插入图片描述

Shadertoy内置了一个时间变量iTime,每帧会增加0.01,所以使用上面的写法效果是完全一致的

注意!在Shadertoy中,由于正常情况下没有参照物,所以默认控制红色,而很少控制透明度,注意两边的代码的区别

将Shadertoy中的数据常量以及颜色,在Threejs的Shader中,修改为uniform传递

在这里插入图片描述

最后将ShaderMaterial应用到你的Mesh上即可

本篇全部源码

<!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;
    }

</script>
<script type="x-shader/x-fragment" id="fragmentShader">
    varying vec2 vUv;
    uniform float vTime;
    uniform float vPow;
    uniform vec3 vColor;
    void main(){
        float vy = 1.0 - vUv.y;
        vy = pow(vy,vPow - abs(sin(vTime)));
        gl_FragColor = vec4(vColor,vy);
    }
</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";

    let gui = new GUI();

    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(10,10,10);
        scene.add(camera);

        orbit = new OrbitControls(camera,renderer.domElement);
        orbit.enableDamping = true;
        scene.add(new THREE.GridHelper(10,10));
    }

    let uniforms = {
        vTime:{value:0.01},
        vPow:{value:2.0},
        vColor:{value:new THREE.Color("#ff0000")}
    }

    function addMesh() {

        let planeHeight = 5;

        let geometry = new THREE.PlaneGeometry(10,planeHeight);
        let material = new THREE.ShaderMaterial({
            //uniforms在这里传入到js的ShaderMaterial中
            uniforms, //这里使用了对象的新语法,uniforms:uniforms可以简写为 uniforms,
            vertexShader:document.getElementById('vertexShader').textContent,
            fragmentShader:document.getElementById('fragmentShader').textContent,
            transparent:true,
            side:THREE.DoubleSide
        })
        let mesh = new THREE.Mesh(geometry,material);

        let mesh1 = mesh.clone();
        mesh1.position.set(5,planeHeight/2,0);
        mesh1.rotation.y = Math.PI/2;
        scene.add(mesh1);

        let mesh2 = mesh.clone();
        mesh2.position.set(-5,planeHeight/2,0);
        mesh2.rotation.y = Math.PI/2;
        scene.add(mesh2);

        let mesh3 = mesh.clone();
        mesh3.position.set(0,planeHeight/2,-5);
        scene.add(mesh3);

        let mesh4 = mesh.clone();
        mesh4.position.set(0,planeHeight/2,5);
        scene.add(mesh4);

        gui.add(uniforms.vPow,'value',0,10).name('光栅栏高度');

        let colors = {
            vColor:"#ff0000"
        };
        gui.addColor(colors,'vColor').name('光栅栏颜色').onChange(v=>{
            uniforms.vColor.value = new THREE.Color(v);
        })
    }


    function render() {
        renderer.render(scene,camera);
        orbit.update();
        requestAnimationFrame(render);
        uniforms.vTime.value += 0.01;
    }

</script>
</body>
</html>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1890743.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

动态住宅代理IP的3个优点

在大数据时代的背景下&#xff0c;代理IP成为了很多企业顺利开展的重要工具。代理IP地址可以分为住宅代理IP地址和数据中心代理IP地址。选择住宅代理IP的好处是可以实现真正的高匿名性&#xff0c;而使用数据中心代理IP可能会暴露自己使用代理的情况。 住宅代理IP是指互联网服务…

JavaScript中location对象的主要属性和方法

属性 href&#xff1a;获取或设置整个URL。protocol&#xff1a;获取URL的协议部分&#xff0c;如"http:"或"https:"。host&#xff1a;获取URL的主机名&#xff08;包括端口号&#xff0c;如果有的话&#xff09;。hostname&#xff1a;获取URL的主机名&…

Android studio 打包低版本的Android项目报错

一、报错内容 Execution failed for task :app:packageRelease. > A failure occurred while executing com.android.build.gradle.internal.tasks.Workers$ActionFacade> com.android.ide.common.signing.KeytoolException: Failed to read key key0 from store "…

【Portswigger 学院】路径遍历

路径遍历&#xff08;Path traversal&#xff09;又称目录遍历&#xff08;Directory traversal&#xff09;&#xff0c;允许攻击者通过应用程序读取或写入服务器上的任意文件&#xff0c;例如读取应用程序源代码和数据、凭证和操作系统文件&#xff0c;或写入应用程序所访问或…

10 Posix API与网络协议栈

POSIX概念 POSIX是由IEEE指定的一系列标准,用于澄清和统一Unix-y操作系统提供的应用程序编程接口(以及辅助问题,如命令行shell实用程序),当您编写程序以依赖POSIX标准时,您可以非常肯定能够轻松地将它们移植到大量的Unix衍生产品系列中(包括Linux,但不限于此!)。 如…

奇瑞被曝强制加班,“896”成常态且没有加班费

ChatGPT狂飙160天&#xff0c;世界已经不是之前的样子。 更多资源欢迎关注 7 月 2 日消息&#xff0c;一位认证为“奇瑞员工”的网友近期发帖引发热议&#xff0c;奇瑞汽车内部存在强制加班行为&#xff0c;每周加班时长需大于 20 小时并且没有加班费&#xff0c;仅补贴 10 元…

人口萎缩,韩国釜山“进入消失阶段”

KlipC报道&#xff1a;调查显示&#xff0c;随着低生育率和人口老化&#xff0c;釜山人口逐渐萎缩&#xff0c;韩国第二大城市釜山显现出“进入消失阶段”的迹象。 据悉&#xff0c;“消失风险指数”是将20岁至39岁女性人口总数除以65岁及以上人口得到的数值。当该指数大于1.5…

第T3周:天气识别

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 一、前期工作 本文将采用CNN实现多云、下雨、晴、日出四种天气状态的识别。较上篇文章&#xff0c;本文为了增加模型的泛化能力&#xff0c;新增了Dropout层并…

【计算机体系结构】缓存的false sharing

在介绍缓存的false sharing之前&#xff0c;本文先介绍一下多核系统中缓存一致性是如何维护的。 目前主流的多核系统中的缓存一致性协议是MESI协议及其衍生协议。 MESI协议 MESI协议的4种状态 MESI协议有4种状态。MESI是4种状态的首字母缩写&#xff0c;缓存行的4种状态分别…

snap和apt的区别简单了解

Linux中没有tree命令的时候提示安装的时候出现了两个命令&#xff0c;简单看了看两者有何区别&#xff08;一般用apt就可以了&#xff09;&#xff1a; sudo snap install tree 和 sudo apt install tree 这两个命令都是用来安装 tree 命令行工具的&#xff0c;但它们使用的是不…

uniapp零基础入门Vue3组合式API语法版本开发咸虾米壁纸项目实战

嗨&#xff0c;大家好&#xff0c;我是爱搞知识的咸虾米。 今天给大家带来的是零基础入门uniapp&#xff0c;课程采用的是最新的Vue3组合式API版本&#xff0c;22年发布的uniappVue2版本获得了官方推荐&#xff0c;有很多同学等着我这个vue3版本的那&#xff0c;如果没有学过vu…

数字信号处理教程(2)——时域离散信号与时域离散系统

上回书说到数字信号处理中基本的一个通用模型框架图。今天咱们继续&#xff0c;可以说今天要讲的东西必须是学习数字信号处理必备的观念——模拟与数字&#xff0c;连续和离散。 时域离散序列 由于数字信号基本都来自模拟信号&#xff0c;所以先来谈谈模拟信号。模拟信号就是…

umi项目中的一些趣事

前言 出于某些安全问题&#xff0c;需要把HTML中框架注入的umi版本信息去掉&#xff0c;那要怎么搞呢~ 方案 查找官方文档&#xff0c;没发现可以去掉注入信息的方法&#xff0c;但在一番折腾后&#x1f609;终究还是解决了~ 发现 版本信息是从这里注入的~ Object.define…

企业短视频-直播运营团队打造课,手把手带你从0-1 搭建运营团队-15节

如何获取精准客户? 一套抖音营销系统打造课 能定位 懂运营 建团队 持续获客 课程目录 1-01、每个老板都应该学习博商团队的打造方法1.mp4 2-02、如何从0-1快速搭建运营团队1.mp4 3-03、怎么才能招聘到运营人才&#xff1f;1.mp4 4-04、怎么才能快速筛选简历招到符合要求…

程序烧录原理

程序烧录原理 ISP(In System Programming)&#xff0c;在系统编程&#xff0c;单片机不须脱离应用系统而直接在产品上烧写/升级程序。 条件&#xff1a;系统须引出单片机的串口引脚&#xff08;TXD、RXD&#xff09;ISP相对于传统的编程方式&#xff0c;在传统的编程方式中我们…

kvm虚拟机启用console登录

kvm虚拟机console登录&#xff0c;就是执行 virsh console 的时候&#xff0c;宿主机可以控制虚拟机。 一、centos7的kvm虚拟机开启console登录&#xff08;在虚拟中操作&#xff09; 1、备份文件 [roothadoop51 ~]# cp /etc/grub2.cfg /etc/grub2.cfg_back 2、用下面命令可…

2024 AIGC 技术创新应用研讨会暨数字造型设计师高级研修班通知

尊敬的老师、领导您好! 为深入响应国家关于教育综合改革的战略部署&#xff0c;深化职业教育、高等教育改革&#xff0c;发挥企业主体重要作用&#xff0c;促进人才培养供给侧和产业需求侧结构要素全方位融合&#xff0c;充分把握人工智能创意式生成(AIGC)技术在教育领域的发展…

强连通分量

强连通分量 强连通定义 有向图 G G G 的强连通是指 G G G 中任意两个节点都可以直接或间接到达。 下方两幅图都是强连通。一个特殊一点&#xff0c;任意两点都可以直接到达&#xff1b;一个则是最常见的强连通图。 特殊强连通图&#xff0c;任意两点都可以直接到达 常见的…

7.7、指针和函数

代码 #include <iostream> using namespace std;//实现两个数字进行交换 void swap01(int a, int b) {int temp a;a b;b temp;cout << "swap01a " << a << endl;cout << "swap01b " << b << endl; }void sw…

Docker精华篇 - 常用命令大全,入门到精通!

大家好,我是CodeQi! 我们都知道 Docker 的重要性,以及 Docker 如何在软件开发生命周期中发挥重要作用 。 说实话,学习 Docker 很有趣,至少在我看来是这样。 一旦掌握了基础知识,这并不难。 困难的是记住所有这些命令。 因此,在这篇文章中,我收集了所有命令,或者更…