1 Star 0 Fork 0

ing10010/verge3d-code-examples

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
webgl_gpgpu_water.html 21.06 KB
一键复制 编辑 原始数据 按行查看 历史
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734
<!DOCTYPE html>
<html lang="en">
<head>
<title>Verge3D webgl - gpgpu - water</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
</head>
<body>
<div id="info">
<a href="https://www.soft8soft.com/verge3d" target="_blank" rel="noopener">Verge3D</a> - <span id="waterSize"></span> webgl gpgpu water<br/>
Move mouse to disturb water.<br>
Press mouse button to orbit around. 'W' key toggles wireframe.
</div>
<!-- This is the 'compute shader' for the water heightmap: -->
<script id="heightmapFragmentShader" type="x-shader/x-fragment">
#include <common>
uniform vec2 mousePos;
uniform float mouseSize;
uniform float viscosityConstant;
uniform float heightCompensation;
void main() {
vec2 cellSize = 1.0 / resolution.xy;
vec2 uv = gl_FragCoord.xy * cellSize;
// heightmapValue.x == height from previous frame
// heightmapValue.y == height from penultimate frame
// heightmapValue.z, heightmapValue.w not used
vec4 heightmapValue = texture2D(heightmap, uv);
// Get neighbours
vec4 north = texture2D(heightmap, uv + vec2(0.0, cellSize.y));
vec4 south = texture2D(heightmap, uv + vec2(0.0, - cellSize.y));
vec4 east = texture2D(heightmap, uv + vec2(cellSize.x, 0.0));
vec4 west = texture2D(heightmap, uv + vec2(- cellSize.x, 0.0));
// https://web.archive.org/web/20080618181901/http://freespace.virgin.net/hugo.elias/graphics/x_water.htm
float newHeight = ((north.x + south.x + east.x + west.x) * 0.5 - heightmapValue.y) * viscosityConstant;
// Mouse influence
float mousePhase = clamp(length((uv - vec2(0.5)) * BOUNDS - vec2(mousePos.x, - mousePos.y)) * PI / mouseSize, 0.0, PI);
newHeight += (cos(mousePhase) + 1.0) * 0.28;
heightmapValue.y = heightmapValue.x;
heightmapValue.x = newHeight;
gl_FragColor = heightmapValue;
}
</script>
<!-- This is just a smoothing 'compute shader' for using manually: -->
<script id="smoothFragmentShader" type="x-shader/x-fragment">
uniform sampler2D smoothTexture;
void main() {
vec2 cellSize = 1.0 / resolution.xy;
vec2 uv = gl_FragCoord.xy * cellSize;
// Computes the mean of texel and 4 neighbours
vec4 textureValue = texture2D(smoothTexture, uv);
textureValue += texture2D(smoothTexture, uv + vec2(0.0, cellSize.y));
textureValue += texture2D(smoothTexture, uv + vec2(0.0, - cellSize.y));
textureValue += texture2D(smoothTexture, uv + vec2(cellSize.x, 0.0));
textureValue += texture2D(smoothTexture, uv + vec2(- cellSize.x, 0.0));
textureValue /= 5.0;
gl_FragColor = textureValue;
}
</script>
<!-- This is a 'compute shader' to read the current level and normal of water at a point -->
<!-- It is used with a variable of size 1x1 -->
<script id="readWaterLevelFragmentShader" type="x-shader/x-fragment">
uniform vec2 point1;
uniform sampler2D levelTexture;
// Integer to float conversion from https://stackoverflow.com/questions/17981163/webgl-read-pixels-from-floating-point-render-target
float shift_right(float v, float amt) {
v = floor(v) + 0.5;
return floor(v / exp2(amt));
}
float shift_left(float v, float amt) {
return floor(v * exp2(amt) + 0.5);
}
float mask_last(float v, float bits) {
return mod(v, shift_left(1.0, bits));
}
float extract_bits(float num, float from, float to) {
from = floor(from + 0.5); to = floor(to + 0.5);
return mask_last(shift_right(num, from), to - from);
}
vec4 encode_float(float val) {
if (val == 0.0) return vec4(0, 0, 0, 0);
float sign = val > 0.0 ? 0.0 : 1.0;
val = abs(val);
float exponent = floor(log2(val));
float biased_exponent = exponent + 127.0;
float fraction = ((val / exp2(exponent)) - 1.0) * 8388608.0;
float t = biased_exponent / 2.0;
float last_bit_of_biased_exponent = fract(t) * 2.0;
float remaining_bits_of_biased_exponent = floor(t);
float byte4 = extract_bits(fraction, 0.0, 8.0) / 255.0;
float byte3 = extract_bits(fraction, 8.0, 16.0) / 255.0;
float byte2 = (last_bit_of_biased_exponent * 128.0 + extract_bits(fraction, 16.0, 23.0)) / 255.0;
float byte1 = (sign * 128.0 + remaining_bits_of_biased_exponent) / 255.0;
return vec4(byte4, byte3, byte2, byte1);
}
void main() {
vec2 cellSize = 1.0 / resolution.xy;
float waterLevel = texture2D(levelTexture, point1).x;
vec2 normal = vec2(
(texture2D(levelTexture, point1 + vec2(- cellSize.x, 0)).x - texture2D(levelTexture, point1 + vec2(cellSize.x, 0)).x) * WIDTH / BOUNDS,
(texture2D(levelTexture, point1 + vec2(0, - cellSize.y)).x - texture2D(levelTexture, point1 + vec2(0, cellSize.y)).x) * WIDTH / BOUNDS);
if (gl_FragCoord.x < 1.5) {
gl_FragColor = encode_float(waterLevel);
} else if (gl_FragCoord.x < 2.5) {
gl_FragColor = encode_float(normal.x);
} else if (gl_FragCoord.x < 3.5) {
gl_FragColor = encode_float(normal.y);
} else {
gl_FragColor = encode_float(0.0);
}
}
</script>
<!-- This is the water visualization shader, copied from the v3d.MeshPhongMaterial and modified: -->
<script id="waterVertexShader" type="x-shader/x-vertex">
uniform sampler2D heightmap;
#define PHONG
varying vec3 vViewPosition;
#ifndef FLAT_SHADED
varying vec3 vNormal;
#endif
#include <common>
#include <uv_pars_vertex>
#include <uv2_pars_vertex>
#include <displacementmap_pars_vertex>
#include <envmap_pars_vertex>
#include <color_pars_vertex>
#include <morphtarget_pars_vertex>
#include <skinning_pars_vertex>
#include <shadowmap_pars_vertex>
#include <logdepthbuf_pars_vertex>
#include <clipping_planes_pars_vertex>
void main() {
vec2 cellSize = vec2(1.0 / WIDTH, 1.0 / WIDTH);
#include <uv_vertex>
#include <uv2_vertex>
#include <color_vertex>
// # include <beginnormal_vertex>
// Compute normal from heightmap
vec3 objectNormal = vec3(
(texture2D(heightmap, uv + vec2(- cellSize.x, 0)).x - texture2D(heightmap, uv + vec2(cellSize.x, 0)).x) * WIDTH / BOUNDS,
(texture2D(heightmap, uv + vec2(0, - cellSize.y)).x - texture2D(heightmap, uv + vec2(0, cellSize.y)).x) * WIDTH / BOUNDS,
1.0);
//<beginnormal_vertex>
#include <morphnormal_vertex>
#include <skinbase_vertex>
#include <skinnormal_vertex>
#include <defaultnormal_vertex>
#ifndef FLAT_SHADED // Normal computed with derivatives when FLAT_SHADED
vNormal = normalize(transformedNormal);
#endif
//# include <begin_vertex>
float heightValue = texture2D(heightmap, uv).x;
vec3 transformed = vec3(position.x, position.y, heightValue);
//<begin_vertex>
#include <morphtarget_vertex>
#include <skinning_vertex>
#include <displacementmap_vertex>
#include <project_vertex>
#include <logdepthbuf_vertex>
#include <clipping_planes_vertex>
vViewPosition = - mvPosition.xyz;
#include <worldpos_vertex>
#include <envmap_vertex>
#include <shadowmap_vertex>
}
</script>
<script type="module">
import * as v3d from '../build/v3d.module.js';
import Stats from './jsm/libs/stats.module.js';
import { GUI } from './jsm/libs/dat.gui.module.js';
import { GPUComputationRenderer } from './jsm/misc/GPUComputationRenderer.js';
import { SimplexNoise } from './jsm/math/SimplexNoise.js';
// Texture width for simulation
const WIDTH = 128;
// Water size in system units
const BOUNDS = 512;
const BOUNDS_HALF = BOUNDS * 0.5;
let container, stats;
let camera, scene, renderer;
let mouseMoved = false;
const mouseCoords = new v3d.Vector2();
const raycaster = new v3d.Raycaster();
let waterMesh;
let meshRay;
let gpuCompute;
let heightmapVariable;
let waterUniforms;
let smoothShader;
let readWaterLevelShader;
let readWaterLevelRenderTarget;
let readWaterLevelImage;
const waterNormal = new v3d.Vector3();
const NUM_SPHERES = 5;
const spheres = [];
let spheresEnabled = true;
const simplex = new SimplexNoise();
init();
animate();
function init() {
container = document.createElement('div');
document.body.appendChild(container);
camera = new v3d.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 3000);
camera.position.set(0, 200, 350);
camera.lookAt(0, 0, 0);
scene = new v3d.Scene();
const sun = new v3d.DirectionalLight(0xFFFFFF, 1.0);
sun.position.set(300, 400, 175);
scene.add(sun);
const sun2 = new v3d.DirectionalLight(0x40A040, 0.6);
sun2.position.set(- 100, 350, - 200);
scene.add(sun2);
renderer = new v3d.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
stats = new Stats();
container.appendChild(stats.dom);
container.style.touchAction = 'none';
container.addEventListener('pointermove', onPointerMove, false);
document.addEventListener('keydown', function(event) {
// W Pressed: Toggle wireframe
if (event.keyCode === 87) {
waterMesh.material.wireframe = ! waterMesh.material.wireframe;
waterMesh.material.needsUpdate = true;
}
}, false);
window.addEventListener('resize', onWindowResize, false);
const gui = new GUI();
const effectController = {
mouseSize: 20.0,
viscosity: 0.98,
spheresEnabled: spheresEnabled
};
const valuesChanger = function() {
heightmapVariable.material.uniforms["mouseSize"].value = effectController.mouseSize;
heightmapVariable.material.uniforms["viscosityConstant"].value = effectController.viscosity;
spheresEnabled = effectController.spheresEnabled;
for (let i = 0; i < NUM_SPHERES; i++) {
if (spheres[i]) {
spheres[i].visible = spheresEnabled;
}
}
};
gui.add(effectController, "mouseSize", 1.0, 100.0, 1.0).onChange(valuesChanger);
gui.add(effectController, "viscosity", 0.9, 0.999, 0.001).onChange(valuesChanger);
gui.add(effectController, "spheresEnabled", 0, 1, 1).onChange(valuesChanger);
const buttonSmooth = {
smoothWater: function() {
smoothWater();
}
};
gui.add(buttonSmooth, 'smoothWater');
initWater();
createSpheres();
valuesChanger();
}
function initWater() {
const materialColor = 0x0040C0;
const geometry = new v3d.PlaneBufferGeometry(BOUNDS, BOUNDS, WIDTH - 1, WIDTH - 1);
// material: make a v3d.ShaderMaterial clone of v3d.MeshPhongMaterial, with customized vertex shader
const material = new v3d.ShaderMaterial({
uniforms: v3d.UniformsUtils.merge([
v3d.ShaderLib['phong'].uniforms,
{
"heightmap": { value: null }
}
]),
vertexShader: document.getElementById('waterVertexShader').textContent,
fragmentShader: v3d.ShaderChunk['meshphong_frag']
});
material.lights = true;
// Material attributes from v3d.MeshPhongMaterial
material.color = new v3d.Color(materialColor);
material.specular = new v3d.Color(0x111111);
material.shininess = 50;
// Sets the uniforms with the material values
material.uniforms["diffuse"].value = material.color;
material.uniforms["specular"].value = material.specular;
material.uniforms["shininess"].value = Math.max(material.shininess, 1e-4);
material.uniforms["opacity"].value = material.opacity;
// Defines
material.defines.WIDTH = WIDTH.toFixed(1);
material.defines.BOUNDS = BOUNDS.toFixed(1);
waterUniforms = material.uniforms;
waterMesh = new v3d.Mesh(geometry, material);
waterMesh.rotation.x = - Math.PI / 2;
waterMesh.matrixAutoUpdate = false;
waterMesh.updateMatrix();
scene.add(waterMesh);
// v3d.Mesh just for mouse raycasting
const geometryRay = new v3d.PlaneBufferGeometry(BOUNDS, BOUNDS, 1, 1);
meshRay = new v3d.Mesh(geometryRay, new v3d.MeshBasicMaterial({ color: 0xFFFFFF, visible: false }));
meshRay.rotation.x = - Math.PI / 2;
meshRay.matrixAutoUpdate = false;
meshRay.updateMatrix();
scene.add(meshRay);
// Creates the gpu computation class and sets it up
gpuCompute = new GPUComputationRenderer(WIDTH, WIDTH, renderer);
if (isSafari()) {
gpuCompute.setDataType(v3d.HalfFloatType);
}
const heightmap0 = gpuCompute.createTexture();
fillTexture(heightmap0);
heightmapVariable = gpuCompute.addVariable("heightmap", document.getElementById('heightmapFragmentShader').textContent, heightmap0);
gpuCompute.setVariableDependencies(heightmapVariable, [heightmapVariable]);
heightmapVariable.material.uniforms["mousePos"] = { value: new v3d.Vector2(10000, 10000) };
heightmapVariable.material.uniforms["mouseSize"] = { value: 20.0 };
heightmapVariable.material.uniforms["viscosityConstant"] = { value: 0.98 };
heightmapVariable.material.uniforms["heightCompensation"] = { value: 0 };
heightmapVariable.material.defines.BOUNDS = BOUNDS.toFixed(1);
const error = gpuCompute.init();
if (error !== null) {
console.error(error);
}
// Create compute shader to smooth the water surface and velocity
smoothShader = gpuCompute.createShaderMaterial(document.getElementById('smoothFragmentShader').textContent, { smoothTexture: { value: null } });
// Create compute shader to read water level
readWaterLevelShader = gpuCompute.createShaderMaterial(document.getElementById('readWaterLevelFragmentShader').textContent, {
point1: { value: new v3d.Vector2() },
levelTexture: { value: null }
});
readWaterLevelShader.defines.WIDTH = WIDTH.toFixed(1);
readWaterLevelShader.defines.BOUNDS = BOUNDS.toFixed(1);
// Create a 4x1 pixel image and a render target (Uint8, 4 channels, 1 byte per channel) to read water height and orientation
readWaterLevelImage = new Uint8Array(4 * 1 * 4);
readWaterLevelRenderTarget = new v3d.WebGLRenderTarget(4, 1, {
wrapS: v3d.ClampToEdgeWrapping,
wrapT: v3d.ClampToEdgeWrapping,
minFilter: v3d.NearestFilter,
magFilter: v3d.NearestFilter,
format: v3d.RGBAFormat,
type: v3d.UnsignedByteType,
depthBuffer: false
});
}
function isSafari() {
return !! navigator.userAgent.match(/Safari/i) && ! navigator.userAgent.match(/Chrome/i);
}
function fillTexture(texture) {
const waterMaxHeight = 10;
function noise(x, y) {
let multR = waterMaxHeight;
let mult = 0.025;
let r = 0;
for (let i = 0; i < 15; i++) {
r += multR * simplex.noise(x * mult, y * mult);
multR *= 0.53 + 0.025 * i;
mult *= 1.25;
}
return r;
}
const pixels = texture.image.data;
let p = 0;
for (let j = 0; j < WIDTH; j ++) {
for (let i = 0; i < WIDTH; i++) {
const x = i * 128 / WIDTH;
const y = j * 128 / WIDTH;
pixels[p + 0] = noise(x, y);
pixels[p + 1] = pixels[p + 0];
pixels[p + 2] = 0;
pixels[p + 3] = 1;
p += 4;
}
}
}
function smoothWater() {
const currentRenderTarget = gpuCompute.getCurrentRenderTarget(heightmapVariable);
const alternateRenderTarget = gpuCompute.getAlternateRenderTarget(heightmapVariable);
for (let i = 0; i < 10; i++) {
smoothShader.uniforms["smoothTexture"].value = currentRenderTarget.texture;
gpuCompute.doRenderTarget(smoothShader, alternateRenderTarget);
smoothShader.uniforms["smoothTexture"].value = alternateRenderTarget.texture;
gpuCompute.doRenderTarget(smoothShader, currentRenderTarget);
}
}
function createSpheres() {
const sphereTemplate = new v3d.Mesh(new v3d.SphereBufferGeometry(4, 24, 12), new v3d.MeshPhongMaterial({ color: 0xFFFF00 }));
for (let i = 0; i < NUM_SPHERES; i++) {
let sphere = sphereTemplate;
if (i < NUM_SPHERES - 1) {
sphere = sphereTemplate.clone();
}
sphere.position.x = (Math.random() - 0.5) * BOUNDS * 0.7;
sphere.position.z = (Math.random() - 0.5) * BOUNDS * 0.7;
sphere.userData.velocity = new v3d.Vector3();
scene.add(sphere);
spheres[i] = sphere;
}
}
function sphereDynamics() {
const currentRenderTarget = gpuCompute.getCurrentRenderTarget(heightmapVariable);
readWaterLevelShader.uniforms["levelTexture"].value = currentRenderTarget.texture;
for (let i = 0; i < NUM_SPHERES; i++) {
const sphere = spheres[i];
if (sphere) {
// Read water level and orientation
const u = 0.5 * sphere.position.x / BOUNDS_HALF + 0.5;
const v = 1 - (0.5 * sphere.position.z / BOUNDS_HALF + 0.5);
readWaterLevelShader.uniforms["point1"].value.set(u, v);
gpuCompute.doRenderTarget(readWaterLevelShader, readWaterLevelRenderTarget);
renderer.readRenderTargetPixels(readWaterLevelRenderTarget, 0, 0, 4, 1, readWaterLevelImage);
const pixels = new Float32Array(readWaterLevelImage.buffer);
// Get orientation
waterNormal.set(pixels[1], 0, - pixels[2]);
const pos = sphere.position;
// Set height
pos.y = pixels[0];
// Move sphere
waterNormal.multiplyScalar(0.1);
sphere.userData.velocity.add(waterNormal);
sphere.userData.velocity.multiplyScalar(0.998);
pos.add(sphere.userData.velocity);
if (pos.x < - BOUNDS_HALF) {
pos.x = - BOUNDS_HALF + 0.001;
sphere.userData.velocity.x *= - 0.3;
} else if (pos.x > BOUNDS_HALF) {
pos.x = BOUNDS_HALF - 0.001;
sphere.userData.velocity.x *= - 0.3;
}
if (pos.z < - BOUNDS_HALF) {
pos.z = - BOUNDS_HALF + 0.001;
sphere.userData.velocity.z *= - 0.3;
} else if (pos.z > BOUNDS_HALF) {
pos.z = BOUNDS_HALF - 0.001;
sphere.userData.velocity.z *= - 0.3;
}
}
}
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function setMouseCoords(x, y) {
mouseCoords.set((x / renderer.domElement.clientWidth) * 2 - 1, - (y / renderer.domElement.clientHeight) * 2 + 1);
mouseMoved = true;
}
function onPointerMove(event) {
if (event.isPrimary === false) return;
setMouseCoords(event.clientX, event.clientY);
}
function animate() {
requestAnimationFrame(animate);
render();
stats.update();
}
function render() {
// Set uniforms: mouse interaction
const uniforms = heightmapVariable.material.uniforms;
if (mouseMoved) {
raycaster.setFromCamera(mouseCoords, camera);
const intersects = raycaster.intersectObject(meshRay);
if (intersects.length > 0) {
const point = intersects[0].point;
uniforms["mousePos"].value.set(point.x, point.z);
} else {
uniforms["mousePos"].value.set(10000, 10000);
}
mouseMoved = false;
} else {
uniforms["mousePos"].value.set(10000, 10000);
}
// Do the gpu computation
gpuCompute.compute();
if (spheresEnabled) {
sphereDynamics();
}
// Get compute output in custom uniform
waterUniforms["heightmap"].value = gpuCompute.getCurrentRenderTarget(heightmapVariable).texture;
// Render
renderer.render(scene, camera);
}
</script>
</body>
</html>
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
HTML
1
https://gitee.com/ing/verge3d-code-examples.git
git@gitee.com:ing/verge3d-code-examples.git
ing
verge3d-code-examples
verge3d-code-examples
master

搜索帮助