效果图:
代码直接上:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
* {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<script type="module">
import * as THREE from '../js_three-0.108.0/build/three.module.js';
import { OrbitControls } from '../js_three-0.108.0/examples/jsm/controls/OrbitControls.js';
import Stats from '../js_three-0.108.0/examples/jsm/libs/stats.module.js';
import { GUI } from '../js_three-0.108.0/examples/jsm/libs/dat.gui.module.js';
let camera, scene, stats, renderer;
const clock = new THREE.Clock();
let box = document.querySelector('body');
let width = window.innerWidth;
let height = window.innerHeight;
let gui = new GUI();
init();
light();
addLight();
let vertexShader = `
uniform vec2 uFrequency;
uniform float uTime;
varying vec2 vUv;
varying float vElevation;
void main()
{
vec4 modelPosition = modelMatrix * vec4(position, 1.0);
float elevation = sin(modelPosition.x * uFrequency.x - uTime) * 0.1;
elevation += sin(modelPosition.y * uFrequency.y - uTime) * 0.1;
// 平面向上移动
modelPosition.y += 10.2;
// 平面移动
modelPosition.z += elevation;
// 平面变波浪形
modelPosition.z += sin(modelPosition.x * uFrequency.x + uTime) * 2.9;
modelPosition.z += sin(modelPosition.y * uFrequency.y + uTime) * 2.9;
vec4 viewPosition = viewMatrix * modelPosition;
vec4 projectedPosition = projectionMatrix * viewPosition;
gl_Position = projectedPosition;
vUv = uv;
vElevation = elevation;
}
`;
// 下面要将我们的旗帜纹理的颜色应用于所有可见片元上,为此我们必须使用texture2D()方法,
// 该方法第一个参数就是应用的纹理,第二个参数是由在纹理上拾取的颜色的坐标组成,
// 我们还没有这些坐标,而这听起来很熟悉,我们正在寻找可以帮助我们在几何体上投射纹理的坐标,也就是UV坐标。
let fragmentShader = `
precision mediump float;
uniform sampler2D uTexture;
varying vec2 vUv;
varying float vElevation;
void main()
{
vec4 textureColor = texture2D(uTexture, vUv);
textureColor.rgb *= vElevation * 2.0 + 0.8;
gl_FragColor = textureColor;
}
`;
// 我们在其他材质中介绍的大多数常见属性(如wireframe、side、transparent、flatShading)
// 仍然适用于RawShaderMaterial
// 但是像map、alphaMap、opacity、 color等属性将不再生效,因为我们需要自己在着色器中编写这些特性
const textureLoader = new THREE.TextureLoader();
const flagTexture = textureLoader.load('../materials/guo.png');
const material = new THREE.ShaderMaterial({
vertexShader: vertexShader,
fragmentShader: fragmentShader,
transparent: false,
wireframe: false,
side: THREE.DoubleSide,
// 然后你会发现结果是一样的,但现在我们可以在js中控制波形频率了。
// 让我们把频率frequency改为vec2来控制水平和垂直方向的波,这里使用Three.js中一个简单的vec2:
uniforms:{
uFrequency: {
value: new THREE.Vector2(10, 5)
},
// 现在让我们再新加一个uniform来让平面像在风中飘动的旗帜。
// 我们将使用统一变量向着色器发送一个时间值,并在sin函数中使用:
uTime: { value: 0 },
uTexture: { value: flagTexture }
}
});
const geometry = new THREE.PlaneBufferGeometry(200, 100, 5, 5);
const flag = new THREE.Mesh(geometry, material);
scene.add(flag);
gui.add(material.uniforms.uFrequency.value, 'x')
.min(0)
.max(20)
.step(0.01)
.name('frequencyX');
gui.add(material.uniforms.uFrequency.value, 'y')
.min(0)
.max(20)
.step(0.01)
.name('frequencyY');
animate();
function addLight() {
const light = new THREE.SpotLight();
light.position.set(0, 30, 30);
light.intensity = 2;
scene.add(light);
light.castShadow = true
}
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(45, width / height, 10, 1000);
camera.position.set(0, 150, 200);
camera.lookAt(scene.position);
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '8px';
stats.domElement.style.zIndex = 100;
renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setClearColor(0x000000, 0.0);
renderer.setSize(width, height);
box.appendChild(renderer.domElement);
box.appendChild(stats.dom);
let controls = new OrbitControls(camera, renderer.domElement);
window.addEventListener('resize', onWindowResize, false);
onWindowResize();
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function light() {
let light = new THREE.AmbientLight(0xFFFFFF);
scene.add(light);
let directionalLight = new THREE.DirectionalLight(0x101010);
directionalLight.position.set(-1, 1, 1).normalize();
scene.add(directionalLight);
}
function animate() {
requestAnimationFrame(animate);
stats.update();
render();
}
function render() {
const elapsedTime = clock.getElapsedTime();
material.uniforms.uTime.value = elapsedTime * 5;
renderer.render(scene, camera);
}
</script>
</body>
</html>
这个案例算是一个比较好的,案例很形象的展示了 Shader 的用法:
1)这里我们用了 ShaderMaterial,所以默认变量不需要再声明。
2)我们可以在 vertexShader 中通过改变顶点位置直接偏移物体,即 modelPosition.y
3)我们在 GLSL语言 和 js 之间能通信吗,案例展示了这种用法,通过 ShaderMaterial 的 uniforms 我们可以将 js中的变量传给 GLSL ,方便控制动画,
4)uniforms 也可以传递图片,通过 gl_FragColor实现
5)展示了 varying 这个vertexShader 和 fragmentShader 之间通信的能力
6)大家可以通过 gui 这个方法,更好的为我们展示 Shader 的效果
附上国旗图片,方便大家使用