First, I knew the following basic knowledges,
- GLSL Language, Learn WebGL2 - GLSL * GLSL Shaders
- OpenGL - Coordinate Systems, [OpenGL - Advanced GLSL]
- 3D Game Shaders For Beginners - GLSL
- Introduction to OpenGL and GLSL - 8.3 Shaders
Then, I can use shader to render 2D graphics on the Shadertoy or GLSL Sandbox Gallery cross-browser online editor through WebGL or with the cross-platform SHADERed IDE. It's easy to use GLSL
to draw 2D objects because 2D coordinates correspond to gl_Position
. But I failed to use ShaderMaterial with the GLSL
code to render a BoxGeometry I created in three.js
. See the following code for my case.
/** @type {HTMLElement} */ var container;
var camera, scene, renderer;
/** @type {THREE.Mesh} */ var mesh;
var uniforms;
/** @type {THREE.TrackballControls} */ var controls;
/** @type {THREE.LineSegments} */ var wireframe;
var clock = new THREE.Clock();
try {
init();
animate();
} catch (err) {
alert(err);
}
function init() {
container = document.getElementById('container');
camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 3000);
camera.position.z = 2.0;
camera.position.y = 1.0;
camera.rotation.x = -0.45;
scene = new THREE.Scene();
//var boxGeometry = new THREE.PlaneGeometry(0.75, 0.75, 1);
var boxGeometry = new THREE.BoxGeometry(0.5, 0.5, 0.5);
uniforms = { u_time: { type: "f", value: 0.0 } };
var material = new THREE.ShaderMaterial({
uniforms: uniforms,
side: THREE.DoubleSide,
transparent: true,
vertexShader: document.getElementById('vertexShader').textContent,
fragmentShader: document.getElementById('fragmentShader').textContent
});
mesh = new THREE.Mesh(boxGeometry, material);
var geo, lineMaterial = new THREE.LineBasicMaterial({ color: 0xffffff, transparent: true, opacity: 0.5 });
// lineMaterial.depthTest = false;
// lineMaterial.polygonOffset = true;
// lineMaterial.depthTest = true;
// lineMaterial.polygonOffsetFactor = -2;
// lineMaterial.polygonOffsetUnits = 0.1;
if (0) {
geo = new THREE.EdgesGeometry(mesh.geometry);
} else {
geo = new THREE.WireframeGeometry(boxGeometry);
}
wireframe = new THREE.LineSegments(geo, lineMaterial);
mesh.add(wireframe); // wireframe.material === lineMaterial
scene.add(mesh);
var axesHelper = new THREE.AxesHelper();
mesh.add(axesHelper);
renderer = new THREE.WebGLRenderer();
renderer.setClearColor(0xffffff, 1);
container.appendChild(renderer.domElement);
onWindowResize();
window.addEventListener('resize', onWindowResize, false);
initControls();
initDatGui();
}
function onWindowResize(event) {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
// requestAnimationFrame(animate);
try {
render();
} catch (err) {
if (confirm("An error occured:\n" + err.message)) {
return;
}
}
requestAnimationFrame(animate);
}
function render() {
if (controls) controls.update();
var delta = clock.getDelta();
uniforms.u_time.value += delta;
mesh.rotation.y += delta * 0.5;
renderer.render(scene, camera);
}
function initControls() {
if (!THREE.TrackballControls) return
controls = new THREE.TrackballControls(camera, renderer.domElement);
// controls.enabled = true; // default is true
// controls.target = new THREE.Vector3(); // default is new Vector3()
// controls.rotateSpeed = 3.0; // default is 1.0
// controls.zoomSpeed = 1.2; // default is 1.2
// controls.panSpeed = 0.3; // default is 0.3
// controls.noRotate = false; // default is false
// controls.noZoom = false; // default is false
// controls.noPan = false; // default is false
// controls.staticMoving = false; // default is false
// controls.dynamicDampingFactor = 0.2; // default is 0.2
// controls.minDistance = 0;
// controls.maxDistance = Infinity;
}
function initDatGui() {
if ('object' !== typeof dat && !dat.GUI) return;
var datGui = new dat.GUI({ closed: true }); // panel is collapsed
if (controls) {
datGui.add(controls, "enabled").name("Enable").onChange(change);
datGui.add(controls, "rotateSpeed", 1.0, 10.0).onChange(change);
datGui.add(controls, "zoomSpeed", 1.0, 5).onChange(change);
datGui.add(controls, "panSpeed", 0.1, 1.0).onChange(change);
datGui.add(controls, "noRotate").name("Rotate").onChange(change);
datGui.add(controls, "noZoom").name("Zoom").onChange(change);
datGui.add(controls, "noPan").name("Pan").onChange(change);
datGui.add(controls, "staticMoving").name("Static Moving").onChange(change);
datGui.add(controls, "dynamicDampingFactor", 0.1, 1.0).name("Dynamic Damping Factor").onChange(change);
datGui.add(controls, "minDistance", 0, 5).name("MinDistance").onChange(change);
datGui.add(controls, "maxDistance", 5, 10).name("MaxDistance").onChange(change);
datGui.add(controls, "reset").name("Reset").onChange(function resetTrackballControls() {
this.object.reset();
});
}
// var gui = { widthSegments: 1 }
datGui.add(mesh.geometry.parameters, "widthSegments", 1, 66, 1).name("Width Segments").onChange(function updateGeometrySegments(newValue) {
wireframe.geometry = mesh.geometry = new THREE.BoxGeometry(0.5, 0.5, 0.5, newValue);
});
datGui.add(wireframe, "visible").name("Show Wireframe");
function change(newValue) {
this.object[this.property] = newValue;
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<meta name="description" content="WebGL Shader in Three.js">
<title>Shader</title>
<meta name='viewport' content='width=device-width, initial-scale=1'>
<style>
a { color: #08f; }
b { color: lightgreen; }
html, body, .bespread { margin:0; padding:0; width:100%; height:100%; }
canvas { border: 1px dotted salmon; }
#info { position:absolute; bottom:0; }
</style>
</head>
<body>
<script>
window.onerror = function (msg, url, line, col, error) {
// Note that col & error are new to the HTML 5 spec and may not be
// supported in every browser. It worked for me in Chrome.
var extra = !col ? '' : '\ncolumn: ' + col;
extra += !error ? '' : '\nerror: ' + error;
// You can view the information in an alert to see things working like this:
alert("Error: " + msg + "\nurl: " + url + "\nline: " + line + extra);
// TODO: Report this error via ajax so you can keep track of what pages have JS issues
var suppressErrorAlert = true;
// If you return true, then error alerts (like in older versions of
// Internet Explorer) will be suppressed.
return suppressErrorAlert;
};
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
varying vec3 v_color;
void main( void ) {
gl_FragColor = vec4(v_color, 1.);
}
</script>
<script id="vertexShader" type="x-shader/x-vertex">
varying vec3 v_color;
void main()
{
v_color = vec3(position.x < 0., 0, position.x >= 0.);
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_Position = projectionMatrix * mvPosition;
}
</script>
<div id="container"></div>
<div id="info">
<a href="https://threejs.org/examples/#webgl_postprocessing_outline">WebGL Postprocessing Outline</a>
<br>
<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a>- Outline Pass by
<a href="http://eduperiment.com" target="_blank" rel="noopener">Prashant Sharma</a>
and <a href="https://clara.io" target="_blank" rel="noopener">Ben Houston</a>
</div>
</body>
<!-- <script src="https://threejs.org/build/three.min.js"></script> -->
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r121/three.min.js"></script> -->
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/TrackballControls.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/[email protected]/build/dat.gui.min.js"></script>
<!-- <script type="module" src="index.js"></script> -->
</html>
I guessed that it was because it could distinguish between vertexes in the same middle position of the +x
sides and -x
sides for v_color = vec3(position.x < 0.25, 0, position.x >= 0.25);
.
Finally, I also knew that I can Split the geometry into groups with different materials, but I just want to use one ShaderMaterial
to do this.
var geo = new THREE.BoxGeometry(48, 48, 48, 2);
var magnetMaterial = ['red', 'blue', 'green', 'purple', 'cyan', 'white', 'black', 'pink', 'orange', 'gray'].map(it =>
new THREE.MeshPhongMaterial({
color: it, side: THREE.DoubleSide,
polygonOffset: true, polygonOffsetFactor: 1
}));
//Initializes the magnetic orientation to +x, red on the left side and blue on the right side.
//faces[].materialIndex: -x,-x, +x,+x, +z,+z,+z,+z, -y,-y,-y,-y, -z,-z,-z,-z, +y,+y,+y,+y
// var faceColors = [1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0];
// console.log('faceColors', geo.faces.map((it, idx) => it.materialIndex = faceColors[idx])); // for Three.js 1.20
// let's regroup materialIndex of five faces of the two ends of the Cuboid (x+,x-,y+,y-,z+,z-)
console.log(geo.getIndex().count, geo.groups.slice(0));
geo.clearGroups();
[0, 1, 1, 0, 1, 0, 1, 0, 0, 1].forEach((val, idx) => geo.addGroup(6 * idx, 6, val));
Anyone could help me to explain and solve this problem because the gradient is not what I want?