<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>WebGL Three.js 流动的水</title>
<style>
body {
margin: 0;
overflow: hidden;
background: #000000;
cursor: move;
font-family: sans-serif;
}
</style>
</head>
<body>
<div id="three-container"></div>
<script id="heightMapFragmentShader" type="x-shader/x-fragment">
#include <common>
uniform vec2 mousePos;
uniform float mouseSize;
uniform float viscosityConstant;
#define deltaTime (1.0 / 60.0)
#define GRAVITY_CONSTANT (resolution.x * deltaTime * 3.0)
const vec2 ONE = vec2(1.0);
void main() {
vec2 cellSize = 1.0 / resolution.xy;
vec2 uv = gl_FragCoord.xy * cellSize;
// heightmapValue.x == height
// heightmapValue.y == velocity
// heightmapValue.z, heightmapValue.w not used
vec4 heightMapValue = texture2D( heightMap, uv );
// Get neighbours
vec4 north = texture2D(heightMap, mod(uv + vec2(0.0, cellSize.y), ONE));
vec4 south = texture2D(heightMap, mod(uv + vec2(0.0, -cellSize.y), ONE));
vec4 east = texture2D(heightMap, uv + vec2(cellSize.x, 0.0));
vec4 west = texture2D(heightMap, uv + vec2(-cellSize.x, 0.0));
float sump = north.x + south.x + east.x + west.x - 4.0 * heightMapValue.x;
float accel = sump * GRAVITY_CONSTANT;
// Dynamics
heightMapValue.y += accel;
heightMapValue.x += heightMapValue.y * deltaTime;
// Viscosity
heightMapValue.x += sump * viscosityConstant;
// Mouse influence
float mousePhase = clamp(length((uv - vec2(0.5)) * BOUNDS - vec2(mousePos.x, -mousePos.y)) * PI / mouseSize, 0.0, PI);
heightMapValue.x += cos(mousePhase) + 1.0;
gl_FragColor = heightMapValue;
}
</script>
<script src="js/three.min.js"></script>
<script src="js/bas.min.1.1.2.js"></script>
<script src="js/OrbitControls-2.js"></script>
<script>function THREERoot(params) {
// defaults
params = Object.assign({
container:'#three-container',
fov:60,
zNear:1,
zFar:10000,
createCameraControls: true,
autoStart: true,
pixelRatio: window.devicePixelRatio,
antialias: (window.devicePixelRatio === 1),
alpha: false
}, params);
// maps and arrays
this.updateCallbacks = [];
this.resizeCallbacks = [];
this.objects = {};
// renderer
this.renderer = new THREE.WebGLRenderer({
antialias: params.antialias,
alpha: params.alpha
});
this.renderer.setPixelRatio(params.pixelRatio);
// container
this.container = (typeof params.container === 'string') ? document.querySelector(params.container) : params.container;
this.container.appendChild(this.renderer.domElement);
// camera
this.camera = new THREE.PerspectiveCamera(
params.fov,
window.innerWidth / window.innerHeight,
params.zNear,
params.zFar
);
// scene
this.scene = new THREE.Scene();
// resize handling
this.resize = this.resize.bind(this);
this.resize();
window.addEventListener('resize', this.resize, false);
// tick / update / render
this.tick = this.tick.bind(this);
params.autoStart && this.tick();
// optional camera controls
params.createCameraControls && this.createOrbitControls();
}
THREERoot.prototype = {
createOrbitControls: function() {
this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement);
this.addUpdateCallback(this.controls.update.bind(this.controls));
},
start: function() {
this.tick();
},
addUpdateCallback: function(callback) {
this.updateCallbacks.push(callback);
},
addResizeCallback: function(callback) {
this.resizeCallbacks.push(callback);
},
add: function(object, key) {
key && (this.objects[key] = object);
this.scene.add(object);
},
addTo: function(object, parentKey, key) {
key && (this.objects[key] = object);
this.get(parentKey).add(object);
},
get: function(key) {
return this.objects[key];
},
remove: function(o) {
var object;
if (typeof o === 'string') {
object = this.objects[o];
}
else {
object = o;
}
if (object) {
object.parent.remove(object);
delete this.objects[o];
}
},
tick: function() {
this.update();
this.render();
requestAnimationFrame(this.tick);
},
update: function() {
this.updateCallbacks.forEach(function(callback) {callback()});
},
render: function() {
this.renderer.render(this.scene, this.camera);
},
resize: function() {
var width = window.innerWidth;
var height = window.innerHeight;
this.camera.aspect = width / height;
this.camera.updateProjectionMatrix();
this.renderer.setSize(width, height);
this.resizeCallbacks.forEach(function(callback) {callback()});
},
initPostProcessing:function(passes) {
var size = this.renderer.getSize();
var pixelRatio = this.renderer.getPixelRatio();
size.width *= pixelRatio;
size.height *= pixelRatio;
var composer = this.composer = new THREE.EffectComposer(this.renderer, new THREE.WebGLRenderTarget(size.width, size.height, {
minFilter: THREE.LinearFilter,
magFilter: THREE.LinearFilter,
format: THREE.RGBAFormat,
stencilBuffer: false
}));
var renderPass = new THREE.RenderPass(this.scene, this.camera);
this.composer.addPass(renderPass);
for (var i = 0; i < passes.length; i++) {
var pass = passes[i];
pass.renderToScreen = (i === passes.length - 1);
this.composer.addPass(pass);
}
this.renderer.autoClear = false;
this.render = function() {
this.renderer.clear();
this.composer.render();
}.bind(this);
this.addResizeCallback(function() {
var width = window.innerWidth;
var height = window.innerHeight;
composer.setSize(width * pixelRatio, height * pixelRatio);
}.bind(this));
}
};
</script>
<script src="js/GPUComputationRenderer.js"></script>
<script src="js/SimplexNoise.js"></script>
<script>
(function() {
var planeWidth = 256;
var planeDepth = 512;
// setup root
var root = new THREERoot();
root.renderer.setClearColor(0xf1f1f1);
root.camera.position.set(200, 200, 0);
// add top light
var light = new THREE.DirectionalLight(0xffffff);
light.position.set(0, 1, 0);
root.add(light);
// add weaker bottom light
light = new THREE.DirectionalLight(0xffffff, 0.25);
light.position.set(0, -1, 0);
root.add(light);
// setup water mesh & material
var ctSizeX = planeWidth; // compute texture size
var ctSizeY = planeDepth; // compute texture size
var geometry = new THREE.PlaneBufferGeometry(planeWidth, planeDepth, ctSizeX - 1, ctSizeY - 1);
var material = new THREE.BAS.StandardAnimationMaterial({
transparent: true,
side: THREE.DoubleSide,
uniforms: {
uCellSize: {
value: new THREE.Vector2(1.0 / ctSizeX, 1.0 / ctSizeY)
},
uHeightMap: {
value: null
},
uFlowOffset: {
value: 0.0
}
},
uniformValues: {
roughness: 0.0,
metalness: 0.0,
diffuse: new THREE.Color(0x8CB0D9), //0xD5E5F1, 0x4B83C3
opacity: 0.8
},
vertexParameters: [
'uniform vec2 uCellSize;',
'uniform sampler2D uHeightMap;',
'uniform float uFlowOffset;'
],
vertexNormal: [
'vec2 tUv = uv;',
'tUv.y = mod(uv.y + uFlowOffset, 1.0);',
'objectNormal = vec3(',