一.从一个shader开始
Three.js已经简化了很多写shader的工作,最主要的工作就是他把一些常用的矩阵已经作为一个常量提供给我们。
首先来看Three.js官网的说明,一些常用参数已经和几何元素、相机等关联在一起,我们只需要拿来用即可。
来看第一个shader,通过shader改变几何体表面的颜色,效果还是很炫酷的。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Three框架</title>
<script src="../library/three.js"></script>
<script src="../library/stats.min.js"></script>
<style type="text/css">
div#canvas-frame {
border: none;
cursor: pointer;
width: 100%;
height: 600px;
background-color: #EEEEEE;
}
</style>
<script id="fragment_shader" type="x-shader/x-fragment">
uniform float time;
varying vec2 vUv;
void main( void ) {
vec2 position = - 1.0 + 2.0 * vUv;
float red = abs( sin( position.x * position.y + time / 5.0 ) );
float green = abs( sin( position.x * position.y + time / 4.0 ) );
float blue = abs( sin( position.x * position.y + time / 3.0 ) );
gl_FragColor = vec4( red, green, blue, 1.0 );
}
</script>
<script id="vertexShader" type="x-shader/x-vertex">
varying vec2 vUv;
void main()
{
vUv = uv;
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_Position = projectionMatrix * mvPosition;
}
</script>
<script>
var renderer;
var stats;
var clock;
var uniforms1;
var camera;
var scene;
var light;
var mesh;
//初始化webgl
function initThree() {
width = document.getElementById('canvas-frame').clientWidth;
height = document.getElementById('canvas-frame').clientHeight;
renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height);
document.getElementById('canvas-frame').appendChild(renderer.domElement);
renderer.setClearColor(0xFFFFFF, 1.0);
clock = new THREE.Clock();
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
document.getElementById('canvas-frame').appendChild(stats.domElement);
}
//设置相机
function initCamera() {
camera = new THREE.PerspectiveCamera(45, width / height, 1, 10000);
camera.position.set(0,0,600);
camera.up = new THREE.Vector3(0,1,0);
camera.lookAt(0,0,0);
}
//初始化场景
function initScene() {
scene = new THREE.Scene();
}
//设置化灯光
function initLight() {
light = new THREE.AmbientLight(0xFF0000);
light.position.set(100, 100, 200);
scene.add(light);
}
//几何物体
function initObject() {
uniforms1 = {
time: { value: 1.0 }
};
var params = uniforms1;
var geometry = new THREE.CylinderGeometry(100, 150, 400);
var material = new THREE.ShaderMaterial({
uniforms: params,
vertexShader: document.getElementById('vertexShader').textContent,
fragmentShader: document.getElementById('fragment_shader').textContent
});
mesh = new THREE.Mesh(geometry, material);
mesh.position = new THREE.Vector3(0, 0, 0);
scene.add(mesh);
}
//运行webgl
function threeStart() {
initThree();
initCamera();
initScene();
initLight();
initObject();
animation();
}
//设置动态场景
function animation() {
var delta = clock.getDelta();
uniforms1.time.value += delta * 5;
renderer.render(scene, camera);
requestAnimationFrame(animation);
stats.update();
}
</script>
</head>
<body οnlοad="threeStart();">
<div id="canvas-frame"></div>
</body>
</html>
然后再来分析代码,看是如何实现的。
1)先看vertexshader,这个简单明了,gl_Position=mvp,然后将UV坐标传递到fragmentshader中,在fragmentshader中定义一个time参数,然后点的颜色随着时间和位置的变化而变化。
2)接下来在创建几何物体的时候,取到time这个参数,几何体中的材料采用shaderMaterial();
3)最后在animation里随着时间改变time这个参数。
二.简化shader
在片面着色器中,如果y坐标大于0,那么颜色为红色,如果y坐标小于0,颜色为绿色。我们只需要在上述基础上修改下片元着色器,然后从顶点传进来一个坐标pos。
<script id="fragment_shader" type="x-shader/x-fragment">
uniform float time;
varying vec2 vUv;
varying vec4 pos;
void main( void ) {
if(pos.y>0.0){
gl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 );
}
else{
gl_FragColor = vec4( 0.0, 1.0, 0.0, 1.0 );
}
}
</script>
<script id="vertexShader" type="x-shader/x-vertex">
varying vec2 vUv;
varying vec4 pos;
void main()
{
vUv = uv;
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_Position = projectionMatrix * mvPosition;
pos=projectionMatrix * mvPosition;
}
</script>
最后结果:
三、进一步完善效果,这个时候发现两个颜色界限太明显,如果能够柔和过渡一下效果可能会更好一些。
根据这种想法,我们把过渡段的正负100的位置加上条件判断,从而确定过渡段的颜色。这里的一个关键点是用了mix这个内置函数。
<script id="fragment_shader" type="x-shader/x-fragment">
uniform float time;
varying vec2 vUv;
varying vec4 pos;
void main( void ) {
if(pos.y>100.0){
gl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 );
}
else if(pos.y<-100.0){
gl_FragColor = vec4( 0.0, 1.0, 0.0, 1.0 );
}
else{
gl_FragColor = mix(vec4( 0.0, 1.0, 0.0, 1.0 ),vec4( 1.0, 0.0, 0.0, 1.0 ),smoothstep(-100.0,100.0,pos.y));
}
}
</script>
<script id="vertexShader" type="x-shader/x-vertex">
varying vec2 vUv;
varying vec4 pos;
void main()
{
vUv = uv;
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_Position = projectionMatrix * mvPosition;
pos=projectionMatrix * mvPosition;
}
</script>
最终效果: