diff --git a/examples/files.json b/examples/files.json index 1df55ea74ed01f..32033fb74e4f4c 100644 --- a/examples/files.json +++ b/examples/files.json @@ -85,6 +85,7 @@ "webgl_loader_gltf", "webgl_loader_gltf_compressed", "webgl_loader_gltf_sheen", + "webgl_loader_gltf_point_sprite", "webgl_loader_gltf_transmission", "webgl_loader_gltf_variants", "webgl_loader_ifc", diff --git a/examples/jsm/loaders/GLTFLoader.js b/examples/jsm/loaders/GLTFLoader.js index 808a0c2f6457ef..7380b46eaee3d0 100644 --- a/examples/jsm/loaders/GLTFLoader.js +++ b/examples/jsm/loaders/GLTFLoader.js @@ -1,4 +1,5 @@ import { + AdditiveBlending, AnimationClip, Bone, Box3, @@ -48,6 +49,7 @@ import { Quaternion, QuaternionKeyframeTrack, RepeatWrapping, + ShaderMaterial, Skeleton, SkinnedMesh, Sphere, @@ -368,6 +370,10 @@ class GLTFLoader extends Loader { extensions[ extensionName ] = new GLTFHighPrecisionNormalExtension(); break; + case EXTENSIONS.OFT_MATERIALS_POINT_SPRITE: + extensions[ extensionName ] = new GLTFMaterialsPointSpriteExtension(); + break; + default: if ( extensionsRequired.indexOf( extensionName ) >= 0 && plugins[ extensionName ] === undefined ) { @@ -459,7 +465,8 @@ const EXTENSIONS = { KHR_MESH_QUANTIZATION: 'KHR_mesh_quantization', EXT_TEXTURE_WEBP: 'EXT_texture_webp', EXT_MESHOPT_COMPRESSION: 'EXT_meshopt_compression', - OFT_TEXTURE_HIGHPRECISION_NORMAL: 'OFT_texture_highPrecisionNormal' + OFT_TEXTURE_HIGHPRECISION_NORMAL: 'OFT_texture_highPrecisionNormal', + OFT_MATERIALS_POINT_SPRITE: 'OFT_materials_pointSprite' }; /** @@ -1811,6 +1818,195 @@ class GLTFHighPrecisionNormalExtension { } +/** + * Point Sprite Materials Extension + * + * Specification: https://github.com/oppenfuture/glTF/tree/pointSprite/extensions/2.0/Vendor/OFT_materials_pointSprite + */ +class OFTPointSpriteMaterial extends ShaderMaterial { + + constructor( params ) { + + super( params ); + this.type = 'OFTPointSpriteMaterial'; + + } + +} + +class GLTFMaterialsPointSpriteExtension { + + constructor() { + + this.name = EXTENSIONS.OFT_MATERIALS_POINT_SPRITE; + + } + + getMaterialType() { + + return OFTPointSpriteMaterial; + + } + + extendParams( materialParams, materialDef, parser ) { + + const pending = []; + + const pointSprite = materialDef.extensions?.OFT_materials_pointSprite; + + if ( pointSprite ) { + + materialParams.pointSize = pointSprite.pointSize + + if ( pointSprite.pointTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'pointTexture', pointSprite.pointTexture ) ); + + } + + } + + return Promise.all( pending ); + + } + + createMaterial( materialParams ) { + + const uniformsPoint = { + + pointSize: { value: materialParams.pointSize * window.devicePixelRatio }, + pointTexture: { value: materialParams.pointTexture }, + depthTexture: { value: null }, + depthBias: { value: 0.01 } + + }; + + const material = new OFTPointSpriteMaterial( { + + uniforms: uniformsPoint, + vertexShader: /* glsl */` + attribute float size; + + uniform float pointSize; + + varying vec4 vColor; + varying float vIntensity; + varying float vRandom; + varying vec4 vMvpPosition; + + #define PI 3.14159265359 + #define PI2 6.28318530718 + + const float Threshold = 0.65; + + highp float rand( const in vec2 uv ) { + const highp float a = 12.9898, b = 78.233, c = 43758.5453; + highp float dt = dot( uv.xy, vec2( a, b ) ), sn = mod( dt, PI ); + return fract(sin(sn) * c); + } + + void main() { + + vColor.rgb = color.rgb; + + vRandom = rand(position.xy); + vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 ); + vec3 ViewPosition = - mvPosition.xyz; + vec3 viewDir = normalize(ViewPosition); + + vec3 n = normalize(normalMatrix * normal); + float dotNV = dot(viewDir, n); + // cos func to liner func + vIntensity = 1.0 - acos(dotNV); + // map Threshold ~ 1.0 to 0 ~ 1.0 + vIntensity = vIntensity < Threshold ? 0.0 : (vIntensity - Threshold) / (1.0 - Threshold); + // sharp the curve + vIntensity = pow(vIntensity, 1.1); + + vColor.a = 0.2 + vIntensity * 0.8; + + gl_Position = projectionMatrix * mvPosition; + + vMvpPosition = gl_Position; + gl_PointSize = ((vRandom * 0.2 + 1.8) * pointSize) * vIntensity * (0.5/-mvPosition.z); + + } + `, + fragmentShader: /* glsl */` + precision highp float; + #include + + #define PI 3.14159265359 + #define PI2 6.28318530718 + + uniform sampler2D pointTexture; + uniform sampler2D depthTexture; + uniform sampler2D colorTexture; + uniform float depthBias; + + varying vec4 vColor; + varying float vRandom; + varying float vDotNV; + varying float vIntensity; + varying vec4 vMvpPosition; + + vec2 rotateUV(vec2 uv, float rotation) { + float mid = 0.5; + return vec2( + cos(rotation) * (uv.x - mid) + sin(rotation) * (uv.y - mid) + mid, + cos(rotation) * (uv.y - mid) - sin(rotation) * (uv.x - mid) + mid + ); + } + + vec3 rgb2hsv(vec3 c) { + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); + } + + vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); + } + + float readDepth( sampler2D depthSampler, vec2 coord ) { + float fragCoordZ = texture2D( depthSampler, coord ).x; + float viewZ = perspectiveDepthToViewZ( fragCoordZ, 0.1, 2.0 ); + return viewZToOrthographicDepth( viewZ, 0.1, 2.0 ); + } + + void main() { + vec3 projCoords = vMvpPosition.xyz / vMvpPosition.w; + projCoords = projCoords * 0.5 + 0.5; + float depth = unpackRGBAToDepth(texture2D(colorTexture, projCoords.xy)); + if (projCoords.z - depth >= depthBias) discard; + + if (vIntensity <= 0.0) discard; + gl_FragColor = vColor; + vec2 rotateUv = rotateUV(gl_PointCoord, vRandom * vIntensity * PI * 0.6); + rotateUv = clamp(rotateUv, 0.0, 1.0); + gl_FragColor = gl_FragColor * texture2D( pointTexture, rotateUv ); + } + `, + + blending: AdditiveBlending, + depthTest: false, + transparent: true, + vertexColors: true + + } ); + + return material; + + } + +} + /*********************************/ /********** INTERPOLATION ********/ /*********************************/ @@ -3009,7 +3205,12 @@ class GLTFParser { const useVertexColors = geometry.attributes.color !== undefined; const useFlatShading = geometry.attributes.normal === undefined; - if ( mesh.isPoints ) { + if ( material.type === 'OFTPointSpriteMaterial' ) { + + // TODO: Why need this line? + mesh = mesh.clone(); + + } else if ( mesh.isPoints ) { const cacheKey = 'PointsMaterial:' + material.uuid; @@ -3134,6 +3335,12 @@ class GLTFParser { materialType = kmuExtension.getMaterialType(); pending.push( kmuExtension.extendParams( materialParams, materialDef, parser ) ); + } else if ( materialExtensions[ EXTENSIONS.OFT_MATERIALS_POINT_SPRITE ] ) { + + const kmuExtension = extensions[ EXTENSIONS.OFT_MATERIALS_POINT_SPRITE ]; + materialType = kmuExtension.getMaterialType(); + pending.push( kmuExtension.extendParams( materialParams, materialDef, parser ) ); + } else { // Specification: @@ -3229,10 +3436,10 @@ class GLTFParser { if ( extensions[ EXTENSIONS.OFT_TEXTURE_HIGHPRECISION_NORMAL ] && materialDef.normalTexture.extensions[ EXTENSIONS.OFT_TEXTURE_HIGHPRECISION_NORMAL ] ) { - var highPrecisionNormalExt = materialDef.normalTexture.extensions[ EXTENSIONS.OFT_TEXTURE_HIGHPRECISION_NORMAL ]; - var lowerTexture = highPrecisionNormalExt.lower8BitTexture; - var texCoord = materialDef.normalTexture.texCoord || 0; - var lowerTexCoord = lowerTexture.texCoord || 0; + const highPrecisionNormalExt = materialDef.normalTexture.extensions[ EXTENSIONS.OFT_TEXTURE_HIGHPRECISION_NORMAL ]; + const lowerTexture = highPrecisionNormalExt.lower8BitTexture; + const texCoord = materialDef.normalTexture.texCoord || 0; + const lowerTexCoord = lowerTexture.texCoord || 0; // The texCoord property of lower8BitTexture must be the same as the texCoord of normalTexture if ( texCoord !== lowerTexCoord ) { @@ -3282,6 +3489,10 @@ class GLTFParser { material = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].createMaterial( materialParams ); + } else if ( materialType === OFTPointSpriteMaterial ) { + + material = extensions[ EXTENSIONS.OFT_MATERIALS_POINT_SPRITE ].createMaterial( materialParams ); + } else { material = new materialType( materialParams ); diff --git a/examples/models/gltf/glitter.glb b/examples/models/gltf/glitter.glb new file mode 100644 index 00000000000000..90111b9d677b84 Binary files /dev/null and b/examples/models/gltf/glitter.glb differ diff --git a/examples/screenshots/webgl_loader_gltf_point_sprite.jpg b/examples/screenshots/webgl_loader_gltf_point_sprite.jpg new file mode 100644 index 00000000000000..0ba61fa5c7a675 Binary files /dev/null and b/examples/screenshots/webgl_loader_gltf_point_sprite.jpg differ diff --git a/examples/webgl_loader_gltf_point_sprite.html b/examples/webgl_loader_gltf_point_sprite.html new file mode 100644 index 00000000000000..c579d962fbc19e --- /dev/null +++ b/examples/webgl_loader_gltf_point_sprite.html @@ -0,0 +1,166 @@ + + + + three.js webgl - GLTFloader + + + + + + +
+ three.js - GLTFLoader + OFT_materials_pointSprite
+ Test model by OppenFuture Technologies
+ Royal Esplanade by HDRI Haven +
+ + + + + + + + + + +