Skip to content
This repository was archived by the owner on Nov 22, 2022. It is now read-only.

Commit c73d19b

Browse files
author
Lucas Crane
authored
Hybrid rendering (#62)
* move out scene processing * add gbuffer prototype * render gbuffer to framebuffer * use gbuffers for first ray trace bounce * extract material buffer creation to shared filed * rasterize material index * normalize normals * rasterize materials * antialiasing * fullscreen reprojection * reproject preview with light upscaling * use half-floats * restructure gbuffer outputs * use float32 for hdrBuffer; use linear filtering * upscale light near envmap * remove debug line * wrap uvs greater than 1 * upscale in tonemap step * disable jitter on camera move * only upscale if necessary * disable missing uniform log * cleanup * use integer attribute for mesh index
1 parent d42ea7d commit c73d19b

30 files changed

+1121
-677
lines changed

src/RayTracingRenderer.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export function RayTracingRenderer(params = {}) {
1616

1717
const gl = canvas.getContext('webgl2', {
1818
alpha: false,
19-
depth: false,
19+
depth: true,
2020
stencil: false,
2121
antialias: false,
2222
powerPreference: 'high-performance',

src/renderer/Framebuffer.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export function makeFramebuffer(gl, { attachments }) {
1+
export function makeFramebuffer(gl, { color, depth }) {
22

33
const framebuffer = gl.createFramebuffer();
44

@@ -15,27 +15,31 @@ export function makeFramebuffer(gl, { attachments }) {
1515

1616
const drawBuffers = [];
1717

18-
for (let location in attachments) {
18+
for (let location in color) {
1919
location = Number(location);
2020

2121
if (location === undefined) {
2222
console.error('invalid location');
2323
}
2424

25-
const tex = attachments[location];
25+
const tex = color[location];
2626
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + location, tex.target, tex.texture, 0);
2727
drawBuffers.push(gl.COLOR_ATTACHMENT0 + location);
2828
}
2929

3030
gl.drawBuffers(drawBuffers);
3131

32+
if (depth) {
33+
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, depth.target, depth.texture);
34+
}
35+
3236
unbind();
3337
}
3438

3539
init();
3640

3741
return {
38-
attachments,
42+
color,
3943
bind,
4044
unbind
4145
};

src/renderer/FullscreenQuad.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ import vertex from './glsl/fullscreenQuad.vert';
22
import { makeVertexShader } from './RenderPass';
33

44
export function makeFullscreenQuad(gl) {
5-
// TODO: use VAOs
5+
const vao = gl.createVertexArray();
6+
7+
gl.bindVertexArray(vao);
8+
69
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
710
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1]), gl.STATIC_DRAW);
811

@@ -12,9 +15,12 @@ export function makeFullscreenQuad(gl) {
1215
gl.enableVertexAttribArray(posLoc);
1316
gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);
1417

18+
gl.bindVertexArray(null);
19+
1520
const vertexShader = makeVertexShader(gl, { vertex });
1621

1722
function draw() {
23+
gl.bindVertexArray(vao);
1824
gl.drawArrays(gl.TRIANGLES, 0, 6);
1925
}
2026

src/renderer/GBufferPass.js

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { makeRenderPass } from './RenderPass';
2+
import vertex from './glsl/gBuffer.vert';
3+
import fragment from './glsl/gBuffer.frag';
4+
import { Matrix4 } from 'three';
5+
6+
export function makeGBufferPass(gl, { materialBuffer, mergedMesh }) {
7+
const renderPass = makeRenderPass(gl, {
8+
defines: materialBuffer.defines,
9+
vertex,
10+
fragment
11+
});
12+
13+
renderPass.setTexture('diffuseMap', materialBuffer.textures.diffuseMap);
14+
renderPass.setTexture('normalMap', materialBuffer.textures.normalMap);
15+
renderPass.setTexture('pbrMap', materialBuffer.textures.pbrMap);
16+
17+
const geometry = mergedMesh.geometry;
18+
19+
const elementCount = geometry.getIndex().count;
20+
21+
const vao = gl.createVertexArray();
22+
23+
gl.bindVertexArray(vao);
24+
uploadAttributes(gl, renderPass, geometry);
25+
gl.bindVertexArray(null);
26+
27+
let jitterX = 0;
28+
let jitterY = 0;
29+
function setJitter(x, y) {
30+
jitterX = x;
31+
jitterY = y;
32+
}
33+
34+
let currentCamera;
35+
function setCamera(camera) {
36+
currentCamera = camera;
37+
}
38+
39+
function calcCamera() {
40+
projView.copy(currentCamera.projectionMatrix);
41+
42+
projView.elements[8] += 2 * jitterX;
43+
projView.elements[9] += 2 * jitterY;
44+
45+
projView.multiply(currentCamera.matrixWorldInverse);
46+
renderPass.setUniform('projView', projView.elements);
47+
}
48+
49+
let projView = new Matrix4();
50+
51+
function draw() {
52+
calcCamera();
53+
gl.bindVertexArray(vao);
54+
renderPass.useProgram();
55+
gl.enable(gl.DEPTH_TEST);
56+
gl.drawElements(gl.TRIANGLES, elementCount, gl.UNSIGNED_INT, 0);
57+
gl.disable(gl.DEPTH_TEST);
58+
}
59+
60+
return {
61+
draw,
62+
outputLocs: renderPass.outputLocs,
63+
setCamera,
64+
setJitter
65+
};
66+
}
67+
68+
function uploadAttributes(gl, renderPass, geometry) {
69+
setAttribute(gl, renderPass.attribLocs.aPosition, geometry.getAttribute('position'));
70+
setAttribute(gl, renderPass.attribLocs.aNormal, geometry.getAttribute('normal'));
71+
setAttribute(gl, renderPass.attribLocs.aUv, geometry.getAttribute('uv'));
72+
setAttribute(gl, renderPass.attribLocs.aMaterialMeshIndex, geometry.getAttribute('materialMeshIndex'));
73+
74+
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.createBuffer());
75+
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, geometry.getIndex().array, gl.STATIC_DRAW);
76+
}
77+
78+
function setAttribute(gl, location, bufferAttribute) {
79+
const { itemSize, array } = bufferAttribute;
80+
81+
gl.enableVertexAttribArray(location);
82+
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
83+
gl.bufferData(gl.ARRAY_BUFFER, array, gl.STATIC_DRAW);
84+
85+
if (array instanceof Float32Array) {
86+
gl.vertexAttribPointer(location, itemSize, gl.FLOAT, false, 0, 0);
87+
} else if (array instanceof Int32Array) {
88+
gl.vertexAttribIPointer(location, itemSize, gl.INT, 0, 0);
89+
} else {
90+
throw 'Unsupported buffer type';
91+
}
92+
}

src/renderer/MaterialBuffer.js

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
import { ThinMaterial, ThickMaterial, ShadowCatcherMaterial } from '../constants';
2+
import materialBufferChunk from './glsl/chunks/materialBuffer.glsl';
3+
import { makeUniformBuffer } from './UniformBuffer';
4+
import { makeRenderPass } from "./RenderPass";
5+
import { makeTexture } from './Texture';
6+
import { getTexturesFromMaterials, mergeTexturesFromMaterials } from './texturesFromMaterials';
7+
8+
export function makeMaterialBuffer(gl, materials) {
9+
const maps = getTexturesFromMaterials(materials, ['map', 'normalMap']);
10+
const pbrMap = mergeTexturesFromMaterials(materials, ['roughnessMap', 'metalnessMap']);
11+
12+
const textures = {};
13+
14+
const bufferData = {};
15+
16+
bufferData.color = materials.map(m => m.color);
17+
bufferData.roughness = materials.map(m => m.roughness);
18+
bufferData.metalness = materials.map(m => m.metalness);
19+
bufferData.normalScale = materials.map(m => m.normalScale);
20+
21+
bufferData.type = materials.map(m => {
22+
if (m.shadowCatcher) {
23+
return ShadowCatcherMaterial;
24+
}
25+
if (m.transparent) {
26+
return m.solid ? ThickMaterial : ThinMaterial;
27+
}
28+
});
29+
30+
if (maps.map.textures.length > 0) {
31+
const { relativeSizes, texture } = makeTextureArray(gl, maps.map.textures, true);
32+
textures.diffuseMap = texture;
33+
bufferData.diffuseMapSize = relativeSizes;
34+
bufferData.diffuseMapIndex = maps.map.indices;
35+
}
36+
37+
if (maps.normalMap.textures.length > 0) {
38+
const { relativeSizes, texture } = makeTextureArray(gl, maps.normalMap.textures, false);
39+
textures.normalMap = texture;
40+
bufferData.normalMapSize = relativeSizes;
41+
bufferData.normalMapIndex = maps.normalMap.indices;
42+
}
43+
44+
if (pbrMap.textures.length > 0) {
45+
const { relativeSizes, texture } = makeTextureArray(gl, pbrMap.textures, false);
46+
textures.pbrMap = texture;
47+
bufferData.pbrMapSize = relativeSizes;
48+
bufferData.roughnessMapIndex = pbrMap.indices.roughnessMap;
49+
bufferData.metalnessMapIndex = pbrMap.indices.metalnessMap;
50+
}
51+
52+
const defines = {
53+
NUM_MATERIALS: materials.length,
54+
NUM_DIFFUSE_MAPS: maps.map.textures.length,
55+
NUM_NORMAL_MAPS: maps.normalMap.textures.length,
56+
NUM_DIFFUSE_NORMAL_MAPS: Math.max(maps.map.textures.length, maps.normalMap.textures.length),
57+
NUM_PBR_MAPS: pbrMap.textures.length,
58+
};
59+
60+
// create temporary shader program including the Material uniform buffer
61+
// used to query the compiled structure of the uniform buffer
62+
const renderPass = makeRenderPass(gl, {
63+
vertex: {
64+
source: `void main() {}`
65+
},
66+
fragment: {
67+
includes: [ materialBufferChunk ],
68+
source: `void main() {}`
69+
},
70+
defines
71+
});
72+
73+
uploadToUniformBuffer(gl, renderPass.program, bufferData);
74+
75+
return { defines, textures };
76+
}
77+
78+
function makeTextureArray(gl, textures, gammaCorrection = false) {
79+
const images = textures.map(t => t.image);
80+
const flipY = textures.map(t => t.flipY);
81+
const { maxSize, relativeSizes } = maxImageSize(images);
82+
83+
// create GL Array Texture from individual textures
84+
const texture = makeTexture(gl, {
85+
width: maxSize.width,
86+
height: maxSize.height,
87+
gammaCorrection,
88+
data: images,
89+
flipY,
90+
channels: 3,
91+
minFilter: gl.LINEAR,
92+
magFilter: gl.LINEAR,
93+
});
94+
95+
return {
96+
texture,
97+
relativeSizes
98+
};
99+
}
100+
101+
function maxImageSize(images) {
102+
const maxSize = {
103+
width: 0,
104+
height: 0
105+
};
106+
107+
for (const image of images) {
108+
maxSize.width = Math.max(maxSize.width, image.width);
109+
maxSize.height = Math.max(maxSize.height, image.height);
110+
}
111+
112+
const relativeSizes = [];
113+
for (const image of images) {
114+
relativeSizes.push(image.width / maxSize.width);
115+
relativeSizes.push(image.height / maxSize.height);
116+
}
117+
118+
return { maxSize, relativeSizes };
119+
}
120+
121+
122+
// Upload arrays to uniform buffer objects
123+
// Packs different arrays into vec4's to take advantage of GLSL's std140 memory layout
124+
125+
function uploadToUniformBuffer(gl, program, bufferData) {
126+
const materialBuffer = makeUniformBuffer(gl, program, 'Materials');
127+
128+
materialBuffer.set('Materials.colorAndMaterialType[0]', interleave(
129+
{ data: [].concat(...bufferData.color.map(d => d.toArray())), channels: 3 },
130+
{ data: bufferData.type, channels: 1}
131+
));
132+
133+
materialBuffer.set('Materials.roughnessMetalnessNormalScale[0]', interleave(
134+
{ data: bufferData.roughness, channels: 1 },
135+
{ data: bufferData.metalness, channels: 1 },
136+
{ data: [].concat(...bufferData.normalScale.map(d => d.toArray())), channels: 2 }
137+
));
138+
139+
materialBuffer.set('Materials.diffuseNormalRoughnessMetalnessMapIndex[0]', interleave(
140+
{ data: bufferData.diffuseMapIndex, channels: 1 },
141+
{ data: bufferData.normalMapIndex, channels: 1 },
142+
{ data: bufferData.roughnessMapIndex, channels: 1 },
143+
{ data: bufferData.metalnessMapIndex, channels: 1 }
144+
));
145+
146+
materialBuffer.set('Materials.diffuseNormalMapSize[0]', interleave(
147+
{ data: bufferData.diffuseMapSize, channels: 2 },
148+
{ data: bufferData.normalMapSize, channels: 2 }
149+
));
150+
151+
materialBuffer.set('Materials.pbrMapSize[0]', bufferData.pbrMapSize);
152+
153+
materialBuffer.bind(0);
154+
}
155+
156+
function interleave(...arrays) {
157+
let maxLength = 0;
158+
for (let i = 0; i < arrays.length; i++) {
159+
const a = arrays[i];
160+
const l = a.data ? a.data.length / a.channels : 0;
161+
maxLength = Math.max(maxLength, l);
162+
}
163+
164+
const interleaved = [];
165+
for (let i = 0; i < maxLength; i++) {
166+
for (let j = 0; j < arrays.length; j++) {
167+
const { data = [], channels } = arrays[j];
168+
for (let c = 0; c < channels; c++) {
169+
interleaved.push(data[i * channels + c]);
170+
}
171+
}
172+
}
173+
174+
return interleaved;
175+
}

0 commit comments

Comments
 (0)