From b7b4c7e56ade52be01d997a5088f034fe321933f Mon Sep 17 00:00:00 2001 From: Ib Green Date: Fri, 10 Nov 2023 13:03:45 -0500 Subject: [PATCH] fix(website): improve example code --- .../shadertools/modules/goraud.md | 0 .../shadertools/modules/lights.md | 23 ++++ docs/api-reference/shadertools/modules/pbr.md | 12 +- .../shadertools/modules/phong.md | 0 examples/tutorials/lighting/app.ts | 108 +++++++++++------- .../core/src/lib/uniforms/uniform-block.ts | 21 ++-- modules/core/src/lib/utils/array-equal.ts | 9 ++ .../engine/src/animation-loop/render-loop.ts | 2 +- .../lighting/lights/lighting-uniforms.glsl.ts | 4 +- .../phong-material/phong-gouraud.glsl.ts | 56 ++++++++- .../resources/webgl-render-pipeline.ts | 79 +++++++------ 11 files changed, 223 insertions(+), 91 deletions(-) create mode 100644 docs/api-reference/shadertools/modules/goraud.md create mode 100644 docs/api-reference/shadertools/modules/lights.md create mode 100644 docs/api-reference/shadertools/modules/phong.md diff --git a/docs/api-reference/shadertools/modules/goraud.md b/docs/api-reference/shadertools/modules/goraud.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docs/api-reference/shadertools/modules/lights.md b/docs/api-reference/shadertools/modules/lights.md new file mode 100644 index 0000000000..5c8400bee9 --- /dev/null +++ b/docs/api-reference/shadertools/modules/lights.md @@ -0,0 +1,23 @@ +# lights (Shader Module) + +The `lights`` shader module collects uniforms describing the lights in a scene. +No view dependent uniforms are includes so the resulting uniform block should be reusable for all draw calls in a scene. + +## Material modules + +Actual lighting computations are done by the various material shader modules, such as: + +- `phongMaterial` +- `goraudmaterial` +- `pbrMaterial` + +Different draw calls can use different material uniform buffers and/or different material modules. + +All material modules depend on this lighting module and base lighting calculations +on the lights defined by this module, meaning that the same lighting uniform buffer can be +bound. + +## Defining lights + +The lights module lets the application define the ambient light color and a number of additional lights that can either be point lights (e.g, a light bulb) or directional lights (e.g. sunlight). + diff --git a/docs/api-reference/shadertools/modules/pbr.md b/docs/api-reference/shadertools/modules/pbr.md index 282acac44b..92ee6dd0aa 100644 --- a/docs/api-reference/shadertools/modules/pbr.md +++ b/docs/api-reference/shadertools/modules/pbr.md @@ -1,12 +1,18 @@ # PBR (Shader Module) -An implementation of PBR (Physically-Based Rendering) based on the [Khronos Reference Implementation](https://github.com/KhronosGroup/glTF-WebGL-PBR). +Implements Physically Based Shading of a microfacet surface defined by a glTF material. -A reference implementation for Physically Based Shading of a microfacet surface defined by a glTF material. +Lighting is expected to be defined by the `lights` module. -References: + +## References - [Real Shading in Unreal Engine 4](http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf) - [Physically Based Shading at Disney](http://blog.selfshadow.com/publications/s2012-shading-course/burley/s2012_pbs_disney_brdf_notes_v3.pdf) - [README.md - Environment Maps](https://github.com/KhronosGroup/glTF-WebGL-PBR/#environment-maps) - ["An Inexpensive BRDF Model for Physically based Rendering" by Christophe Schlick](https://www.cs.virginia.edu/~jdl/bib/appearance/analytic%20models/schlick94b.pdf) + +## Attribution + +This implementation of PBR (Physically-Based Rendering) is a fork of the [Khronos Reference Implementation](https://github.com/KhronosGroup/glTF-WebGL-PBR) under the Apache 2.0 license. + diff --git a/docs/api-reference/shadertools/modules/phong.md b/docs/api-reference/shadertools/modules/phong.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/tutorials/lighting/app.ts b/examples/tutorials/lighting/app.ts index 4acc554303..11093ee88f 100644 --- a/examples/tutorials/lighting/app.ts +++ b/examples/tutorials/lighting/app.ts @@ -10,16 +10,16 @@ Drawing a phong-shaded cube `; type AppUniforms = { - uModel: NumberArray; - uMVP: NumberArray; - uEyePosition: NumberArray; + modelMatrix: NumberArray; + mvpMatrix: NumberArray; + eyePosition: NumberArray; }; -const appUniforms: {uniformTypes: Record} = { +const app: {uniformTypes: Record} = { uniformTypes: { - uMVP: 'mat4x4', - uModel: 'mat4x4', - uEyePosition: 'vec3' + modelMatrix: 'mat4x4', + mvpMatrix: 'mat4x4', + eyePosition: 'vec3' } }; @@ -34,16 +34,16 @@ const vs = glsl`\ varying vec2 vUV; uniform appUniforms { - mat4 uModel; - mat4 uMVP; - vec3 uEyePosition; - } app; + mat4 modelMatrix; + mat4 mvpMatrix; + vec3 eyePosition; + } uApp; void main(void) { - vPosition = (app.uModel * vec4(positions, 1.0)).xyz; - vNormal = mat3(app.uModel) * normals; + vPosition = (uApp.modelMatrix * vec4(positions, 1.0)).xyz; + vNormal = mat3(uApp.modelMatrix) * normals; vUV = texCoords; - gl_Position = app.uMVP * vec4(positions, 1.0); + gl_Position = uApp.mvpMatrix * vec4(positions, 1.0); } `; @@ -58,14 +58,14 @@ const fs = glsl`\ uniform sampler2D uTexture; uniform appUniforms { - mat4 uModel; - mat4 uMVP; - vec3 uEyePosition; - } app; + mat4 modelMatrix; + mat4 mvpMatrix; + vec3 eyePosition; + } uApp; void main(void) { vec3 materialColor = texture2D(uTexture, vec2(vUV.x, 1.0 - vUV.y)).rgb; - vec3 surfaceColor = lighting_getLightColor(materialColor, app.uEyePosition, vPosition, normalize(vNormal)); + vec3 surfaceColor = lighting_getLightColor(materialColor, uApp.eyePosition, vPosition, normalize(vNormal)); gl_FragColor = vec4(surfaceColor, 1.0); } @@ -79,12 +79,12 @@ export default class AppAnimationLoopTemplate extends AnimationLoopTemplate { static info = INFO_HTML; uniformStore = new UniformStore<{ - app: AppUniforms, - lighting: typeof lighting['defaultUniforms'], - phongMaterial: typeof phongMaterial['defaultUniforms'] + app: AppUniforms; + lighting: (typeof lighting)['defaultUniforms']; + phongMaterial: (typeof phongMaterial)['defaultUniforms']; }>({ - app: appUniforms, - lighting, + app, + lighting, phongMaterial }); @@ -92,21 +92,17 @@ export default class AppAnimationLoopTemplate extends AnimationLoopTemplate { modelMatrix = new Matrix4(); viewMatrix = new Matrix4().lookAt({eye: eyePosition}); mvpMatrix = new Matrix4(); - + constructor({device}: AnimationProps) { super(); - const texture = device.createTexture({data: 'vis-logo.png'}); + // Set up static uniforms - this.model = new Model(device, { - vs, - fs, - geometry: new CubeGeometry(), - modules: [phongMaterial], - moduleSettings: { - material: { - specularColor: [255, 255, 255] - }, + this.uniformStore.setUniforms({ + phongMaterial: { + specularColor: [255, 255, 255] + }, + lighting: lighting.getUniforms({ lights: [ { type: 'ambient', @@ -118,6 +114,35 @@ export default class AppAnimationLoopTemplate extends AnimationLoopTemplate { position: [1, 2, 1] } ] + }), + app: { + modelMatrix: this.modelMatrix, + eyePosition + } + }); + + const texture = device.createTexture({data: 'vis-logo.png'}); + + this.model = new Model(device, { + vs, + fs, + geometry: new CubeGeometry(), + modules: [phongMaterial], + moduleSettings: { + // material: { + // specularColor: [255, 255, 255] + // }, + // lights: [ + // { + // type: 'ambient', + // color: [255, 255, 255] + // }, + // { + // type: 'point', + // color: [255, 255, 255], + // position: [1, 2, 1] + // } + // ] }, bindings: { uTexture: texture, @@ -125,18 +150,16 @@ export default class AppAnimationLoopTemplate extends AnimationLoopTemplate { lightingUniforms: this.uniformStore.getManagedUniformBuffer(device, 'lighting'), phongMaterialUniforms: this.uniformStore.getManagedUniformBuffer(device, 'phongMaterial') }, - uniforms: { - uEyePosition: eyePosition - }, parameters: { depthWriteEnabled: true, - depthCompare: 'less-equal' + depthCompare: 'less-equal' } }); } onFinalize() { this.model.destroy(); + this.uniformStore.destroy(); } onRender({device, aspect, tick}) { @@ -151,11 +174,14 @@ export default class AppAnimationLoopTemplate extends AnimationLoopTemplate { .multiplyRight(this.modelMatrix); this.uniformStore.setUniforms({ - app: {uMVP: this.mvpMatrix, uModel: this.modelMatrix} + app: { + mvpMatrix: this.mvpMatrix, + modelMatrix: this.modelMatrix + } }); const renderPass = device.beginRenderPass({ - clearColor: [0, 0, 0, 1], + clearColor: [0, 0, 0, 1], clearDepth: true }); this.model.draw(renderPass); diff --git a/modules/core/src/lib/uniforms/uniform-block.ts b/modules/core/src/lib/uniforms/uniform-block.ts index f7d3b9025f..a9c759428f 100644 --- a/modules/core/src/lib/uniforms/uniform-block.ts +++ b/modules/core/src/lib/uniforms/uniform-block.ts @@ -1,10 +1,14 @@ // luma.gl, MIT license import type {ShaderUniformType} from '../../adapter/types/shader-types'; import type {UniformValue} from '../../adapter/types/types'; -import {ShaderLayout, UniformBufferBindingLayout, UniformInfo} from '../../adapter/types/shader-layout'; -import { arrayEqual } from '../utils/array-equal'; +import { + ShaderLayout, + UniformInfo, + UniformBufferBindingLayout +} from '../../adapter/types/shader-layout'; +import {arrayEqual, arrayCopy} from '../utils/array-equal'; -/** +/** * A uniform block holds values of the of uniform values for one uniform block / buffer. * It also does some book keeping on what has changed, to minimize unnecessary writes to uniform buffers. * @todo - Track changes to individual uniforms (for WebGL1) @@ -21,15 +25,16 @@ export class UniformBlock> { constructor(props?: { name?: string; - shaderLayout?: ShaderLayout; - uniformTypes?: Record> + shaderLayout?: ShaderLayout; + uniformTypes?: Record>; }) { this.name = props?.name; // TODO - Extract uniform layout from the shaderLayout object if (props?.name && props?.shaderLayout) { - const binding = props?.shaderLayout.bindings - ?.find(binding => binding.type === 'uniform' && binding.name === props?.name); + const binding = props?.shaderLayout.bindings?.find( + binding => binding.type === 'uniform' && binding.name === props?.name + ); if (!binding) { throw new Error(props?.name); } @@ -68,7 +73,7 @@ export class UniformBlock> { if (arrayEqual(this.uniforms[key], value)) { return; } - this.uniforms[key] = value; + this.uniforms[key] = arrayCopy(value); this.modifiedUniforms[key] = true; this.modified = true; } diff --git a/modules/core/src/lib/utils/array-equal.ts b/modules/core/src/lib/utils/array-equal.ts index 9d2058d899..31233af676 100644 --- a/modules/core/src/lib/utils/array-equal.ts +++ b/modules/core/src/lib/utils/array-equal.ts @@ -22,3 +22,12 @@ export function arrayEqual(a: unknown, b: unknown, limit: number = 16) { } return true; } + +/** Copy a value */ +export function arrayCopy(a: T): T { + const numberArray = isNumberArray(a); + if (numberArray) { + return numberArray.slice() as T; + } + return a; +} diff --git a/modules/engine/src/animation-loop/render-loop.ts b/modules/engine/src/animation-loop/render-loop.ts index 49fd93217a..75e181e8ba 100644 --- a/modules/engine/src/animation-loop/render-loop.ts +++ b/modules/engine/src/animation-loop/render-loop.ts @@ -16,7 +16,7 @@ import type {AnimationProps} from './animation-props'; * as an argument to create an AnimationLoop. */ export abstract class AnimationLoopTemplate { - constructor(animationProps?: AnimationProps) {} + constructor(animationProps: AnimationProps) {} async onInitialize(animationProps: AnimationProps): Promise { return null; } abstract onRender(animationProps: AnimationProps): unknown; abstract onFinalize(animationProps: AnimationProps): void; diff --git a/modules/shadertools/src/modules-ubo/lighting/lights/lighting-uniforms.glsl.ts b/modules/shadertools/src/modules-ubo/lighting/lights/lighting-uniforms.glsl.ts index 9160d04a8f..a32483c80e 100644 --- a/modules/shadertools/src/modules-ubo/lighting/lights/lighting-uniforms.glsl.ts +++ b/modules/shadertools/src/modules-ubo/lighting/lights/lighting-uniforms.glsl.ts @@ -41,11 +41,11 @@ uniform lightingUniforms { } lighting; PointLight lighting_getPointLight(int index) { - return PointLight(lightColor, lightPosition, lightAttenuation); + return PointLight(lighting.lightColor, lighting.lightPosition, lighting.lightAttenuation); } DirectionalLight lighting_getDirectionalLight(int index) { - return DirectionalLight(lightColor, lightDirection); + return DirectionalLight(lighting.lightColor, lighting.lightDirection); } float getPointLightAttenuation(PointLight pointLight, float distance) { diff --git a/modules/shadertools/src/modules-ubo/lighting/phong-material/phong-gouraud.glsl.ts b/modules/shadertools/src/modules-ubo/lighting/phong-material/phong-gouraud.glsl.ts index 4c9a2b5bf7..de7d03e070 100644 --- a/modules/shadertools/src/modules-ubo/lighting/phong-material/phong-gouraud.glsl.ts +++ b/modules/shadertools/src/modules-ubo/lighting/phong-material/phong-gouraud.glsl.ts @@ -34,8 +34,18 @@ vec3 lighting_getLightColor(vec3 surfaceColor, vec3 cameraPosition, vec3 positio if (lighting.enabled) { vec3 view_direction = normalize(cameraPosition - position_worldspace); - lightColor = material.ambient * surfaceColor * lighting.ambientLight.color; + lightColor = material.ambient * surfaceColor * lighting.ambientColor; + if (lighting.lightType == 0) { + PointLight pointLight = lighting_getPointLight(0); + 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.color); + } else if (lighting.lightType == 1) { + DirectionalLight directionalLight = lighting_getDirectionalLight(0); + lightColor += lighting_getLightColor(surfaceColor, -directionalLight.direction, view_direction, normal_worldspace, directionalLight.color); + } + /* for (int i = 0; i < MAX_LIGHTS; i++) { if (i >= lighting.pointLightCount) { break; @@ -53,6 +63,7 @@ vec3 lighting_getLightColor(vec3 surfaceColor, vec3 cameraPosition, vec3 positio DirectionalLight directionalLight = lighting.directionalLight[i]; lightColor += lighting_getLightColor(surfaceColor, -directionalLight.direction, view_direction, normal_worldspace, directionalLight.color); } + */ } return lightColor; } @@ -64,6 +75,26 @@ vec3 lighting_getSpecularLightColor(vec3 cameraPosition, vec3 position_worldspac if (lighting.enabled) { vec3 view_direction = normalize(cameraPosition - position_worldspace); + switch (lighting.lightType) { + case 0: + PointLight pointLight = lighting_getPointLight(0); + 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.color); + break; + + case 1: + DirectionalLight directionalLight = lighting_getDirectionalLight(0); + lightColor += lighting_getLightColor(surfaceColor, -directionalLight.direction, view_direction, normal_worldspace, directionalLight.color); + break; + } + } + return lightColor; +} +`; + +// TODO - handle multiple lights +/** for (int i = 0; i < MAX_LIGHTS; i++) { if (i >= lighting.pointLightCount) { break; @@ -82,6 +113,23 @@ vec3 lighting_getSpecularLightColor(vec3 cameraPosition, vec3 position_worldspac lightColor += lighting_getLightColor(surfaceColor, -directionalLight.direction, view_direction, normal_worldspace, directionalLight.color); } } - return lightColor; -} -`; + /** + for (int i = 0; i < MAX_LIGHTS; i++) { + if (i >= lighting.pointLightCount) { + break; + } + PointLight pointLight = lighting_getPointLight(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.color); + } + + for (int i = 0; i < MAX_LIGHTS; i++) { + if (i >= lighting.directionalLightCount) { + break; + } + PointLight pointLight = lighting_getDirectionalLight(i); + lightColor += lighting_getLightColor(surfaceColor, -directionalLight.direction, view_direction, normal_worldspace, directionalLight.color); + } + } + */ diff --git a/modules/webgl/src/adapter/resources/webgl-render-pipeline.ts b/modules/webgl/src/adapter/resources/webgl-render-pipeline.ts index 9f144a7d74..0226b119f1 100644 --- a/modules/webgl/src/adapter/resources/webgl-render-pipeline.ts +++ b/modules/webgl/src/adapter/resources/webgl-render-pipeline.ts @@ -133,11 +133,16 @@ export class WEBGLRenderPipeline extends RenderPipeline { for (const [name, value] of Object.entries(bindings)) { const binding = this.shaderLayout.bindings.find(binding => binding.name === name); if (!binding) { - log.warn(`Unknown binding ${name} in render pipeline ${this.id}`)(); + const validBindings = this.shaderLayout.bindings + .map(binding => `"${binding.name}"`) + .join(', '); + log.warn( + `Unknown binding "${name}" in render pipeline "${this.id}", expected one of ${validBindings}` + )(); continue; // eslint-disable-line no-continue } if (!value) { - log.warn(`Unsetting binding ${name} in render pipeline ${this.id}`)(); + log.warn(`Unsetting binding "${name}" in render pipeline "${this.id}"`)(); } switch (binding.type) { case 'uniform': @@ -251,35 +256,40 @@ export class WEBGLRenderPipeline extends RenderPipeline { // } // }); - withDeviceAndGLParameters(this.device, this.props.parameters, webglRenderPass.glParameters, () => { - if (isIndexed && isInstanced) { - // ANGLE_instanced_arrays extension - this.device.gl2?.drawElementsInstanced( - glDrawMode, - vertexCount || 0, // indexCount? - glIndexType, - firstVertex, - instanceCount || 0 - ); - // } else if (isIndexed && this.device.isWebGL2 && !isNaN(start) && !isNaN(end)) { - // this.device.gl2.drawRangeElements(glDrawMode, start, end, vertexCount, glIndexType, offset); - } else if (isIndexed) { - this.device.gl.drawElements(glDrawMode, vertexCount || 0, glIndexType, firstVertex); // indexCount? - } else if (isInstanced) { - this.device.gl2?.drawArraysInstanced( - glDrawMode, - firstVertex, - vertexCount || 0, - instanceCount || 0 - ); - } else { - this.device.gl.drawArrays(glDrawMode, firstVertex, vertexCount || 0); - } - - if (transformFeedback) { - transformFeedback.end(); + withDeviceAndGLParameters( + this.device, + this.props.parameters, + webglRenderPass.glParameters, + () => { + if (isIndexed && isInstanced) { + // ANGLE_instanced_arrays extension + this.device.gl2?.drawElementsInstanced( + glDrawMode, + vertexCount || 0, // indexCount? + glIndexType, + firstVertex, + instanceCount || 0 + ); + // } else if (isIndexed && this.device.isWebGL2 && !isNaN(start) && !isNaN(end)) { + // this.device.gl2.drawRangeElements(glDrawMode, start, end, vertexCount, glIndexType, offset); + } else if (isIndexed) { + this.device.gl.drawElements(glDrawMode, vertexCount || 0, glIndexType, firstVertex); // indexCount? + } else if (isInstanced) { + this.device.gl2?.drawArraysInstanced( + glDrawMode, + firstVertex, + vertexCount || 0, + instanceCount || 0 + ); + } else { + this.device.gl.drawArrays(glDrawMode, firstVertex, vertexCount || 0); + } + + if (transformFeedback) { + transformFeedback.end(); + } } - }); + ); vertexArray.unbindAfterRender(renderPass); @@ -414,8 +424,13 @@ export class WEBGLRenderPipeline extends RenderPipeline { let texture: WEBGLTexture; if (value instanceof WEBGLTexture) { texture = value; - } else if (value instanceof WEBGLFramebuffer && value.colorAttachments[0] instanceof WEBGLTexture) { - log.warn('Passing framebuffer in texture binding may be deprecated. Use fbo.colorAttachments[0] instead')(); + } else if ( + value instanceof WEBGLFramebuffer && + value.colorAttachments[0] instanceof WEBGLTexture + ) { + log.warn( + 'Passing framebuffer in texture binding may be deprecated. Use fbo.colorAttachments[0] instead' + )(); texture = value.colorAttachments[0]; } else { throw new Error('No texture');