diff --git a/docs/api/ar/loaders/managers/DefaultLoadingManager.html b/docs/api/ar/loaders/managers/DefaultLoadingManager.html new file mode 100644 index 00000000000000..7fa15480bc662f --- /dev/null +++ b/docs/api/ar/loaders/managers/DefaultLoadingManager.html @@ -0,0 +1,68 @@ + + + + + + + + + +

[name]

+ +

+ نسخة عالمية من [page:LoadingManager LoadingManager]، يستخدمها + معظم المحملات عندما لم يتم تحديد مدير مخصص.

+ + هذا سيكون كافيًا لمعظم الأغراض، ولكن قد يكون هناك أوقات عندما + ترغب في مديري تحميل منفصلين للقول، القوام والنماذج. +

+ +

مثال للكود

+ +

+ يمكنك تعيين [page:LoadingManager.onStart onStart]، + [page:LoadingManager.onLoad onLoad]، [page:LoadingManager.onProgress onProgress]، + [page:LoadingManager.onStart onError] وظائف لل + مدير. ستطبق هذه على أي محملات تستخدم + DefaultLoadingManager.

+ + يجب عدم الخلط بين هذه الوظائف المسماة بشكل مشابه + من المحملات الفردية، لأنها مخصصة لعرض المعلومات + حول الحالة العامة للتحميل، بدلاً من التعامل مع البيانات + التي تم تحميلها. +

+ +THREE.DefaultLoadingManager.onStart = function ( url, itemsLoaded, itemsTotal ) { + console.log( 'Started loading file: ' + url + '.\nLoaded ' + itemsLoaded + ' of ' + itemsTotal + ' files.' ); +}; + +THREE.DefaultLoadingManager.onLoad = function ( ) { + console.log( 'Loading Complete!'); +}; + +THREE.DefaultLoadingManager.onProgress = function ( url, itemsLoaded, itemsTotal ) { + console.log( 'Loading file: ' + url + '.\nLoaded ' + itemsLoaded + ' of ' + itemsTotal + ' files.' ); +}; + +THREE.DefaultLoadingManager.onError = function ( url ) { + console.log( 'There was an error loading ' + url ); +}; + + +

الخصائص (Properties)

+

+ انظر صفحة [page:LoadingManager LoadingManager] لتفاصيل + الخصائص. +

+ +

الطرق (Methods)

+

+ انظر صفحة [page:LoadingManager LoadingManager] لتفاصيل الطرق. +

+ +

المصدر (Source)

+

