jnsmalm / pixi3d

The 3D renderer for PixiJS. Seamless integration with 2D applications.
https://pixi3d.org
MIT License
759 stars 44 forks source link

Height map example? #75

Closed frankandrobot closed 2 years ago

frankandrobot commented 2 years ago

First off good work. But is there a way to do height maps w/ this library? Looking to procedurally generate some terrain...

jnsmalm commented 2 years ago

Hello and thanks!

Yes, you can generate height maps, it's not something that is build in but you can generate the geometry for it. Below is a full example of a simple height map. It creates the geometry and gives the vertices colors depending on the height of that vertex. It also uses a custom material/shader to display it for simplicity. You could also use the build in standard material to render it of course, but if you want lighting to work you need to generate the normals for the vertices (which I didn't include in this example), there should be plenty of info how to to this in WebGL already. There is some small amount of TypeScript in this code.

import { Application, Shader, Program } from "pixi.js";
import { Camera, CameraOrbitControl, Material, Mesh3D, MeshGeometry3D, MeshShader } from "pixi3d";

let app = new Application({
  backgroundColor: 0xdddddd,
  resizeTo: window,
  antialias: true
});
document.body.appendChild(app.view);

let control = new CameraOrbitControl(app.view)

const vert = `
attribute vec3 a_Position;
attribute vec3 a_Color;

varying vec3 v_Color;

uniform mat4 u_Model;
uniform mat4 u_ViewProjection;

void main() {
  v_Color = a_Color;
  gl_Position = u_ViewProjection * u_Model * vec4(a_Position, 1.0);
}
`;

const frag = `
varying vec3 v_Color;

void main() {
  gl_FragColor = vec4(v_Color, 1.0);
}
`;

class CustomShader extends MeshShader {
  constructor() {
    super(Program.from(vert, frag));
  }

  createShaderGeometry(geometry: MeshGeometry3D) {
    let result = super.createShaderGeometry(geometry, false)
    if (geometry.positions) {
      result.addAttribute("a_Position", <Float32Array>geometry.positions.buffer, 3);
    }
    // @ts-ignore
    if (geometry.colors) {
      // @ts-ignore
      result.addAttribute("a_Color", geometry.colors.buffer, 3);
    }
    return result;
  }
}

class CustomMaterial extends Material {
  updateUniforms(mesh: Mesh3D, shader: Shader) {
    shader.uniforms.u_Model = mesh.worldTransform.toArray(false);
    shader.uniforms.u_ViewProjection = Camera.main.viewProjection;
  }

  createShader() {
    return new CustomShader();
  }
}

const W = 30 + 1
const H = 30 + 1

let heights: any[] = []
for (let i = 0; i < W; i++) {
  let data = []
  for (let i = 0; i < H; i++) {
    data.push(Math.random())
  }
  heights.push(data)
}

const getPoints = () => {
  let points = new Float32Array(W * H * 3)
  let index = 0
  for (let x = 0; x < W; x++) {
    for (let z = 0; z < H; z++) {
      points[index++] = (-W / 2 * 0.3) + x * 0.3
      points[index++] = heights[x][z] * 0.4
      points[index++] = (-H / 2 * 0.3) + z * 0.3
    }
  }
  return points
}

const getIndices = () => {
  let indices = new Uint16Array(W * H * 6)
  let index = 0
  for (let x = 0; x < W - 1; x++) {
    for (let z = 0; z < H - 1; z++) {
      indices[index++] = x + z * W
      indices[index++] = x + z * W + 1
      indices[index++] = x + z * W + H

      indices[index++] = x + z * W + H
      indices[index++] = x + z * W + 1
      indices[index++] = x + z * W + H + 1
    }
  }
}

let getColors = () => {
  let colors = new Float32Array(W * H * 3)
  let index = 0
  for (let x = 0; x < W; x++) {
    for (let z = 0; z < H; z++) {
      colors[index++] = heights[x][z]
      colors[index++] = heights[x][z]
      colors[index++] = heights[x][z]
    }
  }
  return colors
}

let geometry = Object.assign(new MeshGeometry3D(), {
  positions: {
    buffer: getPoints()
  },
  indices: {
    buffer: getIndices()
  },
  colors: {
    buffer: getColors()
  }
});

app.stage.addChild(new Mesh3D(geometry, new CustomMaterial()));
jnsmalm commented 2 years ago

Looks something like this:

Screenshot 2022-01-29 at 11 07 33
frankandrobot commented 2 years ago

This is perfect. Thanks!