xml3d / xml3d.js

The WebGL/JS implementation of XML3D
Other
75 stars 25 forks source link

Custom render passess & Implementing shadows #186

Open Arsakes opened 8 years ago

Arsakes commented 8 years ago

What is the simple way to implement shadows given tools we have withing XML3D?

What I need is two pasess. One that renders whole scene geometry into depth map, then sends this as a texture to the next pass. Second pass also renders whole geometry but with different shaders. Is that possible?

I mean somehow easier than manually giving whole geometry to the pass and setting shaders. The whole difference would be replacement shaders within "normal" pass.

EDIT: if it would be too hard to do shadows I can somehow bake them. How can I save current canvas into downloadable image?

EDIT2: There is an extension to webgl that allows you to render depth into the same buffer.

csvurt commented 8 years ago

XML3D already has basic shadow support for spotlights, you can turn it on by giving the light a castShadow boolean:

<light model="urn:xml3d:light:spot">
     <bool name="castShadow">true</bool>
     <float name="shadowBias">0.001</float>
 </light>

If you need soft shadows or ones that work for different light sources too then you'll need to write the necessary render passes for that. Since you have access to the WebGL context (gl) you can do pretty much anything there, including activating extensions like WEBGL_DEPTH_TEXTURE.

Just be wary of any shadow rendering algorithm that requires rendering to floating point textures. The extensions governing that are a mess and the majority of devices out there probably don't support it.

Every RenderPass gets the scene object as an argument to its render function, if you want to render everything with a different shader you only need to bind that shader and iterate over the objects in the scene. Check out pick-object.js for example, it renders all objects in the scene with a special picking shader. You can ignore the viewMatrix and projMatrix arguments to the render function, those are for picking only. Also you only need to sort the objects once in your very first RenderPass, unless you change your camera in between (ie. when rendering from the point of view of a light).

You can save the contents of the canvas by generating a dataURL for it and either downloading it or using it as the src for an img element:

var url = document.querySelector("canvas").toDataURL();
var img = document.createElement("img");
img.src = url;
document.appendChild(img);
Arsakes commented 8 years ago

What do you mean I can ignore viewMatrix and projMatrix - are those uniforms provided automatically?

Appending new textures to default pass How can I append one additonal uniform to mainPass programs? (It would be depth texture of the whole scene from camera perspective). I'm talking about this function createSceneRenderPass(target) that creates a copy of the same scene render pass that XML3D uses internally to draw the scene. The thing I would like to do is to attach one additional texture (depth map from previous pass). Would it be possible/?

Objects Sorting Do I need to sort objects manually - as it is done in pick-objects? Depth test wouldn't be enough for the case transparency can be ignored? Let me explain what I mean.

for (var j = 0, n = sortedObjects.zLayers.length; j < n; j++) {
            var zLayer = sortedObjects.zLayers[j];
            gl.clear(gl.DEPTH_BUFFER_BIT);
            if (sortedObjects.opaque[zLayer]) {
                for (var prg in sortedObjects.opaque[zLayer]) {
                    this.renderObjects(sortedObjects.opaque[zLayer][prg], program, viewMatrix, projMatrix);
                }
            }
            if (sortedObjects.transparent[zLayer]) {
                this.renderObjects(sortedObjects.transparent[zLayer], program, viewMatrix, projMatrix);
            }
        }

Couldn't this be changed for: this.renderObjects(objects, program, viewMatrix, projMatrix); Just for rendering depth map (no transparency or so).

Whereas renderObjects has following form (compare with the one from pickPass):

renderObjects: (function () {
        var c_mvp = mat4.create(), c_uniformCollection = {
            envBase: {},
            envOverride: null,
            sysBase: {}
        }, c_systemUniformNames = [ "modelViewProjectionMatrix"];

        return function (objects, program, viewMatrix, projMatrix) {
            for (var i=0; i < objects.length; i++) {
                var obj = objects[i];
                var mesh = obj.mesh;

                if (!obj.visible)
                    continue;

                if (viewMatrix && projMatrix) {
                    obj.updateModelViewMatrix(viewMatrix);
                    obj.updateModelViewProjectionMatrix(projMatrix);
                }
                obj.getModelViewProjectionMatrix(c_mvp);   
                c_uniformCollection.sysBase["modelViewProjectionMatrix"] = c_mvp;

                program.setUniformVariables(null, c_systemUniformNames, c_uniformCollection);
                mesh.draw(program);
            }
        };
    }()),
csvurt commented 8 years ago

The render pass has access to the active camera through scene.getActiveView() and you can pull the view and projection matrices out of there. This is done in the ForwardRenderPass (forward.js). The ones passed directly to the render function are only used for picking passes triggered by getElementByRay, since they use a temporary camera created from the origin and direction of the ray. That's why you can ignore them, but you still need to set the uniform variables with the activeView's matrices.

Check out vertexattribute-pass.js, that's really the most bare bones way of rendering all the objects in the scene with a different shader program. It uses the SceneRenderPass as a "parent class", uses a custom shader and then calls the SceneRenderPass's renderObjectsToActiveBuffer function to render all the scene objects with that custom shader. If you need to set a uniform (like a depth texture) you would add that to the scene.systemUniforms object before calling renderObjectsToActiveBuffer like this:

scene.systemUniforms["depthTexture"] = [this.inputs.depthBuffer.colorTarget.handle];

this.renderObjectsToActiveBuffer(scene.ready, scene, target, scene.systemUniforms, [], {
            transparent: false,
            program: this._program
        });

The only catch here is that SceneRenderPass is not exposed outside of xml3d.js, so if you want to create your own render pass using it as a parent class you'll have to add it to the list of things that get exposed in src/global.js and build your own copy of xml3d.js:

XML3D.webgl = {};
XML3D.webgl.BaseRenderTree = require("./renderer/webgl/render-trees/base.js");
XML3D.webgl.BaseRenderPass = require("./renderer/webgl/render-passes/base.js");
XML3D.webgl.SceneRenderPass = require("./renderer/webgl/render-passes/scene-pass.js");

If you don't need sorting (for example for transparency) then you don't need to sort the objects, especially if you're rendering them all with a custom shader anyway. Just note that if you use the z-index property on any of your objects then you will have to sort them, since that has an impact on the depth buffer too. If you don't need support for z-index or transparency then you can just render all the objects in any order.