MirrorReflectionLightmapped

From Unify Community Wiki
Jump to: navigation, search

Author: Aras Pranckevicius

Contents

Description

This is shader+script to make perfectly reflective mirrors that have a texture and a lightmap. Use the "FX/Mirror Reflection Lightmapped" shader on an object, attach the MirrorReflection script to it and there you are.

Works on three texture cards (Radeon 7000, GeForce3/4Ti, Intel 8xx). Requires Unity Pro.

Usage

Prerequisites: This technique requires Unity Pro.

  • Create a material that uses the shader below (FX/Mirror Reflection Lightmapped).
  • Use this material on a plane-like (i.e. flat) object.
  • Attach the MirrorReflection script to the same object.

You can supply a main texture (uses 1st UV set) and a lightmap texture (uses 2nd UV set). Main texture's alpha channel (combined with material color alpha channel) defines the "reflectivity" of surface. So if you want to make reflective water puddles, just draw them into texture's alpha.

Notes:

  • The reflection happens along object's "up" direction (green axis in the scene view). E.g. the builtin plane object is suitable for use as a mirror. If you experience weird reflection, check whether your mirror object is oriented correctly.
  • Reflection calculations use Main Camera (i.e. camera tagged as Main Camera). Be careful not to have multiple cameras tagged as Main in the scene, as you won't know which one is picked up!

Example

A small package including an example scene showing MirrorReflectionLightmapped can be found here:

ShaderLab - MirrorReflectionLightmapped.shader

Invalid language.

You need to specify a language like this: <source lang="html4strict">...</source>

Supported languages for syntax highlighting:

4cs, 6502acme, 6502kickass, 6502tasm, 68000devpac, abap, actionscript, actionscript3, ada, algol68, apache, applescript, apt_sources, asm, asp, autoconf, autohotkey, autoit, avisynth, awk, bascomavr, bash, basic4gl, bf, bibtex, blitzbasic, bnf, boo, c, c_loadrunner, c_mac, caddcl, cadlisp, cfdg, cfm, chaiscript, cil, clojure, cmake, cobol, coffeescript, cpp, cpp-qt, csharp, css, cuesheet, d, dcs, delphi, diff, div, dos, dot, e, ecmascript, eiffel, email, epc, erlang, euphoria, f1, falcon, fo, fortran, freebasic, fsharp, gambas, gdb, genero, genie, gettext, glsl, gml, gnuplot, go, groovy, gwbasic, haskell, hicest, hq9plus, html4strict, html5, icon, idl, ini, inno, intercal, io, j, java, java5, javascript, jquery, kixtart, klonec, klonecpp, latex, lb, lisp, llvm, locobasic, logtalk, lolcode, lotusformulas, lotusscript, lscript, lsl2, lua, m68k, magiksf, make, mapbasic, matlab, mirc, mmix, modula2, modula3, mpasm, mxml, mysql, newlisp, nsis, oberon2, objc, objeck, ocaml, ocaml-brief, oobas, oracle11, oracle8, oxygene, oz, pascal, pcre, per, perl, perl6, pf, php, php-brief, pic16, pike, pixelbender, pli, plsql, postgresql, povray, powerbuilder, powershell, proftpd, progress, prolog, properties, providex, purebasic, pycon, python, q, qbasic, rails, rebol, reg, robots, rpmspec, rsplus, ruby, sas, scala, scheme, scilab, sdlbasic, smalltalk, smarty, sql, systemverilog, tcl, teraterm, text, thinbasic, tsql, typoscript, unicon, uscript, vala, vb, vbnet, verilog, vhdl, vim, visualfoxpro, visualprolog, whitespace, whois, winbatch, xbasic, xml, xorg_conf, xpp, yaml, z80, zxbasic


