UNPKG

@xeokit/xeokit-sdk

Version:

3D BIM IFC Viewer SDK for AEC engineering applications. Open Source JavaScript Toolkit based on pure WebGL for top performance, real-world coordinates and full double precision

1,266 lines (1,029 loc) 77.1 kB
import {FrameContext} from './FrameContext.js'; import {math} from '../math/math.js'; import {stats} from '../stats.js'; import {WEBGL_INFO} from '../webglInfo.js'; import {Map} from "../utils/Map.js"; import {PickResult} from "./PickResult.js"; import {OcclusionTester} from "./occlusion/OcclusionTester.js"; import {createRTCViewMat} from "../math/rtcCoords.js"; import {RenderBuffer} from "./RenderBuffer.js"; import {getExtension} from "./getExtension.js"; import {createProgramVariablesState} from "./WebGLRenderer.js"; import {ArrayBuf} from "./ArrayBuf.js"; const OCCLUSION_TEST_MODE = false; const vec3_0 = math.vec3([0,0,0]); const iota = (n) => { const ret = [ ]; for (let i = 0; i < n; ++i) ret.push(i); return ret; }; const tempPlanes = iota(6).map(() => math.vec4()); const tempVec4 = math.vec4(); const bitShiftScreenZ = math.vec4([1.0 / (256.0 * 256.0 * 256.0 * 256.0), 1.0 / (256.0 * 256.0 * 256.0), 1.0 / (256.0 * 256.0), 1.0 / 256.0]); const pixelToInt = pix => pix[0] + (pix[1] << 8) + (pix[2] << 16) + (pix[3] << 24); const toWorldNormal = (n) => math.normalizeVec3(math.divVec3Scalar(n, math.MAX_INT, math.vec3())); const toWorldPos = (p, origin, scale) => math.vec3([ p[0] * scale[0] + origin[0], p[1] * scale[1] + origin[1], p[2] * scale[2] + origin[2] ]); const makeFrustumAABBIntersectionTest = function(camera) { const m = math.mat4(); math.mulMat4(camera.projMatrix, camera.viewMatrix, m); for (let i = 0; i < 3; ++i) { tempPlanes[i * 2 + 0][0] = m[ 3] + m[i]; tempPlanes[i * 2 + 0][1] = m[ 7] + m[i + 4]; tempPlanes[i * 2 + 0][2] = m[11] + m[i + 8]; tempPlanes[i * 2 + 0][3] = m[15] + m[i + 12]; tempPlanes[i * 2 + 1][0] = m[ 3] - m[i]; tempPlanes[i * 2 + 1][1] = m[ 7] - m[i + 4]; tempPlanes[i * 2 + 1][2] = m[11] - m[i + 8]; tempPlanes[i * 2 + 1][3] = m[15] - m[i + 12]; } // Normalize each plane tempPlanes.forEach(p => math.divVec4Scalar(p, math.lenVec3(p), p)); return aabb => tempPlanes.every(p => { // Compute the positive vertex (farthest in direction of normal) tempVec4[0] = aabb[(p[0] >= 0) ? 3 : 0]; tempVec4[1] = aabb[(p[1] >= 0) ? 4 : 1]; tempVec4[2] = aabb[(p[2] >= 0) ? 5 : 2]; tempVec4[3] = 1; return math.dotVec4(p, tempVec4) >= 0; }); }; const makeRayAABBIntersectionTest = (O, D) => aabb => { let tmin = -Infinity; let tmax = Infinity; for (let i = 0; i < 3; i++) { const origin = O[i]; const direction = D[i]; const minBound = aabb[i]; const maxBound = aabb[i + 3]; if (Math.abs(direction) < 1e-8) { // Ray is parallel to plane if ((origin < minBound) || (origin > maxBound)) { return false; // No intersection } } else { const t1 = (minBound - origin) / direction; const t2 = (maxBound - origin) / direction; tmin = Math.max(tmin, Math.min(t1, t2)); // near tmax = Math.min(tmax, Math.max(t1, t2)); // far if (tmin > tmax) { return false; // No intersection } } } return tmax >= 0; // Check if intersection is in front of ray }; /** * @private */ const Renderer = function (scene, options) { options = options || {}; const frameCtx = new FrameContext(); const canvas = scene.canvas.canvas; /** * @type {WebGL2RenderingContext} */ const gl = scene.canvas.gl; const canvasTransparent = (!!options.transparent); const alphaDepthMask = options.alphaDepthMask; const pickIDs = new Map({}); let drawableTypeInfo = {}; let drawables = {}; let postSortDrawableList = []; let postCullDrawableList = []; let uiDrawableList = []; let drawableListDirty = true; let stateSortDirty = true; let imageDirty = true; let shadowsDirty = true; let transparentEnabled = true; let edgesEnabled = true; let saoEnabled = true; let pbrEnabled = true; let colorTextureEnabled = true; const renderBufferManager = (function() { const renderBuffersBasic = {}; const renderBuffersScaled = {}; return { getRenderBuffer: (id, colorFormats, hasDepthTexture) => { const renderBuffers = (scene.canvas.resolutionScale === 1.0) ? renderBuffersBasic : renderBuffersScaled; if (! renderBuffers[id]) { renderBuffers[id] = new RenderBuffer(gl, colorFormats, hasDepthTexture); } return renderBuffers[id]; }, destroy: () => { Object.values(renderBuffersBasic ).forEach(buf => buf.destroy()); Object.values(renderBuffersScaled).forEach(buf => buf.destroy()); } }; })(); const SAOProgram = (gl, name, programVariablesState, createOutColorDefinition) => { const programVariables = programVariablesState.programVariables; const uViewportInv = programVariables.createUniform("vec2", "uViewportInv"); const uCameraNear = programVariables.createUniform("float", "uCameraNear"); const uCameraFar = programVariables.createUniform("float", "uCameraFar"); const uDepthTexture = programVariables.createUniform("sampler2D", "uDepthTexture"); const uv = programVariables.createAttribute("vec2", "uv"); const vUV = programVariables.createVarying("vec2", "vUV", () => uv); const outColor = programVariables.createOutput("vec4", "outColor"); const getOutColor = programVariables.createFragmentDefinition( "getOutColor", (name, src) => { const getDepth = "getDepth"; src.push(` float ${getDepth}(const in vec2 uv) { return texture(${uDepthTexture}, uv).r; } `); src.push(createOutColorDefinition(name, vUV, uViewportInv, uCameraNear, uCameraFar, getDepth)); }); const [program, errors] = programVariablesState.buildProgram( gl, name, { clipPos: `vec4(2.0 * ${uv} - 1.0, 0.0, 1.0)`, appendFragmentOutputs: (src) => src.push(`${outColor} = ${getOutColor}();`) }); if (errors) { console.error(errors.join("\n")); throw errors; } else { const uvs = new Float32Array([1,1, 0,1, 0,0, 1,0]); const uvBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, uvs, uvs.length, 2, gl.STATIC_DRAW); // Mitigation: if Uint8Array is used, the geometry is corrupted on OSX when using Chrome with data-textures const indices = new Uint32Array([0, 1, 2, 0, 2, 3]); const indicesBuf = new ArrayBuf(gl, gl.ELEMENT_ARRAY_BUFFER, indices, indices.length, 1, gl.STATIC_DRAW); return { destroy: program.destroy, bind: (viewportSize, project, depthTexture) => { program.bind(); uViewportInv.setInputValue([1 / viewportSize[0], 1 / viewportSize[1]]); uCameraNear.setInputValue(project.near); uCameraFar.setInputValue(project.far); uDepthTexture.setInputValue(depthTexture); uv.setInputValue(uvBuf); }, draw: () => { indicesBuf.bind(); gl.drawElements(gl.TRIANGLES, indicesBuf.numItems, indicesBuf.itemType, 0); } }; } }; // SAO implementation inspired from previous SAO work in THREE.js by ludobaka / ludobaka.github.io and bhouston const saoOcclusionRenderer = (function() { let currentRenrerer = null; let curNumSamples = null; return { destroy: () => currentRenrerer && currentRenrerer.destroy(), render: (viewportSize, project, sao, depthTexture) => { const numSamples = Math.floor(sao.numSamples); if (curNumSamples !== numSamples) { currentRenrerer && currentRenrerer.destroy(); const programVariablesState = createProgramVariablesState(); const programVariables = programVariablesState.programVariables; const uProjectMatrix = programVariables.createUniform("mat4", "uProjectMatrix"); const uInvProjMatrix = programVariables.createUniform("mat4", "uInvProjMatrix"); const uPerspective = programVariables.createUniform("bool", "uPerspective"); const uScale = programVariables.createUniform("float", "uScale"); const uIntensity = programVariables.createUniform("float", "uIntensity"); const uBias = programVariables.createUniform("float", "uBias"); const uKernelRadius = programVariables.createUniform("float", "uKernelRadius"); const uMinResolution = programVariables.createUniform("float", "uMinResolution"); const uRandomSeed = programVariables.createUniform("float", "uRandomSeed"); const program = SAOProgram( gl, "SAOOcclusionRenderer", programVariablesState, (name, vUV, uViewportInv, uCameraNear, uCameraFar, getDepth) => { return ` #define EPSILON 1e-6 #define PI 3.14159265359 #define PI2 6.28318530718 #define NUM_SAMPLES ${numSamples} #define NUM_RINGS 4 const vec3 packFactors = vec3(256. * 256. * 256., 256. * 256., 256.); vec4 packFloatToRGBA(const in float v) { vec4 r = vec4(fract(v * packFactors), v); r.yzw -= r.xyz / 256.; return r * 256. / 255.; } highp float rand(const in vec2 uv) { const highp float a = 12.9898, b = 78.233, c = 43758.5453; return fract(sin(mod(dot(uv, vec2(a, b)), PI)) * c); } vec3 getViewPos(const in vec2 screenPos, const in float depth) { float near = ${uCameraNear}; float far = ${uCameraFar}; float viewZ = (${uPerspective} ? ((near * far) / ((far - near) * depth - far)) : (depth * (near - far) - near)); float clipW = ${uProjectMatrix}[2][3] * viewZ + ${uProjectMatrix}[3][3]; return (${uInvProjMatrix} * (clipW * vec4((vec3(screenPos, depth) - 0.5) * 2.0, 1.0))).xyz; } vec4 ${name}() { float centerDepth = ${getDepth}(${vUV}); if (centerDepth >= (1.0 - EPSILON)) { discard; } vec3 centerViewPosition = getViewPos(${vUV}, centerDepth); float scaleDividedByCameraFar = ${uScale} / ${uCameraFar}; float minResolutionMultipliedByCameraFar = ${uMinResolution} * ${uCameraFar}; vec3 centerViewNormal = normalize(cross(dFdx(centerViewPosition), dFdy(centerViewPosition))); vec2 radiusStep = ${uKernelRadius} * ${uViewportInv} / float(NUM_SAMPLES); vec2 radius = radiusStep; const float angleStep = PI2 * float(NUM_RINGS) / float(NUM_SAMPLES); float angle = PI2 * rand(${vUV} + ${uRandomSeed}); float occlusionSum = 0.0; float weightSum = 0.0; for (int i = 0; i < NUM_SAMPLES; i++) { vec2 sampleUv = ${vUV} + vec2(cos(angle), sin(angle)) * radius; radius += radiusStep; angle += angleStep; float sampleDepth = ${getDepth}(sampleUv); if (sampleDepth >= (1.0 - EPSILON)) { continue; } vec3 sampleViewPosition = getViewPos(sampleUv, sampleDepth); vec3 viewDelta = sampleViewPosition - centerViewPosition; float scaledScreenDistance = scaleDividedByCameraFar * length(viewDelta); occlusionSum += max(0.0, (dot(centerViewNormal, viewDelta) - minResolutionMultipliedByCameraFar) / scaledScreenDistance - ${uBias}) / (1.0 + scaledScreenDistance * scaledScreenDistance ); weightSum += 1.0; } return packFloatToRGBA(1.0 - occlusionSum * ${uIntensity} / weightSum); }`; }); currentRenrerer = { destroy: program.destroy, render: (viewportSize, project, sao, depthTexture) => { program.bind(viewportSize, project, depthTexture); uProjectMatrix.setInputValue(project.matrix); uInvProjMatrix.setInputValue(project.inverseMatrix); uPerspective.setInputValue(project.type === "Perspective"); uScale.setInputValue(sao.scale * project.far / 5); uIntensity.setInputValue(sao.intensity); uBias.setInputValue(sao.bias); uKernelRadius.setInputValue(sao.kernelRadius); uMinResolution.setInputValue(sao.minResolution); uRandomSeed.setInputValue(Math.random()); program.draw(); } }; curNumSamples = numSamples; } currentRenrerer.render(viewportSize, project, sao, depthTexture); } }; })(); const saoDepthLimitedBlurRenderer = (function() { const blurStdDev = 4; const blurDepthCutoff = 0.01; const KERNEL_RADIUS = 16; const createSampleOffsets = (uvIncrement) => { const offsets = []; for (let i = 0; i <= KERNEL_RADIUS + 1; i++) { offsets.push(uvIncrement[0] * i); offsets.push(uvIncrement[1] * i); } return new Float32Array(offsets); }; const sampleOffsetsVer = createSampleOffsets([0, 1]); const sampleOffsetsHor = createSampleOffsets([1, 0]); const gaussian = (i, stdDev) => Math.exp(-(i * i) / (2.0 * (stdDev * stdDev))) / (Math.sqrt(2.0 * Math.PI) * stdDev); const sampleWeights = new Float32Array(iota(KERNEL_RADIUS + 1).map(i => gaussian(i, blurStdDev))); // TODO: Optimize const programVariablesState = createProgramVariablesState(); const programVariables = programVariablesState.programVariables; const uDepthCutoff = programVariables.createUniform("float", "uDepthCutoff"); const uSampleOffsets = programVariables.createUniformArray("vec2", "uSampleOffsets", KERNEL_RADIUS + 1); const uSampleWeights = programVariables.createUniformArray("float", "uSampleWeights", KERNEL_RADIUS + 1); const uOcclusionTex = programVariables.createUniform("sampler2D", "uOcclusionTex"); const program = SAOProgram( gl, "SAODepthLimitedBlurRenderer", programVariablesState, (name, vUV, uViewportInv, uCameraNear, uCameraFar, getDepth) => { return ` #define EPSILON 1e-6 const vec3 packFactors = vec3(256. * 256. * 256., 256. * 256., 256.); vec4 packFloatToRGBA(const in float v) { vec4 r = vec4(fract(v * packFactors), v); r.yzw -= r.xyz / 256.; return r * 256. / 255.; } float getOcclusion(const in vec2 uv) { vec4 v = texture(${uOcclusionTex}, uv); return dot(floor(v * 255.0 + 0.5) / 255.0, 255. / 256. / vec4(packFactors, 1.)); // unpackRGBAToFloat } float getViewZ(const in float depth) { return (${uCameraNear} * ${uCameraFar}) / ((${uCameraFar} - ${uCameraNear}) * depth - ${uCameraFar}); } vec4 ${name}() { float centerDepth = ${getDepth}(${vUV}); if (centerDepth >= (1.0 - EPSILON)) { discard; } float centerViewZ = getViewZ(centerDepth); bool rBreak = false; bool lBreak = false; float weightSum = ${uSampleWeights}[0]; float occlusionSum = getOcclusion(${vUV}) * weightSum; for (int i = 1; i <= ${KERNEL_RADIUS}; i++) { float sampleWeight = ${uSampleWeights}[i]; vec2 sampleUVOffset = ${uSampleOffsets}[i] * ${uViewportInv}; if (! rBreak) { vec2 rSampleUV = ${vUV} + sampleUVOffset; if (abs(centerViewZ - getViewZ(${getDepth}(rSampleUV))) > ${uDepthCutoff}) { rBreak = true; } else { occlusionSum += getOcclusion(rSampleUV) * sampleWeight; weightSum += sampleWeight; } } if (! lBreak) { vec2 lSampleUV = ${vUV} - sampleUVOffset; if (abs(centerViewZ - getViewZ(${getDepth}(lSampleUV))) > ${uDepthCutoff}) { lBreak = true; } else { occlusionSum += getOcclusion(lSampleUV) * sampleWeight; weightSum += sampleWeight; } } } return packFloatToRGBA(occlusionSum / weightSum); }`; }); return { destroy: program.destroy, render: (viewportSize, project, direction, depthTexture, occlusionTexture) => { program.bind(viewportSize, project, depthTexture); uDepthCutoff.setInputValue(blurDepthCutoff); uSampleOffsets.setInputValue((direction === 0) ? sampleOffsetsHor : sampleOffsetsVer); uSampleWeights.setInputValue(sampleWeights); uOcclusionTex.setInputValue(occlusionTexture); program.draw(); } }; })(); const getSceneCameraViewParams = (function() { let params = null; // scene.camera not defined yet return function() { if (! params) { const camera = scene.camera; params = { get eye() { return camera.eye; }, get far() { return camera.project.far; }, get projMatrix() { return camera.projMatrix; }, get viewMatrix() { return camera.viewMatrix; }, get viewNormalMatrix() { return camera.viewNormalMatrix; } }; } return params; }; })(); const getNearPlaneHeight = (camera, drawingBufferHeight) => ((camera.projection === "ortho") ? 1.0 : (drawingBufferHeight / (2 * Math.tan(0.5 * camera.perspective.fov * Math.PI / 180.0)))); this.scene = scene; this._occlusionTester = null; // Lazy-created in #addMarker() this.capabilities = { astcSupported: !!getExtension(gl, 'WEBGL_compressed_texture_astc'), etc1Supported: true, // WebGL2 etc2Supported: !!getExtension(gl, 'WEBGL_compressed_texture_etc'), dxtSupported: !!getExtension(gl, 'WEBGL_compressed_texture_s3tc'), bptcSupported: !!getExtension(gl, 'EXT_texture_compression_bptc'), pvrtcSupported: !!(getExtension(gl, 'WEBGL_compressed_texture_pvrtc') || getExtension(gl, 'WEBKIT_WEBGL_compressed_texture_pvrtc')) }; this.setTransparentEnabled = function (enabled) { transparentEnabled = enabled; imageDirty = true; }; this.setEdgesEnabled = function (enabled) { edgesEnabled = enabled; imageDirty = true; }; this.setSAOEnabled = function (enabled) { saoEnabled = enabled; imageDirty = true; }; this.setPBREnabled = function (enabled) { pbrEnabled = enabled; imageDirty = true; }; this.setColorTextureEnabled = function (enabled) { colorTextureEnabled = enabled; imageDirty = true; }; this.needStateSort = function () { stateSortDirty = true; }; this.shadowsDirty = function () { shadowsDirty = true; }; this.imageDirty = function () { imageDirty = true; }; /** * Inserts a drawable into this renderer. * @private */ this.addDrawable = function (id, drawable) { const type = drawable.type; if (!type) { console.error("Renderer#addDrawable() : drawable with ID " + id + " has no 'type' - ignoring"); return; } let drawableInfo = drawableTypeInfo[type]; if (!drawableInfo) { drawableInfo = { type: drawable.type, count: 0, isStateSortable: drawable.isStateSortable, stateSortCompare: drawable.stateSortCompare, drawableMap: {}, drawableListPreCull: [], drawableList: [] }; drawableTypeInfo[type] = drawableInfo; } drawableInfo.count++; drawableInfo.drawableMap[id] = drawable; drawables[id] = drawable; drawableListDirty = true; }; /** * Removes a drawable from this renderer. * @private */ this.removeDrawable = function (id) { const drawable = drawables[id]; if (!drawable) { console.error("Renderer#removeDrawable() : drawable not found with ID " + id + " - ignoring"); return; } const type = drawable.type; const drawableInfo = drawableTypeInfo[type]; if (--drawableInfo.count <= 0) { delete drawableTypeInfo[type]; } else { delete drawableInfo.drawableMap[id]; } delete drawables[id]; drawableListDirty = true; }; /** * Gets a unique pick ID for the given Pickable. A Pickable can be a {@link Mesh} or a {@link PerformanceMesh}. * @returns {Number} New pick ID. */ this.getPickID = function (entity) { return pickIDs.addItem(entity); }; /** * Released a pick ID for reuse. * @param {Number} pickID Pick ID to release. */ this.putPickID = function (pickID) { pickIDs.removeItem(pickID); }; /** * Clears the canvas. * @private */ this.clear = function (params) { params = params || {}; gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); if (canvasTransparent) { gl.clearColor(1, 1, 1, 1); } else { const backgroundColor = scene.canvas.backgroundColorFromAmbientLight ? this.lights.getAmbientColorAndIntensity() : scene.canvas.backgroundColor; gl.clearColor(backgroundColor[0], backgroundColor[1], backgroundColor[2], 1.0); } gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); }; /** * Returns true if the next call to render() will draw something * @returns {Boolean} */ this.needsRender = function () { const needsRender = (imageDirty || drawableListDirty || stateSortDirty); return needsRender; } /** * Renders inserted drawables. * @private */ this.render = function(pass, clear) { updateDrawlist(); if (imageDirty) { draw(pass, clear); stats.frame.frameCount++; imageDirty = false; } }; function updateDrawlist() { // Prepares state-sorted array of drawables from maps of inserted drawables if (drawableListDirty) { Object.values(drawableTypeInfo).forEach(drawableInfo => { const drawableListPreCull = drawableInfo.drawableListPreCull; let lenDrawableList = 0; Object.values(drawableInfo.drawableMap).forEach(drawable => { drawableListPreCull[lenDrawableList++] = drawable; }); drawableListPreCull.length = lenDrawableList; }); drawableListDirty = false; stateSortDirty = true; } if (stateSortDirty) { let lenDrawableList = 0; Object.values(drawableTypeInfo).forEach(drawableInfo => { drawableInfo.drawableListPreCull.forEach(drawable => { postSortDrawableList[lenDrawableList++] = drawable; }); }); postSortDrawableList.length = lenDrawableList; postSortDrawableList.sort((a, b) => a.renderOrder - b.renderOrder); stateSortDirty = false; imageDirty = true; } if (imageDirty) { // Image is usually dirty because the camera moved let lenDrawableList = 0; let lenUiList = 0; postSortDrawableList.forEach(drawable => { drawable.rebuildRenderFlags(); if (!drawable.renderFlags.culled) { if (drawable.isUI) { uiDrawableList[lenUiList++] = drawable; } else { postCullDrawableList[lenDrawableList++] = drawable; } } }); postCullDrawableList.length = lenDrawableList; uiDrawableList.length = lenUiList; } } function draw(pass, clear) { const sao = scene.sao; const occlusionTexture = saoEnabled && sao.possible && (sao.numSamples >= 1) && drawSAOBuffers(pass); scene._lightsState.lights.forEach(light => { if (light.castsShadow) { const shadowRenderBuf = light.getShadowRenderBuf(); shadowRenderBuf.bind(); frameCtx.reset(); frameCtx.backfaces = true; frameCtx.frontface = true; frameCtx.viewParams.viewMatrix = light.getShadowViewMatrix(); frameCtx.viewParams.projMatrix = light.getShadowProjMatrix(); frameCtx.nearPlaneHeight = getNearPlaneHeight(scene.camera, gl.drawingBufferHeight); gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); gl.clearColor(0, 0, 0, 1); gl.enable(gl.DEPTH_TEST); gl.disable(gl.BLEND); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); Object.values(drawableTypeInfo).forEach( drawableInfo => { drawableInfo.drawableList.forEach( drawable => { if ((drawable.visible !== false) && drawable.castsShadow && drawable.drawShadow) { if (drawable.renderFlags.colorOpaque) { // Transparent objects don't cast shadows (yet) drawable.drawShadow(frameCtx); } } }); }); shadowRenderBuf.unbind(); } }); // const numVertexAttribs = WEBGL_INFO.MAX_VERTEX_ATTRIBS; // Fixes https://github.com/xeokit/xeokit-sdk/issues/174 // for (let ii = 0; ii < numVertexAttribs; ii++) { // gl.disableVertexAttribArray(ii); // } // shadowsDirty = false; drawColor(pass, clear, occlusionTexture); } function drawSAOBuffers(pass) { const sao = scene.sao; const size = [gl.drawingBufferWidth, gl.drawingBufferHeight]; // Render depth buffer const saoDepthRenderBuffer = renderBufferManager.getRenderBuffer("saoDepth", [], true); saoDepthRenderBuffer.setSize(size); saoDepthRenderBuffer.bind(); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); frameCtx.reset(); frameCtx.pass = pass; frameCtx.viewParams = getSceneCameraViewParams(); frameCtx.nearPlaneHeight = getNearPlaneHeight(scene.camera, gl.drawingBufferHeight); gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); gl.clearColor(0, 0, 0, 0); gl.enable(gl.DEPTH_TEST); gl.frontFace(gl.CCW); gl.enable(gl.CULL_FACE); gl.depthMask(true); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); postCullDrawableList.forEach(drawable => { if (!drawable.culled && drawable.visible && drawable.drawDepth && drawable.saoEnabled && drawable.renderFlags.colorOpaque) { drawable.drawDepth(frameCtx); } }); saoDepthRenderBuffer.unbind(); const depthTexture = saoDepthRenderBuffer.depthTexture; // Render occlusion buffer const occlusionRenderBuffer1 = renderBufferManager.getRenderBuffer("saoOcclusion"); occlusionRenderBuffer1.setSize(size); occlusionRenderBuffer1.bind(); gl.viewport(0, 0, size[0], size[1]); gl.clearColor(0, 0, 0, 1); gl.disable(gl.DEPTH_TEST); gl.disable(gl.BLEND); gl.frontFace(gl.CCW); gl.clear(gl.COLOR_BUFFER_BIT); saoOcclusionRenderer.render(size, scene.camera.project, sao, depthTexture); occlusionRenderBuffer1.unbind(); if (sao.blur) { const occlusionRenderBuffer2 = renderBufferManager.getRenderBuffer("saoOcclusion2"); occlusionRenderBuffer2.setSize(size); const blurSAO = (src, dst, direction) => { dst.bind(); gl.viewport(0, 0, size[0], size[1]); gl.clearColor(0, 0, 0, 1); gl.enable(gl.DEPTH_TEST); gl.disable(gl.BLEND); gl.frontFace(gl.CCW); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); const project = scene.camera.project; saoDepthLimitedBlurRenderer.render(size, project, direction, depthTexture, src.colorTextures[0]); dst.unbind(); }; blurSAO(occlusionRenderBuffer1, occlusionRenderBuffer2, 0); // horizontally blurSAO(occlusionRenderBuffer2, occlusionRenderBuffer1, 1); // vertically } return occlusionRenderBuffer1.colorTextures[0]; } function drawColor(pass, clear, occlusionTexture) { const normalDrawSAOBin = []; const normalEdgesOpaqueBin = []; const normalFillTransparentBin = []; const normalEdgesTransparentBin = []; const xrayedFillOpaqueBin = []; const xrayEdgesOpaqueBin = []; const xrayedFillTransparentBin = []; const xrayEdgesTransparentBin = []; const highlightedFillOpaqueBin = []; const highlightedEdgesOpaqueBin = []; const highlightedFillTransparentBin = []; const highlightedEdgesTransparentBin = []; const selectedFillOpaqueBin = []; const selectedEdgesOpaqueBin = []; const selectedFillTransparentBin = []; const selectedEdgesTransparentBin = []; const ambientColorAndIntensity = scene._lightsState.getAmbientColorAndIntensity(); frameCtx.reset(); frameCtx.pass = pass; frameCtx.withSAO = false; frameCtx.pbrEnabled = pbrEnabled && !!scene.pbrEnabled; frameCtx.colorTextureEnabled = colorTextureEnabled && !!scene.colorTextureEnabled; frameCtx.viewParams = getSceneCameraViewParams(); frameCtx.nearPlaneHeight = getNearPlaneHeight(scene.camera, gl.drawingBufferHeight); gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); if (canvasTransparent) { gl.clearColor(0, 0, 0, 0); } else { const backgroundColor = scene.canvas.backgroundColorFromAmbientLight ? ambientColorAndIntensity : scene.canvas.backgroundColor; gl.clearColor(backgroundColor[0], backgroundColor[1], backgroundColor[2], 1.0); } gl.enable(gl.DEPTH_TEST); gl.frontFace(gl.CCW); gl.enable(gl.CULL_FACE); gl.depthMask(true); gl.lineWidth(1); frameCtx.lineWidth = 1; const sao = scene.sao; frameCtx.saoParams = [gl.drawingBufferWidth, gl.drawingBufferHeight, scene.sao.blendCutoff, scene.sao.blendFactor]; frameCtx.occlusionTexture = occlusionTexture; let i; let len; let drawable; const startTime = Date.now(); if (clear) { gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); } frameCtx.testAABB = makeFrustumAABBIntersectionTest(scene.camera); const renderDrawables = function(drawables) { let normalDrawSAOBinLen = 0; let normalEdgesOpaqueBinLen = 0; let normalFillTransparentBinLen = 0; let normalEdgesTransparentBinLen = 0; let xrayedFillOpaqueBinLen = 0; let xrayEdgesOpaqueBinLen = 0; let xrayedFillTransparentBinLen = 0; let xrayEdgesTransparentBinLen = 0; let highlightedFillOpaqueBinLen = 0; let highlightedEdgesOpaqueBinLen = 0; let highlightedFillTransparentBinLen = 0; let highlightedEdgesTransparentBinLen = 0; let selectedFillOpaqueBinLen = 0; let selectedEdgesOpaqueBinLen = 0; let selectedFillTransparentBinLen = 0; let selectedEdgesTransparentBinLen = 0; //------------------------------------------------------------------------------------------------------ // Render normal opaque solids, defer others to bins to render after //------------------------------------------------------------------------------------------------------ for (let i = 0, len = drawables.length; i < len; i++) { drawable = drawables[i]; if (drawable.culled === true || drawable.visible === false) { continue; } const renderFlags = drawable.renderFlags; if (renderFlags.colorOpaque) { if (drawable.saoEnabled && occlusionTexture) { normalDrawSAOBin[normalDrawSAOBinLen++] = drawable; } else { drawable.drawColorOpaque(frameCtx); } } if (transparentEnabled) { if (renderFlags.colorTransparent) { normalFillTransparentBin[normalFillTransparentBinLen++] = drawable; } } if (renderFlags.xrayedSilhouetteTransparent) { xrayedFillTransparentBin[xrayedFillTransparentBinLen++] = drawable; } if (renderFlags.xrayedSilhouetteOpaque) { xrayedFillOpaqueBin[xrayedFillOpaqueBinLen++] = drawable; } if (renderFlags.highlightedSilhouetteTransparent) { highlightedFillTransparentBin[highlightedFillTransparentBinLen++] = drawable; } if (renderFlags.highlightedSilhouetteOpaque) { highlightedFillOpaqueBin[highlightedFillOpaqueBinLen++] = drawable; } if (renderFlags.selectedSilhouetteTransparent) { selectedFillTransparentBin[selectedFillTransparentBinLen++] = drawable; } if (renderFlags.selectedSilhouetteOpaque) { selectedFillOpaqueBin[selectedFillOpaqueBinLen++] = drawable; } if (drawable.edges && edgesEnabled) { if (renderFlags.edgesOpaque) { normalEdgesOpaqueBin[normalEdgesOpaqueBinLen++] = drawable; } if (renderFlags.edgesTransparent) { normalEdgesTransparentBin[normalEdgesTransparentBinLen++] = drawable; } if (renderFlags.selectedEdgesTransparent) { selectedEdgesTransparentBin[selectedEdgesTransparentBinLen++] = drawable; } if (renderFlags.selectedEdgesOpaque) { selectedEdgesOpaqueBin[selectedEdgesOpaqueBinLen++] = drawable; } if (renderFlags.xrayedEdgesTransparent) { xrayEdgesTransparentBin[xrayEdgesTransparentBinLen++] = drawable; } if (renderFlags.xrayedEdgesOpaque) { xrayEdgesOpaqueBin[xrayEdgesOpaqueBinLen++] = drawable; } if (renderFlags.highlightedEdgesTransparent) { highlightedEdgesTransparentBin[highlightedEdgesTransparentBinLen++] = drawable; } if (renderFlags.highlightedEdgesOpaque) { highlightedEdgesOpaqueBin[highlightedEdgesOpaqueBinLen++] = drawable; } } } //------------------------------------------------------------------------------------------------------ // Render deferred bins //------------------------------------------------------------------------------------------------------ // Opaque color with SAO if (normalDrawSAOBinLen > 0) { frameCtx.withSAO = true; for (i = 0; i < normalDrawSAOBinLen; i++) { normalDrawSAOBin[i].drawColorOpaque(frameCtx); } } // Opaque edges if (normalEdgesOpaqueBinLen > 0) { for (i = 0; i < normalEdgesOpaqueBinLen; i++) { normalEdgesOpaqueBin[i].drawEdgesColorOpaque(frameCtx); } } // Opaque X-ray fill if (xrayedFillOpaqueBinLen > 0) { for (i = 0; i < xrayedFillOpaqueBinLen; i++) { xrayedFillOpaqueBin[i].drawSilhouetteXRayed(frameCtx); } } // Opaque X-ray edges if (xrayEdgesOpaqueBinLen > 0) { for (i = 0; i < xrayEdgesOpaqueBinLen; i++) { xrayEdgesOpaqueBin[i].drawEdgesXRayed(frameCtx); } } // Transparent if (xrayedFillTransparentBinLen > 0 || xrayEdgesTransparentBinLen > 0 || normalFillTransparentBinLen > 0 || normalEdgesTransparentBinLen > 0) { gl.enable(gl.CULL_FACE); gl.enable(gl.BLEND); if (canvasTransparent) { gl.blendEquation(gl.FUNC_ADD); gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA); } else { gl.blendEquation(gl.FUNC_ADD); gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); } frameCtx.backfaces = false; if (!alphaDepthMask) { gl.depthMask(false); } // Transparent color edges if (normalFillTransparentBinLen > 0 || normalEdgesTransparentBinLen > 0) { gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); } if (normalEdgesTransparentBinLen > 0) { for (i = 0; i < normalEdgesTransparentBinLen; i++) { drawable = normalEdgesTransparentBin[i]; drawable.drawEdgesColorTransparent(frameCtx); } } // Transparent color fill if (normalFillTransparentBinLen > 0) { const eye = frameCtx.viewParams.eye; normalFillTransparentBin.length = normalFillTransparentBinLen; // normalFillTransparentBin reused by renderDrawables calls, so needs to be truncated if necessary const byDist = normalFillTransparentBin.map(d => ({ drawable: d, distSq: math.distVec3(d.origin || vec3_0, eye) })); byDist.sort((a, b) => b.distSq - a.distSq); for (i = 0; i < normalFillTransparentBinLen; i++) { byDist[i].drawable.drawColorTransparent(frameCtx); } } // Transparent X-ray edges if (xrayEdgesTransparentBinLen > 0) { for (i = 0; i < xrayEdgesTransparentBinLen; i++) { xrayEdgesTransparentBin[i].drawEdgesXRayed(frameCtx); } } // Transparent X-ray fill if (xrayedFillTransparentBinLen > 0) { for (i = 0; i < xrayedFillTransparentBinLen; i++) { xrayedFillTransparentBin[i].drawSilhouetteXRayed(frameCtx); } } gl.disable(gl.BLEND); if (!alphaDepthMask) { gl.depthMask(true); } } // Opaque highlight if (highlightedFillOpaqueBinLen > 0 || highlightedEdgesOpaqueBinLen > 0) { frameCtx.lastProgramId = null; if (scene.highlightMaterial.glowThrough) { gl.clear(gl.DEPTH_BUFFER_BIT); } // Opaque highlighted edges if (highlightedEdgesOpaqueBinLen > 0) { for (i = 0; i < highlightedEdgesOpaqueBinLen; i++) { highlightedEdgesOpaqueBin[i].drawEdgesHighlighted(frameCtx); } } // Opaque highlighted fill if (highlightedFillOpaqueBinLen > 0) { for (i = 0; i < highlightedFillOpaqueBinLen; i++) { highlightedFillOpaqueBin[i].drawSilhouetteHighlighted(frameCtx); } } } // Highlighted transparent if (highlightedFillTransparentBinLen > 0 || highlightedEdgesTransparentBinLen > 0 || highlightedFillOpaqueBinLen > 0) { frameCtx.lastProgramId = null; if (scene.selectedMaterial.glowThrough) { gl.clear(gl.DEPTH_BUFFER_BIT); } gl.enable(gl.BLEND); if (canvasTransparent) { gl.blendEquation(gl.FUNC_ADD); gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA); } else { gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); } gl.enable(gl.CULL_FACE); // Highlighted transparent edges if (highlightedEdgesTransparentBinLen > 0) { for (i = 0; i < highlightedEdgesTransparentBinLen; i++) { highlightedEdgesTransparentBin[i].drawEdgesHighlighted(frameCtx); } } // Highlighted transparent fill if (highlightedFillTransparentBinLen > 0) { for (i = 0; i < highlightedFillTransparentBinLen; i++) { highlightedFillTransparentBin[i].drawSilhouetteHighlighted(frameCtx); } } gl.disable(gl.BLEND); } // Selected opaque if (selectedFillOpaqueBinLen > 0 || selectedEdgesOpaqueBinLen > 0) { frameCtx.lastProgramId = null; if (scene.selectedMaterial.glowThrough) { gl.clear(gl.DEPTH_BUFFER_BIT); } // Selected opaque fill if (selectedEdgesOpaqueBinLen > 0) { for (i = 0; i < selectedEdgesOpaqueBinLen; i++) { selectedEdgesOpaqueBin[i].drawEdgesSelected(frameCtx); } } // Selected opaque edges if (selectedFillOpaqueBinLen > 0) { for (i = 0; i < selectedFillOpaqueBinLen; i++) { selectedFillOpaqueBin[i].drawSilhouetteSelected(frameCtx); } } } // Selected transparent if (selectedFillTransparentBinLen > 0 || selectedEdgesTransparentBinLen > 0) { frameCtx.lastProgramId = null; if (scene.selectedMaterial.glowThrough) { gl.clear(gl.DEPTH_BUFFER_BIT); } gl.enable(gl.CULL_FACE); gl.enable(gl.BLEND); if (canvasTransparent) { gl.blendEquation(gl.FUNC_ADD); gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA); } else { gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); } // Selected transparent edges if (selectedEdgesTransparentBinLen > 0) { for (i = 0; i < selectedEdgesTransparentBinLen; i++) { selectedEdgesTransparentBin[i].drawEdgesSelected(frameCtx); } } // Selected transparent fill if (selectedFillTransparentBinLen > 0) { for (i = 0; i < selectedFillTransparentBinLen; i++) { selectedFillTransparentBin[i].drawSilhouetteSelected(frameCtx); } } gl.disable(gl.BLEND); } }; renderDrawables(postCullDrawableList); if (uiDrawableList.length > 0) { gl.clear(gl.DEPTH_BUFFER_BIT); renderDrawables(uiDrawableList); } frameCtx.testAABB = null; const endTime = Date.now(); const frameStats = stats.frame; frameStats.renderTime = (endTime - startTime) / 1000.0; frameStats.drawElements = frameCtx.drawElements; frameStats.drawArrays = frameCtx.drawArrays; frameStats.useProgram = frameCtx.useProgram; frameStats.bindTexture = frameCtx.bindTexture; frameStats.bindArray = frameCtx.bindArray; const numTextureUnits = WEBGL_INFO.MAX_TEXTURE_IMAGE_UNITS; for (let ii = 0; ii < numTextureUnits; ii++) { gl.activeTexture(gl.TEXTURE0 + ii); } gl.bindTexture(gl.TEXTURE_CUBE_MAP, null); gl.bindTexture(gl.TEXTURE_2D, null); const numVertexAttribs = WEBGL_INFO.MAX_VERTEX_ATTRIBS; // Fixes https://github.com/xeokit/xeokit-sdk/issues/174 for (let ii = 0; ii < numVertexAttribs; ii++) { gl.disableVertexAttribArray(ii); } } const resetPickFrameCtx = (canvasPos, clipTransformDiv, camera, eye, projMatrix, viewMatrix, frameCtx) => { frameCtx.reset(); frameCtx.backfaces = true; frameCtx.frontface = true; // "ccw" frameCtx.viewParams.eye = eye; frameCtx.viewParams.projMatrix = projMatrix; frameCtx.viewParams.viewMatrix = viewMatrix; frameCtx.nearPlaneHeight = getNearPlaneHeight(camera, gl.drawingBufferHeight); const resolutionScale = scene.canvas.resolutionScale; frameCtx.pickClipPos = [ canvasPos ? ( 2 * canvasPos[0] * resolutionScale / gl.drawingBufferWidth - 1) : 0, canvasPos ? (1 - 2 * canvasPos[1] * resolutionScale / gl.drawingBufferHeight) : 0 ]; frameCtx.pickClipPosInv = [ gl.drawingBufferWidth / clipTransformDiv, gl.drawingBufferHeight / clipTransformDiv ]; }; /** * Picks an Entity. * @private */ this.pick = (function () { const tempVec3a = math.vec3(); const tempVec3b = math.vec3(); const tempVec4a = math.vec4(); const tempVec4b = math.vec4(); const tempVec4c = math.vec4(); const tempVec4d = math.vec4(); const tempVec4e = math.vec4(); const tempMat4a = math.mat4(); const tempMat4b = math.mat4(); const tempMat4c = math.mat4(); const tempMat4d = math.mat4(); const upVec = math.vec3([0, 1, 0]); const _pickResult = new PickResult(); const nearAndFar = math.vec2(); const canvasPos = math.vec3(); const worldRayOrigin = math.vec3(); const worldRayDir = math.vec3(); const worldSurfacePos = math.vec3(); const