regl-project / regl

👑 Functional WebGL
MIT License
5.19k stars 320 forks source link

Typescript - Context type for shadow example #531

Open nkint opened 5 years ago

nkint commented 5 years ago

Hi! I'm trying to translate the directional shadow example in typescript:

https://github.com/regl-project/regl/blob/master/example/shadow_map.js#L85

But I have some problem in how to extend DefaultContext to let this work:

const globalScope = regl({
  context: {
    lightDir: [0.39, 0.87, 0.29]
  },
  uniforms: {
    lightDir: regl.context('lightDir'),
    lightView: (context) => {
      return mat4.lookAt([], context.lightDir, [0.0, 0.0, 0.0], [0.0, 1.0, 0.0])
    },
    lightProjection: mat4.ortho([], -25, 25, -20, 20, -25, 25)
  }
})

@davschne maybe? Thanks in advance

nkint commented 5 years ago

My current solution is this one:

const globalScope = regl<any, any, any, { lightDir: Vec3 }>({
    context: {
      lightDir: [0.9, 0.87, 0.29],
    },
    uniforms: {
      lightDir: regl.context<LightDirContext, 'lightDir'>('lightDir'),
      lightView: (context: LightDirContext) => {
        return mat4.lookAt(mat4.create(), context.lightDir, [0.0, 0.0, 0.0], [0.0, 1.0, 0.0])
      },
      lightProjection: mat4.ortho(mat4.create(), -25, 25, -20, 20, -25, 25),
    },
  })

Better ways?

davschne commented 5 years ago

Hi,

A little more info would be helpful for diagnosing your problem. Could you please post:

Thanks.

nkint commented 5 years ago

Hi @davschne thanks!

I'm using typescript v3.3.3333. With my solution I get no error but I'm asking if there is a better solution.

Here is how I have typed the example I linked:

/*
  tags: shadows, fbo, advanced
  <p>This example shows how you can render a shadow map for a directional light source in regl.</p>
 */
import createRegl, { Vec3, DefaultContext } from 'regl'
import  mat4 from 'gl-mat4'

const webglCanvas = document.body.appendChild(document.createElement('canvas'))
const camera = require('canvas-orbit-camera')(webglCanvas)
const regl = createRegl({
  canvas: webglCanvas,
  extensions: 'oes_texture_float'
})
const bunny = require('bunny')
const normals = require('angle-normals')

var SHADOW_RES = 1024

const fbo = regl.framebuffer({
  color: regl.texture({
    width: SHADOW_RES,
    height: SHADOW_RES,
    wrap: 'clamp',
    type: 'float'
  }),
  depth: true
})

const planeElements: Vec3[] = []
var planePosition: Vec3[] = []
var planeNormal: Vec3[] = []

planePosition.push([-0.5, 0.0, -0.5])
planePosition.push([+0.5, 0.0, -0.5])
planePosition.push([-0.5, 0.0, +0.5])
planePosition.push([+0.5, 0.0, +0.5])

planeNormal.push([0.0, 1.0, 0.0])
planeNormal.push([0.0, 1.0, 0.0])
planeNormal.push([0.0, 1.0, 0.0])
planeNormal.push([0.0, 1.0, 0.0])

planeElements.push([3, 1, 0])
planeElements.push([0, 2, 3])

var boxPosition: Vec3[] = [
  // side faces
  [-0.5, +0.5, +0.5], [+0.5, +0.5, +0.5], [+0.5, -0.5, +0.5], [-0.5, -0.5, +0.5], // positive z face.
  [+0.5, +0.5, +0.5], [+0.5, +0.5, -0.5], [+0.5, -0.5, -0.5], [+0.5, -0.5, +0.5], // positive x face
  [+0.5, +0.5, -0.5], [-0.5, +0.5, -0.5], [-0.5, -0.5, -0.5], [+0.5, -0.5, -0.5], // negative z face
  [-0.5, +0.5, -0.5], [-0.5, +0.5, +0.5], [-0.5, -0.5, +0.5], [-0.5, -0.5, -0.5], // negative x face.
  [-0.5, +0.5, -0.5], [+0.5, +0.5, -0.5], [+0.5, +0.5, +0.5], [-0.5, +0.5, +0.5],  // top face
  [-0.5, -0.5, -0.5], [+0.5, -0.5, -0.5], [+0.5, -0.5, +0.5], [-0.5, -0.5, +0.5]  // bottom face
]

const boxElements: Vec3[] = [
  [2, 1, 0], [2, 0, 3],
  [6, 5, 4], [6, 4, 7],
  [10, 9, 8], [10, 8, 11],
  [14, 13, 12], [14, 12, 15],
  [18, 17, 16], [18, 16, 19],
  [20, 21, 22], [23, 20, 22]
]

// all the normals of a single block.
var boxNormal: Vec3[] = [
  // side faces
  [0.0, 0.0, +1.0], [0.0, 0.0, +1.0], [0.0, 0.0, +1.0], [0.0, 0.0, +1.0],
  [+1.0, 0.0, 0.0], [+1.0, 0.0, 0.0], [+1.0, 0.0, 0.0], [+1.0, 0.0, 0.0],
  [0.0, 0.0, -1.0], [0.0, 0.0, -1.0], [0.0, 0.0, -1.0], [0.0, 0.0, -1.0],
  [-1.0, 0.0, 0.0], [-1.0, 0.0, 0.0], [-1.0, 0.0, 0.0], [-1.0, 0.0, 0.0],
  // top
  [0.0, +1.0, 0.0], [0.0, +1.0, 0.0], [0.0, +1.0, 0.0], [0.0, +1.0, 0.0],
  // bottom
  [0.0, -1.0, 0.0], [0.0, -1.0, 0.0], [0.0, -1.0, 0.0], [0.0, -1.0, 0.0]
]

type LightDirContext = createRegl.DefaultContext & {lightDir: Vec3}
// This call encapsulates the common state between `drawNormal` and `drawDepth`
const globalScope = regl<{}, {}, {}, { lightDir: Vec3 }>({
  context: {
    lightDir: [0.9, 0.87, 0.29],
  },
  uniforms: {
    lightDir: regl.context<LightDirContext, 'lightDir'>('lightDir'),
    lightView: (context: LightDirContext) => {
      return mat4.lookAt(mat4.create(), context.lightDir, [0.0, 0.0, 0.0], [0.0, 1.0, 0.0])
    },
    lightProjection: mat4.ortho(mat4.create(), -25, 25, -20, 20, -25, 25),
  },
})

const drawDepth = regl({
  frag: `
  precision mediump float;
  varying vec3 vPosition;
  void main () {
    gl_FragColor = vec4(vec3(vPosition.z), 1.0);
  }`,
  vert: `
  precision mediump float;
  attribute vec3 position;
  uniform mat4 lightProjection, lightView, model;
  varying vec3 vPosition;
  void main() {
    vec4 p = lightProjection * lightView * model * vec4(position, 1.0);
    gl_Position = p;
    vPosition = p.xyz;
  }`,
  framebuffer: fbo
})

const drawNormal = regl({
  uniforms: {
    view: () => camera.view(),
    projection: ({viewportWidth, viewportHeight}: createRegl.DefaultContext) =>
      mat4.perspective(mat4.create(),
                       Math.PI / 4,
                       viewportWidth / viewportHeight,
                       0.01,
                       2000),
    shadowMap: fbo,
    minBias: () => 0.005,
    maxBias: () => 0.03
  },
  frag: `
  precision mediump float;
  varying vec3 vNormal;
  varying vec3 vShadowCoord;
  uniform float ambientLightAmount;
  uniform float diffuseLightAmount;
  uniform vec3 color;
  uniform sampler2D shadowMap;
  uniform vec3 lightDir;
  uniform float minBias;
  uniform float maxBias;
#define texelSize 1.0 / float(${SHADOW_RES})
  float shadowSample(vec2 co, float z, float bias) {
    float a = texture2D(shadowMap, co).z;
    float b = vShadowCoord.z;
    return step(b-bias, a);
  }
  void main () {
    vec3 ambient = ambientLightAmount * color;
    float cosTheta = dot(vNormal, lightDir);
    vec3 diffuse = diffuseLightAmount * color * clamp(cosTheta , 0.0, 1.0 );
    float v = 1.0; // shadow value
    vec2 co = vShadowCoord.xy * 0.5 + 0.5;// go from range [-1,+1] to range [0,+1]
    // counteract shadow acne.
    float bias = max(maxBias * (1.0 - cosTheta), minBias);
    float v0 = shadowSample(co + texelSize * vec2(0.0, 0.0), vShadowCoord.z, bias);
    float v1 = shadowSample(co + texelSize * vec2(1.0, 0.0), vShadowCoord.z, bias);
    float v2 = shadowSample(co + texelSize * vec2(0.0, 1.0), vShadowCoord.z, bias);
    float v3 = shadowSample(co + texelSize * vec2(1.0, 1.0), vShadowCoord.z, bias);
    // PCF filtering
    v = (v0 + v1 + v2 + v3) * (1.0 / 4.0);
    // if outside light frustum, render now shadow.
    // If WebGL had GL_CLAMP_TO_BORDER we would not have to do this,
    // but that is unfortunately not the case...
    if(co.x < 0.0 || co.x > 1.0 || co.y < 0.0 || co.y > 1.0) {
      v = 1.0;
    }
    gl_FragColor = vec4((ambient + diffuse * v), 1.0);
  }`,
  vert: `
  precision mediump float;
  attribute vec3 position;
  attribute vec3 normal;
  varying vec3 vPosition;
  varying vec3 vNormal;
  varying vec3 vShadowCoord;
  uniform mat4 projection, view, model;
  uniform mat4 lightProjection, lightView;
  void main() {
    vPosition = position;
    vNormal = normal;
    vec4 worldSpacePosition = model * vec4(position, 1);
    gl_Position = projection * view * worldSpacePosition;
    vShadowCoord = (lightProjection * lightView * worldSpacePosition).xyz;
  }`
})

type MeshProps = {
  color: Vec3
  translate: Vec3
  scale: number
}

type MeshContext = {
  elements: Vec3[]
  normal: Vec3[]
  position: Vec3[]
}

class Mesh{
  elements: Vec3[]
  position: Vec3[]
  normal: Vec3[]

  constructor(elements: Vec3[], position: Vec3[], normal: Vec3[]) {
    this.elements = elements
    this.position = position
    this.normal = normal
  }

  draw = regl<{}, {}, MeshProps, MeshContext>({
    uniforms: {
      model: (_: DefaultContext, props:MeshProps, batchId: number) => {
        var m = mat4.identity(mat4.create())

        mat4.translate(m, m, props.translate)

        var s = props.scale
        mat4.scale(m, m, [s, s, s])
        return m
      },
      ambientLightAmount: 0.3,
      diffuseLightAmount: 0.7,
      color: regl.prop<MeshProps, 'color'>('color')
    },
    attributes: {
      position: regl.this<MeshContext,'position'> ('position'),
      normal: regl.this<MeshContext,'normal'> ('normal')
    },
    elements: regl.this<MeshContext,'elements'> ('elements'),
    cull: {
      enable: true
    }
  })
} 

var bunnyMesh = new Mesh(bunny.cells, bunny.positions, normals(bunny.cells, bunny.positions))
var boxMesh = new Mesh(boxElements, boxPosition, boxNormal)
var planeMesh = new Mesh(planeElements, planePosition, planeNormal)

regl.frame(({tick}) => {
  var drawMeshes = () => {
    regl.clear({
      color: [0, 0, 0, 255],
      depth: 1
    })
    var t = tick * 0.02
    var r = 8.0
    var bp1: Vec3 = [r * Math.sin(t), 3.3, r * Math.cos(t)]

    t = (tick - 100) * 0.015
    r = 5.0
    var bp2: Vec3 = [r * Math.sin(t), 12.3, r * Math.cos(t)]

    boxMesh.draw({scale: 4.2, translate: [0.0, 9.0, 0], color: [0.05, 0.5, 0.5]})
    planeMesh.draw({scale: 80.0, translate: [0.0, 0.0, 0.0], color: [1.0, 1.0, 1.0]})
    bunnyMesh.draw({scale: 0.7, translate: bp1, color: [0.55, 0.2, 0.05]})
    bunnyMesh.draw({scale: 0.8, translate: bp2, color: [0.55, 0.55, 0.05]})
  }

  globalScope(() => {
    drawDepth(drawMeshes)
    drawNormal(drawMeshes)
  })

  camera.tick()
})
davschne commented 5 years ago

Looks alright to me! My only suggestion that you'll get better type checking on your DrawConfig objects if you use the first and second type parameters to specify the types of the uniforms and attributes, for instance:

type MeshUniforms = {
  model: Mat4,
  ambientLightAmount: number,
  diffuseLightAmount: number,
  color: Vec3,
};

type MeshAttributes = {
  position: Vec3,
  normal: Vec3,
};

// ...

draw = regl<MeshUniforms, MeshAttributes, MeshProps, MeshContext>({
  // ...
})