using System.Collections; using System.Collections.Generic; using System.Text.RegularExpressions; using UnityEngine; namespace Flockaroo { [ExecuteInEditMode] [RequireComponent(typeof (Camera))] //[AddComponentMenu("Image Effects/Artistic/OilPainting")] public class OilPaintingEffect : MonoBehaviour { List bufferOrder = new List (); Dictionary buffers = new Dictionary(); Dictionary shaders = new Dictionary(); Dictionary textures = new Dictionary(); Dictionary> textureCh = new Dictionary>(); Dictionary> textureDemandsMip = new Dictionary>(); Dictionary> meshes = new Dictionary>(); RenderTexture mainTex = null; RenderTexture mainMip = null; Regex refRegex = new Regex(@"Ref:([^:]+):Tex([0-9]+)"); private int actWidth=0; private int actHeight=0; private int NumTriangles=0; //FIXME: automate useMipOnMain - activate when needed private bool useMipOnMain = false; [Header("Input/Output")] [Tooltip("take a texture as input instead of the camera")] public Texture inputTexture; [Tooltip("render to a texture instead of the screen")] public bool renderToTexture; [Tooltip("texture being rendered to if above is checked")] public RenderTexture outputTexture; [Tooltip("generate mipmap for output texture")] public bool outputMipmap; [Header("Common")] [Range(0.0f,1.0f)] public float EffectFade = 0.0f; [Range(0.0f,1.0f)] public float PanFade = 0.0f; [Header("Source")] [Range(0.0f,2.0f)] public float SrcBright = 1.0f; [Range(0.0f,2.0f)] public float SrcContrast = 1.4f; [Range(0.0f,4.0f)] public float SrcColor = 1.0f; [Range(0.0f,1.0f)] public float SrcBlur = 0.0f; [Header("Effect")] [Range(0.0f,1.0f)] public float BrushDetail = 0.1f; [Range(0.0f,1.0f)] public float BrushFill = 0.5f; [Range(5000,200000)] public int NumStrokes=0x8000; [Range(0.5f,0.95f)] public float LayerScale = 0.8f; [Range(0.0f,1.0f)] public float Canvas = 0.4f; [Range(0.0f,100.0f)] public float FlickerFreq = 15.0f; [Range(0.0f,1.0f)] public float FlickerStrength = 0.0f; [Range(0.0f,360.0f)] public float LightAng = 135.0f; [Range(0.0f,90.0f)] public float LightOffs = 60.0f; [Range(0.0f,1.0f)] public float PaintDiff = 0.15f; [Range(0.0f,1.0f)] public float PaintSpec = 0.15f; [Range(0.0f,1.0f)] public float PaintShiny = 0.5f; [Range(0.0f,2.0f)] public float ColorSpread = 0.0f; [Range(0.0f,120.0f)] public float ScreenFOV = 0.0f; [Range(0.0f,360.0f)] public float StrokeAng = 0.0f; [Range(-1.0f,1.0f)] public float StrokeBend = -1.0f; [Range(0.0f,1.0f)] public float StrokeDir = 0.0f; [Range(0.0f,1.0f)] public float StrokeContour = 1.0f; [Range(0.0f,16.0f)] public float MultiStroke = 6.0f; [Range(0.0f,100000.0f)] public float strokeSeed = 10.0f; [Range(0.0f,1.0f)] public float Vignette = 1.0f; [Range(0.0f,1.0f)] public float CanvasBg = 0.5f; public Color CanvasTint = new Color(1.0f,0.97f,0.85f); //###PublicVars [Header("Other")] public bool flipY=false; public bool geomFlipY=false; Material createShader(string resname) { Shader shader = Resources.Load(resname); Material mat = new Material(shader); mat.hideFlags = HideFlags.HideAndDontSave; return mat; } RenderTexture createRenderTex(int w = -1, int h = -1, bool mip = false, int aa = 1) { RenderTexture rt; //if(w==-1) w=Screen.width; //if(h==-1) h=Screen.height; if(w==-1) w=actWidth; if(h==-1) h=actHeight; rt = new RenderTexture(w, h,0,RenderTextureFormat.ARGBFloat); rt.antiAliasing=aa; // must be 1 for mipmapping to work!! rt.useMipMap=mip; if(mip) rt.filterMode=FilterMode.Trilinear; return rt; } Texture2D createRandTex(int w, int h) { //if (RandTex == null) // RandTex = Resources.Load("rand256"); Texture2D RandTex; { RandTex = new Texture2D(w, h, TextureFormat.RGBAFloat, true); //RandTex = new Texture2D(w, h, TextureFormat.RGBAHalf, true); //RandTex = new Texture2D(w, h, TextureFormat.RGBA32, true); for (int x = 0; x < RandTex.width; x++) { for (int y = 0; y < RandTex.height; y++) { float r = Random.Range(0.0f, 1.0f); float g = Random.Range(0.0f, 1.0f); float b = Random.Range(0.0f, 1.0f); float a = Random.Range(0.0f, 1.0f); RandTex.SetPixel(x, y, new Color(r, g, b, a) ); } } RandTex.Apply(); } RandTex.filterMode=FilterMode.Trilinear; return RandTex; } List createMesh(int trinum = 0x10000) { List meshes = new List(); int maxMeshSize = 0x10000/3*3; int mnum = (trinum*3+maxMeshSize-1)/maxMeshSize; for(int j=0;j (); textureDemandsMip["Buff_A"] = new Dictionary (); shaders["Buff_A"] = createShader("flockaroo_OilPainting/Buff_A"); textureCh["Buff_A"][0] = "none"; textureCh["Buff_A"][1] = "rand256"; textureCh["Buff_A"][2] = "Buff_A"; buffers["Buff_A"].width=512; buffers["Buff_A"].height=512; buffers["Buff_A"].useMipMap=true; buffers["Buff_A"].filterMode=FilterMode.Trilinear; bufferOrder.Add("Geom_A"); buffers["Geom_A"] = createRenderTex(); buffers["Geom_A"].depth = 24; textureCh["Geom_A"] = new Dictionary (); textureDemandsMip["Geom_A"] = new Dictionary (); shaders["Geom_A"] = createShader("flockaroo_OilPainting/Geom_A"); meshes["Geom_A"] = createMesh(NumStrokes*2); NumTriangles=NumStrokes*2; textureCh["Geom_A"][0] = "https://ak1.picdn.net/shutterstock/videos/15325111/preview/stock-footage-beautiful-young-woman-enjoying-her-vacation-on-santorini-happy-tourist-is-wearing-sunhat.webm"; textureCh["Geom_A"][1] = "rand256"; textureCh["Geom_A"][2] = "Buff_A"; textureDemandsMip["Geom_A"][0]=true; bufferOrder.Add("Image"); buffers["Image"] = createRenderTex(); textureCh["Image"] = new Dictionary (); textureDemandsMip["Image"] = new Dictionary (); shaders["Image"] = createShader("flockaroo_OilPainting/Image"); textureCh["Image"][0] = "Geom_A"; textureCh["Image"][1] = "rand256"; textureCh["Image"][2] = "Buff_A"; textureCh["Image"][3] = "https://ak1.picdn.net/shutterstock/videos/15325111/preview/stock-footage-beautiful-young-woman-enjoying-her-vacation-on-santorini-happy-tourist-is-wearing-sunhat.webm"; //###InitMarker } void Start () { //initAll(Screen.width,Screen.height); } // Update is called once per frame void Update () { } Texture getTexture(string name) { if(name.StartsWith("Ref:")) { Match match = refRegex.Match(name); if (match.Success) { string buff = match.Groups[1].Value; int chan = int.Parse(match.Groups[2].Value); return getTexture(textureCh[buff][chan]); } return null; } if(buffers.ContainsKey(name)) return buffers[name]; if(textures.ContainsKey(name)) return textures[name]; if(name.EndsWith(".mp4")) return mainTex; if(name.EndsWith(".webm")) return mainTex; return mainTex; // FIXME: alloc textures if not present //return textures["rand256"]; } private void OnRenderImage(RenderTexture src, RenderTexture dest) { mainTex=src; bool reinit=false; if(inputTexture) { mainTex = new RenderTexture(inputTexture.width, inputTexture.height, 0, RenderTextureFormat.ARGBFloat); Graphics.Blit(inputTexture, mainTex); } if (renderToTexture && outputTexture==null) { reinit=true; } if (!renderToTexture && outputTexture!=null) { reinit=true; } if( mainTex.width!=actWidth || mainTex.height!=actHeight || reinit ) { Debug.Log("OilPainting 1st init (or Resolution changed)"); initAll(mainTex.width,mainTex.height); } if(NumTriangles!=NumStrokes*2) { meshes["Geom_A"] = createMesh(NumStrokes*2); NumTriangles=NumStrokes*2; } if(useMipOnMain) { initMainMipmapRenderTexture(mainTex); Graphics.Blit(mainTex, mainMip); mainTex = mainMip; } foreach( string buffName in bufferOrder ) { Material mat = null; if(shaders.ContainsKey(buffName)) mat = shaders[buffName]; if(mat==null) { continue; } mat.SetFloat("geomFlipY", geomFlipY?1.0f:0.0f); mat.SetFloat("flipY", flipY?1.0f:0.0f); mat.SetInt("_FrameCount", Time.frameCount); mat.SetFloat("BrushDetail",(BrushDetail*0.8f+0.2f)*(BrushDetail*0.8f+0.2f)); mat.SetFloat("BrushSize",BrushFill*2.0f+0.25f); mat.SetFloat("Canvas",Canvas); mat.SetFloat("EffectFade",EffectFade); mat.SetFloat("FlickerFreq",FlickerFreq); mat.SetFloat("FlickerStrength",FlickerStrength); mat.SetFloat("halfFOV",ScreenFOV/180.0f*3.14159265359f*0.5f); mat.SetFloat("LayerScale",LayerScale); mat.SetFloat("LightAng",LightAng/180.0f*3.14159265359f); mat.SetFloat("LightOffs",LightOffs/180.0f*3.14159265359f); mat.SetFloat("NumTriangles",NumStrokes*2); mat.SetFloat("PaintDiff",PaintDiff); mat.SetFloat("PaintSpec",PaintSpec); mat.SetFloat("PaintShiny",PaintShiny); mat.SetFloat("PanFade",PanFade); mat.SetFloat("SrcBlur",SrcBlur); mat.SetFloat("SrcBright",SrcBright); mat.SetFloat("SrcContrast",SrcContrast); mat.SetFloat("SrcColor",SrcColor); mat.SetFloat("ColorSpread",ColorSpread); mat.SetFloat("StrokeAng",StrokeAng/180.0f*3.14159265359f); mat.SetFloat("StrokeBend",StrokeBend); mat.SetFloat("StrokeContour",StrokeContour); mat.SetFloat("StrokeDir",StrokeDir); mat.SetVector("strokeNumXY",new Vector4(MultiStroke,MultiStroke,0.0f,0.0f)); mat.SetFloat("strokeSeed",strokeSeed); mat.SetFloat("Vignette",Vignette); mat.SetColor("CanvasTint", CanvasTint); mat.SetFloat("CanvasBg", CanvasBg); //###MatParams for(int i=0;i<8;i++) { if(textureCh.ContainsKey(buffName) && textureCh[buffName].ContainsKey(i)) { Texture tex = getTexture(textureCh[buffName][i]); if(mat!=null) mat.SetTexture("iChannel"+i, tex); if(textureDemandsMip.ContainsKey(buffName) && textureDemandsMip[buffName].ContainsKey(i) && textureDemandsMip[buffName][i]) { if(tex==mainTex) useMipOnMain=true; else if(tex is RenderTexture) ((RenderTexture)tex).useMipMap=true; } } } if(meshes.ContainsKey(buffName)) { Graphics.SetRenderTarget(buffers[buffName]); GL.Clear(true, true, Color.clear); if(mat!=null) mat.SetPass(0); foreach(Mesh mesh in meshes[buffName]) { Graphics.DrawMeshNow(mesh, Vector3.zero, Quaternion.identity); } } else { if(buffName=="Image") { if(mat!=null) if(outputTexture) { Graphics.Blit(mainTex, outputTexture, mat); // default blit of screen - no effect Graphics.Blit(src, dest); } else Graphics.Blit(mainTex, dest, mat); } else { if(mat!=null) Graphics.Blit(mainTex, buffers[buffName], mat); } } } } /*public void OnPostRender() { }*/ } }