Having trouble implementing deferred decals

I’ve been trying to implement deferred decals and ran into a bit of a snag, My approach is to create a g-buffer with THREE.WebGLMultipleRenderTargets render the scene into it, and then render a separate scene containing the decals on top of it, with a decal shader where I bind the color target from the g-buffer as the output, and the depth buffer (for world position reconstruction) as an input.
Here’s what I’m trying to do:

    public render(camera: THREE.Camera, gbuffer: THREE.WebGLMultipleRenderTargets, renderer: THREE.WebGLRenderer) {
        //Create decal texture
        const decalRenderTarget = new THREE.WebGLRenderTarget(gbuffer.width, gbuffer.height);
        //Assign the g-buffer color target as output
        decalRenderTarget.texture = gbuffer.texture[1];       
        //Update the uniforms and assign the depth texture to a sampler
        this.updateUniforms(camera, gbuffer);        
        renderer.render(this.decalScene, camera);                

Unfortunately the following code results in a black texture being rendered with no errors appearing on the console.

I’ve also tried an alternative approach of just rendering the decals into the g-buffer directly, but that results in a WebGL feedback loop error (since the G-buffer is both bound as an output, and the depth texture is read)

Could you please help me with this issue?

I’ve also made an attempt to copy the g-buffer textures via renderer.copyTextureToTexture so that the loopback issue can be fixed, but this results in the error:
VM228:3 TypeError: Failed to execute 'texSubImage2D' on 'WebGL2RenderingContext': Overload resolution failed.

For the first issue, you could bind the GBuffer+Depth… copy that to the main framebuffer, using a copy shader, then disable autoClear and render your decals to the main framebuffer.

For the second issue, that looks like you may be passing invalid parameters to the copyTextureToTexture. Are you by chance passing in “renderTarget” instead of “renderTarget.texture” ?

For the first issue, yeah that could work, but I’d rather avoid the perf hit and complexity of having to copy the buffers around unless something is fundamentally wrong with that approach (I don’t think there is). I’d rather figure out the root cause of the issue than have a workaround.

For the second issue, I’ve checked and no, I am not. I am passing textures (I use Typescript to avoid these kinds of issues).

Usually indicates you’re passing something wrong. maybe a null or undefined. Guess typescript didn’t catch that. :expressionless:

r.e. buffer copy, there isn’t really a way around some form of resolve pass if you’re doing deferred. And you need to copy because you’re going to be overlaying your decals, unless they aren’t blended.

So the steps would be like… render you gbuffer (materialID, normal) and (depth) to rendertarget…

Then bind the gbuffer as input… and output to the display…
And “resolve” your gbuffer with your material id ubershader…
Then draw your decals on top… and then draw your transparent stuff.

  this.renderTarget = new THREE.WebGLRenderTarget(width, height, {
      depthBuffer: false,
      generateMipmaps: false,
      format : THREE.RGBAFormat,
      type : THREE.FloatType,
      minFilter: THREE.NearestFilter,
      magFilter: THREE.NearestFilter,
 renderer.copyTextureToTexture(new THREE.Vector2(0.0,0.0),gbuffer.texture[1],this.renderTarget.texture);

This is how I’m doing the copy. It’s not null or undefined. Maybe there’s some format incompatibility issue (but as far as I can tell, they’re the exact same format).

Here’s the full error:

VM292:3 TypeError: Failed to execute 'texSubImage2D' on 'WebGL2RenderingContext': Overload resolution failed.
    at A.executeFunction (<anonymous>:3:507584)
    at A.executeOriginFunction (<anonymous>:3:507212)
    at WebGL2RenderingContext.texSubImage2D (<anonymous>:3:519362)
    at WebGLRenderer.copyTextureToTexture (three.module.js:30256:10)
    at DecalsPass.render (DecalsPass.ts:122:18)
    at animate (main.ts:220:21)
    at <anonymous>:3:592119 []

As for the render texture, after the g-buffer pass is done why can’t I treat the g-buffer’s textures as regular textures, including making them part of a new render target? I’m not reading and writing the same texture. I can bind them to samplers just fine.

Hard to say without looking at your full code. Like what’s gbuffer.texture[1] ? I usually check these things by dropping a breakpoint on the line you’re calling the function, and hovering the mouse over the arguments to make sure they are the right type and non null.
There is also a “level” parameter that you are omitting… not sure if that’s optional. You might need to pass a 0 there.

from the docs:

.copyTextureToTexture ( position : Vector2, srcTexture : Texture, dstTexture : Texture, level : Number )

edit: I checked the code and it does default level to 0, so that’s probably not it.

Regardless, i’m still not clear why you need to copyTexture2Texture?

Here’s how the renderer works currently, it’s a bit crude, since I’m writing it as a learning exercise.

  • I write opaques the g-buffer
  • I render the decals on top of geometry (this currently only affects the colors, normals are TBD)
  • I render the lights to a separate output buffer
  • I render some post-effects into separate buffers
  • I composite and gamma-correct the results

I’m currently trying (and failing) with writing the decals to the color pass.
What I could do is write to a separate decal texture and composite its results with the g-buffer’s color texture (this is what you suggested afaict). I don’t think this extra stuff should be necessary.

I tried adding the miplevel to the copy function, and unfortunately it didn’t fix the issue.

gbuffer.texture[1] is the color texture with the rgb components being the diffuse color and the a being specularity

Hmm ok. So this isn’t quite deferred in the classical sense, since you’re still running the full material shaders in the forward pass “I write the opaques the gbuffer”

Classical gbuffer would just write a material ID, along with normal… and depth…
Then in the “resolve” pass you read the material IDs, read the depth, read the normal… reconstruct worldspace position from fragCoord and depth and use that for shading and lighting, which are all layered on to the same buffer. no need to render everything to separate buffers…

If this is a small app perhaps you could share a glitch or fiddle?

With respect, I think you define deferred too narrowly. Here’s the g-buffer layout from Killzone 2:

They do definitely store the color (Diffuse Albedo) directly, rather than some material id.

Edit: I’ll try to put together a toy example demonstrating the issue, but right now it’s already past midnight here, so I’m going to sleep :slight_smile: Thanks for the help.

1 Like

Fair enough. :slight_smile: