Last active
July 29, 2024 01:23
-
-
Save duhaime/c8375f1c313587ac629e04e0253481f9 to your computer and use it in GitHub Desktop.
Three.js: Indexed Buffer Geometry with Texture and Custom Shader Attributes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> | |
<style> | |
html, body { width: 100%; height: 100%; background: #000; } | |
body { margin: 0; overflow: hidden; } | |
canvas { width: 100%; height: 100%; } | |
</style> | |
</head> | |
<body> | |
<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/92/three.min.js'></script> | |
<script src='TrackballControls.js'></script> | |
<script type='x-shader/x-vertex' id='vertex-shader'> | |
/** | |
* The vertex shader's main() function must define `gl_Position`, | |
* which describes the position of each vertex in the space. | |
* | |
* To do so, we can use the following variables defined by Three.js: | |
* | |
* uniform mat4 modelViewMatrix - combines: | |
* model matrix: maps a point's local coordinate space into world space | |
* view matrix: maps world space into camera space | |
* | |
* uniform mat4 projectionMatrix - maps camera space into screen space | |
* | |
* attribute vec3 position - sets the position of each vertex | |
* | |
* attribute vec2 uv - determines the relationship between vertices and textures | |
* | |
* `uniforms` are constant across all vertices | |
* | |
* `attributes` can vary from vertex to vertex and are defined as arrays | |
* with length equal to the number of vertices. Each index in the array | |
* is an attribute for the corresponding vertex | |
* | |
* `varyings` are values passed from the vertex to the fragment shader | |
**/ | |
varying vec2 vUv; // pass the uv coordinates of each pixel to the frag shader | |
void main() { | |
vUv = uv; | |
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); | |
} | |
</script> | |
<script type='x-shader/x-fragment' id='fragment-shader'> | |
/** | |
* The fragment shader's main() function must define `gl_FragColor`, | |
* which describes the pixel color of each pixel on the screen. | |
* | |
* To do so, we can use uniforms passed into the shader and varyings | |
* passed from the vertex shader | |
**/ | |
precision highp float; // set float precision (optional) | |
uniform sampler2D texture; // identify the texture as a uniform argument | |
varying vec2 vUv; // identify the uv values as a varying attribute | |
void main() { | |
gl_FragColor = texture2D(texture, vUv); | |
} | |
</script> | |
<script> | |
/** | |
* Generate a scene object with a background color | |
**/ | |
function getScene() { | |
var scene = new THREE.Scene(); | |
scene.background = new THREE.Color(0xffffff); | |
return scene; | |
} | |
/** | |
* Generate the camera to be used in the scene. Camera args: | |
* [0] field of view: identifies the portion of the scene | |
* visible at any time (in degrees) | |
* [1] aspect ratio: identifies the aspect ratio of the | |
* scene in width/height | |
* [2] near clipping plane: objects closer than the near | |
* clipping plane are culled from the scene | |
* [3] far clipping plane: objects farther than the far | |
* clipping plane are culled from the scene | |
**/ | |
function getCamera() { | |
var aspectRatio = window.innerWidth / window.innerHeight; | |
var camera = new THREE.PerspectiveCamera(75, aspectRatio, 0.1, 1000); | |
camera.position.set(0, 1, 10); | |
return camera; | |
} | |
/** | |
* Generate the renderer to be used in the scene | |
**/ | |
function getRenderer() { | |
// Create the canvas with a renderer | |
var renderer = new THREE.WebGLRenderer({antialias: true}); | |
// Add support for retina displays | |
renderer.setPixelRatio(window.devicePixelRatio); | |
// Specify the size of the canvas | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
// Add the canvas to the DOM | |
document.body.appendChild(renderer.domElement); | |
return renderer; | |
} | |
/** | |
* Generate the controls to be used in the scene | |
* @param {obj} camera: the three.js camera for the scene | |
* @param {obj} renderer: the three.js renderer for the scene | |
**/ | |
function getControls(camera, renderer) { | |
var controls = new THREE.TrackballControls(camera, renderer.domElement); | |
controls.zoomSpeed = 0.4; | |
controls.panSpeed = 0.4; | |
return controls; | |
} | |
/** | |
* Load image | |
**/ | |
function loadImage() { | |
var geometry = new THREE.BufferGeometry(); | |
/* | |
Now we need to push some vertices into that geometry to identify the coordinates the geometry should cover | |
*/ | |
// Identify the image size | |
var imageSize = {width: 10, height: 7.5}; | |
// Identify the x, y, z coords where the image should be placed | |
var coords = {x: -5, y: -3.75, z: 0}; | |
// Add one vertex for each corner of the image, using the | |
// following order: lower left, lower right, upper right, upper left | |
var vertices = new Float32Array([ | |
coords.x, coords.y, coords.z, // bottom left | |
coords.x+imageSize.width, coords.y, coords.z, // bottom right | |
coords.x+imageSize.width, coords.y+imageSize.height, coords.z, // upper right | |
coords.x, coords.y+imageSize.height, coords.z, // upper left | |
]) | |
// set the uvs for this box; these identify the following corners: | |
// lower-left, lower-right, upper-right, upper-left | |
var uvs = new Float32Array([ | |
0.0, 0.0, | |
1.0, 0.0, | |
1.0, 1.0, | |
0.0, 1.0, | |
]) | |
// indices = sequence of index positions in `vertices` to use as vertices | |
// we make two triangles but only use 4 distinct vertices in the object | |
// the second argument to THREE.BufferAttribute is the number of elements | |
// in the first argument per vertex | |
geometry.setIndex([0,1,2, 2,3,0]) | |
geometry.addAttribute('position', new THREE.BufferAttribute( vertices, 3 )); | |
geometry.addAttribute('uv', new THREE.BufferAttribute( uvs, 2) ) | |
// Create a texture loader so we can load our image file | |
var loader = new THREE.TextureLoader(); | |
// specify the url to the texture | |
var url = 'https://s3.amazonaws.com/duhaime/blog/tsne-webgl/assets/cat.jpg'; | |
// specify custom uniforms and attributes for shaders | |
// Uniform types: https://github.com/mrdoob/three.js/wiki/Uniforms-types | |
var material = new THREE.ShaderMaterial({ | |
uniforms: { | |
texture: { | |
type: 't', | |
value: loader.load(url) | |
}, | |
}, | |
vertexShader: document.getElementById('vertex-shader').textContent, | |
fragmentShader: document.getElementById('fragment-shader').textContent | |
}); | |
// 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,0) | |
// Add the image to the scene | |
scene.add(mesh); | |
} | |
/** | |
* Render! | |
**/ | |
function render() { | |
requestAnimationFrame(render); | |
renderer.render(scene, camera); | |
controls.update(); | |
}; | |
var scene = getScene(); | |
var camera = getCamera(); | |
var renderer = getRenderer(); | |
var controls = getControls(camera, renderer); | |
loadImage(); | |
render(); | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
THREE.TrackballControls=function(e,t){var o=this,n={NONE:-1,ROTATE:0,ZOOM:1,PAN:2,TOUCH_ROTATE:3,TOUCH_ZOOM_PAN:4};this.object=e,this.domElement=void 0!==t?t:document,this.enabled=!0,this.screen={left:0,top:0,width:0,height:0},this.rotateSpeed=1,this.zoomSpeed=1.2,this.panSpeed=.3,this.noRotate=!1,this.noZoom=!1,this.noPan=!1,this.staticMoving=!1,this.dynamicDampingFactor=.2,this.minDistance=0,this.maxDistance=1/0,this.keys=[65,83,68],this.target=new THREE.Vector3;var s=new THREE.Vector3,c=n.NONE,a=n.NONE,i=new THREE.Vector3,r=new THREE.Vector2,p=new THREE.Vector2,h=new THREE.Vector3,d=0,u=new THREE.Vector2,E=new THREE.Vector2,m=0,l=0,g=new THREE.Vector2,y=new THREE.Vector2;this.target0=this.target.clone(),this.position0=this.object.position.clone(),this.up0=this.object.up.clone();var v={type:"change"},w={type:"start"},T={type:"end"};this.handleResize=function(){if(this.domElement===document)this.screen.left=0,this.screen.top=0,this.screen.width=window.innerWidth,this.screen.height=window.innerHeight;else{var e=this.domElement.getBoundingClientRect(),t=this.domElement.ownerDocument.documentElement;this.screen.left=e.left+window.pageXOffset-t.clientLeft,this.screen.top=e.top+window.pageYOffset-t.clientTop,this.screen.width=e.width,this.screen.height=e.height}};var b,f,O,R,H,L,V,k,N,j,D,A,Y=(b=new THREE.Vector2,function(e,t){return b.set((e-o.screen.left)/o.screen.width,(t-o.screen.top)/o.screen.height),b}),C=(f=new THREE.Vector2,function(e,t){return f.set((e-.5*o.screen.width-o.screen.left)/(.5*o.screen.width),(o.screen.height+2*(o.screen.top-t))/o.screen.width),f});function P(e){!1!==o.enabled&&(window.removeEventListener("keydown",P),a=c,c===n.NONE&&(e.keyCode!==o.keys[n.ROTATE]||o.noRotate?e.keyCode!==o.keys[n.ZOOM]||o.noZoom?e.keyCode!==o.keys[n.PAN]||o.noPan||(c=n.PAN):c=n.ZOOM:c=n.ROTATE))}function X(e){!1!==o.enabled&&(c=a,window.addEventListener("keydown",P,!1))}function M(e){!1!==o.enabled&&(e.preventDefault(),e.stopPropagation(),c===n.NONE&&(c=e.button),c!==n.ROTATE||o.noRotate?c!==n.ZOOM||o.noZoom?c!==n.PAN||o.noPan||(g.copy(Y(e.pageX,e.pageY)),y.copy(g)):(u.copy(Y(e.pageX,e.pageY)),E.copy(u)):(p.copy(C(e.pageX,e.pageY)),r.copy(p)),document.addEventListener("mousemove",Z,!1),document.addEventListener("mouseup",S,!1),o.dispatchEvent(w))}function Z(e){!1!==o.enabled&&(e.preventDefault(),e.stopPropagation(),c!==n.ROTATE||o.noRotate?c!==n.ZOOM||o.noZoom?c!==n.PAN||o.noPan||y.copy(Y(e.pageX,e.pageY)):E.copy(Y(e.pageX,e.pageY)):(r.copy(p),p.copy(C(e.pageX,e.pageY))))}function S(e){!1!==o.enabled&&(e.preventDefault(),e.stopPropagation(),c=n.NONE,document.removeEventListener("mousemove",Z),document.removeEventListener("mouseup",S),o.dispatchEvent(T))}function x(e){if(!1!==o.enabled&&!0!==o.noZoom){switch(e.preventDefault(),e.stopPropagation(),e.deltaMode){case 2:u.y-=.025*e.deltaY;break;case 1:u.y-=.01*e.deltaY;break;default:u.y-=25e-5*e.deltaY}o.dispatchEvent(w),o.dispatchEvent(T)}}function z(e){if(!1!==o.enabled){switch(e.preventDefault(),e.touches.length){case 1:c=n.TOUCH_ROTATE,p.copy(C(e.touches[0].pageX,e.touches[0].pageY)),r.copy(p);break;default:c=n.TOUCH_ZOOM_PAN;var t=e.touches[0].pageX-e.touches[1].pageX,s=e.touches[0].pageY-e.touches[1].pageY;l=m=Math.sqrt(t*t+s*s);var a=(e.touches[0].pageX+e.touches[1].pageX)/2,i=(e.touches[0].pageY+e.touches[1].pageY)/2;g.copy(Y(a,i)),y.copy(g)}o.dispatchEvent(w)}}function _(e){if(!1!==o.enabled)switch(e.preventDefault(),e.stopPropagation(),e.touches.length){case 1:r.copy(p),p.copy(C(e.touches[0].pageX,e.touches[0].pageY));break;default:var t=e.touches[0].pageX-e.touches[1].pageX,n=e.touches[0].pageY-e.touches[1].pageY;l=Math.sqrt(t*t+n*n);var s=(e.touches[0].pageX+e.touches[1].pageX)/2,c=(e.touches[0].pageY+e.touches[1].pageY)/2;y.copy(Y(s,c))}}function q(e){if(!1!==o.enabled){switch(e.touches.length){case 0:c=n.NONE;break;case 1:c=n.TOUCH_ROTATE,p.copy(C(e.touches[0].pageX,e.touches[0].pageY)),r.copy(p)}o.dispatchEvent(T)}}function F(e){!1!==o.enabled&&e.preventDefault()}this.rotateCamera=(R=new THREE.Vector3,H=new THREE.Quaternion,L=new THREE.Vector3,V=new THREE.Vector3,k=new THREE.Vector3,N=new THREE.Vector3,function(){N.set(p.x-r.x,p.y-r.y,0),(O=N.length())?(i.copy(o.object.position).sub(o.target),L.copy(i).normalize(),V.copy(o.object.up).normalize(),k.crossVectors(V,L).normalize(),V.setLength(p.y-r.y),k.setLength(p.x-r.x),N.copy(V.add(k)),R.crossVectors(N,i).normalize(),O*=o.rotateSpeed,H.setFromAxisAngle(R,O),i.applyQuaternion(H),o.object.up.applyQuaternion(H),h.copy(R),d=O):!o.staticMoving&&d&&(d*=Math.sqrt(1-o.dynamicDampingFactor),i.copy(o.object.position).sub(o.target),H.setFromAxisAngle(h,d),i.applyQuaternion(H),o.object.up.applyQuaternion(H)),r.copy(p)}),this.zoomCamera=function(){var e;c===n.TOUCH_ZOOM_PAN?(e=m/l,m=l,i.multiplyScalar(e)):(1!==(e=1+(E.y-u.y)*o.zoomSpeed)&&e>0&&i.multiplyScalar(e),o.staticMoving?u.copy(E):u.y+=(E.y-u.y)*this.dynamicDampingFactor)},this.panCamera=(j=new THREE.Vector2,D=new THREE.Vector3,A=new THREE.Vector3,function(){j.copy(y).sub(g),j.lengthSq()&&(j.multiplyScalar(i.length()*o.panSpeed),A.copy(i).cross(o.object.up).setLength(j.x),A.add(D.copy(o.object.up).setLength(j.y)),o.object.position.add(A),o.target.add(A),o.staticMoving?g.copy(y):g.add(j.subVectors(y,g).multiplyScalar(o.dynamicDampingFactor)))}),this.checkDistances=function(){o.noZoom&&o.noPan||(i.lengthSq()>o.maxDistance*o.maxDistance&&(o.object.position.addVectors(o.target,i.setLength(o.maxDistance)),u.copy(E)),i.lengthSq()<o.minDistance*o.minDistance&&(o.object.position.addVectors(o.target,i.setLength(o.minDistance)),u.copy(E)))},this.update=function(){i.subVectors(o.object.position,o.target),o.noRotate||o.rotateCamera(),o.noZoom||o.zoomCamera(),o.noPan||o.panCamera(),o.object.position.addVectors(o.target,i),o.checkDistances(),o.object.lookAt(o.target),s.distanceToSquared(o.object.position)>1e-6&&(o.dispatchEvent(v),s.copy(o.object.position))},this.reset=function(){c=n.NONE,a=n.NONE,o.target.copy(o.target0),o.object.position.copy(o.position0),o.object.up.copy(o.up0),i.subVectors(o.object.position,o.target),o.object.lookAt(o.target),o.dispatchEvent(v),s.copy(o.object.position)},this.dispose=function(){this.domElement.removeEventListener("contextmenu",F,!1),this.domElement.removeEventListener("mousedown",M,!1),this.domElement.removeEventListener("wheel",x,!1),this.domElement.removeEventListener("touchstart",z,!1),this.domElement.removeEventListener("touchend",q,!1),this.domElement.removeEventListener("touchmove",_,!1),document.removeEventListener("mousemove",Z,!1),document.removeEventListener("mouseup",S,!1),window.removeEventListener("keydown",P,!1),window.removeEventListener("keyup",X,!1)},this.domElement.addEventListener("contextmenu",F,!1),this.domElement.addEventListener("mousedown",M,!1),this.domElement.addEventListener("wheel",x,!1),this.domElement.addEventListener("touchstart",z,!1),this.domElement.addEventListener("touchend",q,!1),this.domElement.addEventListener("touchmove",_,!1),window.addEventListener("keydown",P,!1),window.addEventListener("keyup",X,!1),this.handleResize(),this.update()},THREE.TrackballControls.prototype=Object.create(THREE.EventDispatcher.prototype),THREE.TrackballControls.prototype.constructor=THREE.TrackballControls; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment