Skip to content

Commit

Permalink
Area lights refactored to use light source shape property (#2)
Browse files Browse the repository at this point in the history
1) Light type replaced with light source shape property.
2) Disk and Sphere light source shapes added.**
3) Light source shapes made to work with existing punctual light types and features i.e shadows, culling, cookies, falloff range etc.***
4) All light source shapes made to work with all light types.****
5) "point" light renamed to "omni" light, and "point" aliased to "omni" on light creation for backwards compatibility.

** Sphere light source shape diffuse lighting can be further improved - probably relatively cheaply with wrap lighting.
*** Punctual dynamic shadows are only an approximation - area light shadows should be implemented for higher quality.
**** Directional area light specular can be further improved - currently approximated with a shape on the far clip plane.
  • Loading branch information
raytranuk committed Dec 18, 2020
1 parent 60d7d5d commit 36dc089
Show file tree
Hide file tree
Showing 15 changed files with 720 additions and 232 deletions.
2 changes: 1 addition & 1 deletion examples/assets/scripts/lights/area-light-lut.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

136 changes: 84 additions & 52 deletions examples/graphics/area-lights.html
Original file line number Diff line number Diff line change
Expand Up @@ -67,54 +67,70 @@
}

// helper function to create area light including its visual representation in the world
function createAreaLight(position, scale, color) {
function createAreaLight(type, shape, position, scale, color, intensity, shadows, range) {
var lightParent = new pc.Entity();
lightParent.translate(position);
app.root.addChild(lightParent);

var light = new pc.Entity();
light.addComponent("light", {
type: "area",
//type: "spot",
type: type,
shape: shape,
color: color,
intensity: 20,
range: 10,
castShadows: false,


innerConeAngle: 60,
outerConeAngle: 90
intensity: intensity,
falloffMode: pc.LIGHTFALLOFF_INVERSESQUARED,
range: range,
castShadows: shadows,
innerConeAngle: 80,
outerConeAngle: 85,
shadowBias: 0.1,
normalOffsetBias: 0.1,
shadowResolution: 2048
});

// front face quad
var frontMaterial = new pc.StandardMaterial();
frontMaterial.diffuse = new pc.Color(0, 0, 0);
frontMaterial.useLighting = false;
frontMaterial.update();

// add plane that represents the area light
light.addComponent("model", {
type: "plane",
material: frontMaterial

light.setLocalScale( scale, scale, scale);
light.setLocalEulerAngles( (type === "directional") ? 90 : 0, 0, 0);
lightParent.addChild(light);

// emissive material that is the light source color
var brightMaterial = new pc.StandardMaterial();
brightMaterial.emissive = color;
brightMaterial.useLighting = false;
brightMaterial.cull = (shape===pc.LIGHTSHAPE_RECT) ? pc.CULLFACE_NONE : pc.CULLFACE_BACK;
brightMaterial.update();

var brightShape = new pc.Entity();
// primitive shape that matches light source shape
brightShape.addComponent("model", {
type: (shape===pc.LIGHTSHAPE_SPHERE) ? "sphere" : (shape===pc.LIGHTSHAPE_DISK) ? "cone" : "plane",
material: brightMaterial,
castShadows: (type === "directional") ? false : true
});
brightShape.setLocalScale( ((type === "directional") ? scale*range : scale), (shape===pc.LIGHTSHAPE_DISK ) ? 0.001 : ((type === "directional") ? scale*range : scale), ((type === "directional") ? scale*range : scale));
brightShape.setLocalEulerAngles( (type === "directional") ? 90 : 0, 0, 0);
lightParent.addChild(brightShape);

light.setLocalScale(scale, scale, scale);
light.translate(position);
app.root.addChild(light);

// black back face quad
var backMaterial = new pc.StandardMaterial();
backMaterial.emissive = color;
backMaterial.useLighting = false;
backMaterial.update();

var backFace = new pc.Entity();
backFace.addComponent("model", {
type: "plane",
material: backMaterial
});
backFace.setLocalEulerAngles(-180, 0, 0);
backFace.setLocalPosition(0, 0.001, 0);
light.addChild(backFace);
// add black primitive shape if not omni-directional or global directional
if (type === "spot")
{
// black material
var blackMaterial = new pc.StandardMaterial();
blackMaterial.diffuse = new pc.Color(0, 0, 0);
blackMaterial.useLighting = false;
blackMaterial.cull = (shape===pc.LIGHTSHAPE_RECT) ? pc.CULLFACE_NONE : pc.CULLFACE_BACK;
blackMaterial.update();

var blackShape = new pc.Entity();
blackShape.addComponent("model", {
type: (shape===pc.LIGHTSHAPE_SPHERE) ? "sphere" : (shape===pc.LIGHTSHAPE_DISK) ? "cone" : "plane",
material: blackMaterial
});
blackShape.setLocalPosition(0, 0.01/scale, 0);
blackShape.setLocalEulerAngles(-180, 0, 0);
brightShape.addChild(blackShape);
}

return light;
return lightParent;
}

// A list of assets that need to be loaded
Expand Down Expand Up @@ -150,8 +166,11 @@
}
});
});

