Repository: pixijs/pixi-lights
Branch: main
Commit: 1e6a3e8263af
Files: 31
Total size: 35.9 KB
Directory structure:
gitextract_x_4fq0zy/
├── .editorconfig
├── .github/
│ ├── CONTRIBUTING.md
│ └── workflows/
│ └── build.yml
├── .gitignore
├── .npmrc
├── LICENSE
├── README.md
├── examples/
│ ├── index.html
│ └── usage.html
├── package.json
├── src/
│ ├── LayerFinder.ts
│ ├── index.ts
│ ├── lights/
│ │ ├── ambientLight/
│ │ │ ├── AmbientLight.ts
│ │ │ ├── AmbientLightShader.ts
│ │ │ ├── ambient.frag.ts
│ │ │ └── index.ts
│ │ ├── directionalLight/
│ │ │ ├── DirectionalLight.ts
│ │ │ ├── DirectionalLightShader.ts
│ │ │ ├── directional.frag.ts
│ │ │ └── index.ts
│ │ ├── light/
│ │ │ ├── Light.ts
│ │ │ ├── LightShader.ts
│ │ │ ├── ViewportQuad.ts
│ │ │ └── index.ts
│ │ ├── pointLight/
│ │ │ ├── PointLight.ts
│ │ │ ├── PointLightShader.ts
│ │ │ ├── index.ts
│ │ │ └── point.frag.ts
│ │ └── shared.ts
│ └── mixins/
│ └── Circle.ts
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
# This file is for unifying the coding style for different editors and IDEs.
# More information at http://EditorConfig.org
root = true
# General Options
[*]
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
# Markdown
[*.md]
trim_trailing_whitespace = false
# Special Files
[{package.json,.travis.yml}]
indent_size = 2
================================================
FILE: .github/CONTRIBUTING.md
================================================
# How to contribute
Please read this short guide to contributing before performing pull requests or reporting issues. The purpose
of this guide is to ensure the best experience for all involved and make development as smooth as possible.
## Reporting issues
To report a bug, request a feature, or even ask a question, make use of the [GitHub Issues][10] in this repo.
When submitting an issue please take the following steps:
**1. Search for existing issues.** Your bug may have already been fixed or addressed in an unreleased version, so
be sure to search the issues first before putting in a duplicate issue.
**2. Create an isolated and reproducible test case.** If you are reporting a bug, make sure you also have a minimal,
runnable, code example that reproduces the problem you have.
**3. Include a live example.** After narrowing your code down to only the problem areas, make use of [jsFiddle][11],
[jsBin][12], or a link to your live site so that we can view a live example of the problem.
**4. Share as much information as possible.** Include browser/node version affected, your OS, version of the library,
steps to reproduce, etc. "X isn't working!!!1!" will probably just be closed.
[10]: https://github.com/pixijs/lights/issues
[11]: http://jsfiddle.net
[12]: http://jsbin.com/
## Making Changes
To build the library you will need to download node.js from [nodejs.org][20]. After it has been installed open a
console and run `npm install -g gulp` to install the global `gulp` executable.
After that you can clone the repository and run `npm install` inside the cloned folder. This will install
dependencies necessary for building the project. You can rebuild the project by running `gulp` in the cloned
folder.
Once that is ready, you can make your changes and submit a Pull Request:
- **Send Pull Requests to the `master` branch.** All Pull Requests must be sent to the `master` branch, which is where
all "bleeding-edge" development takes place.
- **Ensure changes are jshint validated.** Our JSHint configuration file is provided in the repository and you
should check against it before submitting. This should happen automatically when running `gulp` in the repo directory.
- **Never commit new builds.** When making a code change you should always run `gulp` which will rebuild the project
so you can test, *however* please do not commit the new builds placed in `dist/` or your PR will be closed. By default
the build process will output to an ignored folder (`build/`) you should be fine.
- **Only commit relevant changes.** Don't include changes that are not directly relevant to the fix you are making.
The more focused a PR is, the faster it will get attention and be merged. Extra files changing only whitespace or
trash files will likely get your PR closed.
[20]: http://nodejs.org
## Quickie Code Style Guide
Use EditorConfig and JSHint! Both tools will ensure your code is in the required styles! Either way, here are some tips:
- Use 4 spaces for tabs, never tab characters.
- No trailing whitespace, blank lines should have no whitespace.
- Always favor strict equals `===` unless you *need* to use type coercion.
- Follow conventions already in the code, and listen to jshint. Our config is set-up for a reason.
================================================
FILE: .github/workflows/build.yml
================================================
name: Build CI
on:
push:
branches: [ '**' ]
tags: [ '**' ]
release:
types: [ 'created' ]
pull_request:
branches: [ '**' ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Use Node.js 16.x
uses: actions/setup-node@v3
with:
node-version: 16
- name: Install npm
run: npm install -g npm@8
- name: Install dependencies
run: npm ci
- name: Test
run: npm test
================================================
FILE: .gitignore
================================================
# sublime text files
*.sublime*
*.*~*.TMP
# temp files
.DS_Store
Thumbs.db
Desktop.ini
npm-debug.log
# project files
.project
# vim swap files
*.sw*
# emacs temp files
*~
\#*#
# project ignores
!.gitkeep
*__temp
node_modules
docs/
# jetBrains IDE ignores
.idea
# tsc output
compile
dist
lib
index.d.ts
example.api.json
example.api.json.md5
================================================
FILE: .npmrc
================================================
engine-strict = true
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2015 Ivan Popelyshev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# PixiJS Lights
[](https://github.com/pixijs-userland/lights/actions/workflows/build.yml) [](https://badge.fury.io/js/@pixi%2Flights)
A plugin that adds deferred lighting to PixiJS.
**Note**: This modules *requires* v7.0.0+ of [PixiJS](https://github.com/pixijs/pixijs) and v2.0.1+ of [@pixi/layers](https://github.com/pixijs-userland/layers).
* [Demo](https://userland.pixijs.io/lights/examples/)
## Usage
You have to create three layers: one for sprites, one for their normals and one for lights. Sprites and normals are rendered to temporary RenderTexture, and lights have those two textures as an input.
```js
// Get class references
import {Application, Sprite, Container, lights} from 'pixi.js';
import {Layer, Stage} from '@pixi/layers';
import {diffuseGroup, normalGroup, lightGroup, PointLight} from '@pixi/lights';
// Create new application
const app = new Application({
backgroundColor: 0x000000, // Black is required!
width: 800,
height: 600
});
document.body.appendChild(app.view);
// Use the pixi-layers Stage instead of default Container
app.stage = new Stage();
// Add the background diffuse color
const diffuse = Sprite.fromImage('images/BGTextureTest.jpg');
diffuse.parentGroup = diffuseGroup;
// Add the background normal map
const normals = Sprite.fromImage('images/BGTextureNORM.jpg');
normals.parentGroup = normalGroup;
// Create the point light
const light = new PointLight(0xffffff, 1);
light.x = app.screen.width / 2;
light.y = app.screen.height / 2;
// Create a background container
const background = new Container();
background.addChild(
normals,
diffuse,
light
);
app.stage.addChild(
// put all layers for deferred rendering of normals
new Layer(diffuseGroup),
new Layer(normalGroup),
new Layer(lightGroup),
// Add the lights and images
background
);
```
* [Run This](https://userland.pixijs.io/lights/examples/usage.html)
### Filters
If you want to use light shaders inside a filter, make sure its full-screen:
```js
app.stage.filters = [new BlurFilter()];
app.stage.filterArea = app.screen;
```
## Vanilla JS
Navigate `pixi-lights` npm package, take `dist/pixi-lights.js` file.
```html
```
all classes can be accessed through `PIXI.lights` global namespace.
## Building
You normally don't need to build this module, you can just download a release from the releases page.
Then you can install dependencies and build:
```js
npm i && npm run build
```
That will output the built distributables to `./lib` and `./dist`.
## Roadmap
1. More lighting types, left are:
- Spot lights
- Hemisphere lights
- Area lights
2. Add dynamic shadows
3. Write tests!
================================================
FILE: examples/index.html
================================================
PixiJS Lights - Demo
================================================
FILE: examples/usage.html
================================================
PixiJS Lights - Usage Example
================================================
FILE: package.json
================================================
{
"name": "@pixi/lights",
"version": "4.1.0",
"description": "A plugin that adds deferred lighting to PixiJS v6",
"author": "Ivan Popelyshev",
"contributors": [
"Ivan Popelyshev ",
"Matt Karl "
],
"extensionConfig": {
"namespace": "PIXI.lights",
"lint": [
"src"
],
"globals": {
"@pixi/layers": "PIXI.layers"
},
"docsName": "PixiJS Lights",
"docsCopyright": "Copyright © 2015 - 2022 Ivan Popelyshev",
"docsTitle": "PixiJS Lights API Documentation",
"docsDescription": "Documentation for PixiJS Lights library",
"docsKeyword": "docs, documentation, pixi, pixijs, rendering, pixi-lights, display, pixi-display, javascript"
},
"main": "./lib/index.js",
"module": "./lib/index.mjs",
"types": "./lib/index.d.ts",
"exports": {
".": {
"import": "./lib/index.mjs",
"require": "./lib/index.js",
"types": "./lib/index.d.ts"
}
},
"homepage": "http://www.pixijs.com/",
"bugs": "https://github.com/pixijs/lights/issues",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/pixijs/lights.git"
},
"scripts": {
"clean": "xs clean",
"start": "xs serve",
"watch": "xs watch",
"build": "xs build",
"lint": "xs lint",
"lint:fix": "xs lint --fix",
"types": "xs types",
"release": "xs release",
"docs": "xs docs",
"deploy": "xs deploy",
"test": "xs build,docs"
},
"publishConfig": {
"access": "public"
},
"engines": {
"node": ">=16",
"npm": ">=8"
},
"files": [
"dist/",
"lib/"
],
"peerDependencies": {
"@pixi/core": "^7.0.0",
"@pixi/display": "^7.0.0",
"@pixi/layers": "^2.0.1",
"@pixi/mesh": "^7.0.0"
},
"devDependencies": {
"@pixi/core": "^7.0.0",
"@pixi/display": "^7.0.0",
"@pixi/extension-scripts": "^1.8.1",
"@pixi/layers": "^2.0.1",
"@pixi/mesh": "^7.0.0"
}
}
================================================
FILE: src/LayerFinder.ts
================================================
import { Texture } from '@pixi/core';
import { Group, Layer } from '@pixi/layers';
/**
* @memberof PIXI.lights
* @static
* @type {PIXI.layers.Group}
*/
export const diffuseGroup = new Group(0, false);
/**
* @memberof PIXI.lights
* @static
* @type {PIXI.layers.Group}
*/
export const normalGroup = new Group(0, false);
/**
* @memberof PIXI.lights
* @static
* @type {PIXI.layers.Group}
*/
export const lightGroup = new Group(0, false);
diffuseGroup.useRenderTexture = true;
normalGroup.useRenderTexture = true;
/**
* @memberof PIXI.lights
*/
export class LayerFinder
{
/**
* Last layer
* @type {PIXI.layers.Layer}
*/
lastLayer: Layer | null = null;
/**
* Diffuse texture
* @type {PIXI.Texture}
*/
diffuseTexture: Texture | null = null;
/**
* Normal texture
* @type {PIXI.Texture}
*/
normalTexture: Texture | null = null;
/**
* Check
* @param {PIXI.layers.Layer} layer -
*/
check(layer: Layer): void
{
if (this.lastLayer === layer)
{
return;
}
this.lastLayer = layer;
const stage = layer._activeStageParent;
const layerAny = layer as any;
this.diffuseTexture = Texture.WHITE;
this.normalTexture = Texture.WHITE;
if (layerAny.diffuseTexture && layerAny.normalTexture)
{
this.diffuseTexture = layerAny.diffuseTexture;
this.normalTexture = layerAny.normalTexture;
}
else
{
for (let j = 0; j < stage._activeLayers.length; j++)
{
const texLayer = stage._activeLayers[j];
if (texLayer.group === normalGroup)
{
this.normalTexture = texLayer.getRenderTexture();
}
if (texLayer.group === diffuseGroup)
{
this.diffuseTexture = texLayer.getRenderTexture();
}
}
}
}
static _instance = new LayerFinder();
}
================================================
FILE: src/index.ts
================================================
import './mixins/Circle';
export * from './lights/light';
export * from './lights/ambientLight';
export * from './lights/pointLight';
export * from './lights/directionalLight';
export * from './LayerFinder';
================================================
FILE: src/lights/ambientLight/AmbientLight.ts
================================================
import { Light } from '../light/Light';
import { AmbientLightShader } from './AmbientLightShader';
/**
* Ambient light is drawn using a full-screen quad.
* @memberof PIXI.lights
*/
export class AmbientLight extends Light
{
/**
* @param {number} [color=0xFFFFFF] - The color of the light.
* @param {number} [brightness=0.5] - The brightness of the light.
*/
constructor(color = 0xFFFFFF, brightness = 0.5)
{
super(color, brightness, new AmbientLightShader());
}
}
================================================
FILE: src/lights/ambientLight/AmbientLightShader.ts
================================================
import { Program } from '@pixi/core';
import { LightShader } from '../light/LightShader';
import { ambientFrag } from './ambient.frag';
/**
* @memberof PIXI.lights
*/
export class AmbientLightShader extends LightShader
{
constructor()
{
super({
program: AmbientLightShader._program
});
}
static _program = new Program(LightShader.defaultVertexSrc, ambientFrag);
}
================================================
FILE: src/lights/ambientLight/ambient.frag.ts
================================================
import { commonUniforms, computeVertexPosition, loadNormals } from '../shared';
export const ambientFrag = `precision highp float;
${commonUniforms}
void main(void)
{
${computeVertexPosition}
${loadNormals}
// simplified lambert shading that makes assumptions for ambient color
vec3 diffuse = uColor.rgb * uBrightness;
vec4 diffuseColor = texture2D(uSampler, texCoord);
vec3 finalColor = diffuseColor.rgb * diffuse;
gl_FragColor = vec4(finalColor, diffuseColor.a);
}
`;
================================================
FILE: src/lights/ambientLight/index.ts
================================================
export * from './AmbientLight';
export * from './AmbientLightShader';
================================================
FILE: src/lights/directionalLight/DirectionalLight.ts
================================================
import { Light } from '../light/Light';
import { Point, Renderer } from '@pixi/core';
import { DisplayObject } from '@pixi/display';
import { DirectionalLightShader } from './DirectionalLightShader';
/**
* Directional light is drawn using a full-screen quad.
* @memberof PIXI.lights
*/
export class DirectionalLight extends Light
{
target: DisplayObject | Point;
/**
* @param {number} [color=0xFFFFFF] - The color of the light.
* @param {number} [brightness=1] - The intensity of the light.
* @param {PIXI.DisplayObject|PIXI.Point} [target] - The object in the scene to target.
*/
constructor(color = 0xFFFFFF, brightness = 1, target: DisplayObject | Point)
{
super(color, brightness, new DirectionalLightShader());
this.target = target;
}
/**
* Sync shader
* @param {PIXI.Renderer} renderer - Renderer
*/
override syncShader(renderer: Renderer): void
{
super.syncShader(renderer);
const shader = this.material;
const vec = shader.uniforms.uLightDirection;
const wt = this.worldTransform;
const twt = (this.target as any).worldTransform;
let tx: number;
let ty: number;
if (twt)
{
tx = twt.tx;
ty = twt.ty;
}
else
{
tx = this.target.x;
ty = this.target.y;
}
// calculate direction from this light to the target
vec.x = wt.tx - tx;
vec.y = wt.ty - ty;
// normalize
const len = Math.sqrt((vec.x * vec.x) + (vec.y * vec.y));
vec.x /= len;
vec.y /= len;
}
}
================================================
FILE: src/lights/directionalLight/DirectionalLightShader.ts
================================================
import { Program, Point } from '@pixi/core';
import { LightShader } from '../light/LightShader';
import { directionalFrag } from './directional.frag';
/**
* @memberof PIXI.lights
*/
export class DirectionalLightShader extends LightShader
{
constructor()
{
super({
program: DirectionalLightShader._program,
uniforms: {
uLightRadius: 1.0,
uLightDirection: new Point()
}
});
}
static _program = new Program(LightShader.defaultVertexSrc, directionalFrag);
}
================================================
FILE: src/lights/directionalLight/directional.frag.ts
================================================
import { combine, commonUniforms, computeDiffuse, computeVertexPosition, loadNormals } from '../shared';
export const directionalFrag = `precision highp float;
// imports the common uniforms like samplers, and ambient/light color
${commonUniforms}
uniform vec2 uLightDirection;
void main()
{
${computeVertexPosition}
${loadNormals}
// the directional vector of the light
vec3 lightVector = vec3(uLightDirection, uLightHeight);
// compute Distance
float D = length(lightVector);
${computeDiffuse}
// calculate attenuation
float attenuation = 1.0;
${combine}
}
`;
================================================
FILE: src/lights/directionalLight/index.ts
================================================
export * from './DirectionalLight';
export * from './DirectionalLightShader';
================================================
FILE: src/lights/light/Light.ts
================================================
import { Geometry, Renderer, BLEND_MODES, DRAW_MODES } from '@pixi/core';
import { Layer } from '@pixi/layers';
import { Mesh } from '@pixi/mesh';
import { LayerFinder, lightGroup } from '../../LayerFinder';
import { LightShader } from './LightShader';
import { ViewportQuad } from './ViewportQuad';
/**
* Base light class.
* @extends PIXI.Mesh
* @memberof PIXI.lights
*/
export class Light extends Mesh
{
/** Light height */
lightHeight: number;
/** Brightness */
brightness: number;
/** Shader name */
shaderName: string | null = null;
/** Use Viewport Quad */
readonly useViewportQuad: boolean;
/**
* @param {number} [color=0xFFFFFF] - The color of the light.
* @param {number} [brightness=1] - The brightness of the light, in range [0, 1].
* @param {PIXI.lights.LightShader} [material] -
* @param {Float32Array} [vertices] -
* @param {Uint16Array} [indices] -
*/
constructor(color = 0x4d4d59, brightness = 0.8, material: LightShader,
vertices? : Float32Array, indices?: Uint16Array)
{
super(!vertices ? ViewportQuad._instance : new Geometry()
.addAttribute('aVertexPosition', vertices).addIndex(indices), material);
this.blendMode = BLEND_MODES.ADD;
const useViewportQuad = !vertices;
this.drawMode = useViewportQuad ? DRAW_MODES.TRIANGLE_STRIP : DRAW_MODES.TRIANGLES;
/**
* The height of the light from the viewport.
*
* @default 0.075
*/
this.lightHeight = 0.075;
/**
* The falloff attenuation coeficients.
*
* @member {number[]}
* @default [0.75, 3, 20]
*/
this.falloff = [0.75, 3, 20];
/**
* By default the light uses a viewport sized quad as the mesh.
*
* @member {boolean}
*/
this.useViewportQuad = useViewportQuad;
// color and brightness are exposed through setters
this.tint = color ?? 0x4d4d59;
this.brightness = brightness;
this.parentGroup = lightGroup;
}
/**
* The color of the lighting.
*/
get color(): number
{
return this.tint;
}
set color(val: number)
{
this.tint = val;
}
/**
* Falloff
* @member {number[]}
*/
get falloff(): ArrayLike
{
return this.material.uniforms.uLightFalloff;
}
set falloff(value: ArrayLike)
{
this.material.uniforms.uLightFalloff[0] = value[0];
this.material.uniforms.uLightFalloff[1] = value[1];
this.material.uniforms.uLightFalloff[2] = value[2];
}
/**
* Last layer
* @type {PIXI.layers.Layer}
*/
lastLayer: Layer | null = null;
/**
* Sync Shader
* @param {PIXI.Renderer} renderer - Renderer
*/
syncShader(renderer: Renderer): void
{
const { uniforms } = this.shader;
// TODO: actually pass UV's of screen instead of size
uniforms.uViewSize[0] = renderer.screen.width;
uniforms.uViewSize[1] = renderer.screen.height;
uniforms.uViewPixels[0] = renderer.view.width;
uniforms.uViewPixels[1] = renderer.view.height;
uniforms.uFlipY = !renderer.framebuffer.current;
uniforms.uSampler = LayerFinder._instance.diffuseTexture;
uniforms.uNormalSampler = LayerFinder._instance.normalTexture;
uniforms.uUseViewportQuad = this.useViewportQuad;
uniforms.uBrightness = this.brightness;
}
_renderDefault(renderer: Renderer): void
{
if (!this._activeParentLayer)
{
return;
}
LayerFinder._instance.check(this._activeParentLayer);
const shader = this.shader as unknown as LightShader;
shader.alpha = this.worldAlpha;
if (shader.update)
{
shader.update();
}
renderer.batch.flush();
shader.uniforms.translationMatrix = this.transform.worldTransform.toArray(true);
if (this.useViewportQuad)
{
// TODO: pass the viewport (translated screen) instead
(this.geometry as ViewportQuad).update(renderer.screen);
}
this.syncShader(renderer);
renderer.shader.bind(shader);
renderer.state.set(this.state);
renderer.geometry.bind(this.geometry, shader);
renderer.geometry.draw(this.drawMode, this.size, this.start, this.geometry.instanceCount);
}
}
================================================
FILE: src/lights/light/LightShader.ts
================================================
import { Texture, utils, Matrix } from '@pixi/core';
import { IMeshMaterialOptions, MeshMaterial } from '@pixi/mesh';
import { vert } from '../shared';
/**
* @extends PIXI.MeshMaterial
* @memberof PIXI.lights
*/
export class LightShader extends MeshMaterial
{
/**
* @param {PIXI.lights.IMeshMaterialOptions} [options] - Options to use.
*/
constructor(options?: IMeshMaterialOptions)
{
const uniforms: utils.Dict = {
translationMatrix: Matrix.IDENTITY.toArray(true),
// textures from the previously rendered FBOs
uNormalSampler: Texture.WHITE,
// size of the renderer viewport, CSS
uViewSize: new Float32Array(2),
// same, in PIXELS
uViewPixels: new Float32Array(2),
// light falloff attenuation coefficients
uLightFalloff: new Float32Array([0, 0, 0]),
// height of the light above the viewport
uLightHeight: 0.075,
uBrightness: 1.0,
uUseViewportQuad: true,
};
Object.assign(uniforms, options?.uniforms);
super(Texture.WHITE, { ...options, uniforms });
}
static defaultVertexSrc: string = vert;
}
================================================
FILE: src/lights/light/ViewportQuad.ts
================================================
import { Rectangle, Quad } from '@pixi/core';
/**
* @extends PIXI.Quad
* @memberof PIXI.lights
*/
export class ViewportQuad extends Quad
{
/**
* Update
* @param {PIXI.Rectangle} viewport -
*/
update(viewport: Rectangle): void
{
const b = this.buffers[0].data as Float32Array;
const x1 = viewport.x;
const y1 = viewport.y;
const x2 = viewport.x + viewport.width;
const y2 = viewport.y + viewport.height;
if (b[0] !== x1 || b[1] !== y1
|| b[4] !== x2 || b[5] !== y2)
{
b[0] = b[6] = x1;
b[1] = b[3] = y1;
b[2] = b[4] = x2;
b[5] = b[7] = y2;
this.buffers[0].update();
}
}
static _instance: ViewportQuad = new ViewportQuad();
}
================================================
FILE: src/lights/light/index.ts
================================================
export * from './Light';
export * from './LightShader';
export * from './ViewportQuad';
================================================
FILE: src/lights/pointLight/PointLight.ts
================================================
import { Light } from '../light/Light';
import { Circle, DRAW_MODES } from '@pixi/core';
import { getCircleMesh } from '../../mixins/Circle';
import { PointLightShader } from './PointLightShader';
/**
* @memberof PIXI.lights
*/
export class PointLight extends Light
{
/**
* @param {number} [color=0xFFFFFF] - The color of the light.
* @param {number} [brightness=1] - The intensity of the light.
* @param {number} [radius=Infinity] - The distance the light reaches. You will likely need
* to change the falloff of the light as well if you change this value. Infinity will
* use the entire viewport as the drawing surface.
*/
constructor(color = 0xFFFFFF, brightness = 1, radius = Infinity)
{
if (radius !== Infinity)
{
const shape = new Circle(0, 0, radius);
const { vertices, indices } = getCircleMesh(shape);
super(color, brightness, new PointLightShader(), vertices, indices);
this.drawMode = DRAW_MODES.TRIANGLE_FAN;
}
else
{
super(color, brightness, new PointLightShader());
}
this.shaderName = 'pointLightShader';
this.radius = radius;
}
/** Radius */
get radius(): number
{
return this.material.uniforms.uLightRadius;
}
set radius(value: number)
{
this.material.uniforms.uLightRadius = value;
}
}
================================================
FILE: src/lights/pointLight/PointLightShader.ts
================================================
import { Program } from '@pixi/core';
import { LightShader } from '../light/LightShader';
import { pointFrag } from './point.frag';
/**
* @memberof PIXI.lights
*/
export class PointLightShader extends LightShader
{
constructor()
{
super({
program: PointLightShader._program,
uniforms: {
uLightRadius: 1.0
}
});
}
static _program = new Program(LightShader.defaultVertexSrc, pointFrag);
}
================================================
FILE: src/lights/pointLight/index.ts
================================================
export * from './PointLight';
export * from './PointLightShader';
================================================
FILE: src/lights/pointLight/point.frag.ts
================================================
import { combine, commonUniforms, computeDiffuse, computeVertexPosition, loadNormals } from '../shared';
export const pointFrag = `precision highp float;
// imports the common uniforms like samplers, and ambient color
${commonUniforms}
uniform float uLightRadius;
void main()
{
${computeVertexPosition}
${loadNormals}
vec2 lightPosition = translationMatrix[2].xy / uViewSize;
// the directional vector of the light
vec3 lightVector = vec3(lightPosition - texCoord, uLightHeight);
// correct for aspect ratio
lightVector.x *= uViewSize.x / uViewSize.y;
// compute Distance
float D = length(lightVector);
// bail out early when pixel outside of light sphere
if (D > uLightRadius) discard;
${computeDiffuse}
// calculate attenuation
float attenuation = 1.0 / (uLightFalloff.x + (uLightFalloff.y * D) + (uLightFalloff.z * D * D));
${combine}
}
`;
================================================
FILE: src/lights/shared.ts
================================================
/* eslint-disable @typescript-eslint/no-inferrable-types */
export const combine: string = `vec3 intensity = diffuse * attenuation;
vec4 diffuseColor = texture2D(uSampler, texCoord);
vec3 finalColor = diffuseColor.rgb * intensity;
gl_FragColor = vec4(finalColor, diffuseColor.a);
`;
export const commonUniforms: string = `uniform sampler2D uSampler;
uniform sampler2D uNormalSampler;
uniform mat3 translationMatrix;
uniform vec2 uViewPixels; // size of the viewport, in pixels
uniform vec2 uViewSize; // size of the viewport, in CSS
uniform vec4 uColor; // light color, alpha channel used for intensity.
uniform float uBrightness;
uniform vec3 uLightFalloff; // light attenuation coefficients (constant, linear, quadratic)
uniform float uLightHeight; // light height above the viewport
uniform float uFlipY; // whether we use renderTexture, FBO is flipped
`;
export const computeDiffuse: string = `// normalize vectors
vec3 N = normalize(normalColor.xyz * 2.0 - 1.0);
vec3 L = normalize(lightVector);
// pre-multiply light color with intensity
// then perform "N dot L" to determine our diffuse
vec3 diffuse = uColor.rgb * uBrightness * max(dot(N, L), 0.0);
`;
export const computeVertexPosition: string = `vec2 texCoord = gl_FragCoord.xy / uViewPixels;
texCoord.y = (1.0 - texCoord.y) * uFlipY + texCoord.y * (1.0 - uFlipY); // FBOs positions are flipped.
`;
export const loadNormals: string = `vec4 normalColor = texture2D(uNormalSampler, texCoord);
normalColor.g = 1.0 - normalColor.g; // Green layer is flipped Y coords.
// bail out early when normal has no data
if (normalColor.a == 0.0) discard;
`;
export const vert: string = `attribute vec2 aVertexPosition;
uniform bool uUseViewportQuad;
uniform mat3 translationMatrix;
uniform mat3 projectionMatrix;
void main(void) {
if (uUseViewportQuad) {
gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
}
else
{
gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
}
}
`;
================================================
FILE: src/mixins/Circle.ts
================================================
import { Circle } from '@pixi/core';
/**
* PixiJS namespace.
* @namespace PIXI
*/
/**
* PixiJS Lights namespace.
* @namespace PIXI.lights
*/
/**
* Circle class from PixiJS.
* @class PIXI.Circle
*/
/**
* Creates vertices and indices arrays to describe this circle.
* @method PIXI.Circle#getMesh
* @param {PIXI.Circle} shape -
* @param {number} [totalSegments=40] - Total segments to build for the circle mesh.
* @param vertices -
* @param indices -
* `((totalSegments + 2) * 2)` or more. If not passed it is created for you.
* be `(totalSegments + 3)` or more. If not passed it is created for you.
* @return {PIXI.lights.CircleMeshData} Object with verticies and indices arrays
*/
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
export function getCircleMesh(shape: Circle, totalSegments = 40, vertices?: Float32Array, indices?: Uint16Array)
{
vertices = vertices || new Float32Array((totalSegments + 1) * 2);
indices = indices || new Uint16Array(totalSegments + 1);
const seg = (Math.PI * 2) / totalSegments;
let indicesIndex = -1;
indices[++indicesIndex] = indicesIndex;
for (let i = 0; i <= totalSegments; ++i)
{
const index = i * 2;
const angle = seg * i;
vertices[index] = Math.cos(angle) * shape.radius;
vertices[index + 1] = Math.sin(angle) * shape.radius;
indices[++indicesIndex] = indicesIndex;
}
indices[indicesIndex] = 1;
return { vertices, indices };
}
/**
* @memberof PIXI.lights
* @property {Float32Array} vertices - Vertices data
* @property {Uint16Array} indices - Indices data
*/
export interface CircleMeshData
{
vertices: Float32Array;
indices: Uint16Array;
}
================================================
FILE: tsconfig.json
================================================
{
"extends": "@pixi/extension-scripts/lib/configs/tsconfig.json",
"include": [
"src/**/*"
]
}