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

[FIX] Adopt top-left texture origin #3335

Merged
merged 12 commits into from Jul 15, 2021
Binary file removed examples/assets/textures/playcanvas.basis
Binary file not shown.
Binary file removed examples/assets/textures/sea.basis
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
2 changes: 1 addition & 1 deletion examples/src/examples/graphics/render-to-texture.tsx
Expand Up @@ -130,7 +130,7 @@ class RenderToTextureExample extends Example {
// create a plane called tv which we use to display rendered texture
// this is only added to excluded Layer, so it does not render into texture
const tv = createPrimitive("plane", new pc.Vec3(6, 8, -5), new pc.Vec3(20, 10, 10), pc.Color.BLACK, [excludedLayer.id]);
tv.setLocalEulerAngles(90, 0, 0);
tv.setLocalEulerAngles(90, 0, 180);
tv.render.castShadows = false;
tv.render.receiveShadows = false;
// @ts-ignore engine-tsd
Expand Down
91 changes: 54 additions & 37 deletions examples/src/examples/graphics/texture-basis.tsx
Expand Up @@ -7,15 +7,21 @@ class TextureBasisExample extends Example {
static CATEGORY = 'Graphics';
static NAME = 'Texture Basis';

// Color textures have been converted with the following arguments:
// basisu seaside-rocks01-gloss.jpg -q 255 -mipmap
// The normalmap has been converted with the following arguments:
// basisu seaside-rocks01-normal.jpg -normal_map -swizzle gggr -renorm -q 255 -mipmap
load() {
return <>
<AssetLoader name='seaBasis' type='texture' url='static/assets/textures/sea.basis' />
<AssetLoader name='playcanvasBasis' type='texture' url='static/assets/textures/playcanvas.basis' />
<AssetLoader name='color' type='texture' url='static/assets/textures/seaside-rocks01-color.basis' />
<AssetLoader name='gloss' type='texture' url='static/assets/textures/seaside-rocks01-gloss.basis' />
<AssetLoader name='normal' type='texture' url='static/assets/textures/seaside-rocks01-normal.basis' data={{ type: pc.TEXTURETYPE_SWIZZLEGGGR }} />
<AssetLoader name='helipad' type='cubemap' url='static/assets/cubemaps/helipad.dds' data={{ type: pc.TEXTURETYPE_RGBM }}/>
</>;
}

// @ts-ignore: override class function
example(canvas: HTMLCanvasElement, assets: { seaBasis: pc.Asset, playcanvasBasis: pc.Asset }): void {
example(canvas: HTMLCanvasElement, assets: { color: pc.Asset, gloss: pc.Asset, normal: pc.Asset, helipad: pc.Asset }): void {

// Create the application and start the update loop
const app = new pc.Application(canvas, {});
Expand All @@ -33,30 +39,48 @@ class TextureBasisExample extends Example {
app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW);
app.setCanvasResolution(pc.RESOLUTION_AUTO);

app.scene.ambientLight = new pc.Color(1, 1, 1);

// material using basis texture
const material1 = new pc.StandardMaterial();
material1.diffuseMap = assets.seaBasis.resource;
material1.update();

// Create a Entity with a Box render component
const box1 = new pc.Entity();
box1.addComponent("render", {
type: "box",
material: material1
// Set skybox
app.scene.gammaCorrection = pc.GAMMA_SRGB;
app.scene.toneMapping = pc.TONEMAP_ACES;
app.scene.skyboxMip = 1;
app.scene.skyboxIntensity = 0.7;
app.scene.setSkybox(assets.helipad.resources);

// Create directional light
const light = new pc.Entity();
light.addComponent('light', {
type: 'directional'
});

// another material using basis texture
const material2 = new pc.StandardMaterial();
material2.diffuseMap = assets.playcanvasBasis.resource;
material2.update();

const box2 = new pc.Entity();
box2.addComponent("render", {
type: "box",
material: material2
light.setLocalEulerAngles(45, 0, 45);

// Construct material
const material = new pc.StandardMaterial();
material.useMetalness = true;
material.diffuse = new pc.Color(0.3, 0.3, 0.3);
material.shininess = 80;
material.metalness = 0.7;
material.diffuseMap = assets.color.resource;
material.normalMap = assets.normal.resource;
material.glossMap = assets.gloss.resource;
material.diffuseMapTiling.set(7, 7);
material.normalMapTiling.set(7, 7);
material.glossMapTiling.set(7, 7);
material.update();

// Create a torus shape
const torus = pc.createTorus(app.graphicsDevice, {
tubeRadius: 0.2,
ringRadius: 0.3,
segments: 50,
sides: 40
});
const shape = new pc.Entity();
shape.addComponent('render', {
material: material,
meshInstances: [new pc.MeshInstance(torus, material)]
});
shape.setPosition(0, 0, 0);
shape.setLocalScale(2, 2, 2);

// Create an Entity with a camera component
const camera = new pc.Entity();
Expand All @@ -65,27 +89,20 @@ class TextureBasisExample extends Example {
});

// Adjust the camera position
camera.translate(0, 0, 5);
camera.translate(0, 0, 4);

// Add the new Entities to the hierarchy
app.root.addChild(box1);
app.root.addChild(box2);
app.root.addChild(light);
app.root.addChild(shape);
app.root.addChild(camera);

box1.setPosition(0, -1, 0);
box2.setPosition(0, 1, 0);

// Set an update function on the app's update event
let angle = 0;
app.on("update", function (dt) {
angle += dt;
if (angle > 360) {
angle = 0;
}
angle = (angle + dt * 10) % 360;

// Rotate the boxes
box1.setEulerAngles(angle * 2, angle * 4, angle * 8);
box2.setEulerAngles(90 - angle * 12, 120 - angle * 8, 150 - angle * 10);
shape.setEulerAngles(angle, angle * 2, angle * 4);
});
}
}
Expand Down
16 changes: 8 additions & 8 deletions src/framework/components/element/image-element.js
Expand Up @@ -406,26 +406,26 @@ class ImageElement {
// POS: 0, 0, 0
vertexDataF32[5] = 1; // NZ
vertexDataF32[6] = r.x; // U
vertexDataF32[7] = r.y; // V
vertexDataF32[7] = 1.0 - r.y; // V

// POS: w, 0, 0
vertexDataF32[8] = w; // PX
vertexDataF32[13] = 1; // NZ
vertexDataF32[14] = r.x + r.z; // U
vertexDataF32[15] = r.y; // V
vertexDataF32[15] = 1.0 - r.y; // V

// POS: w, h, 0
vertexDataF32[16] = w; // PX
vertexDataF32[17] = h; // PY
vertexDataF32[21] = 1; // NZ
vertexDataF32[22] = r.x + r.z; // U
vertexDataF32[23] = r.y + r.w; // V
vertexDataF32[23] = 1.0 - (r.y + r.w); // V

// POS: 0, h, 0
vertexDataF32[25] = h; // PY
vertexDataF32[29] = 1; // NZ
vertexDataF32[30] = r.x; // U
vertexDataF32[31] = r.y + r.w; // V
vertexDataF32[31] = 1.0 - (r.y + r.w); // V

var vertexDesc = [
{ semantic: SEMANTIC_POSITION, components: 3, type: TYPE_FLOAT32 },
Expand Down Expand Up @@ -554,13 +554,13 @@ class ImageElement {

// Update vertex texture coordinates
vertexDataF32[6] = rect.x / atlasTextureWidth;
vertexDataF32[7] = rect.y / atlasTextureHeight;
vertexDataF32[7] = 1.0 - rect.y / atlasTextureHeight;
vertexDataF32[14] = (rect.x + rect.z) / atlasTextureWidth;
vertexDataF32[15] = rect.y / atlasTextureHeight;
vertexDataF32[15] = 1.0 - rect.y / atlasTextureHeight;
vertexDataF32[22] = (rect.x + rect.z) / atlasTextureWidth;
vertexDataF32[23] = (rect.y + rect.w) / atlasTextureHeight;
vertexDataF32[23] = 1.0 - (rect.y + rect.w) / atlasTextureHeight;
vertexDataF32[30] = rect.x / atlasTextureWidth;
vertexDataF32[31] = (rect.y + rect.w) / atlasTextureHeight;
vertexDataF32[31] = 1.0 - (rect.y + rect.w) / atlasTextureHeight;

vb.unlock();

Expand Down
8 changes: 4 additions & 4 deletions src/framework/components/element/text-element.js
Expand Up @@ -943,16 +943,16 @@ class TextElement {
var uv = this._getUv(char);

meshInfo.uvs[quad * 4 * 2 + 0] = uv[0];
meshInfo.uvs[quad * 4 * 2 + 1] = uv[1];
meshInfo.uvs[quad * 4 * 2 + 1] = 1.0 - uv[1];

meshInfo.uvs[quad * 4 * 2 + 2] = uv[2];
meshInfo.uvs[quad * 4 * 2 + 3] = uv[1];
meshInfo.uvs[quad * 4 * 2 + 3] = 1.0 - uv[1];

meshInfo.uvs[quad * 4 * 2 + 4] = uv[2];
meshInfo.uvs[quad * 4 * 2 + 5] = uv[3];
meshInfo.uvs[quad * 4 * 2 + 5] = 1.0 - uv[3];

meshInfo.uvs[quad * 4 * 2 + 6] = uv[0];
meshInfo.uvs[quad * 4 * 2 + 7] = uv[3];
meshInfo.uvs[quad * 4 * 2 + 7] = 1.0 - uv[3];

// set per-vertex color
if (this._symbolColors) {
Expand Down
2 changes: 1 addition & 1 deletion src/graphics/program-lib/chunks/particle.frag
Expand Up @@ -27,7 +27,7 @@ float unpackFloat(vec4 rgbaDepth) {
#endif

void main(void) {
vec4 tex = texture2DSRGB(colorMap, texCoordsAlphaLife.xy);
vec4 tex = texture2DSRGB(colorMap, vec2(texCoordsAlphaLife.x, 1.0 - texCoordsAlphaLife.y));
vec4 ramp = texture2DSRGB(colorParam, vec2(texCoordsAlphaLife.w, 0.0));
ramp.rgb *= colorMult;

Expand Down
8 changes: 4 additions & 4 deletions src/graphics/program-lib/chunks/particle_cpu.vert
Expand Up @@ -74,11 +74,11 @@ void main(void)
vec2 velocityV = normalize((mat3(matrix_view) * inVel).xy); // should be removed by compiler if align/stretch is not used

vec2 quadXY = vertPos.xy;

#ifndef USE_MESH
texCoordsAlphaLife = vec4(quadXY * -0.5 + 0.5, particle_vertexData2.z, particle_vertexData.w);
#else

#ifdef USE_MESH
texCoordsAlphaLife = vec4(particle_vertexData5.zw, particle_vertexData2.z, particle_vertexData.w);
#else
texCoordsAlphaLife = vec4(quadXY * -0.5 + 0.5, particle_vertexData2.z, particle_vertexData.w);
#endif
mat2 rotMatrix;

Expand Down
2 changes: 1 addition & 1 deletion src/graphics/program-lib/chunks/particle_normalMap.frag
@@ -1,2 +1,2 @@
vec3 normalMap = normalize(texture2D(normalMap, texCoordsAlphaLife.xy).xyz * 2.0 - 1.0);
vec3 normalMap = normalize(texture2D(normalMap, vec2(texCoordsAlphaLife.x, 1.0 - texCoordsAlphaLife.y)).xyz * 2.0 - 1.0);
vec3 normal = ParticleMat * normalMap;
10 changes: 6 additions & 4 deletions src/graphics/program-lib/chunks/reproject.frag
Expand Up @@ -119,7 +119,8 @@ vec3 fromSpherical(vec2 uv) {
}

vec4 sampleEquirect(vec2 sph) {
return texture2D(sourceTex, sph / vec2(PI * 2.0, PI) + 0.5);
vec2 uv = sph / vec2(PI * 2.0, PI) + 0.5;
return texture2D(sourceTex, vec2(uv.x, 1.0 - uv.y));
}

vec4 sampleEquirect(vec3 dir) {
Expand All @@ -135,7 +136,7 @@ vec4 sampleCubemap(vec2 sph) {
}

vec3 getDirectionEquirect() {
return fromSpherical((vUv0 * 2.0 - 1.0) * vec2(PI, PI * 0.5));
return fromSpherical((vec2(vUv0.x, 1.0 - vUv0.y) * 2.0 - 1.0) * vec2(PI, PI * 0.5));
}

// octahedral code, based on http://jcgt.org/published/0003/02/01
Expand All @@ -159,7 +160,7 @@ vec3 octDecode(vec2 o) {
}

vec3 getDirectionOctahedral() {
return octDecode(vUv0 * 2.0 - 1.0);
return octDecode(vec2(vUv0.x, 1.0 - vUv0.y) * 2.0 - 1.0);
}

// Assumes that v is a unit vector. The result is an octahedral vector on the [-1, +1] square
Expand All @@ -173,7 +174,8 @@ vec2 octEncode(in vec3 v) {
}

vec4 sampleOctahedral(vec3 dir) {
return texture2D(sourceTex, octEncode(dir) * 0.5 + 0.5);
vec2 uv = octEncode(dir) * 0.5 + 0.5;
return texture2D(sourceTex, vec2(uv.x, 1.0 - uv.y));
}

vec4 sampleOctahedral(vec2 sph) {
Expand Down
2 changes: 2 additions & 0 deletions src/graphics/program-lib/chunks/startNineSliced.frag
@@ -1 +1,3 @@
nineSlicedUv = vUv0;
nineSlicedUv.y = 1.0 - nineSlicedUv.y;

2 changes: 2 additions & 0 deletions src/graphics/program-lib/chunks/startNineSlicedTiled.frag
Expand Up @@ -4,3 +4,5 @@
vec2 clampedUv = mix(innerOffset.xy * 0.5, vec2(1.0) - innerOffset.zw * 0.5, fract((vTiledUv - tileSize) * tileScale));
clampedUv = clampedUv * atlasRect.zw + atlasRect.xy;
nineSlicedUv = vUv0 * tileMask + clampedUv * (vec2(1.0) - tileMask);
nineSlicedUv.y = 1.0 - nineSlicedUv.y;

2 changes: 1 addition & 1 deletion src/graphics/texture.js
Expand Up @@ -128,7 +128,7 @@ class Texture {
this._cubemap = false;
this._volume = false;
this.fixCubemapSeams = false;
this._flipY = true;
this._flipY = false;
this._premultiplyAlpha = false;

this._isRenderTarget = false;
Expand Down
28 changes: 3 additions & 25 deletions src/resources/cubemap.js
Expand Up @@ -239,7 +239,7 @@ class CubemapHandler {

// one of the dependent assets has finished loading
let awaiting = 7;
const onReady = function (index, asset) {
const onLoad = function (index, asset) {
assets[index] = asset;
awaiting--;

Expand All @@ -250,28 +250,6 @@ class CubemapHandler {
}
};

// handle a texture asset finished loading.
// the engine is unable to flip ImageBitmaps (to orient them correctly for cubemaps)
// so we do that here.
const onLoad = function (index, asset) {
const level0 = asset && asset.resource && asset.resource._levels[0];
if (level0 && typeof ImageBitmap !== 'undefined' && level0 instanceof ImageBitmap) {
createImageBitmap(level0, {
premultiplyAlpha: 'none',
imageOrientation: 'flipY'
})
.then(function (imageBitmap) {
asset.resource._levels[0] = imageBitmap;
onReady(index, asset);
})
.catch(function (e) {
callback(e);
});
} else {
onReady(index, asset);
}
};

// handle an asset load failure
const onError = function (index, err, asset) {
callback(err);
Expand Down Expand Up @@ -299,10 +277,10 @@ class CubemapHandler {

if (!assetId) {
// no asset
onReady(i, null);
onLoad(i, null);
} else if (self.compareAssetIds(assetId, loadedAssetIds[i])) {
// asset id hasn't changed from what is currently set
onReady(i, loadedAssets[i]);
onLoad(i, loadedAssets[i]);
} else if (parseInt(assetId, 10) === assetId) {
// assetId is an asset id
texAsset = registry.get(assetId);
Expand Down
4 changes: 2 additions & 2 deletions src/resources/parser/glb-parser.js
Expand Up @@ -1594,9 +1594,9 @@ const createResources = function (device, gltf, bufferViews, textureAssets, opti
}

// The original version of FACT generated incorrectly flipped V texture
// coordinates. We must compensate by -not- flipping V in this case. Once
// coordinates. We must compensate by flipping V in this case. Once
// all models have been re-exported we can remove this flag.
const disableFlipV = gltf.asset && gltf.asset.generator === 'PlayCanvas';
const disableFlipV = !(gltf.asset && gltf.asset.generator === 'PlayCanvas');

const nodes = createNodes(gltf, options);
const scenes = createScenes(gltf, nodes);
Expand Down
2 changes: 1 addition & 1 deletion src/resources/parser/json-model.js
Expand Up @@ -296,7 +296,7 @@ class JsonModelParser {
iterator.element[attributeMap[attributeName]].set(attribute.data[j]);
break;
case 2:
iterator.element[attributeMap[attributeName]].set(attribute.data[j * 2], attribute.data[j * 2 + 1]);
iterator.element[attributeMap[attributeName]].set(attribute.data[j * 2], 1.0 - attribute.data[j * 2 + 1]);
break;
case 3:
iterator.element[attributeMap[attributeName]].set(attribute.data[j * 3], attribute.data[j * 3 + 1], attribute.data[j * 3 + 2]);
Expand Down
7 changes: 4 additions & 3 deletions src/resources/parser/texture/img.js
Expand Up @@ -18,7 +18,9 @@ class ImgParser {
// by default don't try cross-origin, because some browsers send different cookies (e.g. safari) if this is set.
this.crossOrigin = registry.prefix ? 'anonymous' : null;
this.maxRetries = 0;
// disable ImageBitmap
// As of today (9 Jul 2021) ImageBitmap only works on Chrome:
slimbuck marked this conversation as resolved.
Show resolved Hide resolved
// - Firefox doesn't support options parameter to createImageBitmap (see https://bugzilla.mozilla.org/show_bug.cgi?id=1533680)
// - Safari supports ImageBitmap only as experimental feature.
this.useImageBitmap = false && typeof ImageBitmap !== 'undefined' && /Firefox/.test(navigator.userAgent) === false;
}

Expand Down Expand Up @@ -105,8 +107,7 @@ class ImgParser {
callback(err);
} else {
createImageBitmap(blob, {
premultiplyAlpha: 'none',
imageOrientation: 'flipY'
premultiplyAlpha: 'none'
})
.then(function (imageBitmap) {
callback(null, imageBitmap);
Expand Down
1 change: 1 addition & 0 deletions src/scene/immediate.js
Expand Up @@ -139,6 +139,7 @@ class ImmediateData {
void main(void) {
gl_Position = matrix_model * vec4(aPosition, 0, 1);
uv0 = aPosition.xy + 0.5;
uv0.y = 1.0 - uv0.y;
}
`;
}
Expand Down