Skip to content

Commit 472b327

Browse files
Support for octahedral projection when projecting textures (playcanvas#2849)
* Support for octahedral projection when projecting textures * small update to make the singularity face down instead of sideways * TEXTUREPROJECTION_XXX constants are strings instead of numbers * update to default values for parameters of reprojectTexture * projection type is stored on texture, and not passed in as parameter to reproject function * updated engine example to show projections from cubemap * removed obsolete parameters from example * Update src/graphics/texture.js Co-authored-by: Donovan Hutchence <slimbuck7@gmail.com> * Update src/graphics/texture.js Co-authored-by: Donovan Hutchence <slimbuck7@gmail.com> * two textures in the example apply prefiltering as well * added TEXTUREPROJECTION_NONE * jsdocs fix to include NONE Co-authored-by: Donovan Hutchence <slimbuck7@gmail.com>
1 parent 202f61b commit 472b327

File tree

5 files changed

+180
-21
lines changed

5 files changed

+180
-21
lines changed

examples/graphics/render-to-cubemap.html

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@
135135
// get world and skybox layers
136136
var worldLayer = app.scene.layers.getLayerByName("World");
137137
var skyboxLayer = app.scene.layers.getLayerByName("Skybox");
138+
var immediateLayer = app.scene.layers.getLayerByName("Immediate");
138139

139140
// add camera component to shiny ball - this defines camera properties for cubemap rendering
140141
shinyBall.addComponent('camera', {
@@ -190,7 +191,7 @@
190191
var camera = new pc.Entity("MainCamera");
191192
camera.addComponent("camera", {
192193
fov: 60,
193-
layers: [worldLayer.id, excludedLayer.id, skyboxLayer.id]
194+
layers: [worldLayer.id, excludedLayer.id, skyboxLayer.id, immediateLayer.id]
194195
});
195196
app.root.addChild(camera);
196197

@@ -208,8 +209,30 @@
208209
});
209210
app.root.addChild(light);
210211

212+
// helper function to create a texture that can be used to project cubemap to
213+
function createReprojectionTexture(projection, size) {
214+
return new pc.Texture(this.app.graphicsDevice, {
215+
width: size,
216+
height: size,
217+
format: pc.PIXELFORMAT_R8_G8_B8,
218+
mipmaps: false,
219+
minFilter: pc.FILTER_LINEAR,
220+
magFilter: pc.FILTER_LINEAR,
221+
addressU: pc.ADDRESS_CLAMP_TO_EDGE,
222+
addressV: pc.ADDRESS_CLAMP_TO_EDGE,
223+
projection: projection
224+
});
225+
}
226+
227+
// create 2 uqirect and 2 octahedral textures
228+
var textureEqui = createReprojectionTexture(pc.TEXTUREPROJECTION_EQUIRECT, 256);
229+
var textureEqui2 = createReprojectionTexture(pc.TEXTUREPROJECTION_EQUIRECT, 256);
230+
var textureOcta = createReprojectionTexture(pc.TEXTUREPROJECTION_OCTAHEDRAL, 64);
231+
var textureOcta2 = createReprojectionTexture(pc.TEXTUREPROJECTION_OCTAHEDRAL, 32);
232+
211233
// update things each frame
212234
var time = 0;
235+
var device = this.app.graphicsDevice;
213236
app.on("update", function (dt) {
214237
time += dt;
215238

@@ -224,6 +247,27 @@
224247
// slowly orbit camera around
225248
camera.setLocalPosition(20 * Math.cos(time * 0.2), 2, 20 * Math.sin(time * 0.2));
226249
camera.lookAt(pc.Vec3.ZERO);
250+
251+
// project textures, and display them on the screen
252+
var srcCube = shinyBall.script.cubemapRenderer.cubeMap;
253+
254+
// cube -> equi1
255+
pc.reprojectTexture(device, srcCube, textureEqui, undefined, 1);
256+
app.renderTexture(-0.6, 0.7, 0.6, 0.3, textureEqui);
257+
258+
// cube -> octa1
259+
pc.reprojectTexture(device, srcCube, textureOcta, undefined, 1);
260+
app.renderTexture(0.7, 0.7, 0.4, 0.4, textureOcta);
261+
262+
// equi1 -> octa2
263+
pc.reprojectTexture(device, textureEqui, textureOcta2, 32, 1024);
264+
app.renderTexture(-0.7, -0.7, 0.4, 0.4, textureOcta2);
265+
266+
// octa1 -> equi2
267+
pc.reprojectTexture(device, textureOcta, textureEqui2, 16, 512);
268+
app.renderTexture(0.6, -0.7, 0.6, 0.3, textureEqui2);
269+
270+
227271
});
228272
</script>
229273
</body>

src/graphics/constants.js

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1000,6 +1000,38 @@ export const TEXHINT_SHADOWMAP = 1;
10001000
export const TEXHINT_ASSET = 2;
10011001
export const TEXHINT_LIGHTMAP = 3;
10021002

1003+
/**
1004+
* @constant
1005+
* @name TEXTUREPROJECTION_NONE
1006+
* @type {number}
1007+
* @description Texture data is not stored a specific projection format.
1008+
*/
1009+
export const TEXTUREPROJECTION_NONE = "none";
1010+
1011+
/**
1012+
* @constant
1013+
* @name TEXTUREPROJECTION_CUBE
1014+
* @type {number}
1015+
* @description Texture data is stored in cubemap projection format.
1016+
*/
1017+
export const TEXTUREPROJECTION_CUBE = "cube";
1018+
1019+
/**
1020+
* @constant
1021+
* @name TEXTUREPROJECTION_EQUIRECT
1022+
* @type {number}
1023+
* @description Texture data is stored in equirectangular projection format.
1024+
*/
1025+
export const TEXTUREPROJECTION_EQUIRECT = "equirect";
1026+
1027+
/**
1028+
* @constant
1029+
* @name TEXTUREPROJECTION_OCTAHEDRAL
1030+
* @type {number}
1031+
* @description Texture data is stored in octahedral projection format.
1032+
*/
1033+
export const TEXTUREPROJECTION_OCTAHEDRAL = "octahedral";
1034+
10031035
/**
10041036
* @constant
10051037
* @name TYPE_INT8
@@ -1075,11 +1107,11 @@ export const UNIFORMTYPE_VEC2ARRAY = 21;
10751107
export const UNIFORMTYPE_VEC3ARRAY = 22;
10761108
export const UNIFORMTYPE_VEC4ARRAY = 23;
10771109

1078-
// map of engine pc.TYPE_*** enums to their corresponding typed array constructors and byte sizes
1110+
// map of engine TYPE_*** enums to their corresponding typed array constructors and byte sizes
10791111
export const typedArrayTypes = [Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array];
10801112
export const typedArrayTypesByteSize = [1, 1, 2, 2, 4, 4, 4];
10811113

1082-
// map of typed array to engine pc.TYPE_***
1114+
// map of typed array to engine TYPE_***
10831115
export const typedArrayToType = {
10841116
"Int8Array": TYPE_INT8,
10851117
"Uint8Array": TYPE_UINT8,
@@ -1090,7 +1122,7 @@ export const typedArrayToType = {
10901122
"Float32Array": TYPE_FLOAT32
10911123
};
10921124

1093-
// map of engine pc.INDEXFORMAT_*** to their corresponding typed array constructors and byte sizes
1125+
// map of engine INDEXFORMAT_*** to their corresponding typed array constructors and byte sizes
10941126
export const typedArrayIndexFormats = [Uint8Array, Uint16Array, Uint32Array];
10951127
export const typedArrayIndexFormatsByteSize = [1, 2, 4];
10961128

src/graphics/program-lib/chunks/reproject.frag

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
// PROCESS_FUNC - must be one of reproject, prefilter
44
// DECODE_FUNC - must be one of decodeRGBM, decodeRGBE, decodeGamma or decodeLinear
55
// ENCODE_FUNC - must be one of encodeRGBM, encodeRGBE, encideGamma or encodeLinear
6-
// SOURCE_FUNC - must be one of sampleCubemap, sampleEquirect
7-
// TARGET_FUNC - must be one of getDirectionCubemap, getDirectionEquirect
6+
// SOURCE_FUNC - must be one of sampleCubemap, sampleEquirect, sampleOctahedral
7+
// TARGET_FUNC - must be one of getDirectionCubemap, getDirectionEquirect, getDirectionOctahedral
88
//
99
// When filtering:
1010
// NUM_SAMPLES - number of samples
@@ -137,6 +137,50 @@ vec3 getDirectionEquirect() {
137137
return fromSpherical((vUv0 * 2.0 - 1.0) * vec2(PI, PI * 0.5));
138138
}
139139

140+
// octahedral code, based on http://jcgt.org/published/0003/02/01
141+
// "Survey of Efficient Representations for Independent Unit Vectors" by Cigolle, Donow, Evangelakos, Mara, McGuire, Meyer
142+
143+
float signNotZero(float k){
144+
return(k >= 0.0) ? 1.0 : -1.0;
145+
}
146+
147+
vec2 signNotZero(vec2 v) {
148+
return vec2(signNotZero(v.x), signNotZero(v.y));
149+
}
150+
151+
// Returns a unit vector. Argument o is an octahedral vector packed via octEncode, on the [-1, +1] square
152+
vec3 octDecode(vec2 o) {
153+
vec3 v = vec3(o.x, 1.0 - abs(o.x) - abs(o.y), o.y);
154+
if (v.y < 0.0) {
155+
v.xz = (1.0 - abs(v.zx)) * signNotZero(v.xz);
156+
}
157+
return normalize(v);
158+
}
159+
160+
vec3 getDirectionOctahedral() {
161+
return octDecode(vUv0 * 2.0 - 1.0);
162+
}
163+
164+
// Assumes that v is a unit vector. The result is an octahedral vector on the [-1, +1] square
165+
vec2 octEncode(in vec3 v) {
166+
float l1norm = abs(v.x) + abs(v.y) + abs(v.z);
167+
vec2 result = v.xz * (1.0 / l1norm);
168+
if (v.y < 0.0) {
169+
result = (1.0 - abs(result.yx)) * signNotZero(result.xy);
170+
}
171+
return result;
172+
}
173+
174+
vec4 sampleOctahedral(vec3 dir) {
175+
return texture2D(sourceTex, octEncode(dir) * 0.5 + 0.5);
176+
}
177+
178+
vec4 sampleOctahedral(vec2 sph) {
179+
return sampleOctahedral(fromSpherical(sph));
180+
}
181+
182+
/////////////////////////////////////////////////////////////////////
183+
140184
vec3 getDirectionCubemap() {
141185
vec2 st = vUv0 * 2.0 - 1.0;
142186
float face = targetFace();

src/graphics/reproject-texture.js

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
TEXTURETYPE_RGBM, TEXTURETYPE_RGBE,
3-
PIXELFORMAT_RGB16F, PIXELFORMAT_RGB32F, PIXELFORMAT_RGBA16F, PIXELFORMAT_RGBA32F
3+
PIXELFORMAT_RGB16F, PIXELFORMAT_RGB32F, PIXELFORMAT_RGBA16F, PIXELFORMAT_RGBA32F,
4+
TEXTUREPROJECTION_OCTAHEDRAL, TEXTUREPROJECTION_EQUIRECT, TEXTUREPROJECTION_CUBE, TEXTUREPROJECTION_NONE
45
} from './constants.js';
56
import { createShaderFromCode } from './program-lib/utils.js';
67
import { drawQuadWithShader } from './simple-post-effect.js';
@@ -27,11 +28,23 @@ function getCoding(texture) {
2728
}
2829
}
2930

31+
function getProjectionName(projection) {
32+
// default to equirect if not specified
33+
if (projection === TEXTUREPROJECTION_NONE) {
34+
projection = TEXTUREPROJECTION_EQUIRECT;
35+
}
36+
switch (projection) {
37+
case TEXTUREPROJECTION_CUBE: return "Cubemap";
38+
case TEXTUREPROJECTION_EQUIRECT: return "Equirect";
39+
case TEXTUREPROJECTION_OCTAHEDRAL: return "Octahedral";
40+
}
41+
}
42+
3043
/**
3144
* @static
3245
* @function
3346
* @name reprojectTexture
34-
* @description This function reprojects textures between cubemap and equirectangular formats. The
47+
* @description This function reprojects textures between cubemap, equirectangular and octahedral formats. The
3548
* function can read and write textures with pixel data in RGBE, RGBM, linear and sRGB formats. When
3649
* specularPower is specified it will perform a phong-weighted convolution of the source (for generating
3750
* a gloss maps).
@@ -43,14 +56,18 @@ function getCoding(texture) {
4356
* the function performs a standard resample.
4457
* @param {number} [numSamples] - optional number of samples (default is 1024).
4558
*/
46-
function reprojectTexture(device, source, target, specularPower, numSamples = 1024) {
47-
var processFunc = (specularPower !== undefined) ? 'prefilter' : 'reproject';
48-
var decodeFunc = "decode" + getCoding(source);
49-
var encodeFunc = "encode" + getCoding(target);
50-
var sourceFunc = source.cubemap ? "sampleCubemap" : "sampleEquirect";
51-
var targetFunc = target.cubemap ? "getDirectionCubemap" : "getDirectionEquirect";
59+
function reprojectTexture(device, source, target, specularPower = 1, numSamples = 1024) {
60+
const processFunc = (specularPower === 1) ? 'reproject' : 'prefilter';
61+
const decodeFunc = "decode" + getCoding(source);
62+
const encodeFunc = "encode" + getCoding(target);
5263

53-
var shader = createShaderFromCode(
64+
// source projection type
65+
const sourceFunc = "sample" + getProjectionName(source.projection);
66+
67+
// target projection type
68+
const targetFunc = "getDirection" + getProjectionName(target.projection);
69+
70+
const shader = createShaderFromCode(
5471
device,
5572
shaderChunks.fullscreenQuadVS,
5673
"#define PROCESS_FUNC " + processFunc + "\n" +
@@ -65,17 +82,21 @@ function reprojectTexture(device, source, target, specularPower, numSamples = 10
6582
device.webgl2 ? "" : "#extension GL_OES_standard_derivatives: enable\n"
6683
);
6784

68-
var constantSource = device.scope.resolve(source.cubemap ? "sourceCube" : "sourceTex");
85+
// #ifdef DEBUG
86+
device.pushMarker("ReprojectTexture");
87+
// #endif
88+
89+
const constantSource = device.scope.resolve(source.cubemap ? "sourceCube" : "sourceTex");
6990
constantSource.setValue(source);
7091

71-
var constantParams = device.scope.resolve("params");
72-
var params = new Float32Array(4);
73-
params[1] = (specularPower !== undefined) ? specularPower : 1;
92+
const constantParams = device.scope.resolve("params");
93+
let params = new Float32Array(4);
94+
params[1] = specularPower;
7495
params[2] = 1.0 - (source.fixCubemapSeams ? 1.0 / source.width : 0.0); // source seam scale
7596
params[3] = 1.0 - (target.fixCubemapSeams ? 1.0 / target.width : 0.0); // target seam scale
7697

77-
for (var face = 0; face < (target.cubemap ? 6 : 1); face++) {
78-
var targ = new RenderTarget(device, target, {
98+
for (let face = 0; face < (target.cubemap ? 6 : 1); face++) {
99+
const targ = new RenderTarget(device, target, {
79100
face: face,
80101
depth: false
81102
});
@@ -84,6 +105,10 @@ function reprojectTexture(device, source, target, specularPower, numSamples = 10
84105

85106
drawQuadWithShader(device, targ, shader);
86107
}
108+
109+
// #ifdef DEBUG
110+
device.popMarker("");
111+
// #endif
87112
}
88113

89114
export { reprojectTexture };

src/graphics/texture.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
PIXELFORMAT_PVRTC_4BPP_RGB_1, PIXELFORMAT_PVRTC_4BPP_RGBA_1, PIXELFORMAT_ASTC_4x4, PIXELFORMAT_ATC_RGB,
1313
PIXELFORMAT_ATC_RGBA,
1414
TEXTURELOCK_WRITE,
15+
TEXTUREPROJECTION_NONE, TEXTUREPROJECTION_CUBE,
1516
TEXTURETYPE_DEFAULT, TEXTURETYPE_RGBM, TEXTURETYPE_SWIZZLEGGGR
1617
} from './constants.js';
1718

@@ -56,6 +57,12 @@ var _blockSizeTable = null;
5657
* * {@link PIXELFORMAT_ATC_RGB}
5758
* * {@link PIXELFORMAT_ATC_RGBA}
5859
* Defaults to {@link PIXELFORMAT_R8_G8_B8_A8}.
60+
* @param {string} [options.projection] - The projection type of the texture, used when the texture represents an environment. Can be:
61+
* * {@link TEXTUREPROJECTION_NONE}
62+
* * {@link TEXTUREPROJECTION_CUBE}
63+
* * {@link TEXTUREPROJECTION_EQUIRECT}
64+
* * {@link TEXTUREPROJECTION_OCTAHEDRAL}
65+
* Defaults to {@link TEXTUREPROJECTION_CUBE} if options.cubemap is specified, otherwise {@link TEXTUREPROJECTION_NONE}.
5966
* @param {number} [options.minFilter] - The minification filter type to use. Defaults to {@link FILTER_LINEAR_MIPMAP_LINEAR}
6067
* @param {number} [options.magFilter] - The magnification filter type to use. Defaults to {@link FILTER_LINEAR}
6168
* @param {number} [options.anisotropy] - The level of anisotropic filtering to use. Defaults to 1
@@ -116,6 +123,7 @@ class Texture {
116123

117124
this._format = PIXELFORMAT_R8_G8_B8_A8;
118125
this.type = TEXTURETYPE_DEFAULT;
126+
this.projection = TEXTUREPROJECTION_NONE;
119127

120128
this._cubemap = false;
121129
this._volume = false;
@@ -175,6 +183,12 @@ class Texture {
175183
this._cubemap = (options.cubemap !== undefined) ? options.cubemap : this._cubemap;
176184
this.fixCubemapSeams = (options.fixCubemapSeams !== undefined) ? options.fixCubemapSeams : this.fixCubemapSeams;
177185

186+
if (this._cubemap) {
187+
this.projection = TEXTUREPROJECTION_CUBE;
188+
} else if (options.projection !== TEXTUREPROJECTION_CUBE) {
189+
this.projection = options.projection;
190+
}
191+
178192
this._minFilter = (options.minFilter !== undefined) ? options.minFilter : this._minFilter;
179193
this._magFilter = (options.magFilter !== undefined) ? options.magFilter : this._magFilter;
180194
this._anisotropy = (options.anisotropy !== undefined) ? options.anisotropy : this._anisotropy;

0 commit comments

Comments
 (0)