CineMut shader family - Opaque

For collaboration on developing the mod capabilities of VS; request new features, report bugs, or suggest improvements

Moderator: Mod Contributor

Post Reply
chuck_starchaser
Elite
Elite
Posts: 8014
Joined: Fri Sep 05, 2003 4:03 am
Location: Montreal
Contact:

Post by chuck_starchaser »

When I went to make the translation between damage.blue channel to actual dielectric constatnt, I almost simply
multiplied by 3.0... Luckily I googled up on dielectric constants of materials...
Some exotic ones go as high as 100,000.

Problem is, the blue channel in DDS is 5-bit precision; i.e.: we only got 32 shades. So we absolutely need some
snazzy linearity trick to make each of those precious few taps count.
Most dielectric materials we're familiar with fall in the 1.1 to 3.0 range; the range from 3.0 to 10.0 is a lot sparser.

I played around with Graph and came up with a function that map 0 to 31/32 at the input, to 1.0 to 300.0 at
the output.
Another of those easy to compute superexponential things, of the form ((K+f(x))/(K-f(x)))^N:

Image

Gives me over half the input range just to cover the 1.0 to 3.0 dielectric constant span; then goes up like crazy.

Problem is, my fresnel formula from the last post is not tested to the 300 range, and probably won't work.
I'll have to revisit the fresnel algorithm, anyhow; but another question nags at me: What's a *useful* range?

I think it's 10, really.
Fresnel reflectivity has a minimum: When you look straight at a surface of a dielectric (normal to it), you have
a minimum of reflectivity given by the formula ((1-k)/(1+k))^2
(A simplification: 1 is the dielectric constant of vacuum and air.)
So, for glass, k is about 1.5, so -0.5/2.5 = -0.2; and -0.2^2 = 0.04; so glass has a minimum reflectivity
of 4 % when looked at straight (and then increases with angle).

Now, what happens with a material with a dielectric constant of 10?
-9/11 = -0.81818181...; squared it's 0.67.
So, looking at right angles it reflects 2/3 of the light.
Well, what can I say?; I'm just not used to seeing such materials.
Diamond is supposedly 5.5 to 10 dielectric constant (I know; I said it was 2.5; that's what I found at one
website, but then 3 other websites said 5.5 to 10 range.)

So, I think I'm going to rework the formula to span the 1 to 10 range, and then revisit the fresnel formula.

EDIT:
I just plotted true fresnel reflectivity as a function of cos of angle and dielectric constants from 1.1 to 10.0 (random polarization):

Image

Chew it!
It will be a myracle if I find a simplified, approximation formula for that.
safemode
Developer
Developer
Posts: 2150
Joined: Mon Apr 23, 2007 1:17 am
Location: Pennsylvania
Contact:

Post by safemode »

well with the following equation
((a - b*((1-(1-a^2)/b^2)^(1/2)))/(a + b*((1-(1-a^2)/b^2)^(1/2))))^2+(((((1-(1-a^2)/b^2))^(1/2)-a*b))/((1-(1-a^2)/b^2)^(1/2)+a*b))^2

I get the following pretty type equation.
Image

Then this reduces to

Image


Though maybe i translated wrong.


edit: Add your .5 constant outside all of that. Wont effect the simplification.
Image
Ed Sweetman endorses this message.
chuck_starchaser
Elite
Elite
Posts: 8014
Joined: Fri Sep 05, 2003 4:03 am
Location: Montreal
Contact:

Post by chuck_starchaser »

Thanks! What software are you using?
I wonder if this wouldn't simplify further... gonna try expanding the denominator:

(ab+sqrt((a^2-1)/b^2+1))^2=
=a^2*b^2+2*a*b*sqrt((a^2-1)/b^2+1)+((a^2-1)/b^2+1)

(a+b*sqrt((a^2-1)/b^2+1))^2=
=a^2+2*a*b*sqrt((a^2-1)/b^2+1)+b^2*((a^2-1)/b^2+1)

multiplying the two scary things we get the horror:
a^2*b^2*a^2
+a^2*b^2*2*a*b*sqrt((a^2-1)/b^2+1)
+a^2*b^2*b^2*((a^2-1)/b^2+1)
+2*a*b*sqrt((a^2-1)/b^2+1)*a^2
+2*a*b*sqrt((a^2-1)/b^2+1)*2*a*b*sqrt((a^2-1)/b^2+1)
+2*a*b*sqrt((a^2-1)/b^2+1)*b^2*((a^2-1)/b^2+1)
+((a^2-1)/b^2+1)*a^2
+((a^2-1)/b^2+1)*2*a*b*sqrt((a^2-1)/b^2+1)
+((a^2-1)/b^2+1)*b^2*((a^2-1)/b^2+1)=

Let's do one at a time:

a^2*b^2*a^2=
=a^4*b^2

a^2*b^2*2*a*b*sqrt((a^2-1)/b^2+1)=
=2*a^3*b^3*sqrt((a^2-1)/b^2+1)

a^2*b^2*b^2*((a^2-1)/b^2+1)=
=a^2*b^4*((a^2-1)/b^2+1)

2*a*b*sqrt((a^2-1)/b^2+1)*a^2=
=2*a^3*b*sqrt((a^2-1)/b^2+1)

2*a*b*sqrt((a^2-1)/b^2+1)*2*a*b*sqrt((a^2-1)/b^2+1)=
=2*a^2*b^2*((a^2-1)/b^2+1)

2*a*b*sqrt((a^2-1)/b^2+1)*b^2*((a^2-1)/b^2+1)=
=2*a*b^3*((a^2-1)/b^2+1)^1.5

((a^2-1)/b^2+1)*a^2=
=a^2*((a^2-1)/b^2+1)

((a^2-1)/b^2+1)*2*a*b*sqrt((a^2-1)/b^2+1)=
=2*a*b*((a^2-1)/b^2+1)^1.5

((a^2-1)/b^2+1)*b^2*((a^2-1)/b^2+1)=
=b^2*((a^2-1)/b^2+1)^2

The last three boil down to
((a^2-1)/b^2+1)*{a^2+2*a*b*sqrt((a^2-1)/b^2+1)+b^2*(sqrt((a^2-1)/b^2+1))^2}
which now can be factorized,
((a^2-1)/b^2+1)*(a+b*sqrt((a^2-1)/b^2+1))^2

The previous 3:
2*a^3*b*sqrt((a^2-1)/b^2+1)
2*a^2*b^2*((a^2-1)/b^2+1)
2*a*b^3*((a^2-1)/b^2+1)^1.5
we can factorize 2*a*b*sqrt((a^2-1)/b^2+1) to get
a^2+
a*b*sqrt((a^2-1)/b^2+1)+
b^2*(sqrt((a^2-1)/b^2+1))^2
and so it becomes
2*a*b*sqrt((a^2-1)/b^2+1)*(a+b*sqrt((a^2-1)/b^2+1))^2

And the first 3 things:
a^4*b^2
2*a^3*b^3*sqrt((a^2-1)/b^2+1)
a^2*b^4*((a^2-1)/b^2+1)
We can take a^2*b^2 outside, and get:
a^2
2*a*b*sqrt((a^2-1)/b^2+1)
b^2*((a^2-1)/b^2+1)
so that's
a^2*b^2*(a+b*sqrt((a^2-1)/b^2+1))^2

So now we got,

a^2*b^2*(a+b*sqrt((a^2-1)/b^2+1))^2
+2*a*b*sqrt((a^2-1)/b^2+1)*(a+b*sqrt((a^2-1)/b^2+1))^2
+((a^2-1)/b^2+1)*(a+b*sqrt((a^2-1)/b^2+1))^2

We take out the common factor (a+b*sqrt((a^2-1)/b^2+1))^2 to get
a^2*b^2
+2*a*b*sqrt((a^2-1)/b^2+1)
+((a^2-1)/b^2+1)
And that can be factorised to
(a*b+sqrt((a^2-1)/b^2+1))^2
So, we got
(a+b*sqrt((a^2-1)/b^2+1))^2*(a*b+sqrt((a^2-1)/b^2+1))^2

This is what I hate about math. You always make mistakes; and when you don't, you end up with the same thing...
Well, at least we can multiply first and then square...
{(a+b*sqrt((a^2-1)/b^2+1))*(a*b+sqrt((a^2-1)/b^2+1))}^2
now
(a+b*sqrt(...))*(a*b+sqrt(...))=
=a^2*b + a*sqrt(...) + a*b^2*sqrt(...) + b*(sqrt(...))^2
And the two middle terms are
(a+a*b^2)*sqrt(...) or
a*(1+b^2)*sqrt(...)
So, we got,
{ a^2*b + a*(1+b^2)*sqrt(...) + b*(sqrt(...))^2 }^2
If we factor b, we get,
{ b*[ a^2 + a*sqrt(...)*(1+b^2)/b + (sqrt(...))^2 ] }^2
Now, if only that middle term was just a*sqrt(...) it would be nice...
Let's see:
a*sqrt(...)*(1+b^2)/b - a*sqrt(...) is,
a*sqrt(...)*((1+b^2)/b-1)
so we could rewrite the above as,
{ b*[ a^2 + a*sqrt(...) + (sqrt(...))^2 + a*sqrt(...)*((1+b^2)/b-1) ] }^2
or
{b*[(a+sqrt((a^2-1)/b^2+1))^2+a*sqrt((a^2-1)/b^2+1)*((1+b^2)/b-1) ]}^2
But I'm not sure the procedure is any shorter, really.
The original form,
{(ab+sqrt((a^2-1)/b^2+1))*(a+b*sqrt((a^2-1)/b^2+1))}^2
actually seems shorter...



EDIT:
Actually, why don't I just write out a routine that implements the actual fresnel formula, and see how computationally
heavy (or not) it is?
Hold on; don't go away...

Code: Select all

fresnel=0.5*
(
  ((EdotN-k*sqrt(1-(1-EdotN^2)/k^2))/(EdotN+k*sqrt(1-(1-EdotN^2)/k^2)))^2+
  ((sqrt(1-(1-EdotN^2)/k^2)-k*EdotN)/(sqrt(1-(1-EdotN^2)/k^2)+k*EdotN))^2
)
if we make,

Code: Select all

 tmp1 = sqrt(1-(1-EdotN^2)/k^2)
the formula above becomes,

Code: Select all

fresnel=0.5*{[(EdotN-k*tmp1)/(EdotN+k*tmp1)]^2+[(tmp1-k*EdotN)/(tmp1+k*EdotN)]^2}
we could make,

Code: Select all

 tmp2 = k*EdotN;
 tmp3 = k*tmp1;
then the formula becomes...

Code: Select all

fresnel=0.5*{[(EdotN-tmp3)/(EdotN+tmp3)]^2+[(tmp1-tmp2)/(tmp1+tmp2)]^2}
make

Code: Select all

tmp4 = (EdotN-tmp3)/(EdotN+tmp3);
tmp5 = (tmp1-tmp2)/(tmp1+tmp2)
then the formula becomes,

Code: Select all

fresnel=0.5*{[tmp4]^2+[tmp5]^2}
Putting it all together:
FOTFLMAO...

Code: Select all

float fresnel( in float EdotN, in float k )
{
	float tmp1 = sqrt(1.0-(1.0-EdotN*EdotN)/(k*k));
	float tmp2 = k*EdotN;
	float tmp3 = k*tmp1;
	float tmp4 = (tmp1-tmp2)/(tmp1+tmp2+0.001);
	float tmp1 = (EdotN-tmp3)/(EdotN+tmp3+0.001);
	return 0.5*(tmp4*tmp4+tmp1*tmp1);
}
EDIT2:
Here's the updated damage.blue to dielectric k formula:

Image

Notice that at 25% gray we get the k of glass; 1.5. The curve goes up to a k of 22 at 31/32 white.

Code: Select all

float blue2k( in float dmgblu )
{
	return (1.0625+dmgblu)/(1.0625-dmgblu);
	/* in table form:
   DMGBLU  K      EXAMPLES
	 0/32	1.000  vacuum, gases, acetone, acetylene, benzene, bromine
	 1/32   1.061  octane, 
	 2/32   1.125  coke
	 3/32   1.194  liquid hydrogen
	 4/32   1.267  polypropylene powder
	 5/32   1.345  cotton, dacron, perlite,
	 6/32   1.429  PVC, soap powders
	 7/32   1.519  glass, polypropylene, polyethylene, charcoal
	 8/32   1.615  pine tree resin, aluminium powder (oxide?), rouge, tide soap, tobacco, wheat flour, liquid propane
	 9/32   1.720  isobuthyl resin, sulfur, rice bran, sodium phosphate, starch paste, jet fuel
	10/32   1.833  ash, caprolactam monomer, corn flakes, kerosene
	11/32   1.957  sesame, ethylene tetraflouride, ethylpentane, methylhexane, nitrotoluene
	12/32   2.091  paper, teflon, zinc oxide, gasoline, octane, kynar, nonane, methylcylopentane,
                  tetrafluoroethylene, mineral oil
	13/32   2.238  clay, paraffin, micanite, polystyrol, pressed-wood, ethylic resin, polyethylene,
                  pentadiene, polybutylene,
                  styrene resin, aluminium hydroxide
	14/32   2.400  toluene, triethylamine, decamethyltetrasiloxane, diisoamylene, octamethylcyclotetrasiloxane,
                  phenylethylene, styrene
	15/32   2.579  portland cement, tar, vaseline, polystyrene resin, tetrachloroethylene, trimethylamine, asphalt,
                  dichlorostyrene, polyamide 
	16/32   2.778  shellac, rubber, contact cement, urethane, amber, almond oil
	17/32   3.000  sugar, silk, mica, mylar, methyl methacrylate, silicone varnish, polycarbonate, calcium, acetate
	18/32   3.250  urethane
	19/32   3.533  acrylic resin, polyester resin, polyvinyl chloride, alkyd resin, sulphur, cast epoxy resin,
                  plexiglass
	20/32   3.857  gypsum/plaster, asbestos, nylon resin, alumina, cellophane, alkyd
	21/32   4.231  vinyl, bakelite, pyroceram, vinyl chloride (flexible), alkyd resin
	22/32   4.667  amylamine, amylmercaptan, benzylamine, benzylmethylamine, nylon,
                  phenol formaldehyde resin (PFR), pyrex glass
	23/32   5.182  manganese dioxide, butylacetate, sand, selenium, zirconium silicate, porcelain
	24/32   5.800  silicone rubber, celluloid, molding resin, methyl butyrate, sodium chloride, thiophosphoryl chloride
	25/32   6.555  polyvinylchloride resin (PVC), urethane resin, slate, steatite, proxylene, methylamine, ethylamine,
                  ilmenite, rutile, neoprene
	26/32   7.500  diamond, neoprene, dolomite, melamine formaldehyde (MF), selenium, apatite,
                  calcium fluoride, urea resin, chrome ore
	27/32   8.714  sodium carbonate, porcelain with zircon, calcite, magnesium sulfate, O-chlorophenol,
                  phenyl isocyanate, quinoline
	28/32  10.333  magnesium oxide, methylpyridine, phenol, smithsonite, creosol, copper sulphate,
                  lonone, methylomine, octanone
	29/32  12.600  calcium oxide, ruby, silicon, tantalum oxide, hydrogen chloride, pinacolin, pyridine, silver bromide,
                  zircon, ferrous oxide
	30/32  16.000  copper oxide, phenol, acetone, cyclohexanone, lead carbonate, nitroglycerin, germanium
	31/32  21.667  TNT, cassiterite, nitroanisole, lead oxide, propionitrile, sorbitol, lead nitrate
	WIDE RANGE K MATERIALS:
	"Plastic pellets" range from 1.1 to 3.2
	shellac 2.0-3.8
	gypsum/plaster 2.5-6.0
	acrylic resin 2.7 - 4.5
	Polyester Resin 2.8 - 4.5
	Nylon Resin 3.0 - 5.0
	Cellophane 3.2-6.4
	silicone rubber 3.2-9.8
	celluloid 3.3-11
	Water 4-88			*/
}
EDIT2:
Oh, yes!!!
This website has all dielectric constants and then some...
http://www.asiinstruments.com/technical ... stants.htm

EDIT3:
Update:

Code: Select all

float fresnel( in float EdotN, in float k )
{
	float tmp1 = sqrt(1.0-(1.0-EdotN*EdotN)/(k*k));
	float tmp2 = k*EdotN;
	float tmp3 = k*tmp1;
	float tmp4 = (tmp1-tmp2)/(tmp1+tmp2+0.0001);
	tmp1 = (EdotN-tmp3)/(EdotN+tmp3+0.0001);
	tmp2 = tmp4*tmp4;
	tmp1 *= tmp1;
	return 0.5*(tmp1+tmp2);
}
chuck_starchaser
Elite
Elite
Posts: 8014
Joined: Fri Sep 05, 2003 4:03 am
Location: Montreal
Contact:

Post by chuck_starchaser »

Now that fresnel is finally out of the way, I decided to revisit the general organization of the shader.
Well, I *had to* because I couldn't understand my own code anymore.
I'm going to need to come up with a flowchart or something.

Anyways, the way it was it could never have worked. Problem is, trying to control too many things with too few
input channels.

I took a pad of square ruled paper and a pen and went to a cafe, to help me concentrate on the problem. I started
listing all the things I wanted to be able to do, and they added up to a bunch.
In the end, 4 families or groups of materials emerged...


Material Families
  • Matte materials: Normally we'd just use diffuse color, black specular, and be done with it. Thing is,
    though, no material is completely matte; there's always a bit of low shininess specularity involved.
    Morover, most matte materials are dielectrics, so this shininess shows fresnel modulation and white color.
    But unlike the more polished dielectrics, this fresnel reflectivity is not full fresnel, but is rather an alpha blend of
    fresnel specularity. Why? Because only a percentage of the surface is reflecting non-diffusely, due to the
    "random" orientations of the surface.
    So, for matte materials, I need a dielectric constant AND a blend ratio input.
  • Plastics, low and high gloss paints, and metallized paints: This group have in common that top surface
    is a dielectric. The material below has a diffuse color, and in the case of metallized paints, there's a blend of low
    shininess, metallic specularity and diffuse reflectivity, overlayed by dielectric, fresnel reflectivity.
    The shininess of metallic reflectivity, in the case of metallized paints, is always 1.0; so we might as well use
    our shininess input to control the shininess of the dielectric interface. Note, however, that we only have ONE
    shininess channel, so this requires that we plug this channel into the fresnel code for some materias, and
    into the specular code for other materials...
    Sub-groups within this family can be characterized solely by diffuse color, dielectric constant and fresnel shininess or
    gloss; but they function pretty much the same way.
  • Metals that feature translucent, dielectric oxides (such as aluminium, zinc...): Here we need
    some artist generated noise in the "blend" channel to produce patches of alpha-blended fresnel reflectivity
    overlayed on the metallic base. The shininess channel should continue to control the metal's shininess; while
    the shininess of the oxide can be defaulted to 1.0.
  • Metals featuring two shininess levels alpha-blended: I wish I could shake this ambition and simplify
    my life but I can't. The fact is that most metal fabrication winds up producing this kind of thing. You look
    at your face reflected of some piece of sheet metal, and you can clearly see that there's a faint but sharp
    image *mixed* with a very blurry image. How come? Well, if you're stamping or laminating metal you
    end up squishing a lot of imperfections that stay there. At the microscopic level it looks like a flat top surface,
    but poked with craters and canals... The imperfections produce the blurry reflection; the smooth areas produce
    a sharp image; and what you see is a blend of low and high shininess, as opposed to a single shininess
    somewhere in-between; both standard (non-fresnel).
    For this we need a high shininess reflection (defaulted to maximum shininess), and a controllable shininess;
    and then a blending ratio control.
The problem is that we don't have a way to tell the shader what material group or strategy to use where. The shader
has to be able to somehow auto-detect the material family, and interpret the scarce input channels to implement it.
No IF statements, either...

So, I made a table with the four material families on the left, and at the top I put the typical attributes that
distinguish them, first; followed by columns expressing the strategies we need to apply to them:

Image

The two purple columns represent two float variables, which control the shininesses of two TextureCubeLOD() calls.
The one on the left is always assigned to metallic specularity; but the one on the right is a bit of a wildcard:
In most cases it controls the shininess of fresnel reflections; but in the case of dual shininess metals it controls
the shininess of the second metalic reflection.
Note, however, that there is only one input shininess; --the one that comes via the specular texture's alpha channel.
Well, depending on the autodetected material family, that shininess input may be routed to either variable. The other
variable is automatically set; --either minimized or maxed out.

The two yellow columns represent another pair of variables controlling alpha blending of specularities: The one
on the left is only used for blending the dual shininess specularities of dual shininess metals. The one on the right
controls blending of dielectric (fresnel) specularities: For matte materials and for metals with translucent, dielectric
rusts.

The question, though, is how do we autodetect the material family?


Detection:

It is actually easier than it might seem; but I had to draw a boolean table to be able to visualize it... Once I figured
it out it seemed obvious:
  • When the brightness of the specular color is greater than the brightness of the diffuse color, we have a metal. Simple
    as that. And this ought to control which of the two gloss variables gets its value from the input shininess (spec alpha).
  • When the input dielectric constant is 1.0, it means we have "clean" metal; by which I mean a metal that does
    not exhibit translucent rusts. This includes metals such as iron, which rust quite well, thank you, but whose rusts are
    not translucent, so they are like a different material altogether.
    So, when the dielectric constant input channel is minimized, we know we have a "clean" metal, with no fresnel at all,
    and so we can use our 2nd env map fetch for that extra, metallic reflection. And so we use this criteria to
    rout our "Blend" input channel to the spec_blend variable, instead of the fresnel blend variable.

I've written some code already; but I won't post it yet, as the smooth step-like functions I haven't decided
upon, yet; --need to do some experimenting with Graph. But I also want to come up with some kind of flow-chart,
and see how I can modularize this huge monstrosity into functions.


