Сфера на three.js и шейдерах с эффектом горения
Сфера с эффектом горения на three.js с использование шейдеров
HTML
<div class='canvas-container'></div>
<script id='sphere-vertex-shader' type='x-shader/x-vertex'>
uniform float uTime;
varying vec2 vUv;
void main() {
vUv = uv;
vec3 delta = normal * sin(position.x * position.y * uTime / 10.0);
vec3 newPosition = position + delta;
gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);
}
</script>
<script id='sphere-fragment-shader' type='x-shader/x-fragment'>
uniform float uTime;
varying vec2 vUv;
float rand(vec2 seed);
float noise(vec2 position);
void main() {
vec2 position1 = vec2(vUv.x * 4.0, vUv.y - uTime);
vec2 position2 = vec2(vUv.x * 4.0, vUv.y - uTime * 2.0);
vec2 position3 = vec2(vUv.x * 4.0, vUv.y - uTime * 3.0);
float color = (
noise(position1 * 5.0)
+ noise(position2 * 10.0)
+ noise(position3 * 15.0)) / 3.0;
gl_FragColor = vec4(0.0, 0.0, 0.0, color - smoothstep(0.1, 1.3, vUv.y));
}
float rand(vec2 seed) {
return fract(sin(dot(seed, vec2(12.9898,78.233))) * 43758.5453123);
}
float noise(vec2 position) {
vec2 blockPosition = floor(position);
float topLeftValue = rand(blockPosition);
float topRightValue = rand(blockPosition + vec2(1.0, 0.0));
float bottomLeftValue = rand(blockPosition + vec2(0.0, 1.0));
float bottomRightValue = rand(blockPosition + vec2(1.0, 1.0));
vec2 computedValue = smoothstep(0.0, 1.0, fract(position));
return mix(topLeftValue, topRightValue, computedValue.x)
+ (bottomLeftValue - topLeftValue) * computedValue.y * (1.0 - computedValue.x)
+ (bottomRightValue - topRightValue) * computedValue.x * computedValue.y;
}
</script>
CSS
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
overflow: hidden;
}
.canvas-container {
height: 100%;
width: 100%;
}
JS
Потребуется подключение следующих дополнительных библиотек<script src='https://unpkg.com/three@0.99.0/build/three.min.js'></script>
<script src='https://unpkg.com/three@0.99.0/examples/js/controls/OrbitControls.js'></script>
<script src='https://unpkg.com/three@0.99.0/examples/js/postprocessing/EffectComposer.js'></script>
<script src='https://unpkg.com/three@0.99.0/examples/js/postprocessing/RenderPass.js'></script>
<script src='https://unpkg.com/three@0.99.0/examples/js/postprocessing/ShaderPass.js'></script>
<script src='https://unpkg.com/three@0.99.0/examples/js/shaders/CopyShader.js'></script>
<script src='https://unpkg.com/three@0.99.0/examples/js/shaders/LuminosityHighPassShader.js'></script>
<script src='https://unpkg.com/three@0.99.0/examples/js/postprocessing/UnrealBloomPass.js'></script>
скриптlet SCENE;
let CAMERA;
let RENDERER;
let CONTROLS;
let COMPOSER;
let TIME = 0;
main();
function main() {
init();
animate();
}
function init() {
initScene();
initCamera();
initRenderer();
initComposer();
initControls();
initEventListeners();
createObjects();
document.querySelector('.canvas-container').appendChild(RENDERER.domElement);
}
function initScene() {
SCENE = new THREE.Scene();
initLights();
}
function initLights() {
const point = new THREE.PointLight(0xffffff, 1, 0);
point.position.set(0, 100, 50);
SCENE.add(point);
}
function initCamera() {
CAMERA = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 2000);
CAMERA.position.y = 100;
CAMERA.position.z = 100;
}
function initRenderer() {
RENDERER = new THREE.WebGLRenderer({ alpha: true });
RENDERER.setPixelRatio(window.devicePixelRatio);
RENDERER.setSize(window.innerWidth, window.innerHeight);
RENDERER.shadowMap.enabled = true;
RENDERER.shadowMapSort = true;
RENDERER.setClearColor(0x0e050f, 1);
}
function initComposer() {
COMPOSER = new THREE.EffectComposer(RENDERER);
COMPOSER.setSize(window.innerWidth, window.innerHeight);
const renderPass = new THREE.RenderPass(SCENE, CAMERA);
COMPOSER.addPass(renderPass);
const bloomPass = new THREE.UnrealBloomPass(
new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 1, 0.1);
bloomPass.renderToScreen = true;
COMPOSER.addPass(bloomPass);
}
function initControls() {
CONTROLS = new THREE.OrbitControls(CAMERA);
CONTROLS.enableZoom = false;
CONTROLS.minPolarAngle = Math.PI * 1 / 4;
CONTROLS.maxPolarAngle = Math.PI * 3 / 4;
CONTROLS.update();
}
function initEventListeners() {
window.addEventListener('resize', onWindowResize);
onWindowResize();
}
function onWindowResize() {
CAMERA.aspect = window.innerWidth / window.innerHeight;
CAMERA.updateProjectionMatrix();
RENDERER.setSize(window.innerWidth, window.innerHeight);
COMPOSER.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
CONTROLS.update();
TIME += 0.005;
updateUniforms();
render();
}
function updateUniforms() {
SCENE.traverse(function(child) {
if (child instanceof THREE.Mesh
&& child.material.type === 'ShaderMaterial') {
child.material.uniforms.uTime.value = TIME;
child.material.needsUpdate = true;
}
});
}
function render() {
CAMERA.lookAt(SCENE.position);
COMPOSER.render(SCENE, CAMERA);
}
function createObjects() {
const geometry1 = new THREE.SphereBufferGeometry(25, 64, 64);
const geometry2 = new THREE.SphereBufferGeometry(30, 64, 64);
const phongMaterial = new THREE.MeshPhongMaterial({
color: 0x782b7f,
emissive: 0x180819,
transparent: true,
opacity: .5
});
const shaderMaterial = new THREE.ShaderMaterial({
uniforms: {
uTime: { value: TIME }
},
transparent: true,
side: THREE.DoubleSide,
vertexShader: document.getElementById('sphere-vertex-shader').textContent,
fragmentShader: document.getElementById('sphere-fragment-shader').textContent
});
const sphere1 = new THREE.Mesh(geometry1, phongMaterial);
const sphere2 = new THREE.Mesh(geometry2, shaderMaterial);
SCENE.add(sphere1);
SCENE.add(sphere2);
}