-
-
Notifications
You must be signed in to change notification settings - Fork 35.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to mix textures and shadow maps on the same geometry? #1135
Comments
You just need to add few snippets to your custom // uniforms
THREE.UniformsLib[ "shadowmap" ],
// fragment shader
THREE.ShaderChunk[ "shadowmap_pars_fragment" ],
THREE.ShaderChunk[ "shadowmap_fragment" ],
// vertex shader
THREE.ShaderChunk[ "shadowmap_pars_vertex" ],
THREE.ShaderChunk[ "shadowmap_vertex" ], Have a look at how normal map shader integrates shadows: https://github.com/mrdoob/three.js/blob/master/src/extras/ShaderUtils.js#L94 Or maybe even simpler check how standard Lambert material is done: https://github.com/mrdoob/three.js/blob/master/src/renderers/WebGLShaders.js#L1235 |
That does look simple enough but I have no idea where to place the snippets, do they go in: Sorry for the n00bness, but I'm not familiar with the context that your linked code is being used in, |
This wouldn't work if for example you use DOM elements to have whole shader string in one piece, you need to be able to "inject" snippets at the right places. A pattern we use to construct long strings for shader sources is to concatenate array of single line strings with
|
Alright that makes sense, I think I know how to do it now, thanks man Quick question, how do I label posts as "question"? |
I maybe closed this issue a little prematurely, even though I'm pretty sure I understand how this should work in Now I'm using that "concatenated snippets array" technique to create the shader, using This guy seems to be having a similar issue. Here's how I'm trying it, can anyone tell me what I'm doing wrong, var terrain = new THREE.Mesh(
new THREE.PlaneGeometry(1000, 1000, heightMapSize-1, heightMapSize-1),
new THREE.ShaderMaterial({
uniforms: terrainShader.uniforms,
vertexShader: terrainShader.vertexShader,
fragmentShader: terrainShader.fragmentShader
})
); var terrainShader = {
uniforms: THREE.UniformsUtils.merge([
THREE.UniformsLib[ "shadowmap" ],
{
texture_dirt: { type: "t", value: 0, texture: THREE.ImageUtils.loadTexture('images/dirt.jpg') },
texture_grass: { type: "t", value: 1, texture: THREE.ImageUtils.loadTexture('images/grass.jpg') },
texture_rock: { type: "t", value: 2, texture: THREE.ImageUtils.loadTexture('images/rock.jpg') }
}
]),
vertexShader: [
"varying vec2 vUv;",
"varying vec3 vPosition;",
"varying float heightFactor;",
THREE.ShaderChunk[ "shadowmap_pars_vertex" ],
"void main(void) {",
"vUv = uv;",
"vPosition = position;",
THREE.ShaderChunk[ "shadowmap_vertex" ],
"gl_Position = projectionMatrix * modelViewMatrix * vec4(vPosition, 1);",
"}"
].join("\n"),
fragmentShader: [
"uniform sampler2D texture_dirt;",
"uniform sampler2D texture_grass;",
"uniform sampler2D texture_rock;",
"varying vec2 vUv;",
"varying vec3 vPosition;",
THREE.ShaderChunk[ "shadowmap_pars_fragment" ],
"void main() {",
"// Texture loading",
"vec4 diffuseDirt = texture2D( texture_dirt, vUv );",
"vec4 diffuseGrass = texture2D( texture_grass, vUv );",
"vec4 diffuseRock = texture2D( texture_rock, vUv );",
"vec4 diffuseSand = vec4(.8, .8, .7, 1.0);",
"vec4 diffuseSnow = vec4(.8, .9, 1.0, 1.0);",
"vec4 color = diffuseDirt; //default texture",
"float heightFactor = 8.0;",
"//add sand",
"color = mix(diffuseSand, color, min(abs(5.0 - vPosition.z) / 5.0/heightFactor, 1.0));",
"//starts at 5.0 for 5.0 units in both directions",
"//add dirt",
"color = mix(diffuseDirt, color, min(abs(10.0*heightFactor - vPosition.z) / 5.0/heightFactor, 1.0));",
"//add grass",
"color = mix(diffuseGrass, color, min(abs(10.0*heightFactor - vPosition.z) / 5.0/heightFactor, 1.0));",
"//add rock",
"color = mix(diffuseRock, color, min(abs(25.0*heightFactor - vPosition.z) / 12.0/heightFactor, 1.0));",
"//add snow",
"color = mix(diffuseSnow, color, min(abs(30.0*heightFactor - vPosition.z) / 10.0/heightFactor, 1.0));",
THREE.ShaderChunk[ "shadowmap_fragment" ],
"gl_FragColor = color;",
"}"
].join("\n")
}; (the object |
Fragment shader should be like this: // ...
"//add rock",
"color = mix(diffuseRock, color, min(abs(25.0*heightFactor - vPosition.z) / 12.0/heightFactor, 1.0));",
"//add snow",
"color = mix(diffuseSnow, color, min(abs(30.0*heightFactor - vPosition.z) / 10.0/heightFactor, 1.0));",
"gl_FragColor = color;",
THREE.ShaderChunk[ "shadowmap_fragment" ], |
I actually figured that out since I last posted, and got the shadow-mapping working, but the issue of the black terrain is still there whenever I use I've worked around the problem in the meantime by manually adding the shadowMap uniforms after my own uniforms like shown below, but that stops me from using uniforms: {
texture_dirt: { type: "t", value: 0, texture: THREE.ImageUtils.loadTexture('images/dirt.jpg') },
texture_grass: { type: "t", value: 1, texture: THREE.ImageUtils.loadTexture('images/grass.jpg') },
texture_rock: { type: "t", value: 2, texture: THREE.ImageUtils.loadTexture('images/rock.jpg') },
//shadowmap
shadowMap: { type: "tv", value: 6, texture: [] },
shadowMapSize: { type: "v2v", value: [] },
shadowBias: { type: "fv1", value: [] },
shadowDarkness: { type: "fv1", value: [] },
shadowMatrix: { type: "m4v", value: [] }
} |
I don't know, looks ok. The only difference I see is that you use uniforms directly, instead of cloning them. This would make troubles if you used the same shader multiple times. Each instance of |
Snake, I also managed to get it working. Not in the "clean way", though. (warning, rather big chunk of code incoming...) I will post the code I am currently using here - maybe this will allow us to .. errm.. share ideas & comments.. :-) Also, a screenshot of the result (no vegetation this time as I chose to focus on the terrain for now): http://imageupload.org/?d=B9967F4F1
What I have done is to copy/paste the normal fragment chunk from the lib, and edit it here and there... so far it is working, but I do realise it is a little shaky. Textures are fine, normals are fine (apart from Crazybump apparently generating funny "edges" on the normals texture). But I cannot get the shadow maps to work. More precisely, I cannot get the terrain cast shadows unto itself. :S Could it be: . That I am using a directional light? Cheers + plop! |
I still have the problem of Here's a workaround that gets the desired effect by adding the uniforms directly, without The following code takes a flat plane, deforms it based on a 128x128px heightmap image, uses the heightmap to bake a lightmap for soft shadowing (thanks to this example), applies textures based on altitude and combines all that with the standard fog and shadowmapping to give this result: <!DOCTYPE html>
<html>
<head>
<title>Terrain</title>
<meta http-equiv="content-type" content="text/html;charset=utf-8" />
<style type="text/css">
body{color: grey; font-family: "Lucida Sans Unicode", Lucida Grande, sans-serif; margin: 0px 0px; padding: 0px 0px; }
#header{width: 800px; margin: 5px auto;}
#viewport{border: 2px solid grey; border-radius: 2px; background-color: white; width: 800px; height: 600px; margin: 0 auto; padding: 0;}
.blue{color: cornflowerblue; padding-left: 2em; padding-right: 2em; opacity: 0.5;}
</style>
<!--built on three.js r47, may not work with newer builds-->
<script src="https://raw.github.com/mrdoob/three.js/master/build/Three.js"></script>
<script src="createShaders.js" type="text/javascript"></script>
<script src="getHeightData.js" type="text/javascript"></script>
<script src="lightmap.js" type="text/javascript"></script>
<script src="program.js" type="text/javascript"></script>
</head>
<body onload="initialize();">
<div id="header">
<h3>Blended and Shadowed Terrain from Heightmap</h3>
<p>
Move: w a s d r f<span class="blue">|</span>
Look: arrow keys / drag mouse<span class="blue">|</span>
Roll: q e
</p>
</div>
<div id="viewport"></div>
</body>
</html> program.js: //----Variables----//
//DOM element to attach the renderer to
var viewport;
var viewportWidth = 800;
var viewportHeight = 600;
//placeholder for built-in three.js controls
var controls;
//camera attributes
var view_angle = 45, //45 is standard, 90 is very wide
aspect = viewportWidth / viewportHeight,
near = 0.1, //near clip-plane
far = 10000; //far clip-plane
//renderer, scene and cameras
var renderer = new THREE.WebGLRenderer({ antialias: true });
var scene = new THREE.Scene();
var cameraRig = new THREE.Object3D();
var camera = new THREE.PerspectiveCamera(
view_angle,
aspect,
near,
far
);
//lights
var sun = new THREE.DirectionalLight( 0xffffff );
var sunPosition;
//terrain
var terrainHeightmap = new Image();
terrainHeightmap.src = "images/heightmap_128.png";
var heightMapSize = 128; //set to dimensions of the square heightmap
var heightMapArea = heightMapSize * heightMapSize;
var terrainHeightData;
var heightFactor = 10.0; //how far heightmap translates to vertex displacement
var tileFactor = 3.0; //how many times texures are tiled across the terrain
var shadowmapIntensity = 165; //255 total - intensity of baked shadowmap
//allows entering degrees rather than radians when defining rotation
var degrees, radians;
function degrees(degrees) {
radians = degrees * (Math.PI / 180);
return radians;
}
//----Initialization----//
function initialize() {
//renderer
viewport = document.getElementById('viewport');
viewport.appendChild(renderer.domElement);
renderer.setSize(viewportWidth, viewportHeight);
renderer.shadowMapEnabled = true;
//scene
scene.fog = new THREE.Fog( 0x6495ED, 250, 2500 );
//camera
camera.position.set(0, 350, 950);
//camera.lookAt(scene.position); //doesn't work when controls are applied
//controls
controls = new THREE.FlyControls( camera ); //pass object to be controlled
controls.movementSpeed = 4;
controls.domElement = viewport;
controls.rollSpeed = 0.025;
controls.autoForward = false;
controls.dragToLook = true;
//lights
sun.castShadow = true;
sun.shadowCameraVisible = false; //set true to see shadow frustum
sunPosition = new THREE.Vector3(1000,800,0);
sun.intensity = 1.8;
sun.position.set(sunPosition.x, sunPosition.y, sunPosition.z);
sun.shadowCameraNear = 100;
sun.shadowCameraFar = 2500;
sun.shadowBias = 0.0001;
sun.shadowDarkness = 0.35;
sun.shadowMapWidth = 1024; //512px by default
sun.shadowMapHeight = 1024; //512px by default
//terrain
var terrainHeightData = getHeightData(terrainHeightmap,heightMapSize,heightMapSize);
var terrainShadowmap = new THREE.Texture(
generateTexture(terrainHeightData, heightMapSize, heightMapSize, shadowmapIntensity, sunPosition),
new THREE.UVMapping(),
THREE.ClampToEdgeWrapping,
THREE.ClampToEdgeWrapping
);
terrainShadowmap.needsUpdate = true;
//sets uniforms to be passed to the shader
var terrainUniforms = THREE.UniformsUtils.clone( terrainShader.uniforms );
terrainUniforms[ "tileFactor" ].value = tileFactor;
terrainUniforms[ "heightFactor" ].value = heightFactor;
terrainUniforms[ "texture_shadow" ].texture = terrainShadowmap;
terrainUniforms[ "texture_dirt" ].texture = THREE.ImageUtils.loadTexture( "images/dirt.jpg" );
terrainUniforms[ "texture_grass" ].texture = THREE.ImageUtils.loadTexture( "images/grass.jpg" );
terrainUniforms[ "texture_rock" ].texture = THREE.ImageUtils.loadTexture( "images/rock.jpg" );
//allows textures to be tiled
terrainUniforms[ "texture_grass" ].texture.wrapS = THREE.RepeatWrapping;
terrainUniforms[ "texture_grass" ].texture.wrapT = THREE.RepeatWrapping;
terrainUniforms[ "texture_dirt" ].texture.wrapS = THREE.RepeatWrapping;
terrainUniforms[ "texture_dirt" ].texture.wrapT = THREE.RepeatWrapping;
terrainUniforms[ "texture_rock" ].texture.wrapS = THREE.RepeatWrapping;
terrainUniforms[ "texture_rock" ].texture.wrapT = THREE.RepeatWrapping;
var terrain = new THREE.Mesh(
new THREE.PlaneGeometry(1000, 1000, heightMapSize-1, heightMapSize-1),
new THREE.ShaderMaterial({
fog: true,
lights: true,
//wireframe: true,
uniforms: terrainUniforms,
vertexShader: terrainShader.vertexShader,
fragmentShader: terrainShader.fragmentShader
})
);
//deforms the terrain plane, based on pixel data from the heightmap
for(var i = 0, l = terrain.geometry.vertices.length; i < l; i++ ) {
terrain.geometry.vertices[i].position.z = terrainHeightData[i] * heightFactor;
}
terrain.position.set(0,0,0);
terrain.rotation.set(degrees(-90),0,0);
//terrain.doubleSided = true;
terrain.receiveShadow = true;
terrain.castShadow = true;
//lights, camera, action
scene.add(sun);
scene.add(camera);
scene.add(terrain);
update();
}
//----Update----//
function update() {
//requests the browser to call update at it's own pace
requestAnimationFrame( update );
/*
//crude sunset simulation
if(sun.intensity > 0.2) {
sun.intensity -= 0.001;
sun.shadowDarkness -= 0.00015;
sun.position.y -= 2.5;
}
*/
controls.update( 1 );
draw();
}
//----Draw----//
function draw() {
renderer.render(scene, camera);
} createShaders.js: //----creatShaders----//
/*
Creates shader strings by concatenating many single-line snippets,
THREE.ShaderChunk[] takes snippets from the Three.js library,
custom.ShaderChunk[] takes snippets from the object below
*/
//custom shaders
var custom = {
ShaderChunk: {
terrainBlend_pars_fragment: [
"uniform sampler2D texture_dirt;",
"uniform sampler2D texture_grass;",
"uniform sampler2D texture_rock;",
"uniform sampler2D texture_shadow;",
"uniform float heightFactor;",
"uniform float tileFactor;",
].join("\n"),
terrainBlend_fragment: [
"// Texture loading",
"vec4 dirt = texture2D( texture_dirt, vUv * tileFactor );",
"vec4 grass = texture2D( texture_grass, vUv * tileFactor );",
"vec4 rock = texture2D( texture_rock, vUv * tileFactor );",
"vec4 shadow = texture2D( texture_shadow, vUv );",
"vec4 sand = vec4(.8, .8, .7, 1.0);",
"vec4 snow = vec4(.8, .9, 1.0, 1.0);",
"vec4 color = dirt; //default texture",
"//add sand",
"color = mix(sand, color, min(abs(2.0*heightFactor - mvPosition.z) / 4.0/heightFactor, 1.0));",
"//starts at 5.0 for 5.0 units in both directions",
"//add dirt",
"color = mix(dirt, color, min(abs(10.0*heightFactor - mvPosition.z) / 5.0/heightFactor, 1.0));",
"//add grass",
"color = mix(grass, color, min(abs(10.0*heightFactor - mvPosition.z) / 5.0/heightFactor, 1.0));",
"//add rock",
"color = mix(rock, color, min(abs(20.0*heightFactor - mvPosition.z) / 10.0/heightFactor, 1.0));",
"//add snow",
"color = mix(snow, color, min(abs(25.0*heightFactor - mvPosition.z) / 5.0/heightFactor, 1.0));",
"color = color * shadow;",
"gl_FragColor = color;",
].join("\n")
}
}
//shader concatenation for blended terrain
var terrainShader = {
uniforms: {
texture_dirt: { type: "t", value: 0, texture: null },
texture_grass: { type: "t", value: 1, texture: null },
texture_rock: { type: "t", value: 2, texture: null },
texture_shadow: { type: "t", value: 4, texture: null },
heightFactor: { type: "f", value: []},
tileFactor: { type: "f", value: []},
//] ),
//common
diffuse : { type: "c", value: new THREE.Color( 0xeeeeee ) },
opacity : { type: "f", value: 1.0 },
map : { type: "t", value: 0, texture: null },
offsetRepeat : { type: "v4", value: new THREE.Vector4( 0, 0, 1, 1 ) },
lightMap : { type: "t", value: 2, texture: null },
envMap : { type: "t", value: 1, texture: null },
flipEnvMap : { type: "f", value: -1 },
useRefract : { type: "i", value: 0 },
reflectivity : { type: "f", value: 1.0 },
refractionRatio : { type: "f", value: 0.98 },
combine : { type: "i", value: 0 },
morphTargetInfluences : { type: "f", value: 0 },
//fog
fogDensity : { type: "f", value: 0.25 },
fogNear : { type: "f", value: 10 },
fogFar : { type: "f", value: 10000 },
fogColor : { type: "c", value: new THREE.Color( 0x6495ED ) },
//lights
ambientLightColor : { type: "fv", value: [] },
directionalLightDirection : { type: "fv", value: [] },
directionalLightColor : { type: "fv", value: [] },
pointLightColor : { type: "fv", value: [] },
pointLightPosition : { type: "fv", value: [] },
pointLightDistance : { type: "fv1", value: [] },
//lambert shading
ambient: { type: "c", value: new THREE.Color(328965) },
wrapRGB: { type: "v3", value: new THREE.Vector3(1, 1, 1)},
//shadowmap
shadowMap: { type: "tv", value: 6, texture: [] },
shadowMapSize: { type: "v2v", value: [] },
shadowBias: { type: "fv1", value: [] },
shadowDarkness: { type: "fv1", value: [] },
shadowMatrix: { type: "m4v", value: [] }
},
vertexShader: [
"varying vec2 vUv;",
"varying vec3 mvPosition;",
"varying vec3 vLightWeighting;",
THREE.ShaderChunk[ "lights_lambert_pars_vertex" ],
THREE.ShaderChunk[ "shadowmap_pars_vertex" ],
"void main(void) {",
"vUv = uv;",
"mvPosition = position;",
"vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );",
//lambert light calculation
"vec3 transformedNormal = normalize( normalMatrix * normal );",
THREE.ShaderChunk[ "lights_lambert_vertex" ],
THREE.ShaderChunk[ "shadowmap_vertex" ],
"gl_Position = projectionMatrix * mvPosition;",
"}"
].join("\n"),
fragmentShader: [
"varying vec2 vUv;",
"varying vec3 mvPosition;",
"varying vec3 vLightWeighting;",
custom.ShaderChunk[ "terrainBlend_pars_fragment" ],
THREE.ShaderChunk[ "shadowmap_pars_fragment" ],
THREE.ShaderChunk[ "fog_pars_fragment" ],
"void main() {",
custom.ShaderChunk[ "terrainBlend_fragment" ],
"gl_FragColor.xyz = gl_FragColor.xyz * vLightWeighting;", //lambert light calculation
THREE.ShaderChunk[ "shadowmap_fragment" ],
THREE.ShaderChunk[ "fog_fragment" ],
"}"
].join("\n")
}; getHeightData.js: //getHeightData
//reads the pixels from an image onto a canvas then returns the values in an array
function getHeightData(img, heightMapWidth, heightMapHeight)
{
var canvas = document.createElement( 'canvas' );
canvas.width = heightMapWidth;
canvas.height = heightMapHeight;
var context = canvas.getContext( '2d' );
var size = heightMapWidth * heightMapHeight, data = new Float32Array( size );
context.drawImage(img,0,0);
for ( var i = 0; i < size; i ++ ) {
data[i] = 0
}
var imgd = context.getImageData(0, 0, heightMapWidth, heightMapHeight);
var pix = imgd.data;
var j=0;
for (var i = 0, n = pix.length; i < n; i += (4)) {
var all = pix[i]+pix[i+1]+pix[i+2];
data[j++] = all/30;
}
return data;
} lightmap.js: //generates a lightmap based on a heightmap input
function generateTexture( data, width, height, intensity, sun ) {
var canvas, canvasScaled, context, image, imageData,base, vector3, sun, shade;
base = 255 - intensity;
vector3 = new THREE.Vector3( 0, 0, 0 );
sun.normalize();
canvas = document.createElement( 'canvas' );
canvas.width = width;
canvas.height = height;
context = canvas.getContext( '2d' );
context.fillRect( 0, 0, width, height );
image = context.getImageData( 0, 0, canvas.width, canvas.height );
imageData = image.data;
for ( var i = 0, j = 0, l = imageData.length; i < l; i += 4, j ++ ) {
vector3.x = data[ j - 2 ] - data[ j + 2 ];
vector3.y = 2;
vector3.z = data[ j - width * 2 ] - data[ j + width * 2 ];
vector3.normalize();
shade = vector3.dot( sun );
imageData[ i ] = ( base + shade * intensity );
imageData[ i + 1 ] = ( base + shade * intensity );
imageData[ i + 2 ] = ( base + shade * intensity ) ;
}
context.putImageData( image, 0, 0 );
// Scaled 4x
canvasScaled = document.createElement( 'canvas' );
canvasScaled.width = width * 4;
canvasScaled.height = height * 4;
context = canvasScaled.getContext( '2d' );
context.scale( 4, 4 );
context.drawImage( canvas, 0, 0 );
image = context.getImageData( 0, 0, canvasScaled.width, canvasScaled.height );
imageData = image.data;
for ( var i = 0, l = imageData.length; i < l; i += 4 ) {
var v = ~~ ( Math.random() * 25 );
imageData[ i ] += v;
imageData[ i + 1 ] += v;
imageData[ i + 2 ] += v;
}
context.putImageData( image, 0, 0 );
return canvasScaled;
} |
I have the same problem if using THREE.UniformUtils.merge() - textures just seem to get clobbered- this doesn't work (i get shadows, but no textures)
where as this works:
|
Code dump of the solution in my last post above, this one's as solved as it can be, thanks guys :] |
Evening Mentlegen,
I'm trying to build terrain and texture it with several colormaps and also the standard Three.js shadowmapping. A plane is deformed in the JS based on a heightmap, then I have a few textures applied in a fragment shader, based on the height of the terrain, and need to keep that custom functionality. So far I have the terrain deforming correctly and can get either the colormaps, or the shadowmapping, but not both.
Would I have to write a new shader that combines the shadowmapping and custom texturing, or is there another way?
I'm really just looking for the cleanest way to go about this, using as much of Three's built-in functionality as possible.
Can anyone... shine some light on this?
The text was updated successfully, but these errors were encountered: