【Threejs进阶教程-着色器篇】1. Shader入门(ShadertoyShader和ThreejsShader入门)

ThreejsShader入门

关于本Shader教程

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

认识Shader

能百度到的我这里就不废话了,自行百度 WebGL,OpenGL,OpenGL ES,GLSL,这些可以帮助你了解shader的历史和发展以及版本关系

Shader和Threejs的关系

shader是Threejs的底层,在前后关系上,Shader要更底层,Threejs的渲染器部分,主要基于Shader来开发

WebGLShader

最原始版本的Shader,在学习《WebGL编程指南》时会经常接触

ThreejsShader

ThreejsShader,特指 THREE.ShaderMaterial和THREE.RawShaderMaterial,这个是Threejs最核心的内容之一,基本上所有的特效物体,特效粒子,都可以使用Shader来制作,在threejs的ShaderMaterial中,内置了很多方便的常用的变量,提供给你访问,如果你不想使用Threejs内置的变量,那么用THREE.RawShaderMaterial即可

ShadertoyShader

基于Shadertoy网站的Shader,由Shadertoy网站提供了大量的内置变量,以编写片元着色器为主

(但是shadertoy上的大佬,早就不满足于只写片元着色器了,在里面加入了各种各样牛逼的写法,什么光线追踪,3d化的各种效果,还有人在里面做游戏的,也有人就纯靠代码写出来超级逼真的场景,比如说iq大佬,而且全部开源,是个非常适合学Shader的地方)

本系列教程,以ShadertoyShader和ThreejsShader为主,可能部分写法不太适用于其他的shader

其他Shader

cesium,babylon,pixi均内置了自己的shader系统,但是所有的shader,基本语法都是一样的,只有内置变量的区别,所以学会了原生Shader基本上可以通吃整个webgl圈子的shader

所以如果你想要学习原生的Shader,建议从《WebGL开发指南》开始学起

再次劝退数学不好的人

Shader难度很高,对数学能力要求很高,如果你已经做好了心理准备,那么我们接下来,就准备开始吧

从ShaderToy开始

ShaderToy的入门案例

但是,这里本人要改一下,改的更简单一点,方便入门

js 复制代码
	void mainImage( out vec4 fragColor, in vec2 fragCoord )
	{
	    vec2 uv = fragCoord/iResolution.xy;
	
	    fragColor = vec4(uv.x,0.0,0.0,1.0);
	}

把这一段代码,替换到Shadertoy中,然后点击编辑窗左下角的播放键,即可得到左边的渐变红色效果

我们来对上述代码做一个分析

Shader的代码是强类型

很多人直接就反应了,这个代码根本看不懂,因为根本就不是 JS语言

首先,我们看到第一行,代码是==void mainImage( out vec4 fragColor, in vec2 fragCoord ) ==

有没有发现,这个代码很像是我们学习的C语言的HelloWorld

glsl是强类型语言,所以我们无论什么时候,都要遵循强类型语言的规则

现阶段,我们只需要死记硬背,第一行是Shadertoy必须要写的内容即可

然后我们开始分析第二行代码,vec2 uv = fragCoord/iResolution.xy;

这里声明了一个变量,vec2 uv

glsl的类型,变量,内置函数,关键字

这里本人就懒得手敲了,《WebGL编程指南》187页~215页,非常多,这里本人仅截图出来第一张 ,后面的请自行去群里下载电子书,或者买一本实体书来阅读
什么群?基本上所有的Threejs/WebGL的qq群文件里都有相关电子书,非常好找,随便加一个就行了

内置函数在 427~436页,内置函数表在后续的开发中会经常查阅,所以建议学习Shader的人自行购买一本实体书
还有,内置变量啊,内置函数啊,现阶段就看一遍就行了,知道哪些是关键字,避开就行了

实在懒得看书的就看我这里的解释吧。。。

这里仅介绍本篇教程中用到的变量和对象

vec2,其实是一个缩写,完整单词是Vector2,二维向量,这里的uv实际上就是一个二维向量,一个二维向量由 x,y两个数据构成

