UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

385 lines (384 loc) 10.9 kB
import { PRIMITIVE_TRISTRIP, SEMANTIC_COLOR, SEMANTIC_POSITION, SHADERLANGUAGE_GLSL, SHADERLANGUAGE_WGSL } from "../../platform/graphics/constants.js"; import { BLEND_NORMAL } from "../constants.js"; import { GraphNode } from "../graph-node.js"; import { Mesh } from "../mesh.js"; import { MeshInstance } from "../mesh-instance.js"; import { ShaderMaterial } from "../materials/shader-material.js"; import { ImmediateBatches } from "./immediate-batches.js"; import { Vec3 } from "../../core/math/vec3.js"; import { ChunkUtils } from "../shader-lib/chunk-utils.js"; import { ShaderChunks } from "../shader-lib/shader-chunks.js"; const tempPoints = []; const vec = new Vec3(); class Immediate { shaderDescs = /* @__PURE__ */ new Map(); constructor(device) { this.device = device; this.quadMesh = null; this.textureShader = null; this.depthTextureShader = null; this.cubeLocalPos = null; this.cubeWorldPos = null; this.batchesMap = /* @__PURE__ */ new Map(); this.allBatches = /* @__PURE__ */ new Set(); this.updatedLayers = /* @__PURE__ */ new Set(); this._materialDepth = null; this._materialNoDepth = null; this.layerMeshInstances = /* @__PURE__ */ new Map(); } // creates material for line rendering createMaterial(depthTest) { const material = new ShaderMaterial({ uniqueName: "ImmediateLine", vertexGLSL: ShaderChunks.get(this.device, SHADERLANGUAGE_GLSL).get("immediateLineVS"), fragmentGLSL: ShaderChunks.get(this.device, SHADERLANGUAGE_GLSL).get("immediateLinePS"), vertexWGSL: ShaderChunks.get(this.device, SHADERLANGUAGE_WGSL).get("immediateLineVS"), fragmentWGSL: ShaderChunks.get(this.device, SHADERLANGUAGE_WGSL).get("immediateLinePS"), attributes: { vertex_position: SEMANTIC_POSITION, vertex_color: SEMANTIC_COLOR } }); material.blendType = BLEND_NORMAL; material.depthTest = depthTest; material.update(); return material; } // material for line rendering with depth testing on get materialDepth() { if (!this._materialDepth) { this._materialDepth = this.createMaterial(true); } return this._materialDepth; } // material for line rendering with depth testing off get materialNoDepth() { if (!this._materialNoDepth) { this._materialNoDepth = this.createMaterial(false); } return this._materialNoDepth; } // returns a batch for rendering lines to a layer with required depth testing state getBatch(layer, depthTest) { let batches = this.batchesMap.get(layer); if (!batches) { batches = new ImmediateBatches(this.device); this.batchesMap.set(layer, batches); } this.allBatches.add(batches); const material = depthTest ? this.materialDepth : this.materialNoDepth; return batches.getBatch(material, layer); } getShaderDesc(id, fragmentGLSL, fragmentWGSL) { if (!this.shaderDescs.has(id)) { this.shaderDescs.set(id, { uniqueName: `DebugShader:${id}`, // shared vertex shader for textured quad rendering vertexGLSL: ` attribute vec2 vertex_position; uniform mat4 matrix_model; varying vec2 uv0; void main(void) { gl_Position = matrix_model * vec4(vertex_position, 0, 1); uv0 = vertex_position.xy + 0.5; } `, vertexWGSL: ` attribute vertex_position: vec2f; uniform matrix_model: mat4x4f; varying uv0: vec2f; @vertex fn vertexMain(input: VertexInput) -> VertexOutput { var output: VertexOutput; output.position = uniform.matrix_model * vec4f(input.vertex_position, 0.0, 1.0); output.uv0 = input.vertex_position.xy + vec2f(0.5); return output; } `, fragmentGLSL, fragmentWGSL, attributes: { vertex_position: SEMANTIC_POSITION } }); } return this.shaderDescs.get(id); } // shader used to display texture getTextureShaderDesc(encoding) { const decodeFunc = ChunkUtils.decodeFunc(encoding); return this.getShaderDesc( `textureShader-${encoding}`, ` #include "gammaPS" varying vec2 uv0; uniform sampler2D colorMap; void main (void) { vec3 linearColor = ${decodeFunc}(texture2D(colorMap, uv0)); gl_FragColor = vec4(gammaCorrectOutput(linearColor), 1); } `, ` #include "gammaPS" varying uv0: vec2f; var colorMap: texture_2d<f32>; var colorMapSampler: sampler; @fragment fn fragmentMain(input : FragmentInput) -> FragmentOutput { var output: FragmentOutput; let sampledTex = textureSample(colorMap, colorMapSampler, input.uv0); let linearColor: vec3f = ${decodeFunc}(sampledTex); output.color = vec4f(gammaCorrectOutput(linearColor), 1.0); return output; } ` ); } // shader used to display infilterable texture sampled using texelFetch getUnfilterableTextureShaderDesc() { return this.getShaderDesc( "textureShaderUnfilterable", ` varying vec2 uv0; uniform highp sampler2D colorMap; void main (void) { ivec2 uv = ivec2(uv0 * textureSize(colorMap, 0)); gl_FragColor = vec4(texelFetch(colorMap, uv, 0).xyz, 1); } `, ` varying uv0: vec2f; var colorMap: texture_2d<uff>; @fragment fn fragmentMain(input : FragmentInput) -> FragmentOutput { var output: FragmentOutput; let uv : vec2<i32> = vec2<i32>(input.uv0 * vec2f(textureDimensions(colorMap, 0))); let fetchedColor : vec4f = textureLoad(colorMap, uv, 0); output.color = vec4f(fetchedColor.xyz, 1.0); return output; } ` ); } // shader used to display depth texture getDepthTextureShaderDesc() { return this.getShaderDesc( "depthTextureShader", ` #include "screenDepthPS" #include "gammaPS" varying vec2 uv0; void main() { float depth = getLinearScreenDepth(getImageEffectUV(uv0)) * camera_params.x; gl_FragColor = vec4(gammaCorrectOutput(vec3(depth)), 1.0); } `, ` #include "screenDepthPS" #include "gammaPS" varying uv0: vec2f; @fragment fn fragmentMain(input: FragmentInput) -> FragmentOutput { var output: FragmentOutput; let depth: f32 = getLinearScreenDepth(getImageEffectUV(input.uv0)) * uniform.camera_params.x; output.color = vec4f(gammaCorrectOutput(vec3f(depth)), 1.0); return output; } ` ); } // creates mesh used to render a quad getQuadMesh() { if (!this.quadMesh) { this.quadMesh = new Mesh(this.device); this.quadMesh.setPositions([ -0.5, -0.5, 0, 0.5, -0.5, 0, -0.5, 0.5, 0, 0.5, 0.5, 0 ]); this.quadMesh.update(PRIMITIVE_TRISTRIP); } return this.quadMesh; } // Draw mesh at this frame drawMesh(material, matrix, mesh, meshInstance, layer) { if (!meshInstance) { const graphNode = this.getGraphNode(matrix); meshInstance = new MeshInstance(mesh, material, graphNode); } let layerMeshInstances = this.layerMeshInstances.get(layer); if (!layerMeshInstances) { layerMeshInstances = []; this.layerMeshInstances.set(layer, layerMeshInstances); } layerMeshInstances.push(meshInstance); } drawWireAlignedBox(min, max, color, depthTest, layer, mat) { if (mat) { const mulPoint = (x, y, z) => { vec.set(x, y, z); mat.transformPoint(vec, vec); tempPoints.push(vec.x, vec.y, vec.z); }; mulPoint(min.x, min.y, min.z); mulPoint(min.x, max.y, min.z); mulPoint(min.x, max.y, min.z); mulPoint(max.x, max.y, min.z); mulPoint(max.x, max.y, min.z); mulPoint(max.x, min.y, min.z); mulPoint(max.x, min.y, min.z); mulPoint(min.x, min.y, min.z); mulPoint(min.x, min.y, max.z); mulPoint(min.x, max.y, max.z); mulPoint(min.x, max.y, max.z); mulPoint(max.x, max.y, max.z); mulPoint(max.x, max.y, max.z); mulPoint(max.x, min.y, max.z); mulPoint(max.x, min.y, max.z); mulPoint(min.x, min.y, max.z); mulPoint(min.x, min.y, min.z); mulPoint(min.x, min.y, max.z); mulPoint(min.x, max.y, min.z); mulPoint(min.x, max.y, max.z); mulPoint(max.x, max.y, min.z); mulPoint(max.x, max.y, max.z); mulPoint(max.x, min.y, min.z); mulPoint(max.x, min.y, max.z); } else { tempPoints.push( min.x, min.y, min.z, min.x, max.y, min.z, min.x, max.y, min.z, max.x, max.y, min.z, max.x, max.y, min.z, max.x, min.y, min.z, max.x, min.y, min.z, min.x, min.y, min.z, min.x, min.y, max.z, min.x, max.y, max.z, min.x, max.y, max.z, max.x, max.y, max.z, max.x, max.y, max.z, max.x, min.y, max.z, max.x, min.y, max.z, min.x, min.y, max.z, min.x, min.y, min.z, min.x, min.y, max.z, min.x, max.y, min.z, min.x, max.y, max.z, max.x, max.y, min.z, max.x, max.y, max.z, max.x, min.y, min.z, max.x, min.y, max.z ); } const batch = this.getBatch(layer, depthTest); batch.addLinesArrays(tempPoints, color); tempPoints.length = 0; } drawWireSphere(center, radius, color, numSegments, depthTest, layer) { const step = 2 * Math.PI / numSegments; let angle = 0; for (let i = 0; i < numSegments; i++) { const sin0 = Math.sin(angle); const cos0 = Math.cos(angle); angle += step; const sin1 = Math.sin(angle); const cos1 = Math.cos(angle); tempPoints.push(center.x + radius * sin0, center.y, center.z + radius * cos0); tempPoints.push(center.x + radius * sin1, center.y, center.z + radius * cos1); tempPoints.push(center.x + radius * sin0, center.y + radius * cos0, center.z); tempPoints.push(center.x + radius * sin1, center.y + radius * cos1, center.z); tempPoints.push(center.x, center.y + radius * sin0, center.z + radius * cos0); tempPoints.push(center.x, center.y + radius * sin1, center.z + radius * cos1); } const batch = this.getBatch(layer, depthTest); batch.addLinesArrays(tempPoints, color); tempPoints.length = 0; } getGraphNode(matrix) { const graphNode = new GraphNode("ImmediateDebug"); graphNode.worldTransform = matrix; graphNode._dirtyWorld = graphNode._dirtyNormal = false; return graphNode; } // This is called just before the layer is rendered to allow lines for the layer to be added from inside // the frame getting rendered onPreRenderLayer(layer, visibleList, transparent) { this.batchesMap.forEach((batches, batchLayer) => { if (batchLayer === layer) { batches.onPreRender(visibleList, transparent); } }); if (!this.updatedLayers.has(layer)) { this.updatedLayers.add(layer); const meshInstances = this.layerMeshInstances.get(layer); if (meshInstances) { for (let i = 0; i < meshInstances.length; i++) { visibleList.push(meshInstances[i]); } meshInstances.length = 0; } } } // called after the frame was rendered, clears data onPostRender() { this.allBatches.forEach((batch) => batch.clear()); this.allBatches.clear(); this.updatedLayers.clear(); } } export { Immediate };