changeset 5842:f2b2903b65fa

RT-33792: Implement specular lighting as specified for the PhongMaterial Reviewed-by: ckyang, kcr
author vadim
date Thu, 28 Nov 2013 14:47:01 +0400
parents 7bf5df173491
children ee94f7e65ab3
files apps/toys/FX8-3DFeatures/src/fx83dfeatures/Camera3D.java apps/toys/FX8-3DFeatures/src/fx83dfeatures/CameraController.java apps/toys/FX8-3DFeatures/src/fx83dfeatures/SpecularColorTestApp.java apps/toys/FX8-3DFeatures/src/resources/spec.png apps/toys/FX8-3DFeatures/src/resources/spec_color.png build.gradle modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGPhongMaterial.java modules/graphics/src/main/java/com/sun/prism/PhongMaterial.java modules/graphics/src/main/java/com/sun/prism/d3d/D3DContext.java modules/graphics/src/main/java/com/sun/prism/d3d/D3DPhongMaterial.java modules/graphics/src/main/java/com/sun/prism/es2/ES2Context.java modules/graphics/src/main/java/com/sun/prism/es2/ES2PhongMaterial.java modules/graphics/src/main/java/com/sun/prism/es2/ES2PhongShader.java modules/graphics/src/main/java/com/sun/prism/es2/GLContext.java modules/graphics/src/main/java/javafx/scene/paint/PhongMaterial.java modules/graphics/src/main/native-prism-d3d/D3DContext.cc modules/graphics/src/main/native-prism-d3d/D3DMeshView.cc modules/graphics/src/main/native-prism-d3d/D3DPhongMaterial.cc modules/graphics/src/main/native-prism-d3d/D3DPhongMaterial.h modules/graphics/src/main/native-prism-d3d/D3DPhongShader.cc modules/graphics/src/main/native-prism-d3d/D3DPhongShader.h modules/graphics/src/main/native-prism-d3d/D3DPhongShaderGen.cc modules/graphics/src/main/native-prism-d3d/hlsl/Mtl1PS.hlsl modules/graphics/src/main/native-prism-d3d/hlsl/psConstants.h modules/graphics/src/main/native-prism-d3d/hlsl/psMath.h modules/graphics/src/main/native-prism-es2/GLContext.c modules/graphics/src/main/native-prism-es2/PrismES2Defs.h modules/graphics/src/main/resources/com/sun/prism/es2/glsl/main1Light.frag modules/graphics/src/main/resources/com/sun/prism/es2/glsl/main2Lights.frag modules/graphics/src/main/resources/com/sun/prism/es2/glsl/main3Lights.frag modules/graphics/src/main/resources/com/sun/prism/es2/glsl/specular_color.frag modules/graphics/src/main/resources/com/sun/prism/es2/glsl/specular_mix.frag modules/graphics/src/main/resources/com/sun/prism/es2/glsl/specular_texture.frag
diffstat 33 files changed, 1272 insertions(+), 324 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/apps/toys/FX8-3DFeatures/src/fx83dfeatures/Camera3D.java	Thu Nov 28 14:47:01 2013 +0400
@@ -0,0 +1,230 @@
+/*
+ * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package fx83dfeatures;
+
+import com.sun.javafx.geom.Vec3d;
+import com.sun.javafx.geom.Vec3f;
+import javafx.beans.property.DoubleProperty;
+import javafx.beans.property.SimpleDoubleProperty;
+import javafx.geometry.Point3D;
+import javafx.scene.PerspectiveCamera;
+import javafx.scene.transform.Affine;
+
+/**
+ * A movable 3D camera node
+ * The camera looks into positive Z direction
+ */
+public class Camera3D extends PerspectiveCamera {
+    public final Vec3d FORWARD = new Vec3d(0,0,1);
+    public final Vec3d UP = new Vec3d(0,-1,0);
+    public final Vec3d RIGHT = new Vec3d(1,0,0);
+
+    private final Affine transform = new Affine();
+
+    private final DoubleProperty upX = new SimpleDoubleProperty(0){
+        @Override protected void invalidated() { updateLookup(); }
+    };
+    public final double getUpX() { return upX.getValue(); }
+    public final void setUpX(double value) { upX.setValue(value); }
+    public final DoubleProperty upXModel() { return upX; }
+
+    private final DoubleProperty upY = new SimpleDoubleProperty(-1){
+        @Override protected void invalidated() { updateLookup(); }
+    };
+    public final double getUpY() { return upY.getValue(); }
+    public final void setUpY(double value) { upY.setValue(value); }
+    public final DoubleProperty upYModel() { return upY; }
+
+    private final DoubleProperty upZ = new SimpleDoubleProperty(0){
+        @Override protected void invalidated() { updateLookup(); }
+    };
+    public final double getUpZ() { return upZ.getValue(); }
+    public final void setUpZ(double value) { upZ.setValue(value); }
+    public final DoubleProperty upZModel() { return upZ; }
+
+    private final DoubleProperty targetX = new SimpleDoubleProperty(0){
+        @Override protected void invalidated() { updateLookup(); }
+    };
+    public final double getTargetX() { return targetX.getValue(); }
+    public final void setTargetX(double value) { targetX.setValue(value); }
+    public final DoubleProperty targetXModel() { return targetX; }
+
+    private final DoubleProperty targetY = new SimpleDoubleProperty(0){
+        @Override protected void invalidated() { updateLookup(); }
+    };
+    public final double getTargetY() { return targetY.getValue(); }
+    public final void setTargetY(double value) { targetY.setValue(value); }
+    public final DoubleProperty targetYModel() { return targetY; }
+
+    private final DoubleProperty targetZ = new SimpleDoubleProperty(1){
+        @Override protected void invalidated() { updateLookup(); }
+    };
+    public final double getTargetZ() { return targetZ.getValue(); }
+    public final void setTargetZ(double value) { targetZ.setValue(value); }
+    public final DoubleProperty targetZModel() { return targetZ; }
+
+    private final DoubleProperty posX = new SimpleDoubleProperty(0){
+        @Override protected void invalidated() { updateLookup(); }
+    };
+    public final double getPosX() { return posX.getValue(); }
+    public final void setPosX(double value) { posX.setValue(value); }
+    public final DoubleProperty posXModel() { return posX; }
+
+    private final DoubleProperty posY = new SimpleDoubleProperty(0){
+        @Override protected void invalidated() { updateLookup(); }
+    };
+    public final double getPosY() { return posY.getValue(); }
+    public final void setPosY(double value) { posY.setValue(value); }
+    public final DoubleProperty posYModel() { return posY; }
+
+    private final DoubleProperty posZ = new SimpleDoubleProperty(0){
+        @Override protected void invalidated() { updateLookup(); }
+    };
+    public final double getPosZ() { return posZ.getValue(); }
+    public final void setPosZ(double value) { posZ.setValue(value); }
+    public final DoubleProperty posZModel() { return posZ; }
+    
+    public Camera3D() {
+        super(true);
+        getTransforms().add(transform);
+        updateLookup();
+    }
+
+    private final Vec3f tm[] = new Vec3f[]{new Vec3f(),new Vec3f(),new Vec3f(),};
+    
+    private void updateLookup() {
+        Vec3f pos = new Vec3f((float)getPosX(), (float)getPosY(), (float)getPosZ());
+        Vec3f target = new Vec3f((float)getTargetX(), (float)getTargetY(), (float)getTargetZ());
+        Vec3f up = new Vec3f((float)getUpX(), (float)getUpY(), (float)getUpZ());
+        tm[2].sub(target, pos);         // z looks to the target
+        tm[1].set(-up.x, -up.y, -up.z); // y looks down
+        tm[0].cross(tm[1], tm[2]);      // x = y ^ z;
+        tm[1].cross(tm[2], tm[0]);      // y = z ^ x
+
+        for (int i=0; i!=3; ++i) {
+            tm[i].normalize();
+        }
+        //Vec3f pos, Vec3f tm[], Affine tma) {
+        transform.setMxx(tm[0].x); transform.setMxy(tm[1].x); transform.setMxz(tm[2].x);
+        transform.setMyx(tm[0].y); transform.setMyy(tm[1].y); transform.setMyz(tm[2].y);
+        transform.setMzx(tm[0].z); transform.setMzy(tm[1].z); transform.setMzz(tm[2].z);
+        transform.setTx (pos.x);   transform.setTy(pos.y);    transform.setTz(pos.z);
+    }
+
+    public Affine getTransform() {
+        return transform;
+    }
+
+    public void setPos(double x, double y, double z) {
+        setPosX(x);
+        setPosY(y);
+        setPosZ(z);
+    }
+    
+    public void setTarget(double x, double y, double z) {
+        setTargetX(x);
+        setTargetY(y);
+        setTargetZ(z);
+    }
+    
+    public void setUp(double x, double y, double z) {
+        setUpX(x);
+        setUpY(y);
+        setUpZ(z);
+    }
+    
+    /*
+     * returns 3D direction from the Camera position to the mouse
+     * in the Scene space 
+     */
+    
+    public Vec3d unProjectDirection(double sceneX, double sceneY, double sWidth, double sHeight) {
+        Vec3d vMouse = null;
+        
+        if (isVerticalFieldOfView()) {
+            // TODO: implement for Vfov
+        } else {
+            double tanHFov = Math.tan(Math.toRadians(getFieldOfView()) * 0.5f);
+            vMouse = new Vec3d(2*sceneX/sWidth-1, 2*sceneY/sWidth-sHeight/sWidth, 1);
+            vMouse.x *= tanHFov;
+            vMouse.y *= tanHFov;
+        }
+
+        Vec3d result = localToSceneDirection(vMouse, new Vec3d());
+        result.normalize();
+        return result;
+    }
+    
+    public Vec3d getPosition() {
+        return new Vec3d(getPosX(), getPosY(), getPosZ());
+    }
+
+    public Vec3d getTarget() {
+        return new Vec3d(getTargetX(), getTargetY(), getTargetZ());
+    }
+    
+    public Vec3d localToScene(Vec3d pt, Vec3d result) {
+        Point3D res = localToParentTransformProperty().get().transform(pt.x, pt.y, pt.z);
+        if (getParent() != null) {
+            res = getParent().localToSceneTransformProperty().get().transform(res);
+        }
+        result.set(res.getX(), res.getY(), res.getZ());
+        return result;
+    }
+    
+    public Vec3d localToSceneDirection(Vec3d dir, Vec3d result) {
+        localToScene(dir, result);
+        result.sub(localToScene(new Vec3d(0, 0, 0), new Vec3d()));
+        return result;
+    }
+    
+    public Vec3d getForward() {
+        Vec3d res = localToSceneDirection(FORWARD, new Vec3d());
+        res.normalize();
+        return res;
+    }
+
+    public Vec3d getUp() {
+        Vec3d res = localToSceneDirection(UP, new Vec3d());
+        res.normalize();
+        return res;
+    }
+
+    public Vec3d getRight() {
+        Vec3d res = localToSceneDirection(RIGHT, new Vec3d());
+        res.normalize();
+        return res;
+    }
+    
+    @Override
+    public String toString() {
+        return "camera3D.setPos(" + posX.get() + ", " + posY.get() + ", " 
+                + posZ.get() + ");\n"
+                + "camera3D.setTarget(" + targetX.get() + ", " 
+                + targetY.get() + ", " + targetZ.get() + ");\n"
+                + "camera3D.setUp(" + upX.get() + ", " + upY.get() + ", " 
+                + upZ.get() + ");";
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/apps/toys/FX8-3DFeatures/src/fx83dfeatures/CameraController.java	Thu Nov 28 14:47:01 2013 +0400
@@ -0,0 +1,454 @@
+/*
+ * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package fx83dfeatures;
+
+import com.sun.javafx.geom.Vec2d;
+import com.sun.javafx.geom.Vec3d;
+import javafx.animation.AnimationTimer;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.event.EventHandler;
+import javafx.geometry.Point3D;
+import javafx.scene.Node;
+import javafx.scene.Scene;
+import javafx.scene.SubScene;
+import javafx.scene.input.KeyEvent;
+import javafx.scene.input.MouseEvent;
+import javafx.scene.transform.Rotate;
+
+/**
+ * This is camera controller to use with new 3D API.
+ *
+ */
+public class CameraController {
+
+    private Camera3D camera;
+    private Node node;
+    private int up, down, left, right,
+            forward, back; // key pressed
+    private double initialSpeed = 5;
+    private double maxSpeed = 200;
+    private double speed = initialSpeed;
+    private double orbitSpeed = 1.0;
+    private double previousX, previousY;
+    private double yaw = 0.0022;
+    private double pitch = -0.0022;
+    private double maxPitch = 0.95;
+    private AnimationTimer moveTimer;
+    private Rotate rotateX;
+    private Rotate rotateY;
+
+    private void handleKeyEvent(KeyEvent t) {
+        if (t.getEventType() == KeyEvent.KEY_PRESSED) {
+            handleKeyEvent(t, true);
+        } else if (t.getEventType() == KeyEvent.KEY_RELEASED) {
+            handleKeyEvent(t, false);
+        }
+        // skip other event types
+    }
+    
+    private void handleMouseEvent(MouseEvent t) {
+        if (t.getEventType() == MouseEvent.MOUSE_PRESSED) {
+            handleMousePress(t);
+        } else if (t.getEventType() == MouseEvent.MOUSE_DRAGGED) {
+            Vec2d d = getDragDelta(t);
+            double speedModifier = getSpeedModifier(t);
+            switch (t.getButton()) {
+                case PRIMARY:
+                    // Alt + LMB = orbit
+                    handlePrimaryMouseDrag(t, d, speedModifier);
+                    break;
+                case MIDDLE:
+                    // Alt + MMB = pan/track
+                    handleMiddleMouseDrag(t, d, speedModifier);
+                    break;
+                case SECONDARY:
+                    // Alt + RMB = zoom
+                    handleSecondaryMouseDrag(t, d, speedModifier);
+                    break;
+                default:
+                    throw new AssertionError();
+            }
+        }
+    }
+
+    private void setEventHandlers(Scene scene) {
+        scene.addEventHandler(KeyEvent.ANY, k -> handleKeyEvent(k));
+        scene.addEventHandler(MouseEvent.ANY, m -> handleMouseEvent(m));
+    }
+
+    private void setEventHandlers(SubScene subScene) {
+        subScene.addEventHandler(KeyEvent.ANY, k -> handleKeyEvent(k));
+        subScene.addEventHandler(MouseEvent.ANY, m -> handleMouseEvent(m));
+    }
+
+    public CameraController(Camera3D camera) {
+        this.camera = camera;
+
+        new AnimationTimer() {
+            long now = 0;
+
+            @Override
+            public void handle(long l) {
+                if (now == 0) {
+                    now = l;
+                } else {
+                    double dt = (l - now) * 1e-9;
+                    now = l;
+                    update(dt);
+                }
+            }
+        }.start();
+    }
+
+    public CameraController(Camera3D camera, Scene scene) {
+        this(camera);
+        setEventHandlers(scene);
+    }
+
+    public CameraController(Camera3D camera, SubScene subScene) {
+        this(camera);
+        setEventHandlers(subScene);
+    }
+
+    private boolean isNotMoving() {
+        return forward - back == 0 && right - left == 0 && up - down == 0;
+    }
+
+    private void update(double dt) {
+        if (isNotMoving()) {
+            return;
+        }
+        Vec3d shiftForward = camera.getForward();
+        shiftForward.mul(forward - back);
+        Vec3d shiftRight = camera.getRight();
+        shiftRight.mul(right - left);
+        Vec3d shiftUp = camera.getUp();
+        shiftUp.mul(up - down);
+        Vec3d shift = new Vec3d(shiftForward);
+        shift.add(shiftRight);
+        shift.add(shiftUp);
+        shift.mul(speed * dt);
+        shiftCamera(shift);
+    }
+
+    private void handleKeyEvent(KeyEvent event, boolean state) {
+        int pressed = state ? 1 : 0;
+        boolean wasNotMoving = isNotMoving();
+        switch (event.getCode()) {
+            case I:
+                if (state) {
+                    System.out.println(camera);
+                }
+                break;
+            case Z:
+                if (state) {
+                    camera.setTarget(0.0, 0.0, 0.0);
+                }
+                break;
+            case W:
+            case NUMPAD8:
+            case UP:
+                this.forward = pressed;
+                break;
+            case A:
+            case Q:
+            case NUMPAD4:
+            case LEFT:
+                this.left = pressed;
+                break;
+            case S:
+            case MINUS:
+            case NUMPAD2:
+            case NUMPAD5:
+            case DOWN:
+                this.back = pressed;
+                break;
+            case D:
+            case E:
+            case NUMPAD6:
+            case RIGHT:
+                this.right = pressed;
+                break;
+            case R:
+            case T:
+            case NUMPAD9:
+            case PAGE_UP:
+                this.up = pressed;
+                break;
+            case G:
+            case NUMPAD3:
+            case PAGE_DOWN:
+                this.down = pressed;
+                break;
+            case DIGIT1:
+                speed = 5;
+                break;
+            case DIGIT2:
+                speed = 10;
+                break;
+            case DIGIT3:
+                speed = 20;
+                break;
+            case DIGIT4:
+                speed = 40;
+                break;
+            case DIGIT5:
+                speed = 60;
+                break;
+            case DIGIT6:
+                speed = 80;
+                break;
+            case DIGIT7:
+                speed = 100;
+                break;
+            case DIGIT8:
+                speed = 120;
+                break;
+            case DIGIT9:
+                speed = 140;
+                break;
+            case DIGIT0:
+                speed = 160;
+                break;
+            case O:
+                orbitSpeed = 1.0;
+                break;
+            case OPEN_BRACKET:
+                //camera.setMinZ(1.0);
+                break;
+            case CLOSE_BRACKET:
+                //camera.setMaxZ(10000.0);
+                break;
+        }
+        if (isNotMoving()) {
+            if (moveTimer != null) {
+                moveTimer.stop();
+                moveTimer = null;
+                speed = initialSpeed;
+            }
+        } else if (wasNotMoving) {
+            moveTimer = new AnimationTimer() {
+                long now = 0;
+
+                @Override
+                public void handle(long l) {
+                    if (now == 0) {
+                        now = l;
+                    } else {
+                        double age = (l - now) * 1e-9;
+                        if (age > 1.0) {
+                            speed = initialSpeed * Math.pow(2, age);
+                            if (speed > maxSpeed) {
+                                speed = maxSpeed;
+                            }
+                        }
+                    }
+                }
+            };
+            moveTimer.start();
+        }
+        event.consume();
+    }
+
+    private Vec2d getDragDelta(MouseEvent event) {
+        Vec2d res = new Vec2d();
+        res.x = event.getSceneX() - previousX;
+        res.y = event.getSceneY() - previousY;
+        previousX = event.getSceneX();
+        previousY = event.getSceneY();
+        return res;
+    }
+
+    private double getSpeedModifier(MouseEvent event) {
+        if (event.isShiftDown()) {
+            return 4.0;
+        } else if (event.isControlDown()) {
+            // We're going to use control for some other purpose
+            // than a speed modifier, at least for now
+            // return 0.25;
+            return 1.0;
+        } else {
+            return 1.0;
+        }
+    }
+
+    private void handleMousePress(MouseEvent event) {
+        previousX = event.getSceneX();
+        previousY = event.getSceneY();
+        event.consume();
+    }
+
+    private void handlePrimaryMouseDrag(MouseEvent event, Vec2d d, double localFactor) {
+        if (event.isAltDown()) {
+            orbit(d, localFactor);
+            event.consume();
+        } else if (node != null) {
+            rotateObject(d, localFactor);
+        }
+    }
+
+    private void handleMiddleMouseDrag(MouseEvent event, Vec2d d, double localFactor) {
+        if (event.isAltDown()) {
+            track(d, localFactor);
+            event.consume();
+        }
+    }
+
+    private void handleSecondaryMouseDrag(MouseEvent event, Vec2d d, double localFactor) {
+        if (event.isAltDown()) {
+            if (!event.isControlDown()) {
+                // zoom actually change distance to lookat target
+                zoom(d, localFactor);
+            } else {
+                // actually move camera forward/backward
+                move(d, localFactor);
+            }
+        } else {
+            rotate(d, localFactor);
+        }
+        event.consume();
+    }
+
+    private double getLookatDist() {
+        Vec3d target = camera.getTarget();
+        target.sub(camera.getPosition());
+        return target.length();
+    }
+
+    private void shiftCamera(Vec3d shift) {
+        Vec3d pos = camera.getPosition();
+        Vec3d target = camera.getTarget();
+        pos.add(shift);
+        target.add(shift);
+        camera.setPos(pos.x, pos.y, pos.z);
+        camera.setTarget(target.x, target.y, target.z);
+    }
+
+    static private double clamp(double val, double min, double max) {
+        return val > max ? max : val < min ? min : val;
+    }
+
+    // get direction vector from (asimut, height)
+    // 0 < asimut < 2
+    // -1 < height < 1
+    static private Vec3d globus(Vec2d ah) {
+        double ra = ah.x * Math.PI;
+        double rh = ah.y * Math.PI * 0.5;
+        double cosH = Math.cos(rh);
+        return new Vec3d(Math.sin(ra) * cosH, Math.sin(rh), Math.cos(ra) * cosH);
+    }
+
+    static private Vec2d antiGlobus(Vec3d globus) {
+        double rh = Math.asin(globus.y);
+        double ra = Math.atan2(globus.x, globus.z);
+        return new Vec2d(ra / Math.PI, rh * 2 / Math.PI);
+    }
+
+    private void orbit(Vec2d d, double speedFactor) {
+        Vec3d lookFromTarget = camera.getForward();
+        lookFromTarget.mul(-1.0); // look from target to pos
+        Vec2d ah = antiGlobus(lookFromTarget);
+        ah.x += d.x * yaw * speedFactor;
+        // -abs(pitch) - don't invert mouse when orbit?
+        ah.y -= d.y * Math.abs(pitch) * speedFactor;
+        ah.y = clamp(ah.y, -maxPitch, maxPitch);
+        lookFromTarget = globus(ah);
+        lookFromTarget.mul(getLookatDist());
+        Vec3d newPos = camera.getTarget();
+        newPos.add(lookFromTarget);
+        camera.setPos(newPos.x, newPos.y, newPos.z);
+    }
+
+    private void track(Vec2d d, double speedFactor) {
+        Vec3d shiftRight = camera.getRight();
+        shiftRight.mul(-d.x * 0.1 * orbitSpeed * speedFactor);
+        Vec3d shiftUp = camera.getUp();
+        shiftUp.mul(d.y * 0.1 * orbitSpeed * speedFactor);
+        shiftRight.add(shiftUp);
+        shiftCamera(shiftRight);
+    }
+
+    private void zoom(Vec2d d, double speedFactor) {
+        double dist = getLookatDist();
+        dist -= d.x * orbitSpeed * speedFactor;
+        dist += d.y * orbitSpeed * speedFactor;
+        if (dist < 0.1) {
+            dist = 0.1;
+        }
+        Vec3d lookFromTarget = camera.getForward();
+        lookFromTarget.mul(-1.0 * dist); // look from target to pos
+        Vec3d newPos = camera.getTarget();
+        newPos.add(lookFromTarget);
+        camera.setPos(newPos.x, newPos.y, newPos.z);
+    }
+
+    private void move(Vec2d d, double speedFactor) {
+        Vec3d shiftForward = camera.getForward();
+        shiftForward.mul(d.x * .5 * orbitSpeed * speedFactor);
+        shiftCamera(shiftForward);
+    }
+
+    private void rotate(Vec2d d, double speedFactor) {
+        Vec2d ah = antiGlobus(camera.getForward());
+        ah.x += d.x * yaw * speedFactor;
+        ah.y += d.y * pitch * speedFactor;
+        ah.y = clamp(ah.y, -maxPitch, maxPitch);
+        Vec3d lookat = globus(ah);
+        lookat.mul(getLookatDist());
+        Vec3d pos = camera.getPosition();
+        pos.add(lookat);
+        camera.setTarget(pos.x, pos.y, pos.z);
+    }
+
+    private void rotateObject(Vec2d d, double speedFactor) {
+        double angle = rotateY.getAngle();
+        angle += d.x * yaw * speedFactor * 180;
+        rotateY.setAngle(angle % 360); // just in case
+
+        angle = rotateX.getAngle();
+        angle += d.y * yaw * speedFactor * 180;
+        rotateX.setAngle(angle % 360); // just in case
+    }
+    
+    public void setObject(Node node) {
+        rotateX = new Rotate(0, new Point3D(1, 0, 0));
+        rotateY = new Rotate(0, new Point3D(0, 1, 0));
+        // explicitly add transforms to the front
+        // because max nodes are rotated during loading
+        node.getTransforms().add(0, rotateX);
+        node.getTransforms().add(1, rotateY);
+        
+        this.node = node;
+    }
+    
+    public void setInvertMouse(boolean invert) {
+        pitch = Math.abs(pitch) * (invert ? -1 : 1);
+    }
+
+    public boolean getInvertMouse() {
+        return pitch < 0;
+    }
+}
--- a/apps/toys/FX8-3DFeatures/src/fx83dfeatures/SpecularColorTestApp.java	Wed Nov 27 18:46:26 2013 -0800
+++ b/apps/toys/FX8-3DFeatures/src/fx83dfeatures/SpecularColorTestApp.java	Thu Nov 28 14:47:01 2013 +0400
@@ -24,16 +24,27 @@
  */
 package fx83dfeatures;
 
+import java.util.ArrayList;
+import java.util.List;
 import javafx.application.Application;
-import javafx.beans.value.ChangeListener;
+import javafx.beans.binding.Bindings;
+import javafx.beans.binding.ObjectBinding;
 import javafx.beans.value.ObservableValue;
+import javafx.geometry.Insets;
 import javafx.scene.Group;
+import javafx.scene.Node;
 import javafx.scene.PerspectiveCamera;
+import javafx.scene.PointLight;
 import javafx.scene.Scene;
+import javafx.scene.SubScene;
 import javafx.scene.control.CheckBox;
 import javafx.scene.control.ColorPicker;
+import javafx.scene.control.Label;
 import javafx.scene.control.ScrollBar;
 import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.layout.GridPane;
+import javafx.scene.layout.HBox;
 import javafx.scene.layout.VBox;
 import javafx.scene.paint.Color;
 import javafx.scene.paint.PhongMaterial;
@@ -44,70 +55,200 @@
 
     private ScrollBar specularPowerScroll;
     private ColorPicker specularColorPicker;
-    private CheckBox specularMapCheckBox;
+    private CheckBox specularColorCheckBox, specularMapCheckBox;
+
+    Image diffuseMap = new Image("resources/cup_diffuseMap_1024.png");
+    Image specularGrayMap = new Image("resources/spec.png");
+    Image specularColorMap = new Image("resources/spec_color.png");
+    //Image bumpMap = new Image("resources/bump.png");
+
+    private List<PhongMaterial> materials;
+
+    private void addSphere(Group g, int x, int y, PhongMaterial material) {
+        Sphere s = new Sphere(10);
+        s.setMaterial(material);
+        s.setTranslateX(x);
+        s.setTranslateY(y);
+        g.getChildren().add(s);
+    }
+
+    interface MaterialModifier {
+        public void modify(PhongMaterial mat);
+    }
+
+    private void addMaterials(MaterialModifier modifier) {
+        PhongMaterial mat = new PhongMaterial(Color.ANTIQUEWHITE);
+        modifier.modify(mat);
+        materials.add(mat);
+
+        mat = new PhongMaterial();
+        mat.setDiffuseMap(diffuseMap);
+        modifier.modify(mat);
+        materials.add(mat);
+
+        mat = new PhongMaterial(Color.RED);
+        mat.setDiffuseMap(diffuseMap);
+        modifier.modify(mat);
+        materials.add(mat);
+/*
+        mat = new PhongMaterial();
+        mat.setDiffuseMap(diffuseMap);
+        mat.setBumpMap(bumpMap);
+        modifier.modify(mat);
+        materials.add(mat);
+*/
+    }
+
+    private void bindMaterial(PhongMaterial m, ObservableValue color, ObservableValue map) {
+        if (color != null) {
+            m.specularColorProperty().bind(color);
+        }
+        if (map != null) {
+            m.specularMapProperty().bind(map);
+        }
+        m.specularPowerProperty().bind(specularPowerScroll.valueProperty());
+    }
+
+    private void addSpheres(Group g) {
+        materials = new ArrayList();
+
+        // no specular
+        addMaterials(m -> {});
+
+        // specular power with default color (null)
+        addMaterials(m -> m.setSpecularPower(100));
+
+        // specular color with default power
+        addMaterials(m -> m.setSpecularColor(Color.WHITE));
+
+        ObjectBinding specularColorBind = Bindings.createObjectBinding(
+                () -> specularColorCheckBox.isSelected() ?
+                        specularColorPicker.getValue() : null,
+                specularColorPicker.valueProperty(),
+                specularColorCheckBox.selectedProperty()
+        );
+
+        ObjectBinding specularGrayMapBind = Bindings.createObjectBinding(
+                () -> specularMapCheckBox.isSelected() ?
+                        specularGrayMap : null,
+                specularMapCheckBox.selectedProperty()
+        );
+
+        ObjectBinding specularColorMapBind = Bindings.createObjectBinding(
+                () -> specularMapCheckBox.isSelected() ?
+                        specularColorMap : null,
+                specularMapCheckBox.selectedProperty()
+        );
+
+        // specular power with color
+        addMaterials(m -> bindMaterial(m, specularColorBind, null));
+
+        // gray specular map
+        addMaterials(m -> m.specularMapProperty().bind(specularGrayMapBind));
+
+        // color specular map
+        addMaterials(m -> m.specularMapProperty().bind(specularColorMapBind));
+
+        // gray specular map with power
+        addMaterials(m -> bindMaterial(m, null, specularGrayMapBind));
+
+        // color specular map with power
+        addMaterials(m -> bindMaterial(m, null, specularColorMapBind));
+
+        // gray specular map with power and color
+        addMaterials(m -> bindMaterial(m, specularColorBind, specularGrayMapBind));
+
+        // color specular map with power and color
+        addMaterials(m -> bindMaterial(m, specularColorBind, specularColorMapBind));
+
+        int numX = 6, numY = ( materials.size() + numX - 1 ) / numX;
+        for (int y = 0; y < numY; y++) {
+            for (int x = 0; x < numX; x++) {
+                int idx = y * numX + x;
+                if (idx < materials.size()) {
+                    int xPos = -50 + x * 100 / (numX - 1);
+                    int yPos = -40 + y * 80 / (numY - 1);
+                    addSphere(g, xPos, yPos, materials.get(idx));
+                }
+            }
+        }
+    }
+
+    private Node createControls() {
+        specularPowerScroll = new ScrollBar();
+        specularPowerScroll.setValue(new PhongMaterial().getSpecularPower());
+        specularPowerScroll.setMin(1);
+        specularPowerScroll.setMax(100);
+
+        specularColorCheckBox = new CheckBox("Specular Color");
+        specularColorCheckBox.setSelected(true);
+        specularColorPicker = new ColorPicker(Color.color(0.5, 1, 0));
+
+        ImageView diffView = createImageView(diffuseMap);
+        specularMapCheckBox = new CheckBox("Specular Map");
+        specularMapCheckBox.setSelected(true);
+        ImageView specGrayView = createImageView(specularGrayMap);
+        ImageView specColorView = createImageView(specularColorMap);
+        VBox controls = new VBox(15);
+        controls.setPadding(new Insets(15));
+        GridPane grid = new GridPane();
+        grid.setHgap(5);
+        grid.setVgap(5);
+        grid.addRow(0, new Label("Diffuse"), new Label("Specuar power only"));
+        grid.addRow(1, new Label("White specular color"), new Label("Specular color and power"));
+        grid.addRow(3, new Label("Gray map"), new Label("Color map"));
+        grid.addRow(4, new Label("Gray map and power"), new Label("Color map and power"));
+        grid.addRow(5, new Label("Gray map with color"), new Label("Color map with color"));
+        controls.getChildren().addAll(specularColorCheckBox, specularColorPicker, specularPowerScroll,
+                grid, diffView, specularMapCheckBox, new HBox(15, specGrayView, specColorView));
+        
+        return controls;
+    }
+
+    private ImageView createImageView(Image image) {
+        ImageView diffView = new ImageView(image);
+        diffView.setFitWidth(100);
+        diffView.setPreserveRatio(true);
+        diffView.setSmooth(true);
+        diffView.setCache(true);
+        return diffView;
+    }
 
     @Override
     public void start(Stage stage) throws Exception {
-        final Image diffuseMap = new Image("resources/cup_diffuseMap_1024.png");
-        final PhongMaterial material = new PhongMaterial(Color.ANTIQUEWHITE);
+        Node controls = createControls();
+        PointLight pointLight = new PointLight(Color.ANTIQUEWHITE);
+        pointLight.setTranslateZ(-200);
 
-        final Sphere s = new Sphere();
-        s.setScaleX(100);
-        s.setScaleY(100);
-        s.setScaleZ(100);
-        s.setMaterial(material);
-        s.setTranslateX(150);
-        s.setTranslateY(250);
+        PointLight pointLight2 = new PointLight(Color.ANTIQUEWHITE);
+        pointLight2.setTranslateX(-250);
+        pointLight2.setTranslateY(300);
+        pointLight2.setTranslateZ(-1500);
 
-        final Sphere s1 = new Sphere(2);
-        s1.setScaleX(100);
-        s1.setScaleY(100);
-        s1.setScaleZ(100);
-        s1.setMaterial(material);
-        s1.setTranslateX(500);
-        s1.setTranslateY(250);
+        PointLight pointLight3 = new PointLight(Color.ANTIQUEWHITE);
+        pointLight3.setTranslateX(250);
+        pointLight3.setTranslateY(100);
+        pointLight3.setTranslateZ(500);
 
-        Group root1 = new Group(s, s1);
-        specularPowerScroll = new ScrollBar();
-        specularPowerScroll.setValue(material.getSpecularPower());
-        specularPowerScroll.setMin(0);
-        specularPowerScroll.setMax(1);
-        specularPowerScroll.valueProperty().addListener(new ChangeListener<Number>() {
-            @Override
-            public void changed(ObservableValue<? extends Number> ov, Number t, Number t1) {
-                material.setSpecularPower(t1.doubleValue());
-                System.out.println("power changed " + t1.doubleValue());
-            }
-        });
+        Group root1 = new Group(pointLight/*, pointLight2, pointLight3*/);
+        addSpheres(root1);
 
-        specularColorPicker = new ColorPicker(material.getSpecularColor());
-        specularColorPicker.valueProperty().addListener(new ChangeListener<Color>() {
-            @Override
-            public void changed(ObservableValue<? extends Color> ov, Color t, Color t1) {
-                material.setSpecularColor(t1);
-                System.out.println("color changed " + t1);
-            }
-        });
-
-        specularMapCheckBox = new CheckBox("Specular Map");
-        specularMapCheckBox.setSelected(false);
-        specularMapCheckBox.selectedProperty().addListener(new ChangeListener<Boolean>() {
-
-            @Override
-            public void changed(ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) {
-                material.setSpecularMap(t1 ? diffuseMap : null);
-            }
-        });
-        VBox controls = new VBox(15);
-        controls.getChildren().addAll(specularColorPicker, specularPowerScroll, specularMapCheckBox);
-
-        Group root = new Group(root1, controls);
-        Scene scene = new Scene(root, 800, 500, true);
-
+        SubScene scene3D = new SubScene(root1, 1024, 768, true, null);
+        final Camera3D cam3d = new Camera3D();
+        cam3d.setPosZ(-200);
+        cam3d.setFarClip(10000);
+        scene3D.setCamera(cam3d);
+        control = new CameraController(cam3d, scene3D);
+        scene3D.setFill(Color.DARKGRAY);
+        HBox root = new HBox(scene3D, controls);
+        Scene scene = new Scene(root, 1324, 768, true);
+        scene3D.requestFocus();
+     
         scene.setCamera(new PerspectiveCamera());
         stage.setScene(scene);
         stage.show();
     }
+    private CameraController control;
 
     public static void main(String[] args) {
         launch(args);
Binary file apps/toys/FX8-3DFeatures/src/resources/spec.png has changed
Binary file apps/toys/FX8-3DFeatures/src/resources/spec_color.png has changed
--- a/build.gradle	Wed Nov 27 18:46:26 2013 -0800
+++ b/build.gradle	Thu Nov 28 14:47:01 2013 +0400
@@ -1158,39 +1158,51 @@
                         ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s1n.h", "/DSpec=1", "/DSType=0", "$PS_3D_SRC"],
                         ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s2n.h", "/DSpec=2", "/DSType=0", "$PS_3D_SRC"],
                         ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s3n.h", "/DSpec=3", "/DSType=0", "$PS_3D_SRC"],
-                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s1a.h", "/DSpec=1", "/DSType=1", "$PS_3D_SRC"],
-                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s2a.h", "/DSpec=2", "/DSType=1", "$PS_3D_SRC"],
-                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s3a.h", "/DSpec=3", "/DSType=1", "$PS_3D_SRC"],
-                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s1s.h", "/DSpec=1", "/DSType=2", "$PS_3D_SRC"],
-                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s2s.h", "/DSpec=2", "/DSType=2", "$PS_3D_SRC"],
-                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s3s.h", "/DSpec=3", "/DSType=2", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s1t.h", "/DSpec=1", "/DSType=1", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s2t.h", "/DSpec=2", "/DSType=1", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s3t.h", "/DSpec=3", "/DSType=1", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s1c.h", "/DSpec=1", "/DSType=2", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s2c.h", "/DSpec=2", "/DSType=2", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s3c.h", "/DSpec=3", "/DSType=2", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s1m.h", "/DSpec=1", "/DSType=3", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s2m.h", "/DSpec=2", "/DSType=3", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s3m.h", "/DSpec=3", "/DSType=3", "$PS_3D_SRC"],
                         ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b1n.h", "/DSpec=1", "/DSType=0", "/DBump=1", "$PS_3D_SRC"],
                         ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b2n.h", "/DSpec=2", "/DSType=0", "/DBump=1", "$PS_3D_SRC"],
                         ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b3n.h", "/DSpec=3", "/DSType=0", "/DBump=1", "$PS_3D_SRC"],
-                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b1a.h", "/DSpec=1", "/DSType=1", "/DBump=1", "$PS_3D_SRC"],
-                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b2a.h", "/DSpec=2", "/DSType=1", "/DBump=1", "$PS_3D_SRC"],
-                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b3a.h", "/DSpec=3", "/DSType=1", "/DBump=1", "$PS_3D_SRC"],
-                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b1s.h", "/DSpec=1", "/DSType=2", "/DBump=1", "$PS_3D_SRC"],
-                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b2s.h", "/DSpec=2", "/DSType=2", "/DBump=1", "$PS_3D_SRC"],
-                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b3s.h", "/DSpec=3", "/DSType=2", "/DBump=1", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b1t.h", "/DSpec=1", "/DSType=1", "/DBump=1", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b2t.h", "/DSpec=2", "/DSType=1", "/DBump=1", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b3t.h", "/DSpec=3", "/DSType=1", "/DBump=1", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b1c.h", "/DSpec=1", "/DSType=2", "/DBump=1", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b2c.h", "/DSpec=2", "/DSType=2", "/DBump=1", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b3c.h", "/DSpec=3", "/DSType=2", "/DBump=1", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b1m.h", "/DSpec=1", "/DSType=3", "/DBump=1", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b2m.h", "/DSpec=2", "/DSType=3", "/DBump=1", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b3m.h", "/DSpec=3", "/DSType=3", "/DBump=1", "$PS_3D_SRC"],
                         ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s1ni.h", "/DSpec=1", "/DSType=0", "/DIllumMap=1", "$PS_3D_SRC"],
                         ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s2ni.h", "/DSpec=2", "/DSType=0", "/DIllumMap=1", "$PS_3D_SRC"],
                         ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s3ni.h", "/DSpec=3", "/DSType=0", "/DIllumMap=1", "$PS_3D_SRC"],
-                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s1ai.h", "/DSpec=1", "/DSType=1", "/DIllumMap=1", "$PS_3D_SRC"],
-                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s2ai.h", "/DSpec=2", "/DSType=1", "/DIllumMap=1", "$PS_3D_SRC"],
-                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s3ai.h", "/DSpec=3", "/DSType=1", "/DIllumMap=1", "$PS_3D_SRC"],
-                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s1si.h", "/DSpec=1", "/DSType=2", "/DIllumMap=1", "$PS_3D_SRC"],
-                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s2si.h", "/DSpec=2", "/DSType=2", "/DIllumMap=1", "$PS_3D_SRC"],
-                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s3si.h", "/DSpec=3", "/DSType=2", "/DIllumMap=1", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s1ti.h", "/DSpec=1", "/DSType=1", "/DIllumMap=1", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s2ti.h", "/DSpec=2", "/DSType=1", "/DIllumMap=1", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s3ti.h", "/DSpec=3", "/DSType=1", "/DIllumMap=1", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s1ci.h", "/DSpec=1", "/DSType=2", "/DIllumMap=1", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s2ci.h", "/DSpec=2", "/DSType=2", "/DIllumMap=1", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s3ci.h", "/DSpec=3", "/DSType=2", "/DIllumMap=1", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s1mi.h", "/DSpec=1", "/DSType=3", "/DIllumMap=1", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s2mi.h", "/DSpec=2", "/DSType=3", "/DIllumMap=1", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_s3mi.h", "/DSpec=3", "/DSType=3", "/DIllumMap=1", "$PS_3D_SRC"],
                         ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b1ni.h", "/DSpec=1", "/DSType=0", "/DBump=1", "/DIllumMap=1", "$PS_3D_SRC"],
                         ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b2ni.h", "/DSpec=2", "/DSType=0", "/DBump=1", "/DIllumMap=1", "$PS_3D_SRC"],
                         ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b3ni.h", "/DSpec=3", "/DSType=0", "/DBump=1", "/DIllumMap=1", "$PS_3D_SRC"],
