Blending renders together without discoloration

2016-04-18 EDIT

I’m closing this thread as solved. For the sake of people like this:

http://imgs.xkcd.com/comics/wisdom_of_the_ancients.png

I made some tweaks to the code to make it a bit more presentable, but I didn’t test it afterwards so it MAY be broken. But if you’ve made it this far, surely you can get it working!

[lua]local tex_render = render.GetSuperFPTex()
local tex_blend = render.GetSuperFPTex2()

local mat_renderholder = CreateMaterial(
“BlendingTest”, – Name
“gmodscreenspace”, – Shader
{
[ “$model” ] = “0”,
[ “$alphatest” ] = “0”,
[ “$additive” ] = “1”,
[ “$translucent” ] = “0”,
[ “$ignorez” ] = “0”
}
)

local mat_multiplier = CreateMaterial(
“BlendMultiplier”, – Name
“g_colourmodify”, – Shader
{
[ “$pp_colour_addb” ] = 0,
[ “$pp_colour_addg” ] = 0,
[ “$pp_colour_addr” ] = 0,
[ “$pp_colour_brightness” ] = 0,
[ “$pp_colour_colour” ] = 1,
[ “$pp_colour_contrast” ] = 1,
[ “$pp_colour_mulr” ] = 0,
[ “$pp_colour_mulg” ] = 0,
[ “$pp_colour_mulb” ] = 0,
[ “$ignorez” ] = 1
}
)
local mat_finish = CreateMaterial(
“BlendFinish”, – Name
“unlitgeneric”, – Shader
{
}
)

local renders = 0

local function RenderWithAlpha()
– Render the scene normally to the whole texture (with inevitable 100% alpha)
render.PushRenderTarget(tex_render)
render.RenderView()
render.PopRenderTarget()

-- Paste the result onto the actual frame, with the correct alpha
render.PushRenderTarget(tex_blend)
	mat_renderholder:SetTexture("$basetexture", tex_render)
	if renders == 0 then render.Clear(0, 0, 0, 255) end
	render.SetMaterial(mat_renderholder)
	render.DrawScreenQuad()
render.PopRenderTarget()

renders = renders + 1

end

local function FinishRender()
local mul = 1/renders
renders = 0

mat_multiplier:SetFloat("$pp_colour_contrast", mul)
mat_multiplier:SetTexture("$fbtexture", tex_blend)

render.PushRenderTarget(tex_blend)
	render.SetMaterial(mat_multiplier)
	render.DrawScreenQuad()
render.PopRenderTarget()

mat_finish:SetTexture("$basetexture", tex_blend)

render.SetMaterial(mat_finish)
render.DrawScreenQuad()

end

hook.Add(“RenderScene”, “GAAHHHHHH”, function(origin, angles, fov)
local view = {}

for i = 1, 5 do
	RenderWithAlpha()
end

FinishRender()

return true

end)[/lua]
END OF EDIT

My code moves a point of light (projected texture) and makes multiple renders in order to make soft shadows. Results currently look like this:

As you can see there is some massive discoloration in the areas where some shadows are case but not all. My current code is this:

[lua]local mat_renderholder = Material( “pp/motionblur” )
local tex_render = render.GetMoBlurTex0()

local function RenderWithAlpha(addalpha)
render.UpdateScreenEffectTexture() – not even sure if I need this or what it does

-- Render the scene normally to the whole texture (with inevitable 100% alpha)
render.PushRenderTarget(tex_render)
	render.RenderView()
render.PopRenderTarget()

-- Paste the result onto the actual frame, with the correct alpha
mat_renderholder:SetTexture("$basetexture", tex_render)	-- do I need this every time? doesn't matter, works
mat_renderholder:SetFloat("$alpha", addalpha)
render.SetMaterial(mat_renderholder)

render.DrawScreenQuad()

end

– later, inside a RenderScene hook:
for i = 1, maxlights do
– (code to move the lightsource…)

RenderWithAlpha(1 / i)

end[/lua]

Now, I’m not too surprised to see this shitty result, as I don’t really understand rendering all that well. Add the fact that it’s not documented very well in the wiki, and I’m still left quite clueless.
So my guess is that a problem lies in the RenderWithAlpha function - probably, I’m just losing a lot of data gradually by pasting more and more renders onto the same render target.
My question to you is, how would you do this? There must be a better way. What is it?

A friend suggested to try and use additive rendering but I’m not sure how he intends for that to work (he’s about as clueless as I am on the technicalities), and even less sure of how to implement it.

Bump…

I guess I’ll try to break it down to an easy question to answer. Just please someone answer, I feel so lost.

