【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,注意不带svoid 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;//这里对应vPowuniform vec3 vColor;//这里对应vColorvoid 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.mzph.cn/pingmian/40180.shtml

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

相关文章

刷题Day41|322. 零钱兑换、279. 完全平方数、139.单词拆分

322. 零钱兑换 322. 零钱兑换 - 力扣&#xff08;LeetCode&#xff09; dp[j]&#xff1a;装满容量为j&#xff0c;最少物品为dp[j] 放物品i&#xff1a;dp[j - coins[i]] 1 dp[j] min(dp[j - coins[i]] 1, dp[j]); dp[0] 0; dp[非零] Integer.MAX_VALUE; 思路&…

C# —— Math对象

Math 数学类 提供了一些相关数学计算的属性和方法、四舍五入、向上求整、向下求整、开平方&#xff0c;几次方 最大值和最小值 sin cos 绝对值 方法 1.Math 常用的字段 Math.PI double x 2 * 180 / Math.PI; Console.WriteLine(x); 2 Math.Abs() 求绝对值 int a -3; Con…

西南交通大学【算法分析与设计实验5】

有障碍物的不同路径数量 实验目的 &#xff08;1&#xff09;理解动态规划算法的求解过程。 &#xff08;2&#xff09;分析动态规划算法的时间复杂度&#xff0c;比较动态规划算法与其他算法的效率差异。 &#xff08;3&#xff09;学会如何利用动态规划算法求解具体问题&…

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;需要注意这几个地…

按是否手工执行测试的角度划分:手工测试、自动化测试

1.手工测试&#xff08;Manual testing&#xff09; 手工测试是由人一个一个的输入用例&#xff0c;然后观察结果&#xff0c;和机器测试相对应&#xff0c;属于比较原始但是必须的一个步骤。 由专门的测试人员从用户视角来验证软件是否满足设计要求的行为。 更适用针对深度…

uniapp 开发备忘录-防坑指南

uniapp 开发备忘录-防坑指南 npm run dev:mp-weixin 编译微信小程序报错&#xff1a; [plugin:uni:mp-using-component] Expected ‘,’ or ‘}’ after property value in JSON at position 解决方案&#xff1a;升级uniapp 到最新 alpha 版。&#xff08;2024年7月13日&am…

Markdown+VSCODE实现最完美流畅写作体验

​下载VSCODE软件 安装插件 Markdown All in One &#xff1a;支持markdown的语言的&#xff1b; Markdown Preview Enhanced &#xff1a;观看写出来文档的效果&#xff1b; Paste IMage :添加图片的 Code Spell Checker检查英文单词错误&#xff1b; 基础语法 标题 #一个…

【数据分享】《中国建筑业统计年鉴》2005-2022 PDF

而今天要免费分享的数据就是2005-2022年间出版的《中国建筑业统计年鉴》并以多格式提供免费下载。&#xff08;无需分享朋友圈即可获取&#xff09; 需要2023的数据的请添加小编咨询 数据介绍 在过去的十八个年头中&#xff0c;中国建筑业经历了翻天覆地的变化。从《中国建…

伺服调试三环讲解

在伺服调试过程中,有些项目要求不高,采用伺服自整定就可以调试好伺服,但有些项目对伺服有着比较高的要求,于是需要采取手动调试伺服参数,下面就介绍一下伺服三环参数的调试的方法。 三环指:电流环、速度环、位置环 带宽关系:电流环带宽>速度环带宽>位置环带宽 三环控…

基于Hadoop平台的电信客服数据的处理与分析③项目开发:搭建基于Hadoop的全分布式集群---任务7:格式化并启动Hadoop集群

任务描述 任务内容为格式化并启动Hadoop集群&#xff0c;并修复可能出现的Bug。 任务指导 Hadoop集群启动前需要在NameNode上格式化元数据&#xff0c;成功格式化后才能启动Hadoop的HDFS和YARN。 格式化启动Hadoop集群的步骤如下&#xff1a; 1. 在NameNode&#xff08;ma…

