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

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>

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

git配置ssh-keygen -t rsa -c“xxxx@xxxx.com.cn出现Too many arguments.解决办法

git配置ssh-keygen -t rsa -c"xxxxxxxx.com.cn出现Too many arguments.解决办法 问题描述 配置Git公钥私钥时候输入命令ssh-keygen -t rsa -c"xxxxxxxx.com.cn出现Too many arguments. 解决办法&#xff1a; 提示输入的参数格式不正确&#xff0c;需要注意这几个地…

甄选版“论软件系统架构评估”,软考高级论文,系统架构设计师论文

论文真题 对于软件系统,尤其是大规模的复杂软件系统来说,软件的系统架构对于确保最终系统的质量具有十分重要的意义,不恰当的系统架构将给项目开发带来高昂的代价和难以避免的灾难。对一个系统架构进行评估,是为了:分析现有架构存在的潜在风险,检验设计中提出的质量需求,…

HexPlane: A Fast Representation for Dynamic Scenes(总结图)

图1。用于动态三维场景的 Hex刨面。我们没有从深度 MLP 中回归颜色和不透明度&#xff0c;而是通过 HexPlann 显式地计算时空点的特征。配对一个微小的 MLP&#xff0c;它允许以上100倍加速匹配的质量。 图2。方法概述。Hex刨包含六个特征平面&#xff0c;跨越每对坐标轴(例如…

四个Python代码片段,全面掌握下划线 “_”的妙用!

在Python的世界里&#xff0c;下划线“_”不仅是一个简单的符号&#xff0c;它还承载了许多编程的小技巧&#xff0c;可以让你的编码工作变得更加轻松和高效。 1、快速回顾&#xff1a;下划线在控制台的神秘力量 当你在Python控制台进行连续计算时&#xff0c;知道如何快速引…

【C语言】顺序表经典算法

本文介绍的是两道顺序表经典算法题目。 移除元素 &#xff08;来源&#xff1a;LeetCode&#xff09; 题目 分析 我们很容易想到的办法是去申请一个新的数组&#xff0c;遍历原数组不等于val就把它拿到新数组里。但是题目的要求是不使用额外空间&#xff0c;所以这种方法我们…

【Koa】KOA 基础-掌握基于koa2搭建web应用的基础

目录 KOA 基础框架介绍与环境搭建koa2 基本介绍Node.JS 环境安装创建 Hello World 程序 Web 应用开发基础处理get请求参数处理post请求参数响应一个页面处理静态资源 中间件基本概念和执行过程中间件概念理解Koa 中间件执行模型-洋葱圈模型Koa洋葱圈设计理解 用 koa-body 处理 …

Tekla Structures钢结构详图设计软件下载;Tekla Structures高效、准确的合作平台

Tekla Structures&#xff0c;它不仅集成了先进的三维建模技术&#xff0c;还融入了丰富的工程实践经验&#xff0c;为设计师、工程师和建筑商提供了一个高效、准确的合作平台。 在建筑项目的整个生命周期中&#xff0c;Tekla Structures都发挥着举足轻重的作用。从规划阶段开始…

朋友圈运营必备!一键转发和自动转发轻松搞定!

你还在手动发布多个微信号的朋友圈吗&#xff1f; 现在&#xff0c;就教你一招&#xff0c;让你轻松实现一键转发和自动转发朋友圈&#xff01; 首先&#xff0c;我们需要在个微管理系统上登录自己的微信号&#xff0c;以便进行统一管理。这个系统可以多个微信号同时登录&…

【长亭WAF(雷池)——网站保护之“动态防护”】

在当今的网络安全环境中&#xff0c;Web应用防火墙&#xff08;WAF&#xff09;扮演着至关重要的角色。 它们不仅能够防御常见的Web攻击&#xff0c;如SQL注入、跨站脚本攻击&#xff08;XSS&#xff09;等&#xff0c;还能够应对日益复杂的网络威胁。 作为业内领先的Web安全解…

苹果手机怎么刷机?适合小白的刷机办法!

