diff --git a/.eslintrc b/.eslintrc index e11624e77c..092822a157 100644 --- a/.eslintrc +++ b/.eslintrc @@ -15,6 +15,7 @@ "no-inline-comments": 0, "camelcase": 0, "tree-shaking/no-side-effects-in-initialization": 0, + "max-statements": 0, "luma-gl-custom-rules/check-log-call": 1 } } diff --git a/dev-docs/RFCs/README.md b/dev-docs/RFCs/README.md index b2903a7ac4..ee89216924 100644 --- a/dev-docs/RFCs/README.md +++ b/dev-docs/RFCs/README.md @@ -22,6 +22,8 @@ These are early ideas not yet associated with any release | RFC | Author | Status | Description | | --- | --- | --- | --- | | **WIP/Draft** | | | | +| [**Automatic Shader Module Injection**](vNext/glsl-function-replacement-rfc.md) | @ibgreen | **Draft** | Replace functions and inject attributes in shaders | +| [**Automatic Shader Module Injection**](vNext/dynamic-shader-regeneration-rfc.md) | @ibgreen | **Draft** | Automatically recompile shader modules when "props" are updated. | | [**Automatic Shader Module Injection**](vNext/automatic-shader-module-injection-rfc.md) | @ibgreen | **Draft** | Automatically inject code required by a shader module | | [**Dist Size Reduction**](vNext/reduce-distribution-size-rfc.md) | @ibgreen | **Draft** | Reduce luma.gl impact on app bundle size | diff --git a/dev-docs/RFCs/vNext/dynamic-shader-regeneration-rfc.md b/dev-docs/RFCs/vNext/dynamic-shader-regeneration-rfc.md new file mode 100644 index 0000000000..a8bc7ac22b --- /dev/null +++ b/dev-docs/RFCs/vNext/dynamic-shader-regeneration-rfc.md @@ -0,0 +1,17 @@ +# RFC: Dynamic Shader Regeneration + +Being able to specify props that affect the shader source code means that either: +* Changes to these props need to be detected, shaders recompiled and programs relinked +* Or we need to specify in documentation that shaders are only generated on creation + +Apart from the work/extra complexity required to accurately detect when shaders should be recompiled, the big issue is that recompilation and relinking of GLSL shaders tends to be slow (sometimes extremely slow, as in several seconds, or even half a minute or more) and happens synchronously on the main WebGL thread, freezing all rendering or even the entire app. + +If we support dynamic recompilation of shaders based on changing props, we therefore need to be extra careful to avoid (and detect/warn for) "spurious" recompiles. The user might not realize that the supplied props are triggering constant recompiles causing app performance to crater. + +For ideas around working around slow shader compilation, see: +* [Deferring linking till first draw](http://toji.github.io/shader-perf/) +* [KHR_parallel_shader_compile extension](https://www.khronos.org/registry/webgl/extensions/KHR_parallel_shader_compile/) +* [KHR_parallel_shader_compile discussion](https://github.com/KhronosGroup/WebGL/issues/2690) +* [Intel webGL 2018 presentation](https://docs.google.com/presentation/d/1qeD2xio2dgkqWGQucZs6nezQPePOM4NtV9J54J7si9c/htmlpresent) search for KHR_parallel_shader_compile + + diff --git a/dev-docs/RFCs/vNext/glsl-function-replacement-rfc.md b/dev-docs/RFCs/vNext/glsl-function-replacement-rfc.md new file mode 100644 index 0000000000..cfabcf53fc --- /dev/null +++ b/dev-docs/RFCs/vNext/glsl-function-replacement-rfc.md @@ -0,0 +1,145 @@ +# RFC: GLSL Function Replacement And Attribute Injection + +* Authors: Ib Green +* Date: March 2019 +* Status: **Draft** + +This RFC is part of the [shadertools roadmap](dev-docs/roadmaps/shadertools-roadmap.md). + + +## Summary + +This RFC proposes a system for shader module users to replace GLSL functions (by supplying a map of function names to strings fragments of GLSL code) as well as injecting new attributes and uniforms. + +This supports use case such as: +* Completely or partially skip CPU side attribute generation +* Work directly on supplied binary data (e.g. binary tables). +* Do additional GPU processing on data, allowing performant custom effects. + + +## Overview + +Let's start with a couple of API examples to illustrate what we want to achieve: + + +### API Example: Using Columnar Binary Data Sirectly + +Columnar binary data systems (like Apache arrow) will often store primitive values (e.g. a Float32) in separate columns, rather than as a `vec2` or `vec3`. This means that even if we exposed these columns as attributes in GLSL, we cannot use them directly as the expected `vec3` attribute for positions. However, if we could just define a snippet of GLSL code to override how the shader extracs a position from attributes (`getPosition`), we could still use these values rather than pre-processing them in JavaScript: + +``` +new Model({ + vs, + fs, + + // inject new attributes + glsl: { + attributes: { + x: 'float', + y: 'float' + }, + }, + + // Define a custom glsl accessor (string valued accessor will be treated as GLSL) + getPosition: 'return vec3(x, y, 0.);', + + // Supply values to the injected attributes + attributes: { + x: new Float32Array(...), + y: new Float32Array(...) + }, +}) +``` + +The input vertex shader would be defined as follows: + +``` +in vec3 instancePosition; + +// Shadertools would e.g. do a line-based replacement of getPosition +vec3 getPosition() { return instancePosition; } + +main() { + const position = getPosition(); + // Note: shader does not assume that `position = instancePositions`, but calls an overridable GLSL function! +} +``` + +Shadertools would resolve this to: + +``` +in vec3 instancePosition; // Note: no longer used, would be nice if this could be removed but not critical. + +in float x; +in float y; + +// Shadertools has done a line-based replacement of getPosition +vec3 getPosition() { return vec3(x, y, 0.);; } + +main() { + const position = getPosition(); + // Note: layer no longer assumes `position = instancePositions`, but calls overridable GLSL function +} +``` + + +### API Example: Adapting Data to fit layer's Requirement + +Let's say that we load binary data for a point cloud that only contains a single value per point (e.g. `reflectance`), but the shader we want to use only supports specifying an RGBA color per point. While we could certainly write a JavaScript function to generate a complete RGBA color array attribute, this extra time and memory could be avoided: + +``` +new Model({ + vs: POINT_CLOUD_VS, + fs: POINT_CLOUD_FS, + + // inject new attributes and uniforms + glsl: { + attributes: { + reflectance + }, + uniforms: { + alpha + } + }, + + // Define a custom glsl accessor (string valued accessor will be treated as GLSL) + getColor: 'return vec4(reflectance, reflectance, reflectance, alpha);', + + // Supply actual values to the injected attributes and uniforms + attributes: { + reflectance: new Float32Array(...) + }, + uniforms: { + alpha: 0.5 + } +}) +``` + + +## Design Discussions + +### Shader Buy-In + +While it might be possible to use the function replacement system to replace of arbitrary functions in a shader, this can lead to brittle overrides as the base shader is updated. + +The ideal setup is one where the shader is written with the intention of having certain functions be overridable. Often, the readout of attributes would be a typical place to add an overridable function, allowing the shader to be used with different attributes. + +Apart from attribute access, a shader could potentially define additional overridable functions that it would call at the right moments. These would then normally be separately documented. + + +## Open Issues + +### Naming Conflicts + +Define/reinforce naming conventions/prefixes for GLSL functions and variables in shader code to maximize predictability/minimize conflicts with user's GLSL accessor code. + +Since we do not have a true syntax aware parser/string replacement system we also risk replacing unrelated strings. This risk is becoming bigger the more extensive our shader replacement support becomes. + + +### Fragment Shader Support + +This RFC as written does not offer any way for users to modify fragment shaders. + +If it is desirable to offer the ability to redefine GLSL functions in the fragment shader, the GLSL accessor system outlined in this system does not extend well. +* attributes are not available in the fragment shader, and such data needs to be explicitly plumbed through as `varyings` that "eat into" a very limited bank (often only 8 varyings or `vec4`s). +* It may be necessary to implement a "varying compressor" and additonal props with more complicated semantics to describe the data flow. +Naturally, if user-defined GLSL functions in the fragment shaders can only use the data already available, the system should be simpler. diff --git a/docs/api-reference/lights/point-light.md b/docs/api-reference/lights/point-light.md index c47ba673da..3688eccb7f 100644 --- a/docs/api-reference/lights/point-light.md +++ b/docs/api-reference/lights/point-light.md @@ -28,3 +28,7 @@ const pointLight = new PointLight({color, intensity, position}); * `color` - (*array*,) RGB color of point light source, default value is `[255, 255, 255]`. * `intensity` - (*number*) Strength of point light source, default value is `1.0`. * `position` - (*array*,) Location of point light source, default value is `[0, 0, 1]`. +* `attenuation` - (*array*,) Attenuation of point light source based on distance. +In order of Constant, Linear, Exponential components. +For details see [this tutorial](http://ogldev.atspace.co.uk/www/tutorial20/tutorial20.html). +Use `[1, 0, 0]` for no attenuation. Default value is `[0, 0, 1]`. diff --git a/docs/api-reference/webgl/texture-2d.md b/docs/api-reference/webgl/texture-2d.md index 45e074412b..4233db7743 100644 --- a/docs/api-reference/webgl/texture-2d.md +++ b/docs/api-reference/webgl/texture-2d.md @@ -1,6 +1,6 @@ # Texture2D -2D textures hold basic "single image" textures (although technically they can contain multiple mimap levels). They hold image memory of a certain format and size, determined at initialization time. They can be read from using shaders and written to by attaching them to frame buffers. +2D textures hold basic "single image" textures (although technically they can contain multiple mipmap levels). They hold image memory of a certain format and size, determined at initialization time. They can be read from using shaders and written to by attaching them to frame buffers. Most texture related functionality is implemented by and documented on the [Texture](/docs/api-reference/webgl/texture.md) base class. For additional information, see [OpenGL Wiki](https://www.khronos.org/opengl/wiki/Texture). diff --git a/docs/api-reference/webgl/texture-3d.md b/docs/api-reference/webgl/texture-3d.md index 5ff3b9f9f4..a6e6bd9375 100644 --- a/docs/api-reference/webgl/texture-3d.md +++ b/docs/api-reference/webgl/texture-3d.md @@ -1,15 +1,13 @@ # Texture3D (WebGL2) -A `Texture3D` holds a number of textures of the same size and format. The entire array can be passed to the shader which uses an extra texture coordinate to sample from it. A core feature of `Texture3D` is that the entire stack of images can passed as a single uniform to and accessed in a GLSL shader, and sampled using 3D coordinates. - -3D textures are typically used to store volumetric data or for 3D lookup tables in shaders. +3D textures hold basic volumetric textures and can be thought of 3-dimentional arrays with a width, height and depth. They hold image memory of a certain format and size, determined at initialization time. They can be sampled in shaders using the `texture` function with a 3D texture coordinate. Most texture related functionality is implemented by and documented on the [Texture](/docs/api-reference/webgl/texture.md) base class. For additional information, see [OpenGL Wiki](https://www.khronos.org/opengl/wiki/Texture). ## Usage -Create a new texture array +Create a new 3D texture ```js if (Texture3D.isSupported()) { texture3D = new Texture3D(gl, {...}); @@ -21,15 +19,15 @@ if (Texture3D.isSupported()) { * `handle` - The underlying `WebGLTexture` * `target` - Always `GL.TEXTURE_3D` -* `depth` - the number of texture layers -* `width` - width of the layer textures -* `height` - height of the layer textures -* `format` - format of the layer textures +* `width` - width of texture +* `height` - height of texture +* `depth` - depth of the texture +* `format` - format of texture ## Methods -`Texture3D` is a subclass of the [Texture](texture.md) and [Resource](resource.md) classes and inherit all methods and members of those classes. +`Texture3D` is a subclass of the [Texture](texture.md) and [Resource](resource.md) classes and inherit all methods and members of those classes. Note that `setSubImageData` is not currently supported for 3D textures. ### Texture3D.isSupported(gl) @@ -41,11 +39,23 @@ Returns true if the context supports creation of `Texture3Ds`. `new Texture3D(gl, {parameters})`; +``` +const texture = new Texture3D(gl, { + width: TEXTURE_DIMENSIONS, + height: TEXTURE_DIMENSIONS, + depth: TEXTURE_DIMENSIONS, + data: textureData, + format: gl.RED, + dataFormat: gl.R8 +}); +``` + * `gl` (WebGLRenderingContext) - gl context * `data`=`null` (*) - See below. * `width`=`0` (*Number*) - The width of the texture. * `height`=`0` (*Number*) - The height of the texture. -* `mipmaps`=`GL/ (*Enum*, default false) - `n`th mipmap reduction level, 0 represents base image +* `depth`=`0` (*Number*) - The depth of the texture. +* `mipmaps`=`true` (*Boolean*) - whether to generate mipmaps * `format` (*enum*, default `GL.RGBA`) - internal format that WebGL should use. * `type` (*enum*, default is autodeduced from format) - type of pixel data (GL.UNSIGNED_BYTE, GL.FLOAT etc). * `dataFormat` (*enum*, default is autodeduced from `format`) - internal format that WebGL should use. diff --git a/examples/core/3d-texture/app.js b/examples/core/3d-texture/app.js new file mode 100644 index 0000000000..d42fffa21a --- /dev/null +++ b/examples/core/3d-texture/app.js @@ -0,0 +1,186 @@ +import {AnimationLoop, setParameters, Model, Texture3D, Buffer} from 'luma.gl'; +import {Matrix4, radians} from 'math.gl'; +import {StatsWidget} from '@probe.gl/stats-widget'; +import {default as noise3d} from 'noise3d'; + +/* + Ported from PicoGL.js example: https://tsherif.github.io/picogl.js/examples/3Dtexture.html +*/ + +const INFO_HTML = ` +

+Cube drawn with instanced rendering. +

+A luma.gl Cube, rendering 65,536 instances in a +single GPU draw call using instanced vertex attributes. +`; + +const vs = `\ +#version 300 es +in vec3 position; + +uniform mat4 uMVP; + +out vec3 vUV; +void main() { + vUV = position.xyz + 0.5; + gl_Position = uMVP * vec4(position, 1.0); + gl_PointSize = 2.0; +}`; + +const fs = `\ +#version 300 es +precision highp float; +precision lowp sampler3D; +in vec3 vUV; +uniform sampler3D uTexture; +uniform float uTime; +out vec4 fragColor; +void main() { + float alpha = texture(uTexture, vUV + vec3(0.0, 0.0, uTime)).r * 0.1; + fragColor = vec4(fract(vUV) * alpha, alpha); +}`; + +const NEAR = 0.1; +const FAR = 10.0; + +class AppAnimationLoop extends AnimationLoop { + constructor() { + super({useDevicePixels: false}); + } + + getInfo() { + return INFO_HTML; + } + + onInitialize({gl}) { + const perlin = noise3d.createPerlin({ + interpolation: noise3d.interpolation.linear, + permutation: noise3d.array.shuffle(noise3d.array.range(0, 255), Math.random) + }); + + setParameters(gl, { + clearColor: [0, 0, 0, 1], + blend: true, + blendFunc: [gl.ONE, gl.ONE_MINUS_SRC_ALPHA] + }); + + // CREATE POINT CLOUD + const DIMENSIONS = 128; + const INCREMENT = 1 / DIMENSIONS; + + const positionData = new Float32Array(DIMENSIONS * DIMENSIONS * DIMENSIONS * 3); + let positionIndex = 0; + let x = -0.5; + for (let i = 0; i < DIMENSIONS; ++i) { + let y = -0.5; + for (let j = 0; j < DIMENSIONS; ++j) { + let z = -0.5; + for (let k = 0; k < DIMENSIONS; ++k) { + positionData[positionIndex++] = x; + positionData[positionIndex++] = y; + positionData[positionIndex++] = z; + z += INCREMENT; + } + y += INCREMENT; + } + x += INCREMENT; + } + + const positionBuffer = new Buffer(gl, positionData); + + // CREATE 3D TEXTURE + const TEXTURE_DIMENSIONS = 16; + const NOISE_DIMENSIONS = TEXTURE_DIMENSIONS * 0.07; + const textureData = new Uint8Array( + TEXTURE_DIMENSIONS * TEXTURE_DIMENSIONS * TEXTURE_DIMENSIONS + ); + let textureIndex = 0; + for (let i = 0; i < TEXTURE_DIMENSIONS; ++i) { + for (let j = 0; j < TEXTURE_DIMENSIONS; ++j) { + for (let k = 0; k < TEXTURE_DIMENSIONS; ++k) { + textureData[textureIndex++] = + (0.5 + 0.5 * perlin(i / NOISE_DIMENSIONS, j / NOISE_DIMENSIONS, k / NOISE_DIMENSIONS)) * + 255; + } + } + } + + const mvpMat = new Matrix4(); + const viewMat = new Matrix4().lookAt({eye: [1, 1, 1]}); + + const texture = new Texture3D(gl, { + width: TEXTURE_DIMENSIONS, + height: TEXTURE_DIMENSIONS, + depth: TEXTURE_DIMENSIONS, + data: textureData, + format: gl.RED, + dataFormat: gl.R8 + }); + + const cloud = new Model(gl, { + vs, + fs, + drawMode: gl.POINTS, + vertexCount: positionData.length / 3, + attributes: { + position: positionBuffer + }, + uniforms: { + uTexture: texture, + uView: viewMat + } + }); + + const statsWidget = new StatsWidget(this.stats, { + title: 'Render Time', + css: { + position: 'absolute', + top: '20px', + left: '20px' + }, + framesPerUpdate: 60, + formatters: { + 'CPU Time': 'averageTime', + 'GPU Time': 'averageTime', + 'Frame Rate': 'fps' + }, + resetOnUpdate: { + 'CPU Time': true, + 'GPU Time': true, + 'Frame Rate': true + } + }); + + return {cloud, mvpMat, viewMat, statsWidget}; + } + + onRender(animationProps) { + const {gl, cloud, mvpMat, viewMat, statsWidget, tick, aspect} = animationProps; + statsWidget.update(); + + mvpMat.perspective({fov: radians(75), aspect, near: NEAR, far: FAR}).multiplyRight(viewMat); + + // Draw the cubes + gl.clear(gl.COLOR_BUFFER_BIT); + cloud.draw({ + uniforms: { + uTime: tick / 100, + uMVP: mvpMat + } + }); + } + + onFinalize({gl, cloud}) { + cloud.delete(); + } +} + +const animationLoop = new AppAnimationLoop(); + +export default animationLoop; + +/* global window */ +if (typeof window !== 'undefined' && !window.website) { + animationLoop.start(); +} diff --git a/examples/core/3d-texture/package.json b/examples/core/3d-texture/package.json new file mode 100644 index 0000000000..bf3be06e1d --- /dev/null +++ b/examples/core/3d-texture/package.json @@ -0,0 +1,16 @@ +{ + "scripts": { + "start": "webpack-dev-server --progress --hot --open -d", + "start-local": "webpack-dev-server --env.local --progress --hot --open -d", + "build": "webpack --env.local --env.analyze --profile --json > stats.json" + }, + "dependencies": { + "luma.gl": "6.0.0", + "noise3d": "^1.0.0" + }, + "devDependencies": { + "html-webpack-plugin": "^3.2.0", + "webpack": "^4.3.0", + "webpack-dev-server": "^3.1.1" + } +} diff --git a/examples/core/3d-texture/webpack.config.js b/examples/core/3d-texture/webpack.config.js new file mode 100644 index 0000000000..a09819a693 --- /dev/null +++ b/examples/core/3d-texture/webpack.config.js @@ -0,0 +1,15 @@ +const {resolve} = require('path'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); + +const CONFIG = { + mode: 'development', + + entry: { + app: resolve('./app.js') + }, + + plugins: [new HtmlWebpackPlugin({title: '3D Texture'})] +}; + +// This line enables bundling against src in this repo rather than installed module +module.exports = env => (env ? require('../../webpack.config.local')(CONFIG)(env) : CONFIG); diff --git a/examples/core/dof/app.js b/examples/core/dof/app.js index 5d96c66836..d63eaeea02 100644 --- a/examples/core/dof/app.js +++ b/examples/core/dof/app.js @@ -1,10 +1,20 @@ import GL from '@luma.gl/constants'; import { - AnimationLoop, Framebuffer, Cube, setParameters, clear, - Program, Texture2D, VertexArray, Buffer, isWebGL2 + AnimationLoop, + Framebuffer, + Cube, + setParameters, + clear, + Program, + Texture2D, + VertexArray, + Buffer, + isWebGL2 } from 'luma.gl'; import {Matrix4, radians} from 'math.gl'; -import {StatsWidget} from '@probe.gl/stats-widget' +import {StatsWidget} from '@probe.gl/stats-widget'; +/* eslint-disable spaced-comment */ +/* global document */ /* Based on: https://github.com/tsherif/picogl.js/blob/master/examples/dof.html @@ -30,10 +40,10 @@ post-processing effect. `; -const ALT_TEXT = 'THIS DEMO REQUIRES WEBLG2, BUT YOUR BRWOSER DOESN\'T SUPPORT IT'; +const ALT_TEXT = "THIS DEMO REQUIRES WEBLG2, BUT YOUR BRWOSER DOESN'T SUPPORT IT"; let isDemoSupported = true; -const QUAD_VERTS = [1, 1, 0, -1, 1, 0, 1, -1, 0, -1, -1, 0]; // eslint-disable-line +const QUAD_VERTS = [1, 1, 0, -1, 1, 0, 1, -1, 0, -1, -1, 0]; // eslint-disable-line const NUM_ROWS = 5; const CUBES_PER_ROW = 20; const NUM_CUBES = CUBES_PER_ROW * NUM_ROWS; @@ -43,16 +53,14 @@ const FAR = 30.0; let focalLength = 2.0; let focusDistance = 3.0; let fStop = 2.8; -let texelOffset = new Float32Array(2); +const texelOffset = new Float32Array(2); class InstancedCube extends Cube { - constructor(gl, props) { - - let count = props.count; - let xforms = new Array(count); - let matrices = new Float32Array(count * 16); - let matrixBuffer = new Buffer(gl, matrices.byteLength); + const count = props.count; + const xforms = new Array(count); + const matrices = new Float32Array(count * 16); + const matrixBuffer = new Buffer(gl, matrices.byteLength); const vs = `\ #version 300 es @@ -100,49 +108,52 @@ void main(void) { } `; - super(gl, Object.assign({}, props, { - vs, - fs, - isInstanced: 1, - instanceCount: count, - uniforms: { - uTexture: props.uniforms.uTexture - }, - attributes: { - // Attributes are limited to 4 components, - // So we have to split the matrices across - // 4 attributes. They're reconstructed in - // the vertex shader. - modelMatCol1: { - buffer: matrixBuffer, - size: 4, - stride: 64, - offset: 0, - divisor: 1 - }, - modelMatCol2: { - buffer: matrixBuffer, - size: 4, - stride: 64, - offset: 16, - divisor: 1 + super( + gl, + Object.assign({}, props, { + vs, + fs, + isInstanced: 1, + instanceCount: count, + uniforms: { + uTexture: props.uniforms.uTexture }, - modelMatCol3: { - buffer: matrixBuffer, - size: 4, - stride: 64, - offset: 32, - divisor: 1 - }, - modelMatCol4: { - buffer: matrixBuffer, - size: 4, - stride: 64, - offset: 48, - divisor: 1 + attributes: { + // Attributes are limited to 4 components, + // So we have to split the matrices across + // 4 attributes. They're reconstructed in + // the vertex shader. + modelMatCol1: { + buffer: matrixBuffer, + size: 4, + stride: 64, + offset: 0, + divisor: 1 + }, + modelMatCol2: { + buffer: matrixBuffer, + size: 4, + stride: 64, + offset: 16, + divisor: 1 + }, + modelMatCol3: { + buffer: matrixBuffer, + size: 4, + stride: 64, + offset: 32, + divisor: 1 + }, + modelMatCol4: { + buffer: matrixBuffer, + size: 4, + stride: 64, + offset: 48, + divisor: 1 + } } - } - })); + }) + ); this.count = count; this.xforms = xforms; @@ -166,7 +177,7 @@ void main() { } `; -const DOF_FRAGMENT=`\ +const DOF_FRAGMENT = `\ #version 300 es precision highp float; #define SHADER_NAME dof.fs @@ -229,7 +240,6 @@ export const animationLoopOptions = { onInitialize: ({gl, _animationLoop}) => { isDemoSupported = isWebGL2(gl); if (!isDemoSupported) { - console.error(ALT_TEXT); return {isDemoSupported}; } @@ -246,15 +256,14 @@ export const animationLoopOptions = { /////////////////////////////////////// const dofProgram = new Program(gl, { - id: "DOF_PROGRAM", + id: 'DOF_PROGRAM', vs: DOF_VERTEX, fs: DOF_FRAGMENT, uniforms: { uDepthRange: [NEAR, FAR] - }, + } }); - ////////////////////// // Set up frambuffers. ////////////////////// @@ -292,34 +301,37 @@ export const animationLoopOptions = { } }) } - }) + }); // Postprocessing FBO doesn't need a depth attachment. - const dofFramebuffer = new Framebuffer(gl, { width: gl.drawingBufferWidth, height: gl.drawingBufferHeight, depth: false}); - + const dofFramebuffer = new Framebuffer(gl, { + width: gl.drawingBufferWidth, + height: gl.drawingBufferHeight, + depth: false + }); ///////////////////// // Input handlers. ///////////////////// - const focalLengthInput = document.getElementById("focal-length"); - const focusDistanceInput = document.getElementById("focus-distance"); - const fStopInput = document.getElementById("f-stop"); + const focalLengthInput = document.getElementById('focal-length'); + const focusDistanceInput = document.getElementById('focus-distance'); + const fStopInput = document.getElementById('f-stop'); if (focalLengthInput) { focalLengthInput.value = focalLength; - focalLengthInput.addEventListener("input", function () { - focalLength = parseFloat(this.value); + focalLengthInput.addEventListener('input', () => { + focalLength = parseFloat(focalLengthInput.value); }); focusDistanceInput.value = focusDistance; - focusDistanceInput.addEventListener("input", function () { - focusDistance = parseFloat(this.value); + focusDistanceInput.addEventListener('input', () => { + focusDistance = parseFloat(focusDistanceInput.value); }); fStopInput.value = fStop; - fStopInput.addEventListener("input", function () { - fStop = parseFloat(this.value); + fStopInput.addEventListener('input', () => { + fStop = parseFloat(fStopInput.value); }); } @@ -345,21 +357,24 @@ export const animationLoopOptions = { let cubeI = 0; for (let j = 0; j < NUM_ROWS; ++j) { - let rowOffset = (j - Math.floor(NUM_ROWS / 2)); - for (let i = 0; i < CUBES_PER_ROW; ++i) { - let scale = [0.4, 0.4, 0.4]; - let rotate = [-Math.random() * Math.PI, 0, Math.random() * Math.PI]; - let translate = [-i + 2 - rowOffset, 0, -i + 2 + rowOffset]; - instancedCubes.xforms[cubeI] = { - scale: scale, - translate: translate, - rotate: rotate, - matrix: new Matrix4().translate(translate).rotateXYZ(rotate).scale(scale) - }; - - instancedCubes.matrices.set(instancedCubes.xforms[cubeI].matrix, cubeI * 16); + const rowOffset = j - Math.floor(NUM_ROWS / 2); + for (let i = 0; i < CUBES_PER_ROW; ++i) { + const scale = [0.4, 0.4, 0.4]; + const rotate = [-Math.random() * Math.PI, 0, Math.random() * Math.PI]; + const translate = [-i + 2 - rowOffset, 0, -i + 2 + rowOffset]; + instancedCubes.xforms[cubeI] = { + scale, + translate, + rotate, + matrix: new Matrix4() + .translate(translate) + .rotateXYZ(rotate) + .scale(scale) + }; + + instancedCubes.matrices.set(instancedCubes.xforms[cubeI].matrix, cubeI * 16); ++cubeI; - } + } } instancedCubes.updateMatrixBuffer(); @@ -377,11 +392,24 @@ export const animationLoopOptions = { }); const statsWidget = new StatsWidget(_animationLoop.stats, { - containerStyle: 'position: absolute;top: 20px;left: 20px;' + title: 'Render Time', + css: { + position: 'absolute', + top: '20px', + left: '20px' + }, + framesPerUpdate: 60, + formatters: { + 'CPU Time': 'averageTime', + 'GPU Time': 'averageTime', + 'Frame Rate': 'fps' + }, + resetOnUpdate: { + 'CPU Time': true, + 'GPU Time': true, + 'Frame Rate': true + } }); - statsWidget.setFormatter('CPU Time', stat => `CPU Time: ${stat.getAverageTime().toFixed(2)}ms`); - statsWidget.setFormatter('GPU Time', stat => `GPU Time: ${stat.getAverageTime().toFixed(2)}ms`); - statsWidget.setFormatter('Frame Rate', stat => `Frame Rate: ${stat.getHz().toFixed(2)}fps`); return { projMat, @@ -395,25 +423,37 @@ export const animationLoopOptions = { }; }, - onRender: ({gl, tick, width, height, aspect, projMat, viewMat, instancedCubes, sceneFramebuffer, dofFramebuffer, quadVertexArray, dofProgram, statsWidget, _animationLoop}) => { - + onRender: ({ + gl, + tick, + width, + height, + aspect, + projMat, + viewMat, + instancedCubes, + sceneFramebuffer, + dofFramebuffer, + quadVertexArray, + dofProgram, + statsWidget + }) => { if (!isDemoSupported) { - return; + return; } - if (tick % 60 === 10) { - statsWidget.update(); - _animationLoop.cpuTime.reset(); - _animationLoop.gpuTime.reset(); - _animationLoop.frameRate.reset(); - } + statsWidget.update(); sceneFramebuffer.resize(gl.drawingBufferWidth, gl.drawingBufferHeight); dofFramebuffer.resize(gl.drawingBufferWidth, gl.drawingBufferHeight); - let magnification = focalLength / Math.max(0.1, Math.abs(focusDistance - focalLength)); - let blurCoefficient = focalLength * magnification / fStop; - let ppm = Math.sqrt(gl.drawingBufferWidth * gl.drawingBufferWidth + gl.drawingBufferHeight * gl.drawingBufferHeight) / 35; + const magnification = focalLength / Math.max(0.1, Math.abs(focusDistance - focalLength)); + const blurCoefficient = (focalLength * magnification) / fStop; + const ppm = + Math.sqrt( + gl.drawingBufferWidth * gl.drawingBufferWidth + + gl.drawingBufferHeight * gl.drawingBufferHeight + ) / 35; clear(gl, {color: [0, 0, 0, 1], depth: true, framebuffer: sceneFramebuffer}); @@ -426,10 +466,14 @@ export const animationLoopOptions = { //////////////////////////////////////// for (let i = 0; i < NUM_CUBES; ++i) { - let box = instancedCubes.xforms[i]; + const box = instancedCubes.xforms[i]; box.rotate[0] += 0.01; box.rotate[1] += 0.02; - box.matrix.identity().translate(box.translate).rotateXYZ(box.rotate).scale(box.scale); + box.matrix + .identity() + .translate(box.translate) + .rotateXYZ(box.rotate) + .scale(box.scale); instancedCubes.matrices.set(box.matrix, i * 16); } @@ -459,12 +503,12 @@ export const animationLoopOptions = { texelOffset[1] = 0; dofProgram.setUniforms({ - uFocusDistance: focusDistance, - uBlurCoefficient: blurCoefficient, - uPPM: ppm, - uTexelOffset: texelOffset, - uColor: sceneFramebuffer.color, - uDepth: sceneFramebuffer.depth + uFocusDistance: focusDistance, + uBlurCoefficient: blurCoefficient, + uPPM: ppm, + uTexelOffset: texelOffset, + uColor: sceneFramebuffer.color, + uDepth: sceneFramebuffer.depth }); dofProgram.draw({ diff --git a/examples/core/instancing/app.js b/examples/core/instancing/app.js index c69d5e5a34..74a248f33e 100644 --- a/examples/core/instancing/app.js +++ b/examples/core/instancing/app.js @@ -1,6 +1,6 @@ import {AnimationLoop, setParameters, pickModels, Cube, picking, dirlight} from 'luma.gl'; import {Matrix4, radians} from 'math.gl'; -import {StatsWidget} from '@probe.gl/stats-widget' +import {StatsWidget} from '@probe.gl/stats-widget'; const INFO_HTML = `

@@ -14,13 +14,12 @@ const SIDE = 256; // Make a cube with 65K instances and attributes to control offset and color of each instance class InstancedCube extends Cube { - constructor(gl, props) { let offsets = []; for (let i = 0; i < SIDE; i++) { - const x = (-SIDE + 1) * 3 / 2 + i * 3; + const x = ((-SIDE + 1) * 3) / 2 + i * 3; for (let j = 0; j < SIDE; j++) { - const y = (-SIDE + 1) * 3 / 2 + j * 3; + const y = ((-SIDE + 1) * 3) / 2 + j * 3; offsets.push(x, y); } } @@ -34,9 +33,7 @@ class InstancedCube extends Cube { } } - const colors = new Float32Array(SIDE * SIDE * 3).map( - () => Math.random() * 0.75 + 0.25 - ); + const colors = new Float32Array(SIDE * SIDE * 3).map(() => Math.random() * 0.75 + 0.25); const vs = `\ attribute float instanceSizes; @@ -79,19 +76,22 @@ void main(void) { } `; - super(gl, Object.assign({}, props, { - vs, - fs, - modules: [picking, dirlight], - isInstanced: 1, - instanceCount: SIDE * SIDE, - attributes: { - instanceSizes: {value: new Float32Array([1]), divisor: 1, constant: true}, - instanceOffsets: {value: offsets, divisor: 1}, - instanceColors: {value: colors, divisor: 1}, - instancePickingColors: {value: pickingColors, divisor: 1} - } - })); + super( + gl, + Object.assign({}, props, { + vs, + fs, + modules: [picking, dirlight], + isInstanced: 1, + instanceCount: SIDE * SIDE, + attributes: { + instanceSizes: {value: new Float32Array([1]), divisor: 1, constant: true}, + instanceOffsets: {value: offsets, divisor: 1}, + instanceColors: {value: colors, divisor: 1}, + instancePickingColors: {value: pickingColors, divisor: 1} + } + }) + ); } } @@ -105,7 +105,6 @@ class AppAnimationLoop extends AnimationLoop { } onInitialize({gl, _animationLoop}) { - setParameters(gl, { clearColor: [0, 0, 0, 1], clearDepth: 1, @@ -121,47 +120,57 @@ class AppAnimationLoop extends AnimationLoop { uProjection: ({aspect}) => new Matrix4().perspective({fov: radians(60), aspect, near: 1, far: 2048.0}), // Move the eye around the plane - uView: ({tick}) => new Matrix4().lookAt({ - center: [0, 0, 0], - eye: [ - Math.cos(tick * 0.005) * SIDE / 2, - Math.sin(tick * 0.006) * SIDE / 2, - (Math.sin(tick * 0.0035) + 1) * SIDE / 4 + 32 - ] - }), + uView: ({tick}) => + new Matrix4().lookAt({ + center: [0, 0, 0], + eye: [ + (Math.cos(tick * 0.005) * SIDE) / 2, + (Math.sin(tick * 0.006) * SIDE) / 2, + ((Math.sin(tick * 0.0035) + 1) * SIDE) / 4 + 32 + ] + }), // Rotate all the individual cubes uModel: ({tick}) => new Matrix4().rotateX(tick * 0.01).rotateY(tick * 0.013) } }); const statsWidget = new StatsWidget(this.stats, { - containerStyle: 'position: absolute;top: 20px;left: 20px;' + title: 'Render Time', + css: { + position: 'absolute', + top: '20px', + left: '20px' + }, + framesPerUpdate: 60, + formatters: { + 'CPU Time': 'averageTime', + 'GPU Time': 'averageTime', + 'Frame Rate': 'fps' + }, + resetOnUpdate: { + 'CPU Time': true, + 'GPU Time': true, + 'Frame Rate': true + } }); - statsWidget.setFormatter('CPU Time', stat => `CPU Time: ${stat.getAverageTime().toFixed(2)}ms`); - statsWidget.setFormatter('GPU Time', stat => `GPU Time: ${stat.getAverageTime().toFixed(2)}ms`); - statsWidget.setFormatter('Frame Rate', stat => `Frame Rate: ${stat.getHz().toFixed(2)}fps`); return {statsWidget}; } onRender(animationProps) { + const {gl, framebuffer, useDevicePixels, _mousePosition, statsWidget} = animationProps; - const {gl, framebuffer, useDevicePixels, _mousePosition, statsWidget, tick} = animationProps; - - if (tick % 60 === 10) { - statsWidget.update(); - this.cpuTime.reset(); - this.gpuTime.reset(); - this.frameRate.reset(); - } + statsWidget.update(); // "Pick" the cube under the mouse - const pickInfo = _mousePosition && pickModels(gl, { - models: [this.cube], - position: _mousePosition, - useDevicePixels, - framebuffer - }); + const pickInfo = + _mousePosition && + pickModels(gl, { + models: [this.cube], + position: _mousePosition, + useDevicePixels, + framebuffer + }); // Highlight it const pickingSelectedColor = (pickInfo && pickInfo.color) || null; diff --git a/examples/core/mandelbrot/app.js b/examples/core/mandelbrot/app.js index caeefec50e..84cfacf736 100644 --- a/examples/core/mandelbrot/app.js +++ b/examples/core/mandelbrot/app.js @@ -1,5 +1,5 @@ import {AnimationLoop, ClipSpace} from 'luma.gl'; -import {StatsWidget} from '@probe.gl/stats-widget' +import {StatsWidget} from '@probe.gl/stats-widget'; const INFO_HTML = `

