Playing with rendering winter forest scene in three.js
Last active
November 10, 2018 22:36
-
-
Save Frankie-666/7def00370a2deb805c46eb7c863ffff9 to your computer and use it in GitHub Desktop.
Winter Trees
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<img src="http://crossorigin.me/http://www.radiolights.com/CODEPEN/b-grasslight-big2.jpg" class="hidden"/> | |
<img src="http://crossorigin.me/http://www.radiolights.com/CODEPEN/b-backgrounddetailed62.jpg" class="hidden"/> | |
<img src="http://crossorigin.me/http://www.radiolights.com/CODEPEN/b-grasslight-big-nm2.jpg" class="hidden"/> | |
<script id="fragmentShaderNoise" type="x-shader/x-fragment"> | |
// | |
// Description : Array and textureless GLSL 3D simplex noise function. | |
// Author : Ian McEwan, Ashima Arts. | |
// Maintainer : ijm | |
// Lastmod : 20110409 (stegu) | |
// License : Copyright (C) 2011 Ashima Arts. All rights reserved. | |
// Distributed under the MIT License. See LICENSE file. | |
// | |
uniform float time; | |
varying vec2 vUv; | |
vec4 permute( vec4 x ) { | |
return mod( ( ( x * 34.0 ) + 1.0 ) * x, 289.0 ); | |
} | |
vec4 taylorInvSqrt( vec4 r ) { | |
return 1.79284291400159 - 0.85373472095314 * r; | |
} | |
float snoise( vec3 v ) { | |
const vec2 C = vec2( 1.0 / 6.0, 1.0 / 3.0 ); | |
const vec4 D = vec4( 0.0, 0.5, 1.0, 2.0 ); | |
// First corner | |
vec3 i = floor( v + dot( v, C.yyy ) ); | |
vec3 x0 = v - i + dot( i, C.xxx ); | |
// Other corners | |
vec3 g = step( x0.yzx, x0.xyz ); | |
vec3 l = 1.0 - g; | |
vec3 i1 = min( g.xyz, l.zxy ); | |
vec3 i2 = max( g.xyz, l.zxy ); | |
vec3 x1 = x0 - i1 + 1.0 * C.xxx; | |
vec3 x2 = x0 - i2 + 2.0 * C.xxx; | |
vec3 x3 = x0 - 1. + 3.0 * C.xxx; | |
// Permutations | |
i = mod( i, 289.0 ); | |
vec4 p = permute( permute( permute( | |
i.z + vec4( 0.0, i1.z, i2.z, 1.0 ) ) | |
+ i.y + vec4( 0.0, i1.y, i2.y, 1.0 ) ) | |
+ i.x + vec4( 0.0, i1.x, i2.x, 1.0 ) ); | |
// Gradients | |
// ( N*N points uniformly over a square, mapped onto an octahedron.) | |
float n_ = 1.0 / 7.0; // N=7 | |
vec3 ns = n_ * D.wyz - D.xzx; | |
vec4 j = p - 49.0 * floor( p * ns.z *ns.z ); // mod(p,N*N) | |
vec4 x_ = floor( j * ns.z ); | |
vec4 y_ = floor( j - 7.0 * x_ ); // mod(j,N) | |
vec4 x = x_ *ns.x + ns.yyyy; | |
vec4 y = y_ *ns.x + ns.yyyy; | |
vec4 h = 1.0 - abs( x ) - abs( y ); | |
vec4 b0 = vec4( x.xy, y.xy ); | |
vec4 b1 = vec4( x.zw, y.zw ); | |
vec4 s0 = floor( b0 ) * 2.0 + 1.0; | |
vec4 s1 = floor( b1 ) * 2.0 + 1.0; | |
vec4 sh = -step( h, vec4( 0.0 ) ); | |
vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy; | |
vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww; | |
vec3 p0 = vec3( a0.xy, h.x ); | |
vec3 p1 = vec3( a0.zw, h.y ); | |
vec3 p2 = vec3( a1.xy, h.z ); | |
vec3 p3 = vec3( a1.zw, h.w ); | |
// Normalise gradients | |
vec4 norm = taylorInvSqrt( vec4( dot( p0, p0 ), dot( p1, p1 ), dot( p2, p2 ), dot( p3, p3 ) ) ); | |
p0 *= norm.x; | |
p1 *= norm.y; | |
p2 *= norm.z; | |
p3 *= norm.w; | |
// Mix final noise value | |
vec4 m = max( 0.6 - vec4( dot( x0, x0 ), dot( x1, x1 ), dot( x2, x2 ), dot( x3, x3 ) ), 0.0 ); | |
m = m * m; | |
return 42.0 * dot( m*m, vec4( dot( p0, x0 ), dot( p1, x1 ), | |
dot( p2, x2 ), dot( p3, x3 ) ) ); | |
} | |
float surface3( vec3 coord ) { | |
float n = 0.0; | |
n += 1.0 * abs( snoise( coord ) ); | |
n += 0.5 * abs( snoise( coord * 2.0 ) ); | |
n += 0.25 * abs( snoise( coord * 4.0 ) ); | |
n += 0.125 * abs( snoise( coord * 8.0 ) ); | |
return n; | |
} | |
void main( void ) { | |
vec3 coord = vec3( vUv, -time ); | |
float n = surface3( coord ); | |
gl_FragColor = vec4( vec3( n, n, n ), 1.0 ); | |
} | |
</script> | |
<script id="vertexShader" type="x-shader/x-vertex"> | |
varying vec2 vUv; | |
uniform vec2 scale; | |
uniform vec2 offset; | |
void main( void ) { | |
vUv = uv * scale + offset; | |
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); | |
} | |
</script> | |
<div class="link"> | |
<a href="https://twitter.com/cjgammon">@cjgammon</a> | |
</div> | |
<div id="loader"> | |
<h1>wait for it...</h1> | |
</div> | |
<div id="container"></div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
if ( ! Detector.webgl ) Detector.addGetWebGLMessage(); | |
var SCREEN_WIDTH = window.innerWidth; | |
var SCREEN_HEIGHT = window.innerHeight; | |
var renderer, container, stats; | |
var camera, scene; | |
var cameraOrtho, sceneRenderTarget; | |
var uniformsNoise, uniformsNormal, | |
heightMap, normalMap, | |
quadTarget; | |
var directionalLight, pointLight; | |
var terrain; | |
var textureCounter = 0; | |
var animDelta = 0, animDeltaDir = -1; | |
var lightVal = 0, lightDir = 1; | |
var clock = new THREE.Clock(); | |
var morph, morphs = []; | |
var updateNoise = true; | |
var animateTerrain = false; | |
var textMesh1; | |
var mlib = {}; | |
THREE.ImageUtils.crossOrigin = ''; | |
diffuseTexture1 = THREE.ImageUtils.loadTexture( "http://crossorigin.me/http://www.radiolights.com/CODEPEN/b-grasslight-big2.jpg", null, function () { | |
loadTextures(); | |
applyShader( THREE.LuminosityShader, diffuseTexture1, specularMap ); | |
} ); | |
diffuseTexture2 = THREE.ImageUtils.loadTexture( "http://crossorigin.me/http://www.radiolights.com/CODEPEN/b-backgrounddetailed62.jpg", null, loadTextures ); | |
detailTexture = THREE.ImageUtils.loadTexture( "http://crossorigin.me/http://www.radiolights.com/CODEPEN/b-grasslight-big-nm2.jpg", null, loadTextures ); | |
function init() { | |
container = document.getElementById( 'container' ); | |
// SCENE (RENDER TARGET) | |
sceneRenderTarget = new THREE.Scene(); | |
cameraOrtho = new THREE.OrthographicCamera( SCREEN_WIDTH / - 2, SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, SCREEN_HEIGHT / - 2, -10000, 10000 ); | |
cameraOrtho.position.z = 100; | |
sceneRenderTarget.add( cameraOrtho ); | |
// CAMERA | |
camera = new THREE.PerspectiveCamera( 40, SCREEN_WIDTH / SCREEN_HEIGHT, 2, 4000 ); | |
camera.position.set( -1200, 800, 1200 ); | |
controls = new THREE.OrbitControls( camera ); | |
controls.target.set( 0, 0, 0 ); | |
controls.rotateSpeed = 1.0; | |
controls.zoomSpeed = 1.2; | |
controls.panSpeed = 0.8; | |
controls.maxPolarAngle = 90 * Math.PI / 180; | |
controls.keys = [ 65, 83, 68 ]; | |
// SCENE (FINAL) | |
scene = new THREE.Scene(); | |
scene.fog = new THREE.Fog( 0x050505, 2000, 4000 ); | |
// LIGHTS | |
scene.add( new THREE.AmbientLight( 0x556380 ) ); | |
directionalLight = new THREE.DirectionalLight( 0x8d4a23, 0.12 ); | |
directionalLight.position.set( 1500, 3000, 0 ); | |
directionalLight.castShadow = true; | |
directionalLight.shadowCameraNear = 50; | |
directionalLight.shadowCameraFar = 10000; | |
directionalLight.shadowCameraLeft = -2000; | |
directionalLight.shadowCameraRight = 2000; | |
directionalLight.shadowCameraTop = 2000; | |
directionalLight.shadowCameraBottom = -2000; | |
directionalLight.shadowMapWidth = 1024; | |
directionalLight.shadowMapHeight = 1024; | |
directionalLight.shadowCameraVisible = true; | |
scene.add( directionalLight ); | |
pointLight = new THREE.PointLight( 0x594b1d, 4, 4000); | |
pointLight.position.set( 1000, 0, 0 ); | |
scene.add( pointLight ); | |
// HEIGHT + NORMAL MAPS | |
var normalShader = THREE.NormalMapShader; | |
var rx = 256, ry = 256; | |
var pars = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBFormat }; | |
heightMap = new THREE.WebGLRenderTarget( rx, ry, pars ); | |
heightMap.texture.generateMipmaps = false; | |
normalMap = new THREE.WebGLRenderTarget( rx, ry, pars ); | |
normalMap.texture.generateMipmaps = false; | |
uniformsNoise = { | |
time: { type: "f", value: 1.0 }, | |
scale: { type: "v2", value: new THREE.Vector2( 1.5, 1.5 ) }, | |
offset: { type: "v2", value: new THREE.Vector2( 0, 0 ) } | |
}; | |
uniformsNormal = THREE.UniformsUtils.clone( normalShader.uniforms ); | |
uniformsNormal.height.value = 0.05; | |
uniformsNormal.resolution.value.set( rx, ry ); | |
uniformsNormal.heightMap.value = heightMap; | |
var vertexShader = document.getElementById( 'vertexShader' ).textContent; | |
// TEXTURES | |
var specularMap = new THREE.WebGLRenderTarget( 2048, 2048, pars ); | |
specularMap.texture.generateMipmaps = false; | |
diffuseTexture1.wrapS = diffuseTexture1.wrapT = THREE.RepeatWrapping; | |
diffuseTexture2.wrapS = diffuseTexture2.wrapT = THREE.RepeatWrapping; | |
detailTexture.wrapS = detailTexture.wrapT = THREE.RepeatWrapping; | |
specularMap.wrapS = specularMap.wrapT = THREE.RepeatWrapping; | |
// TERRAIN SHADER | |
var params = [ | |
[ 'heightmap', document.getElementById( 'fragmentShaderNoise' ).textContent, vertexShader, uniformsNoise, false ], | |
[ 'normal', normalShader.fragmentShader, normalShader.vertexShader, uniformsNormal, false ], | |
]; | |
for( var i = 0; i < params.length; i ++ ) { | |
material = new THREE.ShaderMaterial( { | |
uniforms: params[ i ][ 3 ], | |
vertexShader: params[ i ][ 2 ], | |
fragmentShader: params[ i ][ 1 ], | |
lights: params[ i ][ 4 ], | |
fog: true | |
} ); | |
mlib[ params[ i ][ 0 ] ] = material; | |
} | |
var plane = new THREE.PlaneBufferGeometry( SCREEN_WIDTH, SCREEN_HEIGHT ); | |
quadTarget = new THREE.Mesh( plane, new THREE.MeshBasicMaterial( { color: 0x000000 } ) ); | |
quadTarget.position.z = -500; | |
sceneRenderTarget.add( quadTarget ); | |
// TERRAIN MESH | |
var geometryTerrain = new THREE.PlaneBufferGeometry( 6000, 6000, 256, 256 ); | |
THREE.BufferGeometryUtils.computeTangents( geometryTerrain ); | |
terrain = new THREE.Mesh( geometryTerrain, new THREE.MeshLambertMaterial({ | |
color: 0xe2c5be, | |
map: diffuseTexture1, | |
aoMap: detailTexture, | |
lightMap: diffuseTexture1, | |
})); | |
terrain.position.set( 0, -125, 0 ); | |
terrain.rotation.x = -Math.PI / 2; | |
terrain.visible = true; | |
terrain.receiveShadow = true; | |
scene.add( terrain ); | |
// RENDERER | |
renderer = new THREE.WebGLRenderer(); | |
renderer.setClearColor( scene.fog.color ); | |
renderer.setPixelRatio( window.devicePixelRatio ); | |
renderer.setSize( SCREEN_WIDTH, SCREEN_HEIGHT ); | |
renderer.shadowMap.enabled = true; | |
renderer.shadowMap.type = THREE.PCFSoftShadowMap; | |
container.appendChild( renderer.domElement ); | |
// | |
renderer.gammaInput = true; | |
renderer.gammaOutput = true; | |
// EVENTS | |
onWindowResize(); | |
window.addEventListener( 'resize', onWindowResize, false ); | |
// COMPOSER | |
renderer.autoClear = false; | |
renderTargetParameters = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBFormat, stencilBuffer: false }; | |
renderTarget = new THREE.WebGLRenderTarget( SCREEN_WIDTH, SCREEN_HEIGHT, renderTargetParameters ); | |
renderTarget.texture.generateMipmaps = false; | |
effectBloom = new THREE.BloomPass( 0.6 ); | |
var effectBleach = new THREE.ShaderPass( THREE.BleachBypassShader ); | |
hblur = new THREE.ShaderPass( THREE.HorizontalTiltShiftShader ); | |
vblur = new THREE.ShaderPass( THREE.VerticalTiltShiftShader ); | |
var bluriness = 6; | |
hblur.uniforms[ 'h' ].value = bluriness / SCREEN_WIDTH; | |
vblur.uniforms[ 'v' ].value = bluriness / SCREEN_HEIGHT; | |
hblur.uniforms[ 'r' ].value = vblur.uniforms[ 'r' ].value = 0.5; | |
effectBleach.uniforms[ 'opacity' ].value = 0.65; | |
composer = new THREE.EffectComposer( renderer, renderTarget ); | |
var renderModel = new THREE.RenderPass( scene, camera ); | |
vblur.renderToScreen = true; | |
composer = new THREE.EffectComposer( renderer, renderTarget ); | |
composer.addPass( renderModel ); | |
composer.addPass( effectBloom ); | |
//composer.addPass( effectBleach ); | |
composer.addPass( hblur ); | |
composer.addPass( vblur ); | |
var size = 3000; | |
for (i = 0; i < 200; i += 1) { | |
addTree(-size / 2 + Math.random() * size, -size / 2 + Math.random() * size); | |
} | |
var loader = document.getElementById('loader'); | |
loader.style.display = 'none'; | |
var canvas = document.getElementsByTagName('canvas'); | |
canvas[0].style.opacity = '1'; | |
} | |
function addTree(x, z) { | |
var group = new THREE.Object3D(); | |
// radiusAtTop, radiusAtBottom, height, segmentsAroundRadius, segmentsAlongHeight, | |
var geo = new THREE.CylinderGeometry( 15, 20, 150, 6, 4 ); | |
var mat = new THREE.MeshPhongMaterial({ | |
color: 0xe66f73, | |
shading: THREE.FlatShading | |
}); | |
var shape = new THREE.Mesh(geo, mat); | |
shape.position.set(0, -50, 0); | |
shape.castShadow = true; | |
shape.receiveShadow = true; | |
group.add( shape ); | |
var size = Math.round(Math.random()); | |
var s = 0.3 + Math.random() * 0.7; | |
//top colors | |
var r2 = 246; | |
var g2 = 205; | |
var b2 = 118; | |
//bottom colors | |
var r1 = 253; | |
var g1 = 240; | |
var b1 = 205; | |
var l = 5 + size; | |
for (i = 0; i < l; i += 1) { | |
var r = r1 + (i / l) * (r2 - r1); | |
var g = g1 + (i / l) * (g2 - g1); | |
var b = b1 + (i / l) * (b2 - b1); | |
c = new THREE.Color( r / 255, g / 255, b / 255 ); //0xf6ce76; | |
// pyramid | |
var geo = new THREE.CylinderGeometry( i * 5, 50 + (i * 10), 75, 5, 5 ); | |
var mat = new THREE.MeshPhongMaterial({ | |
color: c, | |
shading: THREE.FlatShading | |
}); | |
if (i > 0) { | |
rot = -0.05 + Math.random() * 0.1; | |
} else { | |
rot = 0; | |
} | |
var shape = new THREE.Mesh(geo, mat); | |
shape.position.set(0, 150 - (i * 35), 0); | |
shape.rotation.set(rot, 0, rot); | |
shape.castShadow = true; | |
shape.receiveShadow = true; | |
group.add( shape ); | |
} | |
var y = 0 - ((1 - s) * 150); | |
group.position.set(x, y, z); | |
group.scale.set(s, s, s); | |
scene.add(group); | |
} | |
function onWindowResize( event ) { | |
SCREEN_WIDTH = window.innerWidth; | |
SCREEN_HEIGHT = window.innerHeight; | |
renderer.setSize( SCREEN_WIDTH, SCREEN_HEIGHT ); | |
camera.aspect = SCREEN_WIDTH / SCREEN_HEIGHT; | |
camera.updateProjectionMatrix(); | |
} | |
function applyShader( shader, texture, target ) { | |
var shaderMaterial = new THREE.ShaderMaterial( { | |
fragmentShader: shader.fragmentShader, | |
vertexShader: shader.vertexShader, | |
uniforms: THREE.UniformsUtils.clone( shader.uniforms ) | |
} ); | |
shaderMaterial.uniforms[ "tDiffuse" ].value = texture; | |
var sceneTmp = new THREE.Scene(); | |
var meshTmp = new THREE.Mesh( new THREE.PlaneBufferGeometry( SCREEN_WIDTH, SCREEN_HEIGHT ), shaderMaterial ); | |
meshTmp.position.z = -500; | |
sceneTmp.add( meshTmp ); | |
renderer.render( sceneTmp, cameraOrtho, target, true ); | |
} | |
// | |
function loadTextures() { | |
textureCounter += 1; | |
if ( textureCounter == 3 ) { | |
init(); | |
animate(); | |
} | |
} | |
// | |
function animate() { | |
requestAnimationFrame( animate ); | |
render(); | |
} | |
function render() { | |
var delta = clock.getDelta(); | |
if ( terrain ) { | |
controls.update(); | |
var time = Date.now() * 0.001; | |
var fLow = 0.1, fHigh = 0.6; | |
lightVal = THREE.Math.clamp( lightVal + 0.5 * delta * lightDir, fLow, fHigh ); | |
var valNorm = ( lightVal - fLow ) / ( fHigh - fLow ); | |
scene.fog.color.setHSL( 2.6, 0.1, lightVal ); | |
renderer.setClearColor( scene.fog.color ); | |
directionalLight.intensity = THREE.Math.mapLinear( valNorm, 0, 1, 0.1, 1.15 ); | |
pointLight.intensity = THREE.Math.mapLinear( valNorm, 0, 1, 0.9, 1.5 ); | |
if ( updateNoise ) { | |
animDelta = THREE.Math.clamp( animDelta + 0.00075 * animDeltaDir, 0, 0.05 ); | |
quadTarget.material = mlib[ "heightmap" ]; | |
renderer.render( sceneRenderTarget, cameraOrtho, heightMap, true ); | |
quadTarget.material = mlib[ "normal" ]; | |
renderer.render( sceneRenderTarget, cameraOrtho, normalMap, true ); | |
} | |
composer.render( 0.1 ); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script src="//cdnjs.cloudflare.com/ajax/libs/gsap/1.18.0/TweenMax.min.js"></script> | |
<script src="//cdnjs.cloudflare.com/ajax/libs/three.js/r75/three.min.js"></script> | |
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/Detector.js"></script> | |
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/controls/OrbitControls.js"></script> | |
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/shaders/BleachBypassShader.js"></script> | |
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/shaders/ConvolutionShader.js"></script> | |
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/shaders/CopyShader.js"></script> | |
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/shaders/HorizontalTiltShiftShader.js"></script> | |
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/shaders/LuminosityShader.js"></script> | |
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/shaders/NormalMapShader.js"></script> | |
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/shaders/VerticalTiltShiftShader.js"></script> | |
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/postprocessing/EffectComposer.js"></script> | |
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/postprocessing/RenderPass.js"></script> | |
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/postprocessing/BloomPass.js"></script> | |
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/postprocessing/ShaderPass.js"></script> | |
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/postprocessing/MaskPass.js"></script> | |
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/postprocessing/SavePass.js"></script> | |
<script src="https://cdn.rawgit.com/mrdoob/three.js/master/examples/js/BufferGeometryUtils.js"></script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
html{ | |
min-height: 100%; | |
} | |
canvas{ | |
background: transparent; | |
opacity: 0; | |
} | |
body { | |
margin: 0; | |
overflow: hidden; | |
min-height: 100%; | |
background: #050505; | |
background-repeat: no-repeat; | |
background-position: center; | |
} | |
#loader{ | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
h1{ | |
color: white; | |
letter-spacing: .1em; | |
font-weight: 100; | |
font-family: sans-serif; | |
} | |
} | |
.link{ | |
position: fixed; | |
bottom: 0; | |
left: 0; | |
margin: 10px; | |
font-family: sans-serif; | |
font-weight: 100; | |
a{ | |
color: black; | |
text-decoration: none; | |
&:hover{ | |
text-decoration: underline; | |
} | |
} | |
} | |
.hidden{ | |
display: none; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment