I am developing a GPU-based paint program that draws quads in OpenGL with a paint shader into a rendertexture canvas. The paint shader makes use of the rendertexture for blending.
This strategy works fine in DirectX9 as seen in this screenshot:
However, when I switch over to DirectX11, it looks like the paint shader treats the rendertexture as a black texture, as seen in the next screenshot.
I have stripped down the code into a single script:
using UnityEngine;
using System.Collections;
public class PaintTool : MonoBehaviour {
public static Vector4 screenDimensions;
private RenderTexture colorTexture;
public Color lineColor = Color.green;
public float brushRadius = 100;
private Color paintColor;
public Material paintMaterial;
private Vector2 pos;
public Material clearColorMaterial;
protected void Awake()
{
float swf = Screen.width;
float shf = Screen.height;
screenDimensions = new Vector4(swf, shf, 1.0f / swf, 1.0f / shf);
Shader.SetGlobalVector("_ScreenDimensions", screenDimensions);
colorTexture = new RenderTexture((int)screenDimensions.x, (int)screenDimensions.y, 0, RenderTextureFormat.ARGB32);
GetComponent<Renderer>().material.SetTexture("_MainTex", colorTexture);
paintMaterial.SetTexture("_MainTex", colorTexture);
colorTexture.filterMode = FilterMode.Point;
Graphics.Blit(null, colorTexture, clearColorMaterial);
}
public void Update()
{
if (Input.GetMouseButton (0)) {
pos = Input.mousePosition;
paintMaterial.SetFloat("_Radius", brushRadius);
paintMaterial.SetVector("_FromPosition", pos);
paintMaterial.SetVector("_ToPosition", pos);
RenderOntoTexture(paintMaterial, BoundingRect(), colorTexture);
}
}
//Renders a rectangle in screen space using a certain paintMaterial ontop of a full screen background material
protected virtual void RenderOntoTexture(Material paintMaterial, Rect paintBounds, RenderTexture renderOnto)
{
GL.PushMatrix();
GL.LoadPixelMatrix(0, screenDimensions.x, 0, screenDimensions.y);
RenderTexture.active = renderOnto;
//Render the altered offsets in a bounded rectangle (should produce good performance)
RenderMaterialRect(paintMaterial, paintBounds);
RenderTexture.active = null;
GL.PopMatrix();
}
//Subtract one due to screen's apparent "off by one" issue
public static Rect ScreenRectToUV(Rect screenBounds)
{
Vector2 screenInv = new Vector2(screenDimensions.z, screenDimensions.w);
Vector2 min = Vector2.Scale(screenBounds.min, screenInv);
Vector2 max = Vector2.Scale(screenBounds.max, screenInv);
return Rect.MinMaxRect(min.x, min.y, max.x, max.y);
}
protected virtual void RenderMaterialRect(Material m, Rect boundsRect)
{
//Determine UV Coordinates based on Screen Rectangle
Rect uvRect = ScreenRectToUV(boundsRect);
// activate the first pass (in this case we know it is the only pass)
m.SetPass (0);
// draw a quad
GL.Begin (GL.QUADS);
GL.TexCoord2 (uvRect.xMin, uvRect.yMin); GL.Vertex3 (boundsRect.xMin, boundsRect.yMin, 0.1f);
GL.TexCoord2 (uvRect.xMax, uvRect.yMin); GL.Vertex3 (boundsRect.xMax, boundsRect.yMin, 0.1f);
GL.TexCoord2 (uvRect.xMax, uvRect.yMax); GL.Vertex3 (boundsRect.xMax, boundsRect.yMax, 0.1f);
GL.TexCoord2 (uvRect.xMin, uvRect.yMax); GL.Vertex3 (boundsRect.xMin, boundsRect.yMax, 0.1f);
GL.End ();
}
public Rect BoundingRect()
{
float minX = Mathf.Max (0, pos.x - brushRadius);
float maxX = Mathf.Min (screenDimensions.x, pos.x + brushRadius);
float minY = Mathf.Max (0, pos.y - brushRadius);
float maxY = Mathf.Min (screenDimensions.y, pos.y + brushRadius);
return Rect.MinMaxRect(minX, minY, maxX, maxY);
}
}
Here is the shader code that does the painting:
Shader "Custom/Paint" {
Properties {
_Radius ("Radius (Screen Space)", Float) = 128.0
_FromPosition ("From Position (Screen Space)", Vector) = (0,0,0,0)
_ToPosition("To Position (Screen Space)", Vector) = (0,0,0,0)
_RadialPaint("Radial Ease Buffer", 2D) = "" {}
_MainTex("Current Color Texture", 2D) = "" {}
_Color("Main Color", Color) = (0,0,0,0)
}
SubShader {
Lighting Off
Blend One Zero, One Zero
Cull Off
ZWrite Off
Fog { Mode Off }
ZTest Always
Pass {
Tags { "RenderType" = "Opaque" }
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag
uniform float4 _Color;
uniform sampler2D _MainTex;
uniform sampler2D _RadialPaint;
uniform float _Radius;
//From Position in Screen coordinates
uniform float4 _FromPosition;
//To Position in Screen coordinates
uniform float4 _ToPosition;
//ScreenDimensions (zw are inverted)
uniform float4 _ScreenDimensions;
#include "UnityCG.cginc"
inline float DistanceToSegment(float2 v, float2 p0, float2 p1)
{
float2 p = p1 - p0;
float2 w = v - p0;
float c1 = dot(w, p);
if(c1 <= 0)
{
return distance(v, p0);
}
else
{
float c2 = dot(p, p);
if(c2 <= c1)
{
return distance(v, p1);
}
else
{
float b = c1 / c2;
float2 pb = p0 + b * p;
return distance(v, pb);
}
}
}
float4 frag(v2f_img i) : COLOR {
float2 screenPos = i.uv * _ScreenDimensions.xy;
float2 fromPos = _FromPosition.xy;
float2 toPos = _ToPosition.xy;
float d = DistanceToSegment(screenPos, fromPos, toPos);
float easeValue = 1.0f - d / _Radius;
float4 currentColor = tex2D(_MainTex, i.uv);
float radiusClamp = ceil(saturate(_Radius - d));
//Paint amount is scaled by distance from radius (Currently linear), and by distance from starting point
return (1.0 - easeValue * radiusClamp) * currentColor + radiusClamp * easeValue * _Color;
}
ENDCG
}
}
}
Is there some aspect of DX11 that is preventing this strategy from working?
I am attaching a unitypackage containing this test. Switch to/from DX9/11 in BuildSettings to see the effect.
Any help or advice is appreciated.
2187979–145139–PaintTest.unitypackage (15.7 KB)