Shaders in WebVR tutorial – Floating in the Sea Experience
In this tutorial, we’ll learn how to use shaders to create a floating in the sea experience. We’ll also use cubemaps to create the background sky.
Cubemaps in WebVR
We’ll load a cubemap as the background sky. Once the image is loaded, we cut up the faces and we use a cube shader to load them to a cube. Once we keep the camera inside this cube, it gives the skybox effect.
This is part of #DaysInVR series. View All VR Projects. Yesterday, we learnt how to create hotspots in WebVR. Today we’ll see how to use shaders in webvr to create floating in the sea experience.
Join 6000+ Students
Upgrade your programming skills to include Virtual Reality. A premium step-by-step training course to get you building real world Virtual Reality apps and website components. You’ll have access to 40+ WebVR & Unity lessons along with their source code.
Start Learning Now
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
var loader1 = new THREE.ImageLoader(); loader1.load( 'sunbox.png', function ( image ) { var getSide = function ( x, y ) { var size = 1024; var canvas = document.createElement( 'canvas' ); canvas.width = size; canvas.height = size; var context = canvas.getContext( '2d' ); context.drawImage( image, - x * size, - y * size ); return canvas; }; cubeMap.images[ 0 ] = getSide( 2, 1 ); // px cubeMap.images[ 1 ] = getSide( 0, 1 ); // nx cubeMap.images[ 2 ] = getSide( 1, 0 ); // py cubeMap.images[ 3 ] = getSide( 1, 2 ); // ny cubeMap.images[ 4 ] = getSide( 1, 1 ); // pz cubeMap.images[ 5 ] = getSide( 3, 1 ); // nz cubeMap.needsUpdate = true; } ); var cubeShader = THREE.ShaderLib[ 'cube' ]; cubeShader.uniforms[ 'tCube' ].value = cubeMap; var skyBoxMaterial = new THREE.ShaderMaterial( { fragmentShader: cubeShader.fragmentShader, vertexShader: cubeShader.vertexShader, uniforms: cubeShader.uniforms, depthWrite: false, side: THREE.BackSide } ); var skyBox = new THREE.Mesh( new THREE.BoxGeometry( 500, 500, 500 ), skyBoxMaterial ); scene.add( skyBox ); |
Shaders in WebVR
We’ll be using WaterShader
and Mirror
effect to create our ocean.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
THREE.ShaderLib[ 'water' ] = { uniforms: THREE.UniformsUtils.merge( [ THREE.UniformsLib[ "fog" ], { "normalSampler": { value: null }, "mirrorSampler": { value: null }, "alpha": { value: 1.0 }, "time": { value: 0.0 }, "distortionScale": { value: 20.0 }, "noiseScale": { value: 1.0 }, "textureMatrix" : { value: new THREE.Matrix4() }, "sunColor": { value: new THREE.Color( 0x7F7F7F ) }, "sunDirection": { value: new THREE.Vector3( 0.70707, 0.70707, 0 ) }, "eye": { value: new THREE.Vector3() }, "waterColor": { value: new THREE.Color( 0x555555 ) } } ] ), vertexShader: [ 'uniform mat4 textureMatrix;', 'uniform float time;', 'varying vec4 mirrorCoord;', 'varying vec3 worldPosition;', THREE.ShaderChunk[ "fog_pars_vertex"], 'void main()', '{', ' mirrorCoord = modelMatrix * vec4( position, 1.0 );', ' worldPosition = mirrorCoord.xyz;', ' mirrorCoord = textureMatrix * mirrorCoord;', ' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', THREE.ShaderChunk[ "fog_vertex"], '}' ].join( '\n' ), fragmentShader: [ 'precision highp float;', 'uniform sampler2D mirrorSampler;', 'uniform float alpha;', 'uniform float time;', 'uniform float distortionScale;', 'uniform sampler2D normalSampler;', 'uniform vec3 sunColor;', 'uniform vec3 sunDirection;', 'uniform vec3 eye;', 'uniform vec3 waterColor;', 'varying vec4 mirrorCoord;', 'varying vec3 worldPosition;', 'vec4 getNoise( vec2 uv )', '{', ' vec2 uv0 = ( uv / 103.0 ) + vec2(time / 17.0, time / 29.0);', ' vec2 uv1 = uv / 107.0-vec2( time / -19.0, time / 31.0 );', ' vec2 uv2 = uv / vec2( 8907.0, 9803.0 ) + vec2( time / 101.0, time / 97.0 );', ' vec2 uv3 = uv / vec2( 1091.0, 1027.0 ) - vec2( time / 109.0, time / -113.0 );', ' vec4 noise = texture2D( normalSampler, uv0 ) +', ' texture2D( normalSampler, uv1 ) +', ' texture2D( normalSampler, uv2 ) +', ' texture2D( normalSampler, uv3 );', ' return noise * 0.5 - 1.0;', '}', 'void sunLight( const vec3 surfaceNormal, const vec3 eyeDirection, float shiny, float spec, float diffuse, inout vec3 diffuseColor, inout vec3 specularColor )', '{', ' vec3 reflection = normalize( reflect( -sunDirection, surfaceNormal ) );', ' float direction = max( 0.0, dot( eyeDirection, reflection ) );', ' specularColor += pow( direction, shiny ) * sunColor * spec;', ' diffuseColor += max( dot( sunDirection, surfaceNormal ), 0.0 ) * sunColor * diffuse;', '}', THREE.ShaderChunk[ "common" ], THREE.ShaderChunk[ "fog_pars_fragment" ], 'void main()', '{', ' vec4 noise = getNoise( worldPosition.xz );', ' vec3 surfaceNormal = normalize( noise.xzy * vec3( 1.5, 1.0, 1.5 ) );', ' vec3 diffuseLight = vec3(0.0);', ' vec3 specularLight = vec3(0.0);', ' vec3 worldToEye = eye-worldPosition;', ' vec3 eyeDirection = normalize( worldToEye );', ' sunLight( surfaceNormal, eyeDirection, 100.0, 2.0, 0.5, diffuseLight, specularLight );', ' float distance = length(worldToEye);', ' vec2 distortion = surfaceNormal.xz * ( 0.001 + 1.0 / distance ) * distortionScale;', ' vec3 reflectionSample = vec3( texture2D( mirrorSampler, mirrorCoord.xy / mirrorCoord.z + distortion ) );', ' float theta = max( dot( eyeDirection, surfaceNormal ), 0.0 );', ' float rf0 = 0.3;', ' float reflectance = rf0 + ( 1.0 - rf0 ) * pow( ( 1.0 - theta ), 5.0 );', ' vec3 scatter = max( 0.0, dot( surfaceNormal, eyeDirection ) ) * waterColor;', ' vec3 albedo = mix( sunColor * diffuseLight * 0.3 + scatter, ( vec3( 0.1 ) + reflectionSample * 0.9 + reflectionSample * specularLight ), reflectance );', ' vec3 outgoingLight = albedo;', THREE.ShaderChunk[ "fog_fragment" ], ' gl_FragColor = vec4( outgoingLight, alpha );', '}' ].join( '\n' ) }; |
We’ll use this to create the water material and then add it to PlaneGeometry
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
var waterNormals = loader.load( 'waternormals.jpg' ); waterNormals.wrapS = waterNormals.wrapT = THREE.RepeatWrapping; waterNormals.repeat.set(160, 160); var water = new THREE.Water( renderer, camera, scene, { textureWidth: 512, textureHeight: 512, waterNormals: waterNormals, alpha: 1.0, sunDirection: light.position.clone().normalize(), sunColor: 0xffffff, waterColor: 0x001e0f, distortionScale: 50.0 }); var planeGeometry = new THREE.PlaneGeometry(1000, 1000); var plane = new THREE.Mesh(planeGeometry, water.material); plane.add(water); plane.receiveShadow = true; plane.rotation.x = -0.5 * Math.PI; scene.add(plane); |
Adding spheres to the scene
To add the spheres, we’ll use IcosahedronGeometry
and MeshPhongMaterial
. We’ll clone the spheres and then add it to a plane. Then move the plane along z
axis.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
var ballGeometry = new THREE.IcosahedronGeometry( 4, 4 ); for ( var i = 0, j = ballGeometry.faces.length; i < j; i ++ ) { ballGeometry.faces[ i ].color.setHex( Math.random() * 0xffffff ); } var ballMaterial = new THREE.MeshPhongMaterial( { vertexColors: THREE.FaceColors, shininess: 100, envMap: cubeMap } ); sphere = new THREE.Mesh( ballGeometry, ballMaterial ); var planeObjectGeometry = new THREE.PlaneGeometry(0, 0); var planeObjectMaterial = new THREE.MeshPhongMaterial(); var planeObject = new THREE.Mesh(planeObjectGeometry, planeObjectMaterial); planeObject.receiveShadow = true; planeObject.position.set(10, -5, -400); // planeObject.rotation.x = - Math.PI * 0.5; var box = new THREE.BoxHelper( planeObject, 0xffff00 ); scene.add( box ); for (var x = -80; x < 80; x += 40) { for (var z = -400; z < 400; z += 20) { var speaker=sphere.clone(); speaker.position.set(x+Math.random()*15,controls.userHeight + 1,z+Math.random()*15); planeObject.add(speaker) } } scene.add(planeObject); |
Its your turn
Create your own version of floating in VRÂ using shaders in WebVR. Don’t forget to share your comments below.
Download Source Code
Kickstart VR development by downloading source code for this project. You’ll have instant access to source code and asset files. You can use them in your personal or commercial projects.
Hey, the source-links lead to a 404