自己的苹果手机用时间长了&#xff0c;有些人想要为自己的手机重新刷新一下&#xff0c;但又不知道怎么刷机。不要慌现在就来给大家详细介绍一下苹果手机怎么刷机&#xff0c;希望可以帮助到大家。 iPhone常见的刷机方式&#xff0c;分为iTunes官方和第三方软件两种刷机方式。 …

cv2.cvtColor的示例用法

-------------OpenCV教程集合------------- Python教程99&#xff1a;一起来初识OpenCV&#xff08;一个跨平台的计算机视觉库&#xff09; OpenCV教程01&#xff1a;图像的操作&#xff08;读取显示保存属性获取和修改像素值&#xff09; OpenCV教程02&#xff1a;图像处理…

代码随想录Day70(图论Part06)

108.冗余连接 题目&#xff1a;108. 冗余连接 (kamacoder.com) 思路&#xff1a;每次更新输出的边&#xff0c;来保证删除的是输入中最后出现的那条边。关键是&#xff0c;我要知道哪条边可以删除&#xff0c;而且是在join的时候就判断 尝试&#xff08;难得AC&#xff09; im…

gdbserver 指南

文章目录 1. 前言2. gdbserver 远程调试2.1 准备工作2.1.1 准备 客户端 gdb 程序2.1.2 准备 服务端 gdbserver2.1.3 准备 被调试程序 2.2 调试2.2.1 通过网络远程调试2.2.1.1 通过 gdbserver 直接启动程序调试2.2.1.2 通过 gdbserver 挂接到已运行程序调试 2.2.2 通过串口远程调…

WPF对象样式

基本样式设置 Style 设置指定对象的属性 属性&#xff1a; TargetType 引用在哪个类型上面&#xff0c;例如Button、Textblock。。 如果在控件对象里面设置Style&#xff0c;则TargetType必须指定当前控件名 只在作用域里面有效果&#xff0c;其他的相同控件没有影响&…

昇思25天学习打卡营第13天|ResNet50图像分类

1. 学习内容复盘 图像分类是最基础的计算机视觉应用&#xff0c;属于有监督学习类别&#xff0c;如给定一张图像(猫、狗、飞机、汽车等等)&#xff0c;判断图像所属的类别。本章将介绍使用ResNet50网络对CIFAR-10数据集进行分类。 ResNet网络介绍 ResNet50网络是2015年由微软…

Linux应用---内存映射

写在前面&#xff1a; 在进程间通信中&#xff0c;有一种方式内存映射。内存映射也是进程间通信的方式之一&#xff0c;其效率高&#xff0c;可以直接对内存进行操作。本节我们对内存映射进行学习&#xff0c;并结合案例进行实践。 1、基本理论 内存映射&#xff1a;是将磁盘文…

ODN网络弱光聚类定界与整治

01 ODN网络弱光运维现状 ODN网络是家庭宽带连接系统-无源光网络 (PON) 的重要组成部分&#xff0c;是连接局端 OLT 和用户 ONT 之间的光路通道&#xff0c;其质量直接影响整个PON系统的性能及可靠性。ODN光纤链路包括OLT PON口、ODF、主干光纤、一级分光器、分支光纤、二级分光…

登录功能和校验

基础版 controller package com.web.management.controller;import com.web.management.pojo.Emp; import com.web.management.pojo.Result; import com.web.management.service.EmpService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.anno…

如何用Vue3和Plotly.js绘制交互式漏斗图

本文由ScriptEcho平台提供技术支持 项目地址&#xff1a;传送门 Plotly.js 绘制漏斗图 应用场景 漏斗图常用于可视化业务流程中的各个阶段的转换率&#xff0c;例如销售漏斗或营销漏斗。它可以帮助用户识别流程中的瓶颈和改进机会。 基本功能 本代码使用 Plotly.js 库绘制…

【微机原理及接口技术】中断控制器8259A

【微机原理及接口技术】中断控制器8259A 文章目录 【微机原理及接口技术】中断控制器8259A前言一、介绍二、8259A的内部结构和引脚三、8259A的中断工作过程四、8259A的工作方式五、8259A的编程六、外部中断服务程序总结 前言 本篇文章将就8259芯片展开介绍&#xff0c;8259A的…