+ [link:https://github.com/mrdoob/three.js/blob/master/src/loaders/LoadingManager.js src/loaders/LoadingManager.js] +

+ + diff --git a/docs/api/ar/loaders/managers/LoadingManager.html b/docs/api/ar/loaders/managers/LoadingManager.html new file mode 100644 index 00000000000000..bfe70192ed9d59 --- /dev/null +++ b/docs/api/ar/loaders/managers/LoadingManager.html @@ -0,0 +1,236 @@ + + + + + + + + + +

[name]

+ +

+ يتعامل ويتتبع البيانات المحملة والمعلقة. يتم إنشاء نسخة عالمية افتراضية + من هذه الفئة واستخدامها من قبل المحملات إذا لم يتم توفيرها + يدويًا - انظر [page:DefaultLoadingManager].

+ + بشكل عام يجب أن يكون ذلك كافيًا، ولكن هناك أوقات يمكن أن تكون فيها + مفيدًا لديك محملات منفصلة - على سبيل المثال إذا كنت ترغب في عرض + شرائط تحميل منفصلة للأشياء والقوام. +

+ +

مثال للكود

+ +

+ يوضح هذا المثال كيفية استخدام LoadingManager لتتبع التقدم + [page:OBJLoader]. +

+ + + const manager = new THREE.LoadingManager(); + manager.onStart = function ( url, itemsLoaded, itemsTotal ) { + console.log( 'Started loading file: ' + url + '.\nLoaded ' + itemsLoaded + ' of ' + itemsTotal + ' files.' ); + }; + + manager.onLoad = function ( ) { + console.log( 'Loading complete!'); + }; + + manager.onProgress = function ( url, itemsLoaded, itemsTotal ) { + console.log( 'Loading file: ' + url + '.\nLoaded ' + itemsLoaded + ' of ' + itemsTotal + ' files.' ); + }; + + manager.onError = function ( url ) { + console.log( 'There was an error loading ' + url ); + }; + + const loader = new THREE.OBJLoader( manager ); + loader.load( 'file.obj', function ( object ) { + // + } ); + + +

+ بالإضافة إلى مراقبة التقدم، يمكن استخدام LoadingManager ل + تجاوز عناوين URL للموارد أثناء التحميل. قد يكون ذلك مفيدًا للأصول + القادمة من أحداث السحب والإفلات، WebSockets، WebRTC، أو غيرها من واجهات برمجة التطبيقات. A + مثال يظهر كيفية تحميل نموذج في الذاكرة باستخدام عناوين URL لـ Blob أدناه. +

+ + + // Blob or File objects created when dragging files into the webpage. + const blobs = {'fish.gltf': blob1, 'diffuse.png': blob2, 'normal.png': blob3}; + + const manager = new THREE.LoadingManager(); + + // Initialize loading manager with URL callback. + const objectURLs = []; + manager.setURLModifier( ( url ) => { + + url = URL.createObjectURL( blobs[ url ] ); + objectURLs.push( url ); + return url; + + } ); + + // Load as usual, then revoke the blob URLs. + const loader = new THREE.GLTFLoader( manager ); + loader.load( 'fish.gltf', (gltf) => { + + scene.add( gltf.scene ); + objectURLs.forEach( ( url ) => URL.revokeObjectURL( url ) ); + + }); + + +

أمثلة (Examples)

+ +

+ [example:webgl_loader_obj WebGL / loader / obj]
+ [example:webgl_postprocessing_outline WebGL / postprocesing / outline] +

+ +

المنشئ (Constructor)

+

+ [name]( [param:Function onLoad], [param:Function onProgress], + [param:Function onError] ) +

+

+ [page:Function onLoad] — (اختياري) سيتم استدعاء هذه الدالة عندما يتم + جميع المحملات.
+ [page:Function onProgress] — (اختياري) سيتم استدعاء هذه الدالة عند + اكتمال عنصر.
+ [page:Function onError] — (اختياري) سيتم استدعاء هذه الدالة عندما يواجه المحمل + أخطاء.
+ + ينشئ جديدًا [name]. +

+ +

الخصائص (Properties)

+ +

[property:Function onStart]

+

+ سيتم استدعاء هذه الدالة عند بدء التحميل. المعاملات هي:
+ [page:String url] — عنوان url للعنصر المحمل للتو.
+ [page:Integer itemsLoaded] — عدد العناصر المحملة حتى الآن.
+ [page:Integer itemsTotal] — إجمالي عدد العناصر التي يجب تحميلها.

+ + بشكل افتراضي هذا غير محدد. +

+ +

[property:Function onLoad]

+

+ سيتم استدعاء هذه الدالة عند اكتمال جميع التحميلات. بشكل افتراضي + هذا غير محدد، ما لم يتم تمريره في المنشئ. +

+ +

[property:Function onProgress]

+

+ سيتم استدعاء هذه الدالة عند اكتمال عنصر. المعاملات + هي:
+ [page:String url] — عنوان url للعنصر المحمل للتو.
+ [page:Integer itemsLoaded] — عدد العناصر المحملة حتى الآن.
+ [page:Integer itemsTotal] — إجمالي عدد العناصر التي يجب تحميلها.

+ + بشكل افتراضي هذا غير محدد، ما لم يتم تمريره في المنشئ. +

+ +

[property:Function onError]

+

+ سيتم استدعاء هذه الدالة عند حدوث خطأ في أي عنصر، مع الحجة:
+ [page:String url] — عنوان url للعنصر الذي حدث فيه خطأ.

+ + بشكل افتراضي هذا غير محدد، ما لم يتم تمريره في المنشئ. +

+ +

الطرق (Methods)

+ +

+ [method:this addHandler]( [param:Object regex], [param:Loader loader] ) +

+

+ [page:Object regex] — تعبير منتظم.
+ [page:Loader loader] — المحمل. +

+ +

+ يسجل محملًا مع التعبير المنتظم المعطى. يمكن استخدامه ل + تحديد أي محمل يجب استخدامه لتحميل ملفات معينة. A + حالة استخدام نموذجية هي الكتابة فوق المحمل الافتراضي للقوام. +

+ +// add handler for TGA textures +manager.addHandler( /\.tga$/i, new TGALoader() ); + + +

[method:Loader getHandler]( [param:String file] )

+

[page:String file] — مسار الملف.

+ +

+ يمكن استخدامه لاسترجاع المحمل المسجل لمسار الملف المعطى. +

+ +

[method:this removeHandler]( [param:Object regex] )

+

[page:Object regex] — تعبير منتظم.

+ +

يزيل المحمل للتعبير المنتظم المعطى.

+ +

[method:String resolveURL]( [param:String url] )

+

+ [page:String url] — عنوان url للتحميل

+ + بالنظر إلى عنوان URL، يستخدم رد الاتصال بتعديل URL (إن وجد) ويعيد + عنوان URL المحلول. إذا لم يتم تعيين تعديل URL، فسيتم إرجاع العنوان الأصلي. +

+ +

[method:this setURLModifier]( [param:Function callback] )

+

+ [page:Function callback] — رد اتصال تعديل URL. يتم استدعاؤه مع [page:String url] الحجة، + ويجب أن يعود [page:String resolvedURL].

+ + إذا تم توفيره، سيتم تمرير رد الاتصال لكل عنوان URL للمورد قبل + يتم إرسال طلب. قد يعود الرد الاتصال بالعنوان الأصلي، أو عنوان URL جديد ل + تجاوز سلوك التحميل. يمكن استخدام هذا السلوك لتحميل الأصول من + ملفات .ZIP، واجهات برمجة التطبيقات السحب والإفلات، وعناوين URI البيانات. +

+ +
+ +

+ + ملاحظة: تم تصميم الطرق التالية ليتم استدعاؤها داخليًا من قبل + المحملات. يجب ألا تستدعيها مباشرة. + +

+ +

[method:undefined itemStart]( [param:String url] )

+

+ [page:String url] — عنوان url للتحميل

+ + يجب استدعاء هذا من قبل أي محمل يستخدم المدير عندما يبدأ المحمل + تحميل عنوان url. +

+ +

[method:undefined itemEnd]( [param:String url] )

+

+ [page:String url] — عنوان url المحمَّل

+ + يجب استدعاء هذا من قبل أي محمل يستخدم المدير عند انتهاء المحمل + تحميل عنوان url. +

+ +

[method:undefined itemError]( [param:String url] )

+

+ [page:String url] — عنوان url المحمَّل

+ + يجب استدعاء هذا من قبل أي محمل يستخدم المدير عند حدوث خطأ في المحمل + تحميل عنوان url. +

+ +

المصدر (Source)

+ +

+ [link:https://github.com/mrdoob/three.js/blob/master/src/loaders/LoadingManager.js src/loaders/LoadingManager.js] +

+ + diff --git a/docs/api/en/math/Color.html b/docs/api/en/math/Color.html index 2e9f7def0d4e39..408ced10806d74 100644 --- a/docs/api/en/math/Color.html +++ b/docs/api/en/math/Color.html @@ -51,7 +51,7 @@

[page:Float b] are defined, the red component of the color. If they are not defined, it can be a [link:https://en.wikipedia.org/wiki/Web_colors#Hex_triplet hexadecimal triplet] (recommended), - a CSS-style string, or another Color instance.
+ a CSS-style string, or another `Color` instance.
[page:Float g] - (optional) If it is defined, the green component of the color.
[page:Float b] - (optional) If it is defined, the blue component of the @@ -91,13 +91,13 @@

[property:Boolean isColor]

Read-only flag to check if a given object is of type [name].

[property:Float r]

-

Red channel value between 0 and 1. Default is 1.

+

Red channel value between `0` and `1`. Default is `1`.

[property:Float g]

-

Green channel value between 0 and 1. Default is 1.

+

Green channel value between `0` and `1`. Default is `1`.

[property:Float b]

-

Blue channel value between 0 and 1. Default is 1.

+

Blue channel value between `0` and `1`. Default is `1`.

Methods

@@ -197,8 +197,7 @@

- [method:Object getHSL]( [param:Object target], [param:string colorSpace] = - LinearSRGBColorSpace ) + [method:Object getHSL]( [param:Object target], [param:string colorSpace] = LinearSRGBColorSpace )

[page:Object target] — the result will be copied into this Object. Adds h, @@ -218,8 +217,7 @@

- [method:Color getRGB]( [param:Color target], [param:string colorSpace] = - SRGBColorSpace ) + [method:Color getRGB]( [param:Color target], [param:string colorSpace] = LinearSRGBColorSpace )

[page:Color target] — the result will be copied into this object.

@@ -295,13 +293,15 @@

RGB.

-

[method:this set]( [param:Color_Hex_or_String value] )

+

[method:this set]( [param:Color_Hex_or_String r], [param:Float g], [param:Float b] )

- [page:Color_Hex_or_String value] - Value to set this color to.

+ [page:Color_Hex_or_String r] - (optional) If arguments [page:Float g] and [page:Float b] are defined, the red component of the color. If they are + not defined, it can be a [link:https://en.wikipedia.org/wiki/Web_colors#Hex_triplet hexadecimal triplet] (recommended), a CSS-style string, or another `Color` instance.
+ [page:Float g] - (optional) If it is defined, the green component of the color.
+ [page:Float b] - (optional) If it is defined, the blue component of the color.

- See the Constructor above for full details of what - [page:Color_Hex_or_String value] can be. Delegates to [page:.copy], - [page:.setStyle], or [page:.setHex] depending on input type. + See the Constructor above for full details about possible arguments. Delegates to [page:.copy], + [page:.setStyle], [page:.setRGB] or [page:.setHex] depending on input type.

[method:this setFromVector3]( [param:Vector3 vector] )

@@ -311,8 +311,7 @@

[method:this setFromVector3]( [param:Vector3 vector] )

- [method:this setHex]( [param:Integer hex], [param:string colorSpace] = - SRGBColorSpace ) + [method:this setHex]( [param:Integer hex], [param:string colorSpace] = SRGBColorSpace )

[page:Integer hex] — @@ -325,9 +324,9 @@

[method:this setHSL]( [param:Float h], [param:Float s], [param:Float l], [param:string colorSpace] = LinearSRGBColorSpace )

- [page:Float h] — hue value between 0.0 and 1.0
- [page:Float s] — saturation value between 0.0 and 1.0
- [page:Float l] — lightness value between 0.0 and 1.0

+ [page:Float h] — hue value between `0.0` and `1.0`
+ [page:Float s] — saturation value between `0.0` and `1.0`
+ [page:Float l] — lightness value between `0.0` and `1.0`

Sets color from HSL values.

@@ -336,23 +335,22 @@

[method:this setRGB]( [param:Float r], [param:Float g], [param:Float b], [param:string colorSpace] = LinearSRGBColorSpace )

- [page:Float r] — Red channel value between 0.0 and 1.0.
- [page:Float g] — Green channel value between 0.0 and 1.0.
- [page:Float b] — Blue channel value between 0.0 and 1.0.

+ [page:Float r] — Red channel value between `0.0` and `1.0`.
+ [page:Float g] — Green channel value between `0.0` and `1.0`.
+ [page:Float b] — Blue channel value between `0.0` and `1.0`.

Sets this color from RGB values.

[method:this setScalar]( [param:Float scalar] )

- [page:Float scalar] — a value between 0.0 and 1.0.

+ [page:Float scalar] — a value between `0.0` and `1.0`.

Sets all three color components to the value [page:Float scalar].

- [method:this setStyle]( [param:String style], [param:string colorSpace] = - SRGBColorSpace ) + [method:this setStyle]( [param:String style], [param:string colorSpace] = SRGBColorSpace )

[page:String style] — color as a CSS-style string.

diff --git a/docs/api/it/math/Color.html b/docs/api/it/math/Color.html index ba7e7f0a7fc0fc..a3ae3341e5c5a3 100644 --- a/docs/api/it/math/Color.html +++ b/docs/api/it/math/Color.html @@ -181,7 +181,7 @@

[method:Object getHSL]( [param:Object target], [param:string colorSpace] = L

-

[method:Color getRGB]( [param:Color target], [param:string colorSpace] = SRGBColorSpace )

+

[method:Color getRGB]( [param:Color target], [param:string colorSpace] = LinearSRGBColorSpace )

[page:Color target] - questo risultato sarà copiato in questo oggetto.

diff --git a/docs/api/zh/math/Color.html b/docs/api/zh/math/Color.html index b7d59f557f0e67..e335145a5d6983 100644 --- a/docs/api/zh/math/Color.html +++ b/docs/api/zh/math/Color.html @@ -182,7 +182,7 @@

[method:Object getHSL]( [param:Object target], [param:string colorSpace] = L

-

[method:Color getRGB]( [param:Color target], [param:string colorSpace] = SRGBColorSpace )

+

[method:Color getRGB]( [param:Color target], [param:string colorSpace] = LinearSRGBColorSpace )

[page:Color target] - 结果将复制到这个对象中.

diff --git a/docs/list.json b/docs/list.json index 63a37b65f5e050..1b4d82772d474f 100644 --- a/docs/list.json +++ b/docs/list.json @@ -373,7 +373,6 @@ "OBJLoader": "examples/en/loaders/OBJLoader", "PCDLoader": "examples/en/loaders/PCDLoader", "PDBLoader": "examples/en/loaders/PDBLoader", - "PRWMLoader": "examples/en/loaders/PRWMLoader", "SVGLoader": "examples/en/loaders/SVGLoader", "TGALoader": "examples/en/loaders/TGALoader" }, @@ -495,7 +494,154 @@ "Cameras": { "ArrayCamera": "api/ar/cameras/ArrayCamera", "Camera": "api/ar/cameras/Camera", - "CubeCamera": "api/ar/cameras/CubeCamera" + "CubeCamera": "api/ar/cameras/CubeCamera", + "OrthographicCamera": "api/ar/cameras/OrthographicCamera", + "PerspectiveCamera": "api/ar/cameras/PerspectiveCamera", + "StereoCamera": "api/ar/cameras/StereoCamera" + }, + + "Constants": { + "Animation": "api/ar/constants/Animation", + "Core": "api/ar/constants/Core", + "CustomBlendingEquation": "api/ar/constants/CustomBlendingEquations", + "BufferAttributeUsage": "api/ar/constants/BufferAttributeUsage", + "Materials": "api/ar/constants/Materials", + "Renderer": "api/ar/constants/Renderer", + "Textures": "api/ar/constants/Textures" + }, + + "Core": { + "BufferAttribute": "api/ar/core/BufferAttribute", + "BufferGeometry": "api/ar/core/BufferGeometry", + "Clock": "api/ar/core/Clock", + "EventDispatcher": "api/ar/core/EventDispatcher", + "GLBufferAttribute": "api/ar/core/GLBufferAttribute", + "InstancedBufferAttribute": "api/ar/core/InstancedBufferAttribute", + "InstancedBufferGeometry": "api/ar/core/InstancedBufferGeometry", + "InstancedInterleavedBuffer": "api/ar/core/InstancedInterleavedBuffer", + "InterleavedBuffer": "api/ar/core/InterleavedBuffer", + "InterleavedBufferAttribute": "api/ar/core/InterleavedBufferAttribute", + "Layers": "api/ar/core/Layers", + "Object3D": "api/ar/core/Object3D", + "Raycaster": "api/ar/core/Raycaster", + "Uniform": "api/ar/core/Uniform" + }, + + "Core / BufferAttributes": { + "BufferAttribute Types": "api/ar/core/bufferAttributeTypes/BufferAttributeTypes" + }, + + "Extras": { + "DataUtils": "api/ar/extras/DataUtils", + "Earcut": "api/ar/extras/Earcut", + "ImageUtils": "api/ar/extras/ImageUtils", + "PMREMGenerator": "api/ar/extras/PMREMGenerator", + "ShapeUtils": "api/ar/extras/ShapeUtils" + }, + + "Extras / Core": { + "Curve": "api/ar/extras/core/Curve", + "CurvePath": "api/ar/extras/core/CurvePath", + "Interpolations": "api/ar/extras/core/Interpolations", + "Path": "api/ar/extras/core/Path", + "Shape": "api/ar/extras/core/Shape", + "ShapePath": "api/ar/extras/core/ShapePath" + }, + + "Extras / Curves": { + "ArcCurve": "api/ar/extras/curves/ArcCurve", + "CatmullRomCurve3": "api/ar/extras/curves/CatmullRomCurve3", + "CubicBezierCurve": "api/ar/extras/curves/CubicBezierCurve", + "CubicBezierCurve3": "api/ar/extras/curves/CubicBezierCurve3", + "EllipseCurve": "api/ar/extras/curves/EllipseCurve", + "LineCurve": "api/ar/extras/curves/LineCurve", + "LineCurve3": "api/ar/extras/curves/LineCurve3", + "QuadraticBezierCurve": "api/ar/extras/curves/QuadraticBezierCurve", + "QuadraticBezierCurve3": "api/ar/extras/curves/QuadraticBezierCurve3", + "SplineCurve": "api/ar/extras/curves/SplineCurve" + }, + + "Geometries": { + "BoxGeometry": "api/ar/geometries/BoxGeometry", + "CapsuleGeometry": "api/ar/geometries/CapsuleGeometry", + "CircleGeometry": "api/ar/geometries/CircleGeometry", + "ConeGeometry": "api/ar/geometries/ConeGeometry", + "CylinderGeometry": "api/ar/geometries/CylinderGeometry", + "DodecahedronGeometry": "api/ar/geometries/DodecahedronGeometry", + "EdgesGeometry": "api/ar/geometries/EdgesGeometry", + "ExtrudeGeometry": "api/ar/geometries/ExtrudeGeometry", + "IcosahedronGeometry": "api/ar/geometries/IcosahedronGeometry", + "LatheGeometry": "api/ar/geometries/LatheGeometry", + "OctahedronGeometry": "api/ar/geometries/OctahedronGeometry", + "PlaneGeometry": "api/ar/geometries/PlaneGeometry", + "PolyhedronGeometry": "api/ar/geometries/PolyhedronGeometry", + "RingGeometry": "api/ar/geometries/RingGeometry", + "ShapeGeometry": "api/ar/geometries/ShapeGeometry", + "SphereGeometry": "api/ar/geometries/SphereGeometry", + "TetrahedronGeometry": "api/ar/geometries/TetrahedronGeometry", + "TorusGeometry": "api/ar/geometries/TorusGeometry", + "TorusKnotGeometry": "api/ar/geometries/TorusKnotGeometry", + "TubeGeometry": "api/ar/geometries/TubeGeometry", + "WireframeGeometry": "api/ar/geometries/WireframeGeometry" + }, + + "Helpers": { + "ArrowHelper": "api/ar/helpers/ArrowHelper", + "AxesHelper": "api/ar/helpers/AxesHelper", + "BoxHelper": "api/ar/helpers/BoxHelper", + "Box3Helper": "api/ar/helpers/Box3Helper", + "CameraHelper": "api/ar/helpers/CameraHelper", + "DirectionalLightHelper": "api/ar/helpers/DirectionalLightHelper", + "GridHelper": "api/ar/helpers/GridHelper", + "PolarGridHelper": "api/ar/helpers/PolarGridHelper", + "HemisphereLightHelper": "api/ar/helpers/HemisphereLightHelper", + "PlaneHelper": "api/ar/helpers/PlaneHelper", + "PointLightHelper": "api/ar/helpers/PointLightHelper", + "SkeletonHelper": "api/ar/helpers/SkeletonHelper", + "SpotLightHelper": "api/ar/helpers/SpotLightHelper" + }, + + "Lights": { + "AmbientLight": "api/ar/lights/AmbientLight", + "AmbientLightProbe": "api/ar/lights/AmbientLightProbe", + "DirectionalLight": "api/ar/lights/DirectionalLight", + "HemisphereLight": "api/ar/lights/HemisphereLight", + "HemisphereLightProbe": "api/ar/lights/HemisphereLightProbe", + "Light": "api/ar/lights/Light", + "LightProbe": "api/ar/lights/LightProbe", + "PointLight": "api/ar/lights/PointLight", + "RectAreaLight": "api/ar/lights/RectAreaLight", + "SpotLight": "api/ar/lights/SpotLight" + }, + + "Lights / Shadows": { + "LightShadow": "api/ar/lights/shadows/LightShadow", + "PointLightShadow": "api/ar/lights/shadows/PointLightShadow", + "DirectionalLightShadow": "api/ar/lights/shadows/DirectionalLightShadow", + "SpotLightShadow": "api/ar/lights/shadows/SpotLightShadow" + }, + + "Loaders": { + "AnimationLoader": "api/ar/loaders/AnimationLoader", + "AudioLoader": "api/ar/loaders/AudioLoader", + "BufferGeometryLoader": "api/ar/loaders/BufferGeometryLoader", + "Cache": "api/ar/loaders/Cache", + "CompressedTextureLoader": "api/ar/loaders/CompressedTextureLoader", + "CubeTextureLoader": "api/ar/loaders/CubeTextureLoader", + "DataTextureLoader": "api/ar/loaders/DataTextureLoader", + "FileLoader": "api/ar/loaders/FileLoader", + "ImageBitmapLoader": "api/ar/loaders/ImageBitmapLoader", + "ImageLoader": "api/ar/loaders/ImageLoader", + "Loader": "api/ar/loaders/Loader", + "LoaderUtils": "api/ar/loaders/LoaderUtils", + "MaterialLoader": "api/ar/loaders/MaterialLoader", + "ObjectLoader": "api/ar/loaders/ObjectLoader", + "TextureLoader": "api/ar/loaders/TextureLoader" + }, + + "Loaders / Managers": { + "DefaultLoadingManager": "api/ar/loaders/managers/DefaultLoadingManager", + "LoadingManager": "api/ar/loaders/managers/LoadingManager" } } @@ -598,6 +744,7 @@ }, "附件": { + "DataUtils": "api/zh/extras/DataUtils", "Earcut": "api/zh/extras/Earcut", "ImageUtils": "api/zh/extras/ImageUtils", "PMREMGenerator": "api/zh/extras/PMREMGenerator", @@ -628,6 +775,7 @@ "几何体": { "BoxGeometry": "api/zh/geometries/BoxGeometry", + "CapsuleGeometry": "api/zh/geometries/CapsuleGeometry", "CircleGeometry": "api/zh/geometries/CircleGeometry", "ConeGeometry": "api/zh/geometries/ConeGeometry", "CylinderGeometry": "api/zh/geometries/CylinderGeometry", @@ -776,6 +924,8 @@ }, "渲染器": { + "WebGL3DRenderTarget": "api/zh/renderers/WebGL3DRenderTarget", + "WebGLArrayRenderTarget": "api/zh/renderers/WebGLArrayRenderTarget", "WebGLMultipleRenderTargets": "api/zh/renderers/WebGLMultipleRenderTargets", "WebGLRenderer": "api/zh/renderers/WebGLRenderer", "WebGL1Renderer": "api/zh/renderers/WebGL1Renderer", @@ -849,11 +999,14 @@ "加载器": { "FontLoader": "examples/zh/loaders/FontLoader", + "DracoLoader": "examples/zh/loaders/DracoLoader", "GLTFLoader": "examples/zh/loaders/GLTFLoader", "MMDLoader": "examples/zh/loaders/MMDLoader", "MTLLoader": "examples/zh/loaders/MTLLoader", "OBJLoader": "examples/zh/loaders/OBJLoader", - "PCDLoader": "examples/zh/loaders/PCDLoader" + "PCDLoader": "examples/zh/loaders/PCDLoader", + "SVGLoader": "examples/zh/loaders/SVGLoader", + "TGALoader": "examples/zh/loaders/TGALoader" }, "物体": { @@ -1987,7 +2140,6 @@ "OBJLoader": "examples/en/loaders/OBJLoader", "PCDLoader": "examples/en/loaders/PCDLoader", "PDBLoader": "examples/en/loaders/PDBLoader", - "PRWMLoader": "examples/en/loaders/PRWMLoader", "SVGLoader": "examples/en/loaders/SVGLoader", "TGALoader": "examples/en/loaders/TGALoader" }, diff --git a/docs/page.js b/docs/page.js index 92b7e407670573..d53df55f02ebb7 100644 --- a/docs/page.js +++ b/docs/page.js @@ -111,16 +111,32 @@ function onDocumentLoad() { // handle code snippets formatting + function dedent( text ) { + + // ignores singleline text + const lines = text.split( '\n' ); + if ( lines.length <= 1 ) return text; + + // ignores blank text + const nonBlankLine = lines.filter( l => l.trim() )[ 0 ]; + if ( nonBlankLine === undefined ) return text; + + // strips indents if any + const m = nonBlankLine.match( /^([\t ]+)/ ); + if ( m ) text = lines.map( l => l.startsWith( m[ 1 ] ) ? l.substring( m[ 1 ].length ) : l ).join( '\n' ); + + // strips leading and trailing whitespaces finally + return text.trim(); + + } + const elements = document.getElementsByTagName( 'code' ); for ( let i = 0; i < elements.length; i ++ ) { const element = elements[ i ]; - text = element.textContent.trim(); - text = text.replace( /^\t\t/gm, '' ); - - element.textContent = text; + element.textContent = dedent( element.textContent ); } diff --git a/examples/files.json b/examples/files.json index d03a2f0225f8f4..b911079e9e8e30 100644 --- a/examples/files.json +++ b/examples/files.json @@ -328,6 +328,7 @@ "webgpu": [ "webgpu_audio_processing", "webgpu_backdrop", + "webgpu_backdrop_area", "webgpu_compute", "webgpu_cubemap_adjustments", "webgpu_cubemap_mix", diff --git a/examples/jsm/nodes/Nodes.js b/examples/jsm/nodes/Nodes.js index 2ba8263f8d114b..eddf15613e5768 100644 --- a/examples/jsm/nodes/Nodes.js +++ b/examples/jsm/nodes/Nodes.js @@ -63,16 +63,17 @@ export * from './shadernode/ShaderNode.js'; // accessors export { default as BitangentNode, bitangentGeometry, bitangentLocal, bitangentView, bitangentWorld, transformedBitangentView, transformedBitangentWorld } from './accessors/BitangentNode.js'; export { default as BufferNode, buffer } from './accessors/BufferNode.js'; -export { default as CameraNode, cameraProjectionMatrix, cameraViewMatrix, cameraNormalMatrix, cameraWorldMatrix, cameraPosition } from './accessors/CameraNode.js'; +export { default as CameraNode, cameraProjectionMatrix, cameraViewMatrix, cameraNormalMatrix, cameraWorldMatrix, cameraPosition, cameraNear, cameraFar } from './accessors/CameraNode.js'; export { default as CubeTextureNode, cubeTexture } from './accessors/CubeTextureNode.js'; export { default as ExtendedMaterialNode, materialNormal } from './accessors/ExtendedMaterialNode.js'; export { default as InstanceNode, instance } from './accessors/InstanceNode.js'; export { default as MaterialNode, materialUV, materialAlphaTest, materialColor, materialShininess, materialEmissive, materialOpacity, materialSpecularColor, materialReflectivity, materialRoughness, materialMetalness, materialRotation } from './accessors/MaterialNode.js'; export { default as MaterialReferenceNode, materialReference } from './accessors/MaterialReferenceNode.js'; -export { default as ModelNode, modelDirection, modelViewMatrix, modelNormalMatrix, modelWorldMatrix, modelPosition, modelViewPosition } from './accessors/ModelNode.js'; +export { default as TextureBicubicNode, textureBicubic } from './accessors/TextureBicubicNode.js'; +export { default as ModelNode, modelDirection, modelViewMatrix, modelNormalMatrix, modelWorldMatrix, modelPosition, modelViewPosition, modelScale } from './accessors/ModelNode.js'; export { default as ModelViewProjectionNode, modelViewProjection } from './accessors/ModelViewProjectionNode.js'; export { default as NormalNode, normalGeometry, normalLocal, normalView, normalWorld, transformedNormalView, transformedNormalWorld } from './accessors/NormalNode.js'; -export { default as Object3DNode, objectDirection, objectViewMatrix, objectNormalMatrix, objectWorldMatrix, objectPosition, objectViewPosition } from './accessors/Object3DNode.js'; +export { default as Object3DNode, objectDirection, objectViewMatrix, objectNormalMatrix, objectWorldMatrix, objectPosition, objectScale, objectViewPosition } from './accessors/Object3DNode.js'; export { default as PointUVNode, pointUV } from './accessors/PointUVNode.js'; export { default as PositionNode, positionGeometry, positionLocal, positionWorld, positionWorldDirection, positionView, positionViewDirection } from './accessors/PositionNode.js'; export { default as ReferenceNode, reference } from './accessors/ReferenceNode.js'; @@ -80,7 +81,7 @@ export { default as ReflectVectorNode, reflectVector } from './accessors/Reflect export { default as SkinningNode, skinning } from './accessors/SkinningNode.js'; export { default as StorageBufferNode, storage } from './accessors/StorageBufferNode.js'; export { default as TangentNode, tangentGeometry, tangentLocal, tangentView, tangentWorld, transformedTangentView, transformedTangentWorld } from './accessors/TangentNode.js'; -export { default as TextureNode, texture, sampler } from './accessors/TextureNode.js'; +export { default as TextureNode, texture, /*textureLevel,*/ sampler } from './accessors/TextureNode.js'; export { default as UVNode, uv } from './accessors/UVNode.js'; export { default as UserDataNode, userData } from './accessors/UserDataNode.js'; @@ -93,8 +94,10 @@ export { default as NormalMapNode, normalMap, TBNViewMatrix } from './display/No export { default as PosterizeNode, posterize } from './display/PosterizeNode.js'; export { default as ToneMappingNode, toneMapping } from './display/ToneMappingNode.js'; export { default as ViewportNode, viewportCoordinate, viewportResolution, viewportTopLeft, viewportBottomLeft, viewportTopRight, viewportBottomRight } from './display/ViewportNode.js'; -export { default as ViewportTextureNode, viewportTexture } from './display/ViewportTextureNode.js'; +export { default as ViewportTextureNode, viewportTexture, viewportMipTexture } from './display/ViewportTextureNode.js'; export { default as ViewportSharedTextureNode, viewportSharedTexture } from './display/ViewportSharedTextureNode.js'; +export { default as ViewportDepthTextureNode, viewportDepthTexture } from './display/ViewportDepthTextureNode.js'; +export { default as ViewportDepthNode, viewZToOrthographicDepth, orthographicDepthToViewZ, viewZToPerspectiveDepth, perspectiveDepthToViewZ, depth, depthTexture } from './display/ViewportDepthNode.js'; // code export { default as ExpressionNode, expression } from './code/ExpressionNode.js'; diff --git a/examples/jsm/nodes/accessors/CameraNode.js b/examples/jsm/nodes/accessors/CameraNode.js index 7e41af5a01f62b..df37f6150be612 100644 --- a/examples/jsm/nodes/accessors/CameraNode.js +++ b/examples/jsm/nodes/accessors/CameraNode.js @@ -18,6 +18,10 @@ class CameraNode extends Object3DNode { return 'mat4'; + } else if ( scope === CameraNode.NEAR || scope === CameraNode.FAR ) { + + return 'float'; + } return super.getNodeType( builder ); @@ -30,13 +34,21 @@ class CameraNode extends Object3DNode { const uniformNode = this._uniformNode; const scope = this.scope; - if ( scope === CameraNode.PROJECTION_MATRIX ) { + if ( scope === CameraNode.VIEW_MATRIX ) { + + uniformNode.value = camera.matrixWorldInverse; + + } else if ( scope === CameraNode.PROJECTION_MATRIX ) { uniformNode.value = camera.projectionMatrix; - } else if ( scope === CameraNode.VIEW_MATRIX ) { + } else if ( scope === CameraNode.NEAR ) { - uniformNode.value = camera.matrixWorldInverse; + uniformNode.value = camera.near; + + } else if ( scope === CameraNode.FAR ) { + + uniformNode.value = camera.far; } else { @@ -56,6 +68,10 @@ class CameraNode extends Object3DNode { this._uniformNode.nodeType = 'mat4'; + } else if ( scope === CameraNode.NEAR || scope === CameraNode.FAR ) { + + this._uniformNode.nodeType = 'float'; + } return super.generate( builder ); @@ -65,10 +81,14 @@ class CameraNode extends Object3DNode { } CameraNode.PROJECTION_MATRIX = 'projectionMatrix'; +CameraNode.NEAR = 'near'; +CameraNode.FAR = 'far'; export default CameraNode; export const cameraProjectionMatrix = nodeImmutable( CameraNode, CameraNode.PROJECTION_MATRIX ); +export const cameraNear = nodeImmutable( CameraNode, CameraNode.NEAR ); +export const cameraFar = nodeImmutable( CameraNode, CameraNode.FAR ); export const cameraViewMatrix = nodeImmutable( CameraNode, CameraNode.VIEW_MATRIX ); export const cameraNormalMatrix = nodeImmutable( CameraNode, CameraNode.NORMAL_MATRIX ); export const cameraWorldMatrix = nodeImmutable( CameraNode, CameraNode.WORLD_MATRIX ); diff --git a/examples/jsm/nodes/accessors/ModelNode.js b/examples/jsm/nodes/accessors/ModelNode.js index d63adbd57b1242..831369a2fac17a 100644 --- a/examples/jsm/nodes/accessors/ModelNode.js +++ b/examples/jsm/nodes/accessors/ModelNode.js @@ -27,6 +27,7 @@ export const modelViewMatrix = nodeImmutable( ModelNode, ModelNode.VIEW_MATRIX ) export const modelNormalMatrix = nodeImmutable( ModelNode, ModelNode.NORMAL_MATRIX ); export const modelWorldMatrix = nodeImmutable( ModelNode, ModelNode.WORLD_MATRIX ); export const modelPosition = nodeImmutable( ModelNode, ModelNode.POSITION ); +export const modelScale = nodeImmutable( ModelNode, ModelNode.SCALE ); export const modelViewPosition = nodeImmutable( ModelNode, ModelNode.VIEW_POSITION ); addNodeClass( ModelNode ); diff --git a/examples/jsm/nodes/accessors/Object3DNode.js b/examples/jsm/nodes/accessors/Object3DNode.js index 516fc32a8d82bb..9cfa4505b08664 100644 --- a/examples/jsm/nodes/accessors/Object3DNode.js +++ b/examples/jsm/nodes/accessors/Object3DNode.js @@ -32,7 +32,7 @@ class Object3DNode extends Node { return 'mat3'; - } else if ( scope === Object3DNode.POSITION || scope === Object3DNode.VIEW_POSITION || scope === Object3DNode.DIRECTION ) { + } else if ( scope === Object3DNode.POSITION || scope === Object3DNode.VIEW_POSITION || scope === Object3DNode.DIRECTION || scope === Object3DNode.SCALE ) { return 'vec3'; @@ -64,6 +64,12 @@ class Object3DNode extends Node { uniformNode.value.setFromMatrixPosition( object.matrixWorld ); + } else if ( scope === Object3DNode.SCALE ) { + + uniformNode.value = uniformNode.value || new Vector3(); + + uniformNode.value.setFromMatrixScale( object.matrixWorld ); + } else if ( scope === Object3DNode.DIRECTION ) { uniformNode.value = uniformNode.value || new Vector3(); @@ -95,7 +101,7 @@ class Object3DNode extends Node { this._uniformNode.nodeType = 'mat3'; - } else if ( scope === Object3DNode.POSITION || scope === Object3DNode.VIEW_POSITION || scope === Object3DNode.DIRECTION ) { + } else if ( scope === Object3DNode.POSITION || scope === Object3DNode.VIEW_POSITION || scope === Object3DNode.DIRECTION || scope === Object3DNode.SCALE ) { this._uniformNode.nodeType = 'vec3'; @@ -127,6 +133,7 @@ Object3DNode.VIEW_MATRIX = 'viewMatrix'; Object3DNode.NORMAL_MATRIX = 'normalMatrix'; Object3DNode.WORLD_MATRIX = 'worldMatrix'; Object3DNode.POSITION = 'position'; +Object3DNode.SCALE = 'scale'; Object3DNode.VIEW_POSITION = 'viewPosition'; Object3DNode.DIRECTION = 'direction'; @@ -137,6 +144,7 @@ export const objectViewMatrix = nodeProxy( Object3DNode, Object3DNode.VIEW_MATRI export const objectNormalMatrix = nodeProxy( Object3DNode, Object3DNode.NORMAL_MATRIX ); export const objectWorldMatrix = nodeProxy( Object3DNode, Object3DNode.WORLD_MATRIX ); export const objectPosition = nodeProxy( Object3DNode, Object3DNode.POSITION ); +export const objectScale = nodeProxy( Object3DNode, Object3DNode.SCALE ); export const objectViewPosition = nodeProxy( Object3DNode, Object3DNode.VIEW_POSITION ); addNodeClass( Object3DNode ); diff --git a/examples/jsm/nodes/accessors/TextureBicubicNode.js b/examples/jsm/nodes/accessors/TextureBicubicNode.js new file mode 100644 index 00000000000000..846450d8f12aca --- /dev/null +++ b/examples/jsm/nodes/accessors/TextureBicubicNode.js @@ -0,0 +1,94 @@ +import TempNode from '../core/TempNode.js'; +import { addNodeClass } from '../core/Node.js'; +import { add, mul, div } from '../math/OperatorNode.js'; +import { floor, ceil, fract, pow } from '../math/MathNode.js'; +import { nodeProxy, addNodeElement, float, vec2, vec4, int } from '../shadernode/ShaderNode.js'; + +// Mipped Bicubic Texture Filtering by N8 +// https://www.shadertoy.com/view/Dl2SDW + +const bC = 1.0 / 6.0; + +const w0 = ( a ) => mul( bC, mul( a, mul( a, a.negate().add( 3.0 ) ).sub( 3.0 ) ).add( 1.0 ) ); + +const w1 = ( a ) => mul( bC, mul( a, mul( a, mul( 3.0, a ).sub( 6.0 ) ) ).add( 4.0 ) ); + +const w2 = ( a ) => mul( bC, mul( a, mul( a, mul( - 3.0, a ).add( 3.0 ) ).add( 3.0 ) ).add( 1.0 ) ); + +const w3 = ( a ) => mul( bC, pow( a, 3 ) ); + +const g0 = ( a ) => w0( a ).add( w1( a ) ); + +const g1 = ( a ) => w2( a ).add( w3( a ) ); + +// h0 and h1 are the two offset functions +const h0 = ( a ) => add( - 1.0, w1( a ).div( w0( a ).add( w1( a ) ) ) ); + +const h1 = ( a ) => add( 1.0, w3( a ).div( w2( a ).add( w3( a ) ) ) ); + +const bicubic = ( textureNode, texelSize, lod ) => { + + const uv = textureNode.uvNode; + const uvScaled = mul( uv, texelSize.zw ).add( 0.5 ); + + const iuv = floor( uvScaled ); + const fuv = fract( uvScaled ); + + const g0x = g0( fuv.x ); + const g1x = g1( fuv.x ); + const h0x = h0( fuv.x ); + const h1x = h1( fuv.x ); + const h0y = h0( fuv.y ); + const h1y = h1( fuv.y ); + + const p0 = vec2( iuv.x.add( h0x ), iuv.y.add( h0y ) ).sub( 0.5 ).mul( texelSize.xy ); + const p1 = vec2( iuv.x.add( h1x ), iuv.y.add( h0y ) ).sub( 0.5 ).mul( texelSize.xy ); + const p2 = vec2( iuv.x.add( h0x ), iuv.y.add( h1y ) ).sub( 0.5 ).mul( texelSize.xy ); + const p3 = vec2( iuv.x.add( h1x ), iuv.y.add( h1y ) ).sub( 0.5 ).mul( texelSize.xy ); + + const a = g0( fuv.y ).mul( add( g0x.mul( textureNode.uv( p0 ).level( lod ) ), g1x.mul( textureNode.uv( p1 ).level( lod ) ) ) ); + const b = g1( fuv.y ).mul( add( g0x.mul( textureNode.uv( p2 ).level( lod ) ), g1x.mul( textureNode.uv( p3 ).level( lod ) ) ) ); + + return a.add( b ); + +}; + +const textureBicubicMethod = ( textureNode, lodNode ) => { + + const fLodSize = vec2( textureNode.size( int( lodNode ) ) ); + const cLodSize = vec2( textureNode.size( int( lodNode.add( 1.0 ) ) ) ); + const fLodSizeInv = div( 1.0, fLodSize ); + const cLodSizeInv = div( 1.0, cLodSize ); + const fSample = bicubic( textureNode, vec4( fLodSizeInv, fLodSize ), floor( lodNode ) ); + const cSample = bicubic( textureNode, vec4( cLodSizeInv, cLodSize ), ceil( lodNode ) ); + + return fract( lodNode ).mix( fSample, cSample ); + +}; + +class TextureBicubicNode extends TempNode { + + constructor( textureNode, blurNode = float( 3 ) ) { + + super( 'vec4' ); + + this.textureNode = textureNode; + this.blurNode = blurNode; + + } + + construct() { + + return textureBicubicMethod( this.textureNode, this.blurNode ); + + } + +} + +export default TextureBicubicNode; + +export const textureBicubic = nodeProxy( TextureBicubicNode ); + +addNodeElement( 'bicubic', textureBicubic ); + +addNodeClass( TextureBicubicNode ); diff --git a/examples/jsm/nodes/accessors/TextureNode.js b/examples/jsm/nodes/accessors/TextureNode.js index fce9e2f6fcfcdb..a2c33d40ec1c1c 100644 --- a/examples/jsm/nodes/accessors/TextureNode.js +++ b/examples/jsm/nodes/accessors/TextureNode.js @@ -1,5 +1,7 @@ import UniformNode from '../core/UniformNode.js'; import { uv } from './UVNode.js'; +import { textureSize } from './TextureSizeNode.js'; +import { context } from '../core/ContextNode.js'; import { addNodeClass } from '../core/Node.js'; import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js'; @@ -140,6 +142,32 @@ class TextureNode extends UniformNode { } + uv( uvNode ) { + + const textureNode = this.clone(); + textureNode.uvNode = uvNode; + + return textureNode; + + } + + level( levelNode ) { + + const textureNode = this.clone(); + textureNode.levelNode = levelNode; + + return context( textureNode, { + getMIPLevelAlgorithmNode: ( textureNode, levelNode ) => levelNode + } ); + + } + + size( levelNode ) { + + return textureSize( this, levelNode ); + + } + serialize( data ) { super.serialize( data ); @@ -156,13 +184,22 @@ class TextureNode extends UniformNode { } + clone() { + + return new this.constructor( this.value, this.uvNode, this.levelNode ); + + } + } export default TextureNode; export const texture = nodeProxy( TextureNode ); +//export const textureLevel = ( value, uv, level ) => texture( value, uv ).level( level ); + export const sampler = ( aTexture ) => ( aTexture.isNode === true ? aTexture : texture( aTexture ) ).convert( 'sampler' ); addNodeElement( 'texture', texture ); +//addNodeElement( 'textureLevel', textureLevel ); addNodeClass( TextureNode ); diff --git a/examples/jsm/nodes/accessors/TextureSizeNode.js b/examples/jsm/nodes/accessors/TextureSizeNode.js new file mode 100644 index 00000000000000..04501f1a16fdd1 --- /dev/null +++ b/examples/jsm/nodes/accessors/TextureSizeNode.js @@ -0,0 +1,35 @@ +import Node from '../core/Node.js'; +import { addNodeClass } from '../core/Node.js'; +import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js'; + +class TextureSizeNode extends Node { + + constructor( textureNode, levelNode = null ) { + + super( 'uvec2' ); + + this.isTextureSizeNode = true; + + this.textureNode = textureNode; + this.levelNode = levelNode; + + } + + generate( builder, output ) { + + const textureProperty = this.textureNode.build( builder, 'property' ); + const levelNode = this.levelNode.build( builder, 'int' ); + + return builder.format( `textureDimensions( ${textureProperty}, ${levelNode} )`, this.getNodeType( builder ), output ); + + } + +} + +export default TextureSizeNode; + +export const textureSize = nodeProxy( TextureSizeNode ); + +addNodeElement( 'textureSize', textureSize ); + +addNodeClass( TextureSizeNode ); diff --git a/examples/jsm/nodes/display/ViewportDepthNode.js b/examples/jsm/nodes/display/ViewportDepthNode.js new file mode 100644 index 00000000000000..7c31c7f4d6349e --- /dev/null +++ b/examples/jsm/nodes/display/ViewportDepthNode.js @@ -0,0 +1,69 @@ +import Node, { addNodeClass } from '../core/Node.js'; +import { nodeImmutable, nodeProxy } from '../shadernode/ShaderNode.js'; +import { cameraNear, cameraFar } from '../accessors/CameraNode.js'; +import { positionView } from '../accessors/PositionNode.js'; +import { viewportDepthTexture } from './ViewportDepthTextureNode.js'; + +class ViewportDepthNode extends Node { + + constructor( scope, textureNode = null ) { + + super( 'float' ); + + this.scope = scope; + this.textureNode = textureNode; + + this.isViewportDepthNode = true; + + } + + construct( /*builder*/ ) { + + const { scope } = this; + + let node = null; + + if ( scope === ViewportDepthNode.DEPTH ) { + + node = viewZToOrthographicDepth( positionView.z, cameraNear, cameraFar ); + + } else if ( scope === ViewportDepthNode.DEPTH_TEXTURE ) { + + const texture = this.textureNode || viewportDepthTexture(); + + const viewZ = perspectiveDepthToViewZ( texture, cameraNear, cameraFar ); + node = viewZToOrthographicDepth( viewZ, cameraNear, cameraFar ); + + } + + return node; + + } + +} + +// NOTE: viewZ, the z-coordinate in camera space, is negative for points in front of the camera + +// -near maps to 0; -far maps to 1 +export const viewZToOrthographicDepth = ( viewZ, near, far ) => viewZ.add( near ).div( near.sub( far ) ); + +// maps orthographic depth in [ 0, 1 ] to viewZ +export const orthographicDepthToViewZ = ( depth, near, far ) => near.sub( far ).mul( depth ).sub( near ); + +// NOTE: https://twitter.com/gonnavis/status/1377183786949959682 + +// -near maps to 0; -far maps to 1 +export const viewZToPerspectiveDepth = ( viewZ, near, far ) => near.add( viewZ ).mul( far ).div( near.sub( far ).mul( viewZ ) ); + +// maps perspective depth in [ 0, 1 ] to viewZ +export const perspectiveDepthToViewZ = ( depth, near, far ) => near.mul( far ).div( far.sub( near ).mul( depth ).sub( far ) ); + +ViewportDepthNode.DEPTH = 'depth'; +ViewportDepthNode.DEPTH_TEXTURE = 'depthTexture'; + +export default ViewportDepthNode; + +export const depth = nodeImmutable( ViewportDepthNode, ViewportDepthNode.DEPTH ); +export const depthTexture = nodeProxy( ViewportDepthNode, ViewportDepthNode.DEPTH_TEXTURE ); + +addNodeClass( ViewportDepthNode ); diff --git a/examples/jsm/nodes/display/ViewportDepthTextureNode.js b/examples/jsm/nodes/display/ViewportDepthTextureNode.js new file mode 100644 index 00000000000000..d090ad2b020b64 --- /dev/null +++ b/examples/jsm/nodes/display/ViewportDepthTextureNode.js @@ -0,0 +1,34 @@ +import ViewportTextureNode from './ViewportTextureNode.js'; +import { addNodeClass } from '../core/Node.js'; +import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js'; +import { viewportTopLeft } from './ViewportNode.js'; +import { DepthTexture, LinearMipmapLinearFilter, DepthFormat, UnsignedIntType } from 'three'; + +let sharedDepthbuffer = null; + +class ViewportDepthTextureNode extends ViewportTextureNode { + + constructor( uvNode = viewportTopLeft, levelNode = null ) { + + if ( sharedDepthbuffer === null ) { + + sharedDepthbuffer = new DepthTexture(); + sharedDepthbuffer.minFilter = LinearMipmapLinearFilter; + sharedDepthbuffer.type = UnsignedIntType; + sharedDepthbuffer.format = DepthFormat; + + } + + super( uvNode, levelNode, sharedDepthbuffer ); + + } + +} + +export default ViewportDepthTextureNode; + +export const viewportDepthTexture = nodeProxy( ViewportDepthTextureNode ); + +addNodeElement( 'viewportDepthTexture', viewportDepthTexture ); + +addNodeClass( ViewportDepthTextureNode ); diff --git a/examples/jsm/nodes/display/ViewportSharedTextureNode.js b/examples/jsm/nodes/display/ViewportSharedTextureNode.js index 395a3daeb2a871..853f26feeda7e6 100644 --- a/examples/jsm/nodes/display/ViewportSharedTextureNode.js +++ b/examples/jsm/nodes/display/ViewportSharedTextureNode.js @@ -2,20 +2,21 @@ import ViewportTextureNode from './ViewportTextureNode.js'; import { addNodeClass } from '../core/Node.js'; import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js'; import { viewportTopLeft } from './ViewportNode.js'; +import { FramebufferTexture } from 'three'; -let rtt = null; +let sharedFramebuffer = null; class ViewportSharedTextureNode extends ViewportTextureNode { - constructor( uv = viewportTopLeft ) { + constructor( uvNode = viewportTopLeft, levelNode = null ) { - super( uv ); + if ( sharedFramebuffer === null ) { - } + sharedFramebuffer = new FramebufferTexture(); - constructRTT( builder ) { + } - return rtt || ( rtt = builder.getRenderTarget() ); + super( uvNode, levelNode, sharedFramebuffer ); } diff --git a/examples/jsm/nodes/display/ViewportTextureNode.js b/examples/jsm/nodes/display/ViewportTextureNode.js index 57a1d6308b287f..356f5c28b4681d 100644 --- a/examples/jsm/nodes/display/ViewportTextureNode.js +++ b/examples/jsm/nodes/display/ViewportTextureNode.js @@ -3,17 +3,24 @@ import { NodeUpdateType } from '../core/constants.js'; import { addNodeClass } from '../core/Node.js'; import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js'; import { viewportTopLeft } from './ViewportNode.js'; -import { Vector2 } from 'three'; +import { Vector2, FramebufferTexture, LinearMipmapLinearFilter } from 'three'; -let size = new Vector2(); +const _size = new Vector2(); class ViewportTextureNode extends TextureNode { - constructor( uv = viewportTopLeft, level = null ) { + constructor( uvNode = viewportTopLeft, levelNode = null, framebufferTexture = null ) { - super( null, uv, level ); + if ( framebufferTexture === null ) { - this.rtt = null; + framebufferTexture = new FramebufferTexture(); + framebufferTexture.minFilter = LinearMipmapLinearFilter; + + } + + super( framebufferTexture, uvNode, levelNode ); + + this.generateMipmaps = false; this.isOutputTextureNode = true; @@ -21,32 +28,37 @@ class ViewportTextureNode extends TextureNode { } - constructRTT( builder ) { + updateBefore( frame ) { - return builder.getRenderTarget(); + const renderer = frame.renderer; + renderer.getDrawingBufferSize( _size ); - } + // - construct( builder ) { + const framebufferTexture = this.value; - if ( this.rtt === null ) this.rtt = this.constructRTT( builder ); + if ( framebufferTexture.image.width !== _size.width || framebufferTexture.image.height !== _size.height ) { - this.value = this.rtt.texture; + framebufferTexture.image.width = _size.width; + framebufferTexture.image.height = _size.height; + framebufferTexture.needsUpdate = true; - return super.construct( builder ); + } - } + // - updateBefore( frame ) { + const currentGenerateMipmaps = framebufferTexture.generateMipmaps; + framebufferTexture.generateMipmaps = this.generateMipmaps; - const rtt = this.rtt; + renderer.copyFramebufferToTexture( framebufferTexture ); - const renderer = frame.renderer; - renderer.getDrawingBufferSize( size ); + framebufferTexture.generateMipmaps = currentGenerateMipmaps; + + } - rtt.setSize( size.width, size.height ); + clone() { - renderer.copyFramebufferToRenderTarget( rtt ); + return new this.constructor( this.uvNode, this.levelNode, this.value ); } @@ -55,7 +67,9 @@ class ViewportTextureNode extends TextureNode { export default ViewportTextureNode; export const viewportTexture = nodeProxy( ViewportTextureNode ); +export const viewportMipTexture = nodeProxy( ViewportTextureNode, null, null, { generateMipmaps: true } ); addNodeElement( 'viewportTexture', viewportTexture ); +addNodeElement( 'viewportMipTexture', viewportMipTexture ); addNodeClass( ViewportTextureNode ); diff --git a/examples/jsm/postprocessing/TAARenderPass.js b/examples/jsm/postprocessing/TAARenderPass.js index 6a7180ae840de0..cbbea08b6601b2 100644 --- a/examples/jsm/postprocessing/TAARenderPass.js +++ b/examples/jsm/postprocessing/TAARenderPass.js @@ -1,4 +1,5 @@ import { + HalfFloatType, WebGLRenderTarget } from 'three'; import { SSAARenderPass } from './SSAARenderPass.js'; @@ -41,14 +42,14 @@ class TAARenderPass extends SSAARenderPass { if ( this.sampleRenderTarget === undefined ) { - this.sampleRenderTarget = new WebGLRenderTarget( readBuffer.width, readBuffer.height, this.params ); + this.sampleRenderTarget = new WebGLRenderTarget( readBuffer.width, readBuffer.height, { type: HalfFloatType } ); this.sampleRenderTarget.texture.name = 'TAARenderPass.sample'; } if ( this.holdRenderTarget === undefined ) { - this.holdRenderTarget = new WebGLRenderTarget( readBuffer.width, readBuffer.height, this.params ); + this.holdRenderTarget = new WebGLRenderTarget( readBuffer.width, readBuffer.height, { type: HalfFloatType } ); this.holdRenderTarget.texture.name = 'TAARenderPass.hold'; } @@ -64,6 +65,9 @@ class TAARenderPass extends SSAARenderPass { const autoClear = renderer.autoClear; renderer.autoClear = false; + renderer.getClearColor( this._oldClearColor ); + const oldClearAlpha = renderer.getClearAlpha(); + const sampleWeight = 1.0 / ( jitterOffsets.length ); if ( this.accumulateIndex >= 0 && this.accumulateIndex < jitterOffsets.length ) { @@ -87,11 +91,18 @@ class TAARenderPass extends SSAARenderPass { } renderer.setRenderTarget( writeBuffer ); + renderer.setClearColor( this.clearColor, this.clearAlpha ); renderer.clear(); renderer.render( this.scene, this.camera ); renderer.setRenderTarget( this.sampleRenderTarget ); - if ( this.accumulateIndex === 0 ) renderer.clear(); + if ( this.accumulateIndex === 0 ) { + + renderer.setClearColor( 0x000000, 0.0 ); + renderer.clear(); + + } + this.fsQuad.render( renderer ); this.accumulateIndex ++; @@ -104,6 +115,7 @@ class TAARenderPass extends SSAARenderPass { } + renderer.setClearColor( this.clearColor, this.clearAlpha ); const accumulationWeight = this.accumulateIndex * sampleWeight; if ( accumulationWeight > 0 ) { @@ -121,12 +133,12 @@ class TAARenderPass extends SSAARenderPass { this.copyUniforms[ 'opacity' ].value = 1.0 - accumulationWeight; this.copyUniforms[ 'tDiffuse' ].value = this.holdRenderTarget.texture; renderer.setRenderTarget( writeBuffer ); - if ( accumulationWeight === 0 ) renderer.clear(); this.fsQuad.render( renderer ); } renderer.autoClear = autoClear; + renderer.setClearColor( this._oldClearColor, oldClearAlpha ); } diff --git a/examples/jsm/renderers/common/Renderer.js b/examples/jsm/renderers/common/Renderer.js new file mode 100644 index 00000000000000..848b7f091b930b --- /dev/null +++ b/examples/jsm/renderers/common/Renderer.js @@ -0,0 +1,828 @@ +import Animation from './Animation.js'; +import RenderObjects from './RenderObjects.js'; +import Attributes from './Attributes.js'; +import Geometries from './Geometries.js'; +import Info from './Info.js'; +import Pipelines from './Pipelines.js'; +import Bindings from './Bindings.js'; +import RenderLists from './RenderLists.js'; +import RenderContexts from './RenderContexts.js'; +import Textures from './Textures.js'; +import Background from './Background.js'; +import Nodes from './nodes/Nodes.js'; +import { Frustum, Matrix4, Vector2, Vector3, Vector4, Color, SRGBColorSpace, NoToneMapping } from 'three'; + +const _drawingBufferSize = new Vector2(); +const _screen = new Vector4(); +const _frustum = new Frustum(); +const _projScreenMatrix = new Matrix4(); +const _vector3 = new Vector3(); + +class Renderer { + + constructor( backend ) { + + this.isRenderer = true; + + // public + + this.domElement = backend.getDomElement(); + + this.backend = backend; + + this.autoClear = true; + this.autoClearColor = true; + this.autoClearDepth = true; + this.autoClearStencil = true; + + this.outputColorSpace = SRGBColorSpace; + + this.toneMapping = NoToneMapping; + this.toneMappingExposure = 1.0; + + this.sortObjects = true; + + this.depth = true; + this.stencil = true; + + // internals + + this._pixelRatio = 1; + this._width = this.domElement.width; + this._height = this.domElement.height; + + this._viewport = new Vector4( 0, 0, this._width, this._height ); + this._scissor = new Vector4( 0, 0, this._width, this._height ); + this._scissorTest = false; + + this._info = null; + this._properties = null; + this._attributes = null; + this._geometries = null; + this._nodes = null; + this._bindings = null; + this._objects = null; + this._pipelines = null; + this._renderLists = null; + this._renderContexts = null; + this._textures = null; + this._background = null; + + this._animation = new Animation(); + + this._currentRenderContext = null; + this._lastRenderContext = null; + + this._opaqueSort = null; + this._transparentSort = null; + + this._clearAlpha = 1; + this._clearColor = new Color( 0x000000 ); + this._clearDepth = 1; + this._clearStencil = 0; + + this._renderTarget = null; + this._currentActiveCubeFace = 0; + + this._initialized = false; + this._initPromise = null; + + // backwards compatibility + + this.shadowMap = { + enabled: false, + type: null + }; + + this.xr = { + enabled: false + }; + + } + + async init() { + + if ( this._initialized ) { + + throw new Error( 'Renderer: Backend has already been initialized.' ); + + } + + if ( this._initPromise !== null ) { + + return this._initPromise; + + } + + this._initPromise = new Promise( async ( resolve, reject ) => { + + const backend = this.backend; + + try { + + await backend.init( this ); + + } catch ( error ) { + + reject( error ); + return; + + } + + this._info = new Info(); + this._nodes = new Nodes( this, backend ); + this._attributes = new Attributes( backend ); + this._background = new Background( this, this._nodes ); + this._geometries = new Geometries( this._attributes, this._info ); + this._textures = new Textures( backend, this._info ); + this._pipelines = new Pipelines( backend, this._nodes ); + this._bindings = new Bindings( backend, this._nodes, this._textures, this._attributes, this._pipelines, this._info ); + this._objects = new RenderObjects( this, this._nodes, this._geometries, this._pipelines, this._info ); + this._renderLists = new RenderLists(); + this._renderContexts = new RenderContexts(); + + // + + this._animation.setNodes( this._nodes ); + this._animation.start(); + + this._initialized = true; + + resolve(); + + } ); + + return this._initPromise; + + } + + get coordinateSystem() { + + return this.backend.coordinateSystem; + + } + + async compile( /*scene, camera*/ ) { + + console.warn( 'THREE.Renderer: .compile() is not implemented yet.' ); + + } + + async render( scene, camera ) { + + if ( this._initialized === false ) await this.init(); + + // preserve render tree + + const nodeFrame = this._nodes.nodeFrame; + + const previousRenderId = nodeFrame.renderId; + const previousRenderState = this._currentRenderContext; + + // + + const renderContext = this._renderContexts.get( scene, camera ); + const renderTarget = this._renderTarget; + const activeCubeFace = this._activeCubeFace; + + this._currentRenderContext = renderContext; + + nodeFrame.renderId ++; + + // + + const coordinateSystem = this.coordinateSystem; + + if ( camera.coordinateSystem !== coordinateSystem ) { + + camera.coordinateSystem = coordinateSystem; + + camera.updateProjectionMatrix(); + + } + + // + + if ( this._animation.isAnimating === false ) nodeFrame.update(); + + if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld(); + + if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld(); + + if ( this._info.autoReset === true ) this._info.reset(); + + this._info.render.frame ++; + + // + + let viewport = this._viewport; + let scissor = this._scissor; + let pixelRatio = this._pixelRatio; + + if ( renderTarget !== null ) { + + viewport = renderTarget.viewport; + scissor = renderTarget.scissor; + pixelRatio = 1; + + } + + this.getDrawingBufferSize( _drawingBufferSize ); + + _screen.set( 0, 0, _drawingBufferSize.width, _drawingBufferSize.height ); + + const minDepth = ( viewport.minDepth === undefined ) ? 0 : viewport.minDepth; + const maxDepth = ( viewport.maxDepth === undefined ) ? 1 : viewport.maxDepth; + + renderContext.viewportValue.copy( viewport ).multiplyScalar( pixelRatio ).floor(); + renderContext.viewportValue.minDepth = minDepth; + renderContext.viewportValue.maxDepth = maxDepth; + renderContext.viewport = renderContext.viewportValue.equals( _screen ) === false; + + renderContext.scissorValue.copy( scissor ).multiplyScalar( pixelRatio ).floor(); + renderContext.scissor = this._scissorTest && renderContext.scissorValue.equals( _screen ) === false; + + renderContext.depth = this.depth; + renderContext.stencil = this.stencil; + + // + + _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); + _frustum.setFromProjectionMatrix( _projScreenMatrix, coordinateSystem ); + + const renderList = this._renderLists.get( scene, camera ); + renderList.init(); + + this._projectObject( scene, camera, 0, renderList ); + + renderList.finish(); + + if ( this.sortObjects === true ) { + + renderList.sort( this._opaqueSort, this._transparentSort ); + + } + + // + + if ( renderTarget !== null ) { + + this._textures.updateRenderTarget( renderTarget ); + + const renderTargetData = this._textures.get( renderTarget ); + + renderContext.texture = renderTargetData.texture; + renderContext.depthTexture = renderTargetData.depthTexture; + + } else { + + renderContext.texture = null; + renderContext.depthTexture = null; + + } + + renderContext.activeCubeFace = activeCubeFace; + + // + + this._nodes.updateScene( scene ); + + // + + this._background.update( scene, renderList, renderContext ); + + // + + this.backend.beginRender( renderContext ); + + // process render lists + + const opaqueObjects = renderList.opaque; + const transparentObjects = renderList.transparent; + const lightsNode = renderList.lightsNode; + + if ( opaqueObjects.length > 0 ) this._renderObjects( opaqueObjects, camera, scene, lightsNode ); + if ( transparentObjects.length > 0 ) this._renderObjects( transparentObjects, camera, scene, lightsNode ); + + // finish render pass + + this.backend.finishRender( renderContext ); + + // restore render tree + + nodeFrame.renderId = previousRenderId; + this._currentRenderContext = previousRenderState; + + this._lastRenderContext = renderContext; + + } + + setAnimationLoop( callback ) { + + if ( this._initialized === false ) this.init(); + + const animation = this._animation; + + animation.setAnimationLoop( callback ); + + ( callback === null ) ? animation.stop() : animation.start(); + + } + + async getArrayBuffer( attribute ) { + + return await this.backend.getArrayBuffer( attribute ); + + } + + getContext() { + + return this._context; + + } + + getPixelRatio() { + + return this._pixelRatio; + + } + + getDrawingBufferSize( target ) { + + return target.set( this._width * this._pixelRatio, this._height * this._pixelRatio ).floor(); + + } + + getSize( target ) { + + return target.set( this._width, this._height ); + + } + + setPixelRatio( value = 1 ) { + + this._pixelRatio = value; + + this.setSize( this._width, this._height, false ); + + } + + setDrawingBufferSize( width, height, pixelRatio ) { + + this._width = width; + this._height = height; + + this._pixelRatio = pixelRatio; + + this.domElement.width = Math.floor( width * pixelRatio ); + this.domElement.height = Math.floor( height * pixelRatio ); + + this.setViewport( 0, 0, width, height ); + + if ( this._initialized ) this.backend.updateSize(); + + } + + setSize( width, height, updateStyle = true ) { + + this._width = width; + this._height = height; + + this.domElement.width = Math.floor( width * this._pixelRatio ); + this.domElement.height = Math.floor( height * this._pixelRatio ); + + if ( updateStyle === true ) { + + this.domElement.style.width = width + 'px'; + this.domElement.style.height = height + 'px'; + + } + + this.setViewport( 0, 0, width, height ); + + if ( this._initialized ) this.backend.updateSize(); + + } + + setOpaqueSort( method ) { + + this._opaqueSort = method; + + } + + setTransparentSort( method ) { + + this._transparentSort = method; + + } + + getScissor( target ) { + + const scissor = this._scissor; + + target.x = scissor.x; + target.y = scissor.y; + target.width = scissor.width; + target.height = scissor.height; + + return target; + + } + + setScissor( x, y, width, height ) { + + const scissor = this._scissor; + + if ( x.isVector4 ) { + + scissor.copy( x ); + + } else { + + scissor.set( x, y, width, height ); + + } + + } + + getScissorTest() { + + return this._scissorTest; + + } + + setScissorTest( boolean ) { + + this._scissorTest = boolean; + + } + + getViewport( target ) { + + return target.copy( this._viewport ); + + } + + setViewport( x, y, width, height, minDepth = 0, maxDepth = 1 ) { + + const viewport = this._viewport; + + if ( x.isVector4 ) { + + viewport.copy( x ); + + } else { + + viewport.set( x, y, width, height ); + + } + + viewport.minDepth = minDepth; + viewport.maxDepth = maxDepth; + + } + + getClearColor( target ) { + + return target.copy( this._clearColor ); + + } + + setClearColor( color, alpha = 1 ) { + + this._clearColor.set( color ); + this._clearAlpha = alpha; + + } + + getClearAlpha() { + + return this._clearAlpha; + + } + + setClearAlpha( alpha ) { + + this._clearAlpha = alpha; + + } + + getClearDepth() { + + return this._clearDepth; + + } + + setClearDepth( depth ) { + + this._clearDepth = depth; + + } + + getClearStencil() { + + return this._clearStencil; + + } + + setClearStencil( stencil ) { + + this._clearStencil = stencil; + + } + + clear( color = true, depth = true, stencil = true ) { + + const renderContext = this._currentRenderContext || this._lastRenderContext; + + if ( renderContext ) this.backend.clear( renderContext, color, depth, stencil ); + + } + + clearColor() { + + this.clear( true, false, false ); + + } + + clearDepth() { + + this.clear( false, true, false ); + + } + + clearStencil() { + + this.clear( false, false, true ); + + } + + dispose() { + + this._objects.dispose(); + this._properties.dispose(); + this._pipelines.dispose(); + this._nodes.dispose(); + this._bindings.dispose(); + this._info.dispose(); + this._renderLists.dispose(); + this._renderContexts.dispose(); + this._textures.dispose(); + + this.setRenderTarget( null ); + this.setAnimationLoop( null ); + + } + + setRenderTarget( renderTarget, activeCubeFace = 0 ) { + + this._renderTarget = renderTarget; + this._activeCubeFace = activeCubeFace; + + } + + async compute( computeNodes ) { + + if ( this._initialized === false ) await this.init(); + + const backend = this.backend; + const pipelines = this._pipelines; + const computeGroup = Array.isArray( computeNodes ) ? computeNodes : [ computeNodes ]; + + backend.beginCompute( computeGroup ); + + for ( const computeNode of computeGroup ) { + + // onInit + + if ( pipelines.has( computeNode ) === false ) { + + computeNode.onInit( { renderer: this } ); + + } + + this._nodes.updateForCompute( computeNode ); + this._bindings.updateForCompute( computeNode ); + + const computePipeline = pipelines.getForCompute( computeNode ); + const computeBindings = this._bindings.getForCompute( computeNode ); + + backend.compute( computeGroup, computeNode, computeBindings, computePipeline ); + + } + + backend.finishCompute( computeGroup ); + + } + + getRenderTarget() { + + return this._renderTarget; + + } + + hasFeature( name ) { + + return this.backend.hasFeature( name ); + + } + + copyFramebufferToTexture( framebufferTexture ) { + + const renderContext = this._currentRenderContext || this._lastRenderContext; + + this._textures.updateTexture( framebufferTexture ); + + this.backend.copyFramebufferToTexture( framebufferTexture, renderContext ); + + } + + _projectObject( object, camera, groupOrder, renderList ) { + + if ( object.visible === false ) return; + + const visible = object.layers.test( camera.layers ); + + if ( visible ) { + + if ( object.isGroup ) { + + groupOrder = object.renderOrder; + + } else if ( object.isLOD ) { + + if ( object.autoUpdate === true ) object.update( camera ); + + } else if ( object.isLight ) { + + renderList.pushLight( object ); + + } else if ( object.isSprite ) { + + if ( ! object.frustumCulled || _frustum.intersectsSprite( object ) ) { + + if ( this.sortObjects === true ) { + + _vector3.setFromMatrixPosition( object.matrixWorld ).applyMatrix4( _projScreenMatrix ); + + } + + const geometry = object.geometry; + const material = object.material; + + if ( material.visible ) { + + renderList.push( object, geometry, material, groupOrder, _vector3.z, null ); + + } + + } + + } else if ( object.isLineLoop ) { + + console.error( 'THREE.Renderer: Objects of type THREE.LineLoop are not supported. Please use THREE.Line or THREE.LineSegments.' ); + + } else if ( object.isMesh || object.isLine || object.isPoints ) { + + if ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) { + + const geometry = object.geometry; + const material = object.material; + + if ( this.sortObjects === true ) { + + if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); + + _vector3 + .copy( geometry.boundingSphere.center ) + .applyMatrix4( object.matrixWorld ) + .applyMatrix4( _projScreenMatrix ); + + } + + if ( Array.isArray( material ) ) { + + const groups = geometry.groups; + + for ( let i = 0, l = groups.length; i < l; i ++ ) { + + const group = groups[ i ]; + const groupMaterial = material[ group.materialIndex ]; + + if ( groupMaterial && groupMaterial.visible ) { + + renderList.push( object, geometry, groupMaterial, groupOrder, _vector3.z, group ); + + } + + } + + } else if ( material.visible ) { + + renderList.push( object, geometry, material, groupOrder, _vector3.z, null ); + + } + + } + + } + + } + + const children = object.children; + + for ( let i = 0, l = children.length; i < l; i ++ ) { + + this._projectObject( children[ i ], camera, groupOrder, renderList ); + + } + + } + + _renderObjects( renderList, camera, scene, lightsNode ) { + + // process renderable objects + + for ( let i = 0, il = renderList.length; i < il; i ++ ) { + + const renderItem = renderList[ i ]; + + // @TODO: Add support for multiple materials per object. This will require to extract + // the material from the renderItem object and pass it with its group data to _renderObject(). + + const { object, geometry, material, group } = renderItem; + + if ( camera.isArrayCamera ) { + + const cameras = camera.cameras; + + for ( let j = 0, jl = cameras.length; j < jl; j ++ ) { + + const camera2 = cameras[ j ]; + + if ( object.layers.test( camera2.layers ) ) { + + const vp = camera2.viewport; + const minDepth = ( vp.minDepth === undefined ) ? 0 : vp.minDepth; + const maxDepth = ( vp.maxDepth === undefined ) ? 1 : vp.maxDepth; + + const viewportValue = this._currentRenderContext.viewportValue; + viewportValue.copy( vp ).multiplyScalar( this._pixelRatio ).floor(); + viewportValue.minDepth = minDepth; + viewportValue.maxDepth = maxDepth; + + this.backend.updateViewport( this._currentRenderContext ); + + this._renderObject( object, scene, camera2, geometry, material, group, lightsNode ); + + } + + } + + } else { + + this._renderObject( object, scene, camera, geometry, material, group, lightsNode ); + + } + + } + + } + + _renderObject( object, scene, camera, geometry, material, group, lightsNode ) { + + material = scene.overrideMaterial !== null ? scene.overrideMaterial : material; + + // + + object.onBeforeRender( this, scene, camera, geometry, material, group ); + + // + + const renderObject = this._objects.get( object, material, scene, camera, lightsNode ); + renderObject.context = this._currentRenderContext; + + // + + this._nodes.updateBefore( renderObject ); + + // + + object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); + object.normalMatrix.getNormalMatrix( object.modelViewMatrix ); + + // + + this._nodes.updateForRender( renderObject ); + this._geometries.update( renderObject ); + this._bindings.updateForRender( renderObject ); + + // + + this.backend.draw( renderObject, this._info ); + + } + +} + +export default Renderer; diff --git a/examples/jsm/renderers/webgpu/WebGPUBackend.js b/examples/jsm/renderers/webgpu/WebGPUBackend.js new file mode 100644 index 00000000000000..4b41c2372297c6 --- /dev/null +++ b/examples/jsm/renderers/webgpu/WebGPUBackend.js @@ -0,0 +1,841 @@ +/*// debugger tools +import 'https://greggman.github.io/webgpu-avoid-redundant-state-setting/webgpu-check-redundant-state-setting.js'; +//*/ + +import { GPUFeatureName, GPUTextureFormat, GPULoadOp, GPUStoreOp, GPUIndexFormat, GPUTextureViewDimension } from './utils/WebGPUConstants.js'; + +import WebGPUNodeBuilder from './nodes/WGSLNodeBuilder.js'; +import Backend from '../common/Backend.js'; + +import { DepthTexture, DepthFormat, DepthStencilFormat, UnsignedInt248Type, UnsignedIntType, WebGPUCoordinateSystem } from 'three'; + +import WebGPUUtils from './utils/WebGPUUtils.js'; +import WebGPUAttributeUtils from './utils/WebGPUAttributeUtils.js'; +import WebGPUBindingUtils from './utils/WebGPUBindingUtils.js'; +import WebGPUPipelineUtils from './utils/WebGPUPipelineUtils.js'; +import WebGPUTextureUtils from './utils/WebGPUTextureUtils.js'; + +// statics + +let _staticAdapter = null; + +if ( navigator.gpu !== undefined ) { + + _staticAdapter = await navigator.gpu.requestAdapter(); + +} + +// + +class WebGPUBackend extends Backend { + + constructor( parameters = {} ) { + + super( parameters ); + + // some parameters require default values other than "undefined" + + this.parameters.antialias = ( parameters.antialias === true ); + + if ( this.parameters.antialias === true ) { + + this.parameters.sampleCount = ( parameters.sampleCount === undefined ) ? 4 : parameters.sampleCount; + + } else { + + this.parameters.sampleCount = 1; + + } + + this.parameters.requiredLimits = ( parameters.requiredLimits === undefined ) ? {} : parameters.requiredLimits; + + this.adapter = null; + this.device = null; + this.context = null; + this.colorBuffer = null; + + this.depthBuffers = new WeakMap(); + + this.utils = new WebGPUUtils( this ); + this.attributeUtils = new WebGPUAttributeUtils( this ); + this.bindingUtils = new WebGPUBindingUtils( this ); + this.pipelineUtils = new WebGPUPipelineUtils( this ); + this.textureUtils = new WebGPUTextureUtils( this ); + + } + + async init( renderer ) { + + await super.init( renderer ); + + // + + const parameters = this.parameters; + + const adapterOptions = { + powerPreference: parameters.powerPreference + }; + + const adapter = await navigator.gpu.requestAdapter( adapterOptions ); + + if ( adapter === null ) { + + throw new Error( 'WebGPUBackend: Unable to create WebGPU adapter.' ); + + } + + // feature support + + const features = Object.values( GPUFeatureName ); + + const supportedFeatures = []; + + for ( const name of features ) { + + if ( adapter.features.has( name ) ) { + + supportedFeatures.push( name ); + + } + + } + + const deviceDescriptor = { + requiredFeatures: supportedFeatures, + requiredLimits: parameters.requiredLimits + }; + + const device = await adapter.requestDevice( deviceDescriptor ); + + const context = ( parameters.context !== undefined ) ? parameters.context : renderer.domElement.getContext( 'webgpu' ); + + this.adapter = adapter; + this.device = device; + this.context = context; + + this.updateSize(); + + } + + get coordinateSystem() { + + return WebGPUCoordinateSystem; + + } + + async getArrayBuffer( attribute ) { + + return await this.attributeUtils.getArrayBuffer( attribute ); + + } + + beginRender( renderContext ) { + + const renderContextData = this.get( renderContext ); + + const device = this.device; + + const descriptor = { + colorAttachments: [ { + view: null + } ], + depthStencilAttachment: { + view: null + } + }; + + const colorAttachment = descriptor.colorAttachments[ 0 ]; + const depthStencilAttachment = descriptor.depthStencilAttachment; + + const antialias = this.parameters.antialias; + + if ( renderContext.texture !== null ) { + + const textureData = this.get( renderContext.texture ); + const depthTextureData = this.get( renderContext.depthTexture ); + + // @TODO: Support RenderTarget with antialiasing. + + colorAttachment.view = textureData.texture.createView( { + baseMipLevel: 0, + mipLevelCount: 1, + baseArrayLayer: renderContext.activeCubeFace, + dimension: GPUTextureViewDimension.TwoD + } ); + + depthStencilAttachment.view = depthTextureData.texture.createView(); + + if ( renderContext.stencil && renderContext.depthTexture.format === DepthFormat ) { + + renderContext.stencil = false; + + } + + } else { + + if ( antialias === true ) { + + colorAttachment.view = this.colorBuffer.createView(); + colorAttachment.resolveTarget = this.context.getCurrentTexture().createView(); + + } else { + + colorAttachment.view = this.context.getCurrentTexture().createView(); + colorAttachment.resolveTarget = undefined; + + } + + depthStencilAttachment.view = this._getDepthBufferGPU( renderContext ).createView(); + + } + + if ( renderContext.clearColor ) { + + colorAttachment.clearValue = renderContext.clearColorValue; + colorAttachment.loadOp = GPULoadOp.Clear; + colorAttachment.storeOp = GPUStoreOp.Store; + + } else { + + colorAttachment.loadOp = GPULoadOp.Load; + colorAttachment.storeOp = GPUStoreOp.Store; + + } + + // + + if ( renderContext.depth ) { + + if ( renderContext.clearDepth ) { + + depthStencilAttachment.depthClearValue = renderContext.clearDepthValue; + depthStencilAttachment.depthLoadOp = GPULoadOp.Clear; + depthStencilAttachment.depthStoreOp = GPUStoreOp.Store; + + } else { + + depthStencilAttachment.depthLoadOp = GPULoadOp.Load; + depthStencilAttachment.depthStoreOp = GPUStoreOp.Store; + + } + + } + + if ( renderContext.stencil ) { + + if ( renderContext.clearStencil ) { + + depthStencilAttachment.stencilClearValue = renderContext.clearStencilValue; + depthStencilAttachment.stencilLoadOp = GPULoadOp.Clear; + depthStencilAttachment.stencilStoreOp = GPUStoreOp.Store; + + } else { + + depthStencilAttachment.stencilLoadOp = GPULoadOp.Load; + depthStencilAttachment.stencilStoreOp = GPUStoreOp.Store; + + } + + } + + // + + const encoder = device.createCommandEncoder( { label: 'renderContext_' + renderContext.id } ); + const currentPass = encoder.beginRenderPass( descriptor ); + + // + + renderContextData.descriptor = descriptor; + renderContextData.encoder = encoder; + renderContextData.currentPass = currentPass; + renderContextData.currentAttributesSet = {}; + + // + + if ( renderContext.viewport ) { + + this.updateViewport( renderContext ); + + } + + if ( renderContext.scissor ) { + + const { x, y, width, height } = renderContext.scissorValue; + + currentPass.setScissorRect( x, y, width, height ); + + } + + } + + finishRender( renderContext ) { + + const renderContextData = this.get( renderContext ); + + renderContextData.currentPass.end(); + + this.device.queue.submit( [ renderContextData.encoder.finish() ] ); + + // + + if ( renderContext.texture !== null && renderContext.texture.generateMipmaps === true ) { + + this.textureUtils.generateMipmaps( renderContext.texture ); + + } + + } + + updateViewport( renderContext ) { + + const { currentPass } = this.get( renderContext ); + const { x, y, width, height, minDepth, maxDepth } = renderContext.viewportValue; + + currentPass.setViewport( x, y, width, height, minDepth, maxDepth ); + + } + + clear( renderContext, color, depth, stencil ) { + + const device = this.device; + const renderContextData = this.get( renderContext ); + + const { descriptor } = renderContextData; + + depth = depth && renderContext.depth; + stencil = stencil && renderContext.stencil; + + const colorAttachment = descriptor.colorAttachments[ 0 ]; + + const antialias = this.parameters.antialias; + + // @TODO: Include render target in clear operation. + if ( antialias === true ) { + + colorAttachment.view = this.colorBuffer.createView(); + colorAttachment.resolveTarget = this.context.getCurrentTexture().createView(); + + } else { + + colorAttachment.view = this.context.getCurrentTexture().createView(); + colorAttachment.resolveTarget = undefined; + + } + + descriptor.depthStencilAttachment.view = this._getDepthBufferGPU( renderContext ).createView(); + + if ( color ) { + + colorAttachment.loadOp = GPULoadOp.Clear; + colorAttachment.clearValue = renderContext.clearColorValue; + + } + + if ( depth ) { + + descriptor.depthStencilAttachment.depthLoadOp = GPULoadOp.Clear; + descriptor.depthStencilAttachment.depthClearValue = renderContext.clearDepthValue; + + } + + if ( stencil ) { + + descriptor.depthStencilAttachment.stencilLoadOp = GPULoadOp.Clear; + descriptor.depthStencilAttachment.stencilClearValue = renderContext.clearStencilValue; + + } + + renderContextData.encoder = device.createCommandEncoder( {} ); + renderContextData.currentPass = renderContextData.encoder.beginRenderPass( descriptor ); + + renderContextData.currentPass.end(); + + device.queue.submit( [ renderContextData.encoder.finish() ] ); + + } + + // compute + + beginCompute( computeGroup ) { + + const groupGPU = this.get( computeGroup ); + + groupGPU.cmdEncoderGPU = this.device.createCommandEncoder( {} ); + groupGPU.passEncoderGPU = groupGPU.cmdEncoderGPU.beginComputePass(); + + } + + compute( computeGroup, computeNode, bindings, pipeline ) { + + const { passEncoderGPU } = this.get( computeGroup ); + + // pipeline + + const pipelineGPU = this.get( pipeline ).pipeline; + passEncoderGPU.setPipeline( pipelineGPU ); + + // bind group + + const bindGroupGPU = this.get( bindings ).group; + passEncoderGPU.setBindGroup( 0, bindGroupGPU ); + + passEncoderGPU.dispatchWorkgroups( computeNode.dispatchCount ); + + } + + finishCompute( computeGroup ) { + + const groupData = this.get( computeGroup ); + + groupData.passEncoderGPU.end(); + this.device.queue.submit( [ groupData.cmdEncoderGPU.finish() ] ); + + } + + // render object + + draw( renderObject, info ) { + + const { object, geometry, context, pipeline } = renderObject; + + const bindingsData = this.get( renderObject.getBindings() ); + const contextData = this.get( context ); + const pipelineGPU = this.get( pipeline ).pipeline; + const attributesSet = contextData.currentAttributesSet; + + // pipeline + + const passEncoderGPU = contextData.currentPass; + passEncoderGPU.setPipeline( pipelineGPU ); + + // bind group + + const bindGroupGPU = bindingsData.group; + passEncoderGPU.setBindGroup( 0, bindGroupGPU ); + + // attributes + + const index = renderObject.getIndex(); + + const hasIndex = ( index !== null ); + + // index + + if ( hasIndex === true ) { + + if ( attributesSet.index !== index ) { + + const buffer = this.get( index ).buffer; + const indexFormat = ( index.array instanceof Uint16Array ) ? GPUIndexFormat.Uint16 : GPUIndexFormat.Uint32; + + passEncoderGPU.setIndexBuffer( buffer, indexFormat ); + + attributesSet.index = index; + + } + + } + + // vertex buffers + + const attributes = renderObject.getAttributes(); + + for ( let i = 0, l = attributes.length; i < l; i ++ ) { + + const attribute = attributes[ i ]; + + if ( attributesSet[ i ] !== attribute ) { + + const buffer = this.get( attribute ).buffer; + passEncoderGPU.setVertexBuffer( i, buffer ); + + attributesSet[ i ] = attribute; + + } + + } + + // draw + + const drawRange = geometry.drawRange; + const firstVertex = drawRange.start; + + const instanceCount = this.getInstanceCount( renderObject ); + + if ( hasIndex === true ) { + + const indexCount = ( drawRange.count !== Infinity ) ? drawRange.count : index.count; + + passEncoderGPU.drawIndexed( indexCount, instanceCount, firstVertex, 0, 0 ); + + info.update( object, indexCount, instanceCount ); + + } else { + + const positionAttribute = geometry.attributes.position; + const vertexCount = ( drawRange.count !== Infinity ) ? drawRange.count : positionAttribute.count; + + passEncoderGPU.draw( vertexCount, instanceCount, firstVertex, 0 ); + + info.update( object, vertexCount, instanceCount ); + + } + + } + + // cache key + + needsUpdate( renderObject ) { + + const renderObjectGPU = this.get( renderObject ); + + const { object, material } = renderObject; + + const utils = this.utils; + + const sampleCount = utils.getSampleCount( renderObject.context ); + const colorSpace = utils.getCurrentColorSpace( renderObject.context ); + const colorFormat = utils.getCurrentColorFormat( renderObject.context ); + const depthStencilFormat = utils.getCurrentDepthStencilFormat( renderObject.context ); + const primitiveTopology = utils.getPrimitiveTopology( object, material ); + + let needsUpdate = false; + + if ( renderObjectGPU.sampleCount !== sampleCount || renderObjectGPU.colorSpace !== colorSpace || + renderObjectGPU.colorFormat !== colorFormat || renderObjectGPU.depthStencilFormat !== depthStencilFormat || + renderObjectGPU.primitiveTopology !== primitiveTopology ) { + + renderObjectGPU.sampleCount = sampleCount; + renderObjectGPU.colorSpace = colorSpace; + renderObjectGPU.colorFormat = colorFormat; + renderObjectGPU.depthStencilFormat = depthStencilFormat; + renderObjectGPU.primitiveTopology = primitiveTopology; + + needsUpdate = true; + + } + + return needsUpdate; + + } + + getCacheKey( renderObject ) { + + const { object, material } = renderObject; + + const utils = this.utils; + const renderContext = renderObject.context; + + return [ + utils.getSampleCount( renderContext ), + utils.getCurrentColorSpace( renderContext ), utils.getCurrentColorFormat( renderContext ), utils.getCurrentDepthStencilFormat( renderContext ), + utils.getPrimitiveTopology( object, material ) + ].join(); + + } + + // textures + + createSampler( texture ) { + + this.textureUtils.createSampler( texture ); + + } + + destroySampler( texture ) { + + this.textureUtils.destroySampler( texture ); + + } + + createDefaultTexture( texture ) { + + this.textureUtils.createDefaultTexture( texture ); + + } + + createTexture( texture ) { + + this.textureUtils.createTexture( texture ); + + } + + updateTexture( texture ) { + + this.textureUtils.updateTexture( texture ); + + } + + destroyTexture( texture ) { + + this.textureUtils.destroyTexture( texture ); + + } + + // node builder + + createNodeBuilder( object, renderer ) { + + return new WebGPUNodeBuilder( object, renderer ); + + } + + // program + + createProgram( program ) { + + const programGPU = this.get( program ); + + programGPU.module = { + module: this.device.createShaderModule( { code: program.code, label: program.stage } ), + entryPoint: 'main' + }; + + } + + destroyProgram( program ) { + + this.delete( program ); + + } + + // pipelines + + createRenderPipeline( renderObject ) { + + this.pipelineUtils.createRenderPipeline( renderObject ); + + } + + createComputePipeline( computePipeline ) { + + this.pipelineUtils.createComputePipeline( computePipeline ); + + } + + // bindings + + createBindings( bindings, pipeline ) { + + this.bindingUtils.createBindings( bindings, pipeline ); + + } + + updateBindings( bindings, pipeline ) { + + this.bindingUtils.createBindings( bindings, pipeline ); + + } + + updateBinding( binding ) { + + this.bindingUtils.updateBinding( binding ); + + } + + // attributes + + createIndexAttribute( attribute ) { + + this.attributeUtils.createAttribute( attribute, GPUBufferUsage.INDEX | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST ); + + } + + createAttribute( attribute ) { + + this.attributeUtils.createAttribute( attribute, GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST ); + + } + + createStorageAttribute( attribute ) { + + this.attributeUtils.createAttribute( attribute, GPUBufferUsage.STORAGE | GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST ); + + } + + updateAttribute( attribute ) { + + this.attributeUtils.updateAttribute( attribute ); + + } + + destroyAttribute( attribute ) { + + this.attributeUtils.destroyAttribute( attribute ); + + } + + // canvas + + updateSize() { + + this._configureContext(); + this._setupColorBuffer(); + + } + + // utils public + + hasFeature( name ) { + + const adapter = this.adapter || _staticAdapter; + + // + + const features = Object.values( GPUFeatureName ); + + if ( features.includes( name ) === false ) { + + throw new Error( 'THREE.WebGPURenderer: Unknown WebGPU GPU feature: ' + name ); + + } + + // + + return adapter.features.has( name ); + + } + + copyFramebufferToTexture( texture, renderContext ) { + + const renderContextData = this.get( renderContext ); + + const { encoder, descriptor } = renderContextData; + + let sourceGPU = null; + + if ( texture.isFramebufferTexture ) { + + sourceGPU = this.context.getCurrentTexture(); + + } else if ( texture.isDepthTexture ) { + + sourceGPU = this._getDepthBufferGPU( renderContext ); + + } + + const destinationGPU = this.get( texture ).texture; + + renderContextData.currentPass.end(); + + encoder.copyTextureToTexture( + { + texture: sourceGPU, + origin: { x: 0, y: 0, z: 0 } + }, + { + texture: destinationGPU + }, + [ + texture.image.width, + texture.image.height + ] + ); + + if ( texture.generateMipmaps ) this.textureUtils.generateMipmaps( texture ); + + descriptor.colorAttachments[ 0 ].loadOp = GPULoadOp.Load; + if ( renderContext.depth ) descriptor.depthStencilAttachment.depthLoadOp = GPULoadOp.Load; + if ( renderContext.stencil ) descriptor.depthStencilAttachment.stencilLoadOp = GPULoadOp.Load; + + renderContextData.currentPass = encoder.beginRenderPass( descriptor ); + renderContextData.currentAttributesSet = {}; + + } + + // utils + + _getDepthBufferGPU( renderContext ) { + + const { depthBuffers } = this; + const { width, height } = this.getDrawingBufferSize(); + + let depthTexture = depthBuffers.get( renderContext ); + + if ( depthTexture !== undefined && depthTexture.image.width === width && depthTexture.image.height === height ) { + + return this.get( depthTexture ).texture; + + } + + this._destroyDepthBufferGPU( renderContext ); + + depthTexture = new DepthTexture(); + depthTexture.name = 'depthBuffer'; + + if ( renderContext.stencil ) { + + depthTexture = new DepthTexture(); + depthTexture.format = DepthStencilFormat; + depthTexture.type = UnsignedInt248Type; + + } else if ( renderContext.depth ) { + + depthTexture = new DepthTexture(); + depthTexture.format = DepthFormat; + depthTexture.type = UnsignedIntType; + + } + + depthTexture.image.width = width; + depthTexture.image.height = height; + + this.textureUtils.createTexture( depthTexture, { sampleCount: this.parameters.sampleCount } ); + + depthBuffers.set( renderContext, depthTexture ); + + return this.get( depthTexture ).texture; + + } + + _destroyDepthBufferGPU( renderContext ) { + + const { depthBuffers } = this; + + const depthTexture = depthBuffers.get( renderContext ); + + if ( depthTexture !== undefined ) { + + this.textureUtils.destroyTexture( depthTexture ); + + depthBuffers.delete( renderContext ); + + } + + } + + _configureContext() { + + this.context.configure( { + device: this.device, + format: GPUTextureFormat.BGRA8Unorm, + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, + alphaMode: 'premultiplied' + } ); + + } + + _setupColorBuffer() { + + if ( this.colorBuffer ) this.colorBuffer.destroy(); + + const { width, height } = this.getDrawingBufferSize(); + //const format = navigator.gpu.getPreferredCanvasFormat(); // @TODO: Move to WebGPUUtils + + this.colorBuffer = this.device.createTexture( { + label: 'colorBuffer', + size: { + width: width, + height: height, + depthOrArrayLayers: 1 + }, + sampleCount: this.parameters.sampleCount, + format: GPUTextureFormat.BGRA8Unorm, + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC + } ); + + } + +} + +export default WebGPUBackend; diff --git a/examples/jsm/renderers/webgpu/WebGPUTextures.js b/examples/jsm/renderers/webgpu/WebGPUTextures.js index ba6e7196b1f408..b85aeffd353b16 100644 --- a/examples/jsm/renderers/webgpu/WebGPUTextures.js +++ b/examples/jsm/renderers/webgpu/WebGPUTextures.js @@ -1,476 +1,405 @@ -import { GPUTextureFormat, GPUAddressMode, GPUFilterMode, GPUTextureDimension, GPUFeatureName } from './constants.js'; -import { VideoTexture, CubeTexture, Texture, NearestFilter, NearestMipmapNearestFilter, NearestMipmapLinearFilter, LinearFilter, RepeatWrapping, MirroredRepeatWrapping, RGB_ETC2_Format, RGBA_ETC2_EAC_Format, - RGBAFormat, RedFormat, RGFormat, RGBA_S3TC_DXT1_Format, RGBA_S3TC_DXT3_Format, RGBA_S3TC_DXT5_Format, UnsignedByteType, FloatType, HalfFloatType, SRGBColorSpace, DepthFormat, DepthStencilFormat, DepthTexture, +import { + GPUTextureFormat, GPUAddressMode, GPUFilterMode, GPUTextureDimension, GPUFeatureName +} from './WebGPUConstants.js'; + +import { + CubeTexture, Texture, + NearestFilter, NearestMipmapNearestFilter, NearestMipmapLinearFilter, LinearFilter, + RepeatWrapping, MirroredRepeatWrapping, + RGB_ETC2_Format, RGBA_ETC2_EAC_Format, + RGBAFormat, RedFormat, RGFormat, RGBA_S3TC_DXT1_Format, RGBA_S3TC_DXT3_Format, RGBA_S3TC_DXT5_Format, UnsignedByteType, FloatType, HalfFloatType, SRGBColorSpace, DepthFormat, DepthStencilFormat, RGBA_ASTC_4x4_Format, RGBA_ASTC_5x4_Format, RGBA_ASTC_5x5_Format, RGBA_ASTC_6x5_Format, RGBA_ASTC_6x6_Format, RGBA_ASTC_8x5_Format, RGBA_ASTC_8x6_Format, RGBA_ASTC_8x8_Format, RGBA_ASTC_10x5_Format, - RGBA_ASTC_10x6_Format, RGBA_ASTC_10x8_Format, RGBA_ASTC_10x10_Format, RGBA_ASTC_12x10_Format, RGBA_ASTC_12x12_Format, UnsignedIntType, UnsignedShortType, UnsignedInt248Type + RGBA_ASTC_10x6_Format, RGBA_ASTC_10x8_Format, RGBA_ASTC_10x10_Format, RGBA_ASTC_12x10_Format, RGBA_ASTC_12x12_Format, UnsignedIntType, UnsignedShortType, UnsignedInt248Type, + NeverCompare, AlwaysCompare, LessCompare, LessEqualCompare, EqualCompare, GreaterEqualCompare, GreaterCompare, NotEqualCompare } from 'three'; -import WebGPUTextureUtils from './WebGPUTextureUtils.js'; -class WebGPUTextures { +import { CubeReflectionMapping, CubeRefractionMapping, EquirectangularReflectionMapping, EquirectangularRefractionMapping } from 'three'; - constructor( device, properties, info ) { +import WebGPUTextureMipmapUtils from './WebGPUTextureMipmapUtils.js'; - this.device = device; - this.properties = properties; - this.info = info; +const _compareToWebGPU = { + [ NeverCompare ]: 'never', + [ AlwaysCompare ]: 'less', + [ LessCompare ]: 'equal', + [ LessEqualCompare ]: 'less-equal', + [ EqualCompare ]: 'greater', + [ GreaterEqualCompare ]: 'not-equal', + [ GreaterCompare ]: 'greater-equal', + [ NotEqualCompare ]: 'always' +}; - this.defaultTexture = null; - this.depthDefaultTexture = null; - this.defaultVideoTexture = null; - this.defaultCubeTexture = null; - this.defaultSampler = null; - - this.samplerCache = new Map(); - this.utils = null; - - } - - getDefaultSampler() { +class WebGPUTextureUtils { - if ( this.defaultSampler === null ) { + constructor( backend ) { - this.defaultSampler = this.device.createSampler( {} ); + this.backend = backend; - } + this.mipmapUtils = null; - return this.defaultSampler; + this.defaultTexture = null; + this.defaultCubeTexture = null; } - getDefaultDepthTexture() { - - if ( this.depthDefaultTexture === null ) { + createSampler( texture ) { - const depthTexture = new DepthTexture(); - depthTexture.image.width = 1; - depthTexture.image.height = 1; + const backend = this.backend; + const device = backend.device; - this._uploadTexture( depthTexture ); + const textureGPU = backend.get( texture ); - this.depthDefaultTexture = this.getTextureGPU( depthTexture ); - - } - - return this.depthDefaultTexture; - - } - - getDefaultTexture() { - - if ( this.defaultTexture === null ) { - - const texture = new Texture(); - texture.minFilter = NearestFilter; - texture.magFilter = NearestFilter; + const samplerDescriptorGPU = { + addressModeU: this._convertAddressMode( texture.wrapS ), + addressModeV: this._convertAddressMode( texture.wrapT ), + addressModeW: this._convertAddressMode( texture.wrapR ), + magFilter: this._convertFilterMode( texture.magFilter ), + minFilter: this._convertFilterMode( texture.minFilter ), + mipmapFilter: this._convertFilterMode( texture.minFilter ), + maxAnisotropy: texture.anisotropy + }; - this._uploadTexture( texture ); + if ( texture.isDepthTexture && texture.compareFunction !== null ) { - this.defaultTexture = this.getTextureGPU( texture ); + samplerDescriptorGPU.compare = _compareToWebGPU[ texture.compareFunction ]; } - return this.defaultTexture; + textureGPU.sampler = device.createSampler( samplerDescriptorGPU ); } - getDefaultVideoTexture() { + createDefaultTexture( texture ) { - if ( this.defaultVideoTexture === null ) { + let textureGPU; - const video = document.getElementById( 'video' ); + if ( texture.isCubeTexture ) { - const texture = new VideoTexture( video ); - texture.minFilter = NearestFilter; - texture.magFilter = NearestFilter; + textureGPU = this._getDefaultCubeTextureGPU(); - this._uploadVideoTexture( texture ); + } else { - this.defaultVideoTexture = this.getTextureGPU( texture ); + textureGPU = this._getDefaultTextureGPU(); } - return this.defaultVideoTexture; + this.backend.get( texture ).texture = textureGPU; } - getDefaultCubeTexture() { + createTexture( texture, options = {} ) { - if ( this.defaultCubeTexture === null ) { - - const texture = new CubeTexture(); - texture.minFilter = NearestFilter; - texture.magFilter = NearestFilter; + const backend = this.backend; + const textureData = backend.get( texture ); - this._uploadTexture( texture ); + if ( textureData.initialized ) { - this.defaultCubeTexture = this.getTextureGPU( texture ); + throw new Error( 'WebGPUTextureUtils: Texture already initialized.' ); } - return this.defaultCubeTexture; + const { width, height, depth } = this._getSize( texture ); - } + const needsMipmaps = this._needsMipmaps( texture ); + const dimension = this._getDimension( texture ); + const mipLevelCount = this._getMipLevelCount( texture, width, height, needsMipmaps ); + const format = texture.internalFormat || this._getFormat( texture ); + //const sampleCount = texture.isRenderTargetTexture || texture.isDepthTexture ? backend.utils.getSampleCount( renderContext ) : 1; + const sampleCount = options.sampleCount !== undefined ? options.sampleCount : 1; - getTextureGPU( texture ) { + let usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC; - const textureProperties = this.properties.get( texture ); + if ( texture.isCompressedTexture !== true ) { - return textureProperties.textureGPU; + usage |= GPUTextureUsage.RENDER_ATTACHMENT; - } + } - getSampler( texture ) { + const textureDescriptorGPU = { + label: texture.name, + size: { + width: width, + height: height, + depthOrArrayLayers: depth, + }, + mipLevelCount: mipLevelCount, + sampleCount: sampleCount, + dimension: dimension, + format: format, + usage: usage + }; - const textureProperties = this.properties.get( texture ); + // texture creation - return textureProperties.samplerGPU; + if ( texture.isVideoTexture ) { - } + const video = texture.source.data; + const videoFrame = new VideoFrame( video ); - updateTexture( texture ) { + textureDescriptorGPU.size.width = videoFrame.displayWidth; + textureDescriptorGPU.size.height = videoFrame.displayHeight; - let needsUpdate = false; + videoFrame.close(); - const textureProperties = this.properties.get( texture ); + textureData.externalTexture = video; - if ( texture.version > 0 && textureProperties.version !== texture.version ) { + } else { - const image = texture.image; + textureData.texture = backend.device.createTexture( textureDescriptorGPU ); - if ( image === undefined ) { + } - console.warn( 'THREE.WebGPURenderer: Texture marked for update but image is undefined.' ); + textureData.initialized = true; - } else if ( image.complete === false ) { + textureData.needsMipmaps = needsMipmaps; + textureData.textureDescriptorGPU = textureDescriptorGPU; - console.warn( 'THREE.WebGPURenderer: Texture marked for update but image is incomplete.' ); + } - } else { + destroyTexture( texture ) { - // texture init + const backend = this.backend; + const textureData = backend.get( texture ); - if ( textureProperties.initialized === undefined ) { + textureData.texture.destroy(); - textureProperties.initialized = true; + backend.delete( texture ); - const disposeCallback = onTextureDispose.bind( this ); - textureProperties.disposeCallback = disposeCallback; + } - texture.addEventListener( 'dispose', disposeCallback ); + destroySampler( texture ) { - this.info.memory.textures ++; + const backend = this.backend; + const textureData = backend.get( texture ); - } + delete textureData.sampler; - // + } - if ( texture.isVideoTexture ) { + generateMipmaps( texture ) { - needsUpdate = this._uploadVideoTexture( texture ); + const textureData = this.backend.get( texture ); - } else { + if ( texture.isCubeTexture ) { - needsUpdate = this._uploadTexture( texture ); + for ( let i = 0; i < 6; i ++ ) { - } + this._generateMipmaps( textureData.texture, textureData.textureDescriptorGPU, i ); } - } - - // if the texture is used for RTT, it's necessary to init it once so the binding - // group's resource definition points to the respective GPUTexture - - if ( textureProperties.initializedRTT === false ) { + } else { - textureProperties.initializedRTT = true; - needsUpdate = true; + this._generateMipmaps( textureData.texture, textureData.textureDescriptorGPU ); } - return needsUpdate; - } - updateSampler( texture ) { + updateTexture( texture ) { - const array = []; + const textureData = this.backend.get( texture ); - array.push( texture.wrapS ); - array.push( texture.wrapT ); - array.push( texture.wrapR ); - array.push( texture.magFilter ); - array.push( texture.minFilter ); - array.push( texture.anisotropy ); + const { needsMipmaps, textureDescriptorGPU } = textureData; - const key = array.join(); - let samplerGPU = this.samplerCache.get( key ); + // transfer texture data - if ( samplerGPU === undefined ) { + if ( texture.isDataTexture || texture.isDataArrayTexture || texture.isData3DTexture ) { - samplerGPU = this.device.createSampler( { - addressModeU: this._convertAddressMode( texture.wrapS ), - addressModeV: this._convertAddressMode( texture.wrapT ), - addressModeW: this._convertAddressMode( texture.wrapR ), - magFilter: this._convertFilterMode( texture.magFilter ), - minFilter: this._convertFilterMode( texture.minFilter ), - mipmapFilter: this._convertFilterMode( texture.minFilter ), - maxAnisotropy: texture.anisotropy - } ); + this._copyBufferToTexture( texture.image, textureData.texture, textureDescriptorGPU, needsMipmaps ); - this.samplerCache.set( key, samplerGPU ); + } else if ( texture.isCompressedTexture ) { - } + this._copyCompressedBufferToTexture( texture.mipmaps, textureData.texture, textureDescriptorGPU ); - const textureProperties = this.properties.get( texture ); - textureProperties.samplerGPU = samplerGPU; + } else if ( texture.isCubeTexture ) { - } + if ( texture.image.length === 6 ) { - initRenderTarget( renderTarget ) { + this._copyCubeMapToTexture( texture.image, texture, textureData.texture, textureDescriptorGPU, needsMipmaps ); - const properties = this.properties; - const renderTargetProperties = properties.get( renderTarget ); + } - if ( renderTargetProperties.initialized === undefined ) { + } else if ( texture.isRenderTargetTexture ) { - const device = this.device; + if ( needsMipmaps === true ) this._generateMipmaps( textureData.texture, textureDescriptorGPU ); - const width = renderTarget.width; - const height = renderTarget.height; + } else if ( texture.isVideoTexture ) { - const texture = renderTarget.texture; + const video = texture.source.data; - const colorTextureFormat = texture.internalFormat || this._getFormat( texture ); - const label = texture.name ? '_' + texture.name : ''; - const needsMipmaps = this._needsMipmaps( texture ); - const mipLevelCount = this._getMipLevelCount( texture, width, height, needsMipmaps ); + textureData.externalTexture = video; - const colorTextureGPU = device.createTexture( { - label: 'renderTarget' + label, - size: { - width: width, - height: height, - depthOrArrayLayers: 1 - }, - mipLevelCount: mipLevelCount, - format: colorTextureFormat, - usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST - } ); + } else if ( texture.image !== null ) { - this.info.memory.textures ++; + this._copyImageToTexture( texture.image, texture, textureData.texture, textureDescriptorGPU, needsMipmaps ); - renderTargetProperties.colorTextureGPU = colorTextureGPU; - renderTargetProperties.colorTextureFormat = colorTextureFormat; + } else { - // When the ".texture" or ".depthTexture" property of a render target is used as a map, - // the renderer has to find the respective GPUTexture objects to setup the bind groups. - // Since it's not possible to see just from a texture object whether it belongs to a render - // target or not, we need the initializedRTT flag. + console.warn( 'WebGPUTextureUtils: Unable to update texture.' ); - const textureProperties = properties.get( texture ); - textureProperties.textureGPU = colorTextureGPU; - textureProperties.initializedRTT = false; + } - if ( renderTarget.depthBuffer === true ) { + // - const depthTextureFormat = renderTarget.depthTexture !== null ? this._getFormat( renderTarget.depthTexture ) : GPUTextureFormat.Depth24PlusStencil8; + textureData.version = texture.version; - const depthTextureGPU = device.createTexture( { - label: 'renderTarget' + label + '_depthBuffer', - size: { - width: width, - height: height, - depthOrArrayLayers: 1 - }, - format: depthTextureFormat, - usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST - } ); + } - this.info.memory.textures ++; + _isEnvironmentTexture( texture ) { - renderTargetProperties.depthTextureGPU = depthTextureGPU; - renderTargetProperties.depthTextureFormat = depthTextureFormat; + const mapping = texture.mapping; - if ( renderTarget.depthTexture !== null ) { + return ( mapping === EquirectangularReflectionMapping || mapping === EquirectangularRefractionMapping ) || ( mapping === CubeReflectionMapping || mapping === CubeRefractionMapping ); - const depthTextureProperties = properties.get( renderTarget.depthTexture ); - depthTextureProperties.textureGPU = depthTextureGPU; - depthTextureProperties.initializedRTT = false; + } - } + _getDefaultTextureGPU() { - } + let defaultTexture = this.defaultTexture; - // + if ( defaultTexture === null ) { - const disposeCallback = onRenderTargetDispose.bind( this ); - renderTargetProperties.disposeCallback = disposeCallback; - - renderTarget.addEventListener( 'dispose', disposeCallback ); + const texture = new Texture(); + texture.minFilter = NearestFilter; + texture.magFilter = NearestFilter; - // + this.createTexture( texture ); - renderTargetProperties.initialized = true; + this.defaultTexture = defaultTexture = texture; } - } - - dispose() { - - this.samplerCache.clear(); + return this.backend.get( defaultTexture ).texture; } - _convertAddressMode( value ) { + _getDefaultCubeTextureGPU() { - let addressMode = GPUAddressMode.ClampToEdge; + let defaultCubeTexture = this.defaultTexture; - if ( value === RepeatWrapping ) { + if ( defaultCubeTexture === null ) { - addressMode = GPUAddressMode.Repeat; + const texture = new CubeTexture(); + texture.minFilter = NearestFilter; + texture.magFilter = NearestFilter; - } else if ( value === MirroredRepeatWrapping ) { + this.createTexture( texture ); - addressMode = GPUAddressMode.MirrorRepeat; + this.defaultCubeTexture = defaultCubeTexture = texture; } - return addressMode; + return this.backend.get( defaultCubeTexture ).texture; } - _convertFilterMode( value ) { - - let filterMode = GPUFilterMode.Linear; - - if ( value === NearestFilter || value === NearestMipmapNearestFilter || value === NearestMipmapLinearFilter ) { - - filterMode = GPUFilterMode.Nearest; + _copyImageToTexture( image, texture, textureGPU, textureDescriptorGPU, needsMipmaps, originDepth ) { - } - - return filterMode; + if ( this._isHTMLImage( image ) ) { - } + this._getImageBitmapFromHTML( image, texture ).then( imageBitmap => { - _uploadVideoTexture( texture ) { + this._copyExternalImageToTexture( imageBitmap, textureGPU, textureDescriptorGPU, needsMipmaps, originDepth ); - const device = this.device; + } ); - const textureProperties = this.properties.get( texture ); + } else { - const textureGPU = device.importExternalTexture( { - source: texture.source.data - } ); + // assume ImageBitmap - textureProperties.textureGPU = textureGPU; - //textureProperties.version = texture.version; // @TODO: Force update for now, study a better solution soon using native VideoTexture.update() to fix warns + this._copyExternalImageToTexture( image, textureGPU, textureDescriptorGPU, needsMipmaps, originDepth ); - return true; + } } - _uploadTexture( texture ) { - - let needsUpdate = false; - - const device = this.device; - const image = texture.image; - - const textureProperties = this.properties.get( texture ); - - const { width, height, depth } = this._getSize( texture ); - const needsMipmaps = this._needsMipmaps( texture ); - const dimension = this._getDimension( texture ); - const mipLevelCount = this._getMipLevelCount( texture, width, height, needsMipmaps ); - const format = texture.internalFormat || this._getFormat( texture ); - - let usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST; + _isHTMLImage( image ) { - if ( needsMipmaps ) { + return ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ); - // current mipmap generation requires RENDER_ATTACHMENT + } - usage |= GPUTextureUsage.RENDER_ATTACHMENT; + _copyCubeMapToTexture( images, texture, textureGPU, textureDescriptorGPU, needsMipmaps ) { - } + for ( let i = 0; i < 6; i ++ ) { - const textureGPUDescriptor = { - label: texture.name, - size: { - width: width, - height: height, - depthOrArrayLayers: depth, - }, - mipLevelCount: mipLevelCount, - sampleCount: 1, - dimension: dimension, - format: format, - usage: usage - }; + const image = images[ i ]; - // texture creation + if ( image.isDataTexture ) { - let textureGPU = textureProperties.textureGPU; + this._copyBufferToTexture( image.image, textureGPU, textureDescriptorGPU, needsMipmaps, i ); - if ( textureGPU === undefined ) { + } else { - textureGPU = device.createTexture( textureGPUDescriptor ); + this._copyImageToTexture( image, texture, textureGPU, textureDescriptorGPU, needsMipmaps, i ); - needsUpdate = true; + } } - // transfer texture data + } - if ( texture.isDataTexture || texture.isDataArrayTexture || texture.isData3DTexture ) { + _copyExternalImageToTexture( image, textureGPU, textureDescriptorGPU, needsMipmaps, originDepth = 0 ) { - this._copyBufferToTexture( image, textureGPU, textureGPUDescriptor, needsMipmaps ); + const device = this.backend.device; - } else if ( texture.isCompressedTexture ) { + device.queue.copyExternalImageToTexture( + { + source: image + }, { + texture: textureGPU, + mipLevel: 0, + origin: { x: 0, y: 0, z: originDepth } + }, { + width: image.width, + height: image.height, + depthOrArrayLayers: 1 + } + ); - this._copyCompressedBufferToTexture( texture.mipmaps, textureGPU, textureGPUDescriptor ); + if ( needsMipmaps ) this._generateMipmaps( textureGPU, textureDescriptorGPU, originDepth ); - } else if ( texture.isCubeTexture ) { + } - if ( image.length === 6 ) { + _generateMipmaps( textureGPU, textureDescriptorGPU, baseArrayLayer = 0 ) { - this._copyCubeMapToTexture( image, texture, textureGPU, textureGPUDescriptor, needsMipmaps ); + if ( this.mipmapUtils === null ) { - } + this.mipmapUtils = new WebGPUTextureMipmapUtils( this.backend.device ); - } else if ( texture.isRenderTargetTexture ) { + } - if ( needsMipmaps === true ) this._generateMipmaps( textureGPU, textureGPUDescriptor ); + this.mipmapUtils.generateMipmaps( textureGPU, textureDescriptorGPU, baseArrayLayer ); - } else if ( texture.isDepthTexture !== true && image !== null ) { + } - this._copyImageToTexture( image, texture, textureGPU, textureGPUDescriptor, needsMipmaps ); + _getImageBitmapFromHTML( image, texture ) { - } + const width = image.width; + const height = image.height; - // + const options = {}; - textureProperties.textureGPU = textureGPU; - textureProperties.version = texture.version; + options.imageOrientation = ( texture.flipY === true ) ? 'flipY' : 'none'; + options.premultiplyAlpha = ( texture.premultiplyAlpha === true ) ? 'premultiply' : 'default'; - return needsUpdate; + return createImageBitmap( image, 0, 0, width, height, options ); } - _copyBufferToTexture( image, textureGPU, textureGPUDescriptor, needsMipmaps, originDepth = 0 ) { + _copyBufferToTexture( image, textureGPU, textureDescriptorGPU, needsMipmaps, originDepth = 0 ) { // @TODO: Consider to use GPUCommandEncoder.copyBufferToTexture() // @TODO: Consider to support valid buffer layouts with other formats like RGB + const device = this.backend.device; + const data = image.data; - const bytesPerTexel = this._getBytesPerTexel( textureGPUDescriptor.format ); + const bytesPerTexel = this._getBytesPerTexel( textureDescriptorGPU.format ); const bytesPerRow = image.width * bytesPerTexel; - this.device.queue.writeTexture( + device.queue.writeTexture( { texture: textureGPU, mipLevel: 0, @@ -487,55 +416,17 @@ class WebGPUTextures { depthOrArrayLayers: ( image.depth !== undefined ) ? image.depth : 1 } ); - if ( needsMipmaps === true ) this._generateMipmaps( textureGPU, textureGPUDescriptor, originDepth ); + if ( needsMipmaps === true ) this._generateMipmaps( textureGPU, textureDescriptorGPU, originDepth ); } - _copyCubeMapToTexture( images, texture, textureGPU, textureGPUDescriptor, needsMipmaps ) { - - for ( let i = 0; i < 6; i ++ ) { - - const image = images[ i ]; - - if ( image.isDataTexture ) { - - this._copyBufferToTexture( image.image, textureGPU, textureGPUDescriptor, needsMipmaps, i ); - - } else { - - this._copyImageToTexture( image, texture, textureGPU, textureGPUDescriptor, needsMipmaps, i ); - - } - - } - - } - - _copyExternalImageToTexture( image, textureGPU, textureGPUDescriptor, needsMipmaps, originDepth = 0 ) { - - this.device.queue.copyExternalImageToTexture( - { - source: image - }, { - texture: textureGPU, - mipLevel: 0, - origin: { x: 0, y: 0, z: originDepth } - }, { - width: image.width, - height: image.height, - depthOrArrayLayers: 1 - } - ); - - if ( needsMipmaps ) this._generateMipmaps( textureGPU, textureGPUDescriptor, originDepth ); - - } - - _copyCompressedBufferToTexture( mipmaps, textureGPU, textureGPUDescriptor ) { + _copyCompressedBufferToTexture( mipmaps, textureGPU, textureDescriptorGPU ) { // @TODO: Consider to use GPUCommandEncoder.copyBufferToTexture() - const blockData = this._getBlockData( textureGPUDescriptor.format ); + const device = this.backend.device; + + const blockData = this._getBlockData( textureDescriptorGPU.format ); for ( let i = 0; i < mipmaps.length; i ++ ) { @@ -546,7 +437,7 @@ class WebGPUTextures { const bytesPerRow = Math.ceil( width / blockData.width ) * blockData.byteLength; - this.device.queue.writeTexture( + device.queue.writeTexture( { texture: textureGPU, mipLevel: i @@ -567,18 +458,6 @@ class WebGPUTextures { } - _generateMipmaps( textureGPU, textureGPUDescriptor, baseArrayLayer ) { - - if ( this.utils === null ) { - - this.utils = new WebGPUTextureUtils( this.device ); // only create this helper if necessary - - } - - this.utils.generateMipmaps( textureGPU, textureGPUDescriptor, baseArrayLayer ); - - } - _getBlockData( format ) { // this method is only relevant for compressed texture formats @@ -616,6 +495,76 @@ class WebGPUTextures { } + _convertAddressMode( value ) { + + let addressMode = GPUAddressMode.ClampToEdge; + + if ( value === RepeatWrapping ) { + + addressMode = GPUAddressMode.Repeat; + + } else if ( value === MirroredRepeatWrapping ) { + + addressMode = GPUAddressMode.MirrorRepeat; + + } + + return addressMode; + + } + + _convertFilterMode( value ) { + + let filterMode = GPUFilterMode.Linear; + + if ( value === NearestFilter || value === NearestMipmapNearestFilter || value === NearestMipmapLinearFilter ) { + + filterMode = GPUFilterMode.Nearest; + + } + + return filterMode; + + } + + _getSize( texture ) { + + const image = texture.image; + + let width, height, depth; + + if ( texture.isCubeTexture ) { + + const faceImage = image.length > 0 ? image[ 0 ].image || image[ 0 ] : null; + + width = faceImage ? faceImage.width : 1; + height = faceImage ? faceImage.height : 1; + depth = 6; // one image for each side of the cube map + + } else if ( image !== null ) { + + width = image.width; + height = image.height; + depth = ( image.depth !== undefined ) ? image.depth : 1; + + } else { + + width = height = depth = 1; + + } + + return { width, height, depth }; + + } + + _needsMipmaps( texture ) { + + if ( this._isEnvironmentTexture( texture ) ) return true; + + return ( texture.isCompressedTexture !== true ) /*&& ( texture.generateMipmaps === true )*/ && ( texture.minFilter !== NearestFilter ) && ( texture.minFilter !== LinearFilter ); + + } + _getBytesPerTexel( format ) { if ( format === GPUTextureFormat.R8Unorm ) return 1; @@ -648,6 +597,28 @@ class WebGPUTextures { } + _getMipLevelCount( texture, width, height, needsMipmaps ) { + + let mipLevelCount; + + if ( texture.isCompressedTexture ) { + + mipLevelCount = texture.mipmaps.length; + + } else if ( needsMipmaps ) { + + mipLevelCount = Math.floor( Math.log2( Math.max( width, height ) ) ) + 1; + + } else { + + mipLevelCount = 1; // a texture without mipmaps has a base mip (mipLevel 0) + + } + + return mipLevelCount; + + } + _getFormat( texture ) { const format = texture.format; @@ -656,7 +627,11 @@ class WebGPUTextures { let formatGPU; - if ( texture.isCompressedTexture === true ) { + if ( /*texture.isRenderTargetTexture === true ||*/ texture.isFramebufferTexture === true ) { + + formatGPU = GPUTextureFormat.BGRA8Unorm; + + } else if ( texture.isCompressedTexture === true ) { switch ( format ) { @@ -875,176 +850,6 @@ class WebGPUTextures { } - _isHTMLImage( image ) { - - return ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ); - - } - - _copyImageToTexture( image, texture, textureGPU, textureGPUDescriptor, needsMipmaps, originDepth ) { - - if ( this._isHTMLImage( image ) ) { - - this._getImageBitmapFromHTML( image, texture ).then( imageBitmap => { - - this._copyExternalImageToTexture( imageBitmap, textureGPU, textureGPUDescriptor, needsMipmaps, originDepth ); - - } ); - - } else { - - // assume ImageBitmap - - this._copyExternalImageToTexture( image, textureGPU, textureGPUDescriptor, needsMipmaps, originDepth ); - - } - - } - - _getImageBitmapFromHTML( image, texture ) { - - const width = image.width; - const height = image.height; - - const options = {}; - - options.imageOrientation = ( texture.flipY === true ) ? 'flipY' : 'none'; - options.premultiplyAlpha = ( texture.premultiplyAlpha === true ) ? 'premultiply' : 'default'; - - return createImageBitmap( image, 0, 0, width, height, options ); - - } - - _getImageBitmap( image, texture ) { - - const width = image.width; - const height = image.height; - - if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) || - ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) ) { - - const options = {}; - - options.imageOrientation = ( texture.flipY === true ) ? 'flipY' : 'none'; - options.premultiplyAlpha = ( texture.premultiplyAlpha === true ) ? 'premultiply' : 'default'; - - return createImageBitmap( image, 0, 0, width, height, options ); - - } else { - - // assume ImageBitmap - - return Promise.resolve( image ); - - } - - } - - _getMipLevelCount( texture, width, height, needsMipmaps ) { - - let mipLevelCount; - - if ( texture.isCompressedTexture ) { - - mipLevelCount = texture.mipmaps.length; - - } else if ( needsMipmaps ) { - - mipLevelCount = Math.floor( Math.log2( Math.max( width, height ) ) ) + 1; - - } else { - - mipLevelCount = 1; // a texture without mipmaps has a base mip (mipLevel 0) - - } - - return mipLevelCount; - - } - - _getSize( texture ) { - - const image = texture.image; - - let width, height, depth; - - if ( texture.isCubeTexture ) { - - const faceImage = image.length > 0 ? image[ 0 ].image || image[ 0 ] : null; - - width = faceImage ? faceImage.width : 1; - height = faceImage ? faceImage.height : 1; - depth = 6; // one image for each side of the cube map - - } else if ( image !== null ) { - - width = image.width; - height = image.height; - depth = ( image.depth !== undefined ) ? image.depth : 1; - - } else { - - width = height = depth = 1; - - } - - return { width, height, depth }; - - } - - _needsMipmaps( texture ) { - - return ( texture.isCompressedTexture !== true ) && ( texture.generateMipmaps === true ) && ( texture.minFilter !== NearestFilter ) && ( texture.minFilter !== LinearFilter ); - - } - -} - -function onRenderTargetDispose( event ) { - - const renderTarget = event.target; - const properties = this.properties; - - const renderTargetProperties = properties.get( renderTarget ); - - renderTarget.removeEventListener( 'dispose', renderTargetProperties.disposeCallback ); - - renderTargetProperties.colorTextureGPU.destroy(); - properties.remove( renderTarget.texture ); - - this.info.memory.textures --; - - if ( renderTarget.depthBuffer === true ) { - - renderTargetProperties.depthTextureGPU.destroy(); - - this.info.memory.textures --; - - if ( renderTarget.depthTexture !== null ) { - - properties.remove( renderTarget.depthTexture ); - - } - - } - - properties.remove( renderTarget ); - -} - -function onTextureDispose( event ) { - - const texture = event.target; - - const textureProperties = this.properties.get( texture ); - textureProperties.textureGPU.destroy(); - - texture.removeEventListener( 'dispose', textureProperties.disposeCallback ); - - this.properties.remove( texture ); - - this.info.memory.textures --; - } -export default WebGPUTextures; +export default WebGPUTextureUtils; diff --git a/examples/jsm/renderers/webgpu/utils/WebGPUUtils.js b/examples/jsm/renderers/webgpu/utils/WebGPUUtils.js new file mode 100644 index 00000000000000..39d8d85d5b2990 --- /dev/null +++ b/examples/jsm/renderers/webgpu/utils/WebGPUUtils.js @@ -0,0 +1,92 @@ +import { GPUPrimitiveTopology, GPUTextureFormat } from './WebGPUConstants.js'; + +class WebGPUUtils { + + constructor( backend ) { + + this.backend = backend; + + } + + getCurrentDepthStencilFormat( renderContext ) { + + let format; + + if ( renderContext.depthTexture !== null ) { + + format = this.getTextureFormatGPU( renderContext.depthTexture ); + + } else if ( renderContext.depth && renderContext.stencil ) { + + format = GPUTextureFormat.Depth24PlusStencil8; + + } else if ( renderContext.depth ) { + + format = GPUTextureFormat.Depth24Plus; + + } + + return format; + + } + + getTextureFormatGPU( texture ) { + + return this.backend.get( texture ).texture.format; + + } + + getCurrentColorFormat( renderContext ) { + + let format; + + if ( renderContext.texture !== null ) { + + format = this.getTextureFormatGPU( renderContext.texture ); + + } else { + + format = GPUTextureFormat.BGRA8Unorm; // default context format + + } + + return format; + + } + + getCurrentColorSpace( renderContext ) { + + if ( renderContext.texture !== null ) { + + return renderContext.texture.colorSpace; + + } + + return this.backend.renderer.outputColorSpace; + + } + + getPrimitiveTopology( object, material ) { + + if ( object.isPoints ) return GPUPrimitiveTopology.PointList; + else if ( object.isLineSegments || ( object.isMesh && material.wireframe === true ) ) return GPUPrimitiveTopology.LineList; + else if ( object.isLine ) return GPUPrimitiveTopology.LineStrip; + else if ( object.isMesh ) return GPUPrimitiveTopology.TriangleList; + + } + + getSampleCount( renderContext ) { + + if ( renderContext.texture !== null ) { + + return 1; + + } + + return this.backend.parameters.sampleCount; + + } + +} + +export default WebGPUUtils; diff --git a/examples/screenshots/webgpu_backdrop_area.jpg b/examples/screenshots/webgpu_backdrop_area.jpg new file mode 100644 index 00000000000000..001fe203e0b94e Binary files /dev/null and b/examples/screenshots/webgpu_backdrop_area.jpg differ diff --git a/examples/webgl_geometry_csg.html b/examples/webgl_geometry_csg.html index c863661a705ef9..fee4003c3b6d01 100644 --- a/examples/webgl_geometry_csg.html +++ b/examples/webgl_geometry_csg.html @@ -33,8 +33,8 @@ "imports": { "three": "../build/three.module.js", "three/addons/": "./jsm/", - "three-mesh-bvh": "https://unpkg.com/three-mesh-bvh@^0.5.22/build/index.module.js", - "three-bvh-csg": "https://unpkg.com/three-bvh-csg@^0.0.4/build/index.module.js" + "three-mesh-bvh": "https://unpkg.com/three-mesh-bvh@0.6.0/build/index.module.js", + "three-bvh-csg": "https://unpkg.com/three-bvh-csg@0.0.7/build/index.module.js" } } diff --git a/examples/webgpu_backdrop_area.html b/examples/webgpu_backdrop_area.html new file mode 100644 index 00000000000000..5d5cd1a7f00d8a --- /dev/null +++ b/examples/webgpu_backdrop_area.html @@ -0,0 +1,181 @@ + + + + three.js - WebGPU - Backdrop Area + + + + + + +

+ three.js WebGPU - Backdrop Area +
+ + + + + + + + diff --git a/src/renderers/webgl/WebGLProgram.js b/src/renderers/webgl/WebGLProgram.js index 332652bee95ae4..71d9bd1cedf283 100644 --- a/src/renderers/webgl/WebGLProgram.js +++ b/src/renderers/webgl/WebGLProgram.js @@ -524,7 +524,7 @@ function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) { // - parameters.vertexTangents ? '#define USE_TANGENT' : '', + parameters.vertexTangents && parameters.flatShading === false ? '#define USE_TANGENT' : '', parameters.vertexColors ? '#define USE_COLOR' : '', parameters.vertexAlphas ? '#define USE_COLOR_ALPHA' : '', parameters.vertexUv1s ? '#define USE_UV1' : '', @@ -705,7 +705,7 @@ function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) { parameters.transmissionMap ? '#define USE_TRANSMISSIONMAP' : '', parameters.thicknessMap ? '#define USE_THICKNESSMAP' : '', - parameters.vertexTangents ? '#define USE_TANGENT' : '', + parameters.vertexTangents && parameters.flatShading === false ? '#define USE_TANGENT' : '', parameters.vertexColors || parameters.instancingColor ? '#define USE_COLOR' : '', parameters.vertexAlphas ? '#define USE_COLOR_ALPHA' : '', parameters.vertexUv1s ? '#define USE_UV1' : '', diff --git a/test/e2e/puppeteer.js b/test/e2e/puppeteer.js index 70c45c3ace85f9..43c3e5af4284e6 100644 --- a/test/e2e/puppeteer.js +++ b/test/e2e/puppeteer.js @@ -62,9 +62,7 @@ const exceptionList = [ 'webgl_camera_logarithmicdepthbuffer', 'webgl_effects_ascii', 'webgl_loader_pdb', - 'webgl_multiple_canvases_circle', 'webgl_multiple_elements_text', - 'webgl_shaders_tonemapping', // Unknown // TODO: most of these can be fixed just by increasing idleTime and parseTime @@ -72,7 +70,6 @@ const exceptionList = [ 'webgl_buffergeometry_glbufferattribute', 'webgl_clipping_advanced', 'webgl_lensflares', - 'webgl_lines_sphere', 'webgl_lights_spotlights', 'webgl_loader_imagebitmap', 'webgl_loader_texture_lottie', @@ -88,7 +85,40 @@ const exceptionList = [ 'webgl_shadowmap', 'webgl_shadowmap_progressive', 'webgl_test_memory2', - 'webgl_tiled_forward' + 'webgl_tiled_forward', + + // TODO: implement determinism for setTimeout and setInterval + // could it fix some examples from above? + 'physics_rapier_instancing', + + // Awaiting for WebGPU support + 'webgpu_audio_processing', + 'webgpu_backdrop', + 'webgpu_backdrop_area', + 'webgpu_compute', + 'webgpu_cubemap_adjustments', + 'webgpu_cubemap_dynamic', + 'webgpu_cubemap_mix', + 'webgpu_depth_texture', + 'webgpu_equirectangular', + 'webgpu_instance_mesh', + 'webgpu_instance_uniform', + 'webgpu_lights_custom', + 'webgpu_lights_ies_spotlight', + 'webgpu_lights_phong', + 'webgpu_lights_selective', + 'webgpu_loader_gltf', + 'webgpu_loader_gltf_compressed', + 'webgpu_materials', + 'webgpu_materials_video', + 'webgpu_particles', + 'webgpu_rtt', + 'webgpu_sandbox', + 'webgpu_shadowmap', + 'webgpu_skinning', + 'webgpu_skinning_instancing', + 'webgpu_skinning_points', + 'webgpu_sprites' ];