当前位置: 首页 > article >正文

【Threejs进阶教程-着色器篇】9.顶点着色器入门

【Threejs进阶教程-着色器篇】9.顶点着色器入门

  • 本系列教程第一篇地址,建议按顺序学习
  • 认识顶点着色器
    • varying介绍
    • 顶点着色器与片元着色器分别的作用
    • Threejs在Shader中的内置变量
    • 各种矩阵
    • gl_Position
  • 尝试使用顶点着色器
    • 增加分段数增强效果
  • 制作平面鼓包效果
    • 鼓包效果分析
    • 路障效果
    • 让路障效果变得圆滑
    • uniform 控制鼓包效果
  • 完整源码

本系列教程第一篇地址,建议按顺序学习

本系列目前已累计第九篇,这里直接省略了2到8篇,可以通过上方专栏来查阅前面的教程
【Threejs进阶教程-着色器篇】1. Shader入门(ShadertoyShader和ThreejsShader入门)

本篇使用到的模板代码,从这里自取一个shader模板代码即可
【模板代码】用于编写Threejs Demo的模板代码

认识顶点着色器

首先我们把着色器部分的代码拎出来逐一分析

<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;
    void main(){
        gl_FragColor = vec4(1.0,0.0,0.0,1.0);
    }
</script>

varying介绍

有人发现顶点着色器的第一行与片元着色器的第一行,完全一致,那么这个varying用来做什么的呢?

这里我们直接借用webgl编程指南211页的介绍
在这里插入图片描述

varying主要用于 顶点着色器到片元着色器传输数据,是个全局变量

现阶段,我们只需要认识到,varying声明的变量,要在顶点着色器和片元着色器都要存在,且初始值需要在顶点着色器中设置即可

了解了varying之后,我们发现,其实uv的值是从顶点着色器中传递给片元着色器的

顶点着色器与片元着色器分别的作用

现阶段我这里不打算讲渲染原理和管线那些,太繁琐,这里先简单的总结一句
后续在讲到后处理的时候,这部分内容会再细讲

顶点着色器决定外型
片元着色器决定色彩

Threejs在Shader中的内置变量

顶点着色器中使用到了几个threejs的内置变量,在下面的官方文档中有说明
Threejs内置变量-WebGLProgram
一般来说我们最需要关注的几个:
position,模型的顶点信息会传递到这里,常用于计算模型的外观和最终渲染的外型
uv,模型的uv信息会传递到这里,常用于传递给顶点着色器用于计算颜色
normal,模型的法线信息会传递到这里,常用于计算光照等高级计算

如果看完了前面的BufferGeometry教程,有没有发现这里很熟悉?
【ThreeJS基础教程-高级几何体篇】2.6 BufferGeometry与BufferAttribute

你们想的没错,这些就是threejs向shader系统传递的数据,如果这里不懂BufferGeometry的,要继续下去学习Shader,就需要去前面补一下BufferGeometry的相关知识了

各种矩阵

在顶点着色器模板代码中的第四行第五行,分别出现了modelViewMatrix和projectionMatrix这两个矩阵,现阶段先不用管,只需要记住顶点着色器最终计算是这样即可

现阶段顶点着色器的代码,在模板代码的最后两行,除了**vec4(position,1.0);**会稍作改变,其他时间不会发生大的变动

gl_Position

一般来说,顶点着色器也需要有个固定输出,gl_Position就是顶点着色器的最终输出结果,最终结果也是一个vec4类型的对象

这样,顶点着色器的代码我们就介绍完毕了,接下来我们要尝试修改一下顶点着色器,感受一下顶点着色器带来的效果

尝试使用顶点着色器

我们用个最简单的方式来操作顶点

<script type="x-shader/x-vertex" id="vertexShader">
    varying vec2 vUv;
    void main(){
        vUv = vec2(uv.x,uv.y);
        vec3 aPosition = position;
        //这里我们直接操作顶点的z轴,偏移的激进一点
        aPosition.z = sin(aPosition.x * aPosition.y) * 10.0;
        vec4 mvPosition = modelViewMatrix * vec4( aPosition , 1.0 );
        gl_Position = projectionMatrix * mvPosition;
    }

</script>

在这里插入图片描述
然后,我们运行起来之后,发现,我们的平面,扭曲了,这是因为我们的z轴发生了改变

增加分段数增强效果

这里我们修改一下addMesh()

    function addMesh() {
    	//增加到100分段
         let geometry = new THREE.PlaneGeometry(10,10,3,3);
        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);
    }

在这里插入图片描述
我们可以发现,增加了分段和顶点之后,我们的这个平面变化巨大,已经不再是最初的PlaneGeometry了

这样,我们就完成了一次顶点着色器的尝试

制作平面鼓包效果

首先我们把平面横过来,然后需要做一个数据变换,分段数增加到100,然后设定材质的线框模式开,不然我们等一下不好看到效果
然后修改回最初的顶点着色器代码

