Search code examples
javascriptthree.jsglslcoordinate-transformation

How can I scale a 3D Cube while maintaining it's center in WebGL GLSL?


I have a scale attribute like that I am applying to different instances of a instance buffered geometry in three js. The shader look like this:

attribute float scale;

uniform vec3 uMeshPosition;

void main() {
  vec3 pos = position;
  pos.x *= ( uMeshPosition.x - pows.x ) * scale + uMeshPosition.x;
  pos.z *=  ( uMeshPosition.z - pos.z ) * scale + uMeshPosition.z;
  pos.y *=  ( uMeshPosition.y - pos.y ) * scale + uMeshPosition.y;

  gl_Position = projectionMatrix * modelViewMatrix * vec4(pos,1.0);
}

position problem

Z is the height in my case!!!

I'd like the scaled cubes to maintain their initial center indicated by the wireframe cube above.

partial solution

Can scaling applied on all 3 axis be done without calculating and applying scale on the CPU?

Updates:

The way I'm creating the Geometry, normally with multiple cubes, but for this example it will be just one instance:

 const createInstancedGeometry = (instanceCount, sizeX = 1, sizeY = 1, sizeZ = 1) => {
  const geometry = new InstancedBufferGeometry()
  geometry.maxInstancedCount = instanceCount

  const shape = new BoxBufferGeometry(0.1 * sizeX, 0.1 * sizeY, 0.1 * sizeZ)
  shape.translate(0, 0.4, 0)

  const data = shape.attributes

  geometry.addAttribute('position', new BufferAttribute(new Float32Array(data.position.array), 3))
  geometry.addAttribute('uv', new BufferAttribute(new Float32Array(data.uv.array), 2))
  geometry.addAttribute('normal', new BufferAttribute(new Float32Array(data.normal.array), 3))
  geometry.setIndex(new BufferAttribute(new Uint16Array(shape.index.array), 1))
  shape.dispose()

  createInstancedAtrributes(geometry, instanceCount)

  return geometry
}

This is the way I'm setting up the shader, I'm not using the colors yet.

const createShader = () => {
  const uniforms = {
    // uMap: { type: 't', value: null },
    uColor1: { type: 'c', value: new Color(0x961800) }, // red
    uColor2: { type: 'c', value: new Color(0x4b5828) }, // yellow
    uMeshPosition: { type: 'vec3', value: new Vector3(0, 0, 0) },
  }

  const shader = new ShaderMaterial({
    uniforms,
    vertexShader,
    fragmentShader,
    blending: AdditiveBlending,
    transparent: true,
    depthWrite: false,
  })

  return shader
}

The constructor for my Particle Fire looks like this:

constructor({ sizeX = 1, sizeY = 1, sizeZ = 1 } = {}) {
    const instanceCount = 1
    const geometry = createInstancedGeometry(instanceCount, sizeX, sizeY, sizeZ)
    const material = createShader()

    const mesh = new Mesh(geometry, material)
    mesh.frustumCulled = false

    this.geometry = geometry
    this.material = material
    this.mesh = mesh

    mesh.up = new Vector3(0, 0, 1)
    mesh.position.set(2, 2, 1)
    mesh.rotateX(Math.PI / 2)

    this.instanceCount = instanceCount

    const lineGeo = new EdgesGeometry(geometry) // or WireframeGeometry
    const mat = new LineBasicMaterial({ color: 0xffffff, linewidth: 2 })
    const wireframe = new LineSegments(lineGeo, mat)
    this.mesh.add(wireframe)
  }

And the update call:

update() {
    const { instanceCount } = this
    const { scale, progress, randoms } = this.geometry.attributes
    const { uMeshPosition } = this.material.uniforms
    uMeshPosition.value = this.mesh.position

    for (let i = 0; i < instanceCount; i += 1) {
      let value = progress.array[i]
      value += 0.025

      if (value > 1) {
        value -= 1
        scale.setX(i, randomValueBetween(0.3, 2, 3))
        // randoms.setX(i, randomValueBetween(0, 1, 3))
      }

      // progress.setX(i, value)
    }


    scale.needsUpdate = true
    // randoms.needsUpdate = true
    // progress.needsUpdate = true
  }

I'm adding the object to the scene like this:

const pFire = new ParticleFire()
scene.add(pFire.mesh)

And updating it in a render loop like this:

  pFire.update({ deltaTime })

  renderer.render(scene, cameraController.camera)

  requestAnimationFrame(animate)

And cameraController.camera is a simple camera controller added to the scene as a child to a 'character' that I move around the scene.

  configuredCamera = new PerspectiveCamera(
    75, window.innerWidth / window.innerHeight, 0.1, 5000,
  )

Solution

  • The translate function of THREE.Geometry translates each vertex of the mesh. So the mesh it self is displaced and its center is not (0, 0, 0) anymore.

    You should set the geoemtry of the instead (position). See THREE.Object3D.

    This means you have to delete shape.translate(0, 0.4, 0) and you have to do the whole placement by geometry.position.
    Then the mesh is not displaced anymore and is placed by the modelViewMatrix alone. And the GLS shader code will work:

    gl_Position = projectionMatrix * modelViewMatrix * vec4(pos * scale, 1.0);