Hi, so I’ve been making some grass using TSL and it looks great. Unfortunately the lighting is off, and I think it is due to the normals not being properly calculated after the vertex displacement. Here is my code for the grass:
import * as THREE from 'three/webgpu'
import * as TSL from 'three/tsl';
export class Grass extends THREE.InstancedMesh {
constructor(model) {
var geo, mat;
model.traverse((e) => {
if (e.geometry) {
geo = e.geometry
}
})
var positions = []
const offset = 0.03
const xCount = 5
const yCount = 5
var count = 0;
var currentPos;
for (var x = 0; x < xCount; x += offset) {
for (var y = 0; y < yCount; y += offset) {
currentPos = [x, y]
currentPos[0] += THREE.MathUtils.randFloat(-offset / 2, offset / 2)
currentPos[1] += THREE.MathUtils.randFloat(-offset / 2, offset / 2)
positions.push(new THREE.Vector3(currentPos[0], 0, currentPos[1]))
count++
}
}
console.log(count)
mat = new THREE.MeshStandardNodeMaterial({ side: THREE.DoubleSide, metalness: 1, roughness: 1 })
var dummyModel = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), new THREE.MeshStandardMaterial())
super(geo, mat, count)
this.mat = mat
this.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
var instanceOffsets = new Float32Array(count * 3);
for (var i = 0; i < count; i++) {
dummyModel.position.copy(positions[i])
//dummyModel.quaternion.copy(new THREE.Quaternion().setFromEuler(new THREE.Euler(0, THREE.MathUtils.randFloat(-Math.PI / 8, Math.PI / 8), 0)))
dummyModel.scale.copy(new THREE.Vector3(0.25, 1, 0.25))
dummyModel.updateMatrix()
this.setMatrixAt(i, dummyModel.matrix)
instanceOffsets.set(positions[i].toArray(), i * 3);
}
this.instanceOffsetAttribute = new THREE.InstancedBufferAttribute(instanceOffsets, 3);
this.geometry.setAttribute('instanceOffset', this.instanceOffsetAttribute);
this.needsUpdate = true
var instanceOffsetNode = TSL.attribute('instanceOffset', 'vec3');
this.time = TSL.uniform('float');
var uv = TSL.uv().mul(-1).add(1)
var invertedUv = TSL.uv()
var normal = TSL.normalLocal // Also tried with normalLocal and normalView, but normalWorld seems to get the best results
// BLADE BENDING
var bendingFactor = 0.6
var bendingAngle = uv.x.mul(bendingFactor);
var bendingCosAngleX = TSL.cos(bendingAngle);
var bendingSinAngleX = TSL.sin(bendingAngle);
var bendingRotationMatrixX = TSL.mat3(
bendingCosAngleX, bendingSinAngleX.mul(-1), 0,
bendingSinAngleX, bendingCosAngleX, 0,
0, 0, 1
);
var bendingX = bendingRotationMatrixX.mul(TSL.positionLocal)
normal = bendingRotationMatrixX.mul(normal)
var randomAngleFactor = 1
var randomAngleMap = TSL.texture(
new THREE.TextureLoader().load("noiseTexture4.png"),
TSL.fract(instanceOffsetNode.xz)
)
var randomAngle = TSL.remap(randomAngleMap.x, 0, 1, 0, 2 * Math.PI).mul(randomAngleFactor)
var bendingCosAngleY = TSL.cos(randomAngle);
var bendingSinAngleY = TSL.sin(randomAngle);
var bendingRotationMatrixY = TSL.mat3(
bendingCosAngleY, 0, bendingSinAngleY,
0, 1, 0,
bendingSinAngleY.mul(-1), 0, bendingCosAngleY
);
var bendingY = bendingRotationMatrixY.mul(bendingX)
normal = bendingRotationMatrixY.mul(normal)
// GENERAL WIND BENDING
var generalWindTextureScalingFactor = 1
var generalWindFactor = 1.75
var generalWindSpeed = 2;
var generalWind = TSL.texture(
new THREE.TextureLoader().load("noiseTexture.png"),
TSL.fract(instanceOffsetNode.xz.mul(generalWindTextureScalingFactor).add(this.time.mul(generalWindSpeed)).mul(1 / 256))
)
var generalWindAngle = uv.x.mul(generalWind.r).mul(generalWindFactor)
var generalWindCosAngle = TSL.cos(generalWindAngle);
var generalWindSinAngle = TSL.sin(generalWindAngle);
var generalWindRotationMatrix = TSL.mat3(
generalWindCosAngle, generalWindSinAngle.mul(-1), 0,
generalWindSinAngle, generalWindCosAngle, 0,
0, 0, 1
);
var generalWindBending = generalWindRotationMatrix.mul(bendingY)
normal = generalWindRotationMatrix.mul(normal)
// DIRECTIONAL WIND BENDING
var directionalWindTextureScalingFactor = 1
var directionaWindStrenghtFactor = 0.8
var directionalWindSpeed = 6
var directionalWindAngleMap = TSL.texture(
new THREE.TextureLoader().load("noiseTexture3.png"),
TSL.fract(instanceOffsetNode.xz.mul(directionalWindTextureScalingFactor).add(this.time.mul(directionalWindSpeed).add(0.1)).mul(1 / 256))
);
var directionalWindAngle = TSL.remap(directionalWindAngleMap.r, 0, 1, 0, 2 * Math.PI)
var directionalWindCosAngleY = TSL.cos(directionalWindAngle);
var directionalWindSinAngleY = TSL.sin(directionalWindAngle);
var directionalWindRotationMatrixY = TSL.mat3(
directionalWindCosAngleY, 0, directionalWindSinAngleY,
0, 1, 0,
directionalWindSinAngleY.mul(-1), 0, directionalWindCosAngleY
);
var directionalWindStrenghtMap = TSL.texture(
new THREE.TextureLoader().load("noiseTexture3.png"),
TSL.fract(instanceOffsetNode.xz.mul(directionalWindTextureScalingFactor).add(this.time.mul(directionalWindSpeed).add(0.3)).mul(1 / 256))
);
var directionalWindStrenght = directionalWindStrenghtMap.r.mul(directionaWindStrenghtFactor).mul(-1)
var directionalWindCosAngleX = TSL.cos(directionalWindStrenght);
var directionalWindSinAngleX = TSL.sin(directionalWindStrenght);
var directionalWindRotationMatrixX = TSL.mat3(
directionalWindCosAngleX, directionalWindSinAngleX.mul(-1), 0,
directionalWindSinAngleX, directionalWindCosAngleX, 0,
0, 0, 1
);
var directionalWindBendingY = directionalWindRotationMatrixY.mul(generalWindBending)
normal = directionalWindRotationMatrixY.mul(normal)
var directionalWindBendingX = directionalWindRotationMatrixX.mul(directionalWindBendingY)
normal = directionalWindRotationMatrixX.mul(normal)
mat.positionNode = directionalWindBendingX
mat.normalNode = TSL.normalize(normal)
mat.colorNode = TSL.mix(TSL.vec3(0.05, 0.2, 0.01), TSL.vec3(0.5, 0.5, 0.1), TSL.uv().x.mul(-1).add(1))
}
update(elapsed) {
this.time.value = elapsed
}
}
I can’t see to understand how to recalculate them, I have also tried using dFdx and dFdy to get the tangent and bitangent and them getting the cross product but it still didn’t work. If you know a cleaner solution let me know. Btw if you want you can suggest any modification to the code that you think will make the end resolut better or simpler.
Thank you very much and happy Christmas!