EDIT:
I just saw a flash of lightning on the road to Damascus: Why do we need specular color anymore?
As far as I understand, there's two types of specularities: Metallic and Dielectric. Metallic specularity has the
same chroma as the diffuse color. Dielectric specularity is white (desaturated).
But while before we needed to represent both with the specular texture, now I'm computing dielectric
specularities using the Fresnel formula; so the only specularities that remain are the metallic type,
which have the same coloricity in diffuse as in specular.
In other words, I could just leave the "diffuse" texture to represent the basic material color, and then use
only one of the three channels in the "specular" texture, say the green channel, to represent the balance
by which that color applies to diffuse versus specular. That would free the red and blue channels! Then I could
use those free channels for other things.
Comments?


EDIT2:
Smooth-step-like function that, from the dielectric input channel, tells us how much the "blend" channel should
go to specular or fresnel alpha-blend.
Note that the curve rises to like 0.9 y at about 0.14 x.
0.125 in x is where our dielectric material input produces a dielectric constant of 1.2; --the lowest useful
value, really; haven't found too many materials with a k of like 1.1.

Image

As for the metal/non-metal discriminator, I'll await comments on the first edit, because to compare diffuse and
specular lumas will take a lot of instructions; but if I can replace specular color with a scalar "specularity", then
this scalar factor can be easily converted into a discriminator signal.
chuck_starchaser
Elite
Elite
Posts: 8014
Joined: Fri Sep 05, 2003 4:03 am
Location: Montreal
Contact:

Post by chuck_starchaser »

Okay, more progress:

Assuming we go ahead with having scalar specularity, here's a pretty good smooth step function:

Image

The green curve is the first one I came up with, using arctangent. The red one is an approximation it took me
a while to produce, messing around with DataFit. Uses a square root; and I don't think I can avoid it.
The y axis is our fuzzy boolean "is_metal". The x axis is our specularity input (0 for all color goes to diffuse;
1 for all color goes to specular).
Metals are pretty dark in diffuse. Non-metals pretty dark in specular. The worst case I can think of is a metallized
paint with as much as 50% density of metal powder; and some pretty matte metal like zinc having half as
much diffuse as specular. So the curve yields 0.1 at the 50% point, and 0.9 at the 66.7% point.

Code: Select all

float spec2ismetal( in float spec )
{
    float tmp1 = 19.6444*(spec-0.583333);
    float tmp2 = sqrt(1+tmp1*tmp1);
    return 0.5*tmp1/tmp2+0.5024772;
}

New texture packing version:

Code: Select all

COL:
 R (5) = red
 G (5) = green
 B (5) = blue
 A (1) = alpha

DMG:
 R (5) = red
 G (6) = green
 B (5) = blue
 A (8) = blend ***

SPC:
 R (5) = spec_saturation ***
 G (6) = spec_to_diff_balance ***
 B (5) = dielectric_constant ***
 A (8) = gloss ***

GLO:
 R (5) = red
 G (6) = green
 B (5) = blue
 A (8) = ambient_occlusion

NOR:
 R (5) = 0.5*tan(0.5*U) + 1/127
 G (6) = 0.5*tan(0.5*U)
 B (5) = 0.5*tan(0.5*U) - 1/127
 A (8) = 0.5*tan(0.5*V)

+DETail
Blend means either the blending between two metallic specularities; or the blending of dielectric specularity on top of metal or a matte material.

Spec to diff balance means how much of the color in COL goes to the diffuse, and how much goes to the specular.

Spec saturation would be kept at 1.0 most of the time. In rare cases, such as metallized paints, it can be used to reduce, or even invert specular saturation.

Dielectric constant controls the dielectric constant, superexponentially, between 1.0 and 21.667.

Gloss controls shininess superexponentially from 1.0 to about 30,000, with 100 roughly in the middle of the range.


EDIT:
I'm also thinking about maybe doing something more intelligent with the damage texture's rgb channels.
Painting a damage texture is HARD!
But there's a very simple way of getting what looks like smoke marks, in Blender: You just have a mesh looking
like random 3D garbage. You load it together with your ship, and bake an ambient occlusion.
Then, LaGrande could put this AO bake in the green channel, and do an ad-hoc computation for normal
modulation, using the bake like a bump-map, and record those normalmap jitters into the red and blue channels.
The shader could compute the square and the square root of the green channel; use the square to modulate
shininess, the plain green channel value to modulate specularity, and the square root to modulate diffuse albedo;
OR... even easier: just lerp it with the ambient occlusion, since the ambient occlusion is already doing thise
kinds of modulations!!! And then blend red and blue with the normalmap dU and dV.
All in proportion to the % damage input, of course.
chuck_starchaser
Elite
Elite
Posts: 8014
Joined: Fri Sep 05, 2003 4:03 am
Location: Montreal
Contact:

Post by chuck_starchaser »

I was working late yesterday, and I went to the kitchen area to brew myself a coffee, and then I looked at the toaster...

It's a white toaster: the middle part is formed sheet-metal with baked white enamel paint; but the ends are molded
shiny white plastic. Now, don't get me wrong: I can see it's plastic from a mile away; but what I can't see is how I see it.
I spent about an hour in the kitchen, turning that toaster every which way, looking at reflections of other objects at
different angles, with and without shadows, and I have to say, they did a superb job of matching the optical
characteristics of the white paint and the white plastic.
Almost exactly the same diffuse color. Almost exactly the same dielectric constant and shininess....
And yet, I can easily tell which is plastic and which is paint; but, frustratingly, I still can't tell how I can tell.

There's something about plastic that I could offer to describe as "dullness", albeit my not having a definition of the term,
yet. So, here's a bit of an un-corroborated theory of what this "dullness" may be, and how to model it in the shader,
to distinguish shiny plastic from glossy paint. Ultimately, experimentation will be the judge.


Single layer versus 2-layer material

The white paint is a 2-layer material: A white diffuse substrate, and a transparent dielectric on top; whereas
the plastic, no matter how shiny it is, gets its shininess from the material itself --whatever it is I mean by that...

(Whatever I mean, indeed... At the microscopic level, plastic should function as a 2-layer material, anyhow.)

There ought to be a mathematical difference between these two; sort of like, in electronics, having components
in series or in parallel is not the same thing. Single layer materials ought to be side-by-side blends of properties,
whereas in multilayer materials, the properties of lower layers are modulated by properties of upper layers.

For an example of the latter, yesterday I was thinking about a subtle effect of high dielectrics on the ambient light
contribution... HOW? Well, because, okay: One thing that's different about CineMut versus previous shaders, is the
way ambient light is applied. In the current shaders, ambient light is a constant passed to the shader from the
CPU. In CineMut, ambient light is maximally blurred fetch of the environment map, using the direction of
the surface normal. This way, if the brightness of the background is not evenly distributed, the fact will be reflected;
--no pun intended.
But in the case of a high gloss paint surface, perhaps that fetch should be less than maximally blurred, as the
dielectric layer will progressively prevent light from reaching the diffuse substrate, as the angle to the normal
increases.
In other words, we want that fetch to be rather like
incident_ambient = textureCubeLod( env, normal, 8.0-f(k) ); //, where k is the dielectric constant
and f() is some function yet to be determined.

By the same token, I was careful enough in CineMut to multiply diffuse and specular material colors by
(1-fresnel_alpha), where fresnel_alpha is the strength of reflectivity of the dielectric layer, as, to the extent that
the dielectric reflects, it can't possibly also allow light from within to escape, depending on the view angle.
In other words, as you look at reflections at shallower and shallower angles, not only you see brighter
reflections, but you also see darker base colors.

One of the subtle differences I noticed between the plastic and paint on the toaster was that, while reflections
looked pretty much exactly the same at most viewing angles, at very shallow angles there was a visible
difference: the reflections on plastic had less contrast; while looking a bit brighter in a diffuse kind of way.

Why would that be?
How can fresnel be almost identical for both materials at most angles, but then at 80 degrees or so begin to
diverge?
Or, more to the point, how can Fresnel be *violated*? Because no matter what the dielectric constant may be,
at 90 degrees fresnel goes to 1.0, --inexorably.

I think that the reason is that the surface of a molded piece of plastic is not 100% a single material model.
It would seem to me that perhaps 95% of it is smooth and shiny, and the other 5% is white diffuse.
Here, this hypothetical "5%" diffuse component is not modulated at all by fresnel or by one-minus-fresnel, or
any such thing. It stands alone, or beside; or rather, --in other words, IT modulates fresnel, rather than the other
way around, as it takes a percentage of surface area away from the shiny component.

So, I think the new "blend" channel will be useful for more than just blending two metallic shininesses, and
adding a subtle amound to dielectric specularity to matte materials. At the opposite end of the spectrum it can
add a subtle amount of imperfection or dust to otherwise perfect or perfectly clean mirrors; and help
distinguish plastics from glossy paints.
The only problem is trying to use a single "blend" channel to control various types of blends; but I'm optimistic
that I can descramble the intended use for this channel from the context of the other data, such as specularity and
dielectric constant.
safemode
Developer
Developer
Posts: 2150
Joined: Mon Apr 23, 2007 1:17 am
Location: Pennsylvania
Contact:

Post by safemode »

Well, the difference could be caused by the reflections of the clear and solid color layers of the two part glossy painted surfaces mixing together. In a paint, they layers are separated by an extremely small distance, so their reflections pile on eachother and are not really distinguishable. With thicker clears, these reflections would diverge into distinctly different reflections on each layer, giving a field of depth to the material the light is passing through.

So perhaps the difference between the painted surface and plastic is that the plastic doesn't have this indistinguishable clear surface reflection, it only has the solid color reflection, which is duller (though how dull depends on how smooth it is). Rougher surface plastics will be quite dull, where as extremely glass smooth ones should have much sharper reflections.


How to determine a plastic from a painted surface using just texture data is beyond my expertise.

Every material should be treated as if it had 2 layers. A semi-transparent layer and a semi-opaque layer. How transparent and how opaque would be a float range from 0 to 1. Each material would have a thickness of each layer associated with it, a thickness of 0 effectively disables that layer's characteristics, since we would use the width to multiply against a transform to the reflection to determine how the reflections will be rendered offset to the surface of the layer's reflection (caused by the refraction of light through the semi-transparent layer).

So plastic would have a semi-trans thickness of 0, but some positive number for the semi-opaque. Along with all the other shader-related material characteristics.


How do you then recognize plastic in the texture?


For some material you can't. Really shiny or painted plastic will like exactly like painted metal or anything else with a smooth surface. You simply can't see any difference from a distance.

This is why i think maybe we can use the detail map to help us. You make mention of the detail map adding to shininess in it's alpha channel.

I think this could be used to our advantage, since you can't discern such details of material from far distances, it makes sense to only have the shader reveal the nature of the material when we're close enough for the detail texture to have an effect. Perhaps we can make a lack of an additional shininess in the detail map as a sign that there is no dual layer makeup in the material, it's only got one (semi-transparent or semi-opaque). When we have shininess in the alpha of the detail map, then we know we're dealing with something with two layers, like paint and such. How much shininess and the material selection dictates how reflective (metal or flat colors) the material is under the paint.

A detail map with "noise" in the alpha layer could be thought of as creating a speckled glitter effect when looked at up close (metallic paint). etc.

At a distance, such effects would not be visible. which makes sense, if we dont use the detail map in this manner when not very close to what we're looking at.
Ed Sweetman endorses this message.
chuck_starchaser
Elite
Elite
Posts: 8014
Joined: Fri Sep 05, 2003 4:03 am
Location: Montreal
Contact:

Post by chuck_starchaser »

safemode wrote:Well, the difference could be caused by the reflections of the clear and solid color layers
of the two part glossy painted surfaces mixing together. In a paint, they layers are separated by an extremely
small distance, so their reflections pile on eachother and are not really distinguishable.
Yes and no; I've been struggling for a while with the question of multiple reflections between the inner and outer
layer, and I've got no answers yet, but a lot of good question to pile on top of yours, such as, for instance, is there
an inner specular layer to most paints? I believe the answer is no; I think the inner layer is diffuse. If you had a
purely specular inner layer, in all likelihood it would look like plastic-wrapped, bare metal. Certainly
not unheard of, or unseen, but just not the way paints look like. The outer surface of a dielectric is extremely
specular from the inside; --much more than from the outside; but reflections off the bottom (opaque) layer
are diffuse reflections. The net effect ought to be an obscuration of the diffuse color of the material. Why?
Because, for diffuse light to come out, it has to reflect off the inner material more than once, on average.
That is, light that enters the dielectric medium strikes the diffuse surface and scatters randomly.
A lot of the scattered photons will strike the outer dielectric surface at an angle beyond the angle of total reflection,
be 100% reflected back down, and hit the diffuse layer a second time. The photons that manage to escape the
dielectric after a second reflection will have bounced off the diffuse substrate twice, and will therefore,
on average, carry a color that is the color of the light source multiplied by the material's
diffuse color squared.
Those that come out after 3 reflections, will be colored by the material's color cubed. But to model this effect,
I'd have to have formulas for average bounce times given the light and view vectors and the dielectric constant.
Unfortunately, calculus is one of the few things I learned in school, and therefore I remember none of it, in spite
of having passed the courses with stellar grades. Education should be abolished. This effect is actually quite
visible; I'm surprised it hasn't been implemented. If you look at a light blue car, as your view angle and/or
light angle gets shallower, you see deeper --more saturated-- shades of blue. The problem is that dielectric
specularities, which are white, get into the mix, and make the chromatic modulations of direct and diffuse lighting
less noticeable.
All this to say that yes, there's a lot to the idea that there are some complex things going on between the
inner and outer layer; indeed there are. But no, these complexities have more to do with modulations of the direct
and ambient components, rather than specular; and the outer, dielectric specularities are quite distinct, separate
and distinquishable; --not the least of reasons being that they are white reflections, as opposed to the substrate
reflections, which are tinted by the substrate's color, and that their intensities change with the view angle.
But yes again, in that these complexities are probably absent from plastics.

I spent the whole day looking at plastics... I still can't figure out what it is...
Come to think of it, perhaps representing plastics is not all that difficult. In fact, it seems the big challenge is
making ships NOT look like made of plastic; so, chances are you're 700% right and the problem is that our current
shader techniques are too lacking in sophistication when trying to represent paints.
With thicker clears, these reflections would diverge into distinctly different reflections on each layer,
giving a field of depth to the material the light is passing through.
I'm not sure that thickness has any bearing whatsoever. Well, unless we get down to layers so thin that they
come to within range of the light's wavelength... But barring that, dielectric reflection follows fresnel law; it
depends on the angle and the dielectric constant; it doesn't even know or care how thick the dielectric layer is.
And as far as the component of the light that penetrates the dielectric and reflects off the diffuse substrate,
whatever happens would happen the same whether the thickness of the dielectric was a micron or a meter.
Unless you're talking about a dielectric containing impurities that make it less than 100% transparent.
Hmmm... That's possible, though; I never considered that...
So perhaps the difference between the painted surface and plastic is that the plastic doesn't have this
indistinguishable clear surface reflection, it only has the solid color reflection, which is duller (though
how dull depends on how smooth it is). Rougher surface plastics will be quite dull, where as extremely
glass smooth ones should have much sharper reflections.
That's the million dollar question. Been googling for info and haven't found answers yet.
I'm no chemist; believe me; I flunked chemistry 101 twice. I hated it. But if my current understanding is correct,
all plastics are transparent. What makes them appear non-transparent is the powders they mix with them. If this
is so, and I think it is, we're back to square one, because plastics are also 2-layer materials, though at a microscopic
level. Now, this theory would agree with my theory from yesterday: If it's true that the diffuse reflectance of plastics
is produced by mixed powders, some of those powder grains probably touch or are even expose partly above the
dielectric resin surface. If this is so, we would indeed see a percentage of diffuse reflectivity *un-modulated* by
the dielectric layer. And this would explain shallow reflections not going to the 100% mandated by fresnel.
How to determine a plastic from a painted surface using just texture data is beyond my expertise.
I think I just found part of the answer; or at least I'm a lot surer today than I was yesterday: I think that what
makes plastics look like plastic is the % of diffuse filler that is exposed through the dielectric surface.
In paints, the diffuse layer is 100% covered by the dielectric layer, which modulates the visibility of the diffuse
contribution depending on view angle by the refractive index. At shallow angles, fresnel reflection approaches
100%, and the diffuse component below becomes invisible.
In plastics, some percentile of the diffuse component breaks above the dielectric surface, thus remaining
unmodulated.
But this ought to be only part of the answer. Subjectively, I know there's something else than mix-in diffuseness
to the "dullness" of plastic, but I can't put my finger on it. Well, maybe I can... I'm sure that when consumer
product manufacturers are working on the aesthetics, they try to match the optical properties of plastics to
those of the painted metals they accompain; so they hire some guy to take BRDF measurements and whatnot.
Now, if plastics lose some specularity to this % of exposed diffuse material, I'm sure what they do is pick
a plastic with a slightly higher dielectric constant, to compensate. Higher dielectric translate into a higher but
flatter curve in the graph of reflectivity versus angle; but the % of diffusenes pulls the whole curve down
a fixed ammount, so they end up with a material that, on average, has the same amount of dielectric specularity as
the paint, but whose variation with angle is less pronounced.
Furthermore, if the specularity is matched, for a given angle, the unmodulated diffuse component will make
the over-all albedo a bit brighter than that of the paint; so they will have to make the plastic's filler a little darker to
match the albedo of the paint.

Come to think of it, this is starting to explain why so many models in games look plastic: I've already trashed
the bad habit of many artists who produce specular textures by desaturating the diffuse texture. Some
idiot somewhere must have said that's the way to do it, and then there was a stampede of blind leading the
blind lemmings down that road. Standard specular reflectivity has the same color as the diffuse color. The only
kind of reflectivity that is white is dielectric, fresnel specularity.
But why does it look like plastic, and not like paint?
The reason is that the specular texture is not fresnel-modulated, so the reflections look like dielectric reflections
in terms of color, but lack the change with view angle that characterizes dielectric reflectivity. And this resembles
plastics more than it resembles paints, precisely because plastics have a flatter curve of angular modulation,
because they typically have higher dielectric
constants than paints, but then the curve is pulled down by the % of diffuse material on the surface, that
remains un-covered by dielectric. If my theory is right, then, desaturating the specular texture is tantamount to
modeling a property that is unique to plastics; even in an exaggerated way.
BINGO!
Every material should be treated as if it had 2 layers. A semi-transparent layer and a
semi-opaque layer. How transparent and how opaque would be a float range from 0 to 1.
No, I think the inner layer is 100% opaque, and the outer layer is 100% transparent, at least
in practical terms. Opacity is not the issue. The dielectic surface reflects because of the fresnel effect, which
is a quantum mechanics kind of thing; NOT because of opacity. The amount of reflection can be calculated
from just two variables: View angle and dielectric constant.
Each material would have a thickness of each layer associated with it, a thickness of 0 effectively disables
that layer's characteristics, since we would use the width to multiply against a transform to the reflection
to determine how the reflections will be rendered offset to the surface of the layer's reflection (caused by the
refraction of light through the semi-transparent layer).
Ditto.
The only material factor affecting the reflectivity (and therefore "opacity") of the outer, dielectric layer, is its
dielectric constant. Nothing else. No
color, no 0 to 1; make that a 1 - 21.6 dielectric range ;-)
BUT, that's just for paints...
So plastic would have a semi-trans thickness of 0, but some positive number for the semi-opaque.
Along with all the other shader-related material characteristics.
If my current theory is correct, what plastics have, that paints don't, is a percentage of diffuse reflectivity
that is NOT modulated by fresnel refractive "opacity".
Or, to put it the other way around, what paints are currently lacking is just such a fresnel modulation of diffuse
and ambient contributions.
How do you then recognize plastic in the texture?
For some material you can't. Really shiny or painted plastic will like exactly like painted metal or anything
else with a smooth surface. You simply can't see any difference from a distance.
Thing is, for some reason I just can't seem to find too many examples of painted plastic.
There must be some quintessential problem getting paints to stick to plastic.
I'm rather lucky in that I work in electronics, and some of the circuit boards we produce have to be protected
from humidity and dust by spraying them with "conformal coating", which is a special kind of varnish. It sticks to
bakelite, metal, plastics... I'm sure that varnish would stick to teflon.
So I do get to see the look of varnished
plastics, and I can say that you're right: If you put a dielectric, glossy layer on top of plastic it becomes totally
indistinguishable from painted metals.
Shiny plastics... Yes and no. Thing is, come to think of it, the only really shiny plastics --really, REALLY shiny ones,
like high gloss paints-- are transparent ones. I can't think of a colored plastic exhibiting the kind of gloss you see
in brand new car paints. Which is even more support for my theory about the dullness being produced by part of the
filler being exposed at the surface: Transparent plastics don't have that problem because they got no fillers.
This is why i think maybe we can use the detail map to help us. You make mention of the detail map adding
to shininess in it's alpha channel.
Yes, but I was thinking of metals when I said that; not that the same principle
couldn't work with shininess of the dielectric specularity.
However, allow me to debunk a popular fallacy here: You can't use texture to characterize materials, except
in very rare situatios, such as marble and wood, which have characteristic patterns.
Dual Joe was once taking his camera on field trips and getting a library of photos of metals. Total waste of time. What
you see on a metal surface, in terms of pattern, 90% is details of the environment reflected on it, rather than stuff
belonging to the metal itself. And if you used perfectly diffuse light, what you would have left is scratches and rust, which
are the effects of wear and weathering. And we most certainly want to model wear and weathering in our texturings,
but this is a second, separate stage. The job
of characterizing the basic materials comes first, and it only involves textures in the sense of the colors (or other
color-encoded parameters) that define the basic material in terms of its global optical properties.
I think this could be used to our advantage, since you can't discern such details of material from far
distances, it makes sense to only have the shader reveal the nature of the material when we're close enough
for the detail texture to have an effect. Perhaps we can make a lack of an additional shininess in the detail
map as a sign that there is no dual layer makeup in the material, it's only got one (semi-transparent or
semi-opaque). When we have shininess in the alpha of the detail map, then we know we're dealing with
something with two layers, like paint and such. How much shininess and the material selection dictates how
reflective (metal or flat colors) the material is under the paint.
Well, like I said, I don't think "texture" helps to characterize basic materials. Texture is a vehicle
to represent whethering and wear and detail; but basic materials should be characterizable and recognizable even
with perfectly flat colors. Think of buying a brand new car, or a brand new toaster, blemishless and immaculate;
and yet you can easily tell plastic from painted metal, no matter how well they are "matched".
Well, not quite, as metal fabrication does produce "shininess noise", so in the case of metal surfaces, detail alpha
noise being applied to shininess IS, indeed, a material characterizer.
It is also true that we could try and characterize paint by putting a bit of detail texture noise jittering the
normalmap. Unfortunately, we only have 4 channels in the detail texture; and rgb channels are not very good
at passing normalmap jitter, with DDS compression. But then again, we could have two detail textures, and use
their alpha channels for dU and dV noise. But then again, we're already using 7 texture units, which is more
than the 6 of many GPU's. On the other hand, GPU's that have more than 6 texture units usually have 8+.
On the other hand, there's another problem I haven't even tackled, and that is how to vary the usage of the
detail texture. I'm desperately out of channels, and we could really use some variations on the application of
detail texture noise. Dielectric materials perhaps could make better use of jittering of their dielectric constant,
instead of their shininess. I'm hoping to come up with some snazzy tricks to use some automatic material family
detection, and using that to vary detail texture application policies.
A detail map with "noise" in the alpha layer could be thought of as creating a speckled glitter effect when looked at up close (metallic paint). etc.
Indeed. I haven't used detail textures yet, but I did add noise to the shininess in the regular specular
texture and it looks fantastically realistic --pardon the oximoronic expression.
It is barely noticeable except in the corona areas of bright reflections, where you see sparkling and shimmering
as you turn the ship and the specular bright spot crawls along the surface.
At a distance, such effects would not be visible. which makes sense, if we dont use the detail map
in this manner when not very close to what we're looking at.
Indeed.
In fact, there's no need to "not use" the detail map, or to modulate its use with distance. As you move away
from the object, trilinear filtering starts switching to lower LOD's and the noise present in the top mipmap
of the detail texture simply vanishes by filtering as you go down to the lower LOD's, so at a distance, the
detail texture adds nothing, by definition, so it can be on all the time, which saves shader instructions.
chuck_starchaser
Elite
Elite
Posts: 8014
Joined: Fri Sep 05, 2003 4:03 am
Location: Montreal
Contact:

Post by chuck_starchaser »

Major new revision of the texture packing:

Code: Select all

COL.texture: (basic mat color; sent to diffuse/specular in (1-SPEC.r)/SPC.r ratios, respectively.)
 R (5) = red
 G (5) = green
 B (5) = blue
 A (1) = alpha (just a 1-bit alpha, for alpha-testing only; transparent materials use another shader)
NOTE that dielectric (fresnel) specularity is computed separately, controlled by dielectric_blend and
dielectric_constant, and is always white (not controlled by material color). The specular color variable
that receives a ratio of the basic material color is the metallic specular color, which always has the
same chromaticity as the diffuse color.

DMG.texture: (Damage darkens the ambient occlusion and perturbs normals, applied per % damage.)
 R (5) = normal dU modifier
 G (6) = detail_blend (NOT part of damage; controls detail texture use; see detail texture note below.)
 B (5) = normal dV modifier
 A (8) = AO darkener (for smoke marks, black for holes, etc.)

SPC.texture: (This texture controls metallic and dielectric specular properties and gloss.)
 R (5) = spec_to_diff_balance (0 for matte materials; high for metals, 1 for mirrors)
 G (6) = dielectric_blend (0 = clean metals or pure matte paints, 0.7 = plastic, 1.0 = glossy paint)
 B (5) = dielectric_constant (spans 1.0 - 21.7; controls fresnel --spec as f(view angle))
 A (8) = shininess (spans 1.0 - 100.0 - 30,000; applied to dominant specularity type)

GLO.texture: (baked lighting in rgb; ambient occlusion in alpha)
 R (5) = red baked ligth (encoded with gamma = 0.5)
 G (6) = green baked ligth (encoded with gamma = 0.5)
 B (5) = blue baked ligth (encoded with gamma = 0.5)
 A (8) = ambient_occlusion (AO)
NOTE: The plain AO modulates the ambient light contribution; its square root modulates diffuse; and its square
modulates metallic specularity and limits fresnel angle, to simulate a bit of specular self-occlusion.

NOR.texture: (normalmap)
 R (5) = 0.5*tan(0.5*U) + 1/127
 G (6) = 0.5*tan(0.5*U)
 B (5) = 0.5*tan(0.5*U) - 1/127
 A (8) = 0.5*tan(0.5*V)
NOTE: RGB carry the same info with slight offsets to improve resolution. Also, Z is not included in this encoding:
DU and DV encode tangents of angles, to make reconstruction of Z take less instructions.

DET.texture: (detail texture; perlin noises tiled multiple times across regular texture)
 R (5) = Normal dU modifier perlin (low frequency)
 G (6) = Diffuse luma modifier perlin (high frequency)
 B (5) = Normal dV modifier perlin (low frequency)
 A (8) = Shininess modifier perlin (medium frequency)
NOTE: This is a mix of two "policies" of detail texture application. DMG.green modulates application of
green and alpha channels in its 0.5 to 1.0 range; and modulates application of red and blue channels
in the "negative" 0.5 to 0.0 range.
Thus, you could use 0 for glossy paint, for example, to make it look bumpy; and 1.0 for a some bare metal
material, to add shininess shimmer; or to a matte material to make it look grainy.
The most important change is "dielectric_blend":
Examples of use:
  • Matte material with a bit of dielectric blend produces a realistic matte material with a bit of
    dielectric specularity.
  • Highly specular material (metal) with a bit of dielectric blend and a medium/low dielectric
    constant produces the look of old aluminium, which forms a translucent, dielectric oxide layer.
  • A non-specular base color material with high dielectric constant, say 6.0, but a modest
    dielectric blend, say 0.75, produces the look of shiny plastic.
  • A non-specular base color material with more modest dielectric constant, say 3.0, but
    with full dielectric blend (1.0), produces the look of high gloss, baked enamel paint.
  • A partially specular base color material with modest dielectric constant and full dielectric blend (1.0),
    produces the look of high gloss, metallized paint.
Assuming it works, of course... :D


EDIT:
On a separate subject, yesterday I was at a cafe, and begun jotting down some notes as to the minimum
computations that need to be done *PER-LIGHT-SOURCE*.
My intent was to try and figure out a way to minimize that code, so that more lights can be had, or just to
optimize the shader --reduce instruction count.
To my frustration, I discovered that MORE computations need to be done per-light than I originally thought.
The problem is we need fresnel stuff in there, for the diffuse component, which depends on the light vector,
so it can't be precomputed or postcomputed. Namely, the incident light that can reach the diffuse layer
has to pass through the dielectric interface first, so it has to be modulated by 1-fresnel reflectivity.
In addition to self-shadowing and soft penumbras, here's a summary of light vector-dependent terms,
and their accumulators --IDL, AMS and ADS:
  • IDL - Incident diffuse light: Needs to be multiplied by (1-fresnel reflection), from light vector
  • AMS - Afferent metallic specularity: Modulated by (1-fresnel reflection), also, and metallic shininess phong.
  • ADS - Afferent dielectric specularity: Doesn't need fresnel, as the only fresnel applicable is view-vector-
    -dependent, which can be applied afterwards, to the accumulated value; but it does have to have its own
    shininess phong :(.
After processing the lights, and once the accumulators have their final values, we:
  • Fetch env map with LOD 3 times:
    1) Along normal, with maximal blur, for ambient light contribution.
    2) Along reflection, with LOD controlled by metallic shininess
    3) Along reflection, with LOD controlled by dielectric shininess
  • Compute view vector fresnel reflectivity
  • Multiply IDL accumulator by sqrt(AO)
  • Add (ambient light contribution * AO) to IDL accumulator.
  • Multiply IDL accumulator by diffuse material color
  • Add (metalic env fetch) * (1-view fresnel) to AMS accumulator
  • Multiply AMS accumulator by AO^2 * specualr material color
  • Add IDL and AMS together, and multiply by (1-view fresnel). (Note that the metallic env fetch will have
    been multiplied by (1-fresnel) twice now; but that's correct, because metallic reflections have to
    pass through the dielectric interface twice (in and out)). Write result to a vec3 FinalACC.
  • Add dielectric env fetch to ADS accumulator
  • Multiply ADS accumulator by view fresnel and add result to FinalACC.
  • Add static light bake and glows (after degamma) to FinalACC.
The above is actually a simplification: It assumes an ideal 2-layer material (glossy paint); but a lot of those
fresnel and (1-fresnel) factors need to be modulated by the dielectric_blend input channel.


EDIT2:
Okay, here's a version of the above including dielectric_blend:
  • IDL - Incident diffuse light: Needs to be multiplied by (1-fresnel_blend*fresnel reflection),
    from light vector
  • AMS - Afferent metallic specularity: Modulated by (1-fresnel_blend*fresnel reflection),
    also, and metallic shininess phong.
  • ADS - Afferent dielectric specularity: Doesn't need fresnel, as the only fresnel applicable is view-vector-
    -dependent, which can be applied afterwards, to the accumulated value; but it does have to have its own
    shininess phong :(.
After processing the lights, and once the accumulators have their final values, we:
  • Fetch env map with LOD 3 times:
    1) Along normal, with maximal blur, for ambient light contribution.
    2) Along reflection, with LOD controlled by metallic shininess
    3) Along reflection, with LOD controlled by dielectric shininess
  • Compute view vector fresnel reflectivity * dielectric_blend
  • Multiply IDL accumulator by sqrt(AO)
  • Add (ambient light contribution * AO) to IDL accumulator.
  • Multiply IDL accumulator by diffuse material color
  • Add (metalic env fetch) * (1-dielectric_blend*view fresnel) to AMS accumulator
  • Multiply AMS accumulator by AO^2 * specualr material color
  • Add IDL and AMS together, and multiply by (1-dielectric_blend*view fresnel).
    (Note that the metallic env fetch will have been multiplied by (1-dielectric_blend*fresnel)
    twice now; but that's correct, because metallic reflections have to
    pass through the dielectric
    interface twice (in and out)).
    Write result to a vec3 FinalACC.
  • Add dielectric env fetch to ADS accumulator
  • Multiply ADS accumulator by dielectric_blend*view fresnel and add result to FinalACC.
  • Add static light bake and glows (after degamma) to FinalACC.
chuck_starchaser
Elite
Elite
Posts: 8014
Joined: Fri Sep 05, 2003 4:03 am
Location: Montreal
Contact:

Post by chuck_starchaser »

Here's the beginning of the final CineMut Opaque shader, starting from the end :D

Code: Select all

    ...............................
    result4.rgb = final_blend( IL_acc3, MS_acc3, DS_acc3, )
    //ALPHA and CLOAK
    result4.rgb *= result4.a; //mul by 1-bit alpha interpolated
    result4 *= cloakdmg.rrrg; //mul by cloaking alpha
    //WRITE
    gl_FragColor = result4;
}

vec3 final_blend
(
 in vec3 DLacc, in vec3 MSacc, in vec3 FSacc, //DL=DiffuseLight; MS=MetallicSpec; FS=FresnelSpec
 in vec3 AMBenv, in vec3 MSenv, in vec3 FSenv, //Environment mapped counterparts
 in vec3 glow, in vec3 view_vec, in vec3 norm_vec,
 in vec3 diff_color, in vec3 spec_color,
 in float dielectric_blend, in float dielectric_k,
 in float ambient_occlusion, in float damage,
 in float ao_darkener
)
{
    float AO = ambient_occlusion * lerp( damage, 1.0, ao_darkener );
    float NdotV = clamp( dot( norm_vec, view_vec ), 0.0, 1.0 );
    float fresnel_alpha = dielectric_blend * fresnel( NdotV, dielectric_k );
    float fresnel_beta = 1.0 - fresnel_alpha;
    float AOsquared = AO*AO;
    vec3 final_acc = ( (DLacc*sqrt(AO)) + (AMBenv*AO) ) * diff_color;
    //MSenv is multiplied by fresnel_beta to account for partial reflection on entering dielectric
    final_acc += ( (MSacc+(MSenv*fresnel_beta)) * spec_color * AOsquared );
    final_acc *= fresnel_beta; //both diffuse and metallic spec have to exit the dielectric
    final_acc += ( (FSacc+FSenv) * fresnel_alpha * AOsquared );
    final_acc += ( glow*glow ); //multiplied by itself for de-gamma
    return final_acc;
}
EDIT:
Here's the new per-light routine (formerly known as lightingLight()):

Code: Select all

void perlite
   (
   in vec3 light, in vec3 normal, in vec3 vnormal, in vec3 reflection,
   in vec3 lightDiffuse, in float lightAtt, in float ltd_gloss, in float k,
   inout vec3 DLacc, inout vec3 MSacc, inout vec3 FSacc
   )
{
	float NdotL, vNdotL;
	soft_penumbra_NdotL( normal, vnormal, light, NdotL, vNdotL );
	float selfshadow = self_shadow_step( vNdotL );
	//cos of reflection to light angle
	float RdotL = clamp( dot( reflection, light), 0.0, vNdotL+vNdotL );
    //  precalculate some factors used more than once
    vec3 incident_light = lightDiffuse.rgb * lightAtt * selfshadow;
    float fresnel_refl = fresnel( clamp( dot( normal, light ), 0.0, 0.1 ), k );
    vec3 reflected_light = incident_light * fresnel_refl;
    vec3 refracted_light = incident_light * (1.0-fresnel_refl);
    //* DL - diffuse light: Needs to be multiplied by
    //  (1-fresnel_blend*fresnel reflection), from light vector
    DLacc += ( NdotL * refracted_light );
    //* MS - metallic specularity: Modulated by
    //  (1-fresnel_blend*fresnel reflection), also, and
    //  metallic shininess phong.
    MSacc += ( pow( NdotL, ltd_Mgloss ) * refracted_light; 
    //* DS - dielectric specularity: Doesn't need fresnel,
    //  as the only fresnel applicable is view-vector-dependent,
    //  which can be applied afterwards, to the accumulated value;
    //  but it does have to have its own
    //  shininess phong.
    DSacc += ( pow( NdotL, ltd_Fgloss ) * reflected_light;
}
And here's a more optimized soft-penumbra routine:

Code: Select all

void soft_penumbra_NdotL
   (
   in vec3 normal, in vec3 vnormal, in vec3 light,
   out float NdotL, out float vNdotL
   )
{
    vec2 dots, temp, result;
    s.x = dot( vnormal, light );
    s.y = dot( normal, light );
    s += vec2( 0.02 );
    s *= vec2( 0.975 );
    temp = dots * dots * dots * 2500.0;
    result = dots * temp / ( dots + temp );
    vNdotL = clamp( result.x, 0.0, 1.0 );
    NdotL = clamp( result.y, 0.0, 2.0*vNdotL );
}
chuck_starchaser
Elite
Elite
Posts: 8014
Joined: Fri Sep 05, 2003 4:03 am
Location: Montreal
Contact:

Post by chuck_starchaser »

Another revision of the texture packing:
This was a hard decision in the sense that I really wanted to keep the textures having distinctive names
describing their purposes; but regretably I couldn't help mixing them up --a bit too much for the names to
make sense any longer. I've only swapped two channels, and yet...
I had "AO_darkener" in DMG.alpha and "dielectric_blend" in SPC.green.
Now I have "dielectric_blend" in DMG.alpha and "AO_darkener" in SPC.green.
The reason is simply precision: In DXT3 compression, the green channel gets 6-bits; the alpha channel gets 8.
Yeah, it IS a big deal; because this dielectric blend channel will have to be able to do very subtle work. You might
want to put it at 0.99 to represent a very light dusting on a glossy surface; or you might want to put it at 0.01 to
model a barely perceptible amount of translucent oxidation on a metal surface.
Seems ironic that three days ago I didn't even have a dielectric balance parameter, and by now I realize it's
going to be a crucially important one.
Anyhow, the bad part is that, now, the so-called "damage" texture only has the normalmap damage dU and
dV modifiers; the other two channels are not related to damage; and the "specular" texture has the damage ao
darkener smack in the middle of it --the green channel.
Suggestions for texture names welcome. Freddy and Fanny spring to mind.

Code: Select all

COL.texture: (basic mat color; sent to diffuse/specular in (1-SPEC.r)/SPC.r ratios, respectively.)
 R (5) = red
 G (5) = green
 B (5) = blue
 A (1) = alpha (just a 1-bit alpha, for alpha-testing only; transparent materials use another shader)

DMG.texture: (Damage darkens the ambient occlusion and perturbs normals, applied per % damage.)
 R (5) = damage normal dU modifier
 G (6) = detail_blend (NOT part of damage; controls detail texture use; see detail texture note below.)
 B (5) = damage normal dV modifier
 A (8) = dielectric_blend (0 = clean metals or pure matte paints, 0.7 = plastic, 1.0 = glossy paint)

SPC.texture: (This texture controls metallic and dielectric specular properties and gloss.)
 R (5) = spec_to_diff_balance (0 for matte materials; high for metals, 1 for mirrors)
 G (6) = damage AO darkener (dark for smoke marks, black for holes)
 B (5) = dielectric_constant (spans 1.0 - 21.7; controls fresnel --spec as f(view angle))
 A (8) = shininess (spans 1.0 thru 100 to 30,000; applied to dominant specularity type)

GLO.texture: (baked lighting in rgb; ambient occlusion in alpha)
 R (5) = red baked ligth (encoded with gamma = 0.5)
 G (6) = green baked ligth (encoded with gamma = 0.5)
 B (5) = blue baked ligth (encoded with gamma = 0.5)
 A (8) = ambient_occlusion (AO)

NOR.texture: (normalmap)
 R (5) = 0.5*tan(0.5*U) + 1/127
 G (6) = 0.5*tan(0.5*U)
 B (5) = 0.5*tan(0.5*U) - 1/127
 A (8) = 0.5*tan(0.5*V)

DET.texture: (detail texture; perlin noises tiled multiple times across regular texture)
 R (5) = detail normal dU modifier perlin (low frequency)
 G (6) = detail wildcard modifier perlin (high frequency)
 B (5) = detail normal dV modifier perlin (low frequency)
 A (8) = detail shininess modifier perlin (medium frequency)
I also changed DET.green from being a diffuse luma modifier to being a "wildcard" modifier. Sorry, I got no
better name to give it. It's a wildcard in the sense that it may modify different things depending on context...

Let me first revisit DMG.green, so-called "detail blend":
At 50%, detail blend turns off the blending of the detail texture.
At 100% it blends the detail texture's normal dU/dV modifiers, producing a bumpy surface. You'd use this for
glossy painted surfaces, typically. I call this "bump mode" detail.
At 0%, it blends the detail texture's shininess and wildcard modifiers at maximum strength. You could
use this for anything from metals to bricks. I call it "texture mode" detail.

While bump-mode detail is pretty straightforward to understand and apply, texture mode detail is black magic.
For a metallic material, it will apply shininess modulation mostly.
For a matte material, it will apply texture modulation mostly.
So far so good? Now it gets more complicated:
If you specify a non-trivial dielectric constant AND a non-trivial dielectric blend value, for some material,
the wildcard channel will be applied to dielectric blend. Now, in the case of a mostly matte material, this will
be done *instead of* diffuse luma modulation; but in the case of a metal, it will be applied to dielectric blend
*in addition to* shininess detail modulation.


EDIT:
Never mind. Disregard the change. The precision issue is only important near the ends of the range; I can deal
with that with a S-shaped linearization function. I'd almost delete the post, but I won't, just in case someone
may have read it already and might wonder what's going on.

Okay, here's the packing with the change reverted; --mostly just for my own reference so I don't have to click
on the Previous page link to look at it...

Code: Select all

COL.texture: (basic mat color; sent to diffuse/specular in (1-SPEC.r)/SPC.r ratios, respectively.)
 R (5) = red
 G (5) = green
 B (5) = blue
 A (1) = alpha (just a 1-bit alpha, for alpha-testing only; transparent materials use another shader)

DMG.texture: (Damage darkens the ambient occlusion and perturbs normals, applied per % damage.)
 R (5) = damage normal dU modifier
 G (6) = detail_blend (NOT part of damage; controls detail texture use; see detail texture note below.)
 B (5) = damage normal dV modifier
 A (8) = damage AO darkener (dark for smoke marks, black for holes)

SPC.texture: (This texture controls metallic and dielectric specular properties and gloss.)
 R (5) = spec_to_diff_balance (0 for matte materials; high for metals, 1 for mirrors)
 G (6) = dielectric_blend (0 = clean metals or pure matte paints, 0.7 = plastic, 1.0 = glossy paint)
 B (5) = dielectric_constant (spans 1.0 - 21.7; controls fresnel --spec as f(view angle))
 A (8) = shininess (spans 1.0 thru 100 to 30,000; applied to dominant specularity type)

GLO.texture: (baked lighting in rgb; ambient occlusion in alpha)
 R (5) = red baked ligth (encoded with gamma = 0.5)
 G (6) = green baked ligth (encoded with gamma = 0.5)
 B (5) = blue baked ligth (encoded with gamma = 0.5)
 A (8) = ambient_occlusion (AO)

NOR.texture: (normalmap)
 R (5) = 0.5*tan(0.5*U) + 1/127
 G (6) = 0.5*tan(0.5*U)
 B (5) = 0.5*tan(0.5*U) - 1/127
 A (8) = 0.5*tan(0.5*V)

DET.texture: (detail texture; perlin noises tiled multiple times across regular texture)
 R (5) = detail normal dU modifier perlin (low frequency)
 G (6) = detail wildcard modifier perlin (high frequency)
 B (5) = detail normal dV modifier perlin (low frequency)
 A (8) = detail shininess modifier perlin (medium frequency)

EDIT2:
LOL; I'm getting better and better at this... :)
In like 5 minutes flat I came up with a formula for an S-shaped curve to convert SPC.green to dielectric blend:

Image

The code:

Code: Select all

float spec_blend_decode( in float linear_input )
{
    float temp1 = linear_input - 0.5;
    float temp2 = temp1 * temp1;
    return temp1/(1.8*temp2+0.55) + 0.5;
}

EDIT3:
Revisiting self shadow step and soft penumbras...
Well, I can't help it; I'm writing the final shader code, and as I do I question things I did earlier.
And I didn't like my last self shadow step function with a pow() instruction, so I started playing with Graph,
again. Turns out I found not only a better way to compute a smooth step function, but one that I can multiply
vNdotL by, to get pretty much exactly the same soft penumbra I got before.

The new smooth step function is

Code: Select all

float selfshadow_step( in float cosa )
{
    float temp1 = 77.7 * cosa;
    float temp2 = temp1 * temp1;
    return 0.5 * temp1 / sqrt( 1.0 + temp2 ) + 0.5;
}
where "cosa" is the cosine of the angle; --i.e. the raw dot product of the interpolated vertex normal
and the light vector.
Here's a graph of what it produces:

Image

It's the red curve, of course. The other curve is sin(x) multiplied by the new shadowstep function.

Now, remember this old graph?:

Image

The last curve, the purple one, was the soft penumbra vNdotL, at the penumbra detail range.
Now look how remarkably similar is the new shadowste*(sin(x)+0.02):

Image

The new curve goes below to the zero, so it will need a clamp; but the thing is, when you consider that the
new selfshadowstep doesn't use powers, AND that I no longer need a separate function for soft penumbras,
the savings in terms of instructions are great.

Actually, here's a better graph: everything as function of light angle (radians):

Image

The green line is cos(x).
The red line is the selfshadow_step() function.
The deep purple line is the product of the selfshadow_step() * 0.97 * (cos(x)+0.02): soft penumbra vNdotL.

A shot of the area of interes:

Image

I'm not sure the function is right and realistic, though. I'd say it's right in the bulk park; better than nothing.
chuck_starchaser
Elite
Elite
Posts: 8014
Joined: Fri Sep 05, 2003 4:03 am
Location: Montreal
Contact:

Post by chuck_starchaser »

I was just reviewing the final mix routine, and got an itch for including at least one internal light bounce,
in 2-layer materials, such as glossy paint. There's nothing like pen and paper and a glass of wine to
tackle a new problem, so I sat at the kitchen.

Light entering the material is already taken care of. Fresnel bounces some back out, the rest gets in,
refracted a bit, and is about to hit the diffuse and/or specular opaque material at the bottom.
Now what happens?

Let's call the light that has penetrated the dielectric "i", for "incident".
Let's call the color of the material at the bottom "c".
The fresnel reflection factor is refL.
The fresnel refraction factor is refR, and is equal to refL-1.

1) i reflects from the opaque material as c*i, then hits the top surface of the dielectric...
2) part of it refracts out: refR*c*i
3) part of it reflects back down: refL*c*i
4) the latter hits bottom again, becoming refL*c^2*i and then hits top...
5) part of it refracts out as refR*refL*c^2*i
6) part of it reflects back down as refR*refL^2*c^2*i
7) hits bottom again coming up refR*refL^2*c^3*i

