【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来研究和学习

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

相关推荐
汪洪墩5 小时前
【Mars3d】设置backgroundImage、map.scene.skyBox、backgroundImage来回切换
开发语言·javascript·python·ecmascript·webgl·cesium
m0_748234347 小时前
webGL硬核知识:图形渲染管渲染流程,各个阶段对应的API调用方式
图形渲染·webgl
MossGrower1 天前
36. Three.js案例-创建带光照和阴影的球体与平面
3d图形·webgl·three.js·光照与阴影
MossGrower1 天前
34. Three.js案例-创建球体与模糊阴影
webgl·three.js·3d渲染·阴影效果
程序员_三木3 天前
Three.js资源-贴图材质网站推荐
javascript·webgl·three.js·材质·贴图
gis分享者3 天前
学习threejs,scene.overrideMaterial全局材质效果
threejs·全局材质·overridemater
程序员_三木3 天前
React和Three.js结合-React Three Fiber
前端·javascript·react.js·前端框架·webgl·材质
MossGrower3 天前
37. Three.js案例-绘制部分球体
3d图形·webgl·three.js·球体几何体
吃豆腐长肉3 天前
着色器 (三)
opengl·着色器
吃豆腐长肉3 天前
opengl 着色器 (四)最终章收尾
opengl·着色器