Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add example to test normals and tangents (and fix WebGL) #5924

Merged
merged 12 commits into from
Apr 2, 2024
Binary file added examples/assets/models/NormalTangentTest.glb
Binary file not shown.
8 changes: 8 additions & 0 deletions examples/assets/models/NormalTangentTest.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Model Information:
* title: MorphStressTest
* source: https://github.com/KhronosGroup/glTF-Sample-Models/blob/main/2.0/NormalTangentTest/README.md
* author: Ed Mackey

Model License:
* license type: CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/)
* requirements: Author must be credited. Commercial use is allowed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* @type {import('../../../../types.mjs').ExampleConfig}
*/
export default {
HIDDEN: true,
WEBGPU_ENABLED: true
};
111 changes: 111 additions & 0 deletions examples/src/examples/graphics/normals-and-tangents/example.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import * as pc from 'playcanvas';
import { deviceType, rootPath } from '@examples/utils';

const canvas = document.getElementById('application-canvas');
if (!(canvas instanceof HTMLCanvasElement)) {
throw new Error('No canvas found');
}

const assets = {
orbitCamera: new pc.Asset('script', 'script', { url: rootPath + '/static/scripts/camera/orbit-camera.js' }),
helipad: new pc.Asset(
'helipad-env-atlas',
'texture',
{ url: rootPath + '/static/assets/cubemaps/helipad-env-atlas.png' },
{ type: pc.TEXTURETYPE_RGBP, mipmaps: false }
),
model: new pc.Asset('model', 'container', { url: rootPath + '/static/assets/models/NormalTangentTest.glb' })
};

const gfxOptions = {
deviceTypes: [deviceType],
glslangUrl: rootPath + '/static/lib/glslang/glslang.js',
twgslUrl: rootPath + '/static/lib/twgsl/twgsl.js'
};

const device = await pc.createGraphicsDevice(canvas, gfxOptions);
const createOptions = new pc.AppOptions();
createOptions.graphicsDevice = device;
createOptions.mouse = new pc.Mouse(document.body);
createOptions.touch = new pc.TouchDevice(document.body);
createOptions.keyboard = new pc.Keyboard(document.body);

createOptions.componentSystems = [
pc.RenderComponentSystem,
pc.CameraComponentSystem,
pc.LightComponentSystem,
pc.ScriptComponentSystem
];
createOptions.resourceHandlers = [
pc.TextureHandler,
pc.ContainerHandler,
pc.ScriptHandler
];

const app = new pc.AppBase(canvas);
app.init(createOptions);

// Set the canvas to fill the window and automatically change resolution to be the same as the canvas size
app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW);
app.setCanvasResolution(pc.RESOLUTION_AUTO);

// Ensure canvas is resized when window changes size
const resize = () => app.resizeCanvas();
window.addEventListener('resize', resize);
app.on('destroy', () => {
window.removeEventListener('resize', resize);
});

const assetListLoader = new pc.AssetListLoader(Object.values(assets), app.assets);
assetListLoader.load(() => {
app.start();

// Setup skydome
app.scene.envAtlas = assets.helipad.resource;
app.scene.toneMapping = pc.TONEMAP_ACES;
app.scene.skyboxRotation = new pc.Quat().setFromEulerAngles(0, 70, 0);
app.scene.skyboxIntensity = 1.5;

const leftEntity = assets.model.resource.instantiateRenderEntity();
leftEntity.setLocalEulerAngles(0, 90, 0);
leftEntity.setPosition(0, 0, 1);
leftEntity.setLocalScale(0.8, 0.8, 0.8);
app.root.addChild(leftEntity);

const rightEntity = assets.model.resource.instantiateRenderEntity();
rightEntity.setLocalEulerAngles(0, 90, 0);
rightEntity.setPosition(0, 0, -1);
rightEntity.setLocalScale(-0.8, -0.8, -0.8);
app.root.addChild(rightEntity);

// Create a camera with an orbit camera script
const camera = new pc.Entity();
camera.addComponent("camera");
camera.addComponent("script");
camera.script.create("orbitCamera", {
attributes: {
inertiaFactor: 0 // Override default of 0 (no inertia)
}
});
camera.script.create("orbitCameraInputMouse");
camera.script.create("orbitCameraInputTouch");
app.root.addChild(camera);
camera.script.orbitCamera.pitch = 0;
camera.script.orbitCamera.yaw = 90;
camera.script.orbitCamera.distance = 4;

const directionalLight = new pc.Entity();
directionalLight.addComponent("light", {
type: "directional",
color: pc.Color.WHITE,
castShadows: true,
intensity: 1,
shadowBias: 0.2,
normalOffsetBias: 0.05,
shadowResolution: 2048
});
directionalLight.setEulerAngles(45, 180, 0);
app.root.addChild(directionalLight);
});

export { app };
2 changes: 2 additions & 0 deletions src/scene/shader-lib/chunks/chunks.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ import tonemappingNonePS from './common/frag/tonemappingNone.js';
import transformVS from './common/vert/transform.js';
import transformDeclVS from './common/vert/transformDecl.js';
import transmissionPS from './standard/frag/transmission.js';
import twoSidedLightingPS from './lit/frag/twoSidedLighting.js';
import uv0VS from './lit/vert/uv0.js';
import uv1VS from './lit/vert/uv1.js';
import viewDirPS from './lit/frag/viewDir.js';
Expand Down Expand Up @@ -408,6 +409,7 @@ const shaderChunks = {
transformVS,
transformDeclVS,
transmissionPS,
twoSidedLightingPS,
uv0VS,
uv1VS,
viewDirPS,
Expand Down
6 changes: 6 additions & 0 deletions src/scene/shader-lib/chunks/lit/frag/twoSidedLighting.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export default /* glsl */`
uniform float twoSidedLightingNegScaleFactor;
void handleTwoSidedLighting() {
dTBN[2] *= gl_FrontFacing ? twoSidedLightingNegScaleFactor : -twoSidedLightingNegScaleFactor;
}
`;
25 changes: 9 additions & 16 deletions src/scene/shader-lib/programs/lit-shader.js
Original file line number Diff line number Diff line change
Expand Up @@ -748,6 +748,9 @@ class LitShader {
func.append(chunks.TBNObjectSpacePS);
}
}
if (options.twoSidedLighting) {
func.append(chunks.twoSidedLightingPS);
}
}

// FIXME: only add these when needed
Expand Down Expand Up @@ -990,34 +993,24 @@ class LitShader {
func.append(chunks.clusteredLightPS);
}

if (options.twoSidedLighting) {
decl.append("uniform float twoSidedLightingNegScaleFactor;");
}

// FRAGMENT SHADER BODY

code.append(this._fsGetStartCode(code, device, chunks, options));

if (this.needsNormal) {
if (options.twoSidedLighting) {
code.append(" dVertexNormalW = normalize(gl_FrontFacing ? vNormalW * twoSidedLightingNegScaleFactor : -vNormalW * twoSidedLightingNegScaleFactor);");
} else {
code.append(" dVertexNormalW = normalize(vNormalW);");
}
code.append(" dVertexNormalW = normalize(vNormalW);");

if ((options.useHeights || options.useNormals) && options.hasTangents) {
if (options.twoSidedLighting) {
code.append(" dTangentW = gl_FrontFacing ? vTangentW * twoSidedLightingNegScaleFactor : -vTangentW * twoSidedLightingNegScaleFactor;");
code.append(" dBinormalW = gl_FrontFacing ? vBinormalW * twoSidedLightingNegScaleFactor : -vBinormalW * twoSidedLightingNegScaleFactor;");
} else {
code.append(" dTangentW = vTangentW;");
code.append(" dBinormalW = vBinormalW;");
}
code.append(" dTangentW = vTangentW;");
code.append(" dBinormalW = vBinormalW;");
}

code.append(" getViewDir();");
if (hasTBN) {
code.append(" getTBN(dTangentW, dBinormalW, dVertexNormalW);");
if (options.twoSidedLighting) {
code.append(" handleTwoSidedLighting();");
}
}
}

Expand Down