【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,一经查实,立即删除!

相关文章

西南交通大学【算法分析与设计实验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;属于比较原始但是必须的一个步骤。 由专门的测试人员从用户视角来验证软件是否满足设计要求的行为。 更适用针对深度…

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;中国建筑业经历了翻天覆地的变化。从《中国建…

伺服调试三环讲解

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

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…

安装依赖时: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

STM32——GPIO(点亮LED)

一、GPIO是什么&#xff1f; 1、GPI/O(general porpose intput output):通用输入输出端口的简称&#xff0c;通俗地说&#xff0c;就是我们所学的51单片机的IO口&#xff0c;即P0_0等。但要注意&#xff1a;并非所有的引脚都是GPIO 输出模式下可控制端口输出高低电平&#xf…

程序员的加油站,各类技术文章,可视化技术,在线源码资源,在线实用工具,数据爬虫接口持续集成更新中

先挂网址&#xff1a;https://wheart.cn 可视化大屏模板与设计&#xff0c;在线预览 上百例可视化模板 技术文章、资源下载等各类资源导航页 echart在线实用demo 各种在线工具提升开发效率 echart在线代码模板

【电商指标详解】

前言&#xff1a; &#x1f49e;&#x1f49e;大家好&#xff0c;我是书生♡&#xff0c;本篇文章主要和大家分享一下电商行业中常见指标的详解&#xff01;存在的原因和作用&#xff01;&#xff01;&#xff01;希望对大家有所帮助。 &#x1f49e;&#x1f49e;代码是你的画…

Typora导出为Word

文章目录 一、场景二、安装1、网址2、解压并验证 三、配置四、重启Typora 一、场景 在使用Typora软件编辑文档时&#xff0c;我们可能需要将其导出为Word格式文件 当然我们可以直接在菜单里进行导出操作 文件-> 导出-> Word(.docx) 如果是第一次导出word文件&#xff0…

Python特征工程 — 1.3 对数与指数变换

目录 1 对数变换 1.1 对数变换的概念 1.2 对数变换实战 2 指数变换 2.1 指数变换的概念 2.2 指数变换实战 3 Box-Cox变换 3.1 Box-Cox变换概念 3.2 Box-Cox变换实战 1 对数变换 1.1 对数变换的概念 特征对数变换和指数变换是数据预处理中的两种常用技术&#xff0c;…

中国植物志(80卷)

中国植物志&#xff0c;全书共80卷126分册&#xff0c;3700页&#xff0c;记载了我国301科3408属31142种植物学名、形态特征、生态环境、地理分布、经济用途和物候期等。是研究中国植物的重要论著&#xff08;截图仅部分&#xff09;。

使用 bend-ingest-kafka 将数据流实时导入到 Databend

作者&#xff1a;韩山杰 Databend Cloud 研发工程师 https://github.com/hantmac Databend是一个开源、高性能、低成本易于扩展的新一代云数据仓库。bend-ingest-kafka 是一个专为 Databend 设计的实时数据导入工具&#xff0c;它允许用户从 Apache Kafka 直接将数据流导入到 D…

linux系统中的各种命令的解释和帮助(含内部命令、外部命令)

目录 一、说明 二、命令详解 1、帮助命令的种类 &#xff08;1&#xff09;help用法 &#xff08;2&#xff09;--help用法 2、如何区别linux内部命令和外部命令 三、help和—help 四、man 命令 1、概述 2、语法和命令格式 &#xff08;1&#xff09;man命令的格式&…