什么是glsl
众所周知threejs是对webGL的封装,而webGL又是openGL的web版本,而GL则是使用GLSL(OpenGL Shading Language)这种语言来描述。
这里有两张图描述这种关系
所以其实threejs也是对glsl的一种封装,但是threejs也保留了对glsl的接口便于我们直接书写glsl来更好的从底层的描述我们的图像
着色器
在GL中绘图我们就必须要使用2种着色器程序
顶点着色器
顶点着色器是用来描述顶点特性(如位置、颜色等)的程序。顶点是指二维或三维空间中的一个点,比如二维或三维图形的端点和交点
顶点是一系列的坐标点,因为一个图形是有大量的三角形构成的,每一个三角形就有3个顶点
顶点着色器的输入是一系列的顶点信息,比如坐标颜色等,然后将这些信息进行处理之后输出给片源着色器
片元着色器
进行逐片元处理过程如光照的程序。片元是一个WebGL术语,你可以将其理解为像素(图像的单元)
在顶点着色器运行完毕之后其实并不是立马执行片元着色器,这个时候回有一部光栅化的过程。什么是光栅化呢?在未光栅化之前,图形是连续的,但是显示器上面的像素是离散的,光栅化就是将连续的图形转化为离散的“像素”,这个一个像素就是一个片元,当然和真正的像素还是有所区别,比如完全重叠的2个图形按像素看就是一份像素,按片元看就要分为“上面的”片元和“下面的”片元
总的来说一个渲染流程入下图所示
如何在theejs中使用glsl
我们这里假装你已经熟知了glsl的语法,欠缺的只是如何把threejs和glsl凑合到一起(如果不熟悉的可以移步这里)
先来展示一段代码
import * as THREE from 'three'
const scene = new THREE.Scene();//创建一个场景
const camera = new THREE.PerspectiveCamera(50, window.innerWidth/window.innerHeight, 0.1, 1000);
camera.position.z=1//因为刚刚创建的对象都会位于原点,我们需要把相机拉远一点
//创建一个渲染器,并设置大小,然后把这个渲染器加入到dom中
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const vertices = new Float32Array([
0.0, 0.0, 0.0,//顶点
]);
//将顶点数组放入缓冲区域
const verticesPosition = new THREE.BufferAttribute( vertices, 3 );
//创建一个空白的模型
const geometry = new THREE.BufferGeometry();
//将顶点数据传入到空白模型中
geometry.addAttribute('position',verticesPosition);
//使用着色器材质
const material=new THREE.ShaderMaterial({
vertexShader: `void main( void ) {
gl_Position = vec4( position, 1.0 );
gl_PointSize = 100.0;
}`,
fragmentShader:`void main() {
gl_FragColor = vec4(0,0,0.9,1.0);
}`,
});
const points=new THREE.Points(geometry,material);//模型对象
scene.add( points )//场景中加入多面体
renderer.render(scene, camera);
这段代码的效果
我们来解释一下上面这段代码和之前的不一样的部分
const vertices = new Float32Array([
0.0, 0.0, 0.0,//顶点
]);
这是一个顶点数数组,JavaScript中的普通数组是无类型的,但是有时候js可能也会与其他强类型的语言沟通,所以不得不需要有类型的数组,因此创建了这一系列的api用来代替类型数组,一个顶点由4个数据组成x、y、z、w,这4个数据组成的坐标称为齐次坐标,等价于三维坐标x/w、y/w、z/w,因为w的值一般是1.0所以就可以省略一个位置,这个1.0在着色器中写死。x/y/z/w的取值范围是[-1.0,1.0]
const verticesPosition = new THREE.BufferAttribute( vertices, 3 );
//创建一个空白的模型
const geometry = new THREE.BufferGeometry();
//将顶点数据传入到空白模型中
geometry.addAttribute('position',verticesPosition);
BufferAttribute方法用于将顶点数据转化为Threejs需要的对象,第二个参数表示3个一组
BufferGeometry空白的缓存几何对象,里面不含任何顶点数据
addAttribute向该几何对象添加属性第一个参数是名称,第二个参数是值。
值得一提的是,这里使用了position作为名称传递给几何对象,这个键值对最终会传递到顶点着色器里面,而顶点着色器必须使用attribute vec3 position;这种方式去接受,但是我们这里却并没有在着色器定义这个变量,原因是因为threejs预先帮我们定义好了一系列变量,其中就包括position,因此如果传无关变量,就需要自己定义了。顺带一提使用着色器材质也可以传递参数而且没这么麻烦
//使用着色器材质
const material=new THREE.ShaderMaterial({
vertexShader: `void main( void ) {
gl_Position = vec4( position, 1.0 );
gl_PointSize = 100.0;
}`,
fragmentShader:`void main() {
gl_FragColor = vec4(0,0,0.9,1.0);
}`,
});
这部分就是我们这次的主角
ShaderMaterial着色器材质有2个配置项一个是vertexShader(顶点着色器),一个是fragmentShader(片元着色器)。
在顶点着色器中我们接到了之前传过来的position并添上"1.0"成为齐次坐标传递给gl_Position ,然后设定点的大小是100.0
在片元着色器中我们将颜色(这里的颜色是rgba,参数是[0.0,1.0])设定为蓝色冰传递给gl_FragColor
const points=new THREE.Points(geometry,material);//模型对象
最后我们有了模型有了材质,2者一结合就形成了我们看到了一个100像素蓝色点的效果
附录
Threejs在顶点着色器中预定义的变量
precision highp float;
precision highp int;
#define HIGH_PRECISION
#define SHADER_NAME ShaderMaterial
#define VERTEX_TEXTURES
#define GAMMA_FACTOR 2
#define MAX_BONES 0
#define BONE_TEXTURE
uniform mat4 modelMatrix;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
uniform mat3 normalMatrix;
uniform vec3 cameraPosition;
attribute vec3 position;
attribute vec3 normal;
attribute vec2 uv;
#ifdef USE_TANGENT
attribute vec4 tangent;
#endif
#ifdef USE_COLOR
attribute vec3 color;
#endif
#ifdef USE_MORPHTARGETS
attribute vec3 morphTarget0;
attribute vec3 morphTarget1;
attribute vec3 morphTarget2;
attribute vec3 morphTarget3;
#ifdef USE_MORPHNORMALS
attribute vec3 morphNormal0;
attribute vec3 morphNormal1;
attribute vec3 morphNormal2;
attribute vec3 morphNormal3;
#else
attribute vec3 morphTarget4;
attribute vec3 morphTarget5;
attribute vec3 morphTarget6;
attribute vec3 morphTarget7;
#endif
#endif
#ifdef USE_SKINNING
attribute vec4 skinIndex;
attribute vec4 skinWeight;
#endif