约束:对于数据的限制

主键约束 主键约束&#xff1a;唯一约束非空约束&#xff0c;该字段上的数据不能重复且不能为null 注意&#xff1a;一张表必须有且只有一个主键 添加主键约束 -- 方式一(推荐) CREATE TABLE user(username VARCHAR(32) PRIMARY KEY,password VARCHAR(32),nick_name VARCHAR(3…

Java使用分布式锁来防止缓存穿透与雪崩

步骤如下&#xff1a; 1&#xff09;选择合适的分布式锁实现&#xff1a;常见的分布式锁实现包括ZooKeeper、Redis和基于数据库等。根据具体情况选择最佳方案。 2&#xff09;获取分布式锁&#xff1a;在需要进行操作时&#xff0c;首先尝试获取分布式锁。如果成功获取到&#…

C++文件系统操作2 - 跨平台实现文件夹的创建和删除

1. 关键词2. fileutil.h3. fileutil.cpp4. filesystem_win.h5. filesystem_win.cpp6. filesystem_unix.cpp7. 源码地址 1. 关键词 C 文件系统操作 创建文件夹 创建多级目录文件夹 删除文件夹 删除文件夹下的所有文件和子目录 跨平台 2. fileutil.h #pragma once#include <…

Linux中为什么etc是存放配置文件

在计算机系统中&#xff0c;/etc 是一个目录的名称&#xff0c;通常位于Unix和类Unix操作系统中&#xff0c;如Linux。这个目录用于存放系统配置文件。/etc 的命名来源于早期Unix系统中的 "etcetera"&#xff08;拉丁语 "et cetera" 的缩写&#xff0c;意为…

AI绘画Stable Diffusion超强提示词插件!一键翻译,AI帮你写提示词!

大家好&#xff0c;我是向阳。 对于AI绘画来说&#xff0c;提示词写得好坏&#xff0c;十分影响最终生成图片的结果。会写提示词的话&#xff0c;生成的图片质量就会比较高&#xff0c;不会写的话&#xff0c;结果可能就不会好。 之前大家在使用Stable Diffuison&#xff08;以…

《数据结构与算法基础 by王卓老师》学习笔记——2.5线性表的链式表示与实现1

1.链式表示 2.链表举例 3.链式存储的相关术语 4.三个讨论题

【linux/shell案例实战】解决Linux和Windows的换行符CRLF和LF问题

目录 一.什么是Linux 和 Windows 的换行符 CRLF 和 LF 二.使用Linux 中命令 dos2unix 和 unix2dos 实现CRLF 和LF的转换 三.使用 windows 中的代码编辑器实现 CRLF 和 LF 的转换&#xff08;Notepad&#xff09; 一.什么是Linux 和 Windows 的换行符 CRLF 和 LF CR是Carria…

英语中‘How often’,‘How long’和‘How soon’的区分用法

Spark: 在英语中&#xff0c;“How often”&#xff0c;“How long”&#xff0c;和“How soon”都是询问时间相关事宜的常用短语&#xff0c;但它们的用法各有不同。以下是对这三个短语的详细区分和用法说明&#xff1a; 1. How often 定义&#xff1a;用于询问某事件在一定…

安装依赖时:Error: pngquant failed to build, make sure that libpng-dev is installed

错误原因&#xff1a;windows系统在安装依赖时可能报错&#xff0c;没有安装libping -dev 解决方法&#xff1a; 1、前往libping -dev官网&#xff1a;LIBPNG (sourceforge.io) 2、点击首行DOWNLOAD 3、进入网站点击Download Latest Verison下载安装&#xff0c;解压压缩包即…

2024.7.3作业

1. 梳理笔记(原创) 明天继续提问 2.程序运行后的输出结果为&#xff08;1&#xff09; #include <stdio.h> #define SQR(X) X*X void main() { int a10,k2,m1; a / SQR(km)/SQR(km); printf("%d\n",a); } 结果为1