Giter Site home page Giter Site logo

webgl-outlines's Introduction

How to render outlines in WebGL

This is the source code for How to render outlines in WebGL & Better outline rendering using surface IDs with WebGL implemented in ThreeJS and PlayCanvas. This renders outlines with a post-process shader that takes the depth buffer and a surface normal buffer as inputs, followed by an FXAA pass.

Three versions of a boat 3D model showing the different outline techniquesBoat model by Google Poly

  • Left is a common way to visualize outlines, boundary only.
  • Middle is the technique in this repo.
  • Right is same as middle with outlines only.

Live demo

See live ThreeJS version.

Drag and drop any glTF file to see the outlines on your own models (must be a single .glb file).

Or click "Login to Sketchfab" and paste in any Sketchfab model URL, such as: https://sketchfab.com/3d-models/skull-downloadable-1a9db900738d44298b0bc59f68123393

Source code

Applying outlines selectively to objects

If you want to apply the outline effect to specific objects, instead of all objects in the scene, an example ThreeJS implementation is documented here: #3.

outline_selected

webgl-outlines's People

Contributors

autonomobil avatar dtrebilco avatar omarshehata avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

webgl-outlines's Issues

SkinnedMesh support

Hi!

I tried using a gltf with a skinned mesh, and the result looks like this:

image

These are the correct normals:

image

And here, the surface id debug buffer:

image

So as you can see, the skinned mesh simply doesn’t get a surface id.

Is there an easy way to get this to work?

deck.gl port

@OmarShehata by any chance would you have any knowledge in deck.gl ?
I am struggling to port your outline pass to deck.gl
They have an effects/pass system which should allow me to get the normalBuffer but i am not seeing how to do it :s
Thought it was worth asking.
Thanks

Automatic merging of vertices based on angle

In Better outline rendering with surface IDs we can see that using a surface ID buffer gives you more stable outlines vs just a normal buffer.

One problem with this is you might need to go through your geometry and merge a lot of vertices manually for faces that should all be considered one surface.

I think it should be possible to do this almost completely automatically. It would work like this:

  • Use an algorithm like ThreeJS EdgesGeometry that looks at the all the triangles, and determines if an edge should be drawn based on the angle of the two triangles it's connected to
  • If an edge does NOT pass this angle test, its vertices should be merged so that it appears as one surface, and the edge does not render
  • If an edge DOES pass the angle test, you should leave the vertices alone (or duplicate them if they are already merged?)

The only manual part about this is setting the threshold, but I think that's something that would be far easier to tweak than the normal buffer parameters.

If we can have a utility like that, then you'll be able to have very clean outlines by default, while still allowing you to edit individual edges in the model. It'd be the best of both worlds.

improvements and fixes

Amazing tuto. It was exactly what i was looking for!
Screenshot 2021-03-29 at 22 01 54

I have an issue with it doing some weird stuff in some case. Do you have any idea?

Mar-29-2021.22-02-58.mp4

Also i have seen your comment about possible improvement in getting normal maps. Could you elaborate? I would like to improve it if possible.

Again thanks a lot for what you have done!

Shader code can't be recognized

Hi Omar, thanks so much for putting this out there!!! Wonderful work. I tried to reproduce it in my own project, but am getting this error on both firefox and Chrome. My browsers support webGL 1 and 2:

THREE.WebGLProgram: Shader Error 0 - VALIDATE_STATUS false

Program Info Log: Must have a compiled fragment shader attached:
SHADER_INFO_LOG:
ERROR: 0:145: '0.0' : syntax error

FRAGMENT

ERROR: 0:145: '0.0' : syntax error

