Skip to content

Commit 4daca59

Browse files
committed
feat: skybox
1 parent fb8b46b commit 4daca59

File tree

11 files changed

+206
-62
lines changed

11 files changed

+206
-62
lines changed
File renamed without changes.

bodies.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { vec3 } from 'gl-matrix';
33
import { Geometry } from './graphics';
44
import { FloatingBody, OceanFieldBuoyancy } from './ocean';
55
import { Box, World, Cylinder, Sphere } from './physics';
6-
import OBJ from './objects/shapes';
6+
import OBJ from './assets/objects/shapes';
77
import { loadObj } from './utils';
88

99
const obj = loadObj(OBJ);

graphics/gpu.ts

+31-2
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,9 @@ export class Gpu {
6161

6262
constructor(private readonly _gl: WebGL2RenderingContext) {
6363
_gl.enable(WebGL2RenderingContext.DEPTH_TEST);
64-
// _gl.enable(WebGL2RenderingContext.CULL_FACE);
65-
// _gl.frontFace(WebGL2RenderingContext.CCW)
64+
_gl.enable(WebGL2RenderingContext.CULL_FACE);
65+
_gl.depthFunc(WebGL2RenderingContext.LEQUAL);
66+
_gl.frontFace(WebGL2RenderingContext.CCW);
6667
_gl.clearDepth(1.0);
6768
_gl.lineWidth(2);
6869
_gl.disable(WebGL2RenderingContext.BLEND);
@@ -288,6 +289,26 @@ export class Gpu {
288289
this._gl.bindTexture(WebGL2RenderingContext.TEXTURE_2D, texture);
289290
}
290291

292+
setProgramCubemap(
293+
program: ShaderProgram,
294+
name: string,
295+
texture: Cubemap,
296+
slot: number
297+
) {
298+
const loc: WebGLUniformLocation = this._gl.getUniformLocation(
299+
program.program,
300+
name
301+
);
302+
if (!loc) {
303+
console.warn('Failed to find loc: ', name);
304+
return;
305+
}
306+
307+
this._gl.uniform1i(loc, slot);
308+
this._gl.activeTexture(WebGL2RenderingContext.TEXTURE0 + slot);
309+
this._gl.bindTexture(WebGL2RenderingContext.TEXTURE_CUBE_MAP, texture);
310+
}
311+
291312
setProgramTextures(
292313
program: ShaderProgram,
293314
names: string[],
@@ -760,6 +781,14 @@ export class Gpu {
760781
this._gl.bindFramebuffer(WebGL2RenderingContext.FRAMEBUFFER, target);
761782
}
762783

784+
setCullFace(face: GLenum) {
785+
this._gl.frontFace(face);
786+
}
787+
788+
enableDepthWrite(flag: boolean) {
789+
this._gl.depthMask(flag);
790+
}
791+
763792
clearRenderTarget() {
764793
this._gl.clear(
765794
WebGL2RenderingContext.COLOR_BUFFER_BIT |

index.ts

+48-38
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Viewport } from './viewport';
66
import { Camera, Gpu } from './graphics';
77
import { OceanFieldBuilder, OceanFieldBuoyancy } from './ocean';
88
import { Gui } from './gui';
9-
import { registerWorkerGlobals } from './utils';
9+
import { readKtx, registerWorkerGlobals } from './utils';
1010
import { FpsCameraController } from './controller';
1111

1212
import {
@@ -34,43 +34,53 @@ import { World } from './physics';
3434

3535
registerWorkerGlobals();
3636

37-
const canvas = document.getElementById('viewport') as HTMLCanvasElement;
38-
canvas.width = self.screen.width;
39-
canvas.height = self.screen.height;
40-
const context = canvas.getContext('webgl2');
41-
if (!context) {
42-
throw new Error('Failed to create webgl2 drawing context');
43-
}
44-
const gpu = new Gpu(context);
45-
const camera = new Camera(45.0, canvas.width / canvas.height, 1.0, 1.0e4);
46-
camera.lookAt(vec3.fromValues(-10, 2.5, -10), vec3.create());
37+
(async () => {
38+
const canvas = document.getElementById('viewport') as HTMLCanvasElement;
39+
canvas.width = self.screen.width;
40+
canvas.height = self.screen.height;
41+
const context = canvas.getContext('webgl2');
42+
if (!context) {
43+
throw new Error('Failed to create webgl2 drawing context');
44+
}
45+
const gpu = new Gpu(context);
46+
const camera = new Camera(45.0, canvas.width / canvas.height, 1.0, 1.0e4);
47+
camera.lookAt(vec3.fromValues(-10, 2.5, -10), vec3.create());
4748

48-
const cameraController = new FpsCameraController(canvas, camera);
49-
const gui = new Gui(document.getElementById('gui'));
50-
const oceanBuilder = new OceanFieldBuilder(gpu);
51-
const oceanField = oceanBuilder.build(gui.params);
52-
const buoyancy = new OceanFieldBuoyancy(oceanField);
53-
const world = new World();
54-
const viewport = new Viewport(
55-
gpu,
56-
oceanField,
57-
world,
58-
buoyancy,
59-
cameraController
60-
);
49+
const cameraController = new FpsCameraController(canvas, camera);
50+
const gui = new Gui(document.getElementById('gui'));
51+
const oceanBuilder = new OceanFieldBuilder(gpu);
52+
const oceanField = oceanBuilder.build(gui.params);
53+
const buoyancy = new OceanFieldBuoyancy(oceanField);
54+
const world = new World();
55+
const skybox = await fetch(
56+
'https://raw.githubusercontent.com/codeagent/webgl-ocean/skybox/assets/cubemaps/sky_skybox.ktx'
57+
)
58+
.then((r) => r.arrayBuffer())
59+
.then((skybox) => readKtx(skybox))
60+
.then((ktx) => gpu.createCubeMap(ktx));
6161

62-
gui.onChange$.subscribe((params) => {
63-
oceanBuilder.update(oceanField, params);
64-
viewport.tileRenderer.setSettings(params.tileRenderer);
65-
viewport.plateRenderer.setSettings(params.plateRenderer);
66-
viewport.projectedGridRenderer.setSettings(params.gridRenderer);
67-
viewport.quadTreeRenderer.setSettings(params.quadTreeRenderer);
68-
});
62+
const viewport = new Viewport(
63+
gpu,
64+
oceanField,
65+
world,
66+
buoyancy,
67+
cameraController,
68+
skybox
69+
);
6970

70-
animationFrames().subscribe(({ elapsed }) => {
71-
buoyancy.update();
72-
world.integrate(1 / 60);
73-
cameraController.update(1 / 60);
74-
oceanField.update(elapsed / 1e3);
75-
viewport.render(gui.params.renderer);
76-
});
71+
gui.onChange$.subscribe((params) => {
72+
oceanBuilder.update(oceanField, params);
73+
viewport.tileRenderer.setSettings(params.tileRenderer);
74+
viewport.plateRenderer.setSettings(params.plateRenderer);
75+
viewport.projectedGridRenderer.setSettings(params.gridRenderer);
76+
viewport.quadTreeRenderer.setSettings(params.quadTreeRenderer);
77+
});
78+
79+
animationFrames().subscribe(({ elapsed }) => {
80+
buoyancy.update();
81+
world.integrate(1 / 60);
82+
cameraController.update(1 / 60);
83+
oceanField.update(elapsed / 1e3);
84+
viewport.render(gui.params.renderer);
85+
});
86+
})();

renderer/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * from './plate-ocean-renderer';
22
export * from './tile-ocean-renderer';
33
export * from './projected-grid-renderer';
44
export * from './quad-tree-renderer';
5+
export * from './skybox-renderer';
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
export default `#version 300 es
2+
precision highp float;
3+
4+
layout( location = 0 ) out vec4 color;
5+
6+
in vec3 _pos;
7+
8+
uniform samplerCube env;
9+
10+
uniform float exposure;
11+
uniform float gamma;
12+
13+
vec3 gammaCorrection(const vec3 color) {
14+
return pow(color, vec3(1.0f / gamma));
15+
}
16+
17+
vec3 toneMapping(const vec3 color) {
18+
return vec3(1.0f) - exp(-color * exposure);
19+
}
20+
21+
void main()
22+
{
23+
vec3 background = textureLod(env, normalize(_pos), 0.0f).rgb;
24+
color = vec4(gammaCorrection(toneMapping(background)), 1.0f);
25+
}
26+
`;
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export default `#version 300 es
2+
layout(location = 0) in vec3 position;
3+
4+
uniform mat4 viewMat;
5+
uniform mat4 projMat;
6+
7+
out vec3 _pos;
8+
9+
void main()
10+
{
11+
_pos = normalize(position);
12+
gl_Position = (projMat * viewMat * vec4(position * 0.5f, 0.0f)).xyww;
13+
}
14+
`;

renderer/skybox-renderer.ts

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import OBJ from '../assets/objects/shapes';
2+
3+
import { Geometry, Gpu, ShaderProgram, Camera, Cubemap } from '../graphics';
4+
import { loadObj } from '../utils';
5+
6+
import vs from './programs/skybox-vertex.glsl';
7+
import fs from './programs/skybox-fragment.glsl';
8+
9+
export class SkyboxRenderer {
10+
private readonly shader: ShaderProgram;
11+
private geometry: Geometry;
12+
13+
public constructor(private readonly gpu: Gpu) {
14+
this.shader = this.gpu.createShaderProgram(vs, fs);
15+
this.geometry = this.createGeometry();
16+
}
17+
18+
public render(camera: Camera, skybox: Cubemap) {
19+
this.gpu.setViewport(
20+
0,
21+
0,
22+
this.gpu.context.canvas.width,
23+
this.gpu.context.canvas.height
24+
);
25+
this.gpu.setProgram(this.shader);
26+
this.gpu.setProgramCubemap(this.shader, 'env', skybox, 0);
27+
this.gpu.setProgramVariable(this.shader, 'exposure', 'float', 3.0);
28+
this.gpu.setProgramVariable(this.shader, 'gamma', 'float', 2.2);
29+
this.gpu.setProgramVariable(this.shader, 'viewMat', 'mat4', camera.view);
30+
this.gpu.setProgramVariable(
31+
this.shader,
32+
'projMat',
33+
'mat4',
34+
camera.projection
35+
);
36+
37+
this.gpu.setCullFace(WebGL2RenderingContext.CW);
38+
this.gpu.enableDepthWrite(false);
39+
this.gpu.drawGeometry(this.geometry);
40+
this.gpu.enableDepthWrite(true);
41+
this.gpu.setCullFace(WebGL2RenderingContext.CCW);
42+
}
43+
44+
private createGeometry() {
45+
const obj = loadObj(OBJ);
46+
return this.gpu.createGeometry(obj['cube']);
47+
}
48+
}

utils/ktx-reader.ts

+26-14
Original file line numberDiff line numberDiff line change
@@ -65,18 +65,18 @@ export class KtxReader {
6565

6666
const props = {} as any;
6767
for (let prop of [
68-
"glType",
69-
"glTypeSize",
70-
"glFormat",
71-
"glInternalFormat",
72-
"glBaseInternalFormat",
73-
"pixelWidth",
74-
"pixelHeight",
75-
"pixelDepth",
76-
"numberOfArrayElements",
77-
"numberOfFaces",
78-
"numberOfMipmapLevels",
79-
"bytesOfKeyValueData"
68+
'glType',
69+
'glTypeSize',
70+
'glFormat',
71+
'glInternalFormat',
72+
'glBaseInternalFormat',
73+
'pixelWidth',
74+
'pixelHeight',
75+
'pixelDepth',
76+
'numberOfArrayElements',
77+
'numberOfFaces',
78+
'numberOfMipmapLevels',
79+
'bytesOfKeyValueData',
8080
]) {
8181
props[prop] = view.getUint32(offset, littleEndian);
8282
offset += 4;
@@ -94,7 +94,7 @@ export class KtxReader {
9494
numberOfArrayElements,
9595
numberOfFaces,
9696
numberOfMipmapLevels,
97-
bytesOfKeyValueData
97+
bytesOfKeyValueData,
9898
} = props;
9999

100100
numberOfMipmapLevels = numberOfMipmapLevels || 1;
@@ -171,7 +171,7 @@ export class KtxReader {
171171
numberOfFaces,
172172
numberOfMipmapLevels,
173173
keyValueData,
174-
mipmaps
174+
mipmaps,
175175
};
176176
}
177177

@@ -187,3 +187,15 @@ export class KtxReader {
187187
return [key, value];
188188
}
189189
}
190+
191+
export const parseSH = (ktx: KtxInfo): number[] => {
192+
const meta = ktx.keyValueData.find(([key]) => /sh/.test(key));
193+
if (!meta) {
194+
return [];
195+
}
196+
const [, sh] = meta;
197+
return sh
198+
.split(/[\s]+/g)
199+
.map(parseFloat)
200+
.filter((v) => !isNaN(v));
201+
};

utils/loader.ts

-5
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ export const loadObj = (content: string) => {
2727
const collection: MeshCollection = {};
2828
let name: string = '';
2929
let vertexFormat: VertexAttribute[] = null;
30-
let verticesCount = 0;
3130

3231
const lines = content.split(/\r\n|\n/);
3332
const objectRegExp = /^o\s+(.+)/;
@@ -53,7 +52,6 @@ export const loadObj = (content: string) => {
5352

5453
lookup.clear();
5554
vertexData.length = 0;
56-
verticesCount = 0;
5755
indexData = [];
5856
}
5957
name = matches[1].trim();
@@ -93,7 +91,6 @@ export const loadObj = (content: string) => {
9391
if (!lookup.has(f[i].key)) {
9492
lookup.set(f[i].key, vertexData.length / 3);
9593
vertexData.push(...positions[f[i].position - 1]);
96-
verticesCount++;
9794
}
9895
indexData.push(lookup.get(f[i].key));
9996
}
@@ -132,7 +129,6 @@ export const loadObj = (content: string) => {
132129
lookup.set(f[i].key, vertexData.length / 6);
133130
vertexData.push(...positions[f[i].position - 1]);
134131
vertexData.push(...normals[f[i].normal - 1]);
135-
verticesCount++;
136132
}
137133
indexData.push(lookup.get(f[i].key));
138134
}
@@ -183,7 +179,6 @@ export const loadObj = (content: string) => {
183179
vertexData.push(...positions[f[i].position - 1]);
184180
vertexData.push(...normals[f[i].normal - 1]);
185181
vertexData.push(...uvs[f[i].uv - 1]);
186-
verticesCount++;
187182
}
188183
indexData.push(lookup.get(f[i].key));
189184
}

0 commit comments

Comments
 (0)