-                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b1ai.h", "/DSpec=1", "/DSType=1", "/DBump=1", "/DIllumMap=1", "$PS_3D_SRC"],
-                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b2ai.h", "/DSpec=2", "/DSType=1", "/DBump=1", "/DIllumMap=1", "$PS_3D_SRC"],
-                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b3ai.h", "/DSpec=3", "/DSType=1", "/DBump=1", "/DIllumMap=1", "$PS_3D_SRC"],
-                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b1si.h", "/DSpec=1", "/DSType=2", "/DBump=1", "/DIllumMap=1", "$PS_3D_SRC"],
-                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b2si.h", "/DSpec=2", "/DSType=2", "/DBump=1", "/DIllumMap=1", "$PS_3D_SRC"],
-                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b3si.h", "/DSpec=3", "/DSType=2", "/DBump=1", "/DIllumMap=1", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b1ti.h", "/DSpec=1", "/DSType=1", "/DBump=1", "/DIllumMap=1", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b2ti.h", "/DSpec=2", "/DSType=1", "/DBump=1", "/DIllumMap=1", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b3ti.h", "/DSpec=3", "/DSType=1", "/DBump=1", "/DIllumMap=1", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b1ci.h", "/DSpec=1", "/DSType=2", "/DBump=1", "/DIllumMap=1", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b2ci.h", "/DSpec=2", "/DSType=2", "/DBump=1", "/DIllumMap=1", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b3ci.h", "/DSpec=3", "/DSType=2", "/DBump=1", "/DIllumMap=1", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b1mi.h", "/DSpec=1", "/DSType=3", "/DBump=1", "/DIllumMap=1", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b2mi.h", "/DSpec=2", "/DSType=3", "/DBump=1", "/DIllumMap=1", "$PS_3D_SRC"],
+                        ["$FXC", "/nologo", "/T", "ps_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1PS_b3mi.h", "/DSpec=3", "/DSType=3", "/DBump=1", "/DIllumMap=1", "$PS_3D_SRC"],
                         ["$FXC", "/nologo", "/T", "vs_2_0", "/Fh", "$buildDir/headers/PrismD3D/hlsl/Mtl1VS_Obj.h", "/DVertexType=ObjVertex", "$VS_3D_SRC"]
                 ]
                 final ExecutorService executor = Executors.newFixedThreadPool(Integer.parseInt(project.NUM_COMPILE_THREADS.toString()));
