Created
August 5, 2013 17:56
-
-
Save edom18/6157977 to your computer and use it in GitHub Desktop.
Three.jsでトゥーンシェーダの実装の実験
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
#Three.jsでトゥーンシェーダの実装の実験をしていきます。 | |
実装については以下の記事を参考にさせてもらいました。 | |
[トゥーンレンダリング | wgld.org](http://wgld.org/d/webgl/w048.html) | |
##更新履歴 | |
* 2013.08.06 - シェーダ切り替えスイッチを追加。 | |
##現状の問題点 | |
<del>なぜかカリングの設定がうまく動いていない・・。なにか勘違いしているのだろうか・・。 | |
なにか知っている人いたら教えて下さい。 | |
↓どうもこれが想定通りに動いていない。 | |
`renderer.setFaceCulling(THREE.CullFaceFront);`</del> | |
レンダリングについてはMaterial側で設定するのが正解でした↓ | |
`material.side = THREE.BackSide;//or THREE.FrontSize or THREE.DoubleSide` | |
これで意図通りにレンダリングされました。 | |
今回のハマったポイントなどは[qiitaに解説記事](http://qiita.com/edo_m18/items/8b84e6318ee49713e40e)を投稿しているので、興味ある人は見てみてください。 |
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
@import "compass/reset"; | |
img, canvas { | |
vertical-align: top; | |
} | |
#ctrl { | |
position: absolute; | |
left: 0; | |
top: 0; | |
} |
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="http://cdnjs.cloudflare.com/ajax/libs/three.js/r58/three.min.js"></script> | |
<div id="ctrl"> | |
<p><input type="button" id="lightCameraVisibility" value="ライトカメラのWF表示・非表示" /></p> | |
<p><input type="button" id="switchMaterial" value="シェーダ切り替え" /></p> | |
<!-- /#ctrl --></div> | |
<script type="x-shader/x-vertex" id="vs"> | |
uniform bool edge; | |
varying vec3 vNormal; | |
void main(void) { | |
vec3 pos = position; | |
if (edge) { | |
pos += normal * 0.04; | |
} | |
vNormal = normal; | |
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0); | |
} | |
</script> | |
<script type="x-shader/x-fragment" id="fs"> | |
precision mediump float; | |
uniform vec3 lightDirection; | |
uniform sampler2D texture; | |
uniform vec4 edgeColor; | |
varying vec3 vNormal; | |
void main(void) { | |
if (edgeColor.a > 0.0) { | |
gl_FragColor = edgeColor; | |
} | |
else { | |
float diffuse = clamp(dot(vNormal, lightDirection), 0.0, 1.0); | |
vec4 smpColor = texture2D(texture, vec2(diffuse, 0.0)); | |
gl_FragColor = smpColor; | |
} | |
} | |
</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
var container, | |
camera, | |
scene, | |
sceneEdge, | |
projector, | |
renderer, | |
mesh, | |
meshEdge, | |
lightCameraVisibility = false, | |
globalLight; | |
var sceneCube, | |
cameraCube, | |
textureCube, | |
meshCube; | |
var shadermaterial, | |
facematerial; | |
function initSkybox() { | |
sceneCube = new THREE.Scene(); | |
cameraCube = new THREE.PerspectiveCamera(25, window.innerWidth / window.innerHeight, 1, 10000); | |
cameraCube.position.y = -50; | |
sceneCube.add(cameraCube); | |
var urls = [ | |
'/assets/k/z/5/Y/kz5YB.jpg', | |
'/assets/x/P/8/J/xP8JV.jpg', | |
'/assets/p/S/A/e/pSAes.jpg', | |
'/assets/4/v/3/x/4v3xz.jpg', | |
'/assets/g/a/G/S/gaGSG.jpg', | |
'/assets/x/z/w/I/xzwIo.jpg' | |
]; | |
textureCube = THREE.ImageUtils.loadTextureCube(urls); | |
var shader = THREE.ShaderLib.cube; | |
shader.uniforms.tCube.value = textureCube; | |
var material = new THREE.ShaderMaterial({ | |
fragmentShader: shader.fragmentShader, | |
vertexShader : shader.vertexShader, | |
uniforms : shader.uniforms, | |
depthWrite : false, | |
side : THREE.BackSide | |
}); | |
meshCube = new THREE.Mesh(new THREE.CubeGeometry(1000, 1000, 1000), material); | |
sceneCube.add(meshCube); | |
} | |
function createScene(geometry, materials) { | |
shadermaterial = new THREE.ShaderMaterial({ | |
fragmentShader: document.getElementById('fs').innerHTML, | |
vertexShader : document.getElementById('vs').innerHTML, | |
attributes: {}, | |
uniforms: { | |
edgeColor: { | |
type: 'v4', | |
value: new THREE.Vector4(0, 0, 0, 0) | |
}, | |
edge: { | |
type: 'i', | |
value: true | |
}, | |
lightDirection: { | |
type: 'v3', | |
value: globalLight.position | |
}, | |
texture: { | |
type: 't', | |
value: THREE.ImageUtils.loadTexture('/assets/2/O/M/q/2OMqF.png') | |
} | |
} | |
}); | |
shadermaterial.morphTargets = true; | |
if (materials) { | |
for (var i = 0, l = materials.length; i < l; i++) { | |
materials[i].morphTargets = true; | |
} | |
facematerial = new THREE.MeshFaceMaterial(materials); | |
} | |
//mesh = new THREE.SkinnedMesh(geometry, shadermaterial); | |
mesh = new THREE.SkinnedMesh(geometry, facematerial); | |
mesh.scale.set(100, 100, 100); | |
mesh.position.y += 40; | |
mesh.castShadow = true; | |
mesh.receiveShadow = true; | |
meshEdge = mesh.clone(); | |
meshEdge.material = shadermaterial; | |
scene.add(mesh); | |
sceneEdge.add(meshEdge); | |
} | |
function initGrid() { | |
// Grid | |
var gmaterial = new THREE.LineBasicMaterial( { color: 0xcccccc } ); | |
var ggeometry = new THREE.Geometry(); | |
var floor = -0.04, step = 1, size = 14; | |
for ( var i = 0; i <= size / step * 2; i ++ ) { | |
ggeometry.vertices.push( new THREE.Vector3( - size, floor, i * step - size ) ); | |
ggeometry.vertices.push( new THREE.Vector3( size, floor, i * step - size ) ); | |
ggeometry.vertices.push( new THREE.Vector3( i * step - size, floor, -size ) ); | |
ggeometry.vertices.push( new THREE.Vector3( i * step - size, floor, size ) ); | |
} | |
var line = new THREE.Line(ggeometry, gmaterial, THREE.LinePieces); | |
line.scale.set(100, 100, 100); | |
scene.add(line); | |
} | |
function initGround() { | |
var initColor = new THREE.Color(0x497f13); | |
var initTexture = THREE.ImageUtils.generateDataTexture(1, 1, initColor); | |
var groundMaterial = new THREE.MeshPhongMaterial({color: 0xffffff, specular: 0x111111, map: initTexture}); | |
var groundTexture = THREE.ImageUtils.loadTexture('/assets/h/M/n/q/hMnqT.jpg', undefined, function () { groundMaterial.map = groundTexture; }); | |
groundTexture.wrapS = groundTexture.wrapT = THREE.RepeatWrapping; | |
groundTexture.repeat.set(25, 25); | |
groundTexture.anisotropy = 6; | |
var mesh = new THREE.Mesh(new THREE.PlaneGeometry(20000, 20000), groundMaterial); | |
mesh.position.y = -150; | |
mesh.rotation.x = -Math.PI / 2; | |
mesh.receiveShadow = true; | |
mesh.castShadow = true; | |
scene.add(mesh); | |
} | |
function init() { | |
container = document.createElement( 'div' ); | |
document.body.appendChild( container ); | |
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 1, 10000); | |
camera.position.y = 100; | |
camera.position.z = 10; | |
camera.target = new THREE.Vector3( 0, 150, 0 ); | |
scene = new THREE.Scene(); | |
sceneEdge = new THREE.Scene(); | |
// | |
globalLight = new THREE.DirectionalLight( 0xefefff, 2 ); | |
globalLight.position.set(1, 1, 1).normalize(); | |
globalLight.castShadow = true; | |
globalLight.shadowMapWidth = 2048; | |
globalLight.shadowMapHeight = 2048; | |
var d = 1000; | |
globalLight.shadowCameraLeft = -d; | |
globalLight.shadowCameraRight = d; | |
globalLight.shadowCameraTop = d; | |
globalLight.shadowCameraBottom = -d; | |
globalLight.shadowCameraNear = 1; | |
globalLight.shadowCameraFar = 1000; | |
globalLight.shadowCameraFov = 40; | |
globalLight.shadowCameraVisible = false; | |
globalLight.shadowBias = 0.0001; | |
globalLight.shadowDarkness = 0.5; | |
scene.add(globalLight); | |
var light2 = new THREE.DirectionalLight( 0xffefef, 2 ); | |
light2.position.set( -1, -1, -1 ).normalize(); | |
scene.add( light2 ); | |
var loader = new THREE.JSONLoader(true); | |
loader.load('/assets/l/2/m/t/l2mtS', createScene); | |
// | |
initGround(); | |
initSkybox(); | |
// | |
renderer = new THREE.WebGLRenderer({antialias: true}); | |
renderer.sortObjects = false; | |
renderer.setSize( window.innerWidth, window.innerHeight ); | |
renderer.setClearColor(0x000000, 1); | |
renderer.shadowMapEnabled = true; | |
renderer.shadowMapType = THREE.PCFShadowMap; | |
renderer.autoClear = false; | |
container.appendChild(renderer.domElement); | |
// | |
window.addEventListener( 'resize', onWindowResize, false ); | |
} | |
function onWindowResize() { | |
camera.aspect = window.innerWidth / window.innerHeight; | |
camera.updateProjectionMatrix(); | |
renderer.setSize( window.innerWidth, window.innerHeight ); | |
} | |
// | |
function animate() { | |
requestAnimationFrame( animate ); | |
render(); | |
} | |
var radius = 800; | |
var theta = 0; | |
var duration = 1000; | |
var keyframes = 30, interpolation = duration / keyframes; | |
var lastKeyframe = 0, currentKeyframe = 0; | |
var y = 0; | |
function render() { | |
camera.position.x = radius * Math.sin(THREE.Math.degToRad(theta)); | |
camera.position.z = radius * Math.cos(THREE.Math.degToRad(theta)); | |
camera.lookAt(camera.target); | |
if (mesh) { | |
y += 0.3; | |
mesh.position.y = 100 * Math.sin(THREE.Math.degToRad(y)); | |
meshEdge.position.y = 100 * Math.sin(THREE.Math.degToRad(y)); | |
// Alternate morph targets | |
var time = Date.now() % duration; | |
var keyframe = Math.floor( time / interpolation ); | |
if ( keyframe != currentKeyframe ) { | |
mesh.morphTargetInfluences[ lastKeyframe ] = 0; | |
mesh.morphTargetInfluences[ currentKeyframe ] = 1; | |
mesh.morphTargetInfluences[ keyframe ] = 0; | |
lastKeyframe = currentKeyframe; | |
currentKeyframe = keyframe; | |
} | |
mesh.morphTargetInfluences[keyframe] = ( time % interpolation ) / interpolation; | |
mesh.morphTargetInfluences[lastKeyframe] = 1 - mesh.morphTargetInfluences[ keyframe ]; | |
cameraCube.rotation.copy(camera.rotation); | |
renderer.clear(); | |
renderer.render(sceneCube, cameraCube); | |
//render front face. | |
if (meshFlg) { | |
meshEdge.material.side = THREE.FrontSide; | |
mesh.material.uniforms.edgeColor.value = new THREE.Vector4(0, 0, 0, 0); | |
mesh.material.uniforms.edge.value = false; | |
} | |
renderer.render(scene, camera); | |
//render back face as edge. | |
if (meshFlg) { | |
meshEdge.material.side = THREE.BackSide; | |
meshEdge.material.uniforms.edgeColor.value = new THREE.Vector4(0, 0, 0, 1); | |
meshEdge.material.uniforms.edge.value = true; | |
renderer.render(sceneEdge, camera); | |
} | |
} | |
} | |
// | |
init(); | |
animate(); | |
//Event handlers. | |
{ | |
document.addEventListener('mousewheel', function (e) { | |
e.preventDefault(); | |
theta -= e.wheelDelta / 100; | |
}, false); | |
document.addEventListener('DOMMouseScroll', function (e) { | |
theta -= e.detail / 10; | |
}, false); | |
document.getElementById('lightCameraVisibility').addEventListener('click', function () { | |
globalLight.shadowCameraVisible = (lightCameraVisibility = !lightCameraVisibility); | |
}, false); | |
var meshFlg = true; | |
document.getElementById('switchMaterial').addEventListener('click', function () { | |
if ((meshFlg = !meshFlg)) { | |
mesh.material = shadermaterial; | |
} | |
else { | |
mesh.material = facematerial; | |
} | |
}, false); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment