学习threejs shader的应该是会用threejs的,所以threejs代码不做讲解。首先我们使用threejs代码构建基础场景
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* {
margin: 0;
padding: 0;
}
canvas {
width: 100%;
height: 100%
}
</style>
</head>
<body>
</body>
<script src="./three.js">
</script>
<script>
const scene = new THREE.Scene();
const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0.1, 10);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const geometry = new THREE.PlaneGeometry(2, 2);
const uniforms = {
}
const material = new THREE.ShaderMaterial({
});
const plane = new THREE.Mesh(geometry, material);
scene.add(plane);
camera.position.z = 1;
onWindowResize();
window.addEventListener("resize", onWindowResize, false);
animate();
function onWindowResize(event) {
const aspectRatio = window.innerWidth / window.innerHeight;
let width, height;
if (aspectRatio >= 1) {
width = 1;
height = (window.innerHeight / window.innerWidth) * width;
} else {
width = aspectRatio;
height = 1;
}
camera.left = -width;
camera.right = width;
camera.top = height;
camera.bottom = -height;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
</script>
</html>
shader代码分为片源着色器和顶点着色器,片元和顶点简单点来说就是颜色和位置的区别,我们先从片元着色器入手,后期讲顶点着色器,而这其中每个顶点都会执行着色器代码,比如有1000个顶点,下面的每个main函数都会执行1000次。
const vshader = `
void main(){
gl_Position=projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
`;
const fshader = `
void main(){
gl_FragColor=vec4(0.0,1.0,0.0,1.0);
}
`;
//把 ShaderMaterial 加上参数
const material = new THREE.ShaderMaterial({
vertexShader: vshader,
fragmentShader: fshader,
});
gl_FragColor内置变量主要用来设置片元像素的颜色,vec4(r,g,b,a),前三个参数表示片元像素颜色值RGB,第四个参数是片元像素透明度A,1.0表示不透明,0.0表示完全透明。
注意 shader代码 是强类型语言,1是整数,1.0是浮点数,类型不同不能做加减乘除运算。
vec4是向量类型,例如 vec4 a=vec4(0.1,0.2,0.3,0.4);取值可以a.x=0.1;a.r=0.1;vec4 b=a*2.0=vec4(0.2,0.4,0.6,0.8);
接下来我们使用内置变量position来改变颜色。
const vshader = `
varying vec3 v_position;
void main(){
v_position=position;
gl_Position=projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
`;
const fshader = `
varying vec3 v_position;
void main(){
gl_FragColor=vec4(v_position,1.0);
}
`;
varying关键字,主要用于变量在两个着色器之间使用,顶点着色器的变量是不能在片元着色器中使用的,要使用顶点着色器中的变量需要加varying关键字,同时position是写好的内置变量,只存在于顶点着色器。因为z方向为0(threejs平面模型的z为0),在二维平面中,中间点是(0,0),左下角是(-1,-1) ,右上角(1,1)。position的值完全等于顶点在空间中的位置。
可以修改 const geometry = new THREE.PlaneGeometry(2, 2); 把(2,2)改成(1,1)或(0.5,0.5)看看效果。
接下来根据position的值画一个正方形。
const fshader = `
varying vec3 v_position;
float rect(vec2 pt,vec2 wh){
vec2 rangeMax=wh/2.0;
vec2 rangeMin=wh/-2.0;
return step(rangeMin.x,pt.x)*step(pt.x,rangeMax.x)*step(rangeMin.y,pt.y)*step(pt.y,rangeMax.y);
}
void main(){
vec3 color=vec3(1.0,0.0,0.0) * rect(v_position.xy,vec2(0.5));
gl_FragColor=vec4(color,1.0);
}
`;
step(a, b);当b > a时, 返回1;当b < a时,返回0。
v_position如果等于vec3(0.2,0.2,0);那么 v_position.xy = vec2(0.2,0.2)。
step(rangeMin.x,pt.x)*step(pt.x,rangeMax.x)*step(rangeMin.y,pt.y)*step(pt.y,rangeMax.y); 这段代码含义其实就是判断传入的x,y 是否在-0.25~0.25之间(传入的宽高是0.5)
vec2(0.5) 是vec2(0.5,0.5)的简写方式。
接下来加一点点旋转。
const fshader = `
varying vec3 v_position;
float rect(vec2 pt,vec2 wh){
// 0.7853 45°
mat2 rotate = mat2(
cos(0.7853), -sin(0.7853),
sin(0.7853), cos(0.7853)
);
pt = rotate*pt;
vec2 rangeMax= wh/2.0;
vec2 rangeMin= wh/-2.0;
return step(rangeMin.x,pt.x)*step(pt.x,rangeMax.x)*step(rangeMin.y,pt.y)*step(pt.y,rangeMax.y);
}
void main(){
vec3 color=vec3(1.0,0.0,0.0) * rect(v_position.xy,vec2(0.5));
gl_FragColor=vec4(color,1.0);
}
`;
旋转这里涉及到一些数学,讲我肯定是讲不清楚的,建议多百度多百度,有threejs基础,应该接触过矩阵的旋转平移缩放之类的吧。可以看看threejs官网Matrix4的旋转平移缩放。
接下来我们改变正方形的位置。
const fshader = `
varying vec3 v_position;
float rect(vec2 pt,vec2 wh,vec2 center){
vec2 rangeMax=center + wh/2.0;
vec2 rangeMin=center - wh/2.0;
return step(rangeMin.x,pt.x)*step(pt.x,rangeMax.x)*step(rangeMin.y,pt.y)*step(pt.y,rangeMax.y);
}
void main(){
vec3 color=vec3(1.0,0.0,0.0) * rect(v_position.xy,vec2(0.5),vec2(0.5));
gl_FragColor=vec4(color,1.0);
}
`;
接下来,来一点点旋转
const fshader = `
varying vec3 v_position;
float rect(vec2 pt,vec2 wh,vec2 center){
// 0.7853 45°
mat2 rotate = mat2(
cos(0.7853), -sin(0.7853),
sin(0.7853), cos(0.7853)
);
pt = rotate*(pt-center)+center;
vec2 rangeMax=center + wh/2.0;
vec2 rangeMin=center - wh/2.0;
return step(rangeMin.x,pt.x)*step(pt.x,rangeMax.x)*step(rangeMin.y,pt.y)*step(pt.y,rangeMax.y);
}
void main(){
vec3 color=vec3(1.0,0.0,0.0) * rect(v_position.xy,vec2(0.5),vec2(0.5));
gl_FragColor=vec4(color,1.0);
}
`;
接下来让矩形根据时间旋转,也就是把0.7853替换为时间变量。
const fshader = `
varying vec3 v_position;
uniform float u_time;
float rect(vec2 pt,vec2 wh,vec2 center){
// 0.7853 45°
mat2 rotate = mat2(
cos(u_time), -sin(u_time),
sin(u_time), cos(u_time)
);
pt = rotate*(pt-center)+center;
vec2 rangeMax=center + wh/2.0;
vec2 rangeMin=center - wh/2.0;
return step(rangeMin.x,pt.x)*step(pt.x,rangeMax.x)*step(rangeMin.y,pt.y)*step(pt.y,rangeMax.y);
}
void main(){
vec3 color=vec3(1.0,0.0,0.0) * rect(v_position.xy,vec2(0.5),vec2(0.5));
gl_FragColor=vec4(color,1.0);
}
`;
const clock = new THREE.Clock()
...
const uniforms = {
u_time: { value: 0 },
}
const material = new THREE.ShaderMaterial({
vertexShader: vshader,
fragmentShader: fshader,
uniforms,
});
...
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
uniforms.u_time.value = clock.getElapsedTime()
}
代码说明 uniform float u_time;这段代码相当于声明全局变量u_time,而它的值来自于
const uniforms = { u_time: { value: 0 }, }
uniform 可以在顶点和片元两个着色器中使用。
这是threejs 帮我们做的封装,我们要在shader代码使用js变量,只需要在THREE.ShaderMaterial({})传入即可,注意u_time:{value:0}是固定写法,value一定不能省略。 除了float 也可以穿其他类型的变量,在shader代码中声明对应的类型即可。
const uniforms = {
u_time: { value: 0 },
u_color: { value: new THREE.Color(0xffff00) }, // uniform vec3 u_color;
u_mouse: { value: { x: 0, y: 0 } }, // uniform vec2 u_mouse;
u_a: { value:new THREE.Vector3(0,1,0) }, // uniform vec3 u_a;
}
接下来我们换一种内置变量 uv 来实现画正方形; uv是vec2的类型,uv的值和position完全不同,uv的左下角为(0,0) 右上角为(1,1)且跟物体的空间几何无关。
const vshader = `
varying vec2 v_uv;
void main(){
v_uv=uv;
gl_Position=projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
`;
const fshader = `
varying vec2 v_uv;
float rect(vec2 pt,vec2 wh,vec2 center){
vec2 rangeMax=center + wh/2.0;
vec2 rangeMin=center - wh/2.0;
return step(rangeMin.x,pt.x)*step(pt.x,rangeMax.x)*step(rangeMin.y,pt.y)*step(pt.y,rangeMax.y);
}
void main(){
// vec3 color=vec3(1.0,0.0,0.0) * rect(v_uv,vec2(0.5),vec2(0.5));
gl_FragColor=vec4(v_uv,0.0,1.0);
}
`;
下面来一点点缩放。
const fshader = `
varying vec2 v_uv;
float rect(vec2 pt,vec2 wh,vec2 center){
pt=(pt-center)*2.0+center;
vec2 rangeMax=center + wh/2.0;
vec2 rangeMin=center - wh/2.0;
return step(rangeMin.x,pt.x)*step(pt.x,rangeMax.x)*step(rangeMin.y,pt.y)*step(pt.y,rangeMax.y);
}
void main(){
vec3 color=vec3(1.0,0.0,0.0) * rect(v_uv,vec2(0.5),vec2(0.5));
gl_FragColor=vec4(color,1.0);
}
`;
最后一个效果正方形改变正方形的旋转的中心点,就不上图了。
const fshader = `
varying vec2 v_uv;
uniform float u_time;
float rect(vec2 pt,vec2 wh,vec2 center){
mat2 rotate = mat2(
cos(u_time), -sin(u_time),
sin(u_time), cos(u_time)
);
pt=rotate*(pt-center-wh/2.0)+center+wh/2.0;
vec2 rangeMax=center + wh/2.0;
vec2 rangeMin=center - wh/2.0;
return step(rangeMin.x,pt.x)*step(pt.x,rangeMax.x)*step(rangeMin.y,pt.y)*step(pt.y,rangeMax.y);
}
void main(){
vec3 color=vec3(1.0,0.0,0.0) * rect(v_uv,vec2(0.25),vec2(0.5));
gl_FragColor=vec4(color,1.0);
}
`;
其中旋转涉及到一些数学知识,如果不懂也没关系,记住uv,position,varying,uniform step的用法即可。
但是最好还是推荐去学一下平移旋转缩放,对之后学习shader有很大的帮助。