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

Commit

Permalink
Hybrid rendering (#62)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Lucas Crane authored Jan 30, 2020
1 parent d42ea7d commit c73d19b
Show file tree
Hide file tree
Showing 30 changed files with 1,121 additions and 677 deletions.
2 changes: 1 addition & 1 deletion src/RayTracingRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export function RayTracingRenderer(params = {}) {

const gl = canvas.getContext('webgl2', {
alpha: false,
depth: false,
depth: true,
stencil: false,
antialias: false,
powerPreference: 'high-performance',
Expand Down
12 changes: 8 additions & 4 deletions src/renderer/Framebuffer.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export function makeFramebuffer(gl, { attachments }) {
export function makeFramebuffer(gl, { color, depth }) {

const framebuffer = gl.createFramebuffer();

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

const drawBuffers = [];

for (let location in attachments) {
for (let location in color) {
location = Number(location);

if (location === undefined) {
console.error('invalid location');
}

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

gl.drawBuffers(drawBuffers);

if (depth) {
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, depth.target, depth.texture);
}

unbind();
}

init();

return {
attachments,
color,
bind,
unbind
};
Expand Down
8 changes: 7 additions & 1 deletion src/renderer/FullscreenQuad.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import vertex from './glsl/fullscreenQuad.vert';
import { makeVertexShader } from './RenderPass';

export function makeFullscreenQuad(gl) {
// TODO: use VAOs
const vao = gl.createVertexArray();

gl.bindVertexArray(vao);

gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1]), gl.STATIC_DRAW);

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

gl.bindVertexArray(null);

const vertexShader = makeVertexShader(gl, { vertex });

function draw() {
gl.bindVertexArray(vao);
gl.drawArrays(gl.TRIANGLES, 0, 6);
}

Expand Down
92 changes: 92 additions & 0 deletions src/renderer/GBufferPass.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { makeRenderPass } from './RenderPass';
import vertex from './glsl/gBuffer.vert';
import fragment from './glsl/gBuffer.frag';
import { Matrix4 } from 'three';

export function makeGBufferPass(gl, { materialBuffer, mergedMesh }) {
const renderPass = makeRenderPass(gl, {
defines: materialBuffer.defines,
vertex,
fragment
});

renderPass.setTexture('diffuseMap', materialBuffer.textures.diffuseMap);
renderPass.setTexture('normalMap', materialBuffer.textures.normalMap);
renderPass.setTexture('pbrMap', materialBuffer.textures.pbrMap);

const geometry = mergedMesh.geometry;

const elementCount = geometry.getIndex().count;

const vao = gl.createVertexArray();

gl.bindVertexArray(vao);
uploadAttributes(gl, renderPass, geometry);
gl.bindVertexArray(null);

let jitterX = 0;
let jitterY = 0;
function setJitter(x, y) {
jitterX = x;
jitterY = y;
}

let currentCamera;
function setCamera(camera) {
currentCamera = camera;
}

function calcCamera() {
projView.copy(currentCamera.projectionMatrix);

projView.elements[8] += 2 * jitterX;
projView.elements[9] += 2 * jitterY;

projView.multiply(currentCamera.matrixWorldInverse);
renderPass.setUniform('projView', projView.elements);
}

let projView = new Matrix4();

function draw() {
calcCamera();
gl.bindVertexArray(vao);
renderPass.useProgram();
gl.enable(gl.DEPTH_TEST);
gl.drawElements(gl.TRIANGLES, elementCount, gl.UNSIGNED_INT, 0);
gl.disable(gl.DEPTH_TEST);
}

return {
draw,
outputLocs: renderPass.outputLocs,
setCamera,
setJitter
};
}

function uploadAttributes(gl, renderPass, geometry) {
setAttribute(gl, renderPass.attribLocs.aPosition, geometry.getAttribute('position'));
setAttribute(gl, renderPass.attribLocs.aNormal, geometry.getAttribute('normal'));
setAttribute(gl, renderPass.attribLocs.aUv, geometry.getAttribute('uv'));
setAttribute(gl, renderPass.attribLocs.aMaterialMeshIndex, geometry.getAttribute('materialMeshIndex'));

gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, geometry.getIndex().array, gl.STATIC_DRAW);
}

function setAttribute(gl, location, bufferAttribute) {
const { itemSize, array } = bufferAttribute;

gl.enableVertexAttribArray(location);
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, array, gl.STATIC_DRAW);

if (array instanceof Float32Array) {
gl.vertexAttribPointer(location, itemSize, gl.FLOAT, false, 0, 0);
} else if (array instanceof Int32Array) {
gl.vertexAttribIPointer(location, itemSize, gl.INT, 0, 0);
} else {
throw 'Unsupported buffer type';
}
}
175 changes: 175 additions & 0 deletions src/renderer/MaterialBuffer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import { ThinMaterial, ThickMaterial, ShadowCatcherMaterial } from '../constants';
import materialBufferChunk from './glsl/chunks/materialBuffer.glsl';
import { makeUniformBuffer } from './UniformBuffer';
import { makeRenderPass } from "./RenderPass";
import { makeTexture } from './Texture';
import { getTexturesFromMaterials, mergeTexturesFromMaterials } from './texturesFromMaterials';

export function makeMaterialBuffer(gl, materials) {
const maps = getTexturesFromMaterials(materials, ['map', 'normalMap']);
const pbrMap = mergeTexturesFromMaterials(materials, ['roughnessMap', 'metalnessMap']);

const textures = {};

const bufferData = {};

bufferData.color = materials.map(m => m.color);
bufferData.roughness = materials.map(m => m.roughness);
bufferData.metalness = materials.map(m => m.metalness);
bufferData.normalScale = materials.map(m => m.normalScale);

bufferData.type = materials.map(m => {
if (m.shadowCatcher) {
return ShadowCatcherMaterial;
}
if (m.transparent) {
return m.solid ? ThickMaterial : ThinMaterial;
}
});

if (maps.map.textures.length > 0) {
const { relativeSizes, texture } = makeTextureArray(gl, maps.map.textures, true);
textures.diffuseMap = texture;
bufferData.diffuseMapSize = relativeSizes;
bufferData.diffuseMapIndex = maps.map.indices;
}

if (maps.normalMap.textures.length > 0) {
const { relativeSizes, texture } = makeTextureArray(gl, maps.normalMap.textures, false);
textures.normalMap = texture;
bufferData.normalMapSize = relativeSizes;
bufferData.normalMapIndex = maps.normalMap.indices;
}

if (pbrMap.textures.length > 0) {
const { relativeSizes, texture } = makeTextureArray(gl, pbrMap.textures, false);
textures.pbrMap = texture;
bufferData.pbrMapSize = relativeSizes;
bufferData.roughnessMapIndex = pbrMap.indices.roughnessMap;
bufferData.metalnessMapIndex = pbrMap.indices.metalnessMap;
}

const defines = {
NUM_MATERIALS: materials.length,
NUM_DIFFUSE_MAPS: maps.map.textures.length,
NUM_NORMAL_MAPS: maps.normalMap.textures.length,
NUM_DIFFUSE_NORMAL_MAPS: Math.max(maps.map.textures.length, maps.normalMap.textures.length),
NUM_PBR_MAPS: pbrMap.textures.length,
};

// create temporary shader program including the Material uniform buffer
// used to query the compiled structure of the uniform buffer
const renderPass = makeRenderPass(gl, {
vertex: {
source: `void main() {}`
},
fragment: {
includes: [ materialBufferChunk ],
source: `void main() {}`
},
defines
});

uploadToUniformBuffer(gl, renderPass.program, bufferData);

return { defines, textures };
}

function makeTextureArray(gl, textures, gammaCorrection = false) {
const images = textures.map(t => t.image);
const flipY = textures.map(t => t.flipY);
const { maxSize, relativeSizes } = maxImageSize(images);

// create GL Array Texture from individual textures
const texture = makeTexture(gl, {
width: maxSize.width,
height: maxSize.height,
gammaCorrection,
data: images,
flipY,
channels: 3,
minFilter: gl.LINEAR,
magFilter: gl.LINEAR,
});

return {
texture,
relativeSizes
};
}

function maxImageSize(images) {
const maxSize = {
width: 0,
height: 0
};

for (const image of images) {
maxSize.width = Math.max(maxSize.width, image.width);
maxSize.height = Math.max(maxSize.height, image.height);
}

const relativeSizes = [];
for (const image of images) {
relativeSizes.push(image.width / maxSize.width);
relativeSizes.push(image.height / maxSize.height);
}

return { maxSize, relativeSizes };
}


// Upload arrays to uniform buffer objects
// Packs different arrays into vec4's to take advantage of GLSL's std140 memory layout

function uploadToUniformBuffer(gl, program, bufferData) {
const materialBuffer = makeUniformBuffer(gl, program, 'Materials');

materialBuffer.set('Materials.colorAndMaterialType[0]', interleave(
{ data: [].concat(...bufferData.color.map(d => d.toArray())), channels: 3 },
{ data: bufferData.type, channels: 1}
));

materialBuffer.set('Materials.roughnessMetalnessNormalScale[0]', interleave(
{ data: bufferData.roughness, channels: 1 },
{ data: bufferData.metalness, channels: 1 },
{ data: [].concat(...bufferData.normalScale.map(d => d.toArray())), channels: 2 }
));

materialBuffer.set('Materials.diffuseNormalRoughnessMetalnessMapIndex[0]', interleave(
{ data: bufferData.diffuseMapIndex, channels: 1 },
{ data: bufferData.normalMapIndex, channels: 1 },
{ data: bufferData.roughnessMapIndex, channels: 1 },
{ data: bufferData.metalnessMapIndex, channels: 1 }
));

materialBuffer.set('Materials.diffuseNormalMapSize[0]', interleave(
{ data: bufferData.diffuseMapSize, channels: 2 },
{ data: bufferData.normalMapSize, channels: 2 }
));

materialBuffer.set('Materials.pbrMapSize[0]', bufferData.pbrMapSize);

materialBuffer.bind(0);
}

function interleave(...arrays) {
let maxLength = 0;
for (let i = 0; i < arrays.length; i++) {
const a = arrays[i];
const l = a.data ? a.data.length / a.channels : 0;
maxLength = Math.max(maxLength, l);
}

const interleaved = [];
for (let i = 0; i < maxLength; i++) {
for (let j = 0; j < arrays.length; j++) {
const { data = [], channels } = arrays[j];
for (let c = 0; c < channels; c++) {
interleaved.push(data[i * channels + c]);
}
}
}

return interleaved;
}
Loading

0 comments on commit c73d19b

Please sign in to comment.