[three.module.js:18957](webpack:///node_modules/three/build/three.module.js)
    WebGLProgram three.module.js:18957
    acquireProgram three.module.js:19656
    getProgram three.module.js:27381
    setProgram three.module.js:27586
    renderBufferDirect three.module.js:26710
    renderObject three.module.js:27318
    renderObjects three.module.js:27287
    renderScene three.module.js:27209
    render three.module.js:27029
    render Pass.js:62
    render CustomOutlinePass.js:83
    render EffectComposer.js:141
    update Renderer.js:85
    update Experience.js:68
    Experience Experience.js:54
    trigger EventEmitter.js:138
    trigger EventEmitter.js:136
    tick Time.js:28
    Time Time.js:17
    (Async: FrameRequestCallback)
    Time Time.js:15
    Experience Experience.js:36
    <anonymous> script.js:5
    <anonymous> bundle.eadd6e7c63194188.js:67059
    <anonymous> bundle.eadd6e7c63194188.js:67061

It seems like the fragment shader can't be recognized? I simply copied the CustomOutlinePass.js.

Below is what my renderer code looks like. If I comment out the last line, this.composer.render(); and use the original renderer, the rendering works.

import * as THREE from 'three'
import Experience from './Experience.js'
import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js";
import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass.js";
import { ShaderPass } from "three/examples/jsm/postprocessing/ShaderPass.js";
import { FXAAShader } from "three/examples/jsm/shaders/FXAAShader.js";
import { CustomOutlinePass } from "./Utils/Shading/CustomOutlinePass.js";

export default class Renderer
{
    constructor()
    {
        this.experience = new Experience()
        this.canvas = this.experience.canvas
        this.sizes = this.experience.sizes
        this.scene = this.experience.scene
        this.camera = this.experience.camera

        this.setInstance()
    }

    setInstance()
    {
        this.instance = new THREE.WebGLRenderer({
            canvas: this.canvas,
            antialias: true
        })
        this.instance.physicallyCorrectLights = true
        this.instance.outputEncoding = THREE.sRGBEncoding
        this.instance.toneMapping = THREE.CineonToneMapping
        this.instance.toneMappingExposure = 1.75
        this.instance.shadowMap.enabled = true
        this.instance.shadowMap.type = THREE.PCFSoftShadowMap
        this.instance.setClearColor('#211d20')
        this.instance.setSize(this.sizes.width, this.sizes.height)
        this.instance.setPixelRatio(Math.min(this.sizes.pixelRatio, 2))

        // Set up post processing
        // Create a render target that holds a depthTexture so we can use it in the outline pass
        // See: https://threejs.org/docs/index.html#api/en/renderers/WebGLRenderTarget.depthBuffer
        const depthTexture = new THREE.DepthTexture();
        this.renderTarget = new THREE.WebGLRenderTarget(
            this.sizes.width,
            this.sizes.height,
        {
            depthTexture: depthTexture,
            depthBuffer: true,
        }
        );

        // Initial render pass.
        this.composer = new EffectComposer(this.instance, this.renderTarget);
        const pass = new RenderPass(this.scene, this.camera.instance);
        this.composer.addPass(pass);

        // Outline pass.
        this.customOutline = new CustomOutlinePass(
            new THREE.Vector2(this.sizes.width, this.sizes.height),
            this.scene,
            this.camera.instance
        );
        this.composer.addPass(this.customOutline);

        // Antialias pass.
        this.effectFXAA = new ShaderPass(FXAAShader);
        this.effectFXAA.uniforms["resolution"].value.set(
        1 / this.sizes.width,
        1 / this.sizes.height
        );
        this.composer.addPass(this.effectFXAA);
    }

    resize()
    {
        this.instance.setSize(this.sizes.width, this.sizes.height)
        this.instance.setPixelRatio(Math.min(this.sizes.pixelRatio, 2))
        // this.composer.setSize(window.innerWidth, window.innerHeight);
        // this.effectFXAA.setSize(window.innerWidth, window.innerHeight);
        // this.customOutline.setSize(window.innerWidth, window.innerHeight);
    }

    update()
    {
        // this.instance.render(this.scene, this.camera.instance)
        this.composer.render();
    }
}

Broken surface ID buffer on GTX 1070 ?

Reported on Twitter: https://twitter.com/tamat/status/1580872701035495425

image

I haven't been able to reproduce. My guess is the surfaceID buffer has invalid data drawn to it. That buffer is of type HalfFloat. I picked that because I thought that'd have more support than Float buffers, but it may be the other way around?

This buffer type is defined here:

surfaceBuffer.texture.type = THREE.HalfFloatType;

Filter outlines by the angle between surface normal & view direction

The updated article Better outlines using surface IDs improves outline rendering for terrain/ground surfaces a bit.

Here's before & after:

terrain_before
terrain_after

The "before" screenshot is a lot more noisy. This is because of the contribution of the normal buffer. In the 2nd screenshot the whole ground is seen as one "surface" so the only thing that contributes to the outline is the depth buffer.

I also clamp the depth diff in the shader so that the outlines are either on or off to make them look more crisp:

if (depthDiff >= 0.2) depthDiff = 1.0; else depthDiff = 0.0;

However, there is a problem here, if you want to be able to see more outlines, you can decrease the depthBias, but then you get a lot of these false edges in the distance:

terrain_depth_bias.mp4

Ian Maclarty's twitter thread here has a solution for this: we should filter out outlines where the surface normal is close to parallel with the view direction.

It would be great to show an example implementation of this in ThreeJS and see if that indeed fixes this specific artifact.

publish NPM package

First of all, I love this repo! This is game changer for people building 3D design tools, thanks @OmarShehata ❤️ !

I wanted to know if there are plans to publish this as an NPM package?

If there is interest and this is possible, I would love to have a look and prep a PR :)

Ideally, my aim would be to move this all the way to be included as a dependency in https://github.com/pmndrs/react-postprocessing in a similar way to how it was done for https://github.com/N8python/n8ao .

Thanks!

InstancedMesh support

Hey, fantastic work here!

Just for the notice, I tried adding a simple InstancedMesh to the scene of your example and it looks like the image below.

const box = new THREE.BoxGeometry();
const material = new THREE.MeshLambertMaterial({ color: "purple" });
const mesh = new THREE.InstancedMesh(box, material, 2);
const position = new THREE.Matrix4();
mesh.setMatrixAt(0, position);
position.setPosition(2, 0, 0);
mesh.setMatrixAt(1, position);
addSurfaceIdAttributeToMesh(mesh);
scene.add(mesh);

image

Add licence

Can you please add a licence so that people can use your class?

Can I have a transparent background in outline only??

Hello sir!
I need a transparent background, not a black background in Outline only.
I want the output to look like the picture below.

스크린샷, 2023-01-16 14-50-13

For Outlines V2

const renderer = new THREE.WebGLRenderer({
      alpha: true,
      canvas: document.querySelector('#outline')
});

As in the code above, if alpha: true is given, a transparent background is output.
like this picture.
outlines V2

However, in Outline only, no matter what I do, I cannot output a transparent background.
like this picture.
image

This is my forked repo. https://github.com/monkeykim111/webgl-outlines
I used three js minimal version and changed gl_FragColor for outline only and ran several tests for transparent background here.

And these are the sites I referenced.
https://stackoverflow.com/questions/20899326/how-do-i-stop-effectcomposer-from-destroying-my-transparent-background

https://discourse.threejs.org/t/effect-composer-keep-transparency/4447

https://discourse.threejs.org/t/transparent-background/22742/3

please help thank you!!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.