Groverburger's 3D Engine (g3d) v1.5.2 Release

Showcase your libraries, tools and other projects that help your fellow love users.
User avatar
pgimeno
Party member
Posts: 3656
Joined: Sun Oct 18, 2015 2:58 pm

Re: Groverburger's 3D Engine (g3d) v1.4 Release

Post by pgimeno »

Löve adds an abstraction layer on top of GLSL shaders; it adds main() itself, and that main() calls effect(). Take into account that Löve is designed for 2D, not 3D; otherwise the design (at least the parameters of effect()) would have been different for sure.

With version 11.3, you can look at what exact shader code it generates for your system, with this program:

Code: Select all

local vshader, fshader = love.graphics._shaderCodeToGLSL(false, nil,
  -- add some shader here
  [[vec4 effect(vec4 colour, Image tex, vec2 texpos, vec2 scrpos) {
     return Texel(tex, texpos) * colour; } ]])
print(fshader)
(better save the output to a file because it's long). The first `false` parameter means no GLES; if you want the GLES version change it to `true` For me, at the end of the code there's this snippet:

Code: Select all

vec4 effect(vec4 vcolor, Image tex, vec2 texcoord, vec2 pixcoord);

void main() {
	love_PixelColor = effect(VaryingColor, MainTex, VaryingTexCoord.st, love_PixelCoord);
}
#line 0
vec4 effect(vec4 colour, Image tex, vec2 texpos, vec2 scrpos) {
     return Texel(tex, texpos) * colour; } 
After `#line 0` the user code (the actual code that you passed to the shader) is inserted verbatim.

love_PixelCoord love_PixelColor is a version-dependent definition that is the variable where the fragment shader returns its result (defined to be gl_FragColor or gl_FragData in some GLSL versions, and an `out` variable in others), and is set to whatever the effect() function returns.

So, basically you have to modify the shader in such a way that instead of using main() and writing to gl_FragColor or to the out variable, it uses effect() and returns the value.

In fact, by monkey-patching that function and making it return the vertex and fragment shaders that you want, you can use a raw shader, but you better know what you're doing with respect to cross-system compatibility.
Last edited by pgimeno on Tue Nov 09, 2021 5:56 pm, edited 2 times in total.
User avatar
groverburger
Prole
Posts: 49
Joined: Tue Oct 30, 2018 9:27 pm

Re: Groverburger's 3D Engine (g3d) v1.4 Release

Post by groverburger »

4aiman wrote: Mon Nov 08, 2021 7:34 pm Can anyone explain why all tutorials on GLSL have this main() function but love2d works with effects?
Should I assume that the entire file will be included into main() ?
But main has no real return value, since it's void.

Seems like I'm googling it wrong, 'cause I can't find a simple example of ambient lighting + a point light source that I can plug in and get anything but pitch black screen.
Well, ok, the builtin one works just fine but I can't make things darker and illuminate a portion of a map with a light source.

I've looked at the suggested tutorial, but struggle to comprehend any of that, cause I can't plug it somewhere and toy with it till it'll make some sense.

Any help?
Yeah I also encountered the learning curve of trying to translate GLSL shaders into Love's dialect. I just bashed my head against a wall until it finally clicked (with lots of trial and error).

I whipped up a simple diffuse lighting fragment shader based on that tutorial and saved it in my project as lighting.frag, hopefully this gives you a good base to start from.

Code: Select all

// variables provided by g3d's vertex shader
varying vec4 worldPosition;
varying vec3 vertexNormal;

// the model matrix comes from the camera automatically
uniform mat4 modelMatrix;
uniform vec3 lightPosition = vec3(1,1,1);
uniform float ambient = 0.2;

vec4 effect(vec4 color, Image tex, vec2 texcoord, vec2 pixcoord) {
    // diffuse light
    // computed by the dot product of the normal vector and the direction to the light source
    vec3 lightDirection = normalize(lightPosition.xyz - worldPosition.xyz);
    vec3 normal = normalize(mat3(modelMatrix) * vertexNormal);
    float diffuse = max(dot(lightDirection, normal), 0);

    // get color from the texture
    vec4 texcolor = Texel(tex, texcoord);

    // if this pixel is invisible, get rid of it
    if (texcolor.a == 0.0) { discard; }

    // draw the color from the texture multiplied by the light amount
    float lightness = diffuse + ambient;
    return vec4((texcolor * color).rgb * lightness, 1.0);
}
I modified the Earth and Moon demo that comes with g3d to make the Earth and Moon use this shader.
Note how I define lightingShader, and update its matrices with g3d.camera.updateViewMatrix and g3d.camera.updateProjectionMatrix.

Code: Select all

local g3d = require "g3d"
local earth = g3d.newModel("assets/sphere.obj", "assets/earth.png", {4,0,0})
local moon = g3d.newModel("assets/sphere.obj", "assets/moon.png", {4,5,0}, nil, 0.5)
local background = g3d.newModel("assets/sphere.obj", "assets/starfield.png", nil, nil, 500)
local timer = 0

local lightingShader = love.graphics.newShader(g3d.path .. "/g3d.vert", "lighting.frag")

function love.update(dt)
    timer = timer + dt
    moon:setTranslation(math.cos(timer)*5 + 4, math.sin(timer)*5, 0)
    moon:setRotation(0, 0, timer - math.pi/2)
    g3d.camera.firstPersonMovement(dt)
    if love.keyboard.isDown "escape" then
        love.event.push "quit"
    end
end

function love.draw()
    g3d.camera.updateViewMatrix(lightingShader)
    g3d.camera.updateProjectionMatrix(lightingShader)
    earth:draw(lightingShader)
    moon:draw(lightingShader)
    background:draw()
end

function love.mousemoved(x,y, dx,dy)
    g3d.camera.firstPersonLook(dx,dy)
end
grump
Party member
Posts: 947
Joined: Sat Jul 22, 2017 7:43 pm

Re: Groverburger's 3D Engine (g3d) v1.4 Release

Post by grump »

groverburger wrote: Tue Nov 09, 2021 3:15 am

Code: Select all

    vec3 lightDirection = normalize(lightPosition.xyz - worldPosition.xyz);
    vec3 normal = normalize(mat3(modelMatrix) * vertexNormal);
This is a per-vertex calculation. Doing it in the fragment shader is very wasteful. It can be calculated in the vertex shader and the results passed as varyings to the fragment shader.
User avatar
4aiman
Party member
Posts: 262
Joined: Sat Jan 16, 2016 10:30 am

Re: Groverburger's 3D Engine (g3d) v1.4 Release

Post by 4aiman »

@pgimeno
Thanks a ton!
While the debugging yields info that I can't yet decode, it's useful to know how it all works and possibly apply some "universal" GLSL knowledge in the future. For now I can compare shades that are being produced by different "inputs" :awesome:

@groverburger
Thanks another ton! :ultrahappy:
Thanks to pgimeno's input and the example you've provided, I finally started to understand what's going on.
OMG, I wrote a whole bunch of new code only to realize I need to use git version, since the diffuse thing doesn't work in 1.4 release (dot product is always negative).

THANKS!!
I managed to create a fragment shader from scratch using that tutorial! :crazy:

That being said, I think there's a problem with model rotation, as the lighting works fine only if I haven't rotated a model.
Before:

Code: Select all

local earth = g3d.newModel("assets/box.obj", "assets/level1_walls.png", {4,0,0}, {0,0,0}, {1,1,1})
Image

After:

Code: Select all

local earth = g3d.newModel("assets/box.obj", "assets/level1_walls.png", {4,0,0}, {math.pi/2,0,0}, {1,1,1})
Image

EDIT:
Sorry, forgot to post an update.
I wasn't using modelMatrix in my test, hence the problem with the lighting. norm should be calculated like this (just like groverburger does it):

Code: Select all

vec3 norm = normalize(mat3(modelMatrix) * vertexNormal);
Hope this helps.
Attachments
1.5beta.love
(3.74 MiB) Downloaded 415 times
User avatar
4aiman
Party member
Posts: 262
Joined: Sat Jan 16, 2016 10:30 am

Re: Groverburger's 3D Engine (g3d) v1.4 Release

Post by 4aiman »

Not sure where to put this, but I guess w/o g3d this loses any context (plus it might be useful for others?)
Also, sorry for double-posting ( I think it's justified in this case?) and for a long post.

TL;DR
I can't understand this article I'm mentioning below, but I want shadows.

Long story:
I've figured out multiple point light sources with Blinn-Phong fragment shading :awesome: :3 :crazy:
1.5beta_2.love
Blinn-Phong frag shader
(4.76 MiB) Downloaded 435 times
and was ready to get into shadows, but I'm overwhelmed again.

I'm trying to follow this https://learnopengl.com/Advanced-Lighti ... w-Mapping article.
As far as I understand, they're creating a framebuffer for rendering shadows.
Googling for "love2d framebuffer" I found out canvases ARE framebuffers.
So, I've created a canvas, made sure it's "readable" by shaders and got lost..

Code: Select all

local SFBO = love.graphics.newCanvas(1024,1024, {readable=true})
I'm not entirely sure whether I need that explicit "readable" thing, but I did that due to what wiki at https://love2d.org/wiki/Texture:setDepthSampleMode says:
When using a depth texture with a comparison mode set in a shader, it must be declared as a sampler2DShadow and used in a GLSL 3 Shader. The result of accessing the texture in the shader will return a float between 0 and 1, proportional to the number of samples (up to 4 samples will be used if bilinear filtering is enabled) that passed the test set by the comparison operation.
Depth texture comparison can only be used with readable depth-formatted Canvases.
Now, I can't say I comprehend any of what that means.
It doesn't help that I couldn't find any other mention of "sampler2DShadow" on wiki pages and/or how to use it. :cry:
Here https://www.programmerall.com/article/39771418621/ it is said that GLSL's texture is just an Image in love2d.
But I thought I can't draw depthbuffer things to anything but a canvas?
And even if I could, creating a new image every frame seems just #oof.
Especially to copy from a canvas :?

Does it even matter?
Well, I figured that if that would turn out to be irrelevant, I'd really be sorry for not trying to persist.

The vertex shader below (taken from the article I linked) looks like it makes the light source to be the camera. Well, the explanation near the snippet looks that way :emo:

Code: Select all

#version 330 core
layout (location = 0) in vec3 aPos;

uniform mat4 lightSpaceMatrix;
uniform mat4 model;

void main()
{
    gl_Position = lightSpaceMatrix * model * vec4(aPos, 1.0);
}  
So I figured I can use the default g3d.vert shader if the light will come from the camera itself.
I've read about layouts here https://www.khronos.org/opengl/wiki/Lay ... ier_(GLSL) and can't say I got it all.
Am I right that aPos is just "a position" of a light source?

If so, the fragment shader should be my next stop.
They're supplying an empty one.
So, do I need to use anything as the second parameter?
I figured no, I don't seem to have to.
But then, assuming all the above is correct (which is highly improbable), I need to render the scene on a texture.
So.. I rendered my scene onto a canvas..
Only to realize, I did nothing to make it look any different from a standard scene rendered with g3d default shaders :o

So.. what should I change to get away from a normal canvas to a depthbuffer image?

But even if I got some crap as a framebuffer image, how to apply that to a second pass of a shader?

They're using this code:

Code: Select all

#version 330 core
out vec4 FragColor;
in vec2 TexCoords;
uniform sampler2D depthMap;

void main()
{             
    float depthValue = texture(depthMap, TexCoords).r;
    FragColor = vec4(vec3(depthValue), 1.0);
}  
Now, "sampler2D" looks a lot like that "sampler2DShadow" form the wiki. :awesome:
But I can't just

Code: Select all

depthShader:send("depthMap", SFBO)
It yields an error, that my canvas is actually a "love_texture", and that shader can't do anything with it, since "no matching overloaded function found".
I mean it DOES pass it into a shader, but it seems there's no way to cast "love_texture" to "texture".
So..how to properly do it?

I can't even formulate the question correctly. :o:
In general sense I want to draw some point light shadows.
But I can't get even directional light shadows to work.
User avatar
pgimeno
Party member
Posts: 3656
Joined: Sun Oct 18, 2015 2:58 pm

Re: Groverburger's 3D Engine (g3d) v1.4 Release

Post by pgimeno »

I can only help with how to create a depth buffer.

love.graphics.newCanvas accepts a table of parameters, one of which is format, which is of type PixelFormat. Look up that type paying attention to the Depth/Stencil formats. That's what you would use to create a depth buffer. Note also the bit about being non-readable by default, that's where the readable flag comes into play.

I have no idea if the text in setDepthSampleMode applies to this case.
User avatar
4aiman
Party member
Posts: 262
Joined: Sat Jan 16, 2016 10:30 am

Re: Groverburger's 3D Engine (g3d) v1.4 Release

Post by 4aiman »

Thanks, @pgimeno,
Turns out, I needed to use TWO canvases:

Code: Select all

local canvas = love.graphics.newCanvas(1024,1024)
local SFBO = love.graphics.newCanvas(1024,1024, {readable=true, format = "depth24"})
and then

Code: Select all

function love.draw()
    love.graphics.setCanvas({canvas, depth=true, depthstencil=SFBO})
    ...
end
LOVE kept throwing
Depth/stencil format Canvases must be used with the 'depthstencil' field of the table passed into setCanvas.
until I made a canvas with "default" settings (i.e. no options) and a second, SFBO canvas, with "special" ones.

For whomever is watching/reading this drama of mine, I got this clue from the very bottom of https://love2d.org/wiki/love.graphics.setCanvas It, however, doesn't say that all canvases should be equal in size or that LOVE will still throw that depthstencil error is you main canvas was created with depth24 format.

Then, in sfbo.frag I had to change things too.
sampler2D became Image, otherwise
'love_texture' : no matching overloaded function found
happened again.
texture call became Texel call (Thanks to @slime and this topic: https://love2d.org/forums/viewtopic.php?t=86439).
Not sure if that's ok to change types like this, but I haven't found an alternative that would work with canvases sent to shaders.

With that, this is the result:
kindaworks.png
kindaworks.png (150.99 KiB) Viewed 18779 times
Note, that is not the SFBO canvas but the canvas canvas you see in top-left corner.
SFBO canvas is seemingly empty, since when I try to render it, I get a perfectly red square.
The color makes sense, since the depth value is extracted like this (remember, this is from that article, I have no idea if this is how it should work):

Code: Select all

float depthValue = Texel(depthMap, TexCoords).r;
Which ought to mean red channel is used for depth, but apparently it isn't (or I've messed up again :x )

Edit:
On closer inspection, that red square isn't uniform in color:
kindaworks2.png
kindaworks2.png (1.47 KiB) Viewed 18776 times
I'll see where this goes then.

@groverburger
Not sure I should continue to post here, since this is some kind of hijacking at this point - closely-related, but not on topic :(
My intention here is to make g3d better by porting lighting stuff into it, but I can see how these walls of text can be seen as a separate thing.
Attachments
1.5beta_3.zip.love
(4.76 MiB) Downloaded 417 times
User avatar
Jasoco
Inner party member
Posts: 3726
Joined: Mon Jun 22, 2009 9:35 am
Location: Pennsylvania, USA
Contact:

Re: Groverburger's 3D Engine (g3d) v1.4 Release

Post by Jasoco »

I like any discussion that helps make g3d better to use. I started using it myself and would love to look into adding in stuff like lighting and shadows. I'm not all super smart on all this shader stuff but seeing examples makes me happy that I could do this stuff too if I really put my mind to it. It just gets confusing when I want to stack shaders on top of shaders. I already have a fog shader from another user who added it for their FPS and it works great but when I tried to also implement the diffuse lighting from groverburgers post above it complained about missing variables (Since the variables were for the fog shader) and when I got it running, the lighting wasn't showing up anywhere. So I guess I have work to do. I'll look at the Hoarder game and it has simple shadows which I'd love to use. Then I look at that flame runner demo and it has neat lighting effects I'd also love to implement. Also a water shader. But there's so much code! lol

I say post anything that has to do directly with making g3d better. I know I'll be asking questions in time.
User avatar
4aiman
Party member
Posts: 262
Joined: Sat Jan 16, 2016 10:30 am

Re: Groverburger's 3D Engine (g3d) v1.4 Release

Post by 4aiman »

Missing variables are those which are declared but not used.
Say, you have a LIGHT_STRENGTH uniform.
Even if you assign something to it, or even if you calculate some values using it, but won't actually use those to get the resulting value, then it'll get dismissed from a shader. GPU doesn't care for something that won't be used in the end.

I think you can go with a diffuse shading if you attach a pointlight to your camera. The "fog" will be there "automatically".
Then ambient lighting can help to colorize things in that "fog".
It won't look the same, but worth a try. (see attachment)

"Combining" shaders can be tricky, but, basically, you apply them in succession.
There's no need to worry about clipping.
Maybe just wrap each of them into a separate function and then do

Code: Select all

result = shader1 + shader2
That's, basically, how it's done for multiple light sources:

Code: Select all

    for (int i=0; i<LIGHTS; i++)
        result += CalcPointLight(pointLights[i], norm, worldPosition.xyz, viewDir, objectColor, viewPos.xyz);
Where CalcPointLight is almost Identical to the "main" shader function, but processes different light sources.

How many of those do you need?
Well, either declare a huge array (and risk getting over the limit - see further) or do something like this to get a precise number:

Code: Select all

shader = shader:gsub("#define LIGHTS <LIGHTS_COUNT>", "#define LIGHTS ".. point_light_count) 	
Good for testing, but later you might need to lower the count and somehow light up only those which are near (that means sending new stuff to shader every frame, accounting for a current position)

If you can't see anything at all - try adding ambient lighting first, deleting anything that's diffuse-related, and see if that works.
Then add some light sources when it does.

If it doesn't, check normals on your *.obj files.
In my case, I opted for double-sided lighting, changing

Code: Select all

float diffuse = max(dot(lightDirection, normal), 0);
to

Code: Select all

float diffuse = abs(dot(lightDirection, normal));
This won't suit just anyone, but will help to see stuff with wrong normals w/o messing with Blender (or any other 3d-editor) or using model:makeNormals(true/false).

Also, note, that there's a limit on uniforms count in your shader (depends on your GPU/driver I guess? For my GTX1660S it's 1024).

Code: Select all

Error
Cannot link shader program object:
Fragment info
-------------
(0) : error C6020: Constant register limit exceeded at lightColor; more than 1024 registers needed to compile program
(0) : error C6020: Constant register limit exceeded at viewPos; more than 1024 registers needed to compile program
(0) : error C6020: Constant register limit exceeded at torchStrength; more than 1024 registers needed to compile program
So, if your light sources are all of the same strength, then maybe don't add that 3rd field into your structs :)
Attachments
here's how it looks
here's how it looks
fog.jpg (171.11 KiB) Viewed 15961 times
User avatar
Jasoco
Inner party member
Posts: 3726
Joined: Mon Jun 22, 2009 9:35 am
Location: Pennsylvania, USA
Contact:

Re: Groverburger's 3D Engine (g3d) v1.4 Release

Post by Jasoco »

I have an issue I'm trying to understand. Is there any way to fix this?



Notice how the trees closest to the camera obscure trees behind it in the transparent pixel areas of the image. But weirdly not all trees. And not all closest trees. Some of the close trees obscure trees way in the back but not ones in the middle. I don't get why it does this. Is there any way to properly do "2d sprites in a 3d world" that won't obscure other sprites or models in the world?

Note: In this demo, the terrain is one model, the water a single flat model, the skybox is one model and each tree is a separate individual flat model.
Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest