Trouble rendering a 2D SVG in Three.js & WebGL - javascript

I have a ThreeJS Scene (below, or here on CodePen) with some objects in it - one is a Mesh object of a cat, one is a cube, and now, I'm trying to render a 2D SVG illustration I made. I want to put the SVG illustration in the scene in between the cat image and the cube, displayed the same way the cat image appears (upright, and 2D).
It has taken me days to figure out how to render my own SVG, I find the documentation and examples on ThreeJs.org for SVGRenderer and SVGLoader extremely cumbersome and hard to pick apply to my own image (I'm a novice). The closest I've come to rendering my SVG is using the code from this SO thread that uses a LegacySVG Loader. Problem is, I'm completely lost on how to render this code onto a canvas versus a DOM, and it appears this LegacySVG Loader was a solution to a bug which makes it extremely hard to find resources.
So, essentially, I have rendered an SVG in an individual CodePen using the above resources and now I am lost on how to render it onto the same scene as my cube and cat image. Is it possible to use LegacySVG to render onto a canvas? Or, is there a simpler way to get my SVG onto the same canvas as the other objects?
let renderer;
let camera;
//let controls;
let scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(54, window.innerWidth / window.innerHeight, 0.1, 1000);
renderer = new THREE.WebGLRenderer({
antialias: true,
canvas: document.getElementById("viewport")
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(new THREE.Color(0xfefefe));
// document.body.appendChild(renderer.domElement);
camera.position.x = 1;
camera.position.y = 1;
camera.position.z = 15;
let light = new THREE.AmbientLight(0xFFFFFF);
scene.add(light);
let gridHelper = new THREE.GridHelper(10, 10);
scene.add(gridHelper);
// example code
const geometry1 = new THREE.BoxGeometry(1, 1, 1);
const material1 = new THREE.MeshStandardMaterial({
color: 0xff0000
});
const topBox = new THREE.Mesh(geometry1, material1);
scene.add(topBox);
var loader = new THREE.TextureLoader();
// Load an image file into a custom material
var material = new THREE.MeshLambertMaterial({
map: loader.load('https://images.unsplash.com/photo-1518791841217-8f162f1e1131?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2850&q=80')
});
// create a plane geometry for the image with a width of 10
// and a height that preserves the image's aspect ratio
var geometry = new THREE.PlaneGeometry(2, 1.5);
// combine our image geometry and material into a mesh
var mesh = new THREE.Mesh(geometry, material);
// set the position of the image mesh in the x,y,z dimensions
mesh.position.set(0,0,5);
// add the image to the scene
scene.add(mesh);
let animate = function() {
requestAnimationFrame(animate);
//controls.update();
renderer.render(scene, camera);
};
//////////////////
animate();
function updateCamera(ev) {
camera.position.z = 15 - window.scrollY / 250.0;
}
window.addEventListener("scroll", updateCamera);
body {
overflow-x: hidden;
overflow-y: scroll;
padding: 0;
margin: 0;
}
canvas {
position: fixed;
height: 100vh;
}
#threeD {
position: fixed;
margin: 0;
padding: 0;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
.page-wrapper {
padding: 0px;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 4000vh;
}
#container {
height: 500vh;
position: fixed;
}
<html>
<script src="https://raw.githubusercontent.com/mrdoob/three.js/master/src/loaders/LoadingManager.js"></script>
<script src="https://unpkg.com/three#0.102.1/build/three.min.js"></script>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://cdn.rawgit.com/mrdoob/three.js/r68/examples/js/loaders/SVGLoader.js"></script>
<script src="https://raw.githubusercontent.com/mrdoob/three.js/master/examples/js/renderers/SVGRenderer.js"></script>
<link rel="stylesheet" type="text/css" href="index1.css" />
<body>
<canvas id="viewport"></canvas>
<div class="page-wrapper" >
<h1> scroll! </h1>
</div>
</body>
<script src="index1.js"></script>
</html>

There are a few things you need to keep in mind.
SVGRenderer does not render the same things as WebGLRenderer.
a. SVGRenderer takes items inside an <svg> element and applies transformations to its internal <path>, <circle>, <rect>, etc, elements. You can see the svg_sandbox example. All SVG elements are 2D, but can give the impression of being 3D when rotated.
b. WebGLRenderer draws onto a <canvas> element, and can render all kinds of true 3D geometry. If you want to draw an SVG in WebGL, you'll need to first convert the SVG file into a geometry that WebGL can understand by using THREE.SVGLoader. You can see how that's done in the webgl_loader_svg example, (the source code is available by clicking on the <> button on the bottom right).
You cannot have <svg> elements co-existing in the same 3D space as WebGL elements in the <canvas>. If you want to add cubes and planes with cat images to the same space, I recommend you use the WebGLRenderer approach.
I noticed in your code snippet that you're using files from many different sources, and all kinds of Three.js revisions. Some files are r102.1, some are r68, and some are the latest, which is r113. You should stick to one revision to avoid conflicts when trying to get older files to work with newer ones. For example:
https://raw.githubusercontent.com/mrdoob/three.js/r113/build/three.min.js
https://raw.githubusercontent.com/mrdoob/three.js/r113/examples/js/loaders/SVGLoader.js

Related

Pixi.js renderer from p5.js canvas

I've been trying to write a script in Pixi that uses the canvas from a p5.js program as the entire "view" to apply a displacement filter on. I've already achieved this with a single image added as a sprite (see below), but I can't figure out how to interface with the output of p5.js and use it as a view with Pixi's autoDetectRenderer(). I've used p5's .parent() function to attach the canvas to a specific element but that doesn't seem to help. Ideally this would all end up existing in my #main-container div.
The next task would be to make sure this feed is coming in live, so animating elements from the p5.js program are constantly fed into Pixi and filtered.
Any help/pointers would be greatly appreciated!
HTML:
<!DOCTYPE html>
<html>
<head>
<title>pixi.js + p5.js displacement filter</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<script type="text/javascript" src="https://cdn.rawgit.com/GoodBoyDigital/pixi.js/v1.6.1/bin/pixi.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.15/p5.min.js"></script>
<style>
#main-container {
position: relative;
width: 300px;
height: 300px;
border: 1px solid red;
}
</style>
</head>
<body>
<div id="main-container"></div>
<script type="text/javascript" src="js/program.js"></script>
</body>
</html>
program.js:
// p5.js program
var theCanvas, width, height;
function setup() {
width = document.getElementById('main-container').offsetWidth;
height = document.getElementById('main-container').offsetHeight;
theCanvas = createCanvas(width, height);
rectMode(CENTER);
}
function draw() {
background(0, 0, 255);
translate(width/2, height/2);
rotate(frameCount*0.01);
fill(0, 255, 0);
rect(0, 0, 100, 100);
}
// -_-_-_-_-_-_-_-_-_-_-_-_
// pixi.js
// Renderer
var renderer = PIXI.autoDetectRenderer(width, height);
document.body.appendChild(renderer.view);
// Stage
var stage = new PIXI.Stage(0xd92256);
// Container
var container = new PIXI.DisplayObjectContainer();
stage.addChild(container);
// Background
var bg = PIXI.Sprite.fromImage("https://i.imgur.com/3q3kNGh.png?1");
container.addChild(bg);
// Filter
var displacementTexture = PIXI.Texture.fromImage("http://i.imgur.com/2yYayZk.png");
var displacementFilter = new PIXI.DisplacementFilter(displacementTexture);
// Apply it
container.filters = [displacementFilter];
// Animate
requestAnimFrame(animate);
function animate() {
var offset = 1;
displacementFilter.offset.x += offset;
displacementFilter.offset.y += offset;
renderer.render(stage);
requestAnimFrame(animate);
}
Thank you!
I think the best best thing to do would be to take different approach to the problem, trying to connect P5 and Pixi is a lot work. I have tried using both libraries before and it went off the rails fast. What you are trying to do can be done with P5 or Pixi alone. The P5 only approach is what I know best so I will walk you though it.
The way that Pixi makes it filters is with webGL shaders, they are small programs the run on the GPU to manipulate images. They are written in a C like language called glsl. P5 has support for webGL shaders (filters) and so, we write our own displacement shader. I am not going to get into the glsl part here but I have made a demo with lots of comments here.
The first part of a shader is loading in the glsl code. Always do this in preload. As an alternative you can use with createShader and grave strings.
let displacementShader;
function preload() {
displacementShader = loadShader("displacement.vert", "displacement.frag");
}
Next you create a WEBGL mode canvas, this is not like a normal canvas and is for 3d graphics and shaders. You still need somewhere for your 2d graphics so make a buffer to draw 2d graphics too.
let buffer;
function setup(){
createCanvas(windowWidth, windowHeight, WEBGL);
buffer = createGraphics(windowWidth, windowHeight);
}
Now that everything is set up, all you need to do is run the shader.
function draw(){
buffer.circle(100, 100, 50, 50) // draw stuff to the buffer
shader(displacementShader);
// pass variables into the shader, it will need to buffer to distort it
displacementShader.setUniform("buffer", buffer);
rect(0, 0, width, height); // some geometry for the shader to draw on too
}
If you want to look at some examples of shader other that my demo there is a lovely Github repo for that. In my demo I also

2D Canvas (behind a 3D Canvas) not drawing images

I'm currently exploring with Three.js, and working on a small project of mine.
The project consists on having a canvas focused on 3D models and animations, and another one behind, which handles the simpler 2D work.
I've set up the 3D canvas properly, so it's background is transparent, and I can see boxes I draw manually on the 2D canvas, which leads me to assume the setup is right.
The issue I'm having is when it comes to images. I simply cannot get an image to display on the 2D canvas. I've experimented on a separate project, and could draw Images there, no problem. The code is pretty basic, and I actually found it here, but is as follows:
window.onload = function() {
var canvas = document.getElementById('bgcanvas');
var context = canvas.getContext('2d');
var logoText = new Image();
logoText.onload = function() {
context.drawImage(logoText, 69, 50);
};
logoText.src = 'images/logotext.png';
}
#canvas {
position: fixed;
z-index: 0;
}
#bgcanvas {
z-index: -10;
position: fixed;
width: 100vw;
height: 100vh;
}
<div id="fixedContainer">
<canvas id="bgcanvas"></canvas>
<canvas id="canvas"></canvas>
</div>
What's going on that I'm unaware of?
Massive thanks in advance!
UPDATE EDIT: The issue was that I had an image on which the top left corner was transparent, and didn't know the image would stretch. user3412847's comment helped me figure it out
Specifying image width and height is a good habit to get into. Use this syntax: context.drawImage(image, x, y, width, height).
Hope this helps.
I'm guessing you don't have an image at that path; It works fine for me with a valid image (eg: http://lorempixel.com/100/100):
window.onload = function() {
var canvas = document.getElementById('bgcanvas');
var context = canvas.getContext('2d');
var logoText = new Image();
logoText.onload = function() {
context.drawImage(logoText, 69, 50);
};
logoText.src = 'http://lorempixel.com/100/100';
}
#canvas {
position: fixed;
z-index: 0;
}
#bgcanvas {
z-index: -10;
position: fixed;
width: 100vw;
height: 100vh;
}
<div id="fixedContainer">
<canvas id="bgcanvas"></canvas>
<canvas id="canvas"></canvas>
</div>

How do I get pixi to expand my canvas and zoom in?

I'm making a game that uses pixi and it renders on a canvas that's 640x480 pixels. As you can imagine, this is very small when viewed on a PC. I'd like to accomplish this:
I want to increase the size of the canvas so it fills up the whole screen
I want to zoom in on the content so that it fills up as much as possible without changing its aspect ratio
I'd like to center the canvas if there's left over space from the previous step
When I google for how to do this in pixi, I can find each of these individually. But I'd like to have the information on how to do this all in one place and on stackoverflow, because you usually want to do all of these things together.
I modified the source code in this example made by the creator: http://www.goodboydigital.com/pixi-js-tutorial-getting-started/ (source download)
Here's what I came up with:
<!DOCTYPE HTML>
<html>
<head>
<title>pixi.js example 1</title>
<style>
body {
margin: 0;
padding: 0;
background-color: #000000;
}
</style>
<script src="pixi.js"></script>
</head>
<body>
<script>
// create an new instance of a pixi stage
var stage = new PIXI.Stage(0x66FF99);
// create a renderer instance
var renderer = PIXI.autoDetectRenderer(400, 300);
renderer.resize(800, 600);
// add the renderer view element to the DOM
document.body.appendChild(renderer.view);
requestAnimFrame( animate );
// create a texture from an image path
var texture = PIXI.Texture.fromImage("bunny.png");
// create a new Sprite using the texture
var bunny = new PIXI.Sprite(texture);
// center the sprites anchor point
bunny.anchor.x = 0.5;
bunny.anchor.y = 0.5;
// move the sprite t the center of the screen
bunny.position.x = 200;
bunny.position.y = 150;
var container = new PIXI.DisplayObjectContainer();
container.scale.x = 2;
container.scale.y = 2;
container.addChild(bunny);
stage.addChild(container);
function animate() {
requestAnimFrame( animate );
// just for fun, lets rotate mr rabbit a little
bunny.rotation += 0.1;
// render the stage
renderer.render(stage);
}
</script>
</body>
</html>
Now the one thing I didn't do is center it. I see two potential ways to do this. I could use CSS to center the canvas (what I'll probably use), or I could do this in code by adding another outer display object to the stage that centers container.

Pixi.js sprite not loading

<!DOCTYPE HTML>
<html>
<head>
<title>Test</title>
<style>
body {
margin: 0;
padding: 0;
background-color: #000000;
overflow: hidden;
}
</style>
<script src="http://www.goodboydigital.com/pixijs/examples/1/pixi.js"></script>
</head>
<body>
<script>
// create an new instance of a pixi stage
var stage = new PIXI.Stage(0x66FF99);
// create a renderer instance
var renderer = PIXI.autoDetectRenderer(window.innerWidth, window.innerHeight);
// add the renderer view element to the DOM
document.body.appendChild(renderer.view);
requestAnimFrame( animate );
// create a texture from an image path
var texture = PIXI.Texture.fromImage("https://dl.dropboxusercontent.com/s/en13743nxusaozy/player.PNG?dl=1&token_hash=AAFVxLm8fEjk3xxPad-kAZ98LJqLoZpdFy9fQtGrIfXL-A");
// create a new Sprite using the texture
var player = new PIXI.Sprite(texture);
// center the sprites anchor point
player.anchor.x = 0.5;
player.anchor.y = 0.5;
// move the sprite t the center of the screen
player.position.x = 200;
player.position.y = 150;
stage.addChild(player);
function animate() {
requestAnimFrame( animate );
//rotate player
player.rotation += 0.1;
// render the stage
renderer.render(stage);
}
</script>
</body>
</html>
This is my code (from the pixijs example, Loaiding the bunny), for some reason I can't seem to get the sprite to load... Can someone take a look at the code and help?
When I put in the right link (the stage rendering turns black). When I put in the wrong link to the sprite, then the stage renders fine but there is no sprite.
var texture = PIXI.Texture.fromImage("https://dl.dropboxusercontent.com/s....");
With the above code, a cross domain request is created for the Sprite texture to load. This is usually not allowed (as in Dropbox case).
In order to see the sprite you will have to copy the file to the local web server or allow Cross domain requests on the other server (https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS)
//local image instead of cross domain
var texture = PIXI.Texture.fromImage("img/player.PNG");

Three.js: Resize rendering canvas (GPU hungry fragment shader)

I am running resource hungry fragment shader with three.js. I have setup rendering size to 800 * 600 to keep shader running smooth even on lowend cards.
I am setting my rendering canvas like this:
var canvas1 = document.getElementById('canvas1') ;
renderer = new THREE.WebGLRenderer(canvas1);
renderer.setSize( 800, 600 );
renderer.autoClear = false;
document.body.appendChild( renderer.domElement );
On body element i have
<canvas id='canvas1' style=" position: absolute; left: 0; top: 0; z-index: -10; background-color: #000000; "></canvas>
I am then in css header doing width: 100%; height: 100%;
This does not help much, as Three.js creates new canvas of size 800*600 on top of canvas1.
What is best approach to zoom Three.js rendering canvas to match web browsers window size, without touching rendering size?
renderer = new THREE.WebGLRenderer( { canvas: canvas1 } );
Passes canvas1 to WebGLRenderer as rendering target.
Then remove:
document.body.appendChild( renderer.domElement );
as we already have canvas1 element in our dom.
Three.js documentation is little bit tricky sometimes,
{ canvas: },
to pass parameter.

Categories

Resources