然后后面的 fragCoord/iResolution.xy,这里,现阶段不做讲解,只需要记住,shadertory第二行必须是这个就行了,后面在threejs的开发中,这一行也不会发生大的改变,如果你需要更详细的了解和学习Shadertoy,再去考虑研究 fragCoord和iResolution的作用,最终的计算结果,就是uv

注意:shadertoy,以及后续所有的Shader代码每一行结尾必须加分号!!!!!!!!

关于uv

uv这个概念,在前面介绍纹理篇已经简单介绍过了,所以还不懂uv概念的,回去补补课了

在Shadertoy中,已经通过内置的计算,把uv给你端上来了,但是shadertoy的uv和我们传统意义上的uv的直观感受不太一样,一般我们在讲解uv的时候,长宽是一样长的,但是shadertoy中不同

基于UV的颜色处理

接下来进入最重要的一行了 fragColor = vec4(uv.x,0.0,0.0,1.0);

首先,在Shadertoyu中,fragColor 代表最终输出,也就是说,无论你怎么编写这里的代码,都必须要有一个最终输出,这个最终输出需要你传入一个 vec4类型的变量,就是最终颜色,对应颜色的rgba

vec4在用作最终变量的时候,4个值分别对应 红色百分比,绿色百分比,蓝色百分比,透明度

我们从代码中可以得到,我们把uv的x值,作为红色的百分比值,传入到了最终输出中,然后绿色和蓝色保持为0,透明度为1,所以整个效果都呈现红色,只有强弱的区别

但是,这个效果并不是纯红色,因为uv的值是在变的,画面最左侧的uv的u值(代码中是uv.x),是0,左右侧的u值为1,所以最终呈现出了这样的一个渐变红色的效果

我们把uv.x放到第二个参数上,看看效果

我们把uv.y 作为参数传进去,看看效果

这样我们就完成了Shader的入门级案例,基于uv的颜色渐变

最基本的ThreeJS Shader

Shader最最基本的案例,我们已经完成了,那么现在我们进入下一阶段,将这个效果应用于Threejs

首先,我们依然要准备一个demo

js 复制代码
<!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;
    void main(){
        gl_FragColor = vec4(vUv.x,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";

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

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

    let uniforms = {

    }

    function addMesh() {
        let geometry = new THREE.PlaneGeometry(10,10);
        let material = new THREE.ShaderMaterial({
            uniforms,
            vertexShader:document.getElementById('vertexShader').textContent,
            fragmentShader:document.getElementById('fragmentShader').textContent,
        })
        let mesh = new THREE.Mesh(geometry,material);
        scene.add(mesh);
    }


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

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

demo效果如下

中间的那条线,是在代码中创建的网格辅助线

ShaderMaterial介绍

ShaderMaterial官方文档
RawShaderMaterial官方文档

大多数情况下都用Shadermaterial,内置的东西已经足够用了,RawShaderMaterial只是用了更原生的方式,来编写Shader

现阶段给你文档也不一定看得懂,只需要记得,下面提到的,现阶段死记硬背的东西,就死记硬背下来,按照这个格式写就行了

代码拆解

创建ShaderMaterial,至少要有三个要点

uniforms(参数集)(本篇不讲)

uniforms是一个对象,而且,threejs对uniforms有严格要求,比如说下面的写法:

let uniforms = {

opacity:{ value: 1.0 },

aPosition:{value: new THREE.Vector3()}

}

uniforms的参数,必须是 [key] : { value : [value] },后续会详细讲解uniforms的用法用途

vertexShader(顶点着色器)(现阶段不讲)

threejs这里需要的是一个字符串,这个字符串,就是glsl的代码
现阶段,默认顶点着色器代码固定 ,且只需要知道,创建ShaderMaterial必须要有一个顶点着色器即可 ,通过document.getElementById('vertexShader').textContent

来获取上面id为 vertexShader的dom,并通过.textContent,来获取到内部编写的文本代码

html 复制代码
<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>

fragmentShader(片元着色器)代码讲解

现阶段主要编写的代码,通过
document.getElementById('fragmentShader').textContent

来获取上面id为 fragmentShader的dom,并通过.textContent,来获取到内部编写的文本代码

html 复制代码
<script type="x-shader/x-fragment" id="fragmentShader">
    varying vec2 vUv;
    void main(){
        gl_FragColor = vec4(vUv.x,0.0,0.0,1.0);
    }
</script>

这里我们先介绍片元着色器的代码,等后续进入到顶点着色器的篇章中,再详细介绍顶点着色器

首先,varying vec2 vUv; ,现阶段视为片元着色器的固定代码,只需要关注下面的即可

我们对比一下和ShaderToy的代码结构

首先,这里我们依然是需要一个main,但是在Threejs的Shader中,写法变更为 main(),而且不需要传入参数(为什么不需要传入参数,这个后续讲解ShadertoyShader时会讲)

然后同样的,threejs中,也需要一个最终输出,只不过,这个最终输出写为 gl_FragColor,参数与Shadertoy一致

我们的代码,采用在Shadertoy的代码的第一版,依然是用vUv.x去控制红色

在Threejs中,uv是内置变量,所以我们为了避免冲突,在这里写成vUv,实际作用与在Shadertoy里面的uv是一致的

现阶段的效果进阶

我们现在改一下代码,改动一点点即可

html 复制代码
<script type="x-shader/x-fragment" id="fragmentShader">
    varying vec2 vUv;
    void main(){
        gl_FragColor = vec4(1.0 - vUv.y,0.0,0.0,1.0 - vUv.y);
    }
</script>

然后改动一下下面的ShaderMaterial

js 复制代码
        let material = new THREE.ShaderMaterial({
            uniforms,
            vertexShader:document.getElementById('vertexShader').textContent,
            fragmentShader:document.getElementById('fragmentShader').textContent,
            transparent:true,
            side:THREE.DoubleSide
        });

这样,我们就得出了一个这样的效果


然后,我们在代码中,创建4个一样的平面,然后做一下旋转,拼成一个围墙

js 复制代码
    function addMesh() {
        let geometry = new THREE.PlaneGeometry(10,10);
        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,5,0);
        mesh1.rotation.y = Math.PI/2;
        scene.add(mesh1);

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

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

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

这个就是最基础的光栅栏效果,各位可以自行修改在片元着色器中的颜色,来修改颜色

但是,这个光栅栏不是最终版本,请阅读后续教程来继续优化效果

ThreejsShader和ShadertoyShader的巧妙使用

上面我们展示了,如何编写最基本的shader,不难看出,我们在编辑片元着色器的时候,是可以借助Shadertoy来编辑的,很多shadertoy的2d效果,也可以被搬到Threejs中现用,但是仅限于你要看懂,且以uv为基准的那些,所以,如果你想要编辑一些2D的,以及动态贴图效果,都可以借助Shadertoy来研究和学习

本系列教程更新速度可能较慢,还请耐心等待

相关推荐
gis分享者16 小时前
学习threejs,导入FBX格式骨骼绑定模型
threejs·骨骼动画·fbx
小彭努力中18 小时前
138. CSS3DRenderer渲染HTML标签
前端·深度学习·3d·webgl·three.js
小春熙子1 天前
Unity图形学之着色器之间传递参数
unity·游戏引擎·技术美术·着色器
优雅永不过时·1 天前
three.js实现地球 外部扫描的着色器
前端·javascript·webgl·three.js·着色器
汪洪墩1 天前
【Mars3d】实现这个地图能靠左,不居中的样式效果
前端·javascript·vue.js·3d·webgl·cesium
gis分享者2 天前
学习threejs,对模型多个动画切换展示
threejs·动画切换
allenjiao2 天前
webgl threejs 云渲染(服务器渲染、后端渲染)解决方案
webgl·云渲染·threejs·服务器渲染·后端渲染·云流化·三维云渲染
踏实探索2 天前
OpenLayers教程12_WebGL自定义着色器:实现高级渲染效果
前端·arcgis·vue·webgl·着色器
EasyNTS3 天前
H.265流媒体播放器EasyPlayer.js网页直播/点播播放器WebGL: CONTEXT_LOST_WEBGL错误引发的原因
javascript·webgl·h.265
那年那棵树4 天前
【Cesium】自定义材质,添加带有方向的滚动路线
vue·webgl·材质