commit e542dbc37d8ad268a43d547ba991e710116fca65
parent 34b252a70c040809264409a9dab62f8236283ac6
Author: mjkloeckner <martin.cachari@gmail.com>
Date: Mon, 3 Jun 2024 12:20:58 -0300
add tree leaves and terrain texture
Diffstat:
A | tp/assets/treesShaders.js | | | 166 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | tp/src/trees.js | | | 119 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------ |
2 files changed, 267 insertions(+), 18 deletions(-)
diff --git a/tp/assets/treesShaders.js b/tp/assets/treesShaders.js
@@ -0,0 +1,166 @@
+export const vertexShader = `
+ precision highp float;
+
+ attribute vec3 position;
+ attribute vec2 uv;
+
+ uniform mat4 modelMatrix; // Matriz de transformación del objeto
+ uniform mat4 viewMatrix; // Matriz de transformación de la cámara
+ uniform mat4 projectionMatrix; // Matriz de proyección de la cámara
+
+ varying vec2 vUv;
+
+ void main() {
+ vec3 pos = position;
+ gl_Position = projectionMatrix*viewMatrix*modelMatrix* vec4(pos, 1.0);
+ vUv = uv;
+ }`;
+
+export const fragmentShader = `
+ precision mediump float;
+
+ uniform float scale1;
+ uniform float mask1low;
+ uniform float mask1high;
+ uniform float mask2low;
+ uniform float mask2high;
+ uniform sampler2D tierraSampler;
+ uniform sampler2D rocaSampler;
+ uniform sampler2D pastoSampler;
+
+ varying vec2 vUv;
+
+ // Perlin Noise
+ vec3 mod289(vec3 x){
+ return x - floor(x * (1.0 / 289.0)) * 289.0;
+ }
+
+ vec4 mod289(vec4 x){
+ return x - floor(x * (1.0 / 289.0)) * 289.0;
+ }
+
+ vec4 permute(vec4 x){
+ return mod289(((x * 34.0) + 1.0) * x);
+ }
+
+ vec4 taylorInvSqrt(vec4 r){
+ return 1.79284291400159 - 0.85373472095314 * r;
+ }
+
+ vec3 fade(vec3 t) {
+ return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);
+ }
+
+ // Classic Perlin noise
+ float cnoise(vec3 P){
+ vec3 Pi0 = floor(P);
+ vec3 Pi1 = Pi0 + vec3(1.0);
+ Pi0 = mod289(Pi0);
+ Pi1 = mod289(Pi1);
+ vec3 Pf0 = fract(P);
+ vec3 Pf1 = Pf0 - vec3(1.0);
+ vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);
+ vec4 iy = vec4(Pi0.yy, Pi1.yy);
+ vec4 iz0 = Pi0.zzzz;
+ vec4 iz1 = Pi1.zzzz;
+
+ vec4 ixy = permute(permute(ix) + iy);
+ vec4 ixy0 = permute(ixy + iz0);
+ vec4 ixy1 = permute(ixy + iz1);
+
+ vec4 gx0 = ixy0 * (1.0 / 7.0);
+ vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5;
+ gx0 = fract(gx0);
+ vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);
+ vec4 sz0 = step(gz0, vec4(0.0));
+ gx0 -= sz0 * (step(0.0, gx0) - 0.5);
+ gy0 -= sz0 * (step(0.0, gy0) - 0.5);
+
+ vec4 gx1 = ixy1 * (1.0 / 7.0);
+ vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5;
+ gx1 = fract(gx1);
+ vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);
+ vec4 sz1 = step(gz1, vec4(0.0));
+ gx1 -= sz1 * (step(0.0, gx1) - 0.5);
+ gy1 -= sz1 * (step(0.0, gy1) - 0.5);
+
+ vec3 g000 = vec3(gx0.x, gy0.x, gz0.x);
+ vec3 g100 = vec3(gx0.y, gy0.y, gz0.y);
+ vec3 g010 = vec3(gx0.z, gy0.z, gz0.z);
+ vec3 g110 = vec3(gx0.w, gy0.w, gz0.w);
+ vec3 g001 = vec3(gx1.x, gy1.x, gz1.x);
+ vec3 g101 = vec3(gx1.y, gy1.y, gz1.y);
+ vec3 g011 = vec3(gx1.z, gy1.z, gz1.z);
+ vec3 g111 = vec3(gx1.w, gy1.w, gz1.w);
+
+ vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000),
+ dot(g010, g010),
+ dot(g100, g100),
+ dot(g110, g110)));
+ g000 *= norm0.x;
+ g010 *= norm0.y;
+ g100 *= norm0.z;
+ g110 *= norm0.w;
+ vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001),
+ dot(g011, g011),
+ dot(g101, g101),
+ dot(g111, g111)));
+ g001 *= norm1.x;
+ g011 *= norm1.y;
+ g101 *= norm1.z;
+ g111 *= norm1.w;
+
+ float n000 = dot(g000, Pf0);
+ float n100 = dot(g100, vec3(Pf1.x, Pf0.yz));
+ float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z));
+ float n110 = dot(g110, vec3(Pf1.xy, Pf0.z));
+ float n001 = dot(g001, vec3(Pf0.xy, Pf1.z));
+ float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z));
+ float n011 = dot(g011, vec3(Pf0.x, Pf1.yz));
+ float n111 = dot(g111, Pf1);
+
+ vec3 fade_xyz = fade(Pf0);
+ vec4 n_z = mix(vec4(n000, n100, n010, n110),
+ vec4(n001, n101, n011, n111),
+ fade_xyz.z);
+
+ vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y);
+ float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x);
+ return 2.2 * n_xyz;
+ }
+
+ void main(void) {
+ vec2 uv2=vUv*scale1;
+
+ // se mezcla la textura 'pasto' a diferentes escalas
+ vec3 pasto1 = texture2D(pastoSampler, uv2 * 1.0).xyz;
+ vec3 pasto2 = texture2D(pastoSampler, uv2 * 3.13).xyz;
+ vec3 pasto3 = texture2D(pastoSampler, uv2 * 2.37).xyz;
+ vec3 colorPasto = mix(mix(pasto1, pasto2, 0.5), pasto3, 0.3);
+
+ // lo mismo para la textura 'tierra'
+ vec3 tierra1 = texture2D(tierraSampler, uv2*3.77).xyz;
+ vec3 tierra2 = texture2D(tierraSampler, uv2*1.58).xyz;
+ vec3 colorTierra = mix(tierra1, tierra2, 0.5);
+
+ // lo mismo para la textura 'roca'
+ vec3 roca1 = texture2D(rocaSampler, uv2).xyz;
+ vec3 roca2 = texture2D(rocaSampler, uv2*2.38).xyz;
+ vec3 colorRoca = mix(roca1, roca2, 0.5);
+
+ float noise1 = cnoise(uv2.xyx*8.23+23.11);
+ float noise2 = cnoise(uv2.xyx*11.77+9.45);
+ float noise3 = cnoise(uv2.xyx*14.8+21.2);
+ float mask1 = mix(mix(noise1, noise2, 0.5), noise3, 0.3);
+ mask1 = smoothstep(mask1low, mask1high, mask1);
+
+ float noise4 = cnoise(uv2.xyx*8.23*scale1);
+ float noise5 = cnoise(uv2.xyx*11.77*scale1);
+ float noise6 = cnoise(uv2.xyx*14.8*scale1);
+ float mask2 = mix(mix(noise4, noise5, 0.5), noise6, 0.3);
+ mask2 = smoothstep(mask2low, mask2high, mask2);
+ vec3 colorTierraRoca = mix(colorTierra, colorRoca, mask1);
+ vec3 color = mix(colorPasto, colorTierraRoca, mask2);
+
+ gl_FragColor = vec4(color, 1.0);
+ }`;
diff --git a/tp/src/trees.js b/tp/src/trees.js
@@ -1,8 +1,15 @@
import * as THREE from 'three';
import * as dat from 'dat.gui';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
+import { vertexShader, fragmentShader } from '/assets/treesShaders.js';
-let scene, camera, renderer, container, material;
+let scene, camera, renderer, container, terrainMaterial, instancedTrees;
+
+const textures = {
+ tierra: { url: '/assets/tierra.jpg', object: null },
+ roca: { url: '/assets/roca.jpg', object: null },
+ pasto: { url: '/assets/pasto.jpg', object: null },
+};
function onResize() {
camera.aspect = container.offsetWidth / container.offsetHeight;
@@ -31,9 +38,12 @@ function setupThreeJs() {
scene.add(hemisphereLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
- directionalLight.position.set(1, 1, 1);
+ directionalLight.position.set(100, 100, 100);
scene.add(directionalLight);
+ const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight, 5);
+ scene.add(directionalLightHelper);
+
//const gridHelper = new THREE.GridHelper(50, 20);
//scene.add(gridHelper);
@@ -47,20 +57,30 @@ function setupThreeJs() {
function createInstancedTrees(count) {
console.log('Generating `' + count + '` instances of tree');
- const treeLogGeometry = new THREE.CylinderGeometry(0.35, 0.35, 4, 40, 40);
- //const treeLeavesGeometry = new THREE.SphereGeometry(1.75,40,40);
+ let logHeight = 4.0;
+
+ const treeLogGeometry = new THREE.CylinderGeometry(0.30, 0.30, logHeight, 40, 40);
+ const treeLeavesGeometry = new THREE.SphereGeometry(1.75,40,40);
- treeLogGeometry.translate(0, 2, 0);
+ treeLogGeometry.translate(0, logHeight/2.0, 0);
const instancedTreeLogGeometry = new THREE.InstancedBufferGeometry();
instancedTreeLogGeometry.copy(treeLogGeometry);
- const treeLogMaterial = new THREE.MeshPhongMaterial({color: 0x7c3f00});
+ const instancedTreeLeavesGeometry = new THREE.InstancedBufferGeometry();
+ instancedTreeLeavesGeometry.copy(treeLeavesGeometry);
+
+ const treeLogMaterial = new THREE.MeshPhongMaterial({color: 0x7c3f00});
const instancedTreeLogs = new THREE.InstancedMesh(instancedTreeLogGeometry, treeLogMaterial, count);
+ const treeLeavesMaterial = new THREE.MeshPhongMaterial({color: 0x365829});
+ const instancedTreeLeaves = new THREE.InstancedMesh(instancedTreeLeavesGeometry, treeLeavesMaterial, count);
+
const rotMatrix = new THREE.Matrix4();
+
const translationMatrix = new THREE.Matrix4();
- const matrix = new THREE.Matrix4();
+ const treeLogMatrix = new THREE.Matrix4();
+ const treeLeavesMatrix = new THREE.Matrix4();
//let origin = new THREE.Vector3();
const RANGE = 50 - 4/2;
@@ -75,15 +95,25 @@ function createInstancedTrees(count) {
translationMatrix.makeTranslation(position);
//rotMatrix.lookAt(0, 0, new THREE.Vector3(0, 1, 0));
- matrix.identity();
- matrix.makeScale(1, 0.5 + Math.random()*2, 1);
+ treeLogMatrix.identity();
+ treeLeavesMatrix.identity();
+
+ let scale = 0.5 + (Math.random()*(logHeight/3));
+ treeLogMatrix.makeScale(1, scale, 1);
//matrix.premultiply(rotMatrix);
- matrix.premultiply(translationMatrix);
- instancedTreeLogs.setMatrixAt(i, matrix);
+ treeLogMatrix.premultiply(translationMatrix);
+
+ position.y = scale*logHeight;
+ translationMatrix.makeTranslation(position);
+ treeLeavesMatrix.premultiply(translationMatrix);
+
+ instancedTreeLogs.setMatrixAt(i, treeLogMatrix);
+ instancedTreeLeaves.setMatrixAt(i, treeLeavesMatrix);
}
- return instancedTreeLogs;
+ scene.add(instancedTreeLogs);
+ scene.add(instancedTreeLeaves);
}
function buildScene() {
@@ -91,19 +121,72 @@ function buildScene() {
console.log('Generating terrain');
const terrainGeometry = new THREE.PlaneGeometry(50, 50);
- const terrainMaterial = new THREE.MeshPhongMaterial( {color: 0x365829, side: THREE.DoubleSide} );
+ //const terrainMaterial = new THREE.MeshPhongMaterial( {color: 0x365829, side: THREE.DoubleSide} );
+ terrainMaterial = new THREE.RawShaderMaterial({
+ uniforms: {
+ tierraSampler: { type: 't', value: textures.tierra.object },
+ rocaSampler: { type: 't', value: textures.roca.object },
+ pastoSampler: { type: 't', value: textures.pasto.object },
+ scale1: { type: 'f', value: 2.0 },
+
+ mask1low: { type: 'f', value: -0.38 },
+ mask1high: { type: 'f', value: 0.1 },
+
+ mask2low: { type: 'f', value: 0.05 },
+ mask2high: { type: 'f', value: -0.70 },
+ },
+ vertexShader: vertexShader,
+ fragmentShader: fragmentShader,
+ side: THREE.DoubleSide,
+ });
+ terrainMaterial.needsUpdate = true;
+
const terrain = new THREE.Mesh(terrainGeometry, terrainMaterial);
terrain.rotateX(Math.PI/2);
terrain.position.set(0, 0, 0);
scene.add(terrain);
console.log('Generating trees');
- const trees = createInstancedTrees(20);
- scene.add(trees);
+ createInstancedTrees(35);
}
+function onTextureLoaded(key, texture) {
+ texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
+ textures[key].object = texture;
+ console.log('Texture `' + key + '` loaded');
+}
+
+function loadTextures(callback) {
+ const loadingManager = new THREE.LoadingManager();
+
+ loadingManager.onLoad = () => {
+ console.log('All textures loaded');
+ callback();
+ };
+
+ for (const key in textures) {
+ console.log("Loading textures");
+ const loader = new THREE.TextureLoader(loadingManager);
+ const texture = textures[key];
+ texture.object = loader.load(
+ texture.url,
+ onTextureLoaded.bind(this, key),
+ null,
+ (error) => {
+ console.error(error);
+ }
+ );
+ }
+}
+
+
function createMenu() {
const gui = new dat.GUI({ width: 400 });
+ gui.add(terrainMaterial.uniforms.scale1, 'value', 0, 10).name('Texture scale');
+ gui.add(terrainMaterial.uniforms.mask1low, 'value', -1, 1).name('Mask1 Low');
+ gui.add(terrainMaterial.uniforms.mask1high, 'value', -1, 1).name('Mask1 High');
+ gui.add(terrainMaterial.uniforms.mask2low, 'value', -1, 1).name('Mask2 Low');
+ gui.add(terrainMaterial.uniforms.mask2high, 'value', -1, 1).name('Mask2 High');
}
function mainLoop() {
@@ -112,10 +195,10 @@ function mainLoop() {
}
function main() {
- setupThreeJs();
buildScene();
- //createMenu();
+ createMenu();
mainLoop();
}
-main();
+setupThreeJs();
+loadTextures(main);