// Processing.jsでバンプマッピング BumpMappedBox myBox; void setup() { size(200, 200, P3D); myBox = new BumpMappedBox(100, createTexture(), createHeightMapImage()); } void draw() { background(0xff); camera(); noStroke(); pushMatrix(); translate(.5 * width, .5 * height); float baseAngle = radians(millis() * 0.015); rotateX(baseAngle); rotateZ(baseAngle); rotateY(baseAngle); myBox.render(); popMatrix(); } PImage createTexture() { int step = 0x20; PImage tex = createImage(0x80, 0x80, RGB); tex.loadPixels(); for(int i = 0; i < tex.width; i++) { for(int j = 0; j < tex.height; j++) { tex.set(i, j, (floor(i / step) + floor(j / step)) % 2 == 0 ? color(0xcc) : color(0xff)); } } tex.updatePixels(); return tex; } PImage createHeightMapImage() { int step = 0x20; PImage tex = createImage(0x80, 0x80, RGB); tex.loadPixels(); for(int i = 0; i < tex.pixels.length; i++) { tex.pixels[i] = color(0xff - random()); } for(int i = step; i < tex.width -1; i += step) { for(int j = 0; j < tex.height; j++) { tex.pixels[i + j * tex.width] = 0xff << 0x16; } } for(int i = 0; i < tex.width; i++) { for(int j = step; j < tex.height; j += step) { tex.pixels[i + j * tex.width] = 0xff << 0x16; } } tex.updatePixels(); return tex; } // ---------------------------------------- interface Renderable { void render(); } // ---------------------------------------- abstract class RenderHelper implements Renderable { private final int NUM_TEXTURE_COORDS_DEFAULT = 4; protected final int DIFFUSE_COLOR = 0x7f; protected final PVector LIGHT_DIRECTION = new PVector(0, 0, -1); protected final PVector ZERO_VECTOR = new PVector(0, 0, 0); protected PVector eyePoint = new PVector(.5 * width, .5 * height, .5 * height / tan(PI / 6.0)); protected PVector _origin = new PVector(); protected PVector _normal = new PVector(); protected PImage _originalTexture; protected PVector[] _worldVertices; protected PVector[] _localVertices; protected PVector[] _originalTextureCoords; private PImage _texture; private boolean _isBack = false; public abstract void render(); public void setEyePoint(PVector p) { if(p != null) { eyePoint.set(p); } else { setEyePoint(0, 0, 0); } } public void setEyePoint(float x, float y, float z) { eyePoint.set(x, y, z); } protected void initVertices(int numVertices) { if(numVertices < 1) return; _worldVertices = new PVector[numVertices]; _localVertices = new PVector[numVertices]; for(int i = 0; i < numVertices; i++) { _worldVertices[i] = new PVector(); _localVertices[i] = new PVector(); } } protected void initTextureCoords() { _originalTextureCoords = new PVector[NUM_TEXTURE_COORDS_DEFAULT]; for(int i = 0; i < _originalTextureCoords.length; i++) { _originalTextureCoords[i] = new PVector(i < NUM_TEXTURE_COORDS_DEFAULT / 2 ? 0 : 1, i % (NUM_TEXTURE_COORDS_DEFAULT - 1) == 0 ? 0 : 1); } } protected void initTextureCoords(int numVertices) { _originalTextureCoords = new PVector[numVertices]; for(int i = 0; i < numVertices; i++) { _originalTextureCoords[i] = new PVector(); } } protected PVector[] updateWorldVertices(PVector[] localVertices, PVector[] destWorldVertices) { for(int i = 0; i < destWorldVertices.length; i++) { updateWorldVertex(localVertices[i], destWorldVertices[i]); } return destWorldVertices; } protected PVector[] updateWorldVertices() { return updateWorldVertices(_localVertices, _worldVertices); } protected PVector updateWorldVertex(PVector localVertex, PVector destWorldVertex) { destWorldVertex.set( modelX(localVertex.x, localVertex.y, localVertex.z), modelY(localVertex.x, localVertex.y, localVertex.z), modelZ(localVertex.x, localVertex.y, localVertex.z)); return destWorldVertex; } protected PVector updateWorldVertex(PVector localVertex) { return new PVector ( modelX(localVertex.x, localVertex.y, localVertex.z), modelY(localVertex.x, localVertex.y, localVertex.z), modelZ(localVertex.x, localVertex.y, localVertex.z)); } protected PVector updateOrigin(PVector center) { updateWorldVertex(center, _origin); return _origin; } protected PVector updateOrigin() { return updateOrigin(ZERO_VECTOR); } protected PVector updateNormalVector(PVector v1, PVector v2) { _normal.set(v1.cross(v2)); _normal.normalize(); return _normal; } private PVector _ = new PVector(); protected int getDefaultTintColor() { subVec(_origin, eyePoint, _); _isBack = 0 < _normal.dot(_); float arg = (_isBack ? 1.0 : -1.0) * _normal.dot(LIGHT_DIRECTION); return (int)(arg * (0xff - DIFFUSE_COLOR) + DIFFUSE_COLOR); } protected void subVec(PVector p1, PVector p2, PVector dest) { dest.set(p1.x - p2.x, p1.y - p2.y, p1.z - p2.z); } protected void renderWithoutShade(PVector[] worldVertices, PVector[] textureCoords) { if(!checkArgsBeforeRender(_originalTexture, worldVertices, textureCoords)) return; getDefaultTintColor(); if(_isBack) return; if(!setupRenderTexture()) return; _texture.resize(_texture.width, _texture.height); renderMain(0xff, _texture, worldVertices, textureCoords); } protected void renderWithShade(PVector[] worldVertices, PVector[] textureCoords) { if(!checkArgsBeforeRender(_originalTexture, worldVertices, textureCoords)) return; int tintColor = getDefaultTintColor(); if(_isBack) return; renderMain(tintColor, _originalTexture, worldVertices, textureCoords); } private boolean checkArgsBeforeRender(PImage originalTexture, PVector[] worldVertices, PVector[] textureCoords) { if(_originalTexture == null || worldVertices == null || textureCoords == null) return false; if(textureCoords.length < worldVertices.length) return false; if(_originalTexture.get(0, 0) == 0) return false; return true; } private boolean setupRenderTexture() { // 毎回 createImage しないとうまくレンダリングされない... _texture = createImage(_originalTexture.width, _originalTexture.height, RGB); updateTexture(_originalTexture, _texture); return !(_texture == null || _texture.get(0, 0) == 0); } protected void updateTexture(PImage originalTexture, PImage destUpdatedTexture) { destUpdatedTexture.loadPixels(); for(int i = 0; i < originalTexture.pixels.length; i++) { destUpdatedTexture.pixels[i] = originalTexture.pixels[i]; } destUpdatedTexture.updatePixels(); } private void renderMain(int tintColor, PImage textureImage, PVector[] worldVertices, PVector[] textureCoords) { pushMatrix(); resetMatrix(); camera(); noLights(); beginShape(); textureMode(NORMAL); texture(textureImage); tint(tintColor); for(int i = 0; i < worldVertices.length; i++) { vertex(worldVertices[i].x, worldVertices[i].y, worldVertices[i].z, textureCoords[i].x, textureCoords[i].y); } endShape(CLOSE); popMatrix(); } protected void renderWithShade() { renderWithShade(_worldVertices, _originalTextureCoords); } protected void renderWithoutShade() { renderWithoutShade(_worldVertices, _originalTextureCoords); } } // ---------------------------------------- class BoxWithTexture extends RenderHelper { protected final int NUM_VERTICES = 8; protected final int NUM_PLANES = 6; protected final int NUM_SIDE_PLANES = 4; protected final int NUM_VERTICES_PER_PLANE = 4; protected float _width; protected float _height; protected float _depth; protected PVector[] _centers = new PVector[NUM_PLANES]; protected PVector[][] _surface = new PVector[NUM_PLANES][NUM_VERTICES_PER_PLANE]; public BoxWithTexture(float size, PImage tex) { this(size, size, size, tex); } public BoxWithTexture(float w, float h, float d, PImage tex) { _width = w; _height = h; _depth = d; _originalTexture = tex; setupSurface(); } private void setupSurface() { float halfWidth = .5 * _width; float halfHeight = .5 * _height; float halfDepth = .5 * _depth; initTextureCoords(); initVertices(NUM_VERTICES); for(int i = 0; i < NUM_VERTICES; i++) { int j = i < NUM_VERTICES_PER_PLANE ? i : i - NUM_VERTICES_PER_PLANE; _localVertices[i].set(j % 3 == 0 ? -halfWidth : halfWidth, i < 4 ? -halfHeight : halfHeight, i % 4 < 2 ? -halfDepth : halfDepth); } setupSide(halfWidth, halfDepth); setupTopAndBottom(halfHeight); } private void setupSide(float halfWidth, float halfDepth) { for(int i = 0; i < NUM_SIDE_PLANES; i++) { float _x = halfWidth * (i % 2 == 0 ? 0 : i < 2 ? 1 : -1); float _z = halfDepth * (i % 2 != 0 ? 0 : i < 2 ? -1 : 1); _centers[i] = new PVector(_x, 0, _z); for(int j = 0; j < NUM_VERTICES_PER_PLANE; j++) { int surplus = j % (NUM_VERTICES_PER_PLANE - 1); int index = surplus != 0 ? (i + surplus + (NUM_VERTICES_PER_PLANE - 1)) % NUM_VERTICES_PER_PLANE + NUM_VERTICES_PER_PLANE : (i + (j < NUM_VERTICES_PER_PLANE / 2 ? 0 : 1)) % NUM_VERTICES_PER_PLANE; _surface[i][j] = _worldVertices[index]; } } } private void setupTopAndBottom(float halfHeight) { for(int i = NUM_SIDE_PLANES; i < _centers.length; i++) { float _y = halfHeight * (i == NUM_SIDE_PLANES ? -1 : 1); _centers[i] = new PVector(0, _y, 0); for(int j = 0; j < NUM_VERTICES_PER_PLANE; j++) { int index = i == NUM_SIDE_PLANES ? j : NUM_VERTICES - (j + 1); _surface[i][j] = _worldVertices[index]; } } } public void render() { updateWorldVertices(); for(int i = 0; i < _surface.length; i++) { updateOrigin(_centers[i]); updateNormalVector(PVector.sub(_surface[i][1], _surface[i][0]), PVector.sub(_surface[i][3], _surface[i][0])); renderWithShade(_surface[i], _originalTextureCoords); } } } // ---------------------------------------- class BumpMappedPlane extends RenderHelper { private final int NUM_VERTICES = 4; private float _width; private float _height; private PImage _heightMap; private PVector[][] _normalMap; // 法線マップ public BumpMappedPlane(float w, float h, PImage tex, PImage hMap) { _width = w; _height = h; _originalTexture = tex; _heightMap = hMap; float halfWidth = .5 * _width; float halfHeight = .5 * _height; initTextureCoords(); initVertices(NUM_VERTICES); for(int i = 0; i < NUM_VERTICES; i++) { _localVertices[i].set(i < 2 ? -halfWidth : halfWidth, i % 3 == 0 ? -halfHeight : halfHeight, 0); } } PVector _worldNormal = new PVector(); PImage _dummyTexture; public void render() { if(_originalTexture.get(0, 0) != 0) { if (_normalMap == null) { _normalMap = createNormalMap(_heightMap); } } updateOrigin(); updateWorldVertices(); updateNormalVector(PVector.sub(_worldVertices[1], _worldVertices[0]), PVector.sub(_worldVertices[3], _worldVertices[0])); renderWithoutShade(); } protected PVector zeroVec = new PVector(); protected void updateTexture(PImage originalTexture, PImage destUpdatedTexture) { destUpdatedTexture.loadPixels(); int imgWidth = destUpdatedTexture.width; for(int i = 0; i < _normalMap.length; i++) { for(int j = 0; j < _normalMap[0].length; j++) { updateWorldVertex(_normalMap[i][j], _worldNormal); _worldNormal.sub(_origin); float arg = _worldNormal.dot(LIGHT_DIRECTION); float coef = (arg * (0xff - DIFFUSE_COLOR) / 0xff) + (DIFFUSE_COLOR / 0xff); int originalColor = originalTexture.pixels[j * imgWidth + i]; int b = (int)(coef * (originalColor & 0xff)); int g = (int)(coef * (originalColor >> 0x8 & 0xff)); int r = (int)(coef * (originalColor >> 0x10 & 0xff)); destUpdatedTexture.pixels[j * imgWidth + i] = 0xff << 0x18 | r << 0x10 | g << 0x8 | b; } } destUpdatedTexture.updatePixels(); } private float dU(PImage heightMap, int i, int j) { if(i < 1) return dU(heightMap, 1, j); if(!(i < heightMap.width-1)) return dU(heightMap, heightMap.width - 2, j); return .5 * (getHeight(heightMap, i+1,j) - getHeight(heightMap, i-1,j)); } private float dV(PImage heightMap, int i, int j) { if(j < 1) return dV(heightMap, i, 1); if(!(j < heightMap.height-1)) return dV(heightMap, i, heightMap.height - 2); return .5 * (getHeight(heightMap, i, j+1) - getHeight(heightMap, i,j-1)); } private int getHeight(PImage heightMap, int i, int j) { return 0xff & heightMap.pixels[i + j * heightMap.width]; } private PVector[][] createNormalMap(PImage heightMapImage) { int mapWidth = heightMapImage.width; int mapHeight = heightMapImage.height; PVector[][] normalMap = new PVector[mapWidth][mapHeight]; PVector v1 = new PVector(); PVector v2 = new PVector(); for(int i = 0; i < mapWidth; ++i) { for(int j = 0; j < mapHeight; ++j) { v1.set(1.0, 0, dU(heightMapImage, i, j)); v1.normalize(); v2.set(0, 1.0, dV(heightMapImage, i, j)); v2.normalize(); normalMap[i][j] = v1.cross(v2); normalMap[i][j].normalize(); } } return normalMap; } } // ---------------------------------------- class BumpMappedBox implements Renderable { private BumpMappedPlane[] _surface = new BumpMappedPlane[6]; private float _size; BumpMappedBox(float boxSize, PImage tex, PImage hMap) { _size = boxSize; for(int i = 0; i < 6; i++) { _surface[i] = new BumpMappedPlane( _size, _size, tex, hMap); } } void render() { pushMatrix(); for(int i = 0; i < 4; i++) { rotateY(i * HALF_PI); pushMatrix(); translate(0, 0, -.5 * _size); _surface[i].render(); popMatrix(); } popMatrix(); pushMatrix(); rotateX(HALF_PI); for(int i = 4; i < 6; i++) { rotateX(i * PI); pushMatrix(); translate(0, 0, -.5 * _size); _surface[i].render(); popMatrix(); } popMatrix(); } }
<canvas width="200px" height="200px"></canvas>
body { background-color: #fff; } </style> <script type="text/javascript"> window.addEventListener('load',function() { var scripts = document.body.getElementsByTagName('script'); var canvases = document.body.getElementsByTagName('canvas'); new Processing(canvases[0],scripts[0].text); }, false); // Here prevent javascript in body from throwing error </script> <style>