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 support for Cube Map Rotation and expose Skybox Rotation #2527

Merged
merged 20 commits into from
Nov 10, 2020
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions examples/graphics/material-physical-cubemap-rotation.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<!DOCTYPE html>
<html>
<head>
<title>PlayCanvas Physical Material</title>
raytranuk marked this conversation as resolved.
Show resolved Hide resolved
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link rel="icon" type="image/png" href="../playcanvas-favicon.png" />
<script src="../../build/playcanvas.js"></script>
<script src="../../build/playcanvas-extras.js"></script>
<style>
body {
margin: 0;
overflow-y: hidden;
}
</style>
</head>

<body>
<!-- The canvas element -->
<canvas id="application-canvas"></canvas>

<!-- The script -->
<script>
var canvas = document.getElementById("application-canvas");

// Create the application and start the update loop
var app = new pc.Application(canvas);
app.start();

// 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);

window.addEventListener("resize", function () {
app.resizeCanvas(canvas.width, canvas.height);
});

var miniStats = new pcx.MiniStats(app);

app.scene.gammaCorrection = pc.GAMMA_SRGB;
app.scene.toneMapping = pc.TONEMAP_ACES;
// Set the skybox to the 128x128 cubemap mipmap level
app.scene.skyboxMip = 0;

// Create an entity with a camera component
var camera = new pc.Entity();
camera.addComponent("camera");
camera.translate(0, 0, 9);
app.root.addChild(camera);

// Load a cubemap asset. This DDS file was 'prefiltered' in the
// PlayCanvas Editor and then downloaded.
var cubemapAsset = new pc.Asset('helipad.dds', 'cubemap', {
url: "../assets/cubemaps/helipad.dds"
}, {
type: pc.TEXTURETYPE_RGBM
});
app.assets.add(cubemapAsset);
app.assets.load(cubemapAsset);
cubemapAsset.ready(function () {
app.scene.setSkybox(cubemapAsset.resources);
});

var NUM_SPHERES = 5;

var entity;

var createSphere = function (x, y, z) {
var material = new pc.StandardMaterial();
material.metalness = (x===2 && y===2) ? 1.0 : y / (NUM_SPHERES - 1);
material.shininess = (x===2 && y===2) ? 100 : x / (NUM_SPHERES - 1) * 100;
material.useMetalness = true;
material.update();

var sphere = new pc.Entity();
sphere.addComponent("model", {
material: material,
type: (x===2 && y===2) ? "box" : "sphere"
});
sphere.setLocalPosition(x - (NUM_SPHERES - 1) * 0.5, y - (NUM_SPHERES - 1) * 0.5, z);
sphere.setLocalScale(0.9, 0.9, 0.9);
app.root.addChild(sphere);
entity = (x===2 && y===2) ? sphere : entity;
};

var createText = function (fontAsset, message, x, y, z, rot) {
// Create a text element-based entity
var text = new pc.Entity();
text.addComponent("element", {
anchor: [0.5, 0.5, 0.5, 0.5],
fontAsset: fontAsset,
fontSize: 0.5,
pivot: [0.5, 0.5],
text: message,
type: pc.ELEMENTTYPE_TEXT
});
text.setLocalPosition(x, y, z);
text.setLocalEulerAngles(0, 0, rot);
app.root.addChild(text);
};

for (var i = 0; i < NUM_SPHERES; i++) {
for (var j = 0; j < NUM_SPHERES; j++) {
createSphere(j, i, 0);
}
}

// Load a font
var fontAsset = new pc.Asset('arial.json', "font", { url: "../assets/fonts/arial.json" });
fontAsset.on('load', function () {
createText(fontAsset, 'Glossiness', 0, -(NUM_SPHERES + 1) * 0.5, 0, 0);
createText(fontAsset, 'Metalness', -(NUM_SPHERES + 1) * 0.5, 0, 0, 90);
});
app.assets.add(fontAsset);
app.assets.load(fontAsset);

var mouse = new pc.Mouse(document.body);

var x = 0;
var y = 0;
var rot = new pc.Quat();
var rotMat4 = new pc.Mat4();
var rotMat3 = new pc.Mat3();

mouse.on('mousemove', function (event) {
if (event.buttons[pc.MOUSEBUTTON_LEFT]) {
x += event.dx;
y += event.dy;

// entity.setLocalEulerAngles(0.2 * y, 0.2 * x, 0);
raytranuk marked this conversation as resolved.
Show resolved Hide resolved

rot.setFromEulerAngles(0.2 * y, 0.2 * x, 0);
rotMat4.setTRS(pc.Vec3.ZERO, rot, pc.Vec3.ONE);
rotMat4.invertTo3x3(rotMat3);
rotMat3.data[0]*=-1.0;
rotMat3.data[1]*=-1.0;
rotMat3.data[2]*=-1.0;

app.scene.skyboxRotationMatrix = rotMat3;
}
});
</script>
</body>
</html>
2 changes: 2 additions & 0 deletions src/framework/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -1695,6 +1695,7 @@ Object.assign(Application.prototype, {
* @param {number|null} [settings.render.skybox] - The asset ID of the cube map texture to be used as the scene's skybox. Defaults to null.
* @param {number} settings.render.skyboxIntensity - Multiplier for skybox intensity.
* @param {number} settings.render.skyboxMip - The mip level of the skybox to be displayed. Only valid for prefiltered cubemap skyboxes.
* @param {pc.Mat3} settings.render.skyboxRotationMatrix - Rotation of skybox.
raytranuk marked this conversation as resolved.
Show resolved Hide resolved
* @param {number} settings.render.lightmapSizeMultiplier - The lightmap resolution multiplier.
* @param {number} settings.render.lightmapMaxResolution - The maximum lightmap resolution.
* @param {number} settings.render.lightmapMode - The lightmap baking mode. Can be:
Expand All @@ -1719,6 +1720,7 @@ Object.assign(Application.prototype, {
* fog_start: 1,
* global_ambient: [0, 0, 0],
* skyboxIntensity: 1,
* skyboxRotationMatrix: [-1, 0, 0, 0, 1, 0, 0, 0, 1],
* fog_color: [0, 0, 0],
* lightmapMode: 1,
* fog: 'none',
Expand Down
4 changes: 4 additions & 0 deletions src/graphics/program-lib/chunks/ambientPrefilteredCube.frag
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ uniform samplerCube texture_prefilteredCubeMap4;
#endif

void addAmbient() {
#ifdef CUBEMAPROT
vec3 fixedReflDir = fixSeamsStatic(dNormalW*cubeMapRotationMatrix, 1.0 - 1.0 / 4.0);
#else
vec3 fixedReflDir = fixSeamsStatic(dNormalW, 1.0 - 1.0 / 4.0);
fixedReflDir.x *= -1.0;
raytranuk marked this conversation as resolved.
Show resolved Hide resolved
#endif
dDiffuseLight += processEnvironment($DECODE(textureCube(texture_prefilteredCubeMap4, fixedReflDir)).rgb);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ uniform samplerCube texture_prefilteredCubeMap128;
#endif

void addAmbient() {
#ifdef CUBEMAPROT
vec3 fixedReflDir = fixSeamsStatic(dNormalW*cubeMapRotationMatrix, 1.0 - 1.0 / 4.0);
#else
vec3 fixedReflDir = fixSeamsStatic(dNormalW, 1.0 - 1.0 / 4.0);
fixedReflDir.x *= -1.0;
#endif
dDiffuseLight += processEnvironment($DECODE( textureCubeLodEXT(texture_prefilteredCubeMap128, fixedReflDir, 5.0) ).rgb);
}
5 changes: 4 additions & 1 deletion src/graphics/program-lib/chunks/ambientSH.frag
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
uniform vec3 ambientSH[9];

void addAmbient() {
#ifdef CUBEMAPROT
vec3 n = (dNormalW*cubeMapRotationMatrix)*vec3(-1,1,1);
#else
vec3 n = dNormalW;

#endif
vec3 color =
ambientSH[0] +
ambientSH[1] * n.x +
Expand Down
2 changes: 2 additions & 0 deletions src/graphics/program-lib/chunks/chunks.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ import skinTexVS from './skinTex.vert';
import skyboxPS from './skybox.frag';
import skyboxVS from './skybox.vert';
import skyboxHDRPS from './skyboxHDR.frag';
import skyboxHDRRotPS from './skyboxHDRRot.frag';
import skyboxPrefilteredCubePS from './skyboxPrefilteredCube.frag';
import specularPS from './specular.frag';
import specularAaNonePS from './specularAaNone.frag';
Expand Down Expand Up @@ -365,6 +366,7 @@ var shaderChunks = {
skyboxPS: skyboxPS,
skyboxVS: skyboxVS,
skyboxHDRPS: skyboxHDRPS,
skyboxHDRRotPS: skyboxHDRRotPS,
skyboxPrefilteredCubePS: skyboxPrefilteredCubePS,
specularPS: specularPS,
specularAaNonePS: specularAaNonePS,
Expand Down
6 changes: 5 additions & 1 deletion src/graphics/program-lib/chunks/cubeMapProjectBox.frag
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
uniform vec3 envBoxMin, envBoxMax;

vec3 cubeMapProject(vec3 nrdir) {
#ifdef CUBEMAPROT
nrdir = (nrdir*cubeMapRotationMatrix)*vec3(-1,1,1);
#endif
vec3 rbmax = (envBoxMax - vPositionW) / nrdir;
vec3 rbmin = (envBoxMin - vPositionW) / nrdir;

Expand All @@ -13,5 +16,6 @@ vec3 cubeMapProject(vec3 nrdir) {

vec3 posonbox = vPositionW + nrdir * fa;
vec3 envBoxPos = (envBoxMin + envBoxMax) * 0.5;
return posonbox - envBoxPos;

return (posonbox - envBoxPos)*vec3(-1,1,1);
}
6 changes: 5 additions & 1 deletion src/graphics/program-lib/chunks/cubeMapProjectNone.frag
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
vec3 cubeMapProject(vec3 dir) {
return dir;
#ifdef CUBEMAPROT
return dir*cubeMapRotationMatrix;
#else
return dir*vec3(-1,1,1);
#endif
}
1 change: 0 additions & 1 deletion src/graphics/program-lib/chunks/reflectionCube.frag
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ uniform float material_reflectivity;

vec3 calcReflection(vec3 tReflDirW, float tGlossiness) {
vec3 lookupVec = fixSeams(cubeMapProject(tReflDirW));
lookupVec.x *= -1.0;
return $textureCubeSAMPLE(texture_cubeMap, lookupVec).rgb;
}

Expand Down
2 changes: 1 addition & 1 deletion src/graphics/program-lib/chunks/reflectionDpAtlas.frag
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ vec2 getDpAtlasUv(vec2 uv, float mip) {
}

vec3 calcReflection(vec3 tReflDirW, float tGlossiness) {
vec3 reflDir = normalize(cubeMapProject(tReflDirW));
vec3 reflDir = normalize(cubeMapProject(tReflDirW)*vec3(-1,1,1));
raytranuk marked this conversation as resolved.
Show resolved Hide resolved

// Convert vector to DP coords
bool up = reflDir.y > 0.0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ vec3 calcReflection(vec3 tReflDirW, float tGlossiness) {

float bias = saturate(1.0 - tGlossiness) * 5.0; // multiply by max mip level
vec3 mirror = vec3(-1.0, 1.0, 1.0);
vec3 refl = cubeMapProject(tReflDirW);
vec3 refl = cubeMapProject(tReflDirW)*vec3(-1,1,1);
vec4 cubes0;
vec4 cubes1;
if (bias < 1.0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ uniform float material_reflectivity;
vec3 calcReflection(vec3 tReflDirW, float tGlossiness) {
float bias = saturate(1.0 - tGlossiness) * 5.0; // multiply by max mip level
vec3 fixedReflDir = fixSeams(cubeMapProject(tReflDirW), bias);
fixedReflDir.x *= -1.0;

vec3 refl = processEnvironment($DECODE( textureCubeLodEXT(texture_prefilteredCubeMap128, fixedReflDir, bias) ).rgb);

Expand Down
11 changes: 11 additions & 0 deletions src/graphics/program-lib/chunks/skyboxHDRRot.frag
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
varying vec3 vViewDir;

uniform samplerCube texture_cubeMap;
uniform mat3 cubeMapRotationMatrix;

void main(void) {
vec3 color = processEnvironment($textureCubeSAMPLE(texture_cubeMap, fixSeamsStatic((vViewDir*vec3(-1,1,1))*cubeMapRotationMatrix, $FIXCONST)).rgb);
raytranuk marked this conversation as resolved.
Show resolved Hide resolved
color = toneMap(color);
color = gammaCorrectOutput(color);
gl_FragColor = vec4(color, 1.0);
}
5 changes: 3 additions & 2 deletions src/graphics/program-lib/programs/skybox.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { gammaCode, precisionCode, tonemapCode } from './common.js';
var skybox = {
generateKey: function (options) {
var key = "skybox" + options.rgbm + " " + options.hdr + " " + options.fixSeams + "" +
options.toneMapping + "" + options.gamma + "" + options.useIntensity + "" + options.mip;
options.toneMapping + "" + options.gamma + "" + options.useIntensity + "" + options.useRotation + "" + options.mip;
return key;
},

Expand All @@ -20,7 +20,8 @@ var skybox = {
fshader += gammaCode(options.gamma);
fshader += tonemapCode(options.toneMapping);
fshader += shaderChunks.rgbmPS;
fshader += shaderChunks.skyboxHDRPS
var skyboxChunkPS = options.useRotation ? shaderChunks.skyboxHDRRotPS : shaderChunks.skyboxHDRPS;
fshader += skyboxChunkPS
.replace(/\$textureCubeSAMPLE/g, options.rgbm ? "textureCubeRGBM" : (options.hdr ? "textureCube" : "textureCubeSRGB"))
.replace(/\$FIXCONST/g, (1 - 1 / mip2size[options.mip]) + "");

Expand Down
7 changes: 7 additions & 0 deletions src/graphics/program-lib/programs/standard.js
Original file line number Diff line number Diff line change
Expand Up @@ -1109,6 +1109,13 @@ var standard = {
code += options.fixSeams ? chunks.fixCubemapSeamsStretchPS : chunks.fixCubemapSeamsNonePS;
}

if (options.cubeMapRotationMatrix && (options.cubeMapRotationMatrix.data[0] !== -1.0 || options.cubeMapRotationMatrix.data[4] !== 1.0 || options.cubeMapRotationMatrix.data[8] !== 1.0)) {
code += "#ifndef CUBEMAPROT\n";
code += "#define CUBEMAPROT\n";
code += "uniform mat3 cubeMapRotationMatrix;\n";
code += "#endif\n";
}

if (needsNormal) {
code += options.cubeMapProjection > 0 ? chunks.cubeMapProjectBoxPS : chunks.cubeMapProjectNonePS;
code += options.skyboxIntensity ? chunks.envMultiplyPS : chunks.envConstPS;
Expand Down
5 changes: 5 additions & 0 deletions src/graphics/texture.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import {
* @param {string} [options.type] - Specifies the image type, see {@link pc.TEXTURETYPE_DEFAULT}
* @param {boolean} [options.fixCubemapSeams] - Specifies whether this cubemap texture requires special
* seam fixing shader code to look right. Defaults to false.
* @param {pc.Mat3} [options.cubeMapRotationMatrix] - specifies cubemap rotation matrix
* @param {boolean} [options.flipY] - Specifies whether the texture should be flipped in the Y-direction. Only affects textures
* with a source that is an image, canvas or video element. Does not affect cubemaps, compressed textures or textures set from raw
* pixel data. Defaults to true.
Expand Down Expand Up @@ -119,6 +120,8 @@ function Texture(graphicsDevice, options) {
this._flipY = true;
this._premultiplyAlpha = false;

this.cubeMapRotationMatrix = null;

this._mipmaps = true;

this._minFilter = FILTER_LINEAR_MIPMAP_LINEAR;
Expand Down Expand Up @@ -169,6 +172,8 @@ function Texture(graphicsDevice, options) {
this._cubemap = (options.cubemap !== undefined) ? options.cubemap : this._cubemap;
this.fixCubemapSeams = (options.fixCubemapSeams !== undefined) ? options.fixCubemapSeams : this.fixCubemapSeams;

this.cubeMapRotationMatrix = options.cubeMapRotationMatrix !== undefined ? options.cubeMapRotationMatrix : null;

this._minFilter = (options.minFilter !== undefined) ? options.minFilter : this._minFilter;
this._magFilter = (options.magFilter !== undefined) ? options.magFilter : this._magFilter;
this._anisotropy = (options.anisotropy !== undefined) ? options.anisotropy : this._anisotropy;
Expand Down
1 change: 1 addition & 0 deletions src/scene/materials/standard-material-options-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ StandardMaterialOptionsBuilder.prototype._updateEnvOptions = function (options,
options.fixSeams = prefilteredCubeMap128 ? prefilteredCubeMap128.fixCubemapSeams : (stdMat.cubeMap ? stdMat.cubeMap.fixCubemapSeams : false);
options.prefilteredCubemap = !!prefilteredCubeMap128;
options.skyboxIntensity = (prefilteredCubeMap128 && globalSky128 && prefilteredCubeMap128 === globalSky128) && (scene.skyboxIntensity !== 1);
options.cubeMapRotationMatrix = stdMat.cubeMap ? stdMat.cubeMap.cubeMapRotationMatrix : scene.skyboxRotationMatrix;
};

StandardMaterialOptionsBuilder.prototype._updateLightOptions = function (options, stdMat, objDefs, sortedLights, staticLightList) {
Expand Down
9 changes: 9 additions & 0 deletions src/scene/materials/standard-material.js
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ import { standardMaterialCubemapParameters, standardMaterialTextureParameters }
* * fastTbn: Use slightly cheaper normal mapping code (skip tangent space normalization). Can look buggy sometimes.
* * refraction: if refraction is used.
* * skyboxIntensity: if reflected skybox intensity should be modulated.
* * cubeMapRotationMatrix: cube Map Rotation.
* * useTexCubeLod: if textureCubeLodEXT function should be used to read prefiltered cubemaps. Usually true of iOS, false on other devices due to quality/performance balance.
* * useInstancing: if hardware instancing compatible shader should be generated. Transform is read from per-instance {@link pc.VertexBuffer} instead of shader's uniforms.
* * useMorphPosition: if morphing code should be generated to morph positions.
Expand Down Expand Up @@ -949,13 +950,15 @@ Object.assign(StandardMaterial.prototype, {
var useDp = !device.extTextureLod; // no basic extension? likely slow device, force dp

var globalSky128, globalSky64, globalSky32, globalSky16, globalSky8, globalSky4;
var globalSkyRotationMatrix;
if (this.useSkybox) {
globalSky128 = scene._skyboxPrefiltered[0];
globalSky64 = scene._skyboxPrefiltered[1];
globalSky32 = scene._skyboxPrefiltered[2];
globalSky16 = scene._skyboxPrefiltered[3];
globalSky8 = scene._skyboxPrefiltered[4];
globalSky4 = scene._skyboxPrefiltered[5];
globalSkyRotationMatrix = scene._skyboxRotationMatrix;
}

var prefilteredCubeMap128 = this.prefilteredCubeMap128 || globalSky128;
Expand All @@ -965,6 +968,8 @@ Object.assign(StandardMaterial.prototype, {
var prefilteredCubeMap8 = this.prefilteredCubeMap8 || globalSky8;
var prefilteredCubeMap4 = this.prefilteredCubeMap4 || globalSky4;

var cubeMapRotationMatrix = this.cubeMap ? this.cubeMap.cubeMapRotationMatrix : globalSkyRotationMatrix;

if (prefilteredCubeMap128) {
var allMips = prefilteredCubeMap128 &&
prefilteredCubeMap64 &&
Expand Down Expand Up @@ -1007,6 +1012,10 @@ Object.assign(StandardMaterial.prototype, {
} else {
console.log("Can't use prefiltered cubemap: " + allMips + ", " + useTexCubeLod + ", " + prefilteredCubeMap128._levels);
}

if (cubeMapRotationMatrix && (cubeMapRotationMatrix.data[0] !== -1.0 || cubeMapRotationMatrix.data[4] !== 1.0 || cubeMapRotationMatrix.data[8] !== 1.0)) {
this._setParameter('cubeMapRotationMatrix', cubeMapRotationMatrix.data);
}
}

// Minimal options for Depth and Shadow passes
Expand Down
Loading