Shader "FX/Mirror Reflection Lightmapped" {
Properties {
    _Color ("Main Color", Color) = (1,1,1,1)
    _MainTex ("Base (RGB) Gloss (A)", 2D) = "white" {}
    _LightMap ("Lightmap (RGB)", 2D) = "black" {}
    _ReflectionTex ("Environment Reflection", 2D) = "" { TexGen ObjectLinear }
}  

// -----------------------------------------------------------
// Texture, Lightmap, Reflection; three texture cards

Subshader {
    // Ambient pass
    Pass {
        Color [_Color]
        BindChannels {
            Bind "Vertex", vertex
            Bind "texcoord", texcoord0 // main uses 1st uv
            Bind "texcoord1", texcoord1 // lightmap uses 2nd uv
        }
        SetTexture [_MainTex] {
            combine texture * primary
        }
        SetTexture [_LightMap] {
            combine texture * previous, previous
        }
        SetTexture [_ReflectionTex] {
            matrix [_ProjMatrix]
            combine texture lerp(previous) previous, texture
        }
    }
}

// -----------------------------------------------------------
// Fallback: just base texture

Subshader {
    Pass {
        SetTexture [_MainTex] { constantColor[_Color] combine texture * constant }
    }
}

}

JavaScript - MirrorReflection.js

// The script is quite long, most of the code deals with oblique-clipped
// projection matrices and whatnot. The code is very similar to
// what ReflectionRenderTexture script in Pro Standard Assets does,
// but I included everything into a single file to avoid confusion and
// dependencies.
 
var renderTextureSize = 256;
var clipPlaneOffset = 0.01;
var disablePixelLights = true;
 
private var renderTexture : RenderTexture;
private var restorePixelLightCount : int;
private var sourceCamera : Camera; // The camera we are going to reflect
 
 
function Start()
{
    if( !RenderTexture.enabled ) {
        print("Render textures are not available. Disabling mirror script");
        enabled = false;
        return;
    }
 
    renderTexture = new RenderTexture( renderTextureSize, renderTextureSize, 16 );
    renderTexture.isPowerOfTwo = true;
 
    gameObject.AddComponent("Camera");
    var cam : Camera = camera;
    var mainCam = Camera.main;
    cam.targetTexture = renderTexture;
    cam.clearFlags = mainCam.clearFlags;
    cam.backgroundColor = mainCam.backgroundColor;
    cam.nearClipPlane = mainCam.nearClipPlane;
    cam.farClipPlane = mainCam.farClipPlane;
    cam.fieldOfView = mainCam.fieldOfView;
 
    renderer.material.SetTexture("_ReflectionTex", renderTexture);
}
 
function Update()
{
    var scaleOffset = Matrix4x4.TRS( Vector3(0.5,0.5,0.5), Quaternion.identity, Vector3(0.5,0.5,0.5) );
    renderer.material.SetMatrix( "_ProjMatrix",
        scaleOffset *
        camera.main.projectionMatrix *
        camera.main.worldToCameraMatrix *
        transform.localToWorldMatrix );
}
 
function OnDisable() {
    Destroy(renderTexture);
}
 
 
function LateUpdate ()
{
    // Use main camera for reflection
    sourceCamera = Camera.main;
 
    // Figure out if we can do reflection/refraction
    if (!RenderTexture.enabled)
    {
        camera.enabled = false; // no RTs - can't do
    }
    else if (camera.targetTexture == null)
    {
        Debug.Log ("No Render Texture assigned! Disabling reflection.");
        camera.enabled = false;
    }
    else if( !sourceCamera )
    {
        Debug.Log ("Reflection rendering requires that a Camera that is tagged \"MainCamera\"! Disabling reflection.");
        camera.enabled = false;
    }
    else
    {
        camera.enabled = true;
    }
}
 
function OnPreCull ()
{
    sourceCamera = Camera.main;
    if( sourceCamera )
    {            
        // find out the reflection plane: position and normal in world space
        var pos = transform.position;
        var normal = transform.up;
 
        // need to reflect the source camera around reflection plane
        var d = -Vector3.Dot (normal, pos) - clipPlaneOffset;
        var reflectionPlane = Vector4 (normal.x, normal.y, normal.z, d);
 
        var reflection = CalculateReflectionMatrix(reflectionPlane);
        camera.worldToCameraMatrix = sourceCamera.worldToCameraMatrix * reflection;
 
        // Setup oblique projection matrix so that near plane is our reflection
        // plane. This way we clip everything below/above it for free.
        var clipPlane = CameraSpacePlane( pos, normal );
        camera.projectionMatrix = CalculateObliqueMatrix( sourceCamera.projectionMatrix, clipPlane );
    }
    else
    {
        camera.ResetWorldToCameraMatrix ();
    }
}
 
function OnPreRender ()
{
    // we need to revert backface culling
    GL.SetRevertBackfacing (true);
 
    if( disablePixelLights )
    {
        restorePixelLightCount = Light.pixelLightCount;
        Light.pixelLightCount = 0;
    }
}
 
function OnPostRender ()
{
    // restore the backface culling
    GL.SetRevertBackfacing (false);
 
    if( disablePixelLights )
        Light.pixelLightCount = restorePixelLightCount;
}
 
// Given position/normal of the plane, calculates plane in camera space.
function CameraSpacePlane( pos : Vector3, normal : Vector3 ) : Vector4
{
    var offsetPos = pos + normal * clipPlaneOffset;
    var m = camera.worldToCameraMatrix;
    var cpos = m.MultiplyPoint( offsetPos );
    var cnormal = m.MultiplyVector( normal ).normalized;
    return Vector4( cnormal.x, cnormal.y, cnormal.z, -Vector3.Dot(cpos,cnormal) );
}
 
// Extended sign: returns -1, 0 or 1 based on sign of a
static function sgn(a : float) : float
{
    if (a > 0.0) return 1.0;
    if (a < 0.0) return -1.0;
    return 0.0;
}
 
// Adjusts the given projection matrix so that near plane is the given clipPlane
// clipPlane is given in camera space. See article in GPG5.
static function CalculateObliqueMatrix( projection : Matrix4x4, clipPlane : Vector4 ) : Matrix4x4
{
    var q : Vector4;
    q.x = (sgn(clipPlane.x) + projection[8]) / projection[0];
    q.y = (sgn(clipPlane.y) + projection[9]) / projection[5];
    q.z = -1.0;
    q.w = (1.0 + projection[10]) / projection[14];
 
    var c = clipPlane * (2.0 / (Vector4.Dot (clipPlane, q)));
 
    projection[2] = c.x;
    projection[6] = c.y;
    projection[10] = c.z + 1.0;
    projection[14] = c.w;
 
    return projection;
}
 
// Calculates reflection matrix around the given plane
static function CalculateReflectionMatrix( plane : Vector4 ) : Matrix4x4
{
    var reflectionMat : Matrix4x4;
    reflectionMat.m00 = (1 - 2*plane[0]*plane[0]);
    reflectionMat.m01 = (  - 2*plane[0]*plane[1]);
    reflectionMat.m02 = (  - 2*plane[0]*plane[2]);
    reflectionMat.m03 = (  - 2*plane[3]*plane[0]);
 
    reflectionMat.m10 = (  - 2*plane[1]*plane[0]);
    reflectionMat.m11 = (1 - 2*plane[1]*plane[1]);
    reflectionMat.m12 = (  - 2*plane[1]*plane[2]);
    reflectionMat.m13 = (  - 2*plane[3]*plane[1]);
 
    reflectionMat.m20 = (  - 2*plane[2]*plane[0]);
    reflectionMat.m21 = (  - 2*plane[2]*plane[1]);
    reflectionMat.m22 = (1 - 2*plane[2]*plane[2]);
    reflectionMat.m23 = (  - 2*plane[3]*plane[2]);
 
    reflectionMat.m30 = 0;
    reflectionMat.m31 = 0;
    reflectionMat.m32 = 0;
    reflectionMat.m33 = 1;
    return reflectionMat;
}
Personal tools
Namespaces

Variants
Actions
Navigation
Extras
Tools