Customizable 3D Scene Construction with Three.js, Shaders, AI‑Generated Assets, and Advanced Character Controls
This article guides readers through building a highly customized 3D scene using Three.js, GLSL shaders, AI‑generated 2D/3D assets, orthographic cameras, pixelated post‑processing, four‑direction character movement, octree collision detection, and stylized water effects, while providing code snippets and resource links.
Click Follow the "Technical Dry Goods" public account for timely updates!
Prerequisites
Hello! Welcome to this article where we will explore how to highly customize a 3D scene using Three.js , Shader (GLSL) , Cursor rules & MCP Servers , and 2D & 3D custom resource acquisition .
Before starting, ensure you have the following basics:
1. Three.js fundamentals Scene ( Scene ): container for the 3D space Camera ( Camera ): viewpoint for observing the scene Renderer ( Renderer ): draws the 3D scene to the screen Geometry ( Geometry ): defines object shapes Material ( Material ): defines surface appearance Mesh ( Mesh ): combination of geometry and material
Recommended learning resource: Bruno Simon’s threejs-journey course.
2. Shader Programming Basics
GLSL (OpenGL Shading Language) basics: Vertex Shader: processes vertex positions and attributes Fragment Shader: processes pixel colors and effects Custom shaders in Three.js
1. Page Preview
After sending my resume and personal website to an HR contact, I received the following awkward screenshot.
What kind of website makes an HR representative ignore a friendly "Are you still there?" message?
Note: Press F near objects with an exclamation mark for a surprise.
Online preview (requires VPN): island.vercel.app/
Online debug interface (requires VPN): island.vercel.app/#debug
Source code (requires VPN): github.com/hexianWeb/i…
Three.js repost
2. 2D & 3D Resource Acquisition
In the AI era, most resource acquisition is handled by AI.
For simple scenes, resources are divided into 2D and 3D. Below are curated sites for low‑poly 3D models:
market.pmnd.rs/
www.kenney.nl/assets
zsky2000.itch.io/
poly.pizza/
sketchfab.com/search?q=low…
2.1 3D Custom Resource Acquisition & Processing
3D AI generation is maturing. Use Blender together with text‑to‑image or image‑to‑image models to generate simple game assets.
When the scene style and UI are decided, missing assets can be filled using gpt-4o and the awesome-gpt4o-images prompt library.
一个
[subject]
的低多边形
3
D 渲染图,由干净的三角形面构成,具有平坦的
[color1]
和
[color2]
表面。环境是一个风格化的数字沙漠,具有极简的几何形状和环境光遮蔽效果。Import the generated model into an AI 3D generation platform such as Mixuan 3D V2.5 (free 20 runs per day).
The result is a low‑poly horse sculpture ready for use.
2.2 2D Custom Resource Acquisition & Processing
Using awesome-gpt4o-images , generate pixelated UI assets with gpt‑4o . Example prompt for an icon wall:
Style the icon in the second photo in the same pixelated style as the icons in the left image. Give it the same style as the image on the right. Solid black backgroundBackground removal can be done with remove.bg , Photoshop, or Adobe Express.
Website Name
Function
URL
Remove.bg
Simple background removal
www.remove.bg/
Photoshop
Professional editing
Requires local install
Adobe Express
Integrated, full‑featured
www.adobe.com/express/
3. Scene Construction
Scene building requires familiarity with tools such as hyper3D , Mixuan 3D , or AI 3D Generation , as well as basic Blender skills.
3.1 Using blender-mcp for Fast 3D Modeling
blender-mcp adds Poly Haven asset support and integrates with hyper3d and MCP servers, enabling tools like Cursor, Claude Desktop, and Trae to work with the scene.
Key tools include get_scene_info and execute_blender_code , allowing batch placement and adjustment of objects via Cursor .
Additional utilities: generate_hyper3d_model_via_images and generate_hyper3d_model_via_text for AI‑driven model generation.
Use the generated model as a reference or as a placeholder for a building.
3.2 Character Control and Octree
Standard 3D controllers like ecctrl allow free 360° movement, which is unsuitable for classic RPG four‑direction movement. The solution limits input to up/down/left/right and handles keyboard layout differences (e.g., AZERTY vs QWERTY).
window.addEventListener('keydown', (e) => {
switch (e.code) {
case 'ArrowUp':
case 'KeyW':
this.actions.up = true;
break;
case 'ArrowDown':
case 'KeyS':
this.actions.down = true;
break;
case 'ArrowLeft':
case 'KeyA':
this.actions.left = true;
break;
case 'ArrowRight':
case 'KeyD':
this.actions.right = true;
break;
case 'Space':
this.actions.brake = true;
if (e.code === 'Space' && this.playerOnFloor && !this.character.isSitting) {
this.jump();
}
break;
case 'KeyR':
this.actions.reset = true;
break;
case 'KeyZ':
this.toggleSit();
break;
}
});Rotation is normalized to the [-π, π] range to avoid unnecessary half‑turns.
updateCharacterRotation(newDirection) {
if (!newDirection || this.character.currentDirection.equals(newDirection)) return;
this.character.currentDirection = newDirection;
let targetRotation = 0;
if (newDirection.z === -1) targetRotation = 0;
else if (newDirection.z === 1) targetRotation = Math.PI;
else if (newDirection.x === -1) targetRotation = Math.PI / 2;
else if (newDirection.x === 1) targetRotation = -Math.PI / 2;
const currentRotation = this.character.instance.rotation.y;
let deltaRotation = targetRotation - currentRotation;
deltaRotation = ((deltaRotation + Math.PI) % (2 * Math.PI) + 2 * Math.PI) % (2 * Math.PI) - Math.PI;
const newTargetRotation = currentRotation + deltaRotation;
gsap.to(this.character.instance.rotation, { y: newTargetRotation, duration: 0.2, ease: 'power1.out' });
}Movement logic calculates speed, applies gravity, updates velocity, and handles damping.
moveCharacter(deltaTime) {
if (this.character.isSitting) return;
let moveX = 0, moveZ = 0, newDirection = null;
if (!this.playerOnFloor) this.playerVelocity.y -= this.GRAVITY * deltaTime;
const speedDelta = deltaTime * (this.playerOnFloor ? 25 : 8);
if (this.actions.up) { moveZ = -speedDelta; newDirection = new THREE.Vector3(0,0,1); }
else if (this.actions.down) { moveZ = speedDelta; newDirection = new THREE.Vector3(0,0,-1); }
else if (this.actions.left) { moveX = -speedDelta; newDirection = new THREE.Vector3(1,0,0); }
else if (this.actions.right) { moveX = speedDelta; newDirection = new THREE.Vector3(-1,0,0); }
if (moveX !== 0 || moveZ !== 0) {
this.updateCharacterRotation(newDirection);
if (this.playerOnFloor && this.currentAnimation !== this.animations.jump) this.playAnimation('walk');
if (moveX !== 0) this.playerVelocity.x += moveX;
if (moveZ !== 0) this.playerVelocity.z += moveZ;
} else if (this.playerOnFloor) {
if (!this.character.isSitting && this.currentAnimation !== this.animations.jump) this.playAnimation('idle');
}
const damping = Math.exp(-4 * deltaTime) - 1;
this.playerVelocity.addScaledVector(this.playerVelocity, damping);
const deltaPosition = this.playerVelocity.clone().multiplyScalar(deltaTime);
this.playerCollider.translate(deltaPosition);
this.updateAnimationState();
}Collision detection uses an Octree built from a simplified collision mesh and a capsule collider for the player.
import { Octree } from 'three/addons/math/Octree.js';
this.worldOctree = new Octree();
setupCollider() {
if ("collision mesh") {
this.worldOctree = new Octree();
this.worldOctree.fromGraphNode("collision mesh");
}
}
import { Capsule } from 'three/addons/math/Capsule.js';
this.playerCollider = new Capsule(new THREE.Vector3(0,2.35,0), new THREE.Vector3(0,3,0), 0.35);
playerCollisions() {
const result = this.worldOctree.capsuleIntersect(this.playerCollider);
this.playerOnFloor = false;
if (result) {
this.playerOnFloor = result.normal.y > 0;
if (result.depth >= 1e-10) {
this.playerCollider.translate(result.normal.multiplyScalar(result.depth));
}
}
}When a collision occurs, the player is nudged along the collision normal to prevent clipping.
4.3 Stylized Water (Magma & Sea)
Stylized water can be created using a water surface texture, edge foam, and flow animation.
4.3.1 Water Surface Texture
Use a noise texture (e.g., cellular noise) to simulate water. Example GLSL SDF for a circle:
float sdfCircle(vec2 center, float r, vec2 pos) {
return distance(center, pos) - r;
}
void main() {
vec2 uv = gl_FragCoord.xy;
float t = sdfCircle(iResolution * 0.5, iResolution.y * 0.4, uv);
gl_FragColor = vec4(vec3(t), 1.0);
}Negative values render black (inside), positive values render gray/white (outside).
4.3.2 Edge Foam (Hack)
float getEdgeGlow(vec2 uv) {
float distToEdge = min(min(uv.x, 1.0 - uv.x), min(uv.y, 1.0 - uv.y));
return 1.0 - smoothstep(0.0, edgeWidth, distToEdge);
}
void main() {
init();
vec2 uv = gl_FragCoord.xy / iResolution.xy;
float m_dist = 1.0;
for (int i = 0; i < 9; i++) {
if (float(i) >= numPoints) break;
float dist = distance(uv, getPoint(i));
m_dist = min(m_dist, dist);
}
float factor = smoothstep(0.05, 0.3, m_dist);
vec3 waterColor = mix(color1, color2, factor);
float edgeGlow = getEdgeGlow(uv) * edgeIntensity;
vec3 finalColor = mix(waterColor, vec3(1.0), edgeGlow);
gl_FragColor = vec4(finalColor, 1.0);
}4.3.3 Flowmap for Animated Water
A flowmap stores 2D vectors in the red and green channels. In the fragment shader, the flow vector offsets UV coordinates over time.
// Sample flow vector (range [0,1] -> [-1,1])
vec2 flow = texture2D(flowMap, vUv).rg * 2.0 - 1.0;
// Time‑dependent offset
vec2 flowOffset = flow * flowSpeed * iTime;
// Apply offset
vec2 uv = vUv + flowOffset;Three.js also provides a built‑in Water2 class that accepts a flowmap texture.
const waterGeometry = new THREE.PlaneGeometry(10, 10);
const flowMap = textureLoader.load('textures/water/flowmap.png');
water = new Water(waterGeometry, {
scale: 1,
textureWidth: 1024,
textureHeight: 1024,
flowMap: flowMap
});
water.position.y = 1;
water.rotation.x = Math.PI * -0.5;
scene.add(water);5. Personal Reflections
After months of creation, I have gained attention from multiple platforms and even industry idols, but I still feel I am not yet capable of systematically teaching Three.js at an expert level.
I am not a formally trained developer.
My Three.js knowledge still has gaps.
Consistently producing high‑quality output is a huge challenge.
Therefore I may pause regular Three.js article updates, though I will continue sharing projects on GitHub, social media, and occasional deep‑dive articles.
6. Final Thoughts
Technology Future & Front‑End Migration
AI is lowering barriers for 3D tech, and upcoming 3D generation tools will shift front‑end development toward richer interactive experiences.
Vision of This Column
The goal is to share intermediate‑to‑advanced Three.js techniques, helping developers integrate impressive 3D hero sections and promote Web3D adoption.
If you love Three.js but find native development cumbersome, consider trying "Tresjs" or "TvTjs" , Vue‑based Three.js frameworks with active communities and many examples.
Follow more AI programming news at the AI Coding section: https://juejin.cn/aicoding
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.