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 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
| import * as THREE from "three"; import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"; let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
camera.position.set(0, 9, 57) let renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); new THREE.TextureLoader().load('/textures/day.jpg', (loaded) => { loaded.mapping = THREE.EquirectangularReflectionMapping scene.background = loaded scene.environment = loaded });
let controls = new OrbitControls(camera, renderer.domElement);
const range = 100; let planeGeometry = new THREE.PlaneGeometry(range, range, 1, 1); const textureLoader = new THREE.TextureLoader();
textureLoader.load('/textures/ground.jpg', (texture) => { texture.wrapS = texture.wrapT = THREE.RepeatWrapping; texture.repeat.set(10, 15); let material = new THREE.MeshBasicMaterial({ side: THREE.DoubleSide, map: texture, color: 0xb79f76 }); let mesh = new THREE.Mesh(planeGeometry, material); mesh.rotation.x = -Math.PI / 2; scene.add(mesh);
}) const grassMaterial = new THREE.ShaderMaterial({ uniforms: { uTime: { value: 0 }, uLightColor: { value: new THREE.Color(0xd9d9d9) }, uAmbientLight: { value: new THREE.Color(0xadadad) }, uColor: { value: new THREE.Color(0x43c70d) }, uLightDirection: { value: new THREE.Vector3(-1.0, -1.0, 0).normalize() }, uWindDirection: { value: new THREE.Vector2(0.8,0.4).normalize() } }, side: THREE.DoubleSide, vertexShader: ` uniform float uTime;//时间变量,用于驱动动画效果 uniform vec2 uWindDirection;//风方向
varying vec3 vPosition;//传递到片元着色器中的顶点坐标 varying vec3 vNormal;//传递到片元着色器中的法线信息
void main() { vPosition = position; vNormal = normalize(normalMatrix * normal); if (position.y > 0.1) { //结合空间位置与风向计算当前顶点的空间因子 //简而言之,为了让草的摆动更加自然,将摆动幅度计算与其空间位置联系起来 //让不同位置的草在同一时刻有不同的摆动幅度 float factor = dot(vPosition.xz, uWindDirection) * 0.06;
//基础摆动幅度,结合空间因子传入正弦函数形成周期性差异化运动 float swayAmplitude = sin(uTime / 500.0 + factor);
//摆动幅度随高度增加而增强,为了形成更拟真的效果这里使用平方增长而非线性增长 float swayStrength = swayAmplitude * vPosition.y * vPosition.y * 0.07; //根据风向将摆动应用到 x 和 z 方向 vPosition.x += swayStrength * uWindDirection.x; vPosition.z += swayStrength * uWindDirection.y; } gl_Position = projectionMatrix * modelViewMatrix * vec4(vPosition, 1.0); }`, fragmentShader: ` varying vec3 vPosition;// 从顶点着色器传入的顶点位置 varying vec3 vNormal;// 从顶点着色器传入的顶点法线
uniform vec3 uLightColor;// 平行光源颜色 uniform vec3 uAmbientLight;// 环境光颜色 uniform vec3 uColor;// 基础颜色(用于草的颜色) uniform vec3 uLightDirection;// 光源方向
void main() { //根据顶点高度(y 值)进行颜色插值,模拟草由下到上的渐变效果 vec3 color = mix(uColor * 0.5, uColor, vPosition.y);
//计算光照方向与法线之间的夹角余弦值(漫反射系数) //使用 max 避免负值 float nDotL = max(dot(uLightDirection, vNormal), 0.0);
//漫反射光照=光源颜色 * 材质颜色 * 漫反射系数 vec3 diffuse = uLightColor * color * nDotL;
//环境光照=环境光颜色 * 材质颜色 vec3 ambient = uAmbientLight * color;
//最终输出颜色为漫反射 + 环境光 gl_FragColor = vec4(diffuse + ambient, 1.0); }`, }) let vertexes = []; let indices = []; let getRandom = function (min, max) { return Math.random() * (max - min) + min; } const count = 20000; for (let i = 0; i < count; i++) { let x = getRandom(-range / 2, range / 2); let z = getRandom(-range / 2, range / 2); const angleBottom = getRandom(0, Math.PI * 2); const angleTop = getRandom(-0.2, 0.2); const height = getRandom(2, 5); const widthBottom = 0.3; const widthTop = 0.2;
let tempVertexes = [ [x - widthBottom / 2, 0, z], [x + widthBottom / 2, 0, z], [x - widthTop / 2, height / 2, z], [x + widthTop / 2, height / 2, z], [x, height, z], ]; let translateToOrigin = new THREE.Matrix4().makeTranslation(-x, -height / 2, -z); let translateBack = new THREE.Matrix4().makeTranslation(x, height / 2, z); let rotationY = new THREE.Matrix4().makeRotationY(angleBottom); tempVertexes.map((vertex, index) => { vertex = new THREE.Vector3(...vertex); vertex.applyMatrix4(translateToOrigin); vertex.applyMatrix4(rotationY); if (index == 4) { vertex.applyMatrix4(new THREE.Matrix4().makeRotationZ(angleTop)); } vertex.applyMatrix4(translateBack); vertexes.push(...vertex.toArray()); }) let startIndex = i * 5; indices.push( startIndex, startIndex + 1, startIndex + 2, startIndex + 1, startIndex + 3, startIndex + 2, startIndex + 2, startIndex + 3, startIndex + 4 ); }
let grassGeometry = new THREE.BufferGeometry(); grassGeometry.setAttribute( 'position', new THREE.BufferAttribute(new Float32Array(vertexes.flat()), 3) ); grassGeometry.setIndex(indices); grassGeometry.computeVertexNormals();
let grass = new THREE.Mesh(grassGeometry, grassMaterial); scene.add(grass); function animate(time) { requestAnimationFrame(animate); controls.update(); renderer.render(scene, camera); grassMaterial.uniforms.uTime.value = time } animate();
|