--- a/modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGPhongMaterial.java	Wed Nov 27 18:46:26 2013 -0800
+++ b/modules/graphics/src/main/java/com/sun/javafx/sg/prism/NGPhongMaterial.java	Thu Nov 28 14:47:01 2013 +0400
@@ -48,7 +48,6 @@
     private boolean specularColorDirty = true;
     private float specularPower;
     private boolean specularPowerDirty = true;
-    private boolean hasSpecularMap = false;
     private TextureMap specularMap = new TextureMap(PhongMaterial.MapType.SPECULAR);
 
     private TextureMap bumpMap = new TextureMap(PhongMaterial.MapType.BUMP);
@@ -67,11 +66,11 @@
 
         if (diffuseColorDirty) {
             if (diffuseColor != null) {
-                material.setSolidColor(
+                material.setDiffuseColor(
                         diffuseColor.getRed(), diffuseColor.getGreen(),
                         diffuseColor.getBlue(), diffuseColor.getAlpha());
             } else {
-                material.setSolidColor(0, 0, 0, 0);
+                material.setDiffuseColor(0, 0, 0, 0);
             }
             diffuseColorDirty = false;
         }
@@ -88,24 +87,18 @@
         if (selfIllumMap.isDirty()) {
             material.setTextureMap(selfIllumMap);
         }
-        if (specularMap.isDirty() || specularColorDirty || specularPowerDirty) {
-            Image specular = specularMap.getImage();
-            if (!hasSpecularMap && specularColor != null) {
-                int ia = (int) (255.0 * specularPower);
-                int ir = (int) (255.0 * specularColor.getRed());
-                int ig = (int) (255.0 * specularColor.getGreen());
-                int ib = (int) (255.0 * specularColor.getBlue());
-                int pixel = (ia << 24) | (ir << 16) | (ig << 8) | (ib << 0);
-
-                if (ir != 0 || ig != 0 || ib != 0) {
-                    specular = Image.fromIntArgbPreData(new int[]{pixel}, 1, 1);
-                    // NOTE: Need to manually mark specularMap dirty when it is
-                    // a color so that native texture can be updated.
-                    specularMap.setDirty(true);
-                }
+        if (specularMap.isDirty()) {
+            material.setTextureMap(specularMap);
+        }
+        if (specularColorDirty || specularPowerDirty) {
+            if (specularColor != null) {
+                float r = specularColor.getRed();
+                float g = specularColor.getGreen();
+                float b = specularColor.getBlue();
+                material.setSpecularColor(true, r, g, b, specularPower);
+            } else {
+                material.setSpecularColor(false, 1, 1, 1, specularPower);
             }
-            specularMap.setImage(specular);
-            material.setTextureMap(specularMap);
             specularColorDirty = false;
             specularPowerDirty = false;
         }
@@ -134,7 +127,6 @@
     public void setSpecularMap(Object specularMap) {
         this.specularMap.setImage((Image)specularMap);
         this.specularMap.setDirty(true);
-        hasSpecularMap = specularMap != null ? true : false;
     }
 
     public void setBumpMap(Object bumpMap) {
--- a/modules/graphics/src/main/java/com/sun/prism/PhongMaterial.java	Wed Nov 27 18:46:26 2013 -0800
+++ b/modules/graphics/src/main/java/com/sun/prism/PhongMaterial.java	Thu Nov 28 14:47:01 2013 +0400
@@ -39,12 +39,12 @@
     public static final int SELF_ILLUM = MapType.SELF_ILLUM.ordinal();
     public static final int MAX_MAP_TYPE = MapType.values().length; 
 
-    public void setSolidColor(float r, float g, float b, float a);
+    public void setDiffuseColor(float r, float g, float b, float a);
+    public void setSpecularColor(boolean set, float r, float g, float b, float a);
 
     public void setTextureMap(TextureMap map);
-    
+
     public void lockTextureMaps();
 
     public void unlockTextureMaps();
-
 }
--- a/modules/graphics/src/main/java/com/sun/prism/d3d/D3DContext.java	Wed Nov 27 18:46:26 2013 -0800
+++ b/modules/graphics/src/main/java/com/sun/prism/d3d/D3DContext.java	Thu Nov 28 14:47:01 2013 +0400
@@ -375,10 +375,12 @@
             float[] vertexBuffer, int vertexBufferLength, int[] indexBuffer, int indexBufferLength);
     private static native long nCreateD3DPhongMaterial(long pContext);
     private static native void nReleaseD3DPhongMaterial(long pContext, long nativeHandle);
-    private static native void nSetSolidColor(long pContext, long nativePhongMaterial,
+    private static native void nSetDiffuseColor(long pContext, long nativePhongMaterial,
             float r, float g, float b, float a);
+    private static native void nSetSpecularColor(long pContext, long nativePhongMaterial,
+            boolean set, float r, float g, float b, float a);
     private static native void nSetMap(long pContext, long nativePhongMaterial,
-            int mapType, long texID, boolean isSpecularAlpha, boolean isBumpAlpha);
+            int mapType, long texID);
     private static native long nCreateD3DMeshView(long pContext, long nativeMesh);
     private static native void nReleaseD3DMeshView(long pContext, long nativeHandle);
     private static native void nSetCullingMode(long pContext, long nativeMeshView,
@@ -466,14 +468,16 @@
         nReleaseD3DPhongMaterial(pContext, nativeHandle);
     }
 
-    void setSolidColor(long nativePhongMaterial, float r, float g, float b, float a) {
-        nSetSolidColor(pContext, nativePhongMaterial, r, g, b, a);
+    void setDiffuseColor(long nativePhongMaterial, float r, float g, float b, float a) {
+        nSetDiffuseColor(pContext, nativePhongMaterial, r, g, b, a);
     }
 
-    void setMap(long nativePhongMaterial, int mapType, long nativeTexture,
-            boolean isSpecularAlpha, boolean isBumpAlpha) {
-        nSetMap(pContext, nativePhongMaterial, mapType, nativeTexture,
-                isSpecularAlpha, isBumpAlpha);
+    void setSpecularColor(long nativePhongMaterial, boolean set, float r, float g, float b, float a) {
+        nSetSpecularColor(pContext, nativePhongMaterial, set, r, g, b, a);
+    }
+
+    void setMap(long nativePhongMaterial, int mapType, long nativeTexture) {
+        nSetMap(pContext, nativePhongMaterial, mapType, nativeTexture);
     }
 
     long createD3DMeshView(long nativeMesh) {
--- a/modules/graphics/src/main/java/com/sun/prism/d3d/D3DPhongMaterial.java	Wed Nov 27 18:46:26 2013 -0800
+++ b/modules/graphics/src/main/java/com/sun/prism/d3d/D3DPhongMaterial.java	Thu Nov 28 14:47:01 2013 +0400
@@ -43,7 +43,7 @@
     private final D3DContext context;
     private final long nativeHandle;
     private TextureMap maps[] = new TextureMap[MAX_MAP_TYPE];
-    
+
     private D3DPhongMaterial(D3DContext context, long nativeHandle,
             Disposer.Record disposerRecord) {
         super(disposerRecord);
@@ -61,8 +61,14 @@
         return nativeHandle;
     }
 
-    public void setSolidColor(float r, float g, float b, float a) {
-        context.setSolidColor(nativeHandle, r, g, b, a);
+    @Override
+    public void setDiffuseColor(float r, float g, float b, float a) {
+        context.setDiffuseColor(nativeHandle, r, g, b, a);
+    }
+
+    @Override
+    public void setSpecularColor(boolean set, float r, float g, float b, float a) {
+        context.setSpecularColor(nativeHandle, set, r, g, b, a);
     }
 
     public void setTextureMap(TextureMap map) {
@@ -70,28 +76,11 @@
     }
 
     private Texture setupTexture(TextureMap map) {
-        boolean isSpecularAlpha = false;
-        boolean isBumpAlpha = false;
         Image image = map.getImage();
         Texture texture = (image == null) ? null
                 : context.getResourceFactory().getCachedTexture(image, Texture.WrapMode.REPEAT);
-        switch (map.getType()) {
-            case SPECULAR:
-                isSpecularAlpha = texture == null ? false : !texture.getPixelFormat().isOpaque();
-                break;
-            case BUMP:
-                isBumpAlpha = texture == null ? false : !texture.getPixelFormat().isOpaque();
-                break;
-            case DIFFUSE:
-                break;
-            case SELF_ILLUM:
-                break;
-            default:
-            // NOP
-        }
         long hTexture = (texture != null) ? ((D3DTexture) texture).getNativeTextureObject() : 0;
-        context.setMap(nativeHandle, map.getType().ordinal(),
-                hTexture, isSpecularAlpha, isBumpAlpha);
+        context.setMap(nativeHandle, map.getType().ordinal(), hTexture);
         return texture;
     }
 
--- a/modules/graphics/src/main/java/com/sun/prism/es2/ES2Context.java	Wed Nov 27 18:46:26 2013 -0800
+++ b/modules/graphics/src/main/java/com/sun/prism/es2/ES2Context.java	Thu Nov 28 14:47:01 2013 +0400
@@ -421,9 +421,8 @@
         glContext.setSolidColor(nativeHandle, r, g, b, a);
     }
 
-    void setMap(long nativeHandle, int mapType, int texID,
-            boolean isSpecularAlpha, boolean isBumpAlpha) {
-        glContext.setMap(nativeHandle, mapType, texID, isSpecularAlpha, isBumpAlpha);
+    void setMap(long nativeHandle, int mapType, int texID) {
+        glContext.setMap(nativeHandle, mapType, texID);
     }
 
     long createES2MeshView(ES2Mesh mesh) {
--- a/modules/graphics/src/main/java/com/sun/prism/es2/ES2PhongMaterial.java	Wed Nov 27 18:46:26 2013 -0800
+++ b/modules/graphics/src/main/java/com/sun/prism/es2/ES2PhongMaterial.java	Thu Nov 28 14:47:01 2013 +0400
@@ -46,9 +46,7 @@
 
     Color diffuseColor = Color.WHITE;
     Color specularColor = Color.WHITE;
-
-    boolean isSpecularAlpha = false;
-    boolean isBumpAlpha = false;
+    boolean specularColorSet = false;
 
     private ES2PhongMaterial(ES2Context context, long nativeHandle,
             Disposer.Record disposerRecord) {
@@ -67,10 +65,17 @@
         return nativeHandle;
     }
 
-    public void setSolidColor(float r, float g, float b, float a) {
+    @Override
+    public void setDiffuseColor(float r, float g, float b, float a) {
         diffuseColor = new Color(r,g,b,a);
     }
 
+    @Override
+    public void setSpecularColor(boolean set, float r, float g, float b, float a) {
+        specularColorSet = set;
+        specularColor = new Color(r,g,b,a);
+    }
+
     public void setTextureMap(TextureMap map) {
         maps[map.getType().ordinal()] = map;
     }
@@ -79,20 +84,6 @@
         Image image = map.getImage();
         Texture texture = (image == null) ? null
                 : context.getResourceFactory().getCachedTexture(image, Texture.WrapMode.REPEAT);
-        switch (map.getType()) {
-            case SPECULAR:
-                isSpecularAlpha = texture == null ? false : !texture.getPixelFormat().isOpaque();
-                break;
-            case BUMP:
-                isBumpAlpha = texture == null ? false : !texture.getPixelFormat().isOpaque();
-                break;
-            case DIFFUSE:
-                break;
-            case SELF_ILLUM:
-                break;
-            default:
-            // NOP
-        }
         return texture;
     }
 
--- a/modules/graphics/src/main/java/com/sun/prism/es2/ES2PhongShader.java	Wed Nov 27 18:46:26 2013 -0800
+++ b/modules/graphics/src/main/java/com/sun/prism/es2/ES2PhongShader.java	Thu Nov 28 14:47:01 2013 +0400
@@ -48,8 +48,9 @@
     enum SpecularState {
 
         NONE,
-        SPECULARCOLOR,
-        TEXTURE
+        TEXTURE,
+        COLOR,
+        MIX
     }
 
     enum SelfIllumState {
@@ -65,7 +66,7 @@
     }
     static final int lightStateCount = 4;
     private static String diffuseShaderParts[] = new String[DiffuseState.values().length];
-    private static String SpecularShaderParts[] = new String[SpecularState.values().length];
+    private static String specularShaderParts[] = new String[SpecularState.values().length];
     private static String selfIllumShaderParts[] = new String[SelfIllumState.values().length];
     private static String normalMapShaderParts[] = new String[BumpMapState.values().length];
     private static String lightingShaderParts[] = new String[lightStateCount];
@@ -82,12 +83,14 @@
         diffuseShaderParts[DiffuseState.TEXTURE.ordinal()] =
                 ES2Shader.readStreamIntoString(ES2ResourceFactory.class.getResourceAsStream("glsl/diffuse_texture.frag"));
 
-        SpecularShaderParts[SpecularState.NONE.ordinal()] =
+        specularShaderParts[SpecularState.NONE.ordinal()] =
                 ES2Shader.readStreamIntoString(ES2ResourceFactory.class.getResourceAsStream("glsl/specular_none.frag"));
-        SpecularShaderParts[SpecularState.SPECULARCOLOR.ordinal()] =
+        specularShaderParts[SpecularState.TEXTURE.ordinal()] =
+                ES2Shader.readStreamIntoString(ES2ResourceFactory.class.getResourceAsStream("glsl/specular_texture.frag"));
+        specularShaderParts[SpecularState.COLOR.ordinal()] =
                 ES2Shader.readStreamIntoString(ES2ResourceFactory.class.getResourceAsStream("glsl/specular_color.frag"));
-        SpecularShaderParts[SpecularState.TEXTURE.ordinal()] =
-                ES2Shader.readStreamIntoString(ES2ResourceFactory.class.getResourceAsStream("glsl/specular_texture.frag"));
+        specularShaderParts[SpecularState.MIX.ordinal()] =
+                ES2Shader.readStreamIntoString(ES2ResourceFactory.class.getResourceAsStream("glsl/specular_mix.frag"));
 
         selfIllumShaderParts[SelfIllumState.NONE.ordinal()] =
                 ES2Shader.readStreamIntoString(ES2ResourceFactory.class.getResourceAsStream("glsl/selfIllum_none.frag"));
@@ -112,6 +115,15 @@
 
     }
 
+    static SpecularState getSpecularState(ES2PhongMaterial material) {
+        if (material.maps[ES2PhongMaterial.SPECULAR].getTexture() != null) {
+            return material.specularColorSet ?
+                    SpecularState.MIX : SpecularState.TEXTURE;
+        }
+        return material.specularColorSet ?
+                SpecularState.COLOR : SpecularState.NONE;
+    }
+
     static ES2Shader getShader(ES2MeshView meshView, ES2Context context) {
 
         ES2PhongMaterial material = meshView.getMaterial();
@@ -121,19 +133,7 @@
             diffuseState = DiffuseState.TEXTURE;
         }
 
-        SpecularState specularState = SpecularState.NONE;
-        //TODO: 3D - determine proper check (does a TEXTURE override a color?)
-        if (material.specularColor != null) {
-            specularState = SpecularState.SPECULARCOLOR;
-        }
-
-        if (material.maps[ES2PhongMaterial.SPECULAR].getTexture() != null) {
-            if (material.isSpecularAlpha) {
-                specularState = SpecularState.TEXTURE;
-            } else {
-                specularState = SpecularState.SPECULARCOLOR;
-            }
-        }
+        SpecularState specularState = getSpecularState(material);
 
         BumpMapState bumpState = BumpMapState.NONE;
         if (material.maps[ES2PhongMaterial.BUMP].getTexture() != null) {
@@ -154,10 +154,10 @@
                 [selfIllumState.ordinal()][bumpState.ordinal()][numLights];
         if (shader == null) {
             String fragShader = lightingShaderParts[numLights].replace("vec4 apply_diffuse();", diffuseShaderParts[diffuseState.ordinal()]);
-            fragShader = fragShader.replace("vec4 apply_specular();", SpecularShaderParts[specularState.ordinal()]);
+            fragShader = fragShader.replace("vec4 apply_specular();", specularShaderParts[specularState.ordinal()]);
             fragShader = fragShader.replace("vec3 apply_normal();", normalMapShaderParts[bumpState.ordinal()]);
             fragShader = fragShader.replace("vec4 apply_selfIllum();", selfIllumShaderParts[selfIllumState.ordinal()]);
-                  
+
             String[] pixelShaders = new String[]{
                 fragShader
             };
@@ -191,6 +191,10 @@
                 material.diffuseColor.getGreen(), material.diffuseColor.getBlue(),
                 material.diffuseColor.getAlpha());
 
+        shader.setConstant("specularColor", material.specularColor.getRed(),
+                material.specularColor.getGreen(), material.specularColor.getBlue(),
+                material.specularColor.getAlpha());
+
         context.updateTexture(0, material.maps[ES2PhongMaterial.DIFFUSE].getTexture());
         context.updateTexture(1, material.maps[ES2PhongMaterial.SPECULAR].getTexture());
         context.updateTexture(2, material.maps[ES2PhongMaterial.BUMP].getTexture());
--- a/modules/graphics/src/main/java/com/sun/prism/es2/GLContext.java	Wed Nov 27 18:46:26 2013 -0800
+++ b/modules/graphics/src/main/java/com/sun/prism/es2/GLContext.java	Thu Nov 28 14:47:01 2013 +0400
@@ -226,7 +226,7 @@
     private static native void nSetSolidColor(long nativeCtxInfo, long nativePhongMaterial,
             float r, float g, float b, float a);
     private static native void nSetMap(long nativeCtxInfo, long nativePhongMaterial,
-            int mapType, int texID, boolean isSpecularAlpha, boolean isBumpAlpha);
+            int mapType, int texID);
     private static native long nCreateES2MeshView(long nativeCtxInfo, long nativeMeshInfo);
     private static native void nReleaseES2MeshView(long nativeCtxInfo, long nativeHandle);
     private static native void nSetCullingMode(long nativeCtxInfo, long nativeMeshViewInfo,
@@ -752,10 +752,8 @@
         nSetSolidColor(nativeCtxInfo, nativePhongMaterial, r, g, b, a);
     }
 
-    void setMap(long nativePhongMaterial, int mapType, int texID,
-            boolean isSpecularAlpha, boolean isBumpAlpha) {
-        nSetMap(nativeCtxInfo, nativePhongMaterial, mapType, texID,
-                isSpecularAlpha, isBumpAlpha);
+    void setMap(long nativePhongMaterial, int mapType, int texID) {
+        nSetMap(nativeCtxInfo, nativePhongMaterial, mapType, texID);
     }
 
     long createES2MeshView(long nativeMeshInfo) {
--- a/modules/graphics/src/main/java/javafx/scene/paint/PhongMaterial.java	Wed Nov 27 18:46:26 2013 -0800
+++ b/modules/graphics/src/main/java/javafx/scene/paint/PhongMaterial.java	Thu Nov 28 14:47:01 2013 +0400
@@ -135,7 +135,7 @@
     /**
      * The specular power of this {@code PhongMaterial}.
      *
-     * @defaultValue 1.0
+     * @defaultValue 32.0
      */
     private DoubleProperty specularPower;
 
@@ -144,13 +144,13 @@
     }
 
     public final double getSpecularPower() {
-        return specularPower == null ? 1 : specularPower.get();
+        return specularPower == null ? 32 : specularPower.get();
     }
 
     public final DoubleProperty specularPowerProperty() {
         if (specularPower == null) {
             specularPower = new SimpleDoubleProperty(PhongMaterial.this, 
-                    "specularPower", 1.0) {
+                    "specularPower", 32.0) {
                 @Override
                 public void invalidated() {
                     specularPowerDirty = true;
--- a/modules/graphics/src/main/native-prism-d3d/D3DContext.cc	Wed Nov 27 18:46:26 2013 -0800
+++ b/modules/graphics/src/main/native-prism-d3d/D3DContext.cc	Thu Nov 28 14:47:01 2013 +0400
@@ -303,35 +303,50 @@
 
 /*
  * Class:     com_sun_prism_d3d_D3DContext
- * Method:    nSetSolidColor
+ * Method:    nSetDiffuseColor
  * Signature: (JJFFFF)V
  */
-JNIEXPORT void JNICALL Java_com_sun_prism_d3d_D3DContext_nSetSolidColor
+JNIEXPORT void JNICALL Java_com_sun_prism_d3d_D3DContext_nSetDiffuseColor
   (JNIEnv *env, jclass, jlong ctx, jlong nativePhongMaterial,
         jfloat r, jfloat g, jfloat b, jfloat a)
 {
-    TraceLn(NWT_TRACE_INFO, "D3DContext_nSetSolidColor");
+    TraceLn(NWT_TRACE_INFO, "D3DContext_nSetDiffuseColor");
     D3DPhongMaterial *phongMaterial = (D3DPhongMaterial *) jlong_to_ptr(nativePhongMaterial);
     RETURN_IF_NULL(phongMaterial);
 
-    phongMaterial->setSolidColor(r, g, b, a);
+    phongMaterial->setDiffuseColor(r, g, b, a);
 }
 
 /*
  * Class:     com_sun_prism_d3d_D3DContext
+ * Method:    nSetSpecularColor
+ * Signature: (JJZFFFF)V
+ */
+JNIEXPORT void JNICALL Java_com_sun_prism_d3d_D3DContext_nSetSpecularColor
+  (JNIEnv *env, jclass, jlong ctx, jlong nativePhongMaterial,
+        jboolean set, jfloat r, jfloat g, jfloat b, jfloat a)
+{
+    TraceLn(NWT_TRACE_INFO, "D3DContext_nSetSpecularColor");
+    D3DPhongMaterial *phongMaterial = (D3DPhongMaterial *) jlong_to_ptr(nativePhongMaterial);
+    RETURN_IF_NULL(phongMaterial);
+
+    phongMaterial->setSpecularColor(set ? true : false, r, g, b, a);
+}
+/*
+ * Class:     com_sun_prism_d3d_D3DContext
  * Method:    nSetMap
- * Signature: (JJIJZZ)V
+ * Signature: (JJIJ)V
  */
 JNIEXPORT void JNICALL Java_com_sun_prism_d3d_D3DContext_nSetMap
   (JNIEnv *env, jclass, jlong ctx, jlong nativePhongMaterial,
-        jint mapType, jlong nativeTexture, jboolean isSpecularAlpha, jboolean isBumpAlpha)
+        jint mapType, jlong nativeTexture)
 {
     TraceLn(NWT_TRACE_INFO, "D3DContext_nSetMap");
     D3DPhongMaterial *phongMaterial = (D3DPhongMaterial *) jlong_to_ptr(nativePhongMaterial);
     IDirect3DBaseTexture9 *texMap = (IDirect3DBaseTexture9 *)  jlong_to_ptr(nativeTexture);
     RETURN_IF_NULL(phongMaterial);
 
-    phongMaterial->setMap(mapType, texMap, isSpecularAlpha ? true : false, isBumpAlpha ? true : false);
+    phongMaterial->setMap(mapType, texMap);
 }
 
 /*
--- a/modules/graphics/src/main/native-prism-d3d/D3DMeshView.cc	Wed Nov 27 18:46:26 2013 -0800
+++ b/modules/graphics/src/main/native-prism-d3d/D3DMeshView.cc	Thu Nov 28 14:47:01 2013 +0400
@@ -142,9 +142,15 @@
         return;
     }
 
-    status = SUCCEEDED(device->SetPixelShaderConstantF(PSR_CONSTANTCOLOR, material->getSolidColor(), 1));
+    status = SUCCEEDED(device->SetPixelShaderConstantF(PSR_DIFFUSECOLOR, material->getDiffuseColor(), 1));
     if (!status) {
-        cout << "D3DMeshView.render() - SetPixelShaderConstantF (PSR_CONSTANTCOLOR) failed !!!" << endl;
+        cout << "D3DMeshView.render() - SetPixelShaderConstantF (PSR_DIFFUSECOLOR) failed !!!" << endl;
+        return;
+    }
+
+    status = SUCCEEDED(device->SetPixelShaderConstantF(PSR_SPECULARCOLOR, material->getSpecularColor(), 1));
+    if (!status) {
+        cout << "D3DMeshView.render() - SetPixelShaderConstantF (PSR_SPECULARCOLOR) failed !!!" << endl;
         return;
     }
 
@@ -163,8 +169,7 @@
     }
 
     int bm = pShader->getBumpMode(material->isBumpMap());
-    int sm = pShader->getSpecularMode(material->isSpecularMap(),
-            material->isSpecularAlpha());
+    int sm = pShader->getSpecularMode(material->isSpecularMap(), material->isSpecularColor());
     int im = material->isSelfIllumMap() ? 1 : 0;
 
     status = pShader->setPixelShader(numLights, sm, bm, im);
--- a/modules/graphics/src/main/native-prism-d3d/D3DPhongMaterial.cc	Wed Nov 27 18:46:26 2013 -0800
+++ b/modules/graphics/src/main/native-prism-d3d/D3DPhongMaterial.cc	Thu Nov 28 14:47:01 2013 +0400
@@ -46,25 +46,40 @@
     diffuseColor[1] = 0;
     diffuseColor[2] = 0;
     diffuseColor[3] = 0;
+    specularColorSet = false;
+    specularColor[0] = 1;
+    specularColor[1] = 1;
+    specularColor[2] = 1;
+    specularColor[3] = 32;
     map[DIFFUSE] = NULL;
     map[SPECULAR] = NULL;
     map[BUMP] = NULL;
     map[SELFILLUMINATION] = NULL;
-    specularAlpha = false;
-    bumpAlpha = false;
 }
 
-void D3DPhongMaterial::setSolidColor(float r, float g, float b, float a) {
+void D3DPhongMaterial::setDiffuseColor(float r, float g, float b, float a) {
     diffuseColor[0] = r;
     diffuseColor[1] = g;
     diffuseColor[2] = b;
     diffuseColor[3] = a;
 }
 
-float * D3DPhongMaterial::getSolidColor() {
+float * D3DPhongMaterial::getDiffuseColor() {
     return diffuseColor;
 }
 
+void D3DPhongMaterial::setSpecularColor(bool set, float r, float g, float b, float a) {
+    specularColorSet = set;
+    specularColor[0] = r;
+    specularColor[1] = g;
+    specularColor[2] = b;
+    specularColor[3] = a;
+}
+
+float * D3DPhongMaterial::getSpecularColor() {
+    return specularColor;
+}
+
 bool D3DPhongMaterial::isBumpMap() {
     return map[BUMP] ? true : false;
 }
@@ -77,8 +92,8 @@
     return map[SELFILLUMINATION] ? true : false;
 }
 
-bool D3DPhongMaterial::isSpecularAlpha() {
-    return specularAlpha;
+bool D3DPhongMaterial::isSpecularColor() {
+    return specularColorSet;
 }
 
 IDirect3DBaseTexture9 * D3DPhongMaterial::getMap(int type) {
@@ -90,13 +105,10 @@
     return NULL;
 }
 
-void D3DPhongMaterial::setMap(int mapID, IDirect3DBaseTexture9 *texMap,
-        bool isSA, bool isBA) {
+void D3DPhongMaterial::setMap(int mapID, IDirect3DBaseTexture9 *texMap) {
     // Within the range of DIFFUSE, SPECULAR, BUMP, SELFILLUMINATION
     if (mapID >= 0 && mapID <= 3) {
         map[mapID] = texMap;
-        specularAlpha = isSA;
-        bumpAlpha = isBA;
     } else {
         cerr << "D3DPhongMaterial::getMap -- mapID is out of range - mapID = " << mapID << endl;
     }
--- a/modules/graphics/src/main/native-prism-d3d/D3DPhongMaterial.h	Wed Nov 27 18:46:26 2013 -0800
+++ b/modules/graphics/src/main/native-prism-d3d/D3DPhongMaterial.h	Thu Nov 28 14:47:01 2013 +0400
@@ -39,21 +39,22 @@
 public:
     D3DPhongMaterial(D3DContext *pCtx);
     virtual ~D3DPhongMaterial();
-    void setSolidColor(float r, float g, float b, float a);
-    float *getSolidColor();
-    void setMap(int mapID, IDirect3DBaseTexture9 *texMap, bool isSA, bool isBA);
+    void setDiffuseColor(float r, float g, float b, float a);
+    float *getDiffuseColor();
+    void setSpecularColor(bool set, float r, float g, float b, float a);
+    float *getSpecularColor();
+    void setMap(int mapID, IDirect3DBaseTexture9 *texMap);
     bool isBumpMap();
     bool isSpecularMap();
+    bool isSpecularColor();
     bool isSelfIllumMap();
-    bool isSpecularAlpha();
     IDirect3DBaseTexture9 * getMap(int type);
 
 private:
     D3DContext *context;
-    float diffuseColor[4];
+    float diffuseColor[4], specularColor[4];
     IDirect3DBaseTexture9 *map[4];
-    bool specularAlpha, bumpAlpha;
-
+    bool specularColorSet;
 };
 
 #endif  /* D3DPHONGMATERIAL_H */
--- a/modules/graphics/src/main/native-prism-d3d/D3DPhongShader.cc	Wed Nov 27 18:46:26 2013 -0800
+++ b/modules/graphics/src/main/native-prism-d3d/D3DPhongShader.cc	Thu Nov 28 14:47:01 2013 +0400
@@ -66,24 +66,28 @@
         {
             {
                 { psMtl1_s1n, psMtl1_s2n, psMtl1_s3n},
-                { psMtl1_s1a, psMtl1_s2a, psMtl1_s3a},
-                { psMtl1_s1s, psMtl1_s2s, psMtl1_s3s}
+                { psMtl1_s1t, psMtl1_s2t, psMtl1_s3t},
+                { psMtl1_s1c, psMtl1_s2c, psMtl1_s3c},
+                { psMtl1_s1m, psMtl1_s2m, psMtl1_s3m}
             },
             {
                 { psMtl1_b1n, psMtl1_b2n, psMtl1_b3n},
-                { psMtl1_b1a, psMtl1_b2a, psMtl1_b3a},
-                { psMtl1_b1s, psMtl1_b2s, psMtl1_b3s}
+                { psMtl1_b1t, psMtl1_b2t, psMtl1_b3t},
+                { psMtl1_b1c, psMtl1_b2c, psMtl1_b3c},
+                { psMtl1_b1m, psMtl1_b2m, psMtl1_b3m}
             }},
         {
             {
                 { psMtl1_s1ni, psMtl1_s2ni, psMtl1_s3ni},
-                { psMtl1_s1ai, psMtl1_s2ai, psMtl1_s3ai},
-                { psMtl1_s1si, psMtl1_s2si, psMtl1_s3si}
+                { psMtl1_s1ti, psMtl1_s2ti, psMtl1_s3ti},
+                { psMtl1_s1ci, psMtl1_s2ci, psMtl1_s3ci},
+                { psMtl1_s1mi, psMtl1_s2mi, psMtl1_s3mi}
             },
             {
                 { psMtl1_b1ni, psMtl1_b2ni, psMtl1_b3ni},
-                { psMtl1_b1ai, psMtl1_b2ai, psMtl1_b3ai},
-                { psMtl1_b1si, psMtl1_b2si, psMtl1_b3si}
+                { psMtl1_b1ti, psMtl1_b2ti, psMtl1_b3ti},
+                { psMtl1_b1ci, psMtl1_b2ci, psMtl1_b3ci},
+                { psMtl1_b1mi, psMtl1_b2mi, psMtl1_b3mi}
             }}
     };
 
@@ -108,12 +112,14 @@
 }
 
 int D3DPhongShader::getBumpMode(bool isBumpMap) {
-    return isBumpMap ? BUMP_SPECIFIED : BUMP_NONE;
+    return isBumpMap ? BumpSpecified : BumpNone;
 }
 
-int D3DPhongShader::getSpecularMode(bool isSpecularMap, bool isSpecularAlpha){
-        return isSpecularMap ?
-            isSpecularAlpha ? SPECULAR_SPECIFIED : SPECULAR_AUTO : SPECULAR_NONE;
+int D3DPhongShader::getSpecularMode(bool isSpecularMap, bool isSpecularColor) {
+    if (isSpecularMap) {
+        return isSpecularColor ? SpecMix : SpecTexture;
+    }
+    return isSpecularColor ? SpecColor : SpecNone;
 }
 
 HRESULT D3DPhongShader::setPixelShader(int numLights, int specularMode,
--- a/modules/graphics/src/main/native-prism-d3d/D3DPhongShader.h	Wed Nov 27 18:46:26 2013 -0800
+++ b/modules/graphics/src/main/native-prism-d3d/D3DPhongShader.h	Thu Nov 28 14:47:01 2013 +0400
@@ -40,7 +40,8 @@
 
 // PSR implies Pixel Shader Registers
 // we have 32 constants for ps 2.0
-#define PSR_CONSTANTCOLOR 0
+#define PSR_DIFFUSECOLOR 0
+#define PSR_SPECULARCOLOR 1
 #define PSR_LIGHTCOLOR 4
 
 // SR implies Sampler Registers
@@ -49,31 +50,42 @@
 #define SR_BUMPHEIGHTMAP 2
 #define SR_SELFILLUMMAP 3
 
-#define SPECULAR_NONE 0
-#define SPECULAR_AUTO 1
-#define SPECULAR_SPECIFIED 2
+enum SpecType {
+    SpecNone,
+    SpecTexture, // map only w/o alpha
+    SpecColor,   // color w/o map
+    SpecMix,     // map & color
+    SpecTotal
+};
 
-#define BUMP_NONE 0
-#define BUMP_SPECIFIED 1
+enum BumpType {
+    BumpNone,
+    BumpSpecified,
+    BumpTotal
+};
 
 typedef const DWORD * ShaderFunction;
 ShaderFunction vsMtl1_Obj();
 ShaderFunction psMtl1(), psMtl1_i(),
 psMtl1_s1n(), psMtl1_s2n(), psMtl1_s3n(),
-psMtl1_s1a(), psMtl1_s2a(), psMtl1_s3a(),
-psMtl1_s1s(), psMtl1_s2s(), psMtl1_s3s(),
+psMtl1_s1t(), psMtl1_s2t(), psMtl1_s3t(),
+psMtl1_s1c(), psMtl1_s2c(), psMtl1_s3c(),
+psMtl1_s1m(), psMtl1_s2m(), psMtl1_s3m(),
 
 psMtl1_b1n(), psMtl1_b2n(), psMtl1_b3n(),
-psMtl1_b1a(), psMtl1_b2a(), psMtl1_b3a(),
-psMtl1_b1s(), psMtl1_b2s(), psMtl1_b3s(),
+psMtl1_b1t(), psMtl1_b2t(), psMtl1_b3t(),
+psMtl1_b1c(), psMtl1_b2c(), psMtl1_b3c(),
+psMtl1_b1m(), psMtl1_b2m(), psMtl1_b3m(),
 
 psMtl1_s1ni(), psMtl1_s2ni(), psMtl1_s3ni(),
-psMtl1_s1ai(), psMtl1_s2ai(), psMtl1_s3ai(),
-psMtl1_s1si(), psMtl1_s2si(), psMtl1_s3si(),
+psMtl1_s1ti(), psMtl1_s2ti(), psMtl1_s3ti(),
+psMtl1_s1ci(), psMtl1_s2ci(), psMtl1_s3ci(),
+psMtl1_s1mi(), psMtl1_s2mi(), psMtl1_s3mi(),
 
 psMtl1_b1ni(), psMtl1_b2ni(), psMtl1_b3ni(),
-psMtl1_b1ai(), psMtl1_b2ai(), psMtl1_b3ai(),
-psMtl1_b1si(), psMtl1_b2si(), psMtl1_b3si();
+psMtl1_b1ti(), psMtl1_b2ti(), psMtl1_b3ti(),
+psMtl1_b1ci(), psMtl1_b2ci(), psMtl1_b3ci(),
+psMtl1_b1mi(), psMtl1_b2mi(), psMtl1_b3mi();
 
 class D3DPhongShader {
 public:
@@ -81,11 +93,9 @@
     virtual ~D3DPhongShader();
     IDirect3DVertexShader9 *getVertexShader();
     int getBumpMode(bool isBumpMap);
-    int getSpecularMode(bool isSpecularMap, bool isSpecularAlpha);
+    int getSpecularMode(bool isSpecularMap, bool isSpecularColor);
     HRESULT setPixelShader(int numLights, int specularMode, int bumpMode, int selfIllumMode);
 
-static const int BumpTotal = 2;
-static const int SpecTotal = 3;
 static const int SelfIlllumTotal = 2;
 static const int maxLights = 3;
 
--- a/modules/graphics/src/main/native-prism-d3d/D3DPhongShaderGen.cc	Wed Nov 27 18:46:26 2013 -0800
+++ b/modules/graphics/src/main/native-prism-d3d/D3DPhongShaderGen.cc	Thu Nov 28 14:47:01 2013 +0400
@@ -55,28 +55,40 @@
 #include "hlsl/Mtl1PS_s3n.h"
 endshader(ps, 20)
 
-shader(psMtl1_s1a)
-#include "hlsl/Mtl1PS_s1a.h"
+shader(psMtl1_s1t)
+#include "hlsl/Mtl1PS_s1t.h"
 endshader(ps, 20)
 
-shader(psMtl1_s2a)
-#include "hlsl/Mtl1PS_s2a.h"
+shader(psMtl1_s2t)
+#include "hlsl/Mtl1PS_s2t.h"
 endshader(ps, 20)
 
-shader(psMtl1_s3a)
-#include "hlsl/Mtl1PS_s3a.h"
+shader(psMtl1_s3t)
+#include "hlsl/Mtl1PS_s3t.h"
 endshader(ps, 20)
 
-shader(psMtl1_s1s)
-#include "hlsl/Mtl1PS_s1s.h"
+shader(psMtl1_s1c)
+#include "hlsl/Mtl1PS_s1c.h"
 endshader(ps, 20)
 
-shader(psMtl1_s2s)
-#include "hlsl/Mtl1PS_s2s.h"
+shader(psMtl1_s2c)
+#include "hlsl/Mtl1PS_s2c.h"
 endshader(ps, 20)
 
-shader(psMtl1_s3s)
-#include "hlsl/Mtl1PS_s3s.h"
+shader(psMtl1_s3c)
+#include "hlsl/Mtl1PS_s3c.h"
+endshader(ps, 20)
+
+shader(psMtl1_s1m)
+#include "hlsl/Mtl1PS_s1m.h"
+endshader(ps, 20)
+
+shader(psMtl1_s2m)
+#include "hlsl/Mtl1PS_s2m.h"
+endshader(ps, 20)
+
+shader(psMtl1_s3m)
+#include "hlsl/Mtl1PS_s3m.h"
 endshader(ps, 20)
 
 shader(psMtl1_b1n)
@@ -91,28 +103,40 @@
 #include "hlsl/Mtl1PS_b3n.h"
 endshader(ps, 20)
 
-shader(psMtl1_b1a)
-#include "hlsl/Mtl1PS_b1a.h"
+shader(psMtl1_b1t)
+#include "hlsl/Mtl1PS_b1t.h"
 endshader(ps, 20)
 
-shader(psMtl1_b2a)
-#include "hlsl/Mtl1PS_b2a.h"
+shader(psMtl1_b2t)
+#include "hlsl/Mtl1PS_b2t.h"
 endshader(ps, 20)
 
-shader(psMtl1_b3a)
-#include "hlsl/Mtl1PS_b3a.h"
+shader(psMtl1_b3t)
+#include "hlsl/Mtl1PS_b3t.h"
 endshader(ps, 20)
 
-shader(psMtl1_b1s)
-#include "hlsl/Mtl1PS_b1s.h"
+shader(psMtl1_b1c)
+#include "hlsl/Mtl1PS_b1c.h"
 endshader(ps, 20)
 
-shader(psMtl1_b2s)
-#include "hlsl/Mtl1PS_b2s.h"
+shader(psMtl1_b2c)
+#include "hlsl/Mtl1PS_b2c.h"
 endshader(ps, 20)
 
-shader(psMtl1_b3s)
-#include "hlsl/Mtl1PS_b3s.h"
+shader(psMtl1_b3c)
+#include "hlsl/Mtl1PS_b3c.h"
+endshader(ps, 20)
+
+shader(psMtl1_b1m)
+#include "hlsl/Mtl1PS_b1m.h"
+endshader(ps, 20)
+
+shader(psMtl1_b2m)
+#include "hlsl/Mtl1PS_b2m.h"
+endshader(ps, 20)
+
+shader(psMtl1_b3m)
+#include "hlsl/Mtl1PS_b3m.h"
 endshader(ps, 20)
 
 shader(psMtl1_s1ni)
@@ -127,30 +151,41 @@
 #include "hlsl/Mtl1PS_s3ni.h"
 endshader(ps, 20)
 
-shader(psMtl1_s1ai)
-#include "hlsl/Mtl1PS_s1ai.h"
+shader(psMtl1_s1ti)
+#include "hlsl/Mtl1PS_s1ti.h"
 endshader(ps, 20)
 
-shader(psMtl1_s2ai)
-#include "hlsl/Mtl1PS_s2ai.h"
+shader(psMtl1_s2ti)
+#include "hlsl/Mtl1PS_s2ti.h"
 endshader(ps, 20)
 
-shader(psMtl1_s3ai)
-#include "hlsl/Mtl1PS_s3ai.h"
+shader(psMtl1_s3ti)
+#include "hlsl/Mtl1PS_s3ti.h"
 endshader(ps, 20)
 
-shader(psMtl1_s1si)
-#include "hlsl/Mtl1PS_s1si.h"
+shader(psMtl1_s1ci)
+#include "hlsl/Mtl1PS_s1ci.h"
 endshader(ps, 20)
 
-shader(psMtl1_s2si)
-#include "hlsl/Mtl1PS_s2si.h"
+shader(psMtl1_s2ci)
+#include "hlsl/Mtl1PS_s2ci.h"
 endshader(ps, 20)
 
-shader(psMtl1_s3si)
-#include "hlsl/Mtl1PS_s3si.h"
+shader(psMtl1_s3ci)
+#include "hlsl/Mtl1PS_s3ci.h"
 endshader(ps, 20)
 
+shader(psMtl1_s1mi)
+#include "hlsl/Mtl1PS_s1mi.h"
+endshader(ps, 20)
+
+shader(psMtl1_s2mi)
+#include "hlsl/Mtl1PS_s2mi.h"
+endshader(ps, 20)
+
+shader(psMtl1_s3mi)
+#include "hlsl/Mtl1PS_s3mi.h"
+endshader(ps, 20)
 
 shader(psMtl1_b1ni)
 #include "hlsl/Mtl1PS_b1ni.h"
@@ -164,26 +199,38 @@
 #include "hlsl/Mtl1PS_b3ni.h"
 endshader(ps, 20)
 
-shader(psMtl1_b1ai)
-#include "hlsl/Mtl1PS_b1ai.h"
+shader(psMtl1_b1ti)
+#include "hlsl/Mtl1PS_b1ti.h"
 endshader(ps, 20)
 
-shader(psMtl1_b2ai)
-#include "hlsl/Mtl1PS_b2ai.h"
+shader(psMtl1_b2ti)
+#include "hlsl/Mtl1PS_b2ti.h"
 endshader(ps, 20)
 
-shader(psMtl1_b3ai)
-#include "hlsl/Mtl1PS_b3ai.h"
+shader(psMtl1_b3ti)
+#include "hlsl/Mtl1PS_b3ti.h"
 endshader(ps, 20)
 
-shader(psMtl1_b1si)
-#include "hlsl/Mtl1PS_b1si.h"
+shader(psMtl1_b1ci)
+#include "hlsl/Mtl1PS_b1ci.h"
 endshader(ps, 20)
 
-shader(psMtl1_b2si)
-#include "hlsl/Mtl1PS_b2si.h"
+shader(psMtl1_b2ci)
+#include "hlsl/Mtl1PS_b2ci.h"
 endshader(ps, 20)
 
-shader(psMtl1_b3si)
-#include "hlsl/Mtl1PS_b3si.h"
+shader(psMtl1_b3ci)
+#include "hlsl/Mtl1PS_b3ci.h"
 endshader(ps, 20)
+
+shader(psMtl1_b1mi)
+#include "hlsl/Mtl1PS_b1mi.h"
+endshader(ps, 20)
+
+shader(psMtl1_b2mi)
+#include "hlsl/Mtl1PS_b2mi.h"
+endshader(ps, 20)
+
+shader(psMtl1_b3mi)
+#include "hlsl/Mtl1PS_b3mi.h"
+endshader(ps, 20)
--- a/modules/graphics/src/main/native-prism-d3d/hlsl/Mtl1PS.hlsl	Wed Nov 27 18:46:26 2013 -0800
+++ b/modules/graphics/src/main/native-prism-d3d/hlsl/Mtl1PS.hlsl	Thu Nov 28 14:47:01 2013 +0400
@@ -30,8 +30,9 @@
 #endif
 
 #define SpecNone 0
-#define SpecAuto 1
-#define SpecAlpha 2
+#define SpecTexture 1
+#define SpecColor 2
+#define SpecMix 3
 
 static const int specType = SType;
 
@@ -41,10 +42,6 @@
 sampler mapBumpHeight : register(s2);
 sampler mapSelfIllum  : register(s3);
 
-float autoSpecular (float3 diffuseRGB, float3 specRGB) {
-    return NTSC_Gray(specRGB);
-}
-
 float4 debug() {
     return float4(0,0,1,1);
 }
@@ -66,23 +63,31 @@
     float4 ambColor = objAttr.ambient;
 
     float4 tDiff = tex2D(mapDiffuse, objAttr.texD);
-    tDiff = tDiff * gConstantColor;
+    tDiff = tDiff * gDiffuseColor;
 
-    // return gConstantColor.aaaa;
+    // return gDiffuseColor.aaaa;
 
     float4 tSpec = float4(0,0,0,0);
-    float sLevel = 0;
+    float sPower = 0;
 
     if ( specType > 0 ) {
-        tSpec = tex2D(mapSpecular, objAttr.texD);
-        sLevel = (specType==SpecAuto) ? autoSpecular(tDiff.xyz, tSpec.rgb) : tSpec.a;
+        sPower = gSpecularColor.a;
+        if (specType != SpecColor) { // Texture or Mix
+            tSpec = tex2D(mapSpecular, objAttr.texD);
+            sPower *= NTSC_Gray(tSpec.rgb);
+        } else { // Color
+            tSpec.rgb = gSpecularColor.rgb;
+        }
+        if (specType == SpecMix) {
+            tSpec.rgb *= gSpecularColor.rgb;
+        }
     }
-    // return sLevel.xxxx;
+    // return sPower.xxxx;
 
     float3 diff = 0;
     float3 spec = 0;
 
-    phong(n, nEye, sLevel, lSpace.lights, diff, spec, 0, nSpecular);
+    phong(n, nEye, sPower, lSpace.lights, diff, spec, 0, nSpecular);
 
     float3 rez = (ambColor.xyz+diff)*tDiff.xyz + spec*tSpec.rgb;
 
@@ -91,4 +96,3 @@
 
     return float4( saturate(rez), tDiff.a);
 }
-
--- a/modules/graphics/src/main/native-prism-d3d/hlsl/psConstants.h	Wed Nov 27 18:46:26 2013 -0800
+++ b/modules/graphics/src/main/native-prism-d3d/hlsl/psConstants.h	Thu Nov 28 14:47:01 2013 +0400
@@ -23,12 +23,12 @@
  * questions.
  */
 
-// see ShaderConstants.h cpp header
+// see D3DPhongShader.h
 
 static const int numMaxLights = 5;
 
-float4 gConstantColor : register(c0);
-float4 gReserved1 : register(c1);
+float4 gDiffuseColor : register(c0);
+float4 gSpecularColor : register(c1); // specular power is in the alpha
 float4 gLightColor[numMaxLights] : register(c4);  // [c4 .. c8]
 
 float4 gSomethingElse : register(c9);
--- a/modules/graphics/src/main/native-prism-d3d/hlsl/psMath.h	Wed Nov 27 18:46:26 2013 -0800
+++ b/modules/graphics/src/main/native-prism-d3d/hlsl/psMath.h	Thu Nov 28 14:47:01 2013 +0400
@@ -59,11 +59,10 @@
 float4 retr(float x) { return float4(x.xxx,1); }
 
 void phong(
-    float3 n, float3 e, float sLevel, in float4 L[LocalBump::nLights],
+    float3 n, float3 e, float power, in float4 L[LocalBump::nLights],
     in out float3 d, in out float3 s, int _s, int _e)
 {
     float3 refl = reflect(e, n);
-    float power = sLevel*32+1;
     for (int i=_s; i<_e; i++) {
         float3 l = normalize(L[i].xyz);
         d += saturate(dot(n,l))*gLightColor[i].xyz;
--- a/modules/graphics/src/main/native-prism-es2/GLContext.c	Wed Nov 27 18:46:26 2013 -0800
+++ b/modules/graphics/src/main/native-prism-es2/GLContext.c	Thu Nov 28 14:47:01 2013 +0400
@@ -2000,8 +2000,6 @@
     pmInfo->diffuseColor[1] = 0.0f;
     pmInfo->diffuseColor[2] = 0.0f;
     pmInfo->diffuseColor[3] = 0.0f;
-    pmInfo->isBumpAlpha = GL_FALSE;
-    pmInfo->isSpecularAlpha = GL_FALSE;
     pmInfo->maps[0] = 0;
     pmInfo->maps[1] = 0;
     pmInfo->maps[2] = 0;
@@ -2053,11 +2051,11 @@
 /*
  * Class:     com_sun_prism_es2_GLContext
  * Method:    nSetMap
- * Signature: (JJIIZZ)V
+ * Signature: (JJII)V
  */
 JNIEXPORT void JNICALL Java_com_sun_prism_es2_GLContext_nSetMap
   (JNIEnv *env, jclass class, jlong nativeCtxInfo, jlong nativePhongMaterialInfo,
-        jint mapType, jint texID, jboolean isSpecularAlpha, jboolean isBumpAlpha)
+        jint mapType, jint texID)
 {
     ContextInfo *ctxInfo = (ContextInfo *) jlong_to_ptr(nativeCtxInfo);
     PhongMaterialInfo *pmInfo = (PhongMaterialInfo *) jlong_to_ptr(nativePhongMaterialInfo);
@@ -2072,8 +2070,6 @@
     }
     
     pmInfo->maps[mapType] = texID;
-    pmInfo->isSpecularAlpha = isSpecularAlpha;
-    pmInfo->isBumpAlpha = isBumpAlpha;
 }
 
 /*
--- a/modules/graphics/src/main/native-prism-es2/PrismES2Defs.h	Wed Nov 27 18:46:26 2013 -0800
+++ b/modules/graphics/src/main/native-prism-es2/PrismES2Defs.h	Thu Nov 28 14:47:01 2013 +0400
@@ -368,8 +368,6 @@
 typedef struct PhongMaterialInfoRec PhongMaterialInfo;
 struct PhongMaterialInfoRec {
    GLfloat diffuseColor[4]; // in the order of rgba
-   GLboolean isSpecularAlpha;
-   GLboolean isBumpAlpha;
    GLuint maps[4];
 };
 
--- a/modules/graphics/src/main/resources/com/sun/prism/es2/glsl/main1Light.frag	Wed Nov 27 18:46:26 2013 -0800
+++ b/modules/graphics/src/main/resources/com/sun/prism/es2/glsl/main1Light.frag	Thu Nov 28 14:47:01 2013 +0400
@@ -54,7 +54,7 @@
 
     vec3 refl = reflect(normalize(eyePos), n);
     vec4 specular = apply_specular();
-    float power = specular.a * 32.0 + 1.0;
+    float power = specular.a;
 
     vec3 l = normalize(lightTangentSpacePositions[0].xyz);
     d = clamp(dot(n,l), 0.0, 1.0)*(lights[0].color).rgb;
@@ -64,4 +64,4 @@
     rez += apply_selfIllum().xyz;
 
     gl_FragColor = vec4(clamp(rez, 0.0, 1.0) , diffuse.a);
-}
\ No newline at end of file
+}
--- a/modules/graphics/src/main/resources/com/sun/prism/es2/glsl/main2Lights.frag	Wed Nov 27 18:46:26 2013 -0800
+++ b/modules/graphics/src/main/resources/com/sun/prism/es2/glsl/main2Lights.frag	Thu Nov 28 14:47:01 2013 +0400
@@ -53,7 +53,7 @@
 
     vec3 refl = reflect(normalize(eyePos), n);
     vec4 specular = apply_specular();
-    float power = specular.a * 32.0 + 1.0;
+    float power = specular.a;
 
     vec3 l = normalize(lightTangentSpacePositions[0].xyz);
     d = clamp(dot(n,l), 0.0, 1.0)*(lights[0].color).rgb;
--- a/modules/graphics/src/main/resources/com/sun/prism/es2/glsl/main3Lights.frag	Wed Nov 27 18:46:26 2013 -0800
+++ b/modules/graphics/src/main/resources/com/sun/prism/es2/glsl/main3Lights.frag	Thu Nov 28 14:47:01 2013 +0400
@@ -54,7 +54,7 @@
 
     vec3 refl = reflect(normalize(eyePos), n);
     vec4 specular = apply_specular();
-    float power = specular.a * 32.0 + 1.0;
+    float power = specular.a;
 
     vec3 l = normalize(lightTangentSpacePositions[0].xyz);
     d = clamp(dot(n,l), 0.0, 1.0)*(lights[0].color).rgb;
--- a/modules/graphics/src/main/resources/com/sun/prism/es2/glsl/specular_color.frag	Wed Nov 27 18:46:26 2013 -0800
+++ b/modules/graphics/src/main/resources/com/sun/prism/es2/glsl/specular_color.frag	Thu Nov 28 14:47:01 2013 +0400
@@ -23,14 +23,13 @@
  * questions.
  */
 
-//specular auto fragment shader
+// specular color fragment shader
 //#version 120
 
+uniform vec4 specularColor;
 uniform sampler2D specularMap;
 
 vec4 apply_specular()
 {
-    vec3 tSpec = texture2D(specularMap, oTexCoords).rgb;
-    float sLevel = dot(tSpec, vec3(0.299, 0.587, 0.114));
-    return vec4(tSpec, sLevel);
+    return specularColor;
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/graphics/src/main/resources/com/sun/prism/es2/glsl/specular_mix.frag	Thu Nov 28 14:47:01 2013 +0400
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+// specular mix fragment shader
+//#version 120
+
+uniform vec4 specularColor;
+uniform sampler2D specularMap;
+
+vec4 apply_specular()
+{
+    vec3 tSpec = texture2D(specularMap, oTexCoords).rgb;
+    tSpec *= specularColor.rgb;
+    float sPower = specularColor.a;
+    sPower *= dot(tSpec, vec3(0.299, 0.587, 0.114)); // Rec. 601 luma conversion
+    return vec4(tSpec, sPower);
+}
--- a/modules/graphics/src/main/resources/com/sun/prism/es2/glsl/specular_texture.frag	Wed Nov 27 18:46:26 2013 -0800
+++ b/modules/graphics/src/main/resources/com/sun/prism/es2/glsl/specular_texture.frag	Thu Nov 28 14:47:01 2013 +0400
@@ -26,10 +26,13 @@
 // specular texture fragment shader
 //#version 120
 
+uniform vec4 specularColor;
 uniform sampler2D specularMap;
 
 vec4 apply_specular()
 {
-    vec4 tSpec = texture2D(specularMap, oTexCoords);
-    return tSpec;
+    vec3 tSpec = texture2D(specularMap, oTexCoords).rgb;
+    float sPower = specularColor.a;
+    sPower *= dot(tSpec, vec3(0.299, 0.587, 0.114)); // Rec. 601 luma conversion
+    return vec4(tSpec, sPower);
 }