var camera;
var light1, light2, light3;
var far = 5000.0;

var light1, light2;
function run() {

app.start();
Expand Down Expand Up @@ -186,7 +205,6 @@
app.scene.setSkybox(cubemapAsset.resources);
});


// create ground plane
createPrimitive("plane", new pc.Vec3(0, 0, 0), new pc.Vec3(20, 20, 20), new pc.Color(0.3, 0.3, 0.3), assetManifest);

Expand All @@ -201,18 +219,20 @@


// Create the camera, which renders entities
var camera = new pc.Entity();
camera = new pc.Entity();
camera.addComponent("camera", {
clearColor: new pc.Color(0.2, 0.2, 0.2),
fov: 60
fov: 60,
farClip: 100000
});
app.root.addChild(camera);
camera.setLocalPosition(0, 6, 12);
camera.lookAt(pc.Vec3.ZERO);
camera.setLocalPosition(0, 2.5, 12);
camera.lookAt(0,0,0);

// Create an area light
light1 = createAreaLight(new pc.Vec3(-5, 3, 2), 4, new pc.Color(1, 1, 1));
light2 = createAreaLight(new pc.Vec3(5, 3, -2), 5, new pc.Color(1, 1, 0));
// Create lights with light source shape
light1 = createAreaLight("spot", pc.LIGHTSHAPE_RECT, new pc.Vec3(-3, 4, 0), 4, new pc.Color(1, 1, 1), 2, true, 10);
light2 = createAreaLight("omni", pc.LIGHTSHAPE_SPHERE, new pc.Vec3(5, 2, -2), 2, new pc.Color(1, 1, 0), 2, true, 10);
light3 = createAreaLight("directional", pc.LIGHTSHAPE_DISK, new pc.Vec3(0, 0, 0), 0.2, new pc.Color(0.7, 0.7, 1), 10, true, far);
}

