From 648bfaac471b29b44235f1cc3c5c933eb47f278a Mon Sep 17 00:00:00 2001 From: Ramiro Date: Sun, 4 Dec 2022 11:08:44 -0300 Subject: [PATCH] Refactored GPUParticleSystem. Improved FP Texture detection --- .version | 2 +- package.json | 2 +- src/Graphics.js | 55 ++++++++- src/gpu_particles/GPUParticleSystem.js | 116 +++++++++++------- src/gpu_particles/ParticleAttribute.js | 51 ++++---- .../ParticlePositionAttribute.js | 32 ++++- src/index.js | 4 + .../gpu_particles/ParticleStorageMaterial.js | 17 ++- .../gpu_particles/PositionStorageMaterial.js | 17 ++- .../gpu_particles/generic_storage.frag | 1 - 10 files changed, 213 insertions(+), 84 deletions(-) diff --git a/.version b/.version index f8ba35d..2d88a61 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v7.0.1 +v7.1.0 diff --git a/package.json b/package.json index 111cb59..9bd99eb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ohzi-core", - "version": "7.0.1", + "version": "7.1.0", "description": "OHZI Core Library", "source": "src/index.js", "module": "build/index.module.js", diff --git a/src/Graphics.js b/src/Graphics.js index 37c3668..5e57a5e 100644 --- a/src/Graphics.js +++ b/src/Graphics.js @@ -7,6 +7,17 @@ import Capabilities from './Capabilities'; import DepthAndNormalsRenderer from './render_utilities/DepthAndNormalsRenderer'; import Blitter from './render_utilities/Blitter'; +import { + NearestFilter, + RGBAFormat, + LinearEncoding, + FloatType, + WebGLRenderTarget, + ShaderMaterial, + NoBlending, + AlwaysDepth +} from 'three'; + import { WebGLRenderer } from 'three'; class Graphics @@ -87,7 +98,7 @@ class Graphics this.current_render_mode = this.no_render; Capabilities.max_anisotropy = this._renderer.capabilities.getMaxAnisotropy(); Capabilities.vertex_texture_sampler_available = this._renderer.capabilities.maxVertexTextures > 0; - Capabilities.fp_textures_available = this._renderer.capabilities.floatVertexTextures; + Capabilities.fp_textures_available = this.is_floating_point_texture_available(); this.generateDepthNormalTexture = false; @@ -332,6 +343,48 @@ class Graphics this.current_render_mode.dispose(); this.blitter.dispose(); } + + is_floating_point_texture_available() + { + const RT = new WebGLRenderTarget(1, 1, { + minFilter: NearestFilter, + magFilter: NearestFilter, + format: RGBAFormat, + encoding: LinearEncoding, + type: FloatType, + stencilBuffer: false, + depthBuffer: false + }); + + const vert = ` + void main() + { + gl_Position = vec4(uv * 2.0 - 1.0, 1.0, 1.0); + } + `; + const frag = ` + void main() + { + gl_FragColor = vec4(0.0, 4865.35, 0.0, 1.0); + } + `; + + const mat = new ShaderMaterial({ + vertexShader: vert, + fragmentShader: frag, + depthWrite: false, + blending: NoBlending, + depthTest: false, + depthFunc: AlwaysDepth + }); + + this.material_pass(mat, RT); + + const output = new Float32Array(4); + this._renderer.readRenderTargetPixels(RT, 0, 0, 1, 1, output); + + return Math.abs(output[1] - 4865.35) < 0.001; + } } export default new Graphics(); diff --git a/src/gpu_particles/GPUParticleSystem.js b/src/gpu_particles/GPUParticleSystem.js index f00815b..970f339 100644 --- a/src/gpu_particles/GPUParticleSystem.js +++ b/src/gpu_particles/GPUParticleSystem.js @@ -1,87 +1,109 @@ import { Object3D } from 'three'; import { BufferAttribute } from 'three'; +import { PlaneGeometry } from 'three'; +import { Mesh } from 'three'; +import { BufferGeometry } from 'three'; +import { Scene } from 'three'; import { Points } from 'three'; +import { InstancedBufferGeometry } from 'three'; +import { InstancedBufferAttribute } from 'three'; +import ParticleAttribute from './ParticleAttribute'; export default class GPUParticleSystem extends Object3D { - constructor() + constructor(particle_count, material) { super(); this.attributes = []; - this.particles = undefined; - } - - set_from_geometry(geometry, material, init_attribute_uvs) - { - // let position = new ParticlePositionAttribute("_Position"); - if (init_attribute_uvs && geometry.getAttribute('storage_uv') === undefined) - { - const uv_storage_attr = this.build_uv_storage_attribute(geometry.getAttribute('position').count); - geometry.setAttribute('storage_uv', uv_storage_attr); - } - // position.init_from_geometry(geometry); - // this.attributes.push(position); + this.mesh = this.build_point_mesh(particle_count, material); + this.add(this.mesh); - // material.uniforms._Position.value = position.read.texture; - - const points = new Points(geometry, material); - points.frustumCulled = false; - this.particles = points; - - this.add(points); + this.attribute_writter_mesh = this.build_attribute_writter_mesh(particle_count); + this.attribute_writter_scene = new Scene(); + this.attribute_writter_scene.add(this.attribute_writter_mesh); } add_texture_attribute(buffer_attribute) { - + this.attributes.push(buffer_attribute); } - add_attribute(name, buffer_attribute) + add_update_attribute_array(name, array, item_size) { + this.attribute_writter_mesh.geometry.setAttribute(name, new BufferAttribute(array, item_size, false)); + } + add_attribute_array(name, array, item_size) + { + this.mesh.geometry.setAttribute(name, new InstancedBufferAttribute(array, item_size, false)); } - build_uv_storage_attribute(particle_count) + update() { - const resolution = this.calculate_resolution(particle_count); - const uvs = new Float32Array(particle_count * 2); - for (let i = 0, j = 0; i < particle_count * 2; i += 2, j++) + this.mesh.material.update(); + for (let i = 0; i < this.attributes.length; i++) { - uvs[i] = ((j % resolution) / resolution) + (0.5 / resolution); - uvs[i + 1] = (Math.floor(j / resolution) / resolution) + (0.5 / resolution); + this.attributes[i].update(this.attribute_writter_scene); } - - return new BufferAttribute(uvs, 2); } - calculate_resolution(particle_count) + dispose() { - return Math.ceil(Math.sqrt(particle_count)); + this.mesh.geometry.dispose(); + this.mesh.material.dispose(); } - update() + build_point_mesh(instance_count = 1, material) { - for (let i = 0; i < this.attributes.length; i++) - { - this.attributes[i].update(); - } + const geo = new PlaneGeometry(); + + const instanced_geo = new InstancedBufferGeometry(); + instanced_geo.setAttribute('position', geo.getAttribute('position')); + instanced_geo.index = geo.index; + + instanced_geo.setAttribute('storage_uv', this.build_uv_storage_attribute(instance_count)); + instanced_geo.instanceCount = instance_count; + const mesh = new Mesh(instanced_geo, material); + mesh.frustumCulled = false; + return mesh; } - set_attribute_update_material(attribute_name, mat) + build_attribute_writter_mesh(particle_count) { - for (let i = 0; i < this.attributes.length; i++) + const { width, height } = ParticleAttribute.calculate_resolution(particle_count); + + const uvs = new Float32Array(particle_count * 3); + + for (let i = 0; i < particle_count; i++) { - if (this.attributes[i].name === attribute_name) - { - this.attributes[i].update_material = mat; - } + const x = i % width; + const y = Math.floor(i / width); + uvs[i * 3 + 0] = (x + 0.5) / width; + uvs[i * 3 + 1] = (y + 0.5) / height; + uvs[i * 3 + 2] = i; } + const geo = new BufferGeometry(); + geo.setAttribute('position', new BufferAttribute(uvs, 3, false)); + + const points = new Points(geo); + points.frustumCulled = false; + return points; } - dispose() + build_uv_storage_attribute(particle_count) { - this.particles.geometry.dispose(); - this.particles.material.dispose(); + const { width, height } = ParticleAttribute.calculate_resolution(particle_count); + + const uvs = new Float32Array(particle_count * 2); + + for (let i = 0; i < particle_count; i++) + { + const x = i % width; + const y = Math.floor(i / width); + uvs[i * 2 + 0] = (x + 0.5) / width; + uvs[i * 2 + 1] = (y + 0.5) / height; + } + return new InstancedBufferAttribute(uvs, 2, false); } } diff --git a/src/gpu_particles/ParticleAttribute.js b/src/gpu_particles/ParticleAttribute.js index 6c72d2e..7816dad 100644 --- a/src/gpu_particles/ParticleAttribute.js +++ b/src/gpu_particles/ParticleAttribute.js @@ -1,26 +1,23 @@ import Graphics from '../Graphics'; -import { Scene } from 'three'; import { NearestFilter } from 'three'; import { RGBAFormat } from 'three'; import { LinearEncoding } from 'three'; import { HalfFloatType } from 'three'; import { FloatType } from 'three'; import { WebGLRenderTarget } from 'three'; -import { Points } from 'three'; +import Capabilities from '../Capabilities'; export default class ParticleAttribute { - constructor(attr_name) + constructor(attr_name, update_material) { this.read = undefined; this.write = undefined; this.name = attr_name; - this.update_material = undefined; - - this.update_scene = new Scene(); + this.update_material = update_material; } init_from_geometry(geometry) @@ -35,23 +32,25 @@ export default class ParticleAttribute build_RT(particle_count) { - const resolution = this.calculate_resolution(particle_count); + const resolution = ParticleAttribute.calculate_resolution(particle_count); const options = { minFilter: NearestFilter, magFilter: NearestFilter, format: RGBAFormat, encoding: LinearEncoding, - type: (/(iPad|iPhone|iPod)/g.test(navigator.userAgent)) ? HalfFloatType : FloatType, + type: Capabilities.fp_textures_available ? FloatType : HalfFloatType, stencilBuffer: false, depthBuffer: false }; - return new WebGLRenderTarget(resolution, resolution, options); + return new WebGLRenderTarget(resolution.width, resolution.height, options); } - calculate_resolution(particle_count) + static calculate_resolution(particle_count) { - return Math.ceil(Math.sqrt(particle_count)); + const width = Math.min(particle_count, 512); + const height = Math.max(1, Math.ceil(particle_count / width)); + return { width, height }; } swap_RT() @@ -61,23 +60,33 @@ export default class ParticleAttribute this.write = tmp; } - update() + update(attribute_writter_scene) { if (this.update_material) { - Graphics.blit(this.read, this.write, this.update_material); + this.update_material.update(); + attribute_writter_scene.children[0].material = this.update_material; + this.update_material.uniforms._MainTex.value = this.read.texture; + Graphics.render(attribute_writter_scene, undefined, this.write); + attribute_writter_scene.children[0].material = undefined; + this.swap_RT(); } } - render_geometry_to_RT(geometry, material, RT) + store_geometry_attribute_in_RT(attribute, RT, storage_material, attribute_writter_scene) + { + attribute_writter_scene.children[0].geometry.setAttribute('data', attribute); + attribute_writter_scene.children[0].material = storage_material; + + Graphics.render(attribute_writter_scene, undefined, RT); + + attribute_writter_scene.children[0].geometry.deleteAttribute('data'); + attribute_writter_scene.children[0].material = undefined; + } + + get_texture() { - const points = new Points(geometry, material); - points.frustumCulled = false; - // let scene = new Scene(); - // scene.add( points ); - this.update_scene.add(points); - Graphics.render(this.update_scene, undefined, RT); - // Graphics.render(scene, undefined, RT); + return this.read.texture; } } diff --git a/src/gpu_particles/ParticlePositionAttribute.js b/src/gpu_particles/ParticlePositionAttribute.js index 7b7b4f0..123a26f 100644 --- a/src/gpu_particles/ParticlePositionAttribute.js +++ b/src/gpu_particles/ParticlePositionAttribute.js @@ -1,20 +1,40 @@ +import { BufferAttribute } from 'three'; + import ParticleAttribute from '../gpu_particles/ParticleAttribute'; -import PositionStorageMaterial from '../materials/gpu_particles/PositionStorageMaterial'; +import ParticleStorageMaterial from '../materials/gpu_particles/ParticleStorageMaterial'; export default class ParticlePositionAttribute extends ParticleAttribute { - constructor() + constructor(update_material) { - super('_Position'); + super('_Position', update_material); } - init_from_geometry(geometry) + store_geometry(geometry, attribute_writter_scene) { const pos_attr = geometry.getAttribute('position'); this.read = this.build_RT(pos_attr.count); this.write = this.build_RT(pos_attr.count); - const mat = new PositionStorageMaterial(); - this.render_geometry_to_RT(geometry, mat, this.read); + const mat = new ParticleStorageMaterial(); + this.store_geometry_attribute_in_RT(geometry.getAttribute('position'), this.read, mat, attribute_writter_scene); + } + + store_positions(positions, attribute_writter_scene) + { + this.read = this.build_RT(positions.length); + this.write = this.build_RT(positions.length); + + const arr = new Float32Array(positions.length * 4); + + for (let i = 0; i < positions.length; i++) + { + arr[i * 4 + 0] = positions[i].x; + arr[i * 4 + 1] = positions[i].y; + arr[i * 4 + 2] = positions[i].z; + arr[i * 4 + 3] = (Math.random() * 2 - 1) * 10; + } + const mat = new ParticleStorageMaterial(); + this.store_geometry_attribute_in_RT(new BufferAttribute(arr, 4), this.read, mat, attribute_writter_scene); } } diff --git a/src/index.js b/src/index.js index b0274e2..6b5c3c2 100644 --- a/src/index.js +++ b/src/index.js @@ -82,6 +82,8 @@ import DualFilteringBlurrer from './render_utilities/DualFilteringBlurrer'; import Blurrer from './render_utilities/Blurrer'; import GPUParticleSystem from './gpu_particles/GPUParticleSystem'; +import ParticleAttribute from './gpu_particles/ParticleAttribute'; +import ParticlePositionAttribute from './gpu_particles/ParticlePositionAttribute'; import DualFilteringBlurMaterial from './materials/DualFilteringBlurMaterial'; import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js'; @@ -187,6 +189,8 @@ export { DualFilteringBlurrer, GPUParticleSystem, + ParticleAttribute, + ParticlePositionAttribute, PerspectiveFrustumPointFitter, OrthographicFrustumPointFitter, diff --git a/src/materials/gpu_particles/ParticleStorageMaterial.js b/src/materials/gpu_particles/ParticleStorageMaterial.js index e9e6e2e..68e33e1 100644 --- a/src/materials/gpu_particles/ParticleStorageMaterial.js +++ b/src/materials/gpu_particles/ParticleStorageMaterial.js @@ -1,17 +1,24 @@ import frag from '../../shaders/gpu_particles/generic_storage.frag'; -import common_utils from '../../shaders/gpu_particles/common_utils.glsl'; - import { ShaderMaterial } from 'three'; -import { ShaderChunk } from 'three'; import { NoBlending } from 'three'; import { AlwaysDepth } from 'three'; export default class ParticleStorageMaterial extends ShaderMaterial { - constructor(vert) + constructor() { - ShaderChunk.gpu_particles_utils = common_utils; + const vert = ` + varying vec4 value; + attribute vec4 data; + + void main() + { + gl_Position = vec4(position.xy * 2.0 - 1.0, 1.0,1.0); + gl_PointSize = 1.0; + value = data; + } + `; super({ uniforms: { diff --git a/src/materials/gpu_particles/PositionStorageMaterial.js b/src/materials/gpu_particles/PositionStorageMaterial.js index aa47455..09ef205 100644 --- a/src/materials/gpu_particles/PositionStorageMaterial.js +++ b/src/materials/gpu_particles/PositionStorageMaterial.js @@ -1,10 +1,25 @@ import ParticleStorageMaterial from '../../materials/gpu_particles/ParticleStorageMaterial'; -import vert from '../../shaders/gpu_particles/store/store_position.vert'; export default class PositionStorageMaterial extends ParticleStorageMaterial { constructor() { + const vert = ` + + varying vec4 value; + + attribute vec2 storage_uv; + + + void main() + { + gl_Position = vec4(storage_uv * 2.0 - 1.0, 1.0,1.0); + gl_PointSize = 1.0; + + value = vec4(position.xyz, 1.0); + } + + `; super(vert); } } diff --git a/src/shaders/gpu_particles/generic_storage.frag b/src/shaders/gpu_particles/generic_storage.frag index 7dd1cdb..2e408f2 100644 --- a/src/shaders/gpu_particles/generic_storage.frag +++ b/src/shaders/gpu_particles/generic_storage.frag @@ -2,6 +2,5 @@ varying vec4 value; void main() { - //gl_FragColor = vec4(5.0, 0.0, 0.0, 1.0); gl_FragColor = value; }