Is it possible to render a scene with colors brighter than 255 255 255, as resulted by bright lamps? (without displaying it to the screen)

I still feel lost with your question…

Do you want to get something similar to Gmod’s lamp, and their ability to basically cover whatever it projects onto, white?

Sadly, the only way to get a different blending mode other than the default one and additive is to use $detail. Dirty hack incoming:

I suggest using the _rt_FullFrameFB render target instead of calling render.RenderView as it’s pretty much the same thing, without the massive FPS loss. However, if people have motion blur turned off in their settings, you will need to call DrawMotionBlur(0, 0, 0) (Note that with 0 as the arguments, no motion blur will actually be applied) every frame for that rendertarget to update (unless you have an entity with halos somewhere, but I wouldn’t rely on that).

Create a VertexLitGeneric material (Can’t be unlitgeneric because some maps break $detail on them for whatever reason) with
$basetexture “_rt_FullFrameFB”
$detail “_your_rendertarget_name”
$detailblendmode “8” // Mess around with the blend mode to get your desired effect. 8 is multiply.

Create a rendertarget the size of the client’s resolution with the same name as your $detail and draw your shadows onto it every frame.

Then
render.SetLightingMode(2) – Hack for VertexLitGeneric
surface.SetMaterial(yourvertexlitgenericmaterial)
surface.DrawTexturedRect(x, y, w, h) – screen quad
render.SetLightingMode(0)

Here is a visual explanation for my desired output and actual output: http://imgur.com/a/cdWnV

Thank you, I will try this out tomorrow!

I should probably mention that I don’t know the first thing about creating materials. Do I have to do this in a file, or is it possible to do it from inside lua?

You can always use CreateMaterial() if you don’t want to create a vmt file. That would be your best bet. Dynamically created materials will break when materials are reloaded from something such as mat_reloadallmaterials or after a player changes their video settings. You’ll also need to re-generate your rendertarget if the resolution changes and edit your material accordingly. This should do the trick to provide you with two hooks you can use:




hook.Add("Initialize", "Resolution / Change", function()
	vgui.CreateFromTable {
		Base = "Panel",

		PerformLayout = function()
			hook.Run("ResolutionChanged", ScrW(), ScrH())
		end

	} : ParentToHUD()

	local mat = CreateMaterial("mat_hook_reload", "VertexLitGeneric", {["$baseTexture"] = 1})
	hook.Add("PreRender", "Materials Reloaded", function()
		if (mat:GetInt("$baseTexture") != 1) then
			hook.Run("MaterialsInvalidated")
			mat:SetInt("$baseTexture", 1)
		end
	end)
end)



It actually shouldn’t be a problem, as my script is run as a console command and is only run for one frame (or more precisely, one run-through of the poster command). I can just generate the material each time the script is run.

Okay, I finally got a chance to fiddle with this, and then… I spent so much time trying to read up on it that I didn’t even try it, and now it’s late and there’s no time left. ><

But I did come up with a few questions that I hope to understand better:

I need to render, like, 100 different renders and blend them all together equally. Now, I don’t expect nor want a ready solution for that, but just in general… is that $detail method going to work well, do you think, for combining that many?

render.GetSuperFPTex (and render.GetSuperFPTex2), what do they return? Could it be useful?

Garry uses things like Material( “pp/motionblur” ) and Material( “pp/fb” )… Is there a list somewhere of the available materials and what they do? What do these two do? What IS a material, even?

Sorry for the noob questions…

$detail could work if both $detail and $basetexture are rendertargets. An example could be: (pseudocode-ish)



local detailmat = CreateMaterial("_shadowdetailtest""UnlitGeneric",
{
    "$basetexture" "_rt_shadow_base",
    "$detail" "_rt_shadow_overlay",
    ...
})

CreateRenderTarget("_rt_shadow_base", ...)
CreateRenderTarget("_rt_shadow_overlay", ...)

local rt = render.GetRenderTarget()

for i=0, 100 do
    render.SetRenderTarget(detailmaterial:GetTexture("$detail"))
    --Draw your shadow here (Note that it can *only* be the shadow, which might get very tricky, unless you find a good blend mode where it'll only darken darkened pixels


    -- We write our two layers back into the first layer
    render.SetRenderTarget(detailmaterial:GetTexture("$basetexture"))
    surface.SetMaterial(detailmat)
    surface.SetDrawColor(Color(255, 255, 255))
    surface.DrawTexturedRect(0, 0, ScrW(), ScrH()

end

render.SetRenderTarget(rt)


A material is simply a way to define a texture and how it should be used/drawn. A texture is the actual image.

But, if this will only be run once, you could easily get away with using render.capture and manipulating the pixels yourself