addMesh

    function addMesh() {
    
    	//注意这里必须旋转几何体,旋转了几何体,我们的数据才是正确的
    	//mesh.rotation是在矩阵层面修改了旋转方向,最终会传递到modelViewMatrix中
        //顶点着色器的所有教程,除非特殊说明,否则全部使用旋转几何体
        
        let geometry = new THREE.PlaneGeometry(10,10,100,100).rotateX(-Math.PI/2);
        let material = new THREE.ShaderMaterial({
            uniforms,
            vertexShader:document.getElementById('vertexShader').textContent,
            fragmentShader:document.getElementById('fragmentShader').textContent,
            transparent:true,
            wireframe:true
        })
        let mesh = new THREE.Mesh(geometry,material);
        scene.add(mesh);
    }

顶点着色器

<script type="x-shader/x-vertex" id="vertexShader">
    varying vec2 vUv;
    void main(){
        vUv = vec2(uv.x,uv.y);
        vec3 aPosition = position;
        vec4 mvPosition = modelViewMatrix * vec4( aPosition , 1.0 );
        gl_Position = projectionMatrix * mvPosition;
    }
</script>

在这里插入图片描述

鼓包效果分析

既然要制作鼓包效果,那么,我们需要一个鼓包顶点,然后鼓包顶点处的高度最高,然后依次递减,所以我们这里直接从顶点着色器来定义这个鼓包点

<script type="x-shader/x-vertex" id="vertexShader">
    varying vec2 vUv;
    void main(){
        vUv = vec2(uv.x,uv.y);
        vec3 aPosition = position;

        //0 为int类型,0.0为float类型, 如果写0,threejs会报错
        // 可以写成 .0 来替代0.0以及任何 0.X 的数字, 但是个人不是很喜欢这种写法,看着太混乱
        vec3 swelling = vec3(0.0);

        //计算鼓包点到顶点的距离
        float dis = distance(swelling,aPosition);
        dis = clamp(dis,0.0,5.0);
        aPosition.y = 5.0 - dis;


        vec4 mvPosition = modelViewMatrix * vec4( aPosition , 1.0 );
        gl_Position = projectionMatrix * mvPosition;
    }
</script>

路障效果

这里,我们先声明了鼓包点在中心点,然后,我们计算鼓包点到四周的距离,但是要做一下限制,如果大于5.0的值,则直接赋值为5.0,紧接着直接把这个计算出来的dis值丢给aPosition.y,我们得到了一个漏斗型
在这里插入图片描述
既然我们计算的dis的最大值为5,那么,我们把大小做一下交换即可,用5.0 - dis,即可把漏斗形改成路障型
在这里插入图片描述

让路障效果变得圆滑

这里我们使用指数函数来优化

我们现在知道了最高点是5,最低点为0,那么,我们就可以计算它的高度比例,然后把线性的比例换成指数型比例
在这里插入图片描述
保持最高点和最低点不变,然后我们直接带入图像上面的数学公式,即可得到我们的鼓包效果

<script type="x-shader/x-vertex" id="vertexShader">
    varying vec2 vUv;
    void main(){
        vUv = vec2(uv.x,uv.y);
        vec3 aPosition = position;

        //0 为int类型,0.0为float类型, 如果写0,threejs会报错
        // 可以写成 .0 来替代0.0以及任何 0.X 的数字, 但是个人不是很喜欢这种写法,看着太混乱
        vec3 swelling = vec3(0.0);

        //计算鼓包点到顶点的距离
        float dis = distance(swelling,aPosition);
        dis = clamp(dis,0.0,5.0);
        dis = pow( dis / 5.0, 2.0 ) * 5.0;
        aPosition.y = 5.0 - dis;


        vec4 mvPosition = modelViewMatrix * vec4( aPosition , 1.0 );
        gl_Position = projectionMatrix * mvPosition;
    }
</script>

在这里插入图片描述

uniform 控制鼓包效果

uniform亦可用于顶点着色器

我们在代码中多次使用到5.0,这个实际上是鼓包的最大高度,这里我们抽出来这个常数作为鼓包最大高度,指数函数用的2次幂,这个参数可以抽出一个参数为鼓包圆滑率,我们写到uniform和lil.gui来调试
当然,我们的鼓包中心点,也可以单独拎出来放到uniform中

顶点着色器中编写uniform与片元着色器基本一致

修改后的顶点着色器

<script type="x-shader/x-vertex" id="vertexShader">
    varying vec2 vUv;

    uniform float maxSwelling;
    uniform vec3 swellingCenter;
    uniform float swellingPower;

    void main(){
        vUv = vec2(uv.x,uv.y);
        vec3 aPosition = position;

        //计算鼓包点到顶点的距离
        float dis = distance(swellingCenter,aPosition);
        dis = clamp(dis,0.0,maxSwelling);
        dis = pow( dis / maxSwelling, swellingPower ) * maxSwelling;
        aPosition.y = maxSwelling - dis;

        vec4 mvPosition = modelViewMatrix * vec4( aPosition , 1.0 );
        gl_Position = projectionMatrix * mvPosition;
    }
</script>

修改后的addMesh()和uniforms


	let uniforms = {
        maxSwelling:{value:5.0},
        swellingCenter:{value:new THREE.Vector3()},
        swellingPower:{value:2.0}
    }

    function addMesh() {

        //注意这里必须旋转几何体,旋转了几何体,我们的数据才是正确的
        //mesh.rotation是在矩阵层面修改了旋转方向,最终会传递到modelViewMatrix中
        //顶点着色器的所有教程,除非特殊说明,否则全部使用旋转几何体

        let geometry = new THREE.PlaneGeometry(10,10,100,100).rotateX(-Math.PI/2);
        let material = new THREE.ShaderMaterial({
            uniforms,
            vertexShader:document.getElementById('vertexShader').textContent,
            fragmentShader:document.getElementById('fragmentShader').textContent,
            transparent:true,
            wireframe:true
        })
        let mesh = new THREE.Mesh(geometry,material);
        scene.add(mesh);

		//注意自己引入lil.gui
        let gui = new GUI();
        gui.add(uniforms.maxSwelling,'value',0,10).step(0.01).name('最大鼓包高度');
        gui.add(uniforms.swellingPower,'value',0,10).step(0.01).name('鼓包曲线');
        let folder = gui.addFolder('鼓包中心');
        folder.add(uniforms.swellingCenter.value,'x',-5,5);
        folder.add(uniforms.swellingCenter.value,'y',-5,5);
        folder.add(uniforms.swellingCenter.value,'z',-5,5);
    }

在这里插入图片描述

完整源码

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

    uniform float maxSwelling;
    uniform vec3 swellingCenter;
    uniform float swellingPower;

    void main(){
        vUv = vec2(uv.x,uv.y);
        vec3 aPosition = position;

        //计算鼓包点到顶点的距离
        float dis = distance(swellingCenter,aPosition);
        dis = clamp(dis,0.0,maxSwelling);
        dis = pow( dis / maxSwelling, swellingPower ) * maxSwelling;
        aPosition.y = maxSwelling - dis;

        vec4 mvPosition = modelViewMatrix * vec4( aPosition , 1.0 );
        gl_Position = projectionMatrix * mvPosition;
    }
</script>
<script type="x-shader/x-fragment" id="fragmentShader">
    varying vec2 vUv;
    void main(){
        gl_FragColor = vec4(1.0,0.0,0.0,1.0);
    }
</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 = {
        maxSwelling:{value:5.0},
        swellingCenter:{value:new THREE.Vector3()},
        swellingPower:{value:2.0}
    }

    function addMesh() {

        //注意这里必须旋转几何体,旋转了几何体,我们的数据才是正确的
        //mesh.rotation是在矩阵层面修改了旋转方向,最终会传递到modelViewMatrix中
        //顶点着色器的所有教程,除非特殊说明,否则全部使用旋转几何体

        let geometry = new THREE.PlaneGeometry(10,10,100,100).rotateX(-Math.PI/2);
        let material = new THREE.ShaderMaterial({
            uniforms,
            vertexShader:document.getElementById('vertexShader').textContent,
            fragmentShader:document.getElementById('fragmentShader').textContent,
            transparent:true,
            wireframe:true
        })
        let mesh = new THREE.Mesh(geometry,material);
        scene.add(mesh);

        let gui = new GUI();
        gui.add(uniforms.maxSwelling,'value',0,10).step(0.01).name('最大鼓包高度');
        gui.add(uniforms.swellingPower,'value',0,10).step(0.01).name('鼓包曲线');
        let folder = gui.addFolder('鼓包中心');
        folder.add(uniforms.swellingCenter.value,'x',-5,5);
        folder.add(uniforms.swellingCenter.value,'y',-5,5);
        folder.add(uniforms.swellingCenter.value,'z',-5,5);
    }


    function render() {
        renderer.render(scene,camera);
        orbit.update();
        requestAnimationFrame(render);
    }

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


http://www.kler.cn/a/412162.html

相关文章:

  • 【滑动窗口】找到字符串中所有字母异位词
  • Golang面经
  • SAP 零售方案 CAR 系统的介绍与研究
  • 如何使用Postman优雅地进行接口自动加密与解密
  • 基于FPGA的2FSK调制-串口收发-带tb仿真文件-实际上板验证成功
  • Elasticsearch面试内容整理-高级特性
  • 大语言模型LLM的微调代码详解
  • Flask项目中PostgreSQL与Elasticsearch的批量更新
  • LLM PPT Translator
  • 笔记:Centos Nginx Jdk Mysql OpenOffce KkFile Minio安装部署
  • BC-Linux8.6上面离线手动安装Docker引擎
  • 使用docker搭建hysteria2服务端
  • 类文件结构详解.下
  • Qt | 开发技能提升档次哈
  • GoogleTest做单元测试
  • [小白系列]Ubuntu安装教程-安装NodeJS
  • k8s认证、授权
  • C#基础56-60
  • unity使用笔记
  • Python 数据结构对比:列表与数组的选择指南
  • 研0找实习【学nlp】15---我的后续,总结(暂时性完结)
  • 11.26 深度学习-激活函数
  • MFC图形函数学习11——路径层函数
  • springcloud中bootstrap.yml配置文件
  • 北京航空航天大学多模态自适应攀岩机器人:突破复杂地形挑战
  • 【C++ 算法进阶】算法提升二十三