GLSL 代表 openGL Shading Language,它是着色器程序的特定标准,您将在接下来的章节中看到。根据硬件和操作系统,还有其他类型的着色器。在这里,我们将使用由Khronos Group监管的 openGL 规范。了解 OpenGL 的历史有助于理解其大部分奇怪的约定,为此我建议您查看:https://openglbook.com/chapter-0-preface-what-is-opengl.html
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
// console.log(THREE);
// 初始化场景
const scene = new THREE.Scene();
// 创建透视相机
const camera = new THREE.PerspectiveCamera(
90,
window.innerHeight / window.innerHeight,
0.1,
1000
);
// 设置相机位置
// object3d具有position,属性是1个3维的向量
camera.position.set(0, 0, 2);
// 更新摄像头
camera.aspect = window.innerWidth / window.innerHeight;
// 更新摄像机的投影矩阵
camera.updateProjectionMatrix();
scene.add(camera);
// 加入辅助轴,帮助我们查看3维坐标轴
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);
// 加载纹理
// const material = new THREE.MeshBasicMaterial({ color: "#00ff00" });
// 创建着色器材质
// vertexShader顶点着色器
//fragmentShader 片源着色器
const shaderMaterial = new THREE.ShaderMaterial({
vertexShader: ` // vertexShader顶点着色器
void main(){ // 投影矩阵(三维投影到二维,因为电脑屏幕是二维)<-视图矩阵<-模型矩阵
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4( position, 1.0 ) ; //vec4 四个维度
}
`,
fragmentShader: `//fragmentShader 片源着色器
void main(){
gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);
}
`,
});
// 创建平面
const floor = new THREE.Mesh(
new THREE.PlaneBufferGeometry(1, 1, 64, 64),
shaderMaterial
);
console.log(floor);
scene.add(floor);
// 初始化渲染器
const renderer = new THREE.WebGLRenderer({ alpha: true });
// renderer.shadowMap.enabled = true;
// renderer.shadowMap.type = THREE.BasicShadowMap;
// renderer.shadowMap.type = THREE.VSMShadowMap;
// 设置渲染尺寸大小
renderer.setSize(window.innerWidth, window.innerHeight);
// 监听屏幕大小改变的变化,设置渲染的尺寸
window.addEventListener("resize", () => {
// console.log("resize");
// 更新摄像头
camera.aspect = window.innerWidth / window.innerHeight;
// 更新摄像机的投影矩阵
camera.updateProjectionMatrix();
// 更新渲染器
renderer.setSize(window.innerWidth, window.innerHeight);
// 设置渲染器的像素比例
renderer.setPixelRatio(window.devicePixelRatio);
});
// 将渲染器添加到body
document.body.appendChild(renderer.domElement);
// 初始化控制器
const controls = new OrbitControls(camera, renderer.domElement);
// 设置控制器阻尼
controls.enableDamping = true;
// 设置自动旋转
// controls.autoRotate = true;
const clock = new THREE.Clock();
function animate(t) {
const elapsedTime = clock.getElapsedTime();
// console.log(elapsedTime);
requestAnimationFrame(animate);
// 使用渲染器渲染相机看这个场景的内容渲染出来
renderer.render(scene, camera);
}
animate();
vscode安装shader插件来写glsl文件就会高亮
01-shader/src/shader/basic/fragment.glsl
片源着色器
void main(){ //vec4 四个维度
gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);
}
01-shader/src/shader/basic/vertex.glsl
顶点着色器
void main(){ // 投影矩阵(三维投影到二维,因为电脑屏幕是二维)<-视图矩阵<-模型矩阵
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4( position, 1.0 ) ;
}
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
// 顶点着色器
import basicVertexShader from "../shader/basic/vertex.glsl";
// 片元着色器
import basicFragmentShader from "../shader/basic/fragment.glsl";
// 初始化场景
const scene = new THREE.Scene();
// 创建透视相机
const camera = new THREE.PerspectiveCamera(
90,
window.innerHeight / window.innerHeight,
0.1,
1000
);
// 设置相机位置
// object3d具有position,属性是1个3维的向量
camera.position.set(0, 0, 2);
// 更新摄像头
camera.aspect = window.innerWidth / window.innerHeight;
// 更新摄像机的投影矩阵
camera.updateProjectionMatrix();
scene.add(camera);
// 加入辅助轴,帮助我们查看3维坐标轴
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);
// const material = new THREE.MeshBasicMaterial({ color: "#00ff00" });
// 创建着色器材质
const shaderMaterial = new THREE.ShaderMaterial({
vertexShader: basicVertexShader,
fragmentShader: basicFragmentShader,
});
// 创建平面
const floor = new THREE.Mesh(
new THREE.PlaneBufferGeometry(1, 1, 64, 64),
shaderMaterial
);
console.log(floor);
scene.add(floor);
// 初始化渲染器
const renderer = new THREE.WebGLRenderer({ alpha: true });
// renderer.shadowMap.enabled = true;
// renderer.shadowMap.type = THREE.BasicShadowMap;
// renderer.shadowMap.type = THREE.VSMShadowMap;
// 设置渲染尺寸大小
renderer.setSize(window.innerWidth, window.innerHeight);
// 监听屏幕大小改变的变化,设置渲染的尺寸
window.addEventListener("resize", () => {
// console.log("resize");
// 更新摄像头
camera.aspect = window.innerWidth / window.innerHeight;
// 更新摄像机的投影矩阵
camera.updateProjectionMatrix();
// 更新渲染器
renderer.setSize(window.innerWidth, window.innerHeight);
// 设置渲染器的像素比例
renderer.setPixelRatio(window.devicePixelRatio);
});
// 将渲染器添加到body
document.body.appendChild(renderer.domElement);
// 初始化控制器
const controls = new OrbitControls(camera, renderer.domElement);
// 设置控制器阻尼
controls.enableDamping = true;
// 设置自动旋转
// controls.autoRotate = true;
function animate(t) {
requestAnimationFrame(animate);
// 使用渲染器渲染相机看这个场景的内容渲染出来
renderer.render(scene, camera);
}
animate();
着色器材质(ShaderMaterial)是一个用GLSL编写的小程序 ,在GPU上运行。它能够提供 materials 之外的效果,也可以将许多对象组合成单个Geometry或BufferGeometry以提高性能。
原始着色器材质(RawShaderMaterial) 此类的工作方式与ShaderMaterial类似,不同之处在于内置的uniforms和attributes的定义不会自动添加到GLSL shader代码中。
每个着色器材质都可以指定两种不同类型的shaders,他们是顶点着色器和片元着色器(Vertex shaders and fragment shaders)。
● 顶点着色器首先运行; 它接收attributes, 计算/操纵每个单独顶点的位置,并将其他数据(varyings)传递给片元着色器。
● 片元(或像素)着色器后运行; 它设置渲染到屏幕的每个单独的“片元”(像素)的颜色。
shader中有三种类型的变量: uniforms, attributes, 和 varyings
● Uniforms是所有顶点都具有相同的值的变量。 比如灯光,雾,和阴影贴图就是被储存在uniforms中的数据。 uniforms可以通过顶点着色器和片元着色器来访问。
● Attributes 与每个顶点关联的变量。例如,顶点位置,法线和顶点颜色都是存储在attributes中的数据。attributes 只 可以在顶点着色器中访问。
● Varyings 是从顶点着色器传递到片元着色器的变量。对于每一个片元,每一个varying的值将是相邻顶点值的平滑插值。
注意:在shader 内部,uniforms和attributes就像常量;你只能使用JavaScript代码通过缓冲区来修改它们的值。
上面说了每个着色器材质都可以指定两种不同类型的shaders,不过如果我们不去指定这两个shaders而直接使用也不会报错,因为ShaderMaterial已经定义了默认的顶点着色器和片元着色器,他们的代码是这样的。
//顶点着色器代码
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
//片元着色器代码
void main() {
gl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 );
}
这里的projectionMatrix、modelViewMatrix和position都是three为我们设置好的变量,可以直接拿来用,前两个变量我们之前已经说了,而position就是每一个顶点的坐标值,当着色器代码执行时,会循环执行gl_Position和gl_FragColor设置顶点位置,和颜色插值。并且我们最终要设置的就是gl_Position和gl_FragColor。多的先不说,下面看一个小例子。
var geom = new THREE.SphereGeometry(10, 30, 20);
var mate = new THREE.ShaderMaterial({
vertexShader: `
varying vec3 vNormal;
void main() {
//将attributes的normal通过varying赋值给了向量vNormal
vNormal = normal;
//projectionMatrix是投影变换矩阵 modelViewMatrix是相机坐标系的变换矩阵 最后我们将y值乘以1.4得到了一个形如鸡蛋的几何体
gl_Position = projectionMatrix * modelViewMatrix * vec4( position.x, position.y * 1.4, position.z, 1.0 );
}
`,
fragmentShader: `
//片元着色器同样需要定义varying vec3 vNormal;
varying vec3 vNormal;
void main() {
//vNormal是一个已经归一化的三维向量
float pr = (vNormal.x + 1.0) / 2.0; //pr红色通道值范围为0~1
float pg = (vNormal.y + 1.0) / 2.0; //pg绿色通道值范围为0~1
float pb = (vNormal.z + 1.0) / 2.0; //pb蓝色通道值范围为0~1
gl_FragColor=vec4(pr, pg, pb, 1.0); //最后设置顶点颜色,点与点之间会自动插值
}
`
})
var mesh = new THREE.Mesh(geom, mate);
scene.add(mesh)
这篇我们简单的操作顶点着色器和片元着色器绘制了一个五彩的鸡蛋,但是这还仅仅是一个静态的着色器
在顶点着色器中,将模型坐标单独提取出来:
vec4 modelPosition = modelMatrix * vec4(position, 1.0);
gl_Position = projectionMatrix * viewMatrix * modelPosition;
modelPosition就是我们说的模型坐标,即右手坐标系,注意它和空间直角坐标系是不同的。(图片来自网络)
对模型的xyz坐标进行处理,修改顶点着色器,对于每个点顶点:
void main() {
v_uv = uv; // uv坐标信息
vec4 modelPosition = modelMatrix * vec4(position, 1.0);
modelPosition.x += 1.0;
modelPosition.y += 1.0;
modelPosition.z += 0.1 * sin(modelPosition.x * 10.0);
gl_Position = projectionMatrix * viewMatrix * modelPosition;
}
threejs为了提高效率默认只渲染单面,需要把 side:THREE.DoubleSide给设置上
const shaderMaterial = new THREE.RawShaderMaterial({
// 投影矩阵 * 视图矩阵 * 模型矩阵 * 顶点坐标
vertexShader: vertexShader,
fragmentShader: fragmentShader,
side:THREE.DoubleSide
})
效果也如预计中的一样:
对模型的片元着色器再进行处理,按照Z坐标的大小为其设置颜色:
顶点着色器:
attribute vec3 position;
attribute vec2 uv;
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
varying vec2 v_uv;
varying float f_height;// 传递Z
precision lowp float;
void main() {
// v_uv = uv; // uv坐标信息
vec4 modelPosition = modelMatrix * vec4(position, 1.0);
modelPosition.x += 1.0;
modelPosition.y += 1.0;
modelPosition.z += 0.1 * sin(modelPosition.x * 10.0);
f_height = modelPosition.z // 放入传递的数据
gl_Position = projectionMatrix * viewMatrix * modelPosition;
}
片元着色器:
// varying vec2 v_uv;
varying float f_height; // 传递来的Z坐标
precision lowp float;
void main() {
float height = f_height + 1.0; // 创建变量接收数据
// gl_FragColor = vec4(v_uv, 0.0, 1.0);
gl_FragColor = vec4(height * 1.0, 0.0, 0.0, 1.0);
}
const texture = textureLoader.load("./texture/ca.jpeg");
const rawShaderMaterial = new THREE.RawShaderMaterial({
vertexShader: basicVertexShader,
fragmentShader: basicFragmentShader,
// wireframe: true,
side: THREE.DoubleSide,
uniforms: { // 传入时间
uTime: {
value: 0,
},
uTexture: {
value: texture,
},
},
});
function animate(t) {
const elapsedTime = clock.getElapsedTime();
// console.log(elapsedTime);
rawShaderMaterial.uniforms.uTime.value = elapsedTime; // 改变时间
requestAnimationFrame(animate);
// 使用渲染器渲染相机看这个场景的内容渲染出来
renderer.render(scene, camera);
}
01-shader/src/shader/raw/vertex.glsl
uTime时间传 进去
precision lowp float;
attribute vec3 position;
attribute vec2 uv;
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
// 获取时间
uniform float uTime;
varying vec2 vUv;
// highp -2^16 - 2^16
// mediump -2^10 - 2^10
// lowp -2^8 - 2^8
varying float vElevation;
void main(){
vUv = uv;
vec4 modelPosition = modelMatrix * vec4( position, 1.0 );
// modelPosition.x += 1.0;
// modelPosition.z += 1.0;
// modelPosition.z += modelPosition.x;
modelPosition.z = sin((modelPosition.x+uTime) * 10.0)*0.05 ;
modelPosition.z += sin((modelPosition.y+uTime) * 10.0)*0.05 ;
vElevation = modelPosition.z;
gl_Position = projectionMatrix * viewMatrix * modelPosition ;
}
01-shader/src/shader/raw/fragment.glsl
precision lowp float;
varying vec2 vUv;
varying float vElevation;
uniform sampler2D uTexture;
void main(){
// gl_FragColor = vec4(vUv, 0.0, 1.0);
// float height = vElevation + 0.05 * 10.0;
// gl_FragColor = vec4(1.0*height,0.0, 0.0, 1.0);
// 根据UV,取出对应的颜色
float height = vElevation + 0.05 * 20.0;
vec4 textureColor = texture2D(uTexture,vUv);
textureColor.rgb*=height;
gl_FragColor = textureColor;
}
开始学习three.js着色器材质时,我们经常会无从下手,辛苦写下的着色器,也会因莫名的报错而手足无措。原因是着色器材质它涉及到另一种语言–GLSL,只有懂了这个语言,我们才能更好的写出着色器材质,利用好的我们的GPU。这篇说一说glsl内置函数。
函数 | 参数 | 描述 |
---|---|---|
sin(x) | 弧度 | 正弦函数 |
cos(x) | 弧度 | 余弦函数 |
tan(x) | 弧度 | 正切函数 |
asin(x) | 弧度 | 反正弦函数 |
acos(x) | 弧度 | 反余弦函数 |
atan(x) | 弧度 | 反正切函数 |
radians(x) | 弧度 | 角度转换为弧度 |
degrees(x) | 弧度 | 弧度转换为角度 |
这类主要是对指数对数幂函数的操作
函数 | 描述 |
---|---|
pow(x,y) | x的y次方。如果x小于0,结果是未定义的。同样,如果x=0并且y<=0,结果也是未定义的。 |
exp(x) | e的x次方 |
log(x) | 计算满足x等于e的y次方的y的值。如果x的值小于0,结果是未定义的。 |
exp2(x) | 计算2的x次方 |
log2(x) | 计算满足x等于2的y次方的y的值。如果x的值小于0,结果是未定义的。 |
sqrt(x) | 计算x的开方。如果x小于0,结果是未定义的。 |
inversesqrt(x) | 计算x的开方之一的值,如果x小于等于0,结果是未定义的。 |
这里是常用函数,和js中的内置函数很像,需要牢记。
函数 | 描述 |
---|---|
abs(x) | 返回x的绝对值 |
sign(x) | 如果x>0,返回1.0;如果x=0,返回0,如果x<0,返回-1.0 |
floor(x) | 返回小于等于x的最大整数值 |
ceil(x) | 返回大于等于x的最小整数值 |
fract(x) | 返回x-floor(x),即返回x的小数部分 |
mod(x) | 返回x和y的模 |
min(x) | 返回x和y的值较小的那个值。 |
max(x) | 返回x和y的值较大的那个值。 |
clamp(x, minVal, maxVal) | 将x值钳于minVal和maxVal之间,意思就是当x |
mix(x, y, a) | 返回线性混合的x和y,如:x*(1−a)+y*a |
step(edge, x) | 如果x < edge,返回0.0,否则返回1.0 |
smoothstep(edge0, edge1, x) | 如果x <= edge0,返回0.0 ;如果x >= edge1 返回1.0;如果edge0 < x < edge1,则执行0~1之间的平滑埃尔米特差值。如果edge0 >= edge1,结果是未定义的。 |
这是与长度、距离、向量等相关的函数
函数 | 描述 |
---|---|
length(x) | 返回向量x的长度 |
distance(p0,p1) | 计算向量p0,p1之间的距离 |
dot | 向量x,y之间的点积 |
cross(x, y) | 向量x,y之间的叉积 |
normalize(x) | 标准化向量,返回一个方向和x相同但长度为1的向量 |
faceforward(N, I, Nref) | 如果Nref和I的点积小于0,返回N;否则,返回-N; |
reflect(I, N) | 返回反射向量 |
refract(I, N, eta) | 返回折射向量 |
经常用的函数差不多就是这些。还需要我们在实践中反复练习,才能使用的得心应手。
随机数
https://thebookofshaders.com/10/?lan=ch
02-three_shader图形/src/shaders/deep/vertex.glsl
顶点着色器
varying vec2 vUv;
// highp -2^16-2^16
// mediump = -2^10-2^10
// lowp -2^8-2^8
precision lowp float;
void main(){
vec4 modelPosition = modelMatrix * vec4( position, 1.0 );
vUv=uv; // uv自动获取,相当于(0,0)、(1,0)、(1,1)、(0,1) 四点坐标
gl_Position = projectionMatrix * viewMatrix * modelPosition;
}
// uv自动获取,相当于(0,0)、(1,0)、(1,1)、(0,1) 四点坐标
02-three_shader图形/src/shaders/deep/fragment.glsl
precision lowp float;
varying vec2 vUv;
void main(){
// 1通过顶点对应的uv,决定每一个像素在uv图像的位置,通过这个位置x,y决定颜色
// rgba(0,0,0,1)、rgba(1,0,0,1)、rgba(1,1,0,1)、rgba(0,1,0,1)
gl_FragColor =vec4(vUv,0,1) ;
}
02-three_shader图形/src/shaders/deep/fragment.glsl
precision lowp float;
varying vec2 vUv;
void main(){
// 1通过顶点对应的uv,决定每一个像素在uv图像的位置,通过这个位置x,y决定颜色
// rgba(0,0,1,1)、rgba(1,0,1,1)、rgba(1,1,1,1)、rgba(0,1,1,1)
gl_FragColor =vec4(vUv,1,1) ;
}
precision lowp float;
varying vec2 vUv;
void main(){
gl_FragColor =vec4(vUv.x,vUv.x,vUv.x,1) ;
}
precision lowp float;
varying vec2 vUv;
void main(){
float strength = vUv.y;
gl_FragColor =vec4(strength,strength,strength,1);
}
precision lowp float;
varying vec2 vUv;
void main(){
float strength = 1.0 - vUv.y;
gl_FragColor =vec4(strength,strength,strength,1);
}
precision lowp float;
varying vec2 vUv;
void main(){
float strength = mod(vUv.y * 10.0 , 1.0) ; // 取模 0.1,0.2,0.3....->0.1,0.2,0.3....
gl_FragColor =vec4(strength,strength,strength,1);
}
precision lowp float;
varying vec2 vUv;
void main(){
float strength = mod(vUv.y * 10.0 , 1.0);
strength = step(0.5,strength);
gl_FragColor =vec4(strength,strength,strength,1);
}
precision lowp float;
varying vec2 vUv;
void main(){
float strength = step(0.8, mod(vUv.x * 10.0 , 1.0)) ;
strength += step(0.8, mod(vUv.y * 10.0 , 1.0)) ;
gl_FragColor =vec4(strength,strength,strength,1);
}
precision lowp float;
varying vec2 vUv;
void main(){
float strength = step(0.8, mod(vUv.x * 10.0 , 1.0)) ;
strength *= step(0.8, mod(vUv.y * 10.0 , 1.0)) ;
gl_FragColor =vec4(strength,strength,strength,1);
}
precision lowp float;
varying vec2 vUv;
void main(){
float strength = step(0.2, mod(vUv.x * 10.0 , 1.0)) ;
strength *= step(0.2, mod(vUv.y * 10.0 , 1.0)) ;
gl_FragColor =vec4(strength,strength,strength,1);
}
precision lowp float;
varying vec2 vUv;
void main(){
float strength = abs(vUv.x - 0.5) ;
gl_FragColor =vec4(strength,strength,strength,1);
}
precision lowp float;
varying vec2 vUv;
void main(){
float strength =min(abs(vUv.x - 0.5), abs(vUv.y - 0.5)) ;
gl_FragColor =vec4(strength,strength,strength,1);
}
precision lowp float;
varying vec2 vUv;
void main(){
float strength =step(0.2,max(abs(vUv.x - 0.5), abs(vUv.y - 0.5)));
gl_FragColor =vec4(strength,strength,strength,1);
}
precision lowp float;
varying vec2 vUv;
void main(){
float strength = floor(vUv.y*10.0)/10.0;
gl_FragColor =vec4(strength,strength,strength,1);
}
precision lowp float;
varying vec2 vUv;
void main(){
float strength = floor(vUv.x*10.0)/10.0*floor(vUv.y*10.0)/10.0;
gl_FragColor =vec4(strength,strength,strength,1);
}
precision lowp float;
varying vec2 vUv;
// 随机函数
float random (vec2 st) {
return fract(sin(dot(st.xy,vec2(12.9898,78.233)))*43758.5453123);
}
strength = random(vec2(strength,strength));
gl_FragColor =vec4(strength,strength,strength,1);
precision lowp float;
varying vec2 vUv;
float strength = length(vUv);
gl_FragColor =vec4(strength,strength,strength,1);
precision lowp float;
varying vec2 vUv;
float strength =1.0 - distance(vUv,vec2(0.5,0.5));
gl_FragColor =vec4(strength,strength,strength,1);
precision lowp float;
varying vec2 vUv;
float strength =0.15 / distance(vUv,vec2(0.5,0.5)) - 1.0;
gl_FragColor =vec4(strength,strength,strength,1);
precision lowp float;
varying vec2 vUv;
float strength = 0.15 / distance(vec2(vUv.x,(vUv.y-0.5)*5.0+0.5),vec2(0.5,0.5)) - 1.0;
strength += 0.15 / distance(vec2(vUv.y,(vUv.x-0.5)*5.0+0.5),vec2(0.5,0.5)) - 1.0;
gl_FragColor =vec4(strength,strength,strength,strength);
数学 派->3.14
precision lowp float;
varying vec2 vUv;
// 旋转函数
vec2 rotate(vec2 uv, float rotation, vec2 mid)
{
return vec2(
cos(rotation) * (uv.x - mid.x) + sin(rotation) * (uv.y - mid.y) + mid.x,
cos(rotation) * (uv.y - mid.y) - sin(rotation) * (uv.x - mid.x) + mid.y
);
}
vec2 rotateUv = rotate(vUv,3.14*0.25,vec2(0.5));
vec2 rotateUv = rotate(vUv,-uTime*5.0,vec2(0.5));
float strength = 0.15 / distance(vec2(rotateUv.x,(rotateUv.y-0.5)*5.0+0.5),vec2(0.5,0.5)) - 1.0;
strength += 0.15 / distance(vec2(rotateUv.y,(rotateUv.x-0.5)*5.0+0.5),vec2(0.5,0.5)) - 1.0;
gl_FragColor =vec4(strength,strength,strength,strength);