Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified examples/screenshots/webgpu_loader_materialx.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions src/nodes/core/NodeBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,14 @@ class NodeBuilder {
*/
this.material = ( object && object.material ) || null;

/**
* The material's side the shader is built for. Nodes must use this property instead
* of `material.side` which is temporarily overwritten for the back side pass.
*
* @type {?number}
*/
this.materialSide = this.material !== null ? this.material.side : null;

/**
* The geometry of the 3D object.
*
Expand Down
12 changes: 4 additions & 8 deletions src/nodes/display/FrontFacingNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,7 @@ class FrontFacingNode extends Node {

//

const { material } = builder;

if ( material.side === BackSide ) {
if ( builder.materialSide === BackSide ) {

return 'false';

Expand Down Expand Up @@ -88,15 +86,13 @@ export const faceDirection = /*@__PURE__*/ float( frontFacing ).mul( 2.0 ).sub(
* @param {Node<vec3>} vector - The vector to process.
* @returns {Node<vec3>} The processed vector.
*/
export const negateOnBackSide = /*@__PURE__*/ Fn( ( [ vector ], { material } ) => {

const side = material.side;
export const negateOnBackSide = /*@__PURE__*/ Fn( ( [ vector ], { materialSide } ) => {

if ( side === BackSide ) {
if ( materialSide === BackSide ) {

vector = vector.mul( - 1.0 );

} else if ( side === DoubleSide ) {
} else if ( materialSide === DoubleSide ) {

vector = vector.mul( faceDirection );

Expand Down
4 changes: 2 additions & 2 deletions src/nodes/functions/PhysicalLightingModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ const applyIorToRoughness = /*@__PURE__*/ Fn( ( [ roughness, ior ] ) => {
const viewportBackSideTexture = /*@__PURE__*/ viewportMipTexture();
const viewportFrontSideTexture = /*@__PURE__*/ viewportOpaqueMipTexture();

const getTransmissionSample = /*@__PURE__*/ Fn( ( [ fragCoord, roughness, ior ], { material } ) => {
const getTransmissionSample = /*@__PURE__*/ Fn( ( [ fragCoord, roughness, ior ], { materialSide } ) => {

const vTexture = material.side === BackSide ? viewportBackSideTexture : viewportFrontSideTexture;
const vTexture = materialSide === BackSide ? viewportBackSideTexture : viewportFrontSideTexture;

const transmissionSample = vTexture.sample( fragCoord );
//const transmissionSample = viewportMipTexture( fragCoord );
Expand Down
13 changes: 11 additions & 2 deletions src/renderers/common/RenderObject.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,15 @@ class RenderObject {
*/
this.material = material;

/**
* The material's side this render object is rendered with. Since render objects are
* unique per pass (see the `backSide` pass ID), this preserves the side of the back
* side pass when `material.side` has already been restored.
*
* @type {number}
*/
this.materialSide = material.side;

/**
* The scene the 3D object belongs to.
*
Expand Down Expand Up @@ -752,9 +761,9 @@ class RenderObject {
if ( property === 'side' ) {

// `side` is an enum (FrontSide/BackSide/DoubleSide) that changes code
// generation, so its exact value must be preserved.
// generation, so the exact side of this render object must be preserved.

valueKey = String( value );
valueKey = String( this.materialSide );

} else {

Expand Down
1 change: 1 addition & 0 deletions src/renderers/common/RenderObjects.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ class RenderObjects {
// update references

renderObject.camera = camera;
renderObject.materialSide = material.side;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to construct a RenderObject for one side and then use it for another?

Could we just define it at build time and restore it afterwards? I think it would be less confusing than having both materialSide and material.side exposed to the end developer, as is the case with TSL functions.

const previousMaterialSide = material.side;
material.side = renderObject.materialSide;
// build ...
material.side = previousMaterialSide;


//

Expand Down
64 changes: 16 additions & 48 deletions src/renderers/common/Renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -653,13 +653,13 @@ class Renderer {
this._initPromise = null;

/**
* An array of compilation promises which are used in `compileAsync()`.
* An array of render objects which are precompiled in `compileAsync()`.
*
* @private
* @type {?Array<Promise>}
* @type {?Array<RenderObject>}
* @default null
*/
this._compilationPromises = null;
this._compilationRenderObjects = null;

/**
* Whether the renderer is currently precompiling a render object in
Expand Down Expand Up @@ -921,7 +921,7 @@ class Renderer {
const previousRenderContext = this._currentRenderContext;
const previousRenderObjectFunction = this._currentRenderObjectFunction;
const previousHandleObjectFunction = this._handleObjectFunction;
const previousCompilationPromises = this._compilationPromises;
const previousCompilationRenderObjects = this._compilationRenderObjects;

//

Expand All @@ -939,14 +939,14 @@ class Renderer {
const renderContext = this._renderContexts.get( renderTarget, this._mrt );
const activeMipmapLevel = this._activeMipmapLevel;

const compilationPromises = [];
const compilationRenderObjects = [];

this._currentRenderContext = renderContext;
this._currentRenderObjectFunction = this.renderObject;

this._handleObjectFunction = this._createObjectPipeline;

this._compilationPromises = compilationPromises;
this._compilationRenderObjects = compilationRenderObjects;

nodeFrame.renderId ++;

Expand Down Expand Up @@ -1039,7 +1039,7 @@ class Renderer {

}

// process render lists - _createObjectPipeline will push async promises to _compilationPromises
// process render lists - _createObjectPipeline will push render objects to _compilationRenderObjects

const opaqueObjects = renderList.opaque;
const transparentObjects = renderList.transparent;
Expand All @@ -1056,19 +1056,15 @@ class Renderer {
this._currentRenderContext = previousRenderContext;
this._currentRenderObjectFunction = previousRenderObjectFunction;
this._handleObjectFunction = previousHandleObjectFunction;
this._compilationPromises = previousCompilationPromises;
this._compilationRenderObjects = previousCompilationRenderObjects;

// Process compilation work items sequentially to avoid freezing
// Yields between objects to keep animation smooth

const total = compilationPromises.length;
const total = compilationRenderObjects.length;
let loaded = 0;

for ( const item of compilationPromises ) {

const renderObject = this._objects.get( item.object, item.material, item.scene, item.camera, item.lightsNode, item.renderContext, item.clippingContext, item.passId );
renderObject.drawRange = item.object.geometry.drawRange;
renderObject.group = item.group;
for ( const renderObject of compilationRenderObjects ) {

// Use async node building to yield to main thread
await this._nodes.getForRenderAsync( renderObject );
Expand All @@ -1083,6 +1079,7 @@ class Renderer {
// Wait for pipeline creation
const pipelinePromises = [];
this._pipelines.getForRender( renderObject, pipelinePromises );

if ( pipelinePromises.length > 0 ) {

await Promise.all( pipelinePromises );
Expand Down Expand Up @@ -3891,8 +3888,8 @@ class Renderer {
}

/**
* A different implementation for `_handleObjectFunction` which only makes sure the object is ready for rendering.
* Used in `compileAsync()`.
* A different implementation for `_handleObjectFunction` which collects the render objects
* that are precompiled in `compileAsync()` instead of rendering them.
*
* @private
* @param {Object3D} object - The 3D object.
Expand All @@ -3906,43 +3903,14 @@ class Renderer {
*/
_createObjectPipeline( object, material, scene, camera, lightsNode, group, clippingContext, passId ) {

// If in async compilation mode, queue the work for sequential execution
if ( this._compilationPromises !== null ) {

// Store work items instead of promises - will be processed sequentially
this._compilationPromises.push( {
object,
material,
scene,
camera,
lightsNode,
group,
clippingContext,
passId,
renderContext: this._currentRenderContext
} );

return;
// the render object must be requested during the traversal since `material.side` is
// temporarily overwritten for the back side pass, see `RenderObject.materialSide`

}

// Sync path
const renderObject = this._objects.get( object, material, scene, camera, lightsNode, this._currentRenderContext, clippingContext, passId );
renderObject.drawRange = object.geometry.drawRange;
renderObject.group = group;

//

this._nodes.updateBefore( renderObject );

this._geometries.updateForRender( renderObject );

this._nodes.updateForRender( renderObject );
this._bindings.updateForRender( renderObject );

this._pipelines.getForRender( renderObject, this._compilationPromises );

this._nodes.updateAfter( renderObject );
this._compilationRenderObjects.push( renderObject );

}

Expand Down
1 change: 1 addition & 0 deletions src/renderers/common/nodes/NodeManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ class NodeManager extends DataMap {

nodeBuilder.scene = renderObject.scene;
nodeBuilder.material = material;
nodeBuilder.materialSide = renderObject.materialSide;
nodeBuilder.camera = renderObject.camera;
nodeBuilder.context.material = material;
nodeBuilder.lightsNode = renderObject.lightsNode;
Expand Down
10 changes: 5 additions & 5 deletions src/renderers/webgpu/WebGPUBackend.js
Original file line number Diff line number Diff line change
Expand Up @@ -2087,7 +2087,7 @@ class WebGPUBackend extends Backend {

const data = this.get( renderObject );

const { object, material } = renderObject;
const { object, material, materialSide } = renderObject;

const utils = this.utils;

Expand All @@ -2108,7 +2108,7 @@ class WebGPUBackend extends Backend {
data.stencilWrite !== material.stencilWrite || data.stencilFunc !== material.stencilFunc ||
data.stencilFail !== material.stencilFail || data.stencilZFail !== material.stencilZFail || data.stencilZPass !== material.stencilZPass ||
data.stencilFuncMask !== material.stencilFuncMask || data.stencilWriteMask !== material.stencilWriteMask ||
data.side !== material.side || data.alphaToCoverage !== material.alphaToCoverage ||
data.side !== materialSide || data.alphaToCoverage !== material.alphaToCoverage ||
data.sampleCount !== sampleCount || data.colorSpace !== colorSpace ||
data.colorFormat !== colorFormat || data.depthStencilFormat !== depthStencilFormat ||
data.primitiveTopology !== primitiveTopology ||
Expand All @@ -2125,7 +2125,7 @@ class WebGPUBackend extends Backend {
data.stencilWrite = material.stencilWrite; data.stencilFunc = material.stencilFunc;
data.stencilFail = material.stencilFail; data.stencilZFail = material.stencilZFail; data.stencilZPass = material.stencilZPass;
data.stencilFuncMask = material.stencilFuncMask; data.stencilWriteMask = material.stencilWriteMask;
data.side = material.side; data.alphaToCoverage = material.alphaToCoverage;
data.side = materialSide; data.alphaToCoverage = material.alphaToCoverage;
data.sampleCount = sampleCount;
data.colorSpace = colorSpace;
data.colorFormat = colorFormat;
Expand All @@ -2150,7 +2150,7 @@ class WebGPUBackend extends Backend {
*/
getRenderCacheKey( renderObject ) {

const { object, material } = renderObject;
const { object, material, materialSide } = renderObject;

const utils = this.utils;
const renderContext = renderObject.context;
Expand All @@ -2169,7 +2169,7 @@ class WebGPUBackend extends Backend {
material.stencilWrite, material.stencilFunc,
material.stencilFail, material.stencilZFail, material.stencilZPass,
material.stencilFuncMask, material.stencilWriteMask,
material.side,
materialSide,
frontFaceCW,
utils.getSampleCountRenderContext( renderContext ),
utils.getCurrentColorSpace( renderContext ), utils.getCurrentColorFormat( renderContext ), utils.getCurrentDepthStencilFormat( renderContext ),
Expand Down
16 changes: 8 additions & 8 deletions src/renderers/webgpu/utils/WebGPUPipelineUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class WebGPUPipelineUtils {
*/
createRenderPipeline( renderObject, promises ) {

const { object, material, geometry, pipeline } = renderObject;
const { material, pipeline } = renderObject;
const { vertexProgram, fragmentProgram } = pipeline;

const backend = this.backend;
Expand Down Expand Up @@ -193,7 +193,7 @@ class WebGPUPipelineUtils {
const vertexModule = backend.get( vertexProgram ).module;
const fragmentModule = backend.get( fragmentProgram ).module;

const primitiveState = this._getPrimitiveState( object, geometry, material );
const primitiveState = this._getPrimitiveState( renderObject );
const depthCompare = this._getDepthCompare( material );
const depthStencilFormat = utils.getCurrentDepthStencilFormat( renderObject.context );

Expand Down Expand Up @@ -888,12 +888,12 @@ class WebGPUPipelineUtils {
* for the pipeline creation.
*
* @private
* @param {Object3D} object - The 3D object.
* @param {BufferGeometry} geometry - The geometry.
* @param {Material} material - The material.
* @param {RenderObject} renderObject - The render object.
* @return {Object} The primitive state.
*/
_getPrimitiveState( object, geometry, material ) {
_getPrimitiveState( renderObject ) {

const { object, geometry, material, materialSide } = renderObject;

const descriptor = {};
const utils = this.backend.utils;
Expand All @@ -910,15 +910,15 @@ class WebGPUPipelineUtils {

//

let flipSided = ( material.side === BackSide );
let flipSided = ( materialSide === BackSide );

if ( object.isMesh && object.matrixWorld.determinantAffine() < 0 ) flipSided = ! flipSided;

descriptor.frontFace = ( flipSided === true ) ? GPUFrontFace.CW : GPUFrontFace.CCW;

//

descriptor.cullMode = ( material.side === DoubleSide ) ? GPUCullMode.None : GPUCullMode.Back;
descriptor.cullMode = ( materialSide === DoubleSide ) ? GPUCullMode.None : GPUCullMode.Back;

return descriptor;

Expand Down