Starting to look like a pattern?

Indeed; it's an infinite series. If you put all the refracting out terms together, you see that
(refR*c*i) is a common factor, so we can write it as,

out = (refR*i*c) * { 1 + refL*c + (refL*c)^2 + (refL*c)^3 +... }

but a^0 + a^1 + a^2 + a^3 ... = 1/(1-a) for |a|<1, so

out = (rfR*i*c) * (1/(1-refL*c))

but refR = 1-refL so we can write

out = i*c*(1-refL)/(1-refL*c)

or

out = in * (c-refL*c) / (1-refL*c)

Let's see:
When fresnel reflectivity is low, it works out to i*c
If the reflectivity is high, like 0.9, we get,
i*(c-0.9c)/(1-0.9c) = i*(0.1c)/(1-0.9c)

Alright, assume c is some sky blue 0.25,0.5,0.75 rgb:
0.9c = 0.225, 0.45, 0.675
1- it = 0.775, 0.55, 0.325
0.1c is 0,025, 0.05, 0.075
divided by 1-.9c... alright, need the calculator for this:
0.032, 0.09, 0.231 out.

Makes sense?
Sure it does! With 90% dielectric reflectivity light gets to bounce a lot; and with a darkish
color down there, it gets absobed before it gets a chance to get out. Works perfectly, I'd say.

But the more interesting thing is the chromatic effect this had on the light coming out.
The incoming light's rgb components were in 1,2,3 ratios relative to red.
Light comes out
in about 1,3,7 ratios relative to red.
Chromatic effect.
I love chromatic effects; --specially realistic ones.

That means that wherever I'm currently multiplying by a color under the dielectric layer, instead of
*= color
I need to,
*= multibounce_color( color, reflect_factor )

where,

Code: Select all

float multibounce_color( in vec3 color_in, in float refl_factor )
{
    //(c-refL*c)/(1-refL*c)
    vec3 temp = color_in * refl_factor;
    return (color_in-temp)/(vec3(1.0)-temp);
}
EDIT:
Note that multibounce is applied to non-fresnel-reflected contributions --i.e.: those who made it
through the dielectric on the way in. This includes just about any stuff refracting out of the dielectric
in the direction of your eye, including diffuse, metallic specular AND ambient light contributions.
Once they are all added up and ready to hit the base material, the above function takes care of the
material multiplication, the refracting out, AND the effect of multiple light bounces.
Well, no; diffuse and specular material color need to be dealt with separately...
Do they?
Damn! Too much wine!

EDIT2:
Well, my formula so far only dealt with a specular bottom material.
I suspect the formula for diffuse wouldn't be much different at all:
My first thought was that I needed a different dielectric reflectance coefficient for diffuse:
One that would average for all angles of reflection, with cos alpha weighting...
Nonsense!
The rays coming out that interest us are those coming along the view vector, so that's the only
light path that interests us.
But yeah, I have to do the two colors separately.
chuck_starchaser
Elite
Elite
Posts: 8014
Joined: Fri Sep 05, 2003 4:03 am
Location: Montreal
Contact:

Post by chuck_starchaser »

Damn!
I just realized that encoding images with gamma = 0.5, as was my plan all along with the glow map and
environment map, is not as easy as it sounds.
The problem is with mipmapping, trilinear, and anisotropic filtering.
They would not work right unless the mipmaps were brightness-adjusted.

The problem is that all static/dynamic filtering techniques are based on averaging concepts. But linearizing
averages of delinearized fragments is mathematically incorrect.

Without filterings, writing sqrt(pixelx) to the texture (gamma of 0.5), can be decoded in the shader
simply by computing pixelx=colorfromtexture^2.

But onsider the average of 4 pixels, a, b, c, d: The average we get from the filtering hardware in the gpu,
as well as from software mipmap generation, is 0.25*(a+b+c+d).
But if we write sqrt(a), sqrt(b), sqrt(c), sqrt(d) to the texture, and the shader gets the filtered
average of the four, the true average is NOT that-squared...

That would assume that
(a+b+c+d)^2 = a^2+b^2+c^2+d^2
which is mighty wrong. :(

So, either we neeed to make/modify tools, or else give up gamma encoding altogether.
I'm more inclined to the latter.
pyramid
Expert Mercenary
Expert Mercenary
Posts: 988
Joined: Thu Jun 15, 2006 1:02 am
Location: Somewhere in the vastness of space
Contact:

Post by pyramid »

I have been following your thoughts with great attention. Not that I do understand most of it and probably even to lazy to imagine the rays of light hitting surfaces, being reflected and refracted, ... I trust you will deliver top quality work as we are used to see from you.

My suggestion is, you'd compile this solid thread into an openly licensed technical paper for current and future generations of game developers to drink from your well of wisdom.
chuck_starchaser
Elite
Elite
Posts: 8014
Joined: Fri Sep 05, 2003 4:03 am
Location: Montreal
Contact:

Post by chuck_starchaser »

Thanks! I'll have to see how wise I am when I try to compile this shader; I'm really concerned about
instruction count; it might be double what it was before, which was already pretty big as shaders go.
I remember when I started modding shaders, a few months back, Hellcat was adamant that we don't
cross the 64-instruction barrier. The last CineMut version was 128 instructions heavy; and now I'm
probably edging towards 200 instructions... He probably thinks I'm crazier than a fish with hydrofobia.

OTOH, I think you end up making better things when you shoot for an "ideal" implementation, and then
optimize it afterwards, than when you struggle with conflicting concerns from the start.
And by the time a good number of models use CineMut, most GPU's will probably handle this fp no
problem, without making any compromises.

As far as writing a paper, good idea! Most of the material is already contained along this thread, tho
I'm sure there's really nothing "new" in CineMut; just an exaggerated concern with getting paints to
look like paints, and not like plastics :D

But I do think this is a wise concern: I'd be willing to bet money that well over 90% of the specularities
any of us sees in one day are dielectric, fresnel specularities, rather than metallic reflections: Reflections
on glass, paints, plastic chairs and tables, glittery dresses, cigarette wrappers, water, plant leaves
and human skin ... all happen to be fresnel reflections. Shiny metals around us are few and far
between, by comparison. And yet we have a tradition in graphics, from D3D and OpenGl, that tackled
metallic reflectivity almost exclusively, totally ignoring fresnel all along.
Fresnel was a "fancy feature"; an after-thought. Then engines started having "specular color", separate
from diffuse in order to be able to desaturate specularity to hint at dielectric reflectivity; but white
specularity without fresnel modulation just happens to be an almost perfect model for plastics...

Same baffling lack of wisdom as their having started off from a diffuse lighting model, when probably
70%+ of the light entering our eyes, on average, comes from specular reflections, not diffuse ones.

So, perhaps you're right about "wisdom" in this shader; in the sense that the direction it has taken seems
to be towards restoring a long lost balance of priorities, by trying to do justice to dielectrics, for a change.

Furthermore, artists often ask "what's a good way to represent metal?".
The answer is that metal is quite easy to represent: Black diffuse, some shade of gray in specular, add
shininess to taste, and voila! Perfect metal!
The problem is that to really appreciate the unique look of metals we need non-metals that actually look
uniquely non-metallic, to provide visual contrast; and the way most opengl graphics and shaders work
nowadays, the only non-metals they can represent are matte materials and plastics; --notoriously
lacking in the way of representing paints; which are so important in depicting human-made things.
chuck_starchaser
Elite
Elite
Posts: 8014
Joined: Fri Sep 05, 2003 4:03 am
Location: Montreal
Contact:

Post by chuck_starchaser »

Detail Details

I thought the shader was getting consolidated, but then I looked at "detail texture" application details,
and realized that this stuff needs to be done first; before anything else. Why? Well, the artist will have
a texture channel to specify type and intensity of detail texture application. Type will either be "bumpy"
or "textury"; with "none" being in the middle, and intensity as the numeric distance from mid value.
Beyond this, further details on how to apply detail are decided by the shader.
However, *every* channel capable of detail modulation will get at least a subliminal minimum of modulation
regardless of input channel value or "shader-AI" assignments. This bare minimum has as its purpose to
dither the data channel to help make "pixelation", --the artifacts of linear filtering--, less noticeable.
For this to work, the bare minimum should be computed as a function of the value quantization in the
modulated channel.

In DDS textures with alpha, red and blue channels are 5-bits, green channel is 6-bits, and alpha is 8.
The minimum value steps are 1/32, 1/64, and 1/256 respectively; and so the minimum detail texture mix-in
factors should be about 1/32, 1/64 and 1/256, as well (assuming detail range 0 to 1, which it will be).

But there would be no way to adjust these minimum levels if detail modulation was applied after those
channels have been put through highly non-linear transformations. And this was my realization yesterday,
and the reason for the present shader re-structuring: Detail texture application has to be done early.

And because the shader will have to make decisions about how to apply detail, based on material family
classifications and whatnot, it means that these characterizations, which are part of the "material AI"
of the shader, have to be done at the earliest possible time.

So I started reworking material AI code to work from the raw input parameters, and moving it up front,
but then I ran into lack of a fine and detailed plan on how to apply detail...


Details revisited

The problem is that the detail texture only carries 4 channels, but we have 16 other texture channels
not counting the environment map. And using one same detail texture channel for modulating multiple
texture channels might not look right in some cases. Furthermore, not all 4 detail texture channels
are uncorrelated. Red and blue carry detail dU and dV, and have to agree with each other as to
where the bumps are. They are probably the two channels with the lowest perlin frequency content.
The green channel is a "wildcard" channel, --probably the one with the highest frequency (high noise).
The alpha channel is our "shininess detail", and probably middle-of-the-road, frequency-wise.
Red and blue being correlated means we have to use them together or not use them at all. Probably
their best use is the one originally intended: Modulating the normalmap. Well, you could argue that
when the detail blend control channel is set to "textury detail", there's no bump control, and thus
we can use red and blue for something else. However, I'm reluctant to do so, because when the artist
moves from positive to negative or viceversa, the artist means "I want something really different".
And one thing that makes a lot of difference is a change in detail frequency. Otherwise we'd suffer
from excessive monotony.

So, having assigned exclusive function to red and blue, that leaves us two channels to play with, to
modulate 12 remaining channels! Time to have a hard look...


Ellimination process

I started to take a hard look at the list of channels. First thing I noticed is that the modulations
of diffuse color and glow rgb have to be correlated and parallel. Otherwise, baked and dynamic lights
would be in disagreement as to the color of the material at a given point. Now, we no longer have
separate diffuse and specular colors; BUT, specular color doesn't have to agree with baked lighting
in the glow map; --and in fact, the more it disagrees the better. In other words, when, --or to the
extent that--, we want to modulate diffuse color, we can apply the same detail texture channel to the
glow map rgb color AND to specular/diffuse balance, in equal amounts, and BINGO!

Color.alpha is a channel we only use for alpha-testing, rarely. No need for detail there.

Damage.red/blue are damage dU/dV normal modifiers, so they benefit from detail.red/blue already being
blended with Normalmap du/dV, so no further action is needed there.

Damage.green has the Detail Blend Factor, and I'll be damned if I'm going to modify detail by itself.

Damage.alpha has the AO darkening channel; --the main vehicle for damage representation. It's already
got 8-bits precision, and it's "just damage", so to hell with it.

Spec.red is diffuse/specular balance, which we've already partly covered together with glow.rgb.

Spec.green is dielectric specularity blend, and this is a good candidate for detail modulation.

Spec.blue is dielectric constant. Now, I could be wrong, but I don't think most materials would show
much variability in dielectric constant. I think that, for the most part, this channel will have a
pretty flat value across a material surface, so there's not filtering artifacts to worry about, and
therefore no need for base detail modulation, --let alone a budget of the detail blend factor.

Spec.alpha is shininess, and this is a good candidate for detail modulation.

Glow.rgb is covered already.

Glow.alpha is ambient occlusion (AO), and here I was initially tempted to throw in detail modulation,
but then I started writing pros and cons... er... just cons:
  • AO is supposed to be smooth; NOT grainy.
  • It's already assigned to an alpha channel, with 8-bits of precision; little need of dither.
  • It only represents a range from 0-1 where the low end doesn't count much, as it's dark :)
  • AO is in the same texture together with baked lighting, and both could use a touch of
    extra filtering, as in specifying an LOD bias of 0.3 to 0.5; which means less filtering
    artifacts to be concerned with.
Normal.rgba are all normal dUdV and already taken care of.

So, in the final analysis, we only have three modulation channels to control from two:
Diffuse color (diffuse balance and glow.rgb), dielectric blend and shininess.


Brainstorming

Materials that use "dielectric blend" (and by "use" I mean that the value is neither 0.0 nor 1.0)
include plastics prominently; but plastics don't usually exhibit much variability in any of their
defining parameters/channels; and a wise artist would probably set detail modulation to zero. So,
we need to redefine "use" as having a non-zero or one value of shininess blend that varies across
the surface; but the shader has no easy means of judging variability (it could do so by computing
difference between LOD levels; but that's expensive), so we'll really have to trust the artist.
So, back to using x != 0 && x != 1. A way to evaluate such "relevance of value", in a fuzzy logic
way would be as relevance = 2.0 * x * (1.0-x).
Other than plastics, the other types of materials that use dielectric blend are matte materials
with spotty application of dielectric specularity; and metals like aluminium and zinc that show
"clouds" of transucent, dielectric oxide.
Notice that matte materials are pretty agnostic to shininess modulation, so we could use detail.a
for dielectric blend, in their case; and keep the noisier detail.g for diffuse modulation, which
many a matte material can use to great advantage.
Cloudy metals, on the other hand, can most certainly use detail.a for shininess modulation; but
metals have virtually zero diffuse reflectivity, so they can't use detail.g there; and the spec
color of metals is not usually grainy at all, so no point in wasting detail.g on it. Therefore,
we are left with no arguments against using detail.g for dielectric blend, for metals.
So far, what we got here is that our metal-non-metal detection continuum causes detail.g to go
from modulating dielectric blend to modulating diffuse luma; and detail.a to go from modulating
shininess to modulating dielectric blend.

But how about non-dielectrically-blended materials, such as noble metals and glossy paints? For
the latter, the artist would probably specify bumpy type detail; but the shader has no business
contradicting the artist if the artist specifies textury type detail. The shader's question is
how to best interpret the orders. But there's another question we never asked: When do we apply
detail to dielectric shininess? Well, that's an easy one to answer: We only have one shininess
input channel, which the material AI may assign to metallic or dielectric specularity; so, if we
apply modulation to shininess, it may end up in dielectric shininess.
But what this tells us is that the conclusion of the previous paragraph was in error: Detail.a
should NOT go to dielectric blend for non-metals; it might be useful for shininess of paints, as
well. So, while the destination of detail.g may be controlled by metal/non-metal detection, we
need some better criteria for detail.a.

After racking my brain for a while I came up with a new material characterization dimension that
I think would work: "dielectric_blend_to_shininess_relevance_factor" (DBSRF):

Code: Select all

float DBSRF( in float dielectric_blend_input, in float shininess_input )
{
    float temp1 = dielectric_blend_input * (1.0-dielectric_blend_input);
    float temp2 = shininess_input * (1.0-shininess_input);
    return temp1 / (temp1+temp2+0.0001);
}
chuck_starchaser
Elite
Elite
Posts: 8014
Joined: Fri Sep 05, 2003 4:03 am
Location: Montreal
Contact:

Post by chuck_starchaser »

I wrote:So far, what we got here is that our metal-non-metal detection continuum causes detail.g to go
from modulating dielectric blend to modulating diffuse luma; and detail.a to go from modulating
shininess to modulating dielectric blend.

But how about non-dielectrically-blended materials, such as noble metals and glossy paints? For
the latter, the artist would probably specify bumpy type detail; but the shader has no business
contradicting the artist if the artist specifies textury type detail. The shader's question is
how to best interpret the orders. But there's another question we never asked: When do we apply
detail to dielectric shininess? Well, that's an easy one to answer: We only have one shininess
input channel, which the material AI may assign to metallic or dielectric specularity; so, if we
apply modulation to shininess, it may end up in dielectric shininess.
But what this tells us is that the conclusion of the previous paragraph was in error: Detail.a
should NOT go to dielectric blend for non-metals; it might be useful for shininess of paints, as
well. So, while the destination of detail.g may be controlled by metal/non-metal detection, we
need some better criteria for detail.a.

After racking my brain for a while I came up with a new material characterization dimension that
I think would work: "dielectric_blend_to_shininess_relevance_factor" (DBSRF):

Code: Select all

float DBSRF( in float dielectric_blend_input, in float shininess_input )
{
    float temp1 = dielectric_blend_input * (1.0-dielectric_blend_input);
    float temp2 = shininess_input * (1.0-shininess_input);
    return temp1 / (temp1+temp2+0.0001);
}
As I was trying to implement these ideas, I ran into another problem: What happens with a metal with a high
"dielectric_blend_to_shininess_relevance_factor"? The first rule says that detail.g goes to modulating
dielectric blend. The second rule says that detail.a goes to modulating dielectric blend also...

Let's see: When do we get a high "dielectric_blend_to_shininess_relevance_factor"?
That would be when shininess is either very low or very high; but dielectric blend is neither very low nor very high.
This could very well happen, unfortunately, even for a metal: weathered aluminium, either ultra low shininess,
like cast; or high shininess but old aluminium foil.
In either of these cases we could let detail.g go to dielectric blend and keep detail.a in shininess.

So, how do we change the rules, exactly?
At first sight, it would seem we're going back to the original rule. That is, metalicity puts detail.a in shininess,
regardless of relevance factors. The rule is not the same as originally stated, though. It only means that
we need our metal detection to have priority. For non-metals, we can still decide where detail.a goes, based
on the DBSRF() function.

In other words,

Code: Select all

//some defines...
    #define _detailblend_ ((DMG_in4.g))
    #define _specdiffbal_ ((SPC_in4.r))
    #define _dielblendin_ ((SPC_in4.g))
    #define _shininessin_ ((SPC_in4.a))
    #define _detail_wild_ ((DET_in4.g))
    #define _detailgloss_ ((DET_in4.a))
//declare the variables
float bump_det_fac1, nonbump_det_fac1;
//decide: bumpy detail or textury detail?
detail_blend_decode( _detailblend_, bump_det_fac1, nonbump_det_fac1 );
//metallic vs non-metallic textury detail deciding fate of detail.green:
diffuse_detail = (1.0-_specdiffbal_) * nobump_det_fac1 * _detail_wild_;
dielectricblend_detail = _specdiffbal_ * nobump_det_fac1 * _detail_wild_;
//in the case of non-metals, dilectric balance versus shininess deciding fate of detail.alpha:
gloss_detail = _specdiffbal_ * nobump_det_fac1 * _detailgloss_;
dielectricblend_detail += ( (1.0-_specdiffbal_) * nobump_det_fac1 * DBSRF( _dielblendin_, _shininessin_ ) * _detailgloss_;

where

Code: Select all

void detail_blend_decode( in float dmgblend, out float bump, out float tex )
{
    float temp = (dmgblend-127.0/256.0)*2.0;
    bump = clamp( temp, 0.0, 1.0 );
    tex = clamp( -temp, 0.0, 1.0 );
}
float DBSRF( in float dielectric_blend_input, in float shininess_input )
{
    float temp1 = dielectric_blend_input * (1.0-dielectric_blend_input);
    float temp2 = shininess_input * (1.0-shininess_input);
    return temp1 / (temp1+temp2+0.0001);
}
Well, I think that will do the trick, but I'm not sure... I think I need food. Later.

EDIT:
After some food, I was able to fix some mistakes and finish the code above. It's quite unoptimal as it is;
there are repeated multiplications of same terms and whatnot. Good enough as a reference for myself,
to write the final code. I'll post the section when it's done.
chuck_starchaser
Elite
Elite
Posts: 8014
Joined: Fri Sep 05, 2003 4:03 am
Location: Montreal
Contact:

Post by chuck_starchaser »

Found a way to optimize some code using parallelism; I had two shininesses to translate from angle form to
real shininess and mipmap LOD forms, so I figured I could put the two into a vector and process them in
parallel; but I had a square root in my "alpha-to-LOD" approximation. Remember this old graph?:

Image

So, using Graph and DataFit and Graph again, I managed to get a pretty good polynomial:

Image

So, now,

Code: Select all

vec2 lin_gloss_2_LOD( in vec2 lin_gloss )
{
    float temp1 = lin_gloss * lin_gloss * lin_gloss + vec2( 0.75 );
    float temp2 = 1.0 - lin_gloss;
    return temp1 * temp2 * 18.0 + lin_gloss * 5.0 - vec2( 4.5 );
}
chuck_starchaser
Elite
Elite
Posts: 8014
Joined: Fri Sep 05, 2003 4:03 am
Location: Montreal
Contact:

Post by chuck_starchaser »

Unpacking code done:

Code: Select all

    //READ INTPUT DATA:
    //get interpolated, per-vertex data
    vec2 texcoords2 = gl_TexCoord[0].xy;
    vnormal_v3 = fastnormalize( gl_TexCoord[1].xyz );
    tangent_v3 = gl_TexCoord[2].xyz;
    cotangent_v3 = gl_TexCoord[3].xyz;
    //get texture data needed for detail and normal computations first
    vec4 DMG_in4 = texture2D(damgMap,texcoords2);
    vec4 DET_in4 = texture2D(detailMap,16.0*texcoords2);
    vec4 NOR_in4 = texture2D(normMap,texcoords2);
    //then the rest
    vec4 SPC_in4 = texture2D(specMap,texcoords2);
    vec4 COL_in4 = texture2D(diffMap,texcoords2);
    vec4 GLO_in4 = texture2D(glowMap,texcoords2);
    //UNPACK:
    //we use a macro table to ease possible future changes to the texture packing:
    #define _matcolor_in_ ((COL_in4.rgb))
    #define _matalpha_in_ ((COL_in4.a))
    #define _damage_dUdV_ ((DMG_in4.rb))
    #define _detailblend_ ((DMG_in4.g))
    #define _damageAOdrk_ ((DMG_in4.a))
    #define _specdiffbal_ ((SPC_in4.r))
    #define _dielblendin_ ((SPC_in4.g))
    #define _dielectrkin_ ((SPC_in4.b))
    #define _shininessin_ ((SPC_in4.a))
    #define _glowcolorin_ ((GLO_in4.rgb))
    #define _ambientoccl_ ((GLO_in4.a))
    #define _normalmapin_ ((vec2(0.3333*(NOR_in4.r+NOR_in4.g+NOR_in4.b),NOR_in4.a)))
    #define _detail_dUdV_ ((DET_in4.rb))
    #define _detail_wild_ ((DET_in4.g))
    #define _detailgloss_ ((DET_in4.a))
    /* The most urgent item to unpack is the detail texture data because at its minimum
    level, detail provides dithering to hide DXT quantization; but such dithering needs
    to be applied before non-linear transformations. Keep in mind that all four rgba
    channels of the detail texture will be scaled to use the full 0-1 range. Minimum
    detail application to a channel will be where full range matches 1 channel step. */
    ///////////////////////////////////////////////////////////////////////////
    //CORE SHADER A.I. --MATERIAL FAMILY DETECTION:
    //characterize the material to determine how to interpret shininess, detail, etc.:
    float ismetal_ch1 = is_metal_decode( _specdiffbal_ );
    //"nonzerok" is short for "has dielectric k greater than 1.2 or so"
    float nonzerok_ch1 = nonzerok_decode( _dielectrkin_ );
    //dielectric blend to shininess relevance factor:
    float relevance_factor = DBSRF( _dielblendin_, _shininessin_ );
    ///////////////////////////////////////////////////////////////////////////
    //Manage distribution of detail texture application:
    float bump_det_fac1, nonbump_det_fac1;
    // decide: bumpy detail or textury detail?
    detail_blend_decode( _detailblend_, bump_det_fac1, nonbump_det_fac1 );
    // metallic vs non-metallic textury detail deciding fate of detail.green:
    float dielectricblend_detail = ismetal_ch1 * nobump_det_fac1;
    float diffuse_detail = nobump_det_fac1 - dielectricblend_detail;
    // metallic shininess detail mostly deciding fate of detail.alpha:
    float adjusted_gloss = nobump_det_fac1 * make_signed(_detailgloss_);
    gloss_detail = ismetal_ch1 * adjusted_gloss;
    // for non-metals, dilectric balance vs shininess relevance spells fate of detail.alpha:
    dielectricblend_detail += ( (adjusted_gloss - gloss_detail) * relevance_factor );
    //Apply details and damage:
    float damage = cloakdmg.b;
    float integrity = 1.0 - damage;
    // to normal:
    vec2 dUdV_in2 = dUdV_first_decode(_normalmapin_);
    dUdV_in2 += ( dUdV_first_decode(_damage_dUdV_) * damage );
    dUdV_in2 += ( dUdV_first_decode(_detail_dUdV_) * (bump_det_fac1+1.0/32.0) );
    // to diff/spec balance and glow:
    float diffuse_jitter = (diffuse_detail+1.0/32.0) * make_signed(_detail_wild_);
    float specdiffbal_in1 = _specdiffbal_ + diffuse_jitter;
    vec3 glow_mat3 = _glowcolorin_ + vec3( diffuse_jitter );
    // to shininess:
    float s_gloss_det = *make_signed(_detailgloss_);
    float shininess_in = _shininessin_ + (gloss_detail+1.0/256.0)*s_gloss_det;
    // to dielectric blend:
    float dielectricblend_in1 = _dielblendin_ + (dielectricblend_detail+1.0/64.0)*s_gloss_det;
    // to ambient occlusion:
    float AO_darkener1 = lerp( damage, 1.0, _damageAOdrk_ );
    float ao_mat1 = _ambientoccl_ * AO_darkener1;
    //Non-linear transformations, rangings, and any unpacking left:
    // diffuse, alpha and specular:
    vec3 spec_mat3 = _matcolor_in_ * _specdiffbal_;
    vec3 diff_mat3 = _matcolor_in_ - spec_mat3;
    float alpha_mat1 = glow_in3 * glow_in3; //de-gamma
    // shininess CTRL goes to metallic spec for metals; dielectric gloss for non-metals;
    // and the defaults are min for both; except when dielectric k is 0/trivial we want to
    // max out shininess for fgloss, as it will be used for dual specularity metals
    vec2 lin_gloss2, gloss_lod2, gloss2;
    #define metallic x
    #define fresnely y
    distribute_gloss( ismetal_ch1, nonzerok_ch1, shininess_in, lin_gloss2 );
    gloss_lod2 = lin_gloss_2_LOD( lin_gloss2 );
    gloss2 = lin_gloss_2_exp( lin_gloss2 );
    // compute final normal
    vec3 tmp3 = dUdV_final_decode( dUdV_in2 );
    vec3 normal_v3 = imatmul( tangent_v3, cotangent_v3, vnormal_v3, normal_v3 );
    // other:
    float dielec_blend_mat1 = dielectricblend_decode( dielectricblend_in1 );
    float dielectric_k_mat1 = dielectric_decode( _dielectrkin_ );
    //END OF UNPACKING
where

Code: Select all

void detail_blend_decode( in float dmgblend, out float bump, out float tex )
{
    float temp = (dmgblend-127.0/256.0)*2.0;
    bump = clamp( temp, 0.0, 1.0 );
    tex = clamp( -temp, 0.0, 1.0 );
}
float DBSRF( in float dielectric_blend_input, in float shininess_input )
{
    float temp1 = dielectric_blend_input * (1.0-dielectric_blend_input);
    float temp2 = shininess_input * (1.0-shininess_input);
    return temp1 / (temp1+temp2+0.0001);
}
float make_signed( in float zero_to_one_signed );
{
    return zero_to_one_signed - 127.0/256.0;
}
vec2 dUdV_first_decode( vec2 raw_dudv )
{
    return raw_dudv - vec2( 127.0/256.0 );
}
vec3 dUdV_final_decode( vec2 first_decode )
{
    vec3 temp;
    temp.rg = first_decode;
    temp.b = 0.25;
    return temp; //might as well normalize after imatmul
}
float dielectricblend_decode( in float linear_input )
{
    float temp1 = linear_input - 0.5;
    float temp2 = temp1 * temp1;
    return temp1/(1.8*temp2+0.55) + 0.5;
}
float dielectric_decode( in float linear_input )
{
    return (1.0625+linear_input) / (1.0625-linear_input);
}
float is_metal_decode( in float specdiffbalance )
{
    float tmp1 = 19.6444*(spec-0.583333);
    float tmp2 = sqrt(1+tmp1*tmp1);
    return 0.5*tmp1/tmp2+0.5024772;
}
float nonzerok_decode( in float linear_k_input )
{
    float temp = dielectric_k * dielectric_k;
    temp *= ( temp * 20000.0 );
    return temp / (temp+1);
}
void distribute_gloss
(
  in float is_metal, in float is_dielectric, in float gloss_in,
  out vec2 linear_glosses
)
{
    // Shininess CTRL goes to metallic spec for metals; dielectric gloss for non-metals;
    // and the defaults are min for both; except when dielectric k is 0/trivial we want
    // to max-out shininess for fgloss, as it will be used for dual specularity metals
    // rather than for dielectric, Fresnel shininess.
    // This whole routine works with linear, 0-1 values (input representation) in & out,
    // rather than with actual shininess values. Outputs to a vec2 with both glosses
    linear_glosses.metallic = is_metal * gloss_in;
    linear_glosses.fresnely = lerp( is_metal, gloss_in, 1.0-is_dielectric );
}
vec2 lin_gloss_2_LOD( in vec2 lin_gloss )
{
    //The following is an approximation of the true formula. It avoids
    //using a logarithm, plus it makes better use of env-map mipmaps.
    //The true formula would be 8+log2( tan( spotlight radial angle ) )
    //The approximation is 15.2*(x^3+1.07)*(0.92-x) + 8.5*x - 6.0; but
    //we do it for 2 shininesses simultaneously, using vec2 in and out.
    float temp1 = lin_gloss * lin_gloss * lin_gloss + vec2( 1.07 );
    float temp2 = vec2( 0.92 ) - lin_gloss;
    return temp1 * temp2 * 15.2 + lin_gloss * 8.5 - vec2( 6.0 );
}
vec2 lin_gloss_2_exp( in vec2 lin_gloss )
{
    //using the formula (1.0625+x)/(1.0625-x)
    vec2 temp1 = vec2( 1.0625 );
    vec2 temp2 = (temp1+lin_gloss) / (temp1-lin_gloss);
    temp1 = temp2 * temp2 * temp2;
    //limit to 1 degree radius (shininess of 4500) by product over sum:
    vec2 temp3 = vec2( 4500.0 );
    return temp1 * temp3 / (temp1+temp3);
}
vec3 imatmul( in vec3 tan, in vec3 cotan, in vec3 norm, in vec3 texnorm )
{
    return normalize( texnorm.xxx*tan + texnorm.yyy*cotan + texnorm.zzz*norm );
}
Unfortunately, I have to go to work --today, Sunday, yes.
I got the rest of the shader pretty much done, already, but I want to check it over before showing it.
In any case, none of this is compiled yet; it's probably full of typos and whatnot.
Later.
chuck_starchaser
Elite
Elite
Posts: 8014
Joined: Fri Sep 05, 2003 4:03 am
Location: Montreal
Contact:

Post by chuck_starchaser »

Probably the last new function:

Ambient_LOD( dielectric_const ):

Image

And what the hell is it?
Well, in the current shader, ambient light is assumed to be equally coming from all directions.
In CineMut, ambient light is fetched from the environment map, just like the env map reflections, except
that instead of the reflection vector, it uses the surface normal; and instead of an LOD (blurriness) based on
shininess, it defaults to the highest blurriness: LOD 9 (assuming 1024 cubemap sides).

BUT.... A dielectric layer reflects some light at angles off the normal, and therefore diverts some ambient
light that would otherwise strike the surface.
That is, having a dielectric coating "focuses" ambient light receptivity a bit in the direction of the normal.

For very low dielectric constants, the diverted light is so small overall it makes little difference.
At higher dielectric constants, the fresnel curve as a function of angle kind of flattens out.
The peak of angular discrimination I found is at a dielectric constant of about 1.77.

The curve above is a rough approximation that starts at an LOD of 9 at a dielectric constant of 1, descends
to LOD of 8 at dielectric constant of 2, and then rises again towards 9 as the dielectric constant increases further.

Code: Select all

float ambient_LOD( in float dielectric_k )
{
    float temp = 1.0 / dielectric_k;
    return 4.0 * (temp*temp - temp) + 9.0;
}
chuck_starchaser
Elite
Elite
Posts: 8014
Joined: Fri Sep 05, 2003 4:03 am
Location: Montreal
Contact:

Post by chuck_starchaser »

First glimpse of the full shader:

Code: Select all

//NEW SHADER (high end)
uniform int light_enabled[gl_MaxLights];
uniform int max_light_enabled;
//samplers
uniform samplerCube cubeMap;
uniform sampler2D diffMap;   //1-bit alpha in alpha, for alpha-testing only
uniform sampler2D specMap;   //log256(shininess) in alpha
uniform sampler2D glowMap;   //ambient occlusion in alpha
uniform sampler2D normMap;   //U in .rgb; V in alpha (special encoding; see norm_decode())
uniform sampler2D damgMap;   //"dielectricness" in blue, specular blend in alpha
uniform sampler2D detailMap; //.rgb adds to diffuse, subtracts from spec; alpha mods shininess
//other uniforms
uniform vec4 cloakdmg; //.rg=cloak, .ba=damage
#define cloak_alpha() ((cloakdmg.rrrg))
#define damage() ((cloakdmg.b))
#define inv_damage ((1.0-cloakdmg.b))
//envColor won't be needed, since we're fetching it from the envmap

//NOTE: Since the term "binormal" has been rightly deprecated, I use "cotangent" instead :)

float lerp( in float f, in float a, in float b)
{
    return (1.0-f)*a + f*b;
}
vec3 lerp( in float f, in vec3 a, in vec3 b)
{
    return (1.0-f)*a + f*b;
}
vec3 fastnormalize( in vec3 v ) //less accurate than normalize() but should use less instructions
{
    float tmp = dot( v, v );
    tmp = 1.5 - (0.5*tmp);
    return tmp * v;
}
void detail_blend_decode( in float dmgblend, out float bump, out float tex )
{
    float temp = (dmgblend-127.0/256.0)*2.0;
    bump = clamp( temp, 0.0, 1.0 );
    tex = clamp( -temp, 0.0, 1.0 );
}
float DBSRF( in float dielectric_blend_input, in float shininess_input )
{
    float temp1 = dielectric_blend_input * (1.0-dielectric_blend_input);
    float temp2 = shininess_input * (1.0-shininess_input);
    return temp1 / (temp1+temp2+0.0001);
}
float make_signed( in float zero_to_one_signed );
{
    return zero_to_one_signed - 127.0/256.0;
}
/* The LaGrande normalmap noodle does away with the z-term for the normal by encoding U & V
as 0.5*tan( angle ), where angle is arcsin( U ) or arcsin( V ), respectively. To fit that
into a 0-1 range, we multiply by 0.5 once again, and add 0.5.
To reverse the encoding, we first subtract 0.5, then multiply by four, fill the z term with
1.0, and normalize. But multiplying by four is not needed if instead we fill the z term with
0.25, instead; *then* normalize :D
Here we've broken up the normalization, since there's no need to fully denormalize before
adding the normalmap, damage and detail normals. Just subtractin 0.5 is enough. And there's
also no point in normalizing before imatmul(); so normalization is done inside imatmul().
 */
vec2 dUdV_first_decode( vec2 raw_dudv )
{
    return raw_dudv - vec2( 127.0/256.0 );
}
vec3 dUdV_final_decode( vec2 blend_of_first_decode )
{
    vec3 temp;
    temp.rg = blend_of_first_decode;
    temp.b = 0.25;
    return temp; //might as well normalize after imatmul
}
vec3 imatmul( in vec3 tan, in vec3 cotan, in vec3 norm, in vec3 texnorm )
{
    return normalize( texnorm.xxx*tan + texnorm.yyy*cotan + texnorm.zzz*norm );
}
float dielectricblend_decode( in float linear_input )
{
    float temp1 = linear_input - 0.5;
    float temp2 = temp1 * temp1;
    return temp1/(1.8*temp2+0.55) + 0.5;
}
float dielectric_decode( in float linear_input )
{
    return (1.0625+linear_input) / (1.0625-linear_input);
}
float ambient_LOD( in float dielectric_k )
{
    float temp = 1.0 / dielectric_k;
    return 4.0 * (temp*temp - temp) + 9.0;
}
float is_metal_decode( in float specdiffbalance )
{
    float tmp1 = 19.6444*(spec-0.583333);
    float tmp2 = sqrt(1+tmp1*tmp1);
    return 0.5*tmp1/tmp2+0.5024772;
}
float nonzerok_decode( in float linear_k_input )
{
    float temp = dielectric_k * dielectric_k;
    temp *= ( temp * 20000.0 );
    return temp / (temp+1);
}
void distribute_gloss
(
  in float is_metal, in float is_dielectric, in float gloss_in,
  out vec2 linear_glosses
)
{
    // Shininess CTRL goes to metallic spec for metals; dielectric gloss for non-metals;
    // and the defaults are min for both; except when dielectric k is 0/trivial we want
    // to max-out shininess for fgloss, as it will be used for dual specularity metals
    // rather than for dielectric, Fresnel shininess.
    // This whole routine works with linear, 0-1 values (input representation) in & out,
    // rather than with actual shininess values. Outputs to a vec2 with both glosses
    linear_glosses.metallic = is_metal * gloss_in;
    linear_glosses.fresnely = lerp( is_metal, gloss_in, 1.0-is_dielectric );
}
vec2 lin_gloss_2_LOD( in vec2 lin_gloss )
{
    //The following is an approximation of the true formula. It avoids
    //using a logarithm, plus it makes better use of env-map mipmaps.
    //The true formula would be 8+log2( tan( spotlight radial angle ) )
    //The approximation is 15.2*(x^3+1.07)*(0.92-x) + 8.5*x - 6.0; but
    //we do it for 2 shininesses simultaneously, using vec2 in and out.
    float temp1 = lin_gloss * lin_gloss * lin_gloss + vec2( 1.07 );
    float temp2 = vec2( 0.92 ) - lin_gloss;
    return temp1 * temp2 * 15.2 + lin_gloss * 8.5 - vec2( 6.0 );
}
vec2 lin_gloss_2_exp( in vec2 lin_gloss )
{
    /* The formula used to compute shininess from alpha is just an ad-hoc formula
    that produces *useful* linearites across the alpha range; --with gradual change
    at the bottom of the curve, but rising fast at the top. Almost linear with the
    radius of specular light-spots, but not quite. Input and output are vec2, so
    that two shininesses (one for metallic, one for dielectric specularities) are
    computed in one shot. Using the formula (1.0625+x)/(1.0625-x) cubed. */
    vec2 temp1 = vec2( 1.0625 );
    vec2 temp2 = (temp1+lin_gloss) / (temp1-lin_gloss);
    temp1 = temp2 * temp2 * temp2;
    /*  tests:
     Alpha  Shininess Angular radius of specular highlights
      0/256     1.000 67.08
      1/256     1.022 66.35 1.10% angular decrement
     32/256     2.032 47.06
     33/256     2.078 46.53 1.14%
     64/256     4.215 32.67
     65/256     4.315 32.29 1.18%
     96/256     9.141 22.19
     97/256     9.375 21.91 1.28%
    128/256    21.433 14.49
    129/256    22.051 14.29 1.40%
    160/256    57.385  8.86
    161/256    59.360  8.71 1.72%
    192/256   195.112  4.80
    193/256   203.928  4.70 2.13%
    224/256  1103.370  2.02
    225/256  1182.430  1.95 3.59%
    254/256 24953.974  0.42
    255/256 29791.000  0.39 7.69% */
    /* limit to 1 degree radius (shininess of 4500) by product over sum; so
    that point source lights don't become single pixels on reflections */
    vec2 temp3 = vec2( 4500.0 );
    return temp1 * temp3 / (temp1+temp3);
}
vec3 envMappingLOD( in vec3 direction, in float LoD )
{
    vec4 result = textureCubeLod( cubeMap, direction, LoD );
    return result.rgb * result.a;
}
float selfshadow_step( in float cosa )
{
    float temp1 = 77.7 * cosa;
    float temp2 = temp1 * temp1;
    return 0.5 * temp1 / sqrt( 1.0 + temp2 ) + 0.5;
}
void soft_penumbra_NdotL
(
  in vec3 normal, in vec3 vnormal, in vec3 light,
  out float selfshadow, out float NdotL, out float vNdotL
)
{
    vec2 result;
    float cosa = dot( vnormal, light );
    result.x = dot( normal, light );
    result.y = cosa;
    float ss = selfshadow_step( cosa );
    result += vec2( 0.02 );
    result *= ( 0.97 * ss );
    vNdotL = clamp( result.y, 0.0, 1.0 );
    selfshadow = ss;
    NdotL = clamp( result.x, 0.0, sqrt(vNdotL) );
}
float fresnel( in float cosa, in float k )
{
   float tmp1 = sqrt(1.0-(1.0-cosa*cosa)/(k*k));
   float tmp2 = k*cosa;
   float tmp3 = k*tmp1;
   float tmp4 = (tmp1-tmp2)/(tmp1+tmp2+0.0001);
   tmp1 = (cosa-tmp3)/(cosa+tmp3+0.0001);
   return 0.5*(tmp1*tmp1+tmp4*tmp4);
}
void perlite
(
  in vec3 light, in vec3 normal, in vec3 vnormal, in vec3 reflection,
  in vec3 lightDiffuse, in float lightAtt, in float fresnel_blend,
  in float k, in float ltd_Mgloss, in float ltd_Fgloss,
  inout vec3 DLacc, inout vec3 MSacc, inout vec3 FSacc
)
{
	float NdotL, vNdotL;
	soft_penumbra_NdotL( normal, vnormal, light, NdotL, vNdotL );
	float selfshadow = self_shadow_step( vNdotL );
	//cos of reflection to light angle
	float RdotL = clamp( dot( reflection, light), 0.0, 4.0*vNdotL );
    //  precalculate some factors used more than once
    vec3 incident_light = lightDiffuse.rgb * lightAtt * selfshadow;
    float fresnel_refl = fresnel_blend * fresnel( NdotL, k );
    vec3 reflected_light = incident_light * fresnel_refl;
    vec3 refracted_light = incident_light * (1.0-fresnel_refl);
    //  * DL - diffuse light: Needs to be multiplied by
    //  (1-fresnel_blend*fresnel reflection), from light vector
    DLacc += ( NdotL * refracted_light );
    //  * MS - metallic specularity: Modulated by
    //  (1-fresnel_blend*fresnel reflection), also, and
    //  metallic shininess phong. And we also multiply by the
    //  shininess, as smaller spots get more light concentration
    MSacc += ( pow( NdotL, ltd_Mgloss ) * ltd_Mgloss * refracted_light ); 
    //  * FS - fresnel specularity: Doesn't need fresnel, really,
    //  as the only fresnel applicable is view-vector-dependent,
    //  which can be applied afterwards, to the accumulated value;
    //  so, we'll multiply the accumulator by view fresnel after...
    FSacc += ( pow( NdotL, ltd_Fgloss ) * ltd_Fgloss ); //*reflected_light);
}
#define lighting(name, lightno_gl, lightno_tex) \
void name( \
   in vec3 normal, in vec3 vnormal, in  vec3 reflection, \
   in float limited_gloss, \
   inout vec3 diff_acc, inout vec3 spec_acc) \
{ \
    lightingLight( \
      normalize(gl_TexCoord[lightno_tex].xyz), \
      normal, vnormal, reflection, \
      gl_FrontLightProduct[lightno_gl].diffuse.rgb, \
      gl_TexCoord[lightno_tex].w, \
      limited_gloss, \
      diff_acc, spec_acc); \
}
float multibounce_color( in vec3 color, in float refl_factor, in float blend )
{
    /* After light's penetrated the outer dielectric & is about to hit the inner, opaque
    layer below, instead of *= color, use this to account for multiple inner bouncings
    The formula is: *= (c-refL*c)/(1-refL*c), where refL is the fresnel reflectivity and
    c is the color of the material under the dielectric coating. But blended materials,
    such as plastics, are only partially covered by a specular dielectric layer, so we do
    have to allow some pure color reflectivity; thus the "blend" thing... */
    vec3 temp = color * refl_factor;
    return blend*(color-temp)/(vec3(1.0)-temp) + (1.0-blend)*color;
}
vec3 final_blend
(
 in vec3 DLacc, in vec3 MSacc, in vec3 FSacc, //DL=DiffuseLight; MS=MetallicSpec; FS=FresnelSpec
 in vec3 AMBenv, in vec3 MSenv, in vec3 FSenv, //Environment mapped counterparts
 in vec3 glow, in vec3 view_vec, in vec3 norm_vec,
 in vec3 diff_color, in vec3 spec_color,
 in float dielectric_blend, in float dielectric_k,
 in float ambient_occlusion, in float damage,
 in float ao_darkener
)
{
    float AO = ambient_occlusion * lerp( damage, 1.0, ao_darkener );
    float NdotV = clamp( dot( norm_vec, view_vec ), 0.0, 1.0 );
    float AO_fresnel_reflection = (1.0-dielectric_k)/(1.0+dielectric_k );
    AO_fresnel_reflection *= AO_fresnel_reflection;
    float reflection = fresnel( NdotV, dielectric_k );
    float diffAOfactor = sqrt(AO);
    float MspecAOfactor = AO * AO;
    float FspecAOfactor = AO * diffAOfactor;
    //Begin with the accumulated direct light (diffuse lighting)
    vec3 final_acc = (DLacc*diffAOfactor);
    //Add ambient light minus fresnel-reflected (note that amb reflection IS the envmapping)
    final_acc += ( AMBenv * AO * (1.0-AO_fresnel_reflection) );
    //multiply by the diffuse color
    final_acc *= multibounce_color( diff_color, reflection, dielectric_blend );
    //MSenv is multiplied by 1-fresnel to account for partial reflection on entering dielectric
    final_acc += (  ( MSacc + (MSenv*refraction) ) *
         multibounce_color( spec_color, reflection, dielectric_blend ) * MspecAOfactor  );
    //Both diffuse and metallic spec have to exit the dielectric. So, we'd multiply by the
    //refraction, here. However, multibounce_color() already took care of it, so, nought to do.
    //We just add fresnel specularity and... By the way, NOW we will multiply by fresnel
    //reflectivity from view angle --remember we didn't in perlite():
    final_acc += ( (FSacc+FSenv) * dielectric_blend * reflection * FspecAOfactor );
    final_acc += ( glow*(1.0-damage) );
    return final_acc;
}

lighting(lite0, 0, 5)
lighting(lite1, 1, 6)

void main()
{
    //READ INTPUT DATA:
    //get interpolated, per-vertex data
    vec2 texcoords2 = gl_TexCoord[0].xy;
    vnormal_v3 = fastnormalize( gl_TexCoord[1].xyz );
    tangent_v3 = gl_TexCoord[2].xyz;
    cotangent_v3 = gl_TexCoord[3].xyz;
    //get texture data needed for detail and normal computations first
    vec4 DMG_in4 = texture2D( damgMap,texcoords2 );
    vec4 DET_in4 = texture2D( detailMap,16.0*texcoords2 );
    vec4 NOR_in4 = texture2D( normMap,texcoords2 );
    //then the rest
    vec4 SPC_in4 = texture2D( specMap,texcoords2 );
    vec4 COL_in4 = texture2D( diffMap,texcoords2 );
    vec4 GLO_in4 = texture2D( glowMap,texcoords2 );
    //UNPACK:
    //we use a macro table to ease possible future changes to the texture packing:
    #define _matcolor_in_ ((COL_in4.rgb))
    #define _matalpha_in_ ((COL_in4.a))
    #define _damage_dUdV_ ((DMG_in4.rb))
    #define _detailblend_ ((DMG_in4.g))
    #define _damageAOdrk_ ((DMG_in4.a))
    #define _specdiffbal_ ((SPC_in4.r))
    #define _dielblendin_ ((SPC_in4.g))
    #define _dielectrkin_ ((SPC_in4.b))
    #define _shininessin_ ((SPC_in4.a))
    #define _glowcolorin_ ((GLO_in4.rgb))
    #define _ambientoccl_ ((GLO_in4.a))
    #define _normalmapin_ ((vec2(0.3333*(NOR_in4.r+NOR_in4.g+NOR_in4.b),NOR_in4.a)))
    #define _detail_dUdV_ ((DET_in4.rb))
    #define _detail_wild_ ((DET_in4.g))
    #define _detailgloss_ ((DET_in4.a))
    /* The most urgent item to unpack is the detail texture data because at its minimum
    level, detail provides dithering to hide DXT quantization; but such dithering needs
    to be applied before non-linear transformations. Keep in mind that all four rgba
    channels of the detail texture will be scaled to use the full 0-1 range. Minimum
    detail application to a channel will be where full range matches 1 channel step. */
    ///////////////////////////////////////////////////////////////////////////
    //CORE SHADER A.I. --MATERIAL FAMILY DETECTION:
    //characterize the material to determine how to interpret shininess, detail, etc.:
    float ismetal_ch1 = is_metal_decode( _specdiffbal_ );
    //"nonzerok" is short for "has dielectric k greater than 1.2 or so"
    float nonzerok_ch1 = nonzerok_decode( _dielectrkin_ );
    //dielectric blend to shininess relevance factor:
    float relevance_factor = DBSRF( _dielblendin_, _shininessin_ );
    ///////////////////////////////////////////////////////////////////////////
    //Manage distribution of detail texture application:
    float bump_det_fac1, nonbump_det_fac1;
    // decide: bumpy detail or textury detail?
    detail_blend_decode( _detailblend_, bump_det_fac1, nonbump_det_fac1 );
    // metallic vs non-metallic textury detail deciding fate of detail.green:
    float dielectricblend_detail = ismetal_ch1 * nobump_det_fac1;
    float diffuse_detail = nobump_det_fac1 - dielectricblend_detail;
    // metallic shininess detail mostly deciding fate of detail.alpha:
    float adjusted_gloss = nobump_det_fac1 * make_signed(_detailgloss_);
    gloss_detail = ismetal_ch1 * adjusted_gloss;
    // for non-metals, dilectric balance vs shininess relevance spells fate of detail.alpha:
    dielectricblend_detail += ( (adjusted_gloss - gloss_detail) * relevance_factor );
    //Apply details and damage:
    float damage = cloakdmg.b;
    float integrity = 1.0 - damage;
    // to normal:
    vec2 dUdV_in2 = dUdV_first_decode(_normalmapin_);
    dUdV_in2 += ( dUdV_first_decode(_damage_dUdV_) * damage );
    dUdV_in2 += ( dUdV_first_decode(_detail_dUdV_) * (bump_det_fac1+1.0/32.0) );
    // to diff/spec balance and glow:
    float diffuse_jitter = (diffuse_detail+1.0/32.0) * make_signed(_detail_wild_);
    float specdiffbal_in1 = _specdiffbal_ + diffuse_jitter;
    vec3 glow_mat3 = _glowcolorin_ + vec3( diffuse_jitter );
    // to shininess:
    float s_gloss_det = *make_signed(_detailgloss_);
    float shininess_in = _shininessin_ + (gloss_detail+1.0/256.0)*s_gloss_det;
    // to dielectric blend:
    float dielectricblend_in1 = _dielblendin_ + (dielectricblend_detail+1.0/64.0)*s_gloss_det;
    // to ambient occlusion:
    float AO_darkener1 = lerp( damage, 1.0, _damageAOdrk_ );
    float ao_mat1 = _ambientoccl_ * AO_darkener1;
    //Non-linear transformations, rangings, and any unpacking left:
    // diffuse, alpha and specular:
    vec3 spec_mat3 = _matcolor_in_ * _specdiffbal_;
    vec3 diff_mat3 = _matcolor_in_ - spec_mat3;
    float alpha_mat1 = glow_in3 * glow_in3; //de-gamma
    // shininess CTRL goes to metallic spec for metals; dielectric gloss for non-metals;
    // and the defaults are min for both; except when dielectric k is 0/trivial we want to
    // max out shininess for fgloss, as it will be used for dual specularity metals
    vec2 lin_gloss2, gloss_lod2, gloss2;
    #define metallic x
    #define fresnely y
    distribute_gloss( ismetal_ch1, nonzerok_ch1, shininess_in, lin_gloss2 );
    gloss_lod2 = lin_gloss_2_LOD( lin_gloss2 );
    gloss2 = lin_gloss_2_exp( lin_gloss2 );
    // compute final normal
    vec3 tmp3 = dUdV_final_decode( dUdV_in2 );
    vec3 normal_v3 = imatmul( tangent_v3, cotangent_v3, vnormal_v3, normal_v3 );
    // dielectric stuff:
    float dielec_blend_mat1 = dielectricblend_decode( dielectricblend_in1 );
    float dielectric_k_mat1 = dielectric_decode( _dielectrkin_ );
    // LOD for ambient lighting:
    float ambient_lod1 = ambient_LOD( dielectric_k_mat1 );
    //texture fetches (dependent on normal)
    ambenv_il3 = envMappingLOD( normal_v3, ambient_lod1 ); //ambient env mapping
    MSenv_il3 = envMappingLOD( reflect_v3, gloss_lod2.metallic ); //metallic spec env mapping
    FSenv_il3 = envMappingLOD( reflect_v3, gloss_lod2.fresnely ); //fresnel spec env mapping
    //END OF UNPACKING
    ///////////////////////////////////////////////////////////////////////////
    //PER-LIGHT COMPUTATIONS
    // initialize accumulators
    DL_acc3 = MS_acc3 = DS_acc3 = vec3( 0.0 );
    // then loop:
    if( light_enabled[0] != 0 )
      lite0(normal_v3,vnormal_v3,reflect_v3,limited_gloss_fac1,diff_light_acc3,spec_light_acc3);
    if( light_enabled[1] != 0 )
      lite1(normal_v3,vnormal_v3,reflect_v3,limited_gloss_fac1,diff_light_acc3,spec_light_acc3);
    //FINAL BLEND
    result4.rgb = final_blend( DL_acc3, MS_acc3, FS_acc3, ... );
    //ALPHA and CLOAK
    result4.rgb *= result4.a; //mul by 1-bit alpha interpolated
    result4 *= cloak_alpha();
    //WRITE
    gl_FragColor = result4;
}
What remains to be done:
  • The per-light macro is still what it was before; needs to be recoded for the new per-light function interface.
  • The arguments to the per-light function calls need to be redone.
  • The arguments to the final mix routine have to be filled in.
  • I still haven't implemented the special case of dual shininess metals, for which the dielectric environment
    mapping needs to be blended in without fresnel.
  • During the design of the paints representation, I assumed the opaque layer to be diffuse, but recently was
    observing paints that appear to be quite specular even underneath the dielectric layer. Need to think
    about whether metallic specularity under a dielectric might interfere with the material AI.
chuck_starchaser
Elite
Elite
Posts: 8014
Joined: Fri Sep 05, 2003 4:03 am
Location: Montreal
Contact:

Post by chuck_starchaser »

IT COMPILES! :D (after some fixing, of course)
Under GPU Shader Analyzer (ATI's tool), that is; not under nvshaderperf... well, haven't tried yet.

Code: Select all

//NEW SHADER (high end)
uniform int light_enabled[gl_MaxLights];
uniform int max_light_enabled;
//samplers
uniform samplerCube cubeMap;
uniform sampler2D diffMap;   //1-bit alpha in alpha, for alpha-testing only
uniform sampler2D specMap;   //log256(shininess) in alpha
uniform sampler2D glowMap;   //ambient occlusion in alpha
uniform sampler2D normMap;   //U in .rgb; V in alpha (special encoding; see norm_decode())
uniform sampler2D damgMap;   //"dielectricness" in blue, specular blend in alpha
uniform sampler2D detailMap; //.rgb adds to diffuse, subtracts from spec; alpha mods shininess
//other uniforms
uniform vec4 cloakdmg; //.rg=cloak, .ba=damage
#define cloak_alpha() ((cloakdmg.rrrg))
#define damage() ((cloakdmg.b))
#define inv_damage() ((1.0-cloakdmg.b))
//envColor won't be needed, since we're fetching it from the envmap

//NOTE: Since the term "binormal" has been rightly deprecated, I use "cotangent" instead :)

//general subroutines:

float lerp( in float f, in float a, in float b)
{
    return (1.0-f)*a + f*b;
}
vec3 lerp( in float f, in vec3 a, in vec3 b)
{
    return (1.0-f)*a + f*b;
}
vec3 fastnormalize( in vec3 v ) //less accurate than normalize() but should use less instructions
{
    float tmp = dot( v, v );
    tmp = 1.5 - (0.5*tmp);
    return tmp * v;
}
float make_signed( in float zero_to_one_signed )
{
    return zero_to_one_signed - 127.0/256.0;
}
vec3 imatmul( in vec3 tan, in vec3 cotan, in vec3 norm, in vec3 texnorm )
{
    return normalize( texnorm.xxx*tan + texnorm.yyy*cotan + texnorm.zzz*norm );
}

//decoding subroutines:

void detail_blend_decode( in float dmgblend, out float bump, out float tex )
{
    float temp = (dmgblend-127.0/256.0)*2.0;
    bump = clamp( temp, 0.0, 1.0 );
    tex = clamp( -temp, 0.0, 1.0 );
}
float DBSRF( in float dielectric_blend_input, in float shininess_input )
{
    float temp1 = dielectric_blend_input * (1.0-dielectric_blend_input);
    float temp2 = shininess_input * (1.0-shininess_input);
    return temp1 / (temp1+temp2+0.0001);
}
/* The LaGrande normalmap noodle does away with the z-term for the normal by encoding U & V
as 0.5*tan( angle ), where angle is arcsin( U ) or arcsin( V ), respectively. To fit that
into a 0-1 range, we multiply by 0.5 once again, and add 0.5.
To reverse the encoding, we first subtract 0.5, then multiply by four, fill the z term with
1.0, and normalize. But multiplying by four is not needed if instead we fill the z term with
0.25, instead; *then* normalize :D
Here we've broken up the normalization, since there's no need to fully denormalize before
adding the normalmap, damage and detail normals. Just subtractin 0.5 is enough. And there's
also no point in normalizing before imatmul(); so normalization is done inside imatmul().
 */
vec2 dUdV_first_decode( vec2 raw_dudv )
{
    return raw_dudv - vec2( 127.0/256.0 );
}
vec3 dUdV_final_decode( vec2 blend_of_first_decode )
{
    vec3 temp;
    temp.rg = blend_of_first_decode;
    temp.b = 0.25;
    return temp; //might as well normalize after imatmul
}
float dielectricblend_decode( in float linear_input )
{
    float temp1 = linear_input - 0.5;
    float temp2 = temp1 * temp1;
    return temp1/(1.8*temp2+0.55) + 0.5;
}
float dielectric_decode( in float linear_input )
{
    return (1.0625+linear_input) / (1.0625-linear_input);
}
float ambient_LOD( in float dielectric_k )
{
    float temp = 1.0 / dielectric_k;
    return 4.0 * (temp*temp - temp) + 9.0;
}
float is_metal_decode( in float specdiffbalance )
{
    float tmp1 = 19.6444*(specdiffbalance-0.583333);
    float tmp2 = sqrt(1+tmp1*tmp1);
    return 0.5*tmp1/tmp2+0.5024772;
}
float nonzerok_decode( in float linear_k_input )
{
    float temp = linear_k_input * linear_k_input;
    temp *= ( temp * 20000.0 );
    return temp / (temp+1);
}
void distribute_gloss
(
  in float is_metal, in float is_dielectric, in float gloss_in,
  out vec2 linear_glosses
)
{
    // Shininess CTRL goes to metallic spec for metals; dielectric gloss for non-metals;
    // and the defaults are min for both; except when dielectric k is 0/trivial we want
    // to max-out shininess for fgloss, as it will be used for dual specularity metals
    // rather than for dielectric, Fresnel shininess.
    // This whole routine works with linear, 0-1 values (input representation) in & out,
    // rather than with actual shininess values. Outputs to a vec2 with both glosses
    linear_glosses.x = is_metal * gloss_in;
    linear_glosses.y = lerp( is_metal, gloss_in, 1.0-is_dielectric );
}
vec2 lin_gloss_2_LOD( in vec2 lin_gloss )
{
    //The following is an approximation of the true formula. It avoids
    //using a logarithm, plus it makes better use of env-map mipmaps.
    //The true formula would be 8+log2( tan( spotlight radial angle ) )
    //The approximation is 15.2*(x^3+1.07)*(0.92-x) + 8.5*x - 6.0; but
    //we do it for 2 shininesses simultaneously, using vec2 in and out.
    vec2 temp1 = lin_gloss * lin_gloss * lin_gloss + vec2( 1.07 );
    vec2 temp2 = vec2( 0.92 ) - lin_gloss;
    return temp1 * temp2 * 15.2 + lin_gloss * 8.5 - vec2( 6.0 );
}
vec2 lin_gloss_2_exp( in vec2 lin_gloss )
{
    /* The formula used to compute shininess from alpha is just an ad-hoc formula
    that produces *useful* linearites across the alpha range; --with gradual change
    at the bottom of the curve, but rising fast at the top. Almost linear with the
    radius of specular light-spots, but not quite. Input and output are vec2, so
    that two shininesses (one for metallic, one for dielectric specularities) are
    computed in one shot. Using the formula (1.0625+x)/(1.0625-x) cubed. */
    vec2 temp1 = vec2( 1.0625 );
    vec2 temp2 = (temp1+lin_gloss) / (temp1-lin_gloss);
    temp1 = temp2 * temp2 * temp2;
    /*  tests:
     Alpha  Shininess Angular radius of specular highlights
      0/256     1.000 67.08
      1/256     1.022 66.35 1.10% angular decrement
     32/256     2.032 47.06
     33/256     2.078 46.53 1.14%
     64/256     4.215 32.67
     65/256     4.315 32.29 1.18%
     96/256     9.141 22.19
     97/256     9.375 21.91 1.28%
    128/256    21.433 14.49
    129/256    22.051 14.29 1.40%
    160/256    57.385  8.86
    161/256    59.360  8.71 1.72%
    192/256   195.112  4.80
    193/256   203.928  4.70 2.13%
    224/256  1103.370  2.02
    225/256  1182.430  1.95 3.59%
    254/256 24953.974  0.42
    255/256 29791.000  0.39 7.69% */
    /* limit to 1 degree radius (shininess of 4500) by product over sum; so
    that point source lights don't become single pixels on reflections */
    vec2 temp3 = vec2( 4500.0 );
    return temp1 * temp3 / (temp1+temp3);
}
vec3 envMappingLOD( in vec3 direction, in float LoD )
{
    vec4 result = textureCubeLod( cubeMap, direction, LoD );
    return result.rgb * result.a;
}

//Per-light called subroutines and macros:

float selfshadow_step( in float cosa )
{
    float temp1 = 77.7 * cosa;
    float temp2 = temp1 * temp1;
    return 0.5 * temp1 / sqrt( 1.0 + temp2 ) + 0.5;
}
void soft_penumbra_NdotL
(
  in vec3 normal, in vec3 vnormal, in vec3 light,
  out float selfshadow, out float NdotL, out float vNdotL
)
{
    vec2 result;
    float cosa = dot( vnormal, light );
    result.x = dot( normal, light );
    result.y = cosa;
    float ss = selfshadow_step( cosa );
    result += vec2( 0.02 );
    result *= ( 0.97 * ss );
    vNdotL = clamp( result.y, 0.0, 1.0 );
    selfshadow = ss;
    NdotL = clamp( result.x, 0.0, sqrt(vNdotL) );
}
float fresnel( in float cosa, in float k )
{
   float tmp1 = sqrt(1.0-(1.0-cosa*cosa)/(k*k));
   float tmp2 = k*cosa;
   float tmp3 = k*tmp1;
   float tmp4 = (tmp1-tmp2)/(tmp1+tmp2+0.0001);
   tmp1 = (cosa-tmp3)/(cosa+tmp3+0.0001);
   return 0.5*(tmp1*tmp1+tmp4*tmp4);
}
void perlite
(
  in vec3 light, in vec3 normal, in vec3 vnormal, in vec3 reflection,
  in vec3 lightDiffuse, in float lightAtt,
  in float fresnel_blend, in float k, in vec2 ltd_glosses,
  inout vec3 DLacc, inout vec3 MSacc, inout vec3 FSacc
)
{
	float selfshadow, NdotL, vNdotL;
	soft_penumbra_NdotL( normal, vnormal, light, selfshadow, NdotL, vNdotL );
	//cos of reflection to light angle
	float RdotL = clamp( dot( reflection, light), 0.0, 4.0*vNdotL );
    //  precalculate some factors used more than once
    vec3 incident_light = lightDiffuse.rgb * lightAtt * selfshadow;
    float fresnel_refl = fresnel_blend * fresnel( NdotL, k );
    vec3 reflected_light = incident_light * fresnel_refl;
    vec3 refracted_light = incident_light * (1.0-fresnel_refl);
    float ltd_Mgloss = ltd_glosses.x;
    float ltd_Fgloss = ltd_glosses.y;
    //  * DL - diffuse light: Needs to be multiplied by
    //  (1-fresnel_blend*fresnel reflection), from light vector
    DLacc += ( NdotL * refracted_light );
    //  * MS - metallic specularity: Modulated by
    //  (1-fresnel_blend*fresnel reflection), also, and
    //  metallic shininess phong. And we also multiply by the
    //  shininess, as smaller spots get more light concentration
    MSacc += ( pow( NdotL, ltd_Mgloss ) * ltd_Mgloss * refracted_light ); 
    //  * FS - fresnel specularity: Doesn't need fresnel, really,
    //  as the only fresnel applicable is view-vector-dependent,
    //  which can be applied afterwards, to the accumulated value;
    //  so, we'll multiply the accumulator by view fresnel after...
    FSacc += ( pow( NdotL, ltd_Fgloss ) * ltd_Fgloss ); //*reflected_light);
}
#define lighting(name, lightno_gl, lightno_tex) \
void name( \
   in vec3 normal, in vec3 vnormal, in  vec3 reflection, \
   in float k_blend, in float k_const, in vec2 limited_glosses, \
   inout vec3 DL_acc, inout vec3 MS_acc, inout vec3 FS_acc) \
{ \
    perlite( normalize(gl_TexCoord[lightno_tex].xyz), \
      normal, vnormal, reflection, \
      gl_FrontLightProduct[lightno_gl].diffuse.rgb, \
      gl_TexCoord[lightno_tex].w, \
      k_blend, k_const, limited_glosses, \
      DL_acc, MS_acc, FS_acc); \
}
lighting(lite0, 0, 5)
lighting(lite1, 1, 6)

//final blend subroutines:

vec3 multibounce_color( in vec3 color, in float refl_factor, in float blend )
{
    /* After light's penetrated the outer dielectric & is about to hit the inner, opaque
    layer below, instead of *= color, use this to account for multiple inner bouncings
    The formula is: *= (c-refL*c)/(1-refL*c), where refL is the fresnel reflectivity and
    c is the color of the material under the dielectric coating. But blended materials,
    such as plastics, are only partially covered by a specular dielectric layer, so we do
    have to allow some pure color reflectivity; thus the "blend" thing... */
    vec3 temp = color * refl_factor;
    return blend*(color-temp)/(vec3(1.0)-temp) + (1.0-blend)*color;
}
vec3 final_blend
(
 in vec3 DLacc, in vec3 MSacc, in vec3 FSacc, //DL=DiffuseLight; MS=MetallicSpec; FS=FresnelSpec
 in vec3 AMBenv, in vec3 MSenv, in vec3 FSenv, //Environment mapped counterparts
 in vec3 view_vec, in vec3 norm_vec, in vec3 glow,
 in vec3 diff_color, in vec3 spec_color,
 in float dielectric_blend, in float dielectric_k, in float AO
)
{
    float NdotV = clamp( dot( norm_vec, view_vec ), 0.0, 1.0 );
    float AO_fresnel_reflection = (1.0-dielectric_k)/(1.0+dielectric_k );
    AO_fresnel_reflection *= AO_fresnel_reflection;
    float reflection = fresnel( NdotV, dielectric_k );
    float diffAOfactor = sqrt(AO);
    float MspecAOfactor = AO * AO;
    float FspecAOfactor = AO * diffAOfactor;
    //Begin with the accumulated direct light (diffuse lighting)
    vec3 final_acc = (DLacc*diffAOfactor);
    //Add ambient light minus fresnel-reflected (note that amb reflection IS the envmapping)
    final_acc += ( AMBenv * AO * (1.0-AO_fresnel_reflection) );
    //multiply by the diffuse color
    final_acc *= multibounce_color( diff_color, reflection, dielectric_blend );
    //MSenv is multiplied by 1-fresnel to account for partial reflection on entering dielectric
    final_acc += (  ( MSacc + (MSenv*(1.0-reflection)) ) *
         multibounce_color( spec_color, reflection, dielectric_blend ) * MspecAOfactor  );
    //Both diffuse and metallic spec have to exit the dielectric. So, we'd multiply by the
    //refraction, here. However, multibounce_color() already took care of it, so, nought to do.
    //We just add fresnel specularity and... By the way, NOW we will multiply by fresnel
    //reflectivity from view angle --remember we didn't in perlite():
    final_acc += ( (FSacc+FSenv) * dielectric_blend * reflection * FspecAOfactor );
    return final_acc + glow;
}

//main:

void main()
{
    //READ INTPUT DATA:
    //get interpolated, per-vertex data
    vec2 texcoords2 = gl_TexCoord[0].xy;
    vec3 vnormal_v3 = fastnormalize( gl_TexCoord[1].xyz );
    vec3 tangent_v3 = gl_TexCoord[2].xyz;
    vec3 cotangent_v3 = gl_TexCoord[3].xyz;
    vec3 eye_v3 = gl_TexCoord[4].xyz;
    //get texture data needed for detail and normal computations first
    vec4 DMG_in4 = texture2D( damgMap,texcoords2 );
    vec4 DET_in4 = texture2D( detailMap,16.0*texcoords2 );
    vec4 NOR_in4 = texture2D( normMap,texcoords2 );
    //then the rest
    vec4 SPC_in4 = texture2D( specMap,texcoords2 );
    vec4 COL_in4 = texture2D( diffMap,texcoords2 );
    vec4 GLO_in4 = texture2D( glowMap,texcoords2 );
    // computed vectors
    vec3 reflection_v3 = -reflect(eye_v3,vnormal_v3);
    //UNPACK:
    //we use a macro table to ease possible future changes to the texture packing:
    #define _matcolor_in_ ((COL_in4.rgb))
    #define _matalpha_in_ ((COL_in4.a))
    #define _damage_dUdV_ ((DMG_in4.rb))
    #define _detailblend_ ((DMG_in4.g))
    #define _damageAOdrk_ ((DMG_in4.a))
    #define _specdiffbal_ ((SPC_in4.r))
    #define _dielblendin_ ((SPC_in4.g))
    #define _dielectrkin_ ((SPC_in4.b))
    #define _shininessin_ ((SPC_in4.a))
    #define _glowcolorin_ ((GLO_in4.rgb))
    #define _ambientoccl_ ((GLO_in4.a))
    #define _normalmapin_ ((vec2(0.3333*(NOR_in4.r+NOR_in4.g+NOR_in4.b),NOR_in4.a)))
    #define _detail_dUdV_ ((DET_in4.rb))
    #define _detail_wild_ ((DET_in4.g))
    #define _detailgloss_ ((DET_in4.a))
    /* The most urgent item to unpack is the detail texture data because at its minimum
    level, detail provides dithering to hide DXT quantization; but such dithering needs
    to be applied before non-linear transformations. Keep in mind that all four rgba
    channels of the detail texture will be scaled to use the full 0-1 range. Minimum
    detail application to a channel will be where full range matches 1 channel step. */
    ///////////////////////////////////////////////////////////////////////////
    //CORE SHADER A.I. --MATERIAL FAMILY DETECTION:
    //characterize the material to determine how to interpret shininess, detail, etc.:
    float ismetal_ch1 = is_metal_decode( _specdiffbal_ );
    //"nonzerok" is short for "has dielectric k greater than 1.2 or so"
    float nonzerok_ch1 = nonzerok_decode( _dielectrkin_ );
    //dielectric blend to shininess relevance factor:
    float relevance_factor = DBSRF( _dielblendin_, _shininessin_ );
    ///////////////////////////////////////////////////////////////////////////
    //Manage distribution of detail texture application:
    float bump_det_fac1, nonbump_det_fac1;
    // decide: bumpy detail or textury detail?
    detail_blend_decode( _detailblend_, bump_det_fac1, nonbump_det_fac1 );
    // metallic vs non-metallic textury detail deciding fate of detail.green:
    float dielectricblend_detail = ismetal_ch1 * nonbump_det_fac1;
    float diffuse_detail = nonbump_det_fac1 - dielectricblend_detail;
    // metallic shininess detail mostly deciding fate of detail.alpha:
    float adjusted_gloss = nonbump_det_fac1 * make_signed(_detailgloss_);
    float gloss_detail = ismetal_ch1 * adjusted_gloss;
    // for non-metals, dilectric balance vs shininess relevance spells fate of detail.alpha:
    dielectricblend_detail += ( (adjusted_gloss - gloss_detail) * relevance_factor );
    //Apply details and damage:
    float damage = cloakdmg.b;
    float integrity = 1.0 - damage;
    // to normal:
    vec2 dUdV_in2 = dUdV_first_decode(_normalmapin_);
    dUdV_in2 += ( dUdV_first_decode(_damage_dUdV_) * damage );
    dUdV_in2 += ( dUdV_first_decode(_detail_dUdV_) * (bump_det_fac1+1.0/32.0) );
    // to diff/spec balance and glow:
    float diffuse_jitter = (diffuse_detail+1.0/32.0) * make_signed(_detail_wild_);
    float specdiffbal_in1 = _specdiffbal_ + diffuse_jitter;
    vec3 glow_in3 = _glowcolorin_ + vec3( diffuse_jitter );
    // to shininess:
    float s_gloss_det = make_signed(_detailgloss_);
    float shininess_in = _shininessin_ + (gloss_detail+1.0/256.0)*s_gloss_det;
    // to dielectric blend:
    float dielectricblend_in1 = _dielblendin_ + (dielectricblend_detail+1.0/64.0)*s_gloss_det;
    // to ambient occlusion:
    float AO_darkener1 = lerp( damage(), 1.0, _damageAOdrk_ );
    float ao_mat1 = _ambientoccl_ * AO_darkener1;
    //Non-linear transformations, rangings, and any unpacking left:
    // diffuse, alpha and specular:
    vec3 spec_mat3 = _matcolor_in_ * _specdiffbal_;
    vec3 diff_mat3 = _matcolor_in_ - spec_mat3;
    vec3 glow_mat3 = glow_in3 * glow_in3 * inv_damage(); //de-gamma & damage fade
    // shininess CTRL goes to metallic spec for metals; dielectric gloss for non-metals;
    // and the defaults are min for both; except when dielectric k is 0/trivial we want to
    // max out shininess for fgloss, as it will be used for dual specularity metals
    vec2 lin_gloss2, gloss_lod2, glosses2;
    distribute_gloss( ismetal_ch1, nonzerok_ch1, shininess_in, lin_gloss2 );
    gloss_lod2 = lin_gloss_2_LOD( lin_gloss2 );
    glosses2 = lin_gloss_2_exp( lin_gloss2 );
    // compute final normal
    vec3 tmp3 = dUdV_final_decode( dUdV_in2 );
    vec3 normal_v3 = imatmul( tangent_v3, cotangent_v3, vnormal_v3, tmp3 );
    // dielectric stuff:
    float dielec_blend_mat1 = dielectricblend_decode( dielectricblend_in1 );
    float dielectric_k_mat1 = dielectric_decode( _dielectrkin_ );
    // LOD for ambient lighting:
    float ambient_lod1 = ambient_LOD( dielectric_k_mat1 );
    //texture fetches (dependent on normal)
    vec3 ambenv_il3 = envMappingLOD( normal_v3, ambient_lod1 ); //ambient env mapping
    vec3 MSenv_il3 = envMappingLOD( reflection_v3, gloss_lod2.x ); //metallic spec env mapping
    vec3 FSenv_il3 = envMappingLOD( reflection_v3, gloss_lod2.y ); //fresnel spec env mapping
    //END OF UNPACKING
    ///////////////////////////////////////////////////////////////////////////
    //PER-LIGHT COMPUTATIONS
    // initialize accumulators
    vec3 DL_acc3, MS_acc3, FS_acc3;
    DL_acc3 = MS_acc3 = FS_acc3 = vec3( 0.0 );
    // then loop:
    if( light_enabled[0] != 0 )
      lite0
      (
        normal_v3,vnormal_v3,reflection_v3,
        dielec_blend_mat1,dielectric_k_mat1,glosses2,
        DL_acc3,MS_acc3,FS_acc3
      );
    if( light_enabled[1] != 0 )
      lite1
      (
        normal_v3,vnormal_v3,reflection_v3,
        dielec_blend_mat1,dielectric_k_mat1,glosses2,
        DL_acc3,MS_acc3,FS_acc3
      );
    //FINAL BLEND
    //////vec3 reflection_v3 = -reflect(eye_v3,vnormal_v3);
    vec4 result4;
    result4.rgb = final_blend
    (
      DL_acc3, MS_acc3, FS_acc3, ambenv_il3, MSenv_il3, FSenv_il3,
      eye_v3, vnormal_v3, glow_mat3, diff_mat3, spec_mat3,
      dielec_blend_mat1, dielectric_k_mat1, ao_mat1
    );
    result4.a = _matalpha_in_;
    //ALPHA and CLOAK
    result4.rgb *= _matalpha_in_; //mul by 1-bit alpha interpolated
    result4 *= cloak_alpha();
    //WRITE
    gl_FragColor = result4;
}
It says,

Code: Select all

Name,GPR,Min,Max,Avg,Est Cycles(Bi),ALU:TEX(Bi),Est Cycles(Tri),ALU:TEX(Tri),Est Cycles(Aniso),ALU:TEX(Aniso),BottleNeck(Bi),BottleNeck(Tri),BottleNeck(Aniso),%s\Clock(Bi),%s\Clock(Tri),%s\Clock(Aniso),Scratch Reg
Radeon 9700,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A
Radeon x800,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A
Radeon x850,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A,N/A
Radeon x1800,26,93.00,154.00,126.11,126.11,9.13,126.11,7.61,126.11,6.52,ALU,ALU,ALU,0.13,0.13,0.13,0
Radeon x1300,26,93.00,154.00,123.44,123.44,12.03,123.44,10.03,123.44,8.59,ALU,ALU,ALU,0.03,0.03,0.03,0
Radeon x1600,26,31.67,52.00,42.42,42.42,3.35,42.42,2.79,42.42,2.39,ALU,ALU,ALU,0.09,0.09,0.09,0
Radeon x1900,26,31.67,52.00,44.71,44.71,2.05,44.71,1.70,44.71,1.46,ALU,ALU,ALU,0.36,0.36,0.36,0
Radeon HD 2900,19,18.75,33.25,26.00,26.00,2.89,26.00,2.41,26.00,2.06,ALU,ALU,ALU,0.62,0.62,0.62,0
Radeon HD 2400,19,75.00,133.00,104.00,104.00,11.56,104.00,9.63,104.00,8.25,ALU,ALU,ALU,0.04,0.04,0.04,0
Radeon HD 2600,19,25.00,44.33,34.67,34.67,3.85,34.67,3.21,34.67,2.75,ALU,ALU,ALU,0.12,0.12,0.12,0
Radeon HD 3870,19,18.75,33.25,26.00,26.00,2.89,26.00,2.41,26.00,2.06,ALU,ALU,ALU,0.62,0.62,0.62,0
Radeon HD 4870,19,9.00,13.30,10.40,10.40,2.89,10.40,2.41,10.40,2.06,ALU,ALU,ALU,1.54,1.54,1.54,0
Okay, a pixel speaks better than 1024 bits:

Image

Looks like the Radeon 1300 and 2400 are trashed, with 0.03 and 0.04 pixels per clock...
HD 4870 is the way to go, folks :D

Ehm... I still haven't done the dual shininess metals special case.

EDIT:
NVshaderPerf analysis:

Code: Select all

!!NVfp4.0
# cgc version 2.0.0012, build date Jan 30 2008
# command line args: -profile gp4fp -oglsl
# source file: new_cinemut_opaque.fp
#vendor NVIDIA Corporation
#version 2.0.0.12
#profile gp4fp
#program main
#semantic light_enabled
#semantic max_light_enabled
#semantic cubeMap
#semantic diffMap
#semantic specMap
#semantic glowMap
#semantic normMap
#semantic damgMap
#semantic detailMap
#semantic cloakdmg
#semantic gl_FrontLightProduct : state.lightprod.front
#var int light_enabled[0] :  : c[0] : -1 : 1
#var int light_enabled[1] :  : c[1] : -1 : 1
#var int light_enabled[2] :  :  : -1 : 0
#var int light_enabled[3] :  :  : -1 : 0
#var int light_enabled[4] :  :  : -1 : 0
#var int light_enabled[5] :  :  : -1 : 0
#var int light_enabled[6] :  :  : -1 : 0
#var int light_enabled[7] :  :  : -1 : 0
#var int max_light_enabled :  :  : -1 : 0
#var samplerCUBE cubeMap :  : texunit 6 : -1 : 1
#var sampler2D diffMap :  : texunit 4 : -1 : 1
#var sampler2D specMap :  : texunit 3 : -1 : 1
#var sampler2D glowMap :  : texunit 5 : -1 : 1
#var sampler2D normMap :  : texunit 2 : -1 : 1
#var sampler2D damgMap :  : texunit 0 : -1 : 1
#var sampler2D detailMap :  : texunit 1 : -1 : 1
#var float4 cloakdmg :  : c[2] : -1 : 1
#var float4 gl_TexCoord[0] : $vin.TEX0 : TEX0 : -1 : 1
#var float4 gl_TexCoord[1] : $vin.TEX1 : TEX1 : -1 : 1
#var float4 gl_TexCoord[2] : $vin.TEX2 : TEX2 : -1 : 1
#var float4 gl_TexCoord[3] : $vin.TEX3 : TEX3 : -1 : 1
#var float4 gl_TexCoord[4] : $vin.TEX4 : TEX4 : -1 : 1
#var float4 gl_TexCoord[5] : $vin.TEX5 : TEX5 : -1 : 1
#var float4 gl_TexCoord[6] : $vin.TEX6 : TEX6 : -1 : 1
#var float4 gl_TexCoord[7] :  :  : -1 : 0
#var float4 gl_FrontLightProduct[0].ambient : state.lightprod[0].front.ambient :  : -1 : 0
#var float4 gl_FrontLightProduct[0].diffuse : state.lightprod[0].front.diffuse : c[3] : -1 : 1
#var float4 gl_FrontLightProduct[0].specular : state.lightprod[0].front.specular :  : -1 : 0
#var float4 gl_FrontLightProduct[1].ambient : state.lightprod[1].front.ambient :  : -1 : 0
#var float4 gl_FrontLightProduct[1].diffuse : state.lightprod[1].front.diffuse : c[4] : -1 : 1
#var float4 gl_FrontLightProduct[1].specular : state.lightprod[1].front.specular :  : -1 : 0
#var float4 gl_FrontLightProduct[2].ambient : state.lightprod[2].front.ambient :  : -1 : 0
#var float4 gl_FrontLightProduct[2].diffuse : state.lightprod[2].front.diffuse :  : -1 : 0
#var float4 gl_FrontLightProduct[2].specular : state.lightprod[2].front.specular :  : -1 : 0
#var float4 gl_FrontLightProduct[3].ambient : state.lightprod[3].front.ambient :  : -1 : 0
#var float4 gl_FrontLightProduct[3].diffuse : state.lightprod[3].front.diffuse :  : -1 : 0
#var float4 gl_FrontLightProduct[3].specular : state.lightprod[3].front.specular :  : -1 : 0
#var float4 gl_FrontLightProduct[4].ambient : state.lightprod[4].front.ambient :  : -1 : 0
#var float4 gl_FrontLightProduct[4].diffuse : state.lightprod[4].front.diffuse :  : -1 : 0
#var float4 gl_FrontLightProduct[4].specular : state.lightprod[4].front.specular :  : -1 : 0
#var float4 gl_FrontLightProduct[5].ambient : state.lightprod[5].front.ambient :  : -1 : 0
#var float4 gl_FrontLightProduct[5].diffuse : state.lightprod[5].front.diffuse :  : -1 : 0
#var float4 gl_FrontLightProduct[5].specular : state.lightprod[5].front.specular :  : -1 : 0
#var float4 gl_FrontLightProduct[6].ambient : state.lightprod[6].front.ambient :  : -1 : 0
#var float4 gl_FrontLightProduct[6].diffuse : state.lightprod[6].front.diffuse :  : -1 : 0
#var float4 gl_FrontLightProduct[6].specular : state.lightprod[6].front.specular :  : -1 : 0
#var float4 gl_FrontLightProduct[7].ambient : state.lightprod[7].front.ambient :  : -1 : 0
#var float4 gl_FrontLightProduct[7].diffuse : state.lightprod[7].front.diffuse :  : -1 : 0
#var float4 gl_FrontLightProduct[7].specular : state.lightprod[7].front.specular :  : -1 : 0
#var float4 gl_FragColor : $vout.COLOR : COL : -1 : 1
PARAM c[5] = { program.local[0..2],
		state.lightprod[0].front.diffuse,
		state.lightprod[1].front.diffuse };
ATTRIB fragment_texcoord[] = { fragment.texcoord[0..6] };
TEMP R0, R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12;
TEMP RC, HC;
OUTPUT oCol = result.color;
TEX.F R3, fragment.texcoord[0], texture[3], 2D;
TEX.F R4, fragment.texcoord[0], texture[0], 2D;
ADD.F R0.w, R4.y, {-0.49609375}.x;
ADD.F R0.x, R3, {-0.58333302};
MUL.F R0.x, R0, {19.6444};
MAD.F R0.y, R0.x, R0.x, {1}.x;
RSQ.F R0.y, R0.y;
MUL.F R0.x, R0.y, R0;
MAD.F R1.z, R0.x, {0.5, 0.50247723}.x, {0.5, 0.50247723}.y;
MUL.F R0.xy, fragment.texcoord[0], {16}.x;
TEX.F R8, R0, texture[1], 2D;
MUL.F R1.w, R0, {2}.x;
MIN.F R0.x, -R1.w, {1};
MIN.F R1.w, R1, {1}.x;
MAX.F R1.w, R1, {0}.x;
MAX.F R5.w, R0.x, {0}.x;
ADD.F R6.x, R8.w, {-0.49609375};
MUL.F R6.y, R5.w, R6.x;
MUL.F R0.z, R3, R3;
MUL.F R0.x, R0.z, R0.z;
MUL.F R0.y, R0.x, {20000}.x;
MUL.F R7.x, R1.z, R6.y;
ADD.F R0.z, R0.y, {1}.x;
DIV.F R0.y, R0, R0.z;
ADD.F R0.x, R7, {0.00390625};
MAD.F R0.x, R0, R6, R3.w;
MUL.F R1.x, R1.z, R0;
MAD.F R0.y, R1.z, -R0, R1.z;
MAD.F R0.x, -R1.z, R0, R0;
ADD.F R1.y, R0.x, R0;
ADD.F R0.xy, -R1, {1.0625}.x;
MUL.F R5.xy, R1, R1;
ADD.F R0.zw, R1.xyxy, {1.0625}.x;
ADD.F R6.zw, -R1.xyxy, {0.92000002}.x;
MAD.F R5.xy, R1, R5, {1.0700001}.x;
MUL.F R5.xy, R5, R6.zwzw;
MUL.F R1.xy, R1, {8.5}.x;
MAD.F R1.xy, R5, {15.2}.x, R1;
ADD.F R6.zw, R1.xyxy, {-6}.x;
ADD.F R1.y, -R7.x, R6;
ADD.F R1.w, R1, {0.03125}.x;
MAD.F R1.x, -R3.w, R3.w, R3.w;
MAD.F R4.w, R4, c[2].z, -c[2].z;
RCP.F R0.y, R0.y;
RCP.F R0.x, R0.x;
MUL.F R0.xy, R0.zwzw, R0;
MUL.F R0.zw, R0.xyxy, R0.xyxy;
MUL.F R2.xy, R0.zwzw, R0;
TEX.F R0, fragment.texcoord[0], texture[2], 2D;
ADD.F R0.y, R0.x, R0;
ADD.F R2.zw, R2.xyxy, {4500}.x;
ADD.F R0.z, R0.y, R0;
RCP.F R0.x, R2.z;
RCP.F R0.y, R2.w;
ADD.F R2.zw, R4.xyxz, {-0.49609375}.x;
MUL.F R4.xy, R2, R0;
MUL.F R0.z, R0, {0.33329999}.x;
MAD.F R0.zw, R2, c[2].z, R0;
ADD.F R2.zw, R8.xyxz, {-0.49609375}.x;
MAD.F R0.zw, R2, R1.w, R0;
ADD.F R2.xy, R0.zwzw, {-0.49609375}.x;
MUL.F R0.xyz, R2.y, fragment.texcoord[3];
DP3.F R0.w, fragment.texcoord[1], fragment.texcoord[1];
MAD.F R2.xyz, R2.x, fragment.texcoord[2], R0;
MAD.F R0.w, -R0, {0.5, 1.5}.x, {0.5, 1.5}.y;
MUL.F R0.xyz, R0.w, fragment.texcoord[1];
MAD.F R2.xyz, R0, {0.25}.x, R2;
DP3.F R0.w, R2, R2;
ADD.F R2.w, R3.z, {1.0625}.x;
ADD.F R1.w, -R3.z, {1.0625}.x;
DIV.F R1.w, R2, R1.w;
RSQ.F R2.w, R0.w;
MUL.F R12.xy, R4, {4500}.x;
MUL.F R4.xyz, R2.w, R2;
RCP.F R0.w, R1.w;
MAD.F R0.w, R0, R0, -R0;
MAD.F R2.w, R0, {4, 9}.x, {4, 9}.y;
MAD.F R0.w, -R3.y, R3.y, R3.y;
ADD.F R1.x, R0.w, R1;
ADD.F R1.x, R1, {9.9999997e-005};
DIV.F R0.w, R0, R1.x;
MUL.F R3.z, R5.w, R1;
MAD.F R0.w, R1.y, R0, R3.z;
MOV.F R2.xyz, R4;
TXL.F R2, R2, texture[6], CUBE;
MUL.F R5.xyz, R2, R2.w;
DP3.F R1.x, R0, fragment.texcoord[4];
MUL.F R1.xyz, R0, R1.x;
MAD.F R1.xyz, -R1, {2}.x, fragment.texcoord[4];
ADD.F R0.w, R0, {0.015625}.x;
MAD.F R0.w, R6.x, R0, R3.y;
MOV.F R2.xyz, -R1;
MOV.F R2.w, R6.z;
TXL.F R2, R2, texture[6], CUBE;
MUL.F R6.xyz, R2, R2.w;
ADD.F R0.w, R0, {-0.5}.x;
MUL.F R2.x, R0.w, R0.w;
MAD.F R3.y, R2.x, {1.8, 0.55000001}.x, {1.8, 0.55000001};
MOV.F R2.xyz, -R1;
RCP.F R1.x, R3.y;
MOV.F R2.w, R6;
ADD.F R3.y, R5.w, -R3.z;
TXL.F R2, R2, texture[6], CUBE;
ADD.F R6.w, R8.y, {-0.49609375}.x;
MUL.F R7.xyz, R2, R2.w;
TEX.F R2, fragment.texcoord[0], texture[4], 2D;
MAD.F R0.w, R0, R1.x, {0.5}.x;
MUL.F R1.xyz, R2, R3.x;
ADD.F R5.w, R3.y, {0.03125}.x;
TEX.F R3, fragment.texcoord[0], texture[5], 2D;
MAD.F R3.xyz, R5.w, R6.w, R3;
MUL.F R3.xyz, R3, R3;
MAD.F R3.w, R4, R3, R3;
MOV.S R4.w, {0}.x;
SNE.S R4.w, c[0].x, R4;
MOV.U.CC HC.x, -R4.w;
ADD.F R2.xyz, R2, -R1;
MAD.F R3.xyz, R3, -c[2].z, R3;
MOV.F R8.xyz, {0}.x;
MOV.F R9.xyz, {0}.x;
MOV.F R10.xyz, {0}.x;
IF    NE.x;
DP3.F R4.w, fragment.texcoord[5], fragment.texcoord[5];
RSQ.F R4.w, R4.w;
MUL.F R8.xyz, R4.w, fragment.texcoord[5];
DP3.F R5.w, R0, R8;
MUL.F R4.w, R5, {77.699997}.x;
DP3.F R8.x, R8, R4;
MAD.F R6.w, R4, R4, {1}.x;
RSQ.F R6.w, R6.w;
MUL.F R4.w, R6, R4;
MOV.F R8.y, R5.w;
MUL.F R6.w, R1, R1;
MAD.F R5.w, R4, {0.5}.x, {0.5}.x;
ADD.F R8.xy, R8, {0.02}.x;
MUL.F R8.xy, R5.w, R8;
MUL.F R8.xy, R8, {0.97000003}.x;
MIN.F R4.w, R8.y, {1}.x;
MAX.F R4.w, R4, {0}.x;
RSQ.F R4.w, R4.w;
RCP.F R4.w, R4.w;
MIN.F R4.w, R4, R8.x;
MAX.F R4.w, R4, {0}.x;
RCP.F R7.w, R6.w;
MUL.F R6.w, R4, R4;
MAD.F R6.w, -R6, R7, R7;
ADD.F R6.w, -R6, {1}.x;
RSQ.F R7.w, R6.w;
MUL.F R8.y, R4.w, R1.w;
RCP.F R8.x, R7.w;
ADD.F R8.z, R8.x, R8.y;
DIV.F R6.w, R1, R7.w;
ADD.F R7.w, R4, R6;
ADD.F R8.x, R8, -R8.y;
ADD.F R8.z, R8, {9.9999997e-005}.x;
DIV.F R8.x, R8, R8.z;
ADD.F R7.w, R7, {9.9999997e-005}.x;
ADD.F R6.w, R4, -R6;
DIV.F R6.w, R6, R7.w;
MUL.F R7.w, R8.x, R8.x;
MUL.F R8.xyz, fragment.texcoord[5].w, c[3];
MUL.F R8.xyz, R5.w, R8;
MAD.F R6.w, R6, R6, R7;
MUL.F R5.w, R6, R0;
MUL.F R9.xyz, -R5.w, R8;
MAD.F R8.xyz, R9, {0.5}.x, R8;
POW.F R5.w, R4.w, R12.x;
MUL.F R10.xyz, R4.w, R8;
MUL.F R5.w, R5, R12.x;
POW.F R4.w, R4.w, R12.y;
MUL.F R9.xyz, R8, R5.w;
MUL.F R8.xyz, R12.y, R4.w;
ENDIF;
MOV.S R4.w, {0}.x;
SNE.S R4.w, c[1].x, R4;
MOV.U.CC HC.x, -R4.w;
IF    NE.x;
DP3.F R4.w, fragment.texcoord[6], fragment.texcoord[6];
RSQ.F R4.w, R4.w;
MUL.F R11.xyz, R4.w, fragment.texcoord[6];
DP3.F R5.w, R0, R11;
DP3.F R4.x, R11, R4;
MUL.F R4.w, R5, {77.699997}.x;
MAD.F R6.w, R4, R4, {1}.x;
RSQ.F R6.w, R6.w;
MOV.F R4.y, R5.w;
MUL.F R4.w, R6, R4;
MAD.F R5.w, R4, {0.5}.x, {0.5}.x;
ADD.F R4.xy, R4, {0.02}.x;
MUL.F R4.xy, R5.w, R4;
MUL.F R4.xy, R4, {0.97000003}.x;
MIN.F R4.y, R4, {1}.x;
MAX.F R4.y, R4, {0}.x;
RSQ.F R4.y, R4.y;
RCP.F R4.y, R4.y;
MIN.F R4.y, R4, R4.x;
MAX.F R4.w, R4.y, {0}.x;
MUL.F R4.x, R1.w, R1.w;
RCP.F R4.y, R4.x;
MUL.F R4.x, R4.w, R4.w;
MAD.F R4.x, -R4, R4.y, R4.y;
ADD.F R4.x, -R4, {1};
RSQ.F R4.y, R4.x;
MUL.F R6.w, R4, R1;
RCP.F R4.z, R4.y;
ADD.F R7.w, R4.z, R6;
DIV.F R4.x, R1.w, R4.y;
ADD.F R4.y, R4.w, R4.x;
ADD.F R4.z, R4, -R6.w;
ADD.F R7.w, R7, {9.9999997e-005}.x;
DIV.F R4.z, R4, R7.w;
ADD.F R4.x, R4.w, -R4;
ADD.F R4.y, R4, {9.9999997e-005}.x;
DIV.F R6.w, R4.x, R4.y;
MUL.F R7.w, R4.z, R4.z;
MUL.F R4.xyz, fragment.texcoord[6].w, c[4];
MUL.F R4.xyz, R5.w, R4;
MAD.F R6.w, R6, R6, R7;
MUL.F R5.w, R6, R0;
MUL.F R11.xyz, -R5.w, R4;
MAD.F R4.xyz, R11, {0.5}.x, R4;
POW.F R5.w, R4.w, R12.x;
MAD.F R10.xyz, R4.w, R4, R10;
MUL.F R5.w, R5, R12.x;
POW.F R4.w, R4.w, R12.y;
MAD.F R9.xyz, R4, R5.w, R9;
MAD.F R8.xyz, R12.y, R4.w, R8;
ENDIF;
DP3.F R0.x, R0, fragment.texcoord[4];
MUL.F R0.y, R1.w, R1.w;
MIN.F R0.x, R0, {1};
MAX.F R0.x, R0, {0};
RCP.F R0.z, R0.y;
MUL.F R0.y, R0.x, R0.x;
MAD.F R0.y, -R0, R0.z, R0.z;
ADD.F R0.y, -R0, {1}.x;
RSQ.F R0.y, R0.y;
RCP.F R0.z, R0.y;
MUL.F R4.x, R0, R1.w;
ADD.F R4.y, R0.z, R4.x;
ADD.F R0.z, R0, -R4.x;
ADD.F R4.x, R4.y, {9.9999997e-005};
DIV.F R4.x, R0.z, R4.x;
DIV.F R0.y, R1.w, R0.y;
ADD.F R0.z, R0.x, R0.y;
ADD.F R0.x, R0, -R0.y;
ADD.F R0.z, R0, {9.9999997e-005}.x;
DIV.F R0.x, R0, R0.z;
MUL.F R4.x, R4, R4;
MAD.F R0.x, R0, R0, R4;
MUL.F R4.w, R0.x, {0.5}.x;
MUL.F R4.xyz, R4.w, R2;
ADD.F R0.xyz, R2, -R4;
ADD.F R4.xyz, -R4, {1}.x;
MAD.F R2.xyz, -R0.w, R2, R2;
MUL.F R0.xyz, R0.w, R0;
RCP.F R4.x, R4.x;
RCP.F R4.y, R4.y;
RCP.F R4.z, R4.z;
MAD.F R0.xyz, R0, R4, R2;
MAD.F R4.xyz, -R4.w, R6, R6;
MUL.F R2.xyz, R4.w, R1;
ADD.F R6.xyz, R1, -R2;
ADD.F R2.xyz, -R2, {1}.x;
MUL.F R6.xyz, R0.w, R6;
RCP.F R2.x, R2.x;
RCP.F R2.y, R2.y;
MAD.F R1.xyz, -R0.w, R1, R1;
RCP.F R2.z, R2.z;
MAD.F R1.xyz, R6, R2, R1;
ADD.F R2.xyz, R9, R4;
MUL.F R1.xyz, R2, R1;
ADD.F R2.x, R1.w, {1};
RCP.F R4.x, R2.x;
MAD.F R1.w, -R1, R4.x, R4.x;
MUL.F R2.y, R3.w, R3.w;
MUL.F R1.xyz, R1, R2.y;
MUL.F R4.xyz, R3.w, R5;
MUL.F R5.x, R1.w, R1.w;
ADD.F R2.xyz, R8, R7;
RSQ.F R1.w, R3.w;
MAD.F R4.xyz, -R5.x, R4, R4;
MUL.F R2.xyz, R2, R0.w;
RCP.F R5.x, R1.w;
MAD.F R4.xyz, R10, R5.x, R4;
MAD.F R0.xyz, R4, R0, R1;
DIV.F R0.w, R3, R1.w;
MUL.F R1.xyz, R2, R4.w;
MAD.F R0.xyz, R1, R0.w, R0;
ADD.F R0.xyz, R0, R3;
MOV.F R0.w, R2;
MUL.F R0.xyz, R0, R2.w;
MUL.F oCol, R0, c[2].xxxy;
END
# 293 instructions, 13 R-regs
It says in standard output:
nvshaderperf wrote:GPU G80, flags 0x0
Results: 457 cycles, 47 r regs, 266,240,000 pixels/s
Let's see, at 1280 x 1024, we got 1310720 pixels.

266,240,000/1,310,720 = 203.125 FPS !!!

Not bad!

I hope you're not stuck with a 6600GT, though...
nvshaderperf wrote:GPU NV43-GT, flags 0x0
Results: 220 cycles, 13 r regs, 18,181,818 pixels/s
18,181,818/1,310,720 = 13.9 FPS :(

EDIT2:
Actually, 1.54 pixels per clock, for the Radeon HD 4870 is lighning fast:
Its clock speed is 750MHz, so that's more than a billion pixels/s; 4 times faster than my NVidia 8800.
klauss
Elite
Elite
Posts: 7243
Joined: Mon Apr 18, 2005 2:40 pm
Location: LS87, Buenos Aires, República Argentina

Post by klauss »

Wow - so much to read, that I won't.

Too much.

If you haven't figured it out yet, chuck, the difference between painted stuff and plastic stuff might be subsurface scattering. Now I'm not sure which is which, but I bet one has a stronger subsurface scattering effect than the other. The visual cue is that one has softer lighting than the other, because subsurface scattering blurs out light-shade boundaries.
Oíd mortales, el grito sagrado...
Call me "Menes, lord of Cats"
Wing Commander Universe
chuck_starchaser
Elite
Elite
Posts: 8014
Joined: Fri Sep 05, 2003 4:03 am
Location: Montreal
Contact:

Post by chuck_starchaser »

klauss wrote:Wow - so much to read, that I won't.

Too much.
Don't worry; I write mostly for myself. I just can't cope with the amount of stuff that accumulates in my
computer; but somehow when I commit stuff to a forum I know where to find it back when I need it. That's
the main reason I post all this stuff. And for record-keeping.

If you haven't figured it out yet, chuck, the difference between painted stuff and plastic stuff might
be subsurface scattering. Now I'm not sure which is which, but I bet one has a stronger subsurface scattering effect
than the other. The visual cue is that one has softer lighting than the other, because subsurface scattering blurs
out light-shade boundaries.
Good point! It did cross my mind that plastics might feature SSS. I suppose this is the lighter ones. I was thinking
about dark and very specular plastics; the kinds they try to pass for painted metal. There's a fundamental difference
in the specularity. When you get above 10 in dielectric constant, the function of angle that yields reflectivity
kind of flattens out; and I think plastics are typically like 30% diffuse and 60% specular; but with a higher dielectric
constant than paints, the bottom line fresnel reflectivity might be the same; but the paint's reflectivity changes a lot
with angle; whereas plastic being partly diffuse, and high k where not, their fresnel reflectivity kind of stays constant
with angle. I think that's the main difference.

BTW, I could get a fake subsurface scattering effect by changing parameters in the soft penumbra function, and
applying it to red (for skin SSS); probably a lot cheaper than writing the illumination down, then having the
cpu blur it and send it back....

EDIT:
Nevermind; that wouldn't work.

Working on a model to use for testing the shader:

Image

Saved time by finding an existing model; but it already took many hours of cleaning up; and it looks like it will
take a few more hours yet; and nothing in it makes much sense; like whoever did the original model didn't
copy a bike, but apparently did it out of his head, because there's no real frame; --bottom pipe of the frame
comes down from the front and towards the back wheel and ends somewhere, never attaching to anything;
the pedals are floating in the wrong places... The drive chain was going through the wheel and its cover was
going through the rear spring and shock; I fixed that. Needs rear-view mirrors among a lot of missing parts.
There's no unwrap; and the unwrap would be hard with the mesh as it is; I think I'll use a script, and use only
material and other bakes, rather than manually touch the textures at all.

I think tonight I'll work on learning ATI's CubeMapGen and make a few cubemaps; and finish the bike later.

Reason for wanting a bike for testing the shader is that with spaceships one doesn't know what materials to expect;
whereas a bike can be compared to photographs of real ones.
chuck_starchaser
Elite
Elite
Posts: 8014
Joined: Fri Sep 05, 2003 4:03 am
Location: Montreal
Contact:

Post by chuck_starchaser »

Not much progress, but some... Here's a Blender render:

Image

I've basically given up trying to make sense of the model; it just won't.
Just eye-candy, but don't look too keenly.
Anyways, done fixing up mesh disasters, and almost done assigning materials.
Well, "what kind of materials" you ask?
Alright, they don't and won't look right unless I slap on an environment map.
This is very rough, I agree. Without an env map, chrome looks black, period.
Once I finish the materials I'll do a quick UV unwrap and start baking textures.
Then I have to make a noodle to package them right.

EDIT:
Some of the blue parts are painted; some are plastic. Can you tell the difference?
No?
Good!
We'll see if CineMut makes it clear...
Post Reply