// update things each frame
Expand All @@ -221,16 +241,28 @@
app.on("update", function (dt) {
time += dt;

var factor1 = (Math.sin(time) + 1) * 0.5;
var factor2 = (Math.sin(time * 0.6) + 1) * 0.5;
var factor3 = (Math.sin(time * 0.4) + 1) * 0.5;

if (light1) {
var factor1 = (Math.sin(time) + 1) * 0.5;
light1.setLocalEulerAngles(pc.math.lerp(-90, 110, factor1), 0, 90);
light1.setLocalPosition(-4, pc.math.lerp(2, 4, factor3), pc.math.lerp(-2, 2, factor2));
}

if (light2) {
var factor2 = (Math.sin(time * 0.6) + 1) * 0.5;
light2.setLocalEulerAngles(pc.math.lerp(50, 250, factor2), 0, 90);
light2.setLocalPosition(5, pc.math.lerp(1, 3, factor1), pc.math.lerp(-2, 2, factor2));
}

if (light3) {

light3.setLocalEulerAngles(pc.math.lerp(140, 220, factor2), pc.math.lerp(-30, 0, factor3), 90);

var dir = light3.getWorldTransform().getZ();
var campos = camera.getPosition();

light3.setPosition(campos.x + dir.x * far, campos.y + dir.y * far, campos.z + dir.z * far);
}
});
</script>
</body>
Expand Down
16 changes: 13 additions & 3 deletions src/framework/components/light/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Vec4 } from '../../../math/vec4.js';
import {
BLUR_GAUSSIAN,
LAYERID_WORLD,
LIGHTSHAPE_PUNCTUAL,
LIGHTFALLOFF_LINEAR,
MASK_BAKED, MASK_DYNAMIC, MASK_LIGHTMAP,
SHADOW_PCF3,
Expand Down Expand Up @@ -46,13 +47,19 @@ import { Component } from '../component.js';
* entity.light.range = 20;
* @property {string} type The type of light. Can be:
* * "directional": A light that is infinitely far away and lights the entire scene from one direction.
* * "point": A light that illuminates in all directions from a point.
* * "spot": A light that illuminates in all directions from a point and is bounded by a cone.
* * "area": A light in the shape of a quad that illuminates in one direction.
* * "omni": A omni-directional light that illuminates in all directions from the light source.
* * "point": A omni-directional light but light source shape is {@link pc.LIGHTSHAPE_PUNCTUAL}.
* * "spot": A omni-directional light but is bounded by a cone.
* Defaults to "directional".
* @property {pc.Color} color The Color of the light. The alpha component of the color is
* ignored. Defaults to white (1, 1, 1).
* @property {number} intensity The brightness of the light. Defaults to 1.
* @property {number} shape The light source shape. Can be:
* * {@link pc.LIGHTSHAPE_PUNCTUAL}: Infinitesimally small point.
* * {@link pc.LIGHTSHAPE_RECT}: Rectangle shape.
* * {@link pc.LIGHTSHAPE_DISK}: Disk shape.
* * {@link pc.LIGHTSHAPE_SPHERE}: Sphere shape.
* Affects spot lights only. Defaults to pc.LIGHTSHAPE_PUNCTUAL.
* @property {boolean} castShadows If enabled the light will cast shadows. Defaults to false.
* @property {number} shadowDistance The distance from the viewpoint beyond which shadows
* are no longer rendered. Affects directional lights only. Defaults to 40.
Expand Down Expand Up @@ -160,6 +167,9 @@ var _defineProps = function () {
_defineProperty("intensity", 1, function (newValue, oldValue) {
this.light.intensity = newValue;
});
_defineProperty("shape", LIGHTSHAPE_PUNCTUAL, function (newValue, oldValue) {
this.light.shape = newValue;
});
_defineProperty("castShadows", false, function (newValue, oldValue) {
this.light.castShadows = newValue;
});
Expand Down
9 changes: 5 additions & 4 deletions src/framework/components/light/system.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Color } from '../../../core/color.js';

import { Vec2 } from '../../../math/vec2.js';

import { LIGHTTYPE_DIRECTIONAL, LIGHTTYPE_POINT, LIGHTTYPE_SPOT, LIGHTTYPE_AREA } from '../../../scene/constants.js';
import { LIGHTTYPE_DIRECTIONAL, LIGHTTYPE_OMNI, LIGHTTYPE_SPOT } from '../../../scene/constants.js';
import { Light } from '../../../scene/light.js';

import { ComponentSystem } from '../system.js';
Expand All @@ -20,9 +20,9 @@ import { LightComponentData } from './data.js';
*/
var lightTypes = {
'directional': LIGHTTYPE_DIRECTIONAL,
'point': LIGHTTYPE_POINT,
'spot': LIGHTTYPE_SPOT,
'area': LIGHTTYPE_AREA
'omni': LIGHTTYPE_OMNI,
'point': LIGHTTYPE_OMNI,
'spot': LIGHTTYPE_SPOT
};

function LightComponentSystem(app) {
Expand Down Expand Up @@ -74,6 +74,7 @@ Object.assign(LightComponentSystem.prototype, {

var light = new Light();
light.type = lightTypes[data.type];
light.shape = data.shape;
light._node = component.entity;
light._scene = this.app.scene;
component.data.light = light;
Expand Down
8 changes: 8 additions & 0 deletions src/graphics/program-lib/chunks/falloffInvSquared.frag
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
float getFalloffWindow(float lightRadius) {
float sqrDist = dot(dLightDirW, dLightDirW);
float invRadius = 1.0 / lightRadius;
return square( saturate( 1.0 - square( sqrDist * square(invRadius) ) ) );
}

float getFalloffInvSquared(float lightRadius) {
float sqrDist = dot(dLightDirW, dLightDirW);
float falloff = 1.0 / (sqrDist + 1.0);
Expand All @@ -8,3 +14,5 @@ float getFalloffInvSquared(float lightRadius) {

return falloff;
}


15 changes: 15 additions & 0 deletions src/graphics/program-lib/chunks/fresnelSchlick.frag
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
// Schlick's approximation
uniform float material_fresnelFactor; // unused

#ifdef HAS_AREA_LIGHTS
vec3 dSpecularityNoFres;
#ifdef CLEARCOAT
vec3 ccSpecularityNoFres;
#endif
#endif

void getFresnel() {

#ifdef HAS_AREA_LIGHTS
vec3 dSpecularityNoFres = dSpecularity;
#ifdef CLEARCOAT
vec3 ccSpecularityNoFres = ccSpecularity;
#endif
#endif

float fresnel = 1.0 - max(dot(dNormalW, dViewDirW), 0.0);
float fresnel2 = fresnel * fresnel;
fresnel *= fresnel2 * fresnel2;
Expand Down

0 comments on commit 36dc089

Please sign in to comment.