@@ -62,12 +62,7 @@ const ZOOM_THRESHOLD = 1e5; const ZOOM_CENTER_X = -0.0150086889504513; const ZOOM_CENTER_Y = 0.78186693904085048; -const BASE_CORNERS = [ - [-2.2, -1.2], - [0.7, -1.2], - [-2.2, 1.2], - [0.7, 1.2] -]; +const BASE_CORNERS = [[-2.2, -1.2], [0.7, -1.2], [-2.2, 1.2], [0.7, 1.2]]; let centerOffsetX = 0; let centerOffsetY = 0; @@ -97,13 +92,25 @@ function getZoomedCorners(zoomFactor = 1.01) { const animationLoop = new AnimationLoop({ onInitialize: ({gl, _animationLoop}) => { - const statsWidget = new StatsWidget(_animationLoop.stats, { - containerStyle: 'position: absolute;top: 20px;left: 20px;' + title: 'Render Time', + css: { + position: 'absolute', + top: '20px', + left: '20px' + }, + framesPerUpdate: 60, + formatters: { + 'CPU Time': 'averageTime', + 'GPU Time': 'averageTime', + 'Frame Rate': 'fps' + }, + resetOnUpdate: { + 'CPU Time': true, + 'GPU Time': true, + 'Frame Rate': true + } }); - statsWidget.setFormatter('CPU Time', stat => `CPU Time: ${stat.getAverageTime().toFixed(2)}ms`); - statsWidget.setFormatter('GPU Time', stat => `GPU Time: ${stat.getAverageTime().toFixed(2)}ms`); - statsWidget.setFormatter('Frame Rate', stat => `Frame Rate: ${stat.getHz().toFixed(2)}fps`); return { clipSpace: new ClipSpace(gl, {fs: MANDELBROT_FRAGMENT_SHADER}), @@ -111,13 +118,8 @@ const animationLoop = new AnimationLoop({ }; }, - onRender: ({gl, canvas, tick, clipSpace, statsWidget, _animationLoop}) => { - if (tick % 60 === 10) { - statsWidget.update(); - _animationLoop.cpuTime.reset(); - _animationLoop.gpuTime.reset(); - _animationLoop.frameRate.reset(); - } + onRender: ({gl, canvas, tick, clipSpace, statsWidget}) => { + statsWidget.update(); gl.viewport(0, 0, Math.max(canvas.width, canvas.height), Math.max(canvas.width, canvas.height)); diff --git a/examples/gltf/app.js b/examples/gltf/app.js index c326296457..dce1112e58 100644 --- a/examples/gltf/app.js +++ b/examples/gltf/app.js @@ -35,11 +35,94 @@ const INFO_HTML = `
+

+ Light + +
+
`; +const LIGHT_SOURCES = { + default: { + directionalLights: [{ + color: [255, 255, 255], + direction: [0.0, 0.5, 0.5], + intensity: 1.0, + } + ] + }, + ambient: { + ambientLight: { + color: [255, 255, 255], + intensity: 1.0, + } + }, + directional1: { + directionalLights: [{ + color: [255, 0, 0], + direction: [1.0, 0.0, 0.0], + intensity: 1.0, + } + ], + ambientLight: { + color: [255, 255, 255], + intensity: 1.0, + } + }, + directional3: { + directionalLights: [{ + color: [255, 0.0, 0.0], + direction: [1.0, 0.0, 0.0], + intensity: 1.0, + },{ + color: [0.0, 0.0, 255], + direction: [0.0, 0.0, 1.0], + intensity: 1.0, + },{ + color: [0.0, 255, 0.0], + direction: [0.0, 1.0, 0.0], + intensity: 1.0, + } + ] + }, + point1far: { + pointLights: [{ + color: [255, 0, 0], + position: [200.0, 0.0, 0.0], + attenuation: [0, 0, 0.01], + intensity: 1.0, + } + ], + ambientLight: { + color: [255, 255, 255], + intensity: 1.0, + } + }, + point1near: { + pointLights: [{ + color: [255, 0, 0], + position: [10.0, 0.0, 0.0], + attenuation: [0, 0, 0.01], + intensity: 1.0, + } + ], + ambientLight: { + color: [255, 255, 255], + intensity: 1.0, + } + } +}; + const DEFAULT_OPTIONS = { pbrDebug: true, - pbrIbl: true + pbrIbl: false }; async function loadGLTF(urlOrPromise, gl, options = DEFAULT_OPTIONS) { @@ -205,9 +288,23 @@ export class DemoApp { }; } + const lightSelector = document.getElementById("lightSelector"); + if (lightSelector) { + lightSelector.onchange = event => { + this.light = lightSelector.value; + }; + } + this.initalizeEventHandling(canvas); } + applyLight(model) { + // TODO: only do this when light changes + model.updateModuleSettings({ + lightSources: LIGHT_SOURCES[this.light || 'default'] + }); + } + onRender({gl, time, width, height, aspect}) { gl.viewport(0, 0, width, height); clear(gl, {color: [0.2, 0.2, 0.2, 1.0], depth: true}); @@ -237,6 +334,7 @@ export class DemoApp { this.scenes[0].traverse((model, {worldMatrix}) => { // In glTF, meshes and primitives do no have their own matrix. const u_MVPMatrix = new Matrix4(uProjection).multiplyRight(uView).multiplyRight(worldMatrix); + this.applyLight(model); success = success && model.draw({ diff --git a/modules/core/package.json b/modules/core/package.json index 36c1aa19d8..2bc98f4d41 100644 --- a/modules/core/package.json +++ b/modules/core/package.json @@ -43,7 +43,7 @@ "@luma.gl/webgl-state-tracker": "^7.0.0-alpha.15", "@luma.gl/webgl2-polyfill": "^7.0.0-alpha.15", "math.gl": "^2.2.0", - "probe.gl": "3.0.0-alpha.6", + "probe.gl": "3.0.0-alpha.7", "seer": "^0.2.4" }, "nyc": { diff --git a/modules/core/src/index.js b/modules/core/src/index.js index 98f481b568..0fb4f551fe 100644 --- a/modules/core/src/index.js +++ b/modules/core/src/index.js @@ -143,6 +143,7 @@ export { fp32, fp64, project, + legacyLighting, lighting, dirlight, picking, diff --git a/modules/core/src/lighting/light-source.js b/modules/core/src/lighting/light-source.js index 4e97f43942..6d87c85ffc 100644 --- a/modules/core/src/lighting/light-source.js +++ b/modules/core/src/lighting/light-source.js @@ -6,6 +6,7 @@ const DEFAULT_LIGHT_POSITION = [0.0, 0.0, 1.0]; const DEFAULT_LIGHT_DIRECTION = [0.0, 0.0, -1.0]; const DEFAULT_LIGHT_INTENSITY = 1.0; const DEFAULT_LIGHT_COLOR = [255, 255, 255]; +const DEFAULT_ATTENUATION = [0, 0, 1]; class LightSource extends ScenegraphNode { constructor(props) { @@ -29,7 +30,8 @@ export class AmbientLight extends LightSource {} export class PointLight extends LightSource { constructor(props) { super(props); - const {position = DEFAULT_LIGHT_POSITION} = props; + const {position = DEFAULT_LIGHT_POSITION, attenuation = DEFAULT_ATTENUATION} = props; this.position = position; + this.attenuation = attenuation; } } diff --git a/modules/core/src/scenegraph/gltf/gltf-material-parser.js b/modules/core/src/scenegraph/gltf/gltf-material-parser.js index 8f41d99616..e7e565f689 100644 --- a/modules/core/src/scenegraph/gltf/gltf-material-parser.js +++ b/modules/core/src/scenegraph/gltf/gltf-material-parser.js @@ -94,9 +94,6 @@ export default class GLTFMaterialParser { // TODO: find better values? u_Camera: [0, 0, 0], // Model should override - u_LightDirection: [0.0, 0.5, 0.5], - u_LightColor: [1.0, 1.0, 1.0], - u_MetallicRoughnessValues: [1, 1] // Default is 1 and 1 }; diff --git a/modules/core/src/webgl/classes/texture-3d.js b/modules/core/src/webgl/classes/texture-3d.js index 4b4ef2c2de..961287fcc0 100644 --- a/modules/core/src/webgl/classes/texture-3d.js +++ b/modules/core/src/webgl/classes/texture-3d.js @@ -1,7 +1,5 @@ import GL from '@luma.gl/constants'; import Texture from './texture'; -import Buffer from './buffer'; -import {withParameters} from '../context'; import {isWebGL2, assertWebGL2Context} from '../utils'; export default class Texture3D extends Texture { @@ -9,79 +7,12 @@ export default class Texture3D extends Texture { return isWebGL2(gl); } - constructor(gl, opts = {}) { + constructor(gl, props = {}) { assertWebGL2Context(gl); - super(gl, Object.assign({}, opts, {target: opts.target || GL.TEXTURE_3D})); + props = Object.assign({depth: 1}, props, {target: GL.TEXTURE_3D, unpackFlipY: false}); + super(gl, props); + this.initialize(props); - this.width = null; - this.height = null; - this.depth = null; Object.seal(this); - - this.setImageData(opts); - if (opts.generateMipmap) { - this.generateMipmap(); - } - } - - initialize(opts = {}) { - this.opts = Object.assign({}, this.opts, opts); - const {pixels, settings} = this.opts; - if (settings) { - withParameters(settings, () => { - if (pixels) { - this.setImage3D(this.opts); - } - }); - this.setParameters(opts); - } - } - - // WebGL2 - - // Image 3D copies from Typed Array or WebGLBuffer - setImage3D({ - level = 0, - internalformat = GL.RGBA, - width, - height, - depth = 1, - border = 0, - format, - type = GL.UNSIGNED_BYTE, - offset = 0, - pixels - }) { - if (ArrayBuffer.isView(pixels)) { - this.gl.texImage3D( - this.target, - level, - internalformat, - width, - height, - depth, - border, - format, - type, - pixels - ); - return; - } - if (pixels instanceof Buffer) { - this.gl.bindBuffer(GL.PIXEL_UNPACK_BUFFER, pixels.handle); - this.gl.texImage3D( - this.target, - level, - internalformat, - width, - height, - depth, - border, - format, - type, - offset - ); - this.gl.bindBuffer(GL.PIXEL_UNPACK_BUFFER, pixels.handle); - } } } diff --git a/modules/core/src/webgl/classes/texture.js b/modules/core/src/webgl/classes/texture.js index e43c1469ed..dcaccc0c4c 100644 --- a/modules/core/src/webgl/classes/texture.js +++ b/modules/core/src/webgl/classes/texture.js @@ -52,6 +52,7 @@ export default class Texture extends Resource { this.width = undefined; this.height = undefined; + this.depth = undefined; this.format = undefined; this.type = undefined; this.dataFormat = undefined; @@ -104,6 +105,7 @@ export default class Texture extends Resource { } let {width, height, dataFormat} = props; + const {depth = 0} = props; // Deduce width and height ({width, height, dataFormat} = this._deduceParameters({ @@ -119,6 +121,7 @@ export default class Texture extends Resource { // Store opts for accessors this.width = width; this.height = height; + this.depth = depth; this.format = format; this.type = type; this.dataFormat = dataFormat; @@ -151,6 +154,7 @@ export default class Texture extends Resource { data, width, height, + depth, format, type, dataFormat, @@ -224,6 +228,10 @@ export default class Texture extends Resource { */ /* eslint-disable max-len, max-statements, complexity */ setImageData(options) { + if (this.depth > 0) { + return this.setImage3D(options); + } + const { target = this.target, pixels = null, @@ -368,6 +376,8 @@ export default class Texture extends Resource { height })); + assert(this.depth === 0, 'texSubImage not supported for 3D textures'); + // pixels variable is for API compatibility purpose if (!data) { data = pixels; @@ -489,7 +499,7 @@ export default class Texture extends Resource { // Image 3D copies from Typed Array or WebGLBuffer setImage3D({ level = 0, - internalformat = GL.RGBA, + dataFormat = GL.RGBA, width, height, depth = 1, @@ -497,39 +507,45 @@ export default class Texture extends Resource { format, type = GL.UNSIGNED_BYTE, offset = 0, - pixels + data, + parameters = {} }) { - if (ArrayBuffer.isView(pixels)) { - this.gl.texImage3D( - this.target, - level, - internalformat, - width, - height, - depth, - border, - format, - type, - pixels - ); - return this; - } + this.gl.bindTexture(this.target, this.handle); - if (pixels instanceof Buffer) { - this.gl.bindBuffer(GL.PIXEL_UNPACK_BUFFER, pixels.handle); - this.gl.texImage3D( - this.target, - level, - internalformat, - width, - height, - depth, - border, - format, - type, - offset - ); - } + withParameters(this.gl, parameters, () => { + if (ArrayBuffer.isView(data)) { + this.gl.texImage3D( + this.target, + level, + dataFormat, + width, + height, + depth, + border, + format, + type, + data + ); + } + + if (data instanceof Buffer) { + this.gl.bindBuffer(GL.PIXEL_UNPACK_BUFFER, data.handle); + this.gl.texImage3D( + this.target, + level, + dataFormat, + width, + height, + depth, + border, + format, + type, + offset + ); + } + }); + + this.loaded = true; return this; } diff --git a/modules/core/test/webgl/classes/index.js b/modules/core/test/webgl/classes/index.js index a48cb2c273..dd99e9ed97 100644 --- a/modules/core/test/webgl/classes/index.js +++ b/modules/core/test/webgl/classes/index.js @@ -12,6 +12,7 @@ import './uniforms.spec'; import './texture.spec'; import './texture-2d.spec'; +import './texture-3d.spec'; import './renderbuffer.spec'; import './framebuffer.spec'; @@ -33,5 +34,4 @@ import './transform-feedback.spec'; /* UNUSED SPECS: texture-2d-array.spec -texture-3d.spec */ diff --git a/modules/core/test/webgl/classes/texture-3d.spec.js b/modules/core/test/webgl/classes/texture-3d.spec.js index e93ff5900f..33f0b013a7 100644 --- a/modules/core/test/webgl/classes/texture-3d.spec.js +++ b/modules/core/test/webgl/classes/texture-3d.spec.js @@ -1,39 +1,55 @@ import test from 'tape-catch'; -import {Texture3D} from 'luma.gl'; +import {Texture3D, Buffer} from 'luma.gl'; import {fixture} from 'test/setup'; test('WebGL#Texture3D construct/delete', t => { - const {gl} = fixture; + const gl = fixture.gl2; - t.throws( - () => new Texture3D(), - /.*WebGLRenderingContext.*/, - 'Texture3D throws on missing gl context' - ); + if (!gl) { + t.comment('WebGL2 not available, skipping tests'); + t.end(); + return; + } - const texture = new Texture3D(gl); + t.throws(() => new Texture3D(), /.*Requires WebGL2.*/, 'Texture3D throws on missing gl context'); + + let texture = new Texture3D(gl); t.ok(texture instanceof Texture3D, 'Texture3D construction successful'); + texture.delete(); + + gl.getError(); // Reset error - t.comment(JSON.stringify(texture.getParameters({keys: true}))); + texture = new Texture3D(gl, { + width: 4, + height: 4, + depth: 4, + data: new Uint8Array(4 * 4 * 4), + format: gl.RED, + dataFormat: gl.R8 + }); + + t.ok(gl.getError() === gl.NO_ERROR, 'Texture3D construction with array produces no errors'); texture.delete(); + t.ok(!gl.isTexture(texture.handle), `Texture GL object was deleted`); t.ok(texture instanceof Texture3D, 'Texture3D delete successful'); - texture.delete(); - t.ok(texture instanceof Texture3D, 'Texture3D repeated delete successful'); + const buffer = new Buffer(gl, new Uint8Array(4 * 4 * 4)); - t.end(); -}); - -test('WebGL#Texture3D buffer update', t => { - const {gl} = fixture; + texture = new Texture3D(gl, { + width: 4, + height: 4, + depth: 4, + data: buffer, + format: gl.RED, + dataFormat: gl.R8 + }); - let texture = new Texture3D(gl); - t.ok(texture instanceof Texture3D, 'Texture3D construction successful'); + t.ok(gl.getError() === gl.NO_ERROR, 'Texture3D construction with buffer produces no errors'); - texture = texture.delete(); - t.ok(texture instanceof Texture3D, 'Texture3D delete successful'); + texture.delete(); + buffer.delete(); t.end(); }); diff --git a/modules/shadertools/src/index.js b/modules/shadertools/src/index.js index 3b911c6523..f6770651dd 100644 --- a/modules/shadertools/src/index.js +++ b/modules/shadertools/src/index.js @@ -26,6 +26,7 @@ export { export {default as fp32} from './modules/fp32/fp32'; export {default as fp64} from './modules/fp64/fp64'; export {default as project} from './modules/project/project'; +export {default as legacyLighting} from './modules/legacy-lighting/lighting'; export {default as lighting} from './modules/lighting/lighting'; export {default as dirlight} from './modules/dirlight/dirlight'; export {default as picking} from './modules/picking/picking'; diff --git a/modules/shadertools/src/lib/assemble-shaders.js b/modules/shadertools/src/lib/assemble-shaders.js index eb064290d8..1bb50df51e 100644 --- a/modules/shadertools/src/lib/assemble-shaders.js +++ b/modules/shadertools/src/lib/assemble-shaders.js @@ -51,6 +51,13 @@ function assembleShader( coreSource = sourceLines.slice(1).join('\n'); } + // Combine Module and Application Defines + const allDefines = {}; + modules.forEach(module => { + Object.assign(allDefines, module.getDefines()); + }); + Object.assign(allDefines, defines); + // Add platform defines (use these to work around platform-specific bugs and limitations) // Add common defines (GLSL version compatibility, feature detection) // Add precision declaration for fragment shaders @@ -58,9 +65,10 @@ function assembleShader( ? `\ ${versionLine} ${getShaderName({id, source, type})} +${getShaderType({type})} ${getPlatformShaderDefines(gl)} ${getVersionDefines(gl, glslVersion, !isVertex)} -${getApplicationDefines(defines)} +${getApplicationDefines(allDefines)} ${isVertex ? '' : FRAGMENT_SHADER_PROLOGUE} ` : `${versionLine} @@ -120,6 +128,12 @@ function assembleModuleMap(modules) { return result; } +function getShaderType({type}) { + return ` +#define SHADER_TYPE_${SHADER_TYPE[type].toUpperCase()} +`; +} + // Generate "glslify-compatible" SHADER_NAME defines // These are understood by the GLSL error parsing function // If id is provided and no SHADER_NAME constant is present in source, create one diff --git a/modules/shadertools/src/lib/shader-module.js b/modules/shadertools/src/lib/shader-module.js index fd2850ab9e..3d6e751b63 100644 --- a/modules/shadertools/src/lib/shader-module.js +++ b/modules/shadertools/src/lib/shader-module.js @@ -12,6 +12,7 @@ export default class ShaderModule { dependencies = [], getUniforms = () => ({}), deprecations = [], + defines = {}, // DEPRECATED vertexShader, fragmentShader @@ -23,6 +24,7 @@ export default class ShaderModule { this.getModuleUniforms = getUniforms; this.dependencies = dependencies; this.deprecations = this._parseDeprecationDefinitions(deprecations); + this.defines = defines; } // Extracts the source code chunk for the specified shader type from the named shader module @@ -55,6 +57,10 @@ ${moduleSource}\ return this.getModuleUniforms(opts, uniforms); } + getDefines() { + return this.defines; + } + // Warn about deprecated uniforms or functions checkDeprecations(shaderSource, log) { this.deprecations.forEach(def => { diff --git a/modules/shadertools/src/modules/lighting/README.md b/modules/shadertools/src/modules/legacy-lighting/README.md similarity index 100% rename from modules/shadertools/src/modules/lighting/README.md rename to modules/shadertools/src/modules/legacy-lighting/README.md diff --git a/modules/shadertools/src/modules/lighting/lighting-common.glsl.js b/modules/shadertools/src/modules/legacy-lighting/lighting-common.glsl.js similarity index 100% rename from modules/shadertools/src/modules/lighting/lighting-common.glsl.js rename to modules/shadertools/src/modules/legacy-lighting/lighting-common.glsl.js diff --git a/modules/shadertools/src/modules/lighting/lighting-fragment.glsl.js b/modules/shadertools/src/modules/legacy-lighting/lighting-fragment.glsl.js similarity index 100% rename from modules/shadertools/src/modules/lighting/lighting-fragment.glsl.js rename to modules/shadertools/src/modules/legacy-lighting/lighting-fragment.glsl.js diff --git a/modules/shadertools/src/modules/lighting/lighting-vertex.glsl.js b/modules/shadertools/src/modules/legacy-lighting/lighting-vertex.glsl.js similarity index 100% rename from modules/shadertools/src/modules/lighting/lighting-vertex.glsl.js rename to modules/shadertools/src/modules/legacy-lighting/lighting-vertex.glsl.js diff --git a/modules/shadertools/src/modules/legacy-lighting/lighting.js b/modules/shadertools/src/modules/legacy-lighting/lighting.js new file mode 100644 index 0000000000..e618bc782f --- /dev/null +++ b/modules/shadertools/src/modules/legacy-lighting/lighting.js @@ -0,0 +1,96 @@ +import {Vector3} from 'math.gl'; + +import commonShader from './lighting-common.glsl'; +import vertexShader1 from './lighting-vertex.glsl'; +import fragmentShader1 from './lighting-fragment.glsl'; + +export const vertexShader = `\ +${commonShader} +${vertexShader1} +`; + +export const fragmentShader = `\ +${commonShader} +${fragmentShader1} +`; + +export const name = 'lighting'; + +export const config = { + MAX_POINT_LIGHTS: 4 +}; + +// Setup the lighting system: ambient, directional, point lights. +export function getUniforms({ + lightingEnable = false, + + // ambient light + lightingAmbientColor = [0.2, 0.2, 0.2], + + // directional light + lightingDirection = [1, 1, 1], + lightingDirectionalColor = [0, 0, 0], + + // point lights + lightingPointLights = [] +}) { + // Set light uniforms. Ambient, directional and point lights. + return Object.assign( + { + lightingEnable, + // Ambient + lightingAmbientColor + }, + getDirectionalUniforms(lightingDirection), + getPointUniforms(lightingPointLights) + ); +} + +function getDirectionalUniforms({color, direction}) { + // Normalize lighting direction vector + const dir = new Vector3(direction.x, direction.y, direction.z).normalize().scale(-1, -1, -1); + + return { + directionalColor: [color.r, color.g, color.b], + lightingDirection: [dir.x, dir.y, dir.z] + }; +} + +function getPointUniforms(points) { + points = points instanceof Array ? points : [points]; + const numberPoints = points.length; + const pointLocations = []; + const pointColors = []; + const enableSpecular = []; + const pointSpecularColors = []; + for (const point of points) { + const {position, color, diffuse, specular} = point; + const pointColor = color || diffuse; + + pointLocations.push(position.x, position.y, position.z); + pointColors.push(pointColor.r, pointColor.g, pointColor.b); + + // Add specular color + enableSpecular.push(Number(Boolean(specular))); + if (specular) { + pointSpecularColors.push(specular.r, specular.g, specular.b); + } else { + pointSpecularColors.push(0, 0, 0); + } + } + + return { + numberPoints, + pointLocation: pointLocations, + pointColor: pointColors, + enableSpecular, + pointSpecularColor: pointSpecularColors + }; +} + +export default { + name, + vs: vertexShader, + fs: fragmentShader, + getUniforms +}; diff --git a/modules/shadertools/src/modules/lighting/lighting.glsl.js b/modules/shadertools/src/modules/lighting/lighting.glsl.js new file mode 100644 index 0000000000..be89cd9843 --- /dev/null +++ b/modules/shadertools/src/modules/lighting/lighting.glsl.js @@ -0,0 +1,36 @@ +export default `\ +#if (defined(SHADER_TYPE_FRAGMENT) && defined(LIGHTING_FRAGMENT)) || (defined(SHADER_TYPE_VERTEX) && defined(LIGHTING_VERTEX)) + +struct AmbientLight { + vec3 color; +}; + +struct PointLight { + vec3 color; + vec3 position; + + // Constant-Linear-Exponential + vec3 attenuation; +}; + +struct DirectionalLight { + vec3 color; + vec3 direction; +}; + +uniform AmbientLight lighting_uAmbientLight; +uniform PointLight lighting_uPointLight[MAX_LIGHTS]; +uniform DirectionalLight lighting_uDirectionalLight[MAX_LIGHTS]; +uniform int lighting_uPointLightCount; +uniform int lighting_uDirectionalLightCount; + +uniform bool lighting_uEnabled; + +float getPointLightAttenuation(PointLight pointLight, float distance) { + return pointLight.attenuation.x + + pointLight.attenuation.y * distance + + pointLight.attenuation.z * distance * distance; +} + +#endif +`; diff --git a/modules/shadertools/src/modules/lighting/lighting.js b/modules/shadertools/src/modules/lighting/lighting.js index e618bc782f..b8177a01b7 100644 --- a/modules/shadertools/src/modules/lighting/lighting.js +++ b/modules/shadertools/src/modules/lighting/lighting.js @@ -1,96 +1,66 @@ -import {Vector3} from 'math.gl'; +import lightingShader from './lighting.glsl'; -import commonShader from './lighting-common.glsl'; -import vertexShader1 from './lighting-vertex.glsl'; -import fragmentShader1 from './lighting-fragment.glsl'; - -export const vertexShader = `\ -${commonShader} -${vertexShader1} -`; - -export const fragmentShader = `\ -${commonShader} -${fragmentShader1} -`; - -export const name = 'lighting'; - -export const config = { - MAX_POINT_LIGHTS: 4 +export default { + name: 'lighting', + vs: lightingShader, + fs: lightingShader, + getUniforms, + defines: { + MAX_LIGHTS: 3 + } }; -// Setup the lighting system: ambient, directional, point lights. -export function getUniforms({ - lightingEnable = false, - - // ambient light - lightingAmbientColor = [0.2, 0.2, 0.2], +const INITIAL_MODULE_OPTIONS = {}; - // directional light - lightingDirection = [1, 1, 1], - lightingDirectionalColor = [0, 0, 0], - - // point lights - lightingPointLights = [] -}) { - // Set light uniforms. Ambient, directional and point lights. - return Object.assign( - { - lightingEnable, - // Ambient - lightingAmbientColor - }, - getDirectionalUniforms(lightingDirection), - getPointUniforms(lightingPointLights) - ); +// Take color 0-255 and intensity as input and output 0.0-1.0 range +function convertColor({color = [0, 0, 0], intensity = 1.0} = {}) { + return color.map(component => (component * intensity) / 255.0); } -function getDirectionalUniforms({color, direction}) { - // Normalize lighting direction vector - const dir = new Vector3(direction.x, direction.y, direction.z).normalize().scale(-1, -1, -1); +function getLightSourceUniforms({ambientLight, pointLights = [], directionalLights = []}) { + const lightSourceUniforms = {}; + + if (ambientLight) { + lightSourceUniforms['lighting_uAmbientLight.color'] = convertColor(ambientLight); + } else { + lightSourceUniforms['lighting_uAmbientLight.color'] = [0, 0, 0]; + } - return { - directionalColor: [color.r, color.g, color.b], - lightingDirection: [dir.x, dir.y, dir.z] - }; + pointLights.forEach((pointLight, index) => { + lightSourceUniforms[`lighting_uPointLight[${index}].color`] = convertColor(pointLight); + lightSourceUniforms[`lighting_uPointLight[${index}].position`] = pointLight.position; + lightSourceUniforms[`lighting_uPointLight[${index}].attenuation`] = pointLight.attenuation; + }); + lightSourceUniforms.lighting_uPointLightCount = pointLights.length; + + directionalLights.forEach((directionalLight, index) => { + lightSourceUniforms[`lighting_uDirectionalLight[${index}].color`] = convertColor( + directionalLight + ); + lightSourceUniforms[`lighting_uDirectionalLight[${index}].direction`] = + directionalLight.direction; + }); + lightSourceUniforms.lighting_uDirectionalLightCount = directionalLights.length; + + return lightSourceUniforms; } -function getPointUniforms(points) { - points = points instanceof Array ? points : [points]; - const numberPoints = points.length; - const pointLocations = []; - const pointColors = []; - const enableSpecular = []; - const pointSpecularColors = []; - for (const point of points) { - const {position, color, diffuse, specular} = point; - const pointColor = color || diffuse; +function getUniforms(opts = INITIAL_MODULE_OPTIONS) { + if (!('lightSources' in opts)) { + return {}; + } - pointLocations.push(position.x, position.y, position.z); - pointColors.push(pointColor.r, pointColor.g, pointColor.b); + const {ambientLight, pointLights, directionalLights} = opts.lightSources; + const hasLights = + ambientLight || + (pointLights && pointLights.length > 0) || + (directionalLights && directionalLights.length > 0); - // Add specular color - enableSpecular.push(Number(Boolean(specular))); - if (specular) { - pointSpecularColors.push(specular.r, specular.g, specular.b); - } else { - pointSpecularColors.push(0, 0, 0); - } + if (!hasLights) { + return {lighting_uEnabled: false}; } - return { - numberPoints, - pointLocation: pointLocations, - pointColor: pointColors, - enableSpecular, - pointSpecularColor: pointSpecularColors - }; + return Object.assign({}, getLightSourceUniforms({ambientLight, pointLights, directionalLights}), { + lighting_uEnabled: true + }); } - -export default { - name, - vs: vertexShader, - fs: fragmentShader, - getUniforms -}; diff --git a/modules/shadertools/src/modules/pbr/pbr-fragment.glsl.js b/modules/shadertools/src/modules/pbr/pbr-fragment.glsl.js index ec0543f530..d5985f4e4e 100644 --- a/modules/shadertools/src/modules/pbr/pbr-fragment.glsl.js +++ b/modules/shadertools/src/modules/pbr/pbr-fragment.glsl.js @@ -12,9 +12,6 @@ export default `\ precision highp float; -uniform vec3 u_LightDirection; -uniform vec3 u_LightColor; - #ifdef USE_IBL uniform samplerCube u_DiffuseEnvSampler; uniform samplerCube u_SpecularEnvSampler; @@ -85,6 +82,8 @@ struct PBRInfo float alphaRoughness; // roughness mapped to a more linear change in the roughness (proposed by [2]) vec3 diffuseColor; // color contribution from diffuse lighting vec3 specularColor; // color contribution from specular lighting + vec3 n; // normal at surface point + vec3 v; // vector from surface point to camera }; const float M_PI = 3.141592653589793; @@ -216,6 +215,43 @@ float microfacetDistribution(PBRInfo pbrInputs) return roughnessSq / (M_PI * f * f); } +void PBRInfo_setAmbientLight(inout PBRInfo pbrInputs) { + pbrInputs.NdotL = 1.0; + pbrInputs.NdotH = 0.0; + pbrInputs.LdotH = 0.0; + pbrInputs.VdotH = 1.0; +} + +void PBRInfo_setDirectionalLight(inout PBRInfo pbrInputs, vec3 lightDirection) { + vec3 n = pbrInputs.n; + vec3 v = pbrInputs.v; + vec3 l = normalize(lightDirection); // Vector from surface point to light + vec3 h = normalize(l+v); // Half vector between both l and v + + pbrInputs.NdotL = clamp(dot(n, l), 0.001, 1.0); + pbrInputs.NdotH = clamp(dot(n, h), 0.0, 1.0); + pbrInputs.LdotH = clamp(dot(l, h), 0.0, 1.0); + pbrInputs.VdotH = clamp(dot(v, h), 0.0, 1.0); +} + +void PBRInfo_setPointLight(inout PBRInfo pbrInputs, PointLight pointLight) { + vec3 light_direction = normalize(pointLight.position - pbr_vPosition); + PBRInfo_setDirectionalLight(pbrInputs, light_direction); +} + +vec3 calculateFinalColor(PBRInfo pbrInputs, vec3 lightColor) { + // Calculate the shading terms for the microfacet specular shading model + vec3 F = specularReflection(pbrInputs); + float G = geometricOcclusion(pbrInputs); + float D = microfacetDistribution(pbrInputs); + + // Calculation of analytical lighting contribution + vec3 diffuseContrib = (1.0 - F) * diffuse(pbrInputs); + vec3 specContrib = F * G * D / (4.0 * pbrInputs.NdotL * pbrInputs.NdotV); + // Obtain final intensity as reflectance (BRDF) scaled by the energy of the light (cosine law) + return pbrInputs.NdotL * lightColor * (diffuseContrib + specContrib); +} + vec4 pbr_filterColor(vec4 colorUnused) { // Metallic and Roughness material properties are packed together @@ -265,43 +301,45 @@ vec4 pbr_filterColor(vec4 colorUnused) vec3 specularEnvironmentR0 = specularColor.rgb; vec3 specularEnvironmentR90 = vec3(1.0, 1.0, 1.0) * reflectance90; - vec3 n = getNormal(); // normal at surface point - vec3 v = normalize(u_Camera - pbr_vPosition); // Vector from surface point to camera - vec3 l = normalize(u_LightDirection); // Vector from surface point to light - vec3 h = normalize(l+v); // Half vector between both l and v - vec3 reflection = -normalize(reflect(v, n)); + vec3 n = getNormal(); // normal at surface point + vec3 v = normalize(u_Camera - pbr_vPosition); // Vector from surface point to camera - float NdotL = clamp(dot(n, l), 0.001, 1.0); float NdotV = clamp(abs(dot(n, v)), 0.001, 1.0); - float NdotH = clamp(dot(n, h), 0.0, 1.0); - float LdotH = clamp(dot(l, h), 0.0, 1.0); - float VdotH = clamp(dot(v, h), 0.0, 1.0); + vec3 reflection = -normalize(reflect(v, n)); PBRInfo pbrInputs = PBRInfo( - NdotL, + 0.0, // NdotL NdotV, - NdotH, - LdotH, - VdotH, + 0.0, // NdotH + 0.0, // LdotH + 0.0, // VdotH perceptualRoughness, metallic, specularEnvironmentR0, specularEnvironmentR90, alphaRoughness, diffuseColor, - specularColor + specularColor, + n, + v ); - // Calculate the shading terms for the microfacet specular shading model - vec3 F = specularReflection(pbrInputs); - float G = geometricOcclusion(pbrInputs); - float D = microfacetDistribution(pbrInputs); + // Apply ambient light + PBRInfo_setAmbientLight(pbrInputs); + vec3 color = calculateFinalColor(pbrInputs, lighting_uAmbientLight.color); - // Calculation of analytical lighting contribution - vec3 diffuseContrib = (1.0 - F) * diffuse(pbrInputs); - vec3 specContrib = F * G * D / (4.0 * NdotL * NdotV); - // Obtain final intensity as reflectance (BRDF) scaled by the energy of the light (cosine law) - vec3 color = NdotL * u_LightColor * (diffuseContrib + specContrib); + // Apply directional light + for(int i = 0; i < lighting_uDirectionalLightCount; i++) { + PBRInfo_setDirectionalLight(pbrInputs, lighting_uDirectionalLight[i].direction); + color += calculateFinalColor(pbrInputs, lighting_uDirectionalLight[i].color); + } + + // Apply point light + for(int i = 0; i < lighting_uPointLightCount; i++) { + PBRInfo_setPointLight(pbrInputs, lighting_uPointLight[i]); + float attenuation = getPointLightAttenuation(lighting_uPointLight[i], distance(lighting_uPointLight[i].position, pbr_vPosition)); + color += calculateFinalColor(pbrInputs, lighting_uPointLight[i].color / attenuation); + } // Calculate lighting contribution from image based lighting source (IBL) #ifdef USE_IBL @@ -322,12 +360,14 @@ vec4 pbr_filterColor(vec4 colorUnused) // This section uses mix to override final color for reference app visualization // of various parameters in the lighting equation. #ifdef PBR_DEBUG - color = mix(color, F, u_ScaleFGDSpec.x); - color = mix(color, vec3(G), u_ScaleFGDSpec.y); - color = mix(color, vec3(D), u_ScaleFGDSpec.z); - color = mix(color, specContrib, u_ScaleFGDSpec.w); + // TODO: Figure out how to debug multiple lights + + // color = mix(color, F, u_ScaleFGDSpec.x); + // color = mix(color, vec3(G), u_ScaleFGDSpec.y); + // color = mix(color, vec3(D), u_ScaleFGDSpec.z); + // color = mix(color, specContrib, u_ScaleFGDSpec.w); - color = mix(color, diffuseContrib, u_ScaleDiffBaseMR.x); + // color = mix(color, diffuseContrib, u_ScaleDiffBaseMR.x); color = mix(color, baseColor.rgb, u_ScaleDiffBaseMR.y); color = mix(color, vec3(metallic), u_ScaleDiffBaseMR.z); color = mix(color, vec3(perceptualRoughness), u_ScaleDiffBaseMR.w); diff --git a/modules/shadertools/src/modules/pbr/pbr.js b/modules/shadertools/src/modules/pbr/pbr.js index 0a7b73e46e..8156c85f5c 100644 --- a/modules/shadertools/src/modules/pbr/pbr.js +++ b/modules/shadertools/src/modules/pbr/pbr.js @@ -1,4 +1,5 @@ import project2 from '../project2/project2'; +import lighting from '../lighting/lighting'; import vs from './pbr-vertex.glsl'; import fs from './pbr-fragment.glsl'; @@ -7,6 +8,9 @@ export default { name: 'pbr', vs, fs, - dependencies: [project2] + defines: { + LIGHTING_FRAGMENT: 1 + }, + dependencies: [project2, lighting] // getUniforms }; diff --git a/modules/shadertools/src/modules/phong-lighting/phong-lighting.glsl.js b/modules/shadertools/src/modules/phong-lighting/phong-lighting.glsl.js index 29f28d79c4..a63957bc5e 100644 --- a/modules/shadertools/src/modules/phong-lighting/phong-lighting.glsl.js +++ b/modules/shadertools/src/modules/phong-lighting/phong-lighting.glsl.js @@ -1,23 +1,5 @@ export default `\ -#define MAX_LIGHTS 5 -struct AmbientLight { - vec3 color; - float intensity; -}; - -struct PointLight { - vec3 color; - float intensity; - vec3 position; -}; - -struct DirectionalLight { - vec3 color; - float intensity; - vec3 direction; -}; - uniform AmbientLight lighting_uAmbientLight; uniform PointLight lighting_uPointLight[MAX_LIGHTS]; uniform DirectionalLight lighting_uDirectionalLight[MAX_LIGHTS]; @@ -31,7 +13,7 @@ uniform vec3 lighting_uSpecularColor; uniform bool lighting_uEnabled; -vec3 lighting_getLightColor(vec3 surfaceColor, vec3 light_direction, vec3 view_direction, vec3 normal_worldspace, float intensity) { +vec3 lighting_getLightColor(vec3 surfaceColor, vec3 light_direction, vec3 view_direction, vec3 normal_worldspace, vec3 color) { vec3 halfway_direction = normalize(light_direction + view_direction); float lambertian = dot(light_direction, normal_worldspace); float specular = 0.0; @@ -40,7 +22,7 @@ vec3 lighting_getLightColor(vec3 surfaceColor, vec3 light_direction, vec3 view_d specular = pow(specular_angle, lighting_uShininess); } lambertian = max(lambertian, 0.0); - return (lambertian * lighting_uDiffuse * surfaceColor + specular * lighting_uSpecularColor) * intensity; + return (lambertian * lighting_uDiffuse * surfaceColor + specular * lighting_uSpecularColor) * color; } vec3 lighting_getLightColor(vec3 surfaceColor, vec3 cameraPosition, vec3 position_worldspace, vec3 normal_worldspace) { @@ -48,7 +30,7 @@ vec3 lighting_getLightColor(vec3 surfaceColor, vec3 cameraPosition, vec3 positio if (lighting_uEnabled) { vec3 view_direction = normalize(cameraPosition - position_worldspace); - lightColor = lighting_uAmbient * surfaceColor * lighting_uAmbientLight.intensity; + lightColor = lighting_uAmbient * surfaceColor * lighting_uAmbientLight.color; for (int i = 0; i < MAX_LIGHTS; i++) { if (i >= lighting_uPointLightCount) { @@ -57,7 +39,7 @@ vec3 lighting_getLightColor(vec3 surfaceColor, vec3 cameraPosition, vec3 positio PointLight pointLight = lighting_uPointLight[i]; vec3 light_position_worldspace = pointLight.position; vec3 light_direction = normalize(light_position_worldspace - position_worldspace); - lightColor += lighting_getLightColor(surfaceColor, light_direction, view_direction, normal_worldspace, pointLight.intensity); + lightColor += lighting_getLightColor(surfaceColor, light_direction, view_direction, normal_worldspace, pointLight.color); } for (int i = 0; i < MAX_LIGHTS; i++) { @@ -65,7 +47,7 @@ vec3 lighting_getLightColor(vec3 surfaceColor, vec3 cameraPosition, vec3 positio break; } DirectionalLight directionalLight = lighting_uDirectionalLight[i]; - lightColor += lighting_getLightColor(surfaceColor, -directionalLight.direction, view_direction, normal_worldspace, directionalLight.intensity); + lightColor += lighting_getLightColor(surfaceColor, -directionalLight.direction, view_direction, normal_worldspace, directionalLight.color); } } return lightColor; @@ -85,7 +67,7 @@ vec3 lighting_getSpecularLightColor(vec3 cameraPosition, vec3 position_worldspac PointLight pointLight = lighting_uPointLight[i]; vec3 light_position_worldspace = pointLight.position; vec3 light_direction = normalize(light_position_worldspace - position_worldspace); - lightColor += lighting_getLightColor(surfaceColor, light_direction, view_direction, normal_worldspace, pointLight.intensity); + lightColor += lighting_getLightColor(surfaceColor, light_direction, view_direction, normal_worldspace, pointLight.color); } for (int i = 0; i < MAX_LIGHTS; i++) { @@ -93,7 +75,7 @@ vec3 lighting_getSpecularLightColor(vec3 cameraPosition, vec3 position_worldspac break; } DirectionalLight directionalLight = lighting_uDirectionalLight[i]; - lightColor += lighting_getLightColor(surfaceColor, -directionalLight.direction, view_direction, normal_worldspace, directionalLight.intensity); + lightColor += lighting_getLightColor(surfaceColor, -directionalLight.direction, view_direction, normal_worldspace, directionalLight.color); } } return lightColor; diff --git a/modules/shadertools/src/modules/phong-lighting/phong-lighting.js b/modules/shadertools/src/modules/phong-lighting/phong-lighting.js index 0097d81eb2..9bf05305ea 100644 --- a/modules/shadertools/src/modules/phong-lighting/phong-lighting.js +++ b/modules/shadertools/src/modules/phong-lighting/phong-lighting.js @@ -1,49 +1,18 @@ +import lighting from '../lighting/lighting'; import lightingShader from './phong-lighting.glsl'; export default { - name: 'lighting', + name: 'phong-lighting', + dependencies: [lighting], vs: lightingShader, + defines: { + LIGHTING_VERTEX: 1 + }, getUniforms }; const INITIAL_MODULE_OPTIONS = {}; -function getLightSourceUniforms({ambientLight, pointLights, directionalLights}) { - const lightSourceUniforms = {}; - - if (ambientLight) { - lightSourceUniforms['lighting_uAmbientLight.color'] = ambientLight.color; - lightSourceUniforms['lighting_uAmbientLight.intensity'] = ambientLight.intensity; - } else { - lightSourceUniforms['lighting_uAmbientLight.color'] = [0, 0, 0]; - lightSourceUniforms['lighting_uAmbientLight.intensity'] = 0.0; - } - - let index = 0; - for (const i in pointLights) { - const pointLight = pointLights[i]; - lightSourceUniforms[`lighting_uPointLight[${index}].color`] = pointLight.color; - lightSourceUniforms[`lighting_uPointLight[${index}].intensity`] = pointLight.intensity; - lightSourceUniforms[`lighting_uPointLight[${index}].position`] = pointLight.position; - index++; - } - lightSourceUniforms.lighting_uPointLightCount = pointLights.length; - - index = 0; - for (const i in directionalLights) { - const directionalLight = directionalLights[i]; - lightSourceUniforms[`lighting_uDirectionalLight[${index}].color`] = directionalLight.color; - lightSourceUniforms[`lighting_uDirectionalLight[${index}].intensity`] = - directionalLight.intensity; - lightSourceUniforms[`lighting_uDirectionalLight[${index}].direction`] = - directionalLight.direction; - index++; - } - lightSourceUniforms.lighting_uDirectionalLightCount = directionalLights.length; - - return lightSourceUniforms; -} - function getMaterialUniforms(material) { const materialUniforms = {}; materialUniforms.lighting_uAmbient = material.ambient; @@ -54,27 +23,15 @@ function getMaterialUniforms(material) { } function getUniforms(opts = INITIAL_MODULE_OPTIONS) { - if (!('lightSources' in opts && 'material' in opts)) { + if (!('material' in opts)) { return {}; } - const {ambientLight, pointLights, directionalLights} = opts.lightSources; const {material} = opts; - const hasLights = - ambientLight || - (pointLights && pointLights.length > 0) || - (directionalLights && directionalLights.length > 0); - if (!hasLights || !material) { + if (!material) { return {lighting_uEnabled: false}; } - const lightUniforms = Object.assign( - {}, - getLightSourceUniforms({ambientLight, pointLights, directionalLights}), - getMaterialUniforms(material), - {lighting_uEnabled: true} - ); - - return lightUniforms; + return Object.assign({}, getMaterialUniforms(material), {lighting_uEnabled: true}); } diff --git a/package.json b/package.json index 59d3c08154..4720905ea3 100644 --- a/package.json +++ b/package.json @@ -50,9 +50,9 @@ "devDependencies": { "@loaders.gl/draco": "^0.8.0", "@loaders.gl/gltf": "^0.8.1", - "@probe.gl/bench": "^3.0.0-alpha.6", - "@probe.gl/stats-widget": "^3.0.0-alpha.6", - "@probe.gl/test-utils": "^3.0.0-alpha.6", + "@probe.gl/bench": "^3.0.0-alpha.7", + "@probe.gl/stats-widget": "^3.0.0-alpha.7", + "@probe.gl/test-utils": "^3.0.0-alpha.7", "babel-loader": "^8.0.0", "babel-plugin-inline-webgl-constants": "^1.0.0", "babel-plugin-remove-glsl-comments": "^1.0.0", diff --git a/yarn.lock b/yarn.lock index cb382bfc13..72ec270a97 100644 --- a/yarn.lock +++ b/yarn.lock @@ -766,29 +766,29 @@ resolved "https://registry.yarnpkg.com/@luma.gl/glfx/-/glfx-6.3.0.tgz#08929e3488b58ddf339c6df0627282543d78c66a" integrity sha512-6k3hTQefv2Fawy+yEnsdqQDm7eCT5eIQ6Q4iA+WM6agmTpNaRCQw61sNJEjpZYYl3ppxR92wjHvObYzcUbVxNQ== -"@probe.gl/bench@^3.0.0-alpha.6": - version "3.0.0-alpha.6" - resolved "https://registry.yarnpkg.com/@probe.gl/bench/-/bench-3.0.0-alpha.6.tgz#45914319384772c880c9e5c755af6570a3251f32" - integrity sha512-6XfQWptrMKj4XWOplDfO7VMcBda+tJG6xgA1vi/YprPe7Ot5DcQqs02ShkwBU/CPouGO5F8ZwNxR4NQ4AfT6ug== +"@probe.gl/bench@^3.0.0-alpha.7": + version "3.0.0-alpha.7" + resolved "https://registry.yarnpkg.com/@probe.gl/bench/-/bench-3.0.0-alpha.7.tgz#f6371a3e0bb38054de8689f839205154e7371081" + integrity sha512-RUIr+kh4rMKIBEvmHXNYyU2FDOXCkHHovbQg7bcUxxK20UlMaKiRXJdNTs0v6pV61J/iZd9LZlqudF9P+ZhWyA== dependencies: "@babel/runtime" "^7.0.0" - probe.gl "^3.0.0-alpha.6" + probe.gl "^3.0.0-alpha.7" -"@probe.gl/stats-widget@^3.0.0-alpha.6": - version "3.0.0-alpha.6" - resolved "https://registry.yarnpkg.com/@probe.gl/stats-widget/-/stats-widget-3.0.0-alpha.6.tgz#509436828f8330f096acdb3e8ad1c78d173c67fb" - integrity sha512-IErtTALHTnZdgixeMnWzERF5mNa9OVjcct59PcpD7ERe66C8A6SKIJMyidKXDt8QY7IXC49YpjOs9ZTdG8m02w== +"@probe.gl/stats-widget@^3.0.0-alpha.7": + version "3.0.0-alpha.7" + resolved "https://registry.yarnpkg.com/@probe.gl/stats-widget/-/stats-widget-3.0.0-alpha.7.tgz#51e4ad584e3823f874814d5db4d32165723ff619" + integrity sha512-JX3JqJXYO8BNa85HxJ5sh62s3aEXLPNrUASoYi1sHuCBiXTgql0pDADONT6jW7O/JwtWhvxYq5RFdvyNfFI/lA== dependencies: "@babel/runtime" "^7.0.0" -"@probe.gl/test-utils@^3.0.0-alpha.6": - version "3.0.0-alpha.6" - resolved "https://registry.yarnpkg.com/@probe.gl/test-utils/-/test-utils-3.0.0-alpha.6.tgz#03f375a41c591849cb93834fa98879667a748f66" - integrity sha512-DM4ppBSIuktRllWCvEOJL0vsTt7SENcmv6aXMRk6QH/+CUpJ//zujYNRSgNGHstfOMMKOE1QRhP7cNXHxFWWEQ== +"@probe.gl/test-utils@^3.0.0-alpha.7": + version "3.0.0-alpha.7" + resolved "https://registry.yarnpkg.com/@probe.gl/test-utils/-/test-utils-3.0.0-alpha.7.tgz#0b09d4083cccbc5096b602ad57c34ecd12a2ade7" + integrity sha512-hNMwO9Ca7Uoert02mtPpctryDn4q93nzMVkDxVyhZd35KJCswBd13qjFa3ezAVKDszNOcA1+uqbLt9Nw2/hF6g== dependencies: "@babel/runtime" "^7.0.0" pixelmatch "^4.0.2" - probe.gl "^3.0.0-alpha.6" + probe.gl "^3.0.0-alpha.7" puppeteer "1.8.0" "@webassemblyjs/ast@1.8.5": @@ -6330,10 +6330,10 @@ private@^0.1.6: resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== -probe.gl@3.0.0-alpha.6, probe.gl@^3.0.0-alpha.6: - version "3.0.0-alpha.6" - resolved "https://registry.yarnpkg.com/probe.gl/-/probe.gl-3.0.0-alpha.6.tgz#1658518b19f577c85bdcd20629db396bbffa6552" - integrity sha512-WgIj1LnM4wBNOMAkF/titGvK/ykkB5q4X7VcOzTYNNhyYMhSYQxWZBS3MR4eri7xEviZZmBAUtkT5ez8DutUYg== +probe.gl@3.0.0-alpha.7, probe.gl@^3.0.0-alpha.7: + version "3.0.0-alpha.7" + resolved "https://registry.yarnpkg.com/probe.gl/-/probe.gl-3.0.0-alpha.7.tgz#c8dae03faa322af7cb0ac6681884ae641768f117" + integrity sha512-t1zCnD4iAsw2j7ir4DdmRtFS+t/XLbHg6f/znK4qcj4QBhdOd+lrnnH0bUvBILAaJeQz2jSxQz0oqMPcr87c8Q== dependencies: "@